CognxSafeTrack commited on
Commit
fe40cec
·
1 Parent(s): 0aedaaf

ux: clear messages for tenant selection in admin dashboard

Browse files
apps/admin/src/pages/DashboardPage.tsx CHANGED
@@ -1,18 +1,29 @@
1
  import { useEffect, useState } from 'react';
2
- import { Users, PlayCircle, CheckCircle, Lightbulb, DollarSign, Download } from 'lucide-react';
3
  import { useAuth } from '../lib/auth';
 
4
  import { API_URL } from '../lib/api';
5
 
6
  export default function DashboardPage() {
7
  const { apiKey, logout } = useAuth();
 
8
  const [stats, setStats] = useState<any>(null);
9
  const [enrollments, setEnrollments] = useState<any[]>([]);
10
  const [loading, setLoading] = useState(true);
11
 
12
  useEffect(() => {
 
 
 
 
 
13
  (async () => {
 
14
  try {
15
- const h = { 'Authorization': `Bearer ${apiKey}` };
 
 
 
16
  const [sRes, eRes] = await Promise.all([
17
  fetch(`${API_URL}/v1/admin/stats`, { headers: h }),
18
  fetch(`${API_URL}/v1/admin/enrollments`, { headers: h })
@@ -22,7 +33,7 @@ export default function DashboardPage() {
22
  setEnrollments(await eRes.json());
23
  } finally { setLoading(false); }
24
  })();
25
- }, [apiKey, logout]);
26
 
27
  const exportCSV = () => {
28
  if (!enrollments.length) return alert('Aucune inscription.');
@@ -34,7 +45,29 @@ export default function DashboardPage() {
34
  a.click();
35
  };
36
 
37
- if (loading) return <div className="p-8 text-slate-400">Chargement...</div>;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  const statCards = [
40
  { icon: <Users className="w-6 h-6 text-slate-400" />, label: 'Utilisateurs', value: stats?.totalUsers || 0, color: 'text-slate-900' },
 
1
  import { useEffect, useState } from 'react';
2
+ import { Users, PlayCircle, CheckCircle, Lightbulb, DollarSign, Download, Building2, Loader2 } from 'lucide-react';
3
  import { useAuth } from '../lib/auth';
4
+ import { useTenant } from '../lib/tenant';
5
  import { API_URL } from '../lib/api';
6
 
7
  export default function DashboardPage() {
8
  const { apiKey, logout } = useAuth();
9
+ const { selectedOrgId } = useTenant();
10
  const [stats, setStats] = useState<any>(null);
11
  const [enrollments, setEnrollments] = useState<any[]>([]);
12
  const [loading, setLoading] = useState(true);
13
 
14
  useEffect(() => {
15
+ if (!selectedOrgId) {
16
+ setLoading(false);
17
+ return;
18
+ }
19
+
20
  (async () => {
21
+ setLoading(true);
22
  try {
23
+ const h = {
24
+ 'Authorization': `Bearer ${apiKey}`,
25
+ 'x-organization-id': selectedOrgId
26
+ };
27
  const [sRes, eRes] = await Promise.all([
28
  fetch(`${API_URL}/v1/admin/stats`, { headers: h }),
29
  fetch(`${API_URL}/v1/admin/enrollments`, { headers: h })
 
33
  setEnrollments(await eRes.json());
34
  } finally { setLoading(false); }
35
  })();
36
+ }, [apiKey, logout, selectedOrgId]);
37
 
38
  const exportCSV = () => {
39
  if (!enrollments.length) return alert('Aucune inscription.');
 
45
  a.click();
46
  };
47
 
48
+ if (!selectedOrgId) {
49
+ return (
50
+ <div className="flex flex-col items-center justify-center min-h-[80vh] text-slate-400">
51
+ <Building2 className="w-16 h-16 mb-6 opacity-20" />
52
+ <h2 className="text-2xl font-bold text-slate-900">Bienvenue sur EdTech Admin</h2>
53
+ <p className="max-w-md text-center mt-3 text-lg">
54
+ Pour commencer, veuillez sélectionner une **organisation** dans le menu déroulant à gauche.
55
+ </p>
56
+ <div className="mt-8 p-4 bg-blue-50 text-blue-700 rounded-2xl text-sm font-medium border border-blue-100">
57
+ 💡 L'isolation des données garantit que vous ne voyez que les statistiques de l'organisation active.
58
+ </div>
59
+ </div>
60
+ );
61
+ }
62
+
63
+ if (loading) {
64
+ return (
65
+ <div className="flex flex-col items-center justify-center min-h-[80vh] text-slate-400">
66
+ <Loader2 className="w-10 h-10 animate-spin mb-4 text-slate-900" />
67
+ <p className="text-lg font-medium">Analyse des données en cours...</p>
68
+ </div>
69
+ );
70
+ }
71
 
72
  const statCards = [
73
  { icon: <Users className="w-6 h-6 text-slate-400" />, label: 'Utilisateurs', value: stats?.totalUsers || 0, color: 'text-slate-900' },
apps/admin/src/pages/TrackListPage.tsx CHANGED
@@ -1,6 +1,6 @@
1
  import { useEffect, useState } from 'react';
2
  import { useNavigate } from 'react-router-dom';
3
- import { BookOpen, Plus, Edit2, Trash2, ChevronRight } from 'lucide-react';
4
  import { useAuth } from '../lib/auth';
5
  import { useTenant } from '../lib/tenant';
6
  import { API_URL } from '../lib/api';
@@ -36,7 +36,24 @@ export default function TrackListPage() {
36
  load();
37
  };
38
 
39
- if (loading) return <div className="p-8 text-slate-400">Chargement...</div>;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  return (
42
  <div className="p-8">
 
1
  import { useEffect, useState } from 'react';
2
  import { useNavigate } from 'react-router-dom';
3
+ import { BookOpen, Plus, Edit2, Trash2, ChevronRight, Building2 } from 'lucide-react';
4
  import { useAuth } from '../lib/auth';
5
  import { useTenant } from '../lib/tenant';
6
  import { API_URL } from '../lib/api';
 
36
  load();
37
  };
38
 
39
+ if (!selectedOrgId) {
40
+ return (
41
+ <div className="flex flex-col items-center justify-center min-h-[60vh] text-slate-400">
42
+ <Building2 className="w-12 h-12 mb-4 opacity-20" />
43
+ <h3 className="text-lg font-bold text-slate-900">Aucune organisation sélectionnée</h3>
44
+ <p className="max-w-xs text-center mt-2">Veuillez sélectionner une organisation dans le menu en haut à gauche pour voir ses parcours.</p>
45
+ </div>
46
+ );
47
+ }
48
+
49
+ if (loading) {
50
+ return (
51
+ <div className="flex flex-col items-center justify-center min-h-[60vh] text-slate-400">
52
+ <div className="w-8 h-8 border-4 border-slate-200 border-t-slate-900 rounded-full animate-spin mb-4"></div>
53
+ <p>Chargement des parcours...</p>
54
+ </div>
55
+ );
56
+ }
57
 
58
  return (
59
  <div className="p-8">
apps/admin/src/pages/UserListPage.tsx CHANGED
@@ -1,10 +1,12 @@
1
  import { useEffect, useState } from 'react';
2
- import { X } from 'lucide-react';
3
  import { useAuth } from '../lib/auth';
 
4
  import { API_URL } from '../lib/api';
5
 
6
  export default function UserListPage() {
7
  const { apiKey } = useAuth();
 
8
  const [users, setUsers] = useState<any[]>([]);
9
  const [total, setTotal] = useState(0);
10
  const [loading, setLoading] = useState(true);
@@ -12,9 +14,18 @@ export default function UserListPage() {
12
  const [messages, setMessages] = useState<any[]>([]);
13
  const [loadingMsg, setLoadingMsg] = useState(false);
14
 
15
- const ah = (k: string) => ({ 'Authorization': `Bearer ${k}`, 'Content-Type': 'application/json' });
 
 
 
 
16
 
17
  useEffect(() => {
 
 
 
 
 
18
  fetch(`${API_URL}/v1/admin/users`, { headers: ah(apiKey!) })
19
  .then(r => r.json())
20
  .then(d => {
@@ -22,7 +33,7 @@ export default function UserListPage() {
22
  setTotal(d.total || 0);
23
  setLoading(false);
24
  });
25
- }, [apiKey]);
26
 
27
  const viewMessages = async (userId: string) => {
28
  setLoadingMsg(true);
@@ -39,7 +50,24 @@ export default function UserListPage() {
39
  }
40
  };
41
 
42
- if (loading) return <div className="p-8 text-slate-400">Chargement...</div>;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
  return (
45
  <div className="p-8">
 
1
  import { useEffect, useState } from 'react';
2
+ import { X, Building2, Loader2 } from 'lucide-react';
3
  import { useAuth } from '../lib/auth';
4
+ import { useTenant } from '../lib/tenant';
5
  import { API_URL } from '../lib/api';
6
 
7
  export default function UserListPage() {
8
  const { apiKey } = useAuth();
9
+ const { selectedOrgId } = useTenant();
10
  const [users, setUsers] = useState<any[]>([]);
11
  const [total, setTotal] = useState(0);
12
  const [loading, setLoading] = useState(true);
 
14
  const [messages, setMessages] = useState<any[]>([]);
15
  const [loadingMsg, setLoadingMsg] = useState(false);
16
 
17
+ const ah = (k: string) => ({
18
+ 'Authorization': `Bearer ${k}`,
19
+ 'Content-Type': 'application/json',
20
+ ...(selectedOrgId ? { 'x-organization-id': selectedOrgId } : {})
21
+ });
22
 
23
  useEffect(() => {
24
+ if (!selectedOrgId) {
25
+ setLoading(false);
26
+ return;
27
+ }
28
+ setLoading(true);
29
  fetch(`${API_URL}/v1/admin/users`, { headers: ah(apiKey!) })
30
  .then(r => r.json())
31
  .then(d => {
 
33
  setTotal(d.total || 0);
34
  setLoading(false);
35
  });
36
+ }, [apiKey, selectedOrgId]);
37
 
38
  const viewMessages = async (userId: string) => {
39
  setLoadingMsg(true);
 
50
  }
51
  };
52
 
53
+ if (!selectedOrgId) {
54
+ return (
55
+ <div className="flex flex-col items-center justify-center min-h-[60vh] text-slate-400">
56
+ <Building2 className="w-12 h-12 mb-4 opacity-20" />
57
+ <h3 className="text-lg font-bold text-slate-900">Aucune organisation sélectionnée</h3>
58
+ <p className="max-w-xs text-center mt-2">Sélectionnez une organisation pour gérer ses utilisateurs.</p>
59
+ </div>
60
+ );
61
+ }
62
+
63
+ if (loading) {
64
+ return (
65
+ <div className="flex flex-col items-center justify-center min-h-[60vh] text-slate-400">
66
+ <Loader2 className="w-8 h-8 animate-spin mb-4 text-slate-900" />
67
+ <p>Chargement des utilisateurs...</p>
68
+ </div>
69
+ );
70
+ }
71
 
72
  return (
73
  <div className="p-8">