+ {/* Fond semi-transparent sur mobile */}
+ {mobileMenuOpen && (
+
setMobileMenuOpen(false)}
+ />
+ )}
+
+ {/* Barre latérale */}
+
+ {/* Zone logo */}
+
+
+ +
+
+ {sidebarOpen && (
+
+
+ CHK Intranet
+
+
+ Administration
+
+
+ )}
+
+
+ {/* Navigation */}
+
+ {NAV_ITEMS.map((item) => {
+ const isActive =
+ location === item.path ||
+ (item.path !== "/" && location.startsWith(item.path));
+ return (
+
+
+ {isActive && (
+
+ )}
+
+ {sidebarOpen && (
+
{item.label}
+ )}
+
+
+ );
+ })}
+
+
+ {/* Pied de la barre latérale */}
+
+ setSidebarOpen(!sidebarOpen)}
+ className="hidden lg:flex items-center justify-center w-full py-2 rounded-md text-sidebar-foreground/50 hover:text-sidebar-foreground hover:bg-sidebar-accent/50 transition-colors"
+ title={sidebarOpen ? "Réduire le menu" : "Agrandir le menu"}
+ >
+
+
+
+
+
+ {/* Zone de contenu principale */}
+
+ {/* En-tête supérieur */}
+
+
+ {/* Contenu de la page */}
+
+ {children}
+
+
+
+ );
+}
diff --git a/client/src/index.css b/client/src/index.css
index 72b423d..4acfc6e 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -40,77 +40,46 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
+ --font-sans: 'Inter', system-ui, sans-serif;
+ --font-heading: 'Outfit', system-ui, sans-serif;
}
:root {
- --primary: var(--color-blue-700);
- --primary-foreground: var(--color-blue-50);
- --sidebar-primary: var(--color-blue-600);
- --sidebar-primary-foreground: var(--color-blue-50);
- --chart-1: var(--color-blue-300);
- --chart-2: var(--color-blue-500);
- --chart-3: var(--color-blue-600);
- --chart-4: var(--color-blue-700);
- --chart-5: var(--color-blue-800);
- --radius: 0.65rem;
- --background: oklch(1 0 0);
- --foreground: oklch(0.235 0.015 65);
+ --radius: 0.5rem;
+ /* CHK Medical Command Center Theme - Light */
+ --background: oklch(0.98 0.002 240);
+ --foreground: oklch(0.15 0.02 250);
--card: oklch(1 0 0);
- --card-foreground: oklch(0.235 0.015 65);
+ --card-foreground: oklch(0.15 0.02 250);
--popover: oklch(1 0 0);
- --popover-foreground: oklch(0.235 0.015 65);
- --secondary: oklch(0.98 0.001 286.375);
- --secondary-foreground: oklch(0.4 0.015 65);
- --muted: oklch(0.967 0.001 286.375);
- --muted-foreground: oklch(0.552 0.016 285.938);
- --accent: oklch(0.967 0.001 286.375);
- --accent-foreground: oklch(0.141 0.005 285.823);
- --destructive: oklch(0.577 0.245 27.325);
- --destructive-foreground: oklch(0.985 0 0);
- --border: oklch(0.92 0.004 286.32);
- --input: oklch(0.92 0.004 286.32);
- --ring: oklch(0.623 0.214 259.815);
- --sidebar: oklch(0.985 0 0);
- --sidebar-foreground: oklch(0.235 0.015 65);
- --sidebar-accent: oklch(0.967 0.001 286.375);
- --sidebar-accent-foreground: oklch(0.141 0.005 285.823);
- --sidebar-border: oklch(0.92 0.004 286.32);
- --sidebar-ring: oklch(0.623 0.214 259.815);
-}
-
-.dark {
- --primary: var(--color-blue-700);
- --primary-foreground: var(--color-blue-50);
- --sidebar-primary: var(--color-blue-500);
- --sidebar-primary-foreground: var(--color-blue-50);
- --background: oklch(0.141 0.005 285.823);
- --foreground: oklch(0.85 0.005 65);
- --card: oklch(0.21 0.006 285.885);
- --card-foreground: oklch(0.85 0.005 65);
- --popover: oklch(0.21 0.006 285.885);
- --popover-foreground: oklch(0.85 0.005 65);
- --secondary: oklch(0.24 0.006 286.033);
- --secondary-foreground: oklch(0.7 0.005 65);
- --muted: oklch(0.274 0.006 286.033);
- --muted-foreground: oklch(0.705 0.015 286.067);
- --accent: oklch(0.274 0.006 286.033);
- --accent-foreground: oklch(0.92 0.005 65);
- --destructive: oklch(0.704 0.191 22.216);
- --destructive-foreground: oklch(0.985 0 0);
- --border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
- --ring: oklch(0.488 0.243 264.376);
- --chart-1: var(--color-blue-300);
- --chart-2: var(--color-blue-500);
- --chart-3: var(--color-blue-600);
- --chart-4: var(--color-blue-700);
- --chart-5: var(--color-blue-800);
- --sidebar: oklch(0.21 0.006 285.885);
- --sidebar-foreground: oklch(0.85 0.005 65);
- --sidebar-accent: oklch(0.274 0.006 286.033);
- --sidebar-accent-foreground: oklch(0.985 0 0);
- --sidebar-border: oklch(1 0 0 / 10%);
- --sidebar-ring: oklch(0.488 0.243 264.376);
+ --popover-foreground: oklch(0.15 0.02 250);
+ --primary: oklch(0.45 0.15 240);
+ --primary-foreground: oklch(0.98 0.005 240);
+ --secondary: oklch(0.96 0.01 240);
+ --secondary-foreground: oklch(0.35 0.05 240);
+ --muted: oklch(0.96 0.005 240);
+ --muted-foreground: oklch(0.55 0.02 250);
+ --accent: oklch(0.65 0.18 195);
+ --accent-foreground: oklch(0.98 0.005 195);
+ --destructive: oklch(0.55 0.22 25);
+ --destructive-foreground: oklch(0.98 0 0);
+ --border: oklch(0.92 0.005 240);
+ --input: oklch(0.92 0.005 240);
+ --ring: oklch(0.55 0.18 220);
+ --chart-1: oklch(0.65 0.18 195);
+ --chart-2: oklch(0.55 0.15 240);
+ --chart-3: oklch(0.6 0.15 155);
+ --chart-4: oklch(0.7 0.15 60);
+ --chart-5: oklch(0.5 0.12 280);
+ /* Sidebar - Dark Navy */
+ --sidebar: oklch(0.18 0.03 250);
+ --sidebar-foreground: oklch(0.9 0.01 240);
+ --sidebar-primary: oklch(0.65 0.18 195);
+ --sidebar-primary-foreground: oklch(0.98 0.005 195);
+ --sidebar-accent: oklch(0.25 0.04 250);
+ --sidebar-accent-foreground: oklch(0.95 0.01 240);
+ --sidebar-border: oklch(0.28 0.03 250);
+ --sidebar-ring: oklch(0.65 0.18 195);
}
@layer base {
@@ -119,6 +88,10 @@
}
body {
@apply bg-background text-foreground;
+ font-family: 'Inter', system-ui, sans-serif;
+ }
+ h1, h2, h3, h4, h5, h6 {
+ font-family: 'Outfit', system-ui, sans-serif;
}
button:not(:disabled),
[role="button"]:not([aria-disabled="true"]),
@@ -134,24 +107,11 @@
}
@layer components {
- /**
- * Custom container utility that centers content and adds responsive padding.
- *
- * This overrides Tailwind's default container behavior to:
- * - Auto-center content (mx-auto)
- * - Add responsive horizontal padding
- * - Set max-width for large screens
- *
- * Usage:
...
- *
- * For custom widths, use max-w-* utilities directly:
- *
...
- */
.container {
width: 100%;
margin-left: auto;
margin-right: auto;
- padding-left: 1rem; /* 16px - mobile padding */
+ padding-left: 1rem;
padding-right: 1rem;
}
@@ -162,16 +122,61 @@
@media (min-width: 640px) {
.container {
- padding-left: 1.5rem; /* 24px - tablet padding */
+ padding-left: 1.5rem;
padding-right: 1.5rem;
}
}
@media (min-width: 1024px) {
.container {
- padding-left: 2rem; /* 32px - desktop padding */
+ padding-left: 2rem;
padding-right: 2rem;
- max-width: 1280px; /* Standard content width */
+ max-width: 1280px;
}
}
-}
\ No newline at end of file
+}
+
+/* Custom scrollbar for sidebar */
+.sidebar-scroll::-webkit-scrollbar {
+ width: 4px;
+}
+.sidebar-scroll::-webkit-scrollbar-track {
+ background: transparent;
+}
+.sidebar-scroll::-webkit-scrollbar-thumb {
+ background: oklch(0.35 0.03 250);
+ border-radius: 2px;
+}
+
+/* Animations */
+@keyframes fade-in-up {
+ from {
+ opacity: 0;
+ transform: translateY(8px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes counter {
+ from {
+ opacity: 0;
+ transform: translateY(4px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.animate-fade-in-up {
+ animation: fade-in-up 0.3s cubic-bezier(0.23, 1, 0.32, 1) forwards;
+}
+
+.stagger-1 { animation-delay: 30ms; }
+.stagger-2 { animation-delay: 60ms; }
+.stagger-3 { animation-delay: 90ms; }
+.stagger-4 { animation-delay: 120ms; }
+.stagger-5 { animation-delay: 150ms; }
diff --git a/client/src/pages/Applications.tsx b/client/src/pages/Applications.tsx
new file mode 100644
index 0000000..3a95d83
--- /dev/null
+++ b/client/src/pages/Applications.tsx
@@ -0,0 +1,76 @@
+/**
+ * Applications — Gestion des raccourcis vers les outils métier
+ */
+import { useState } from "react";
+import { Card, CardContent } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Badge } from "@/components/ui/badge";
+import { Switch } from "@/components/ui/switch";
+import { Plus, ExternalLink, Settings, Trash2, Edit } from "lucide-react";
+import { toast } from "sonner";
+
+const APPLICATIONS = [
+ { id: 1, nom: "Outlook Web", url: "https://outlook.office365.com", categorie: "Communication", actif: true, couleur: "#0078D4", initiale: "O" },
+ { id: 2, nom: "PMSIpilot", url: "#", categorie: "Médical", actif: true, couleur: "#1a5276", initiale: "P" },
+ { id: 3, nom: "Vidal Hoptimal", url: "#", categorie: "Médical", actif: true, couleur: "#c0392b", initiale: "V" },
+ { id: 4, nom: "Dedalus", url: "#", categorie: "Médical", actif: true, couleur: "#117a65", initiale: "D" },
+ { id: 5, nom: "GLPI", url: "#", categorie: "Support", actif: true, couleur: "#6c3483", initiale: "G" },
+ { id: 6, nom: "Antibiogarde", url: "#", categorie: "Médical", actif: false, couleur: "#935116", initiale: "A" },
+ { id: 7, nom: "Intranet RH", url: "#", categorie: "RH", actif: true, couleur: "#1f618d", initiale: "R" },
+ { id: 8, nom: "Portail Formation", url: "#", categorie: "Formation", actif: false, couleur: "#196f3d", initiale: "F" },
+];
+
+export default function Applications() {
+ const [apps, setApps] = useState(APPLICATIONS);
+
+ const toggleApp = (id: number) => {
+ setApps((prev) => prev.map((a) => a.id === id ? { ...a, actif: !a.actif } : a));
+ const app = apps.find((a) => a.id === id);
+ toast.success(`Application "${app?.nom}" ${app?.actif ? "masquée" : "affichée"} sur l'intranet`);
+ };
+
+ return (
+
+
+
+
Applications
+
Gérez les raccourcis vers les outils métier de l'intranet
+
+
toast.info("Ajout d'application — Fonctionnalité à venir")}>
+ Ajouter une application
+
+
+
+
+ {apps.map((app) => (
+
+
+
+
+ {app.initiale}
+
+
toggleApp(app.id)} />
+
+ {app.nom}
+ {app.categorie}
+
+ toast.info("Modification — Fonctionnalité à venir")}>
+ Modifier
+
+ window.open(app.url, "_blank")}>
+
+
+ toast.info("Suppression — Fonctionnalité à venir")}>
+
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/client/src/pages/Dashboard.tsx b/client/src/pages/Dashboard.tsx
new file mode 100644
index 0000000..56d5b6b
--- /dev/null
+++ b/client/src/pages/Dashboard.tsx
@@ -0,0 +1,340 @@
+/**
+ * Tableau de bord — Vue d'ensemble principale
+ * Thème : Centre de commande numérique — widgets modulaires, indicateurs, graphiques
+ */
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import {
+ Users,
+ FileText,
+ Eye,
+ TrendingUp,
+ TrendingDown,
+ Clock,
+ AlertTriangle,
+ CheckCircle,
+ ArrowRight,
+ Activity,
+} from "lucide-react";
+import {
+ BarChart,
+ Bar,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ ResponsiveContainer,
+ LineChart,
+ Line,
+ PieChart,
+ Pie,
+ Cell,
+} from "recharts";
+
+const visitData = [
+ { name: "Lun", visites: 245 },
+ { name: "Mar", visites: 312 },
+ { name: "Mer", visites: 289 },
+ { name: "Jeu", visites: 378 },
+ { name: "Ven", visites: 420 },
+ { name: "Sam", visites: 156 },
+ { name: "Dim", visites: 98 },
+];
+
+const trafficData = [
+ { name: "Sem. 1", utilisateurs: 1200 },
+ { name: "Sem. 2", utilisateurs: 1350 },
+ { name: "Sem. 3", utilisateurs: 1180 },
+ { name: "Sem. 4", utilisateurs: 1520 },
+ { name: "Sem. 5", utilisateurs: 1680 },
+ { name: "Sem. 6", utilisateurs: 1420 },
+];
+
+const appUsageData = [
+ { name: "Outlook", value: 35, color: "#0891b2" },
+ { name: "PMSIpilot", value: 20, color: "#0f172a" },
+ { name: "Vidal", value: 18, color: "#10b981" },
+ { name: "GLPI", value: 15, color: "#f59e0b" },
+ { name: "Autres", value: 12, color: "#8b5cf6" },
+];
+
+const recentActivity = [
+ { action: "Page modifiée", target: "Protocoles Urgences", user: "Dr. Martin", time: "Il y a 5 min", status: "success" },
+ { action: "Utilisateur ajouté", target: "Sophie Leclerc (IDE)", user: "Admin", time: "Il y a 12 min", status: "success" },
+ { action: "Alerte système", target: "Serveur PMSIpilot indisponible", user: "Système", time: "Il y a 25 min", status: "warning" },
+ { action: "Widget mis à jour", target: "Annuaire téléphonique", user: "P. Boursiquot", time: "Il y a 1h", status: "success" },
+ { action: "Page publiée", target: "Informations COVID-19", user: "Service Communication", time: "Il y a 2h", status: "success" },
+];
+
+export default function Dashboard() {
+ return (
+
+ {/* En-tête de page */}
+
+
+
+ Tableau de bord
+
+
+ Vue d'ensemble de l'activité de l'intranet CHK
+
+
+
+
+
+ En ligne
+
+
+ Dernière mise à jour : il y a 2 min
+
+
+
+
+ {/* Indicateurs clés */}
+
+
+
+
+
+
+
+ {/* Rangée de graphiques */}
+
+ {/* Histogramme — Visites */}
+
+
+
+ Visites cette semaine
+
+
+
+
+
+
+
+
+ [`${value} visites`, "Visites"]}
+ contentStyle={{
+ borderRadius: "8px",
+ border: "1px solid oklch(0.92 0.005 240)",
+ boxShadow: "0 4px 12px rgba(0,0,0,0.08)",
+ fontFamily: "Inter, sans-serif",
+ fontSize: "12px",
+ }}
+ />
+
+
+
+
+
+
+ {/* Camembert — Applications les plus utilisées */}
+
+
+
+ Applications les plus utilisées
+
+
+
+
+
+
+ {appUsageData.map((entry, index) => (
+ |
+ ))}
+
+ [`${value} %`, "Utilisation"]}
+ contentStyle={{
+ borderRadius: "8px",
+ border: "1px solid oklch(0.92 0.005 240)",
+ fontSize: "12px",
+ }}
+ />
+
+
+
+ {appUsageData.map((item) => (
+
+ ))}
+
+
+
+
+
+ {/* Rangée inférieure */}
+
+ {/* Activité récente */}
+
+
+
+ Activité récente
+
+
+ Voir tout
+
+
+
+ {recentActivity.map((item, i) => (
+
+
+ {item.status === "warning" ? (
+
+ ) : (
+
+ )}
+
+
+
+ {item.action} —{" "}
+ {item.target}
+
+
+ {item.user} · {item.time}
+
+
+
+ ))}
+
+
+
+ {/* Tendance utilisateurs */}
+
+
+
+ Tendance des connexions (6 semaines)
+
+
+
+
+
+
+
+
+ [`${value} utilisateurs`, "Connexions"]}
+ contentStyle={{
+ borderRadius: "8px",
+ border: "1px solid oklch(0.92 0.005 240)",
+ boxShadow: "0 4px 12px rgba(0,0,0,0.08)",
+ fontFamily: "Inter, sans-serif",
+ fontSize: "12px",
+ }}
+ />
+
+
+
+
+
+
+
+ );
+}
+
+function KPICard({
+ title,
+ value,
+ change,
+ trend,
+ icon: Icon,
+ description,
+}: {
+ title: string;
+ value: string;
+ change: string;
+ trend: "up" | "down";
+ icon: React.ComponentType<{ className?: string }>;
+ description: string;
+}) {
+ return (
+
+
+
+
+
+ {title}
+
+
+ {value}
+
+
+ {trend === "up" ? (
+
+ ) : (
+
+ )}
+
+ {change}
+
+ {description}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/client/src/pages/Medias.tsx b/client/src/pages/Medias.tsx
new file mode 100644
index 0000000..c4a51a8
--- /dev/null
+++ b/client/src/pages/Medias.tsx
@@ -0,0 +1,248 @@
+/**
+ * Médias — Bibliothèque de fichiers et images
+ * Gestion des images, documents et vidéos de l'intranet
+ */
+import { useState } from "react";
+import { Card, CardContent } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Badge } from "@/components/ui/badge";
+import {
+ Upload,
+ Search,
+ Grid3X3,
+ List,
+ Image,
+ FileText,
+ Film,
+ Download,
+ Trash2,
+ Eye,
+ Filter,
+} from "lucide-react";
+import { toast } from "sonner";
+
+const FICHIERS = [
+ { id: 1, nom: "Logo-CHU-Kourou.png", type: "image", taille: "245 Ko", date: "21/05/2026", miniature: "https://images.unsplash.com/photo-1505751172876-fa1923c5c528?w=200&h=200&fit=crop" },
+ { id: 2, nom: "Protocole-urgences.pdf", type: "document", taille: "1,2 Mo", date: "20/05/2026", miniature: null },
+ { id: 3, nom: "Photo-equipe-2026.jpg", type: "image", taille: "3,4 Mo", date: "19/05/2026", miniature: "https://images.unsplash.com/photo-1559839734-2b71ea197ec2?w=200&h=200&fit=crop" },
+ { id: 4, nom: "Banniere-accueil.png", type: "image", taille: "890 Ko", date: "18/05/2026", miniature: "https://images.unsplash.com/photo-1538108149393-fbbd81895907?w=200&h=200&fit=crop" },
+ { id: 5, nom: "Organigramme-2026.pdf", type: "document", taille: "560 Ko", date: "17/05/2026", miniature: null },
+ { id: 6, nom: "Video-presentation.mp4", type: "video", taille: "45 Mo", date: "16/05/2026", miniature: "https://images.unsplash.com/photo-1576091160399-112ba8d25d1d?w=200&h=200&fit=crop" },
+ { id: 7, nom: "Plan-hopital.png", type: "image", taille: "1,8 Mo", date: "15/05/2026", miniature: "https://images.unsplash.com/photo-1587351021759-3e566b6af7cc?w=200&h=200&fit=crop" },
+ { id: 8, nom: "Charte-graphique.pdf", type: "document", taille: "2,1 Mo", date: "14/05/2026", miniature: null },
+ { id: 9, nom: "Photo-batiment.jpg", type: "image", taille: "4,2 Mo", date: "13/05/2026", miniature: "https://images.unsplash.com/photo-1519494026892-80bbd2d6fd0d?w=200&h=200&fit=crop" },
+];
+
+const TYPE_LABELS: Record
= {
+ image: "Image",
+ document: "Document",
+ video: "Vidéo",
+};
+
+export default function Medias() {
+ const [modeAffichage, setModeAffichage] = useState<"grille" | "liste">("grille");
+ const [recherche, setRecherche] = useState("");
+
+ const fichiersFiltres = FICHIERS.filter((f) =>
+ f.nom.toLowerCase().includes(recherche.toLowerCase())
+ );
+
+ const getIconeFichier = (type: string) => {
+ switch (type) {
+ case "image": return ;
+ case "document": return ;
+ case "video": return ;
+ default: return ;
+ }
+ };
+
+ return (
+
+ {/* En-tête */}
+
+
+
Médias
+
+ Bibliothèque de fichiers et images de l'intranet
+
+
+
toast.info("Téléversement — Fonctionnalité à venir")}
+ >
+
+ Téléverser un fichier
+
+
+
+ {/* Barre d'outils */}
+
+
+
+
+
+ setRecherche(e.target.value)}
+ />
+
+
+
toast.info("Filtres — Fonctionnalité à venir")}
+ >
+ Filtrer
+
+
+ setModeAffichage("grille")}
+ title="Affichage en grille"
+ >
+
+
+ setModeAffichage("liste")}
+ title="Affichage en liste"
+ >
+
+
+
+
+
+
+
+
+ {/* Résumé statistique */}
+
+
+
+ 9
+ Fichiers au total
+
+
+
+
+ 59,4 Mo
+ Espace utilisé
+
+
+
+
+ 5
+ Images
+
+
+
+
+ {/* Grille ou liste de fichiers */}
+ {modeAffichage === "grille" ? (
+
+ {fichiersFiltres.map((fichier) => (
+
+
+ {fichier.miniature ? (
+
+ ) : (
+
+ {getIconeFichier(fichier.type)}
+
+ {fichier.nom.split(".").pop()}
+
+
+ )}
+ {/* Actions au survol */}
+
+ toast.info("Prévisualisation — Fonctionnalité à venir")}
+ title="Prévisualiser"
+ >
+
+
+ toast.info("Téléchargement — Fonctionnalité à venir")}
+ title="Télécharger"
+ >
+
+
+ toast.info("Suppression — Fonctionnalité à venir")}
+ title="Supprimer"
+ >
+
+
+
+
+
+ {fichier.nom}
+
+ {fichier.taille}
+ {fichier.date}
+
+
+
+ ))}
+
+ ) : (
+
+
+
+ {fichiersFiltres.map((fichier) => (
+
+
+ {getIconeFichier(fichier.type)}
+
+
+
{fichier.nom}
+
+ {fichier.taille} · {fichier.date}
+
+
+
+ {TYPE_LABELS[fichier.type] ?? fichier.type}
+
+
+ toast.info("Téléchargement — Fonctionnalité à venir")}
+ >
+
+
+ toast.info("Suppression — Fonctionnalité à venir")}
+ >
+
+
+
+
+ ))}
+
+
+
+ )}
+
+ );
+}
diff --git a/client/src/pages/Notifications.tsx b/client/src/pages/Notifications.tsx
new file mode 100644
index 0000000..d902ac6
--- /dev/null
+++ b/client/src/pages/Notifications.tsx
@@ -0,0 +1,84 @@
+/**
+ * Notifications — Centre de messages et alertes
+ */
+import { useState } from "react";
+import { Card, CardContent } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Badge } from "@/components/ui/badge";
+import { Bell, AlertTriangle, Info, CheckCircle, Trash2, Check } from "lucide-react";
+import { toast } from "sonner";
+
+const NOTIFICATIONS = [
+ { id: 1, type: "alerte", titre: "Serveur PMSIpilot indisponible", message: "Le serveur PMSIpilot est inaccessible depuis 14h30. L'équipe DSI est informée.", temps: "Il y a 25 min", lue: false },
+ { id: 2, type: "info", titre: "Mise à jour de l'intranet planifiée", message: "Une maintenance est prévue le samedi 24 mai de 22h à 2h du matin.", temps: "Il y a 2h", lue: false },
+ { id: 3, type: "succes", titre: "Sauvegarde automatique réussie", message: "La sauvegarde quotidienne des données s'est terminée avec succès.", temps: "Il y a 4h", lue: true },
+ { id: 4, type: "info", titre: "Nouvel utilisateur inscrit", message: "Sophie Leclerc (IDE - Urgences) a été ajoutée au système.", temps: "Hier à 16h45", lue: true },
+ { id: 5, type: "alerte", titre: "Tentative de connexion suspecte", message: "3 tentatives de connexion échouées depuis une adresse IP inconnue.", temps: "Hier à 09h12", lue: true },
+];
+
+const ICONES: Record = {
+ alerte: ,
+ info: ,
+ succes: ,
+};
+
+const COULEURS: Record = {
+ alerte: "bg-amber-100",
+ info: "bg-blue-100",
+ succes: "bg-emerald-100",
+};
+
+export default function Notifications() {
+ const [notifs, setNotifs] = useState(NOTIFICATIONS);
+
+ const marquerToutesLues = () => {
+ setNotifs((prev) => prev.map((n) => ({ ...n, lue: true })));
+ toast.success("Toutes les notifications ont été marquées comme lues");
+ };
+
+ const nonLues = notifs.filter((n) => !n.lue).length;
+
+ return (
+
+
+
+
+ Notifications
+ {nonLues > 0 && {nonLues} }
+
+
Alertes et messages du système
+
+ {nonLues > 0 && (
+
+ Tout marquer comme lu
+
+ )}
+
+
+
+ {notifs.map((notif) => (
+
+
+
+
+ {ICONES[notif.type]}
+
+
+
+
{notif.titre}
+ {!notif.lue &&
Nouveau }
+
+
{notif.message}
+
{notif.temps}
+
+
{ setNotifs((prev) => prev.filter((n) => n.id !== notif.id)); toast.success("Notification supprimée"); }}>
+
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/client/src/pages/Pages.tsx b/client/src/pages/Pages.tsx
new file mode 100644
index 0000000..76c2ee7
--- /dev/null
+++ b/client/src/pages/Pages.tsx
@@ -0,0 +1,266 @@
+/**
+ * Pages — Gestion du contenu de l'intranet (CMS)
+ * Fonctionnalités : liste, création, édition, publication, catégorisation
+ */
+import { useState } from "react";
+import { Card, CardContent } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Badge } from "@/components/ui/badge";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogFooter,
+} from "@/components/ui/dialog";
+import { Textarea } from "@/components/ui/textarea";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import {
+ Plus,
+ Search,
+ MoreHorizontal,
+ Edit,
+ Trash2,
+ Eye,
+ Copy,
+ FileText,
+ Globe,
+ Lock,
+} from "lucide-react";
+import { toast } from "sonner";
+
+const PAGES_DATA = [
+ { id: 1, titre: "Accueil", slug: "/", statut: "publie", categorie: "Général", auteur: "Admin", modifie: "21/05/2026", vues: 12540 },
+ { id: 2, titre: "Protocoles Urgences", slug: "/protocoles-urgences", statut: "publie", categorie: "Médical", auteur: "Dr. Martin", modifie: "20/05/2026", vues: 3420 },
+ { id: 3, titre: "Annuaire du personnel", slug: "/annuaire", statut: "publie", categorie: "RH", auteur: "Admin", modifie: "19/05/2026", vues: 8900 },
+ { id: 4, titre: "Informations COVID-19", slug: "/covid-19", statut: "publie", categorie: "Médical", auteur: "Service Communication", modifie: "18/05/2026", vues: 2100 },
+ { id: 5, titre: "Planning des gardes", slug: "/planning-gardes", statut: "brouillon", categorie: "RH", auteur: "Cadre de santé", modifie: "17/05/2026", vues: 0 },
+ { id: 6, titre: "Procédures qualité", slug: "/qualite", statut: "publie", categorie: "Qualité", auteur: "Responsable Qualité", modifie: "16/05/2026", vues: 1560 },
+ { id: 7, titre: "Documentation utilisateurs", slug: "/doc-utilisateurs", statut: "publie", categorie: "Support", auteur: "P. Boursiquot", modifie: "15/05/2026", vues: 4200 },
+ { id: 8, titre: "Nouvelle page pharmacie", slug: "/pharmacie-v2", statut: "brouillon", categorie: "Médical", auteur: "Pharmacien", modifie: "14/05/2026", vues: 0 },
+];
+
+export default function Pages() {
+ const [recherche, setRecherche] = useState("");
+ const [dialogCreation, setDialogCreation] = useState(false);
+ const [filtreCategorie, setFiltreCategorie] = useState("toutes");
+
+ const pagesFiltrees = PAGES_DATA.filter((page) => {
+ const correspondRecherche = page.titre.toLowerCase().includes(recherche.toLowerCase());
+ const correspondCategorie =
+ filtreCategorie === "toutes" || page.categorie === filtreCategorie;
+ return correspondRecherche && correspondCategorie;
+ });
+
+ return (
+
+ {/* En-tête */}
+
+
+
Pages
+
+ Gérez les pages de contenu de l'intranet
+
+
+
setDialogCreation(true)}
+ >
+
+ Nouvelle page
+
+
+
+ {/* Filtres */}
+
+
+
+
+
+ setRecherche(e.target.value)}
+ />
+
+
+
+
+
+
+ Toutes les catégories
+ Général
+ Médical
+ Ressources humaines
+ Qualité
+ Support
+
+
+
+
+
+
+ {/* Tableau des pages */}
+
+
+
+
+
+ Page
+ Catégorie
+ Statut
+ Auteur
+ Vues
+ Modifié
+
+
+
+
+ {pagesFiltrees.map((page) => (
+
+
+
+
+
+
+
+
{page.titre}
+
{page.slug}
+
+
+
+
+
+ {page.categorie}
+
+
+
+ {page.statut === "publie" ? (
+
+
+ Publié
+
+ ) : (
+
+
+ Brouillon
+
+ )}
+
+ {page.auteur}
+
+ {page.vues.toLocaleString("fr-FR")}
+
+ {page.modifie}
+
+
+
+
+
+
+
+
+ toast.info("Éditeur de page — Fonctionnalité à venir")}>
+ Modifier
+
+ toast.info("Prévisualisation — Fonctionnalité à venir")}>
+ Prévisualiser
+
+ toast.success("Page dupliquée avec succès")}>
+ Dupliquer
+
+ toast.info("Suppression — Fonctionnalité à venir")}
+ >
+ Supprimer
+
+
+
+
+
+ ))}
+
+
+
+
+
+ {/* Dialogue de création */}
+
+
+
+ Créer une nouvelle page
+
+
+
+ Titre de la page
+
+
+
+ Catégorie
+
+
+
+
+
+ Général
+ Médical
+ Ressources humaines
+ Qualité
+ Support
+
+
+
+
+ Description (optionnel)
+
+
+
+
+ setDialogCreation(false)}>
+ Annuler
+
+ {
+ setDialogCreation(false);
+ toast.success("Page créée avec succès !");
+ }}
+ >
+ Créer la page
+
+
+
+
+
+ );
+}
diff --git a/client/src/pages/Parametres.tsx b/client/src/pages/Parametres.tsx
new file mode 100644
index 0000000..924662e
--- /dev/null
+++ b/client/src/pages/Parametres.tsx
@@ -0,0 +1,115 @@
+/**
+ * Paramètres — Configuration générale de l'intranet
+ */
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Textarea } from "@/components/ui/textarea";
+import { Switch } from "@/components/ui/switch";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { Settings, Globe, Bell, Shield, Palette, Save } from "lucide-react";
+import { toast } from "sonner";
+
+export default function Parametres() {
+ return (
+
+
+
Paramètres
+
Configuration générale de l'intranet CHK
+
+
+
+
+ Général
+ Apparence
+ Notifications
+ Sécurité
+
+
+
+
+ Informations générales
+
+ Nom de l'intranet
+ Description
+ URL de l'intranet
+ Adresse e-mail de contact
+
+
Mode maintenance
Affiche une page de maintenance aux utilisateurs
+
toast.info(v ? "Mode maintenance activé" : "Mode maintenance désactivé")} />
+
+ toast.success("Paramètres enregistrés avec succès")}>
+ Enregistrer
+
+
+
+
+
+
+
+ Apparence et thème
+
+ Couleur principale
+
+ {["#0891b2", "#1d4ed8", "#059669", "#7c3aed", "#dc2626"].map((c) => (
+ toast.info("Changement de couleur — Fonctionnalité à venir")} />
+ ))}
+
+
+
+
Logo personnalisé
Remplacer le logo par défaut par celui du CHK
+
toast.info("Téléversement du logo — Fonctionnalité à venir")}>Choisir un fichier
+
+
+
Favicon
Icône affichée dans l'onglet du navigateur
+
toast.info("Téléversement du favicon — Fonctionnalité à venir")}>Choisir un fichier
+
+ toast.success("Apparence enregistrée")}>
+ Enregistrer
+
+
+
+
+
+
+
+ Préférences de notifications
+
+ {[
+ { label: "Alertes système", desc: "Pannes, erreurs et indisponibilités" },
+ { label: "Nouvelles publications", desc: "Lorsqu'une page est publiée ou modifiée" },
+ { label: "Nouveaux utilisateurs", desc: "Lors de la création d'un compte" },
+ { label: "Rapports hebdomadaires", desc: "Résumé des statistiques chaque lundi" },
+ ].map((item) => (
+
+
+
toast.info(`Notification "${item.label}" ${v ? "activée" : "désactivée"}`)} />
+
+ ))}
+
+
+
+
+
+
+ Sécurité et accès
+
+ Durée de session (minutes)
+
+
Double authentification (2FA)
Obligatoire pour les administrateurs
+
toast.info(`2FA ${v ? "activée" : "désactivée"}`)} />
+
+
+
Journalisation des connexions
Enregistrer toutes les tentatives de connexion
+
toast.info(`Journalisation ${v ? "activée" : "désactivée"}`)} />
+
+ toast.success("Paramètres de sécurité enregistrés")}>
+ Enregistrer
+
+
+
+
+
+
+ );
+}
diff --git a/client/src/pages/Roles.tsx b/client/src/pages/Roles.tsx
new file mode 100644
index 0000000..996f782
--- /dev/null
+++ b/client/src/pages/Roles.tsx
@@ -0,0 +1,109 @@
+/**
+ * Rôles et droits — Gestion des permissions par profil
+ */
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+import { Switch } from "@/components/ui/switch";
+import { Shield, Users, Edit, Eye } from "lucide-react";
+import { toast } from "sonner";
+
+const ROLES = [
+ {
+ nom: "Administrateur",
+ couleur: "bg-purple-100 text-purple-700",
+ icone: Shield,
+ description: "Accès complet à toutes les fonctionnalités de l'administration",
+ utilisateurs: 1,
+ permissions: {
+ "Gérer les pages": true,
+ "Publier du contenu": true,
+ "Gérer les médias": true,
+ "Gérer les utilisateurs": true,
+ "Voir les statistiques": true,
+ "Configurer les paramètres": true,
+ "Gérer les widgets": true,
+ "Gérer les applications": true,
+ },
+ },
+ {
+ nom: "Éditeur",
+ couleur: "bg-blue-100 text-blue-700",
+ icone: Edit,
+ description: "Peut créer, modifier et publier des pages et des médias",
+ utilisateurs: 3,
+ permissions: {
+ "Gérer les pages": true,
+ "Publier du contenu": true,
+ "Gérer les médias": true,
+ "Gérer les utilisateurs": false,
+ "Voir les statistiques": true,
+ "Configurer les paramètres": false,
+ "Gérer les widgets": false,
+ "Gérer les applications": false,
+ },
+ },
+ {
+ nom: "Lecteur",
+ couleur: "bg-gray-100 text-gray-600",
+ icone: Eye,
+ description: "Accès en lecture seule à l'ensemble du contenu de l'intranet",
+ utilisateurs: 3,
+ permissions: {
+ "Gérer les pages": false,
+ "Publier du contenu": false,
+ "Gérer les médias": false,
+ "Gérer les utilisateurs": false,
+ "Voir les statistiques": false,
+ "Configurer les paramètres": false,
+ "Gérer les widgets": false,
+ "Gérer les applications": false,
+ },
+ },
+];
+
+export default function Roles() {
+ return (
+
+
+
Rôles et droits
+
Configurez les permissions pour chaque profil d'utilisateur
+
+
+
+ {ROLES.map((role) => (
+
+
+
+
+
+
+
+
+
+ {role.nom}
+ {role.utilisateurs} utilisateur{role.utilisateurs > 1 ? "s" : ""}
+
+
{role.description}
+
+
+
+
+
+
+ {Object.entries(role.permissions).map(([permission, actif]) => (
+
+ {permission}
+ toast.info(`Modification des droits — Fonctionnalité à venir`)}
+ />
+
+ ))}
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/client/src/pages/Statistiques.tsx b/client/src/pages/Statistiques.tsx
new file mode 100644
index 0000000..bd8d06d
--- /dev/null
+++ b/client/src/pages/Statistiques.tsx
@@ -0,0 +1,114 @@
+/**
+ * Statistiques — Analyse de l'audience et de l'utilisation
+ */
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, LineChart, Line, AreaChart, Area } from "recharts";
+
+const donneesJournalieres = [
+ { jour: "Lun", visites: 245, utilisateurs: 180 },
+ { jour: "Mar", visites: 312, utilisateurs: 230 },
+ { jour: "Mer", visites: 289, utilisateurs: 210 },
+ { jour: "Jeu", visites: 378, utilisateurs: 290 },
+ { jour: "Ven", visites: 420, utilisateurs: 340 },
+ { jour: "Sam", visites: 156, utilisateurs: 90 },
+ { jour: "Dim", visites: 98, utilisateurs: 55 },
+];
+
+const donneesMensuelles = [
+ { mois: "Déc", visites: 8200 },
+ { mois: "Jan", visites: 9100 },
+ { mois: "Fév", visites: 8700 },
+ { mois: "Mar", visites: 10200 },
+ { mois: "Avr", visites: 11500 },
+ { mois: "Mai", visites: 12800 },
+];
+
+const pagesPopulaires = [
+ { page: "Accueil", vues: 12540 },
+ { page: "Annuaire", vues: 8900 },
+ { page: "Protocoles", vues: 3420 },
+ { page: "Documentation", vues: 4200 },
+ { page: "Procédures qualité", vues: 1560 },
+];
+
+export default function Statistiques() {
+ return (
+
+
+
Statistiques
+
Analyse de l'audience et de l'utilisation de l'intranet
+
+
+
+ {[
+ { label: "Visites ce mois", valeur: "12 800", variation: "+11 %" },
+ { label: "Utilisateurs uniques", valeur: "347", variation: "+12 %" },
+ { label: "Pages vues", valeur: "48 200", variation: "+8 %" },
+ { label: "Durée moyenne", valeur: "4 min 32", variation: "-15 %" },
+ ].map((kpi) => (
+
+
+ {kpi.label}
+ {kpi.valeur}
+ {kpi.variation} vs mois précédent
+
+
+ ))}
+
+
+
+
+
+ Visites et utilisateurs cette semaine
+
+
+
+
+
+
+
+ [v, n === "visites" ? "Visites" : "Utilisateurs"]} contentStyle={{ borderRadius: "8px", fontSize: "12px" }} />
+
+
+
+
+
+
+
+
+
+ Évolution mensuelle des visites
+
+
+
+
+
+
+
+ [v, "Visites"]} contentStyle={{ borderRadius: "8px", fontSize: "12px" }} />
+
+
+
+
+
+
+
+
+
+ Pages les plus consultées
+
+
+
+
+
+
+
+ [v, "Vues"]} contentStyle={{ borderRadius: "8px", fontSize: "12px" }} />
+
+
+
+
+
+
+ );
+}
diff --git a/client/src/pages/Utilisateurs.tsx b/client/src/pages/Utilisateurs.tsx
new file mode 100644
index 0000000..700a775
--- /dev/null
+++ b/client/src/pages/Utilisateurs.tsx
@@ -0,0 +1,186 @@
+/**
+ * Utilisateurs — Gestion des comptes et des accès
+ */
+import { useState } from "react";
+import { Card, CardContent } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Badge } from "@/components/ui/badge";
+import { Avatar, AvatarFallback } from "@/components/ui/avatar";
+import {
+ Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
+} from "@/components/ui/table";
+import {
+ DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import {
+ Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
+} from "@/components/ui/dialog";
+import {
+ Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
+} from "@/components/ui/select";
+import { Plus, Search, MoreHorizontal, Edit, Trash2, Shield, UserCheck, UserX } from "lucide-react";
+import { toast } from "sonner";
+
+const UTILISATEURS = [
+ { id: 1, nom: "Pierre Boursiquot", email: "p.boursiquot@chk.gf", role: "Administrateur", service: "DSI", statut: "actif", connexion: "Il y a 5 min" },
+ { id: 2, nom: "Dr. Sophie Martin", email: "s.martin@chk.gf", role: "Éditeur", service: "Urgences", statut: "actif", connexion: "Il y a 1h" },
+ { id: 3, nom: "Marie Leclerc", email: "m.leclerc@chk.gf", role: "Éditeur", service: "RH", statut: "actif", connexion: "Hier" },
+ { id: 4, nom: "Jean-Paul Dumont", email: "jp.dumont@chk.gf", role: "Lecteur", service: "Pharmacie", statut: "actif", connexion: "Il y a 3 jours" },
+ { id: 5, nom: "Isabelle Noel", email: "i.noel@chk.gf", role: "Éditeur", service: "Qualité", statut: "inactif", connexion: "Il y a 2 semaines" },
+ { id: 6, nom: "Thomas Bernard", email: "t.bernard@chk.gf", role: "Lecteur", service: "Chirurgie", statut: "actif", connexion: "Il y a 2h" },
+ { id: 7, nom: "Lucie Fontaine", email: "l.fontaine@chk.gf", role: "Lecteur", service: "Maternité", statut: "actif", connexion: "Aujourd'hui" },
+];
+
+const ROLE_COLORS: Record = {
+ "Administrateur": "bg-purple-100 text-purple-700",
+ "Éditeur": "bg-blue-100 text-blue-700",
+ "Lecteur": "bg-gray-100 text-gray-600",
+};
+
+function initiales(nom: string) {
+ return nom.split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2);
+}
+
+export default function Utilisateurs() {
+ const [recherche, setRecherche] = useState("");
+ const [dialogCreation, setDialogCreation] = useState(false);
+
+ const utilisateursFiltres = UTILISATEURS.filter((u) =>
+ u.nom.toLowerCase().includes(recherche.toLowerCase()) ||
+ u.email.toLowerCase().includes(recherche.toLowerCase())
+ );
+
+ return (
+
+
+
+
Utilisateurs
+
Gestion des comptes et des droits d'accès
+
+
setDialogCreation(true)}>
+ Nouvel utilisateur
+
+
+
+ {/* Résumé */}
+
+ {[
+ { label: "Utilisateurs actifs", valeur: "6" },
+ { label: "Administrateurs", valeur: "1" },
+ { label: "Comptes inactifs", valeur: "1" },
+ ].map((stat) => (
+
+
+ {stat.valeur}
+ {stat.label}
+
+
+ ))}
+
+
+ {/* Barre de recherche */}
+
+
+
+
+ setRecherche(e.target.value)} />
+
+
+
+
+ {/* Tableau */}
+
+
+
+
+
+ Utilisateur
+ Rôle
+ Service
+ Statut
+ Dernière connexion
+
+
+
+
+ {utilisateursFiltres.map((u) => (
+
+
+
+
+ {initiales(u.nom)}
+
+
+
+
+
+ {u.role}
+
+ {u.service}
+
+ {u.statut === "actif" ? (
+ Actif
+ ) : (
+ Inactif
+ )}
+
+ {u.connexion}
+
+
+
+
+
+
+
+
+ toast.info("Modification — Fonctionnalité à venir")}> Modifier
+ toast.info("Gestion des droits — Fonctionnalité à venir")}> Gérer les droits
+ toast.info("Suppression — Fonctionnalité à venir")}> Supprimer
+
+
+
+
+ ))}
+
+
+
+
+
+ {/* Dialogue de création */}
+
+
+
+ Créer un utilisateur
+
+
+
+
Adresse e-mail
+
+ Rôle
+
+
+
+ Administrateur
+ Éditeur
+ Lecteur
+
+
+
+
Service
+
+
+ setDialogCreation(false)}>Annuler
+ { setDialogCreation(false); toast.success("Utilisateur créé avec succès !"); }}>Créer
+
+
+
+
+ );
+}
diff --git a/client/src/pages/Widgets.tsx b/client/src/pages/Widgets.tsx
new file mode 100644
index 0000000..239a44b
--- /dev/null
+++ b/client/src/pages/Widgets.tsx
@@ -0,0 +1,77 @@
+/**
+ * Widgets — Gestion des blocs de contenu modulaires
+ */
+import { useState } from "react";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Badge } from "@/components/ui/badge";
+import { Switch } from "@/components/ui/switch";
+import { Plus, GripVertical, Settings, Trash2, Clock, Link, Image, FileText, Bell, Calendar, Users } from "lucide-react";
+import { toast } from "sonner";
+
+const WIDGETS = [
+ { id: 1, nom: "Liens rapides", description: "Raccourcis vers les applications métier", icone: Link, actif: true, zone: "Accueil" },
+ { id: 2, nom: "Actualités du CHK", description: "Dernières nouvelles et annonces internes", icone: Bell, actif: true, zone: "Accueil" },
+ { id: 3, nom: "Agenda partagé", description: "Calendrier des événements institutionnels", icone: Calendar, actif: true, zone: "Accueil" },
+ { id: 4, nom: "Annuaire rapide", description: "Recherche rapide dans l'annuaire du personnel", icone: Users, actif: false, zone: "Barre latérale" },
+ { id: 5, nom: "Bannière image", description: "Image promotionnelle ou message important", icone: Image, actif: true, zone: "Accueil" },
+ { id: 6, nom: "Documents récents", description: "Derniers documents publiés sur l'intranet", icone: FileText, actif: false, zone: "Barre latérale" },
+ { id: 7, nom: "Horloge & Météo", description: "Heure locale et météo de Kourou", icone: Clock, actif: true, zone: "En-tête" },
+];
+
+export default function Widgets() {
+ const [widgets, setWidgets] = useState(WIDGETS);
+
+ const toggleWidget = (id: number) => {
+ setWidgets((prev) => prev.map((w) => w.id === id ? { ...w, actif: !w.actif } : w));
+ const widget = widgets.find((w) => w.id === id);
+ toast.success(`Widget "${widget?.nom}" ${widget?.actif ? "désactivé" : "activé"}`);
+ };
+
+ return (
+
+
+
+
Widgets
+
Gérez les blocs de contenu modulaires de l'intranet
+
+
toast.info("Création de widget — Fonctionnalité à venir")}>
+ Nouveau widget
+
+
+
+
+ {widgets.map((widget) => (
+
+
+
+
+
+
+
+
+
+
+
+
{widget.nom}
+
{widget.zone}
+
+
{widget.description}
+
+
+ toggleWidget(widget.id)} />
+ toast.info("Configuration — Fonctionnalité à venir")}>
+
+
+ toast.info("Suppression — Fonctionnalité à venir")}>
+
+
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/ideas.md b/ideas.md
new file mode 100644
index 0000000..119e9de
--- /dev/null
+++ b/ideas.md
@@ -0,0 +1,108 @@
+# Brainstorm — Interface d'Administration Intranet CHK
+
+## Contexte
+Interface d'administration pour l'intranet du Centre Hospitalier de Kourou (CHU Guyane site Kourou). Thème hospitalier clair, sobre et dynamique. Doit permettre la gestion de pages, médias, utilisateurs, widgets, avec un mode simulation utilisateur.
+
+---
+
+
+
+
+## Idée 1 : "Clinical Precision" — Design Néo-Brutaliste Médical
+
+**Design Movement :** Néo-Brutalisme appliqué au domaine médical — lignes franches, contrastes nets, typographie affirmée, avec une touche de rigueur clinique.
+
+**Core Principles :**
+1. Contraste radical entre zones d'action et zones de repos visuel
+2. Typographie hiérarchique forte (titres massifs, corps lisible)
+3. Couleurs franches et limitées (blanc pur, noir profond, cyan médical en accent)
+4. Grilles structurées mais asymétriques
+
+**Color Philosophy :** Blanc chirurgical (#FAFBFC) comme base, noir charbon (#1A1A2E) pour les textes, cyan médical (#0891B2) comme couleur d'action évoquant la stérilité et la technologie médicale. Accents corail (#F97316) pour les alertes.
+
+**Layout Paradigm :** Sidebar compacte à gauche (icônes + labels), contenu principal en grille asymétrique avec des cartes à bordures franches (2px solid), pas de border-radius excessif.
+
+**Signature Elements :** Bordures épaisses sur les cartes actives, indicateurs de statut en pastilles colorées vives, micro-animations de "pulse" sur les notifications.
+
+**Interaction Philosophy :** Feedback immédiat et franc — les éléments cliqués se décalent légèrement (translateY), les hover révèlent des bordures colorées.
+
+**Animation :** Transitions courtes (120-180ms), scale(0.98) au clic, apparition des panneaux par slide-in latéral rapide.
+
+**Typography System :** Space Grotesk (titres, bold 700) + Inter (corps, regular 400/medium 500). Hiérarchie marquée par la taille et le poids.
+
+
+0.07
+
+
+---
+
+
+
+
+## Idée 2 : "Airy Healthcare" — Design Scandinave Médical
+
+**Design Movement :** Minimalisme scandinave adapté au secteur santé — espaces généreux, lumière naturelle simulée, formes organiques douces, sensation de calme et de confiance.
+
+**Core Principles :**
+1. Espaces blancs abondants créant une sensation d'air et de sérénité
+2. Couleurs désaturées et naturelles inspirées de l'environnement guyanais
+3. Formes légèrement arrondies évoquant le soin et l'humain
+4. Hiérarchie visuelle par l'espacement plutôt que par la couleur
+
+**Color Philosophy :** Fond ivoire chaud (#F8F9FA), bleu-gris apaisant (#64748B) pour la navigation, teal médical (#0D9488) comme accent principal évoquant la nature guyanaise et la santé. Touches de doré doux (#D4A574) pour les éléments premium/admin.
+
+**Layout Paradigm :** Sidebar étendue avec navigation verticale espacée, contenu en colonnes fluides avec beaucoup de padding. Les sections respirent avec des gaps généreux (24-32px).
+
+**Signature Elements :** Ombres très douces (0 1px 3px rgba), icônes en trait fin (stroke-width: 1.5), séparateurs subtils en dégradé transparent.
+
+**Interaction Philosophy :** Interactions douces et progressives — les éléments s'illuminent au hover (légère augmentation de luminosité), les transitions sont fluides et apaisantes.
+
+**Animation :** Durées moyennes (200-300ms), easing doux (cubic-bezier(0.4, 0, 0.2, 1)), fade-in progressif des contenus au scroll, micro-animations de respiration sur les indicateurs de statut.
+
+**Typography System :** Plus Jakarta Sans (titres, semibold 600) + DM Sans (corps, regular 400/medium 500). Tailles généreuses avec un line-height aéré (1.6-1.7).
+
+
+0.05
+
+
+---
+
+
+
+
+## Idée 3 : "Digital Command Center" — Design Dashboard Hospitalier
+
+**Design Movement :** Interface de commande inspirée des tableaux de bord de contrôle aérospatial (clin d'œil à Kourou/CSG) — données en temps réel, organisation modulaire, efficacité maximale.
+
+**Core Principles :**
+1. Densité d'information maîtrisée avec une lisibilité parfaite
+2. Modules indépendants et repositionnables (widget-based)
+3. Indicateurs visuels instantanés (badges, jauges, compteurs)
+4. Navigation contextuelle qui s'adapte à la section active
+
+**Color Philosophy :** Fond blanc cassé (#F1F5F9) avec sidebar en bleu marine profond (#0F172A) créant un ancrage visuel fort. Accent cyan vif (#06B6D4) pour les actions et les données en temps réel. Vert émeraude (#10B981) pour les statuts positifs, ambre (#F59E0B) pour les alertes.
+
+**Layout Paradigm :** Sidebar sombre fixe à gauche (navigation principale), header léger avec breadcrumb et actions rapides, zone de contenu en grille modulaire (CSS Grid) avec des cartes-widgets redimensionnables.
+
+**Signature Elements :** Mini-graphiques sparkline dans les cartes de statistiques, badges numériques animés sur la navigation, barre de progression subtile en haut de page lors des chargements.
+
+**Interaction Philosophy :** Efficacité et feedback — hover avec élévation (shadow increase), clic avec ripple effect subtil, drag & drop pour réorganiser les widgets du dashboard.
+
+**Animation :** Rapides et fonctionnelles (150-250ms), counter animations pour les chiffres, stagger reveal (30ms) pour les listes, slide-down pour les menus déroulants.
+
+**Typography System :** Outfit (titres et navigation, medium 500/semibold 600) + Inter (données et corps, regular 400/medium 500). Tailles compactes mais lisibles, utilisation de tabular-nums pour les chiffres.
+
+
+0.08
+
+
+---
+
+## Choix retenu : Idée 3 — "Digital Command Center"
+
+Cette approche est la plus adaptée pour une interface d'administration hospitalière car elle combine :
+- L'efficacité d'un tableau de bord de contrôle (essentiel pour les administrateurs)
+- Un ancrage visuel fort avec la sidebar sombre (navigation claire)
+- La modularité des widgets (gestion flexible du contenu)
+- Un clin d'œil à l'identité spatiale de Kourou
+- Une palette professionnelle et sobre qui reflète le milieu hospitalier