From b37e19fb01852ee72ea12f9e4d91ed30e8a774af Mon Sep 17 00:00:00 2001 From: Manus Date: Thu, 21 May 2026 20:29:58 +0000 Subject: [PATCH] =?UTF-8?q?Checkpoint:=20Ajout=20de=20la=20page=20Annuaire?= =?UTF-8?q?=20du=20personnel=20=E2=80=94=2024=20agents=20fictifs=20r=C3=A9?= =?UTF-8?q?partis=20sur=2014=20services,=20barre=20de=20recherche=20full-t?= =?UTF-8?q?ext,=20filtres=20par=20service=20(select=20+=20pastilles=20rapi?= =?UTF-8?q?des)=20et=20par=20disponibilit=C3=A9,=20vue=20grille=20et=20vue?= =?UTF-8?q?=20liste,=20modale=20fiche=20agent=20avec=20coordonn=C3=A9es=20?= =?UTF-8?q?compl=C3=A8tes.=20Accessible=20depuis=20la=20sidebar=20admin=20?= =?UTF-8?q?(/annuaire)=20et=20depuis=20le=20bouton=20"Ouvrir=20l'annuaire?= =?UTF-8?q?=20complet"=20dans=20la=20vue=20publique=20de=20l'intranet.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/App.tsx | 2 + client/src/components/AdminLayout.tsx | 2 + client/src/pages/Annuaire.tsx | 511 ++++++++++++++++++++++++++ client/src/pages/IntranetPublic.tsx | 2 +- 4 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 client/src/pages/Annuaire.tsx 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)} /> + ))} +
+ ) : ( +
+ + + + + + + + + + + + + {personnelFiltre.map((agent) => ( + setAgentSelectionne(agent)} /> + ))} + +
AgentServicePosteE-mailBureauStatut
+
+ )} + + {/* Modale fiche agent */} + setAgentSelectionne(null)}> + {agentSelectionne && ( + + + Fiche agent + +
+ {/* En-tête fiche */} +
+
+ + + {agentSelectionne.initiales} + + + +
+
+

+ {agentSelectionne.prenom} {agentSelectionne.nom} +

+

{agentSelectionne.poste}

+ + + {agentSelectionne.disponible ? "Disponible" : "Absent(e)"} + +
+
+ + {/* Service */} + {(() => { + const s = SERVICES.find((sv) => sv.id === agentSelectionne.service); + return s ? ( +
+ + {s.label} +
+ ) : null; + })()} + + {/* Coordonnées */} +
+ + +
+
+ +
+
+

Bureau

+

{agentSelectionne.bureau}

+
+
+
+
+
+ )} +
+
+ ); +} 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() { ))} -