diff --git a/client/src/App.tsx b/client/src/App.tsx
index 7eef07b..cced38f 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -17,6 +17,7 @@ import Notifications from "./pages/Notifications";
import Roles from "./pages/Roles";
import Parametres from "./pages/Parametres";
import EditeurPage from "./pages/EditeurPage";
+import Annuaire from "./pages/Annuaire";
// Pages publiques (sans AdminLayout)
import Connexion from "./pages/Connexion";
@@ -34,6 +35,7 @@ function AdminRouter() {
+
diff --git a/client/src/components/AdminLayout.tsx b/client/src/components/AdminLayout.tsx
index 8d1a5fe..ad4afd3 100644
--- a/client/src/components/AdminLayout.tsx
+++ b/client/src/components/AdminLayout.tsx
@@ -22,6 +22,7 @@ import {
LogOut,
ExternalLink,
User,
+ BookUser,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -41,6 +42,7 @@ const NAV_ITEMS = [
{ icon: Puzzle, label: "Widgets", path: "/widgets" },
{ icon: Globe, label: "Applications", path: "/applications" },
{ icon: Users, label: "Utilisateurs", path: "/utilisateurs" },
+ { icon: BookUser, label: "Annuaire", path: "/annuaire" },
{ icon: BarChart3, label: "Statistiques", path: "/statistiques" },
{ icon: Bell, label: "Notifications", path: "/notifications" },
{ icon: Shield, label: "Rôles et droits", path: "/roles" },
diff --git a/client/src/pages/Annuaire.tsx b/client/src/pages/Annuaire.tsx
new file mode 100644
index 0000000..18d784f
--- /dev/null
+++ b/client/src/pages/Annuaire.tsx
@@ -0,0 +1,511 @@
+/**
+ * Page Annuaire du personnel — Intranet CHK
+ * Recherche par nom/prénom/poste, filtres par service, vue carte et liste
+ */
+import { useState, useMemo } from "react";
+import { Input } from "@/components/ui/input";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Avatar, AvatarFallback } from "@/components/ui/avatar";
+import { Card, CardContent } from "@/components/ui/card";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import {
+ Search,
+ Phone,
+ Mail,
+ MapPin,
+ LayoutGrid,
+ List,
+ Users,
+ Filter,
+ X,
+ ChevronRight,
+ Building2,
+ Stethoscope,
+ Wrench,
+ BookOpen,
+ Shield,
+ HeartPulse,
+ FlaskConical,
+ Pill,
+ Baby,
+ Ambulance,
+ Scan,
+ Utensils,
+ Landmark,
+} from "lucide-react";
+import { toast } from "sonner";
+
+// ─── Données du personnel ────────────────────────────────────────────────────
+
+const SERVICES = [
+ { id: "tous", label: "Tous les services", icone: Users, couleur: "bg-slate-100 text-slate-700" },
+ { id: "direction", label: "Direction", icone: Landmark, couleur: "bg-violet-100 text-violet-700" },
+ { id: "urgences", label: "Urgences", icone: Ambulance, couleur: "bg-red-100 text-red-700" },
+ { id: "medecine", label: "Médecine générale", icone: Stethoscope, couleur: "bg-blue-100 text-blue-700" },
+ { id: "chirurgie", label: "Chirurgie", icone: HeartPulse, couleur: "bg-pink-100 text-pink-700" },
+ { id: "pediatrie", label: "Pédiatrie", icone: Baby, couleur: "bg-yellow-100 text-yellow-700" },
+ { id: "imagerie", label: "Imagerie médicale", icone: Scan, couleur: "bg-cyan-100 text-cyan-700" },
+ { id: "laboratoire", label: "Laboratoire", icone: FlaskConical,couleur: "bg-emerald-100 text-emerald-700" },
+ { id: "pharmacie", label: "Pharmacie", icone: Pill, couleur: "bg-orange-100 text-orange-700" },
+ { id: "dsi", label: "DSI / Informatique", icone: Wrench, couleur: "bg-indigo-100 text-indigo-700" },
+ { id: "rh", label: "Ressources humaines", icone: Shield, couleur: "bg-teal-100 text-teal-700" },
+ { id: "formation", label: "Formation", icone: BookOpen, couleur: "bg-lime-100 text-lime-700" },
+ { id: "logistique", label: "Logistique", icone: Building2, couleur: "bg-amber-100 text-amber-700" },
+ { id: "restauration",label: "Restauration", icone: Utensils, couleur: "bg-rose-100 text-rose-700" },
+];
+
+interface Agent {
+ id: number;
+ prenom: string;
+ nom: string;
+ poste: string;
+ service: string;
+ telephone: string;
+ email: string;
+ bureau: string;
+ disponible: boolean;
+ initiales: string;
+ couleurAvatar: string;
+}
+
+const PERSONNEL: Agent[] = [
+ { id: 1, prenom: "Pierre", nom: "Boursiquot", poste: "Directeur des systèmes d'information", service: "dsi", telephone: "4242", email: "p.boursiquot@chk.gf", bureau: "Bât. Admin — Bureau 201", disponible: true, initiales: "PB", couleurAvatar: "#1a5276" },
+ { id: 2, prenom: "Marie", nom: "Dupont", poste: "Directrice générale", service: "direction", telephone: "4000", email: "m.dupont@chk.gf", bureau: "Bât. Admin — Bureau 100", disponible: true, initiales: "MD", couleurAvatar: "#6c3483" },
+ { id: 3, prenom: "Jean-Luc", nom: "Martin", poste: "Chef de service — Urgences", service: "urgences", telephone: "4100", email: "jl.martin@chk.gf", bureau: "Urgences — Poste central",disponible: true, initiales: "JM", couleurAvatar: "#c0392b" },
+ { id: 4, prenom: "Sophie", nom: "Lebrun", poste: "Infirmière coordinatrice", service: "urgences", telephone: "4101", email: "s.lebrun@chk.gf", bureau: "Urgences — Salle de soins",disponible: false, initiales: "SL", couleurAvatar: "#e74c3c" },
+ { id: 5, prenom: "Thierry", nom: "Cazal", poste: "Médecin généraliste", service: "medecine", telephone: "4200", email: "t.cazal@chk.gf", bureau: "Médecine — Bureau 12", disponible: true, initiales: "TC", couleurAvatar: "#2980b9" },
+ { id: 6, prenom: "Amandine", nom: "Rivière", poste: "Chirurgien orthopédiste", service: "chirurgie", telephone: "4300", email: "a.riviere@chk.gf", bureau: "Chirurgie — Bloc A", disponible: true, initiales: "AR", couleurAvatar: "#c0392b" },
+ { id: 7, prenom: "Karim", nom: "Benmoussa", poste: "Pédiatre", service: "pediatrie", telephone: "4400", email: "k.benmoussa@chk.gf", bureau: "Pédiatrie — Bureau 5", disponible: true, initiales: "KB", couleurAvatar: "#f39c12" },
+ { id: 8, prenom: "Lucie", nom: "Fontaine", poste: "Infirmière puéricultrice", service: "pediatrie", telephone: "4401", email: "l.fontaine@chk.gf", bureau: "Pédiatrie — Salle 3", disponible: false, initiales: "LF", couleurAvatar: "#e67e22" },
+ { id: 9, prenom: "Éric", nom: "Moreau", poste: "Radiologue", service: "imagerie", telephone: "4500", email: "e.moreau@chk.gf", bureau: "Imagerie — Salle IRM", disponible: true, initiales: "EM", couleurAvatar: "#0e6655" },
+ { id: 10, prenom: "Nathalie", nom: "Guérin", poste: "Technicienne de radiologie", service: "imagerie", telephone: "4501", email: "n.guerin@chk.gf", bureau: "Imagerie — Scanner", disponible: true, initiales: "NG", couleurAvatar: "#117a65" },
+ { id: 11, prenom: "Pascal", nom: "Théodore", poste: "Biologiste médical", service: "laboratoire", telephone: "4600", email: "p.theodore@chk.gf", bureau: "Labo — Bureau principal", disponible: true, initiales: "PT", couleurAvatar: "#1e8449" },
+ { id: 12, prenom: "Isabelle", nom: "Noël", poste: "Technicienne de laboratoire", service: "laboratoire", telephone: "4601", email: "i.noel@chk.gf", bureau: "Labo — Salle d'analyses", disponible: false, initiales: "IN", couleurAvatar: "#27ae60" },
+ { id: 13, prenom: "Franck", nom: "Delorme", poste: "Pharmacien hospitalier", service: "pharmacie", telephone: "4700", email: "f.delorme@chk.gf", bureau: "Pharmacie — Comptoir", disponible: true, initiales: "FD", couleurAvatar: "#d35400" },
+ { id: 14, prenom: "Céline", nom: "Aubert", poste: "Préparatrice en pharmacie", service: "pharmacie", telephone: "4701", email: "c.aubert@chk.gf", bureau: "Pharmacie — Réserve", disponible: true, initiales: "CA", couleurAvatar: "#e67e22" },
+ { id: 15, prenom: "Marc", nom: "Loiseau", poste: "Technicien informatique", service: "dsi", telephone: "4243", email: "m.loiseau@chk.gf", bureau: "DSI — Salle serveurs", disponible: true, initiales: "ML", couleurAvatar: "#2471a3" },
+ { id: 16, prenom: "Valérie", nom: "Petit", poste: "Responsable RH", service: "rh", telephone: "4800", email: "v.petit@chk.gf", bureau: "RH — Bureau 302", disponible: true, initiales: "VP", couleurAvatar: "#148f77" },
+ { id: 17, prenom: "Olivier", nom: "Charron", poste: "Chargé de formation", service: "formation", telephone: "4900", email: "o.charron@chk.gf", bureau: "Formation — Salle 1", disponible: false, initiales: "OC", couleurAvatar: "#7d6608" },
+ { id: 18, prenom: "Sandra", nom: "Beaumont", poste: "Responsable logistique", service: "logistique", telephone: "5000", email: "s.beaumont@chk.gf", bureau: "Logistique — Entrepôt", disponible: true, initiales: "SB", couleurAvatar: "#784212" },
+ { id: 19, prenom: "René", nom: "Chabrier", poste: "Chef cuisinier", service: "restauration", telephone: "5100", email: "r.chabrier@chk.gf", bureau: "Restauration — Cuisine", disponible: true, initiales: "RC", couleurAvatar: "#922b21" },
+ { id: 20, prenom: "Hélène", nom: "Voisin", poste: "Directrice des soins infirmiers", service: "direction", telephone: "4001", email: "h.voisin@chk.gf", bureau: "Bât. Admin — Bureau 102", disponible: true, initiales: "HV", couleurAvatar: "#7d3c98" },
+ { id: 21, prenom: "Antoine", nom: "Sainte-Rose", poste: "Médecin urgentiste", service: "urgences", telephone: "4102", email: "a.sainteRose@chk.gf", bureau: "Urgences — SAMU", disponible: true, initiales: "AS", couleurAvatar: "#a93226" },
+ { id: 22, prenom: "Marlène", nom: "Joachim", poste: "Aide-soignante", service: "medecine", telephone: "4201", email: "m.joachim@chk.gf", bureau: "Médecine — Couloir B", disponible: false, initiales: "MJ", couleurAvatar: "#1f618d" },
+ { id: 23, prenom: "Bertrand", nom: "Quentin", poste: "Anesthésiste-réanimateur", service: "chirurgie", telephone: "4301", email: "b.quentin@chk.gf", bureau: "Chirurgie — Bloc B", disponible: true, initiales: "BQ", couleurAvatar: "#943126" },
+ { id: 24, prenom: "Laure", nom: "Ménil", poste: "Manipulatrice en électroradiologie", service: "imagerie", telephone: "4502", email: "l.menil@chk.gf", bureau: "Imagerie — Radiologie", disponible: true, initiales: "LM", couleurAvatar: "#0b6e4f" },
+];
+
+// ─── Composant fiche agent ────────────────────────────────────────────────────
+
+function CarteAgent({ agent, onClick }: { agent: Agent; onClick: () => void }) {
+ const service = SERVICES.find((s) => s.id === agent.service);
+ return (
+
+
+
+
+
+
+ {agent.initiales}
+
+
+
+
+
+
+ {agent.prenom} {agent.nom}
+
+
{agent.poste}
+ {service && (
+
+ {service.label}
+
+ )}
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function LigneAgent({ agent, onClick }: { agent: Agent; onClick: () => void }) {
+ const service = SERVICES.find((s) => s.id === agent.service);
+ return (
+
+
+
+
+
+
+ {agent.initiales}
+
+
+
+
+
+
+ {agent.prenom} {agent.nom}
+
+ {agent.poste}
+
+
+ |
+
+ {service && (
+
+ {service.label}
+
+ )}
+ |
+
+
+ |
+
+
+ |
+
+
+ {agent.bureau}
+
+ |
+
+
+
+ {agent.disponible ? "Disponible" : "Absent(e)"}
+
+ |
+
+ );
+}
+
+// ─── Composant principal ─────────────────────────────────────────────────────
+
+export default function Annuaire() {
+ const [recherche, setRecherche] = useState("");
+ const [serviceActif, setServiceActif] = useState("tous");
+ const [disponibilite, setDisponibilite] = useState("tous");
+ const [vue, setVue] = useState<"grille" | "liste">("grille");
+ const [agentSelectionne, setAgentSelectionne] = useState(null);
+
+ const personnelFiltre = useMemo(() => {
+ return PERSONNEL.filter((agent) => {
+ const terme = recherche.toLowerCase();
+ const correspondRecherche =
+ !terme ||
+ agent.prenom.toLowerCase().includes(terme) ||
+ agent.nom.toLowerCase().includes(terme) ||
+ agent.poste.toLowerCase().includes(terme) ||
+ agent.email.toLowerCase().includes(terme);
+ const correspondService = serviceActif === "tous" || agent.service === serviceActif;
+ const correspondDispo =
+ disponibilite === "tous" ||
+ (disponibilite === "disponible" && agent.disponible) ||
+ (disponibilite === "absent" && !agent.disponible);
+ return correspondRecherche && correspondService && correspondDispo;
+ });
+ }, [recherche, serviceActif, disponibilite]);
+
+ const serviceInfo = SERVICES.find((s) => s.id === serviceActif);
+ const nbDisponibles = personnelFiltre.filter((a) => a.disponible).length;
+
+ return (
+
+ {/* En-tête */}
+
+
+
Annuaire du personnel
+
+ {personnelFiltre.length} agent{personnelFiltre.length > 1 ? "s" : ""} trouvé{personnelFiltre.length > 1 ? "s" : ""}
+ {" "}— {nbDisponibles} disponible{nbDisponibles > 1 ? "s" : ""}
+
+
+
+
+
+
+
+
+ {/* Barre de recherche + filtres */}
+
+ {/* Recherche */}
+
+
+ setRecherche(e.target.value)}
+ />
+ {recherche && (
+
+ )}
+
+
+ {/* Filtres */}
+
+
+
+
+
+
+ {(serviceActif !== "tous" || disponibilite !== "tous" || recherche) && (
+
+ )}
+
+
+ {/* Pastilles de services rapides */}
+
+ {SERVICES.slice(1, 9).map((s) => (
+
+ ))}
+
+
+
+ {/* Résultats */}
+ {personnelFiltre.length === 0 ? (
+
+
+
Aucun agent trouvé
+
Essayez de modifier vos critères de recherche.
+
+ ) : vue === "grille" ? (
+
+ {personnelFiltre.map((agent) => (
+ setAgentSelectionne(agent)} />
+ ))}
+
+ ) : (
+
+
+
+
+ | Agent |
+ Service |
+ Poste |
+ E-mail |
+ Bureau |
+ Statut |
+
+
+
+ {personnelFiltre.map((agent) => (
+ setAgentSelectionne(agent)} />
+ ))}
+
+
+
+ )}
+
+ {/* Modale fiche agent */}
+
+
+ );
+}
diff --git a/client/src/pages/IntranetPublic.tsx b/client/src/pages/IntranetPublic.tsx
index 8d6142e..0c795ee 100644
--- a/client/src/pages/IntranetPublic.tsx
+++ b/client/src/pages/IntranetPublic.tsx
@@ -342,7 +342,7 @@ export default function IntranetPublic() {
))}
-