import React, { useState } from 'react';
import { Shield, Users, Phone, ChevronRight, ChevronLeft, Check, Eye, BarChart3, User, Home, LogOut, Bell, Plus, X, Star, Lock, Stethoscope, ClipboardList, Building, ArrowRight, ArrowUp, ArrowDown, MessageCircle, ChevronDown, Menu, Sparkles, FileText, Database, Code, Download, AlertTriangle, CheckCircle, Clock, Info, Scale, FileCheck, ShieldCheck, Globe, BookOpen, ExternalLink, Filter, Search, Settings, UserCog, Award, Activity, Trash2, Edit, UserPlus } from 'lucide-react';
// === DONNÉES ===
const ratings = {
outstanding: { label: 'Exemplaire', bg: 'bg-emerald-500', text: 'text-emerald-600', light: 'bg-emerald-50', stars: 5 },
good: { label: 'Satisfaisant', bg: 'bg-teal-500', text: 'text-teal-600', light: 'bg-teal-50', stars: 4 },
requires_improvement: { label: 'À améliorer', bg: 'bg-amber-500', text: 'text-amber-600', light: 'bg-amber-50', stars: 3 },
inadequate: { label: 'Insuffisant', bg: 'bg-rose-500', text: 'text-rose-600', light: 'bg-rose-50', stars: 2 },
};
const institutions = [
{ id: 1, name: 'HUG', fullName: 'Hôpitaux Universitaires de Genève', type: 'Hôpital universitaire', city: 'Genève', rating: 'good', reports: 89, verified: 71, contested: 3, avgDays: 3.8, trend: 'up', reliability: 94 },
{ id: 2, name: 'CHUV', fullName: 'Centre Hospitalier Universitaire Vaudois', type: 'Hôpital universitaire', city: 'Lausanne', rating: 'good', reports: 56, verified: 48, contested: 1, avgDays: 4.2, trend: 'stable', reliability: 96 },
{ id: 3, name: 'Hôpital de la Tour', fullName: 'Hôpital de la Tour', type: 'Clinique privée', city: 'Meyrin', rating: 'outstanding', reports: 12, verified: 11, contested: 0, avgDays: 2.1, trend: 'up', reliability: 98 },
{ id: 4, name: 'Unisanté', fullName: 'Unisanté', type: 'Centre universitaire', city: 'Lausanne', rating: 'outstanding', reports: 23, verified: 21, contested: 0, avgDays: 2.8, trend: 'up', reliability: 97 },
{ id: 5, name: 'Hôpital du Valais', fullName: 'Hôpital du Valais - Sion', type: 'Hôpital cantonal', city: 'Sion', rating: 'requires_improvement', reports: 34, verified: 22, contested: 5, avgDays: 6.2, trend: 'down', reliability: 85 },
{ id: 6, name: 'Hôpital Riviera-Chablais', fullName: 'Hôpital Riviera-Chablais', type: 'Hôpital régional', city: 'Rennaz', rating: 'inadequate', reports: 67, verified: 28, contested: 12, avgDays: 9.4, trend: 'down', reliability: 78 },
];
const dmsQuestions = [
{ id: 1, text: "On vous traite avec moins de courtoisie que les autres personnes.", category: 'respect' },
{ id: 2, text: "On vous traite avec moins de respect que les autres personnes.", category: 'respect' },
{ id: 3, text: "Vous recevez un service de moins bonne qualité que les autres.", category: 'equity' },
{ id: 4, text: "Un médecin ou une infirmière agit comme s'il ou elle pensait que vous n'étiez pas intelligent(e).", category: 'stereotypes' },
{ id: 5, text: "Un médecin ou une infirmière agit comme s'il ou elle avait peur de vous.", category: 'stereotypes' },
{ id: 6, text: "Un médecin ou une infirmière agit comme s'il ou elle était supérieur(e) à vous.", category: 'respect' },
{ id: 7, text: "Vous avez l'impression qu'un médecin ou une infirmière n'écoute pas ce que vous dites.", category: 'listening' },
];
const incidentTypes = [
'Propos discriminatoires ou racistes', 'Refus de soins injustifié', 'Traitement différencié',
'Moqueries ou remarques déplacées', 'Attitude condescendante', 'Manque de respect',
'Non-écoute ou minimisation des symptômes', 'Stéréotypes ou préjugés', 'Temps d\'attente excessif', 'Autre'
];
const services = [
'Urgences', 'Consultation externe', 'Hospitalisation', 'Maternité', 'Pédiatrie', 'Chirurgie',
'Radiologie', 'Laboratoire', 'Accueil', 'Psychiatrie', 'Oncologie', 'Hématologie',
'Cardiologie', 'Gynécologie', 'Neurologie', 'Médecine interne', 'Gériatrie', 'Autre'
];
const resourceOrgs = [
{ id: 1, name: 'FAANG', fullName: 'Fédération des Associations Afrodescendant.e.x.s de Genève', services: ['Médiation', 'Accompagnement'], phone: '+41 22 301 16 50' },
{ id: 2, name: 'ASPR-GE', fullName: 'Association des Professionnel·le·s de Santé Racisé·e·s de Genève', services: ['Pair-aidance', 'Formation'], phone: '+41 78 xxx xx xx' },
{ id: 3, name: 'Suisse Drépano', fullName: 'Association Suisse Drépanocytose', services: ['Soutien patients', 'Advocacy'], phone: '+41 79 128 63 68' },
{ id: 4, name: 'Centre LAVI', fullName: 'Centre de consultation pour victimes', services: ['Aide juridique', 'Soutien'], phone: '+41 22 320 01 02' },
];
const healthcarePros = [
{ id: 1, name: 'Dr. Aminata Koné', specialty: 'Médecine générale', institution: 'HUG', languages: ['Français', 'Bambara'], rating: 4.8 },
{ id: 2, name: 'Dr. Jean-Pierre Müller', specialty: 'Hématologie', institution: 'CHUV', languages: ['Français', 'Allemand'], rating: 4.9 },
{ id: 3, name: 'Sophie Ndongo', specialty: 'Infirmière spécialisée', institution: 'Unisanté', languages: ['Français', 'Wolof'], rating: 4.7 },
];
const stats = { total: 318, verified: 251, avgDays: 4.1, institutions: 6, reliability: 89 };
// === COMPOSANTS UTILITAIRES ===
const RatingBadge = ({ r, size = 'sm' }) => {
const rt = ratings[r];
const sizes = { xs: 'px-1.5 py-0.5 text-xs', sm: 'px-2 py-1 text-xs', md: 'px-3 py-1.5 text-sm' };
return {rt.label} ;
};
const Stars = ({ r }) => {
const rt = ratings[r];
return (
{[...Array(5)].map((_, i) => (
))}
);
};
const ReliabilityBadge = ({ score }) => {
const color = score >= 90 ? 'emerald' : score >= 75 ? 'amber' : 'rose';
return (
{score}% fiable
);
};
// === COMPOSANT FORMULAIRE SIGNALEMENT (EXTRAIT) ===
const NewReportForm = ({ step, setStep, data, setData, dmsAnswers, setDmsAnswers, onSubmit, onComplete }) => {
const steps = ['Vérification', 'Rôle', 'Lieu', 'Incident', 'Questionnaire', 'Détails', 'Confirmation'];
const [code, setCode] = useState(null);
const genCode = () => {
const c = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
let r = 'CW';
for (let i = 0; i < 6; i++) r += c[Math.floor(Math.random() * c.length)];
return r;
};
const submit = () => {
const c = genCode();
setCode(c);
onSubmit(c);
setStep(6);
};
return (
{/* Progress */}
{steps.map((s, i) => (
{i < step ? : i + 1}
{i < steps.length - 1 &&
}
))}
{steps[step]}
{/* Step 0: Vérification anti-bashing */}
{step === 0 && (
Vérification préalable
Pour garantir la fiabilité des signalements
Pourquoi cette étape ?
Afin de protéger les établissements contre les signalements abusifs et garantir la crédibilité de l'observatoire, nous vérifions certaines informations. Les signalements sont modérés avant publication.
)}
{/* Step 1: Rôle */}
{step === 1 && (
Vous êtes :
Cette information contextualise votre témoignage
{[
{ value: 'victim', icon: User, label: 'Victime directe', desc: 'J\'ai personnellement vécu cette situation' },
{ value: 'witness', icon: Eye, label: 'Témoin', desc: 'J\'ai observé une situation envers quelqu\'un' },
{ value: 'relative', icon: Users, label: 'Proche', desc: 'Mon proche a subi cette situation' },
{ value: 'professional', icon: Stethoscope, label: 'Professionnel·le', desc: 'J\'ai été témoin en tant que soignant·e' },
].map(r => (
setData({...data, role: r.value})} className={`p-4 rounded-xl border-2 text-left ${data.role === r.value ? 'border-teal-500 bg-teal-50' : 'border-gray-100 hover:border-gray-200'}`}>
{r.label}
{r.desc}
))}
)}
{/* Step 2: Lieu */}
{step === 2 && (
Où et quand ?
Précisez l'établissement et le contexte
Établissement *
setData({...data, institution: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-teal-500 outline-none">
Sélectionner...
{institutions.map(i => {i.fullName} )}
Autre établissement...
{data.institution === 'other' && (
setData({...data, otherInstitution: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 outline-none" />
)}
Service concerné *
setData({...data, service: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-teal-500 outline-none">
Sélectionner...
{services.map(s => {s} )}
)}
{/* Step 3: Type d'incident */}
{step === 3 && (
Type d'incident
Sélectionnez ce qui correspond le mieux
{incidentTypes.map(type => (
setData({...data, incidentType: type})} className={`w-full p-4 rounded-xl border-2 text-left ${data.incidentType === type ? 'border-teal-500 bg-teal-50' : 'border-gray-100 hover:border-gray-200'}`}>
{type}
))}
)}
{/* Step 4: Questionnaire DMS */}
{step === 4 && (
Questionnaire d'expérience
À quelle fréquence avez-vous vécu ces situations ?
{dmsQuestions.map((q, idx) => (
{idx + 1}. {q.text}
{['Jamais', 'Rarement', 'Parfois', 'Souvent', 'Toujours'].map(opt => (
setDmsAnswers({...dmsAnswers, [q.id]: opt})} className={`px-3 py-2 rounded-lg text-sm ${dmsAnswers[q.id] === opt ? 'bg-teal-500 text-white' : 'bg-white border border-gray-200 text-gray-600'}`}>
{opt}
))}
))}
)}
{/* Step 5: Détails */}
{step === 5 && (
Détails de l'incident
Décrivez ce qui s'est passé
Description *
Impact (optionnel)
Souhaitez-vous être recontacté·e ?
setData({...data, contact: 'yes'})} className={`flex-1 p-3 rounded-xl border-2 ${data.contact === 'yes' ? 'border-teal-500 bg-teal-50' : 'border-gray-100'}`}>Oui
setData({...data, contact: 'no'})} className={`flex-1 p-3 rounded-xl border-2 ${data.contact === 'no' ? 'border-teal-500 bg-teal-50' : 'border-gray-100'}`}>Non
{data.contact === 'yes' && (
setData({...data, email: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 outline-none" />
)}
)}
{/* Step 6: Confirmation */}
{step === 6 && code && (
Signalement envoyé
Votre témoignage sera examiné par nos modérateurs sous 48h.
Code de suivi confidentiel :
{code}
Processus de validation
• Examen par un modérateur sous 48h
• L'établissement peut contester sous 14 jours
• Publication après validation
• Impact sur la note après période probatoire
Retour à l'accueil
)}
{/* Navigation */}
{step < 6 && (
setStep(Math.max(0, step - 1))} disabled={step === 0} className="px-4 py-2 rounded-lg text-gray-500 disabled:opacity-50">
Précédent
{step < 5 ? (
setStep(step + 1)} disabled={
(step === 0 && (!data.postalCode || !data.charterAccepted)) ||
(step === 1 && !data.role) ||
(step === 2 && (!data.institution || !data.service || !data.date)) ||
(step === 3 && !data.incidentType) ||
(step === 4 && Object.keys(dmsAnswers).length < 3)
} className="px-6 py-2 rounded-xl bg-teal-600 text-white font-medium disabled:opacity-50">
Suivant
) : (
Envoyer
)}
)}
);
};
// === COMPOSANT PRINCIPAL ===
export default function CareWatch() {
const [view, setView] = useState('landing');
const [userType, setUserType] = useState(null);
const [showLogin, setShowLogin] = useState(false);
const [loginType, setLoginType] = useState('patient');
const [selectedInst, setSelectedInst] = useState(null);
const [mobileMenu, setMobileMenu] = useState(false);
const [sidebarOpen, setSidebarOpen] = useState(true);
const [reportStep, setReportStep] = useState(0);
const [reportData, setReportData] = useState({});
const [dmsAnswers, setDmsAnswers] = useState({});
const [myReports, setMyReports] = useState([]);
const handleReportSubmit = (code) => {
const newReport = {
id: Date.now(), code, date: new Date().toLocaleDateString('fr-CH'),
institution: reportData.institution, service: reportData.service,
type: reportData.incidentType, status: 'En modération', ...reportData, dmsAnswers
};
setMyReports(prev => [newReport, ...prev]);
};
const handleReportComplete = () => {
setView('patient-home');
setReportStep(0);
setReportData({});
setDmsAnswers({});
};
// === LOGIN MODAL ===
const LoginModal = () => (
);
// === INSTITUTION DETAIL MODAL ===
const InstDetail = ({ inst, onClose }) => (
{inst.fullName}
{inst.type} • {inst.city}
{inst.trend === 'up' ? <> Amélioration> :
inst.trend === 'down' ? <> Dégradation> :
<> Stable>}
{inst.reports}
Signalements
{inst.contested}
Contestés
{inst.avgDays}j
Temps rép.
Processus d'évaluation équitable
Les signalements sont vérifiés par des modérateurs indépendants. L'établissement dispose d'un droit de réponse et peut contester les signalements qu'il estime infondés.
{ onClose(); setReportStep(0); setReportData({ institution: inst.fullName }); setView('new-report'); }} className="w-full py-3 rounded-xl bg-teal-600 text-white font-medium">
Signaler un incident
);
// === LANDING PAGE ===
const Landing = () => (
{/* Header */}
{/* Hero */}
Plateforme indépendante et participative
Observatoire Suisse de l'Équité des Soins
Évaluer • Améliorer • Garantir l'équité
Plateforme indépendante d'évaluation de la qualité et de l'équité dans les établissements de santé suisses.
Signalez anonymement, consultez les évaluations, contribuez à des soins plus justes.
{/* Mission cards */}
{[
{ icon: Eye, title: 'Transparence', desc: 'Évaluations publiques basées sur des signalements vérifiés et une méthodologie rigoureuse' },
{ icon: Scale, title: 'Équité', desc: 'Processus équitable avec droit de réponse pour les établissements et modération indépendante' },
{ icon: Database, title: 'Open Data', desc: 'Données ouvertes et API publique pour la recherche et l\'amélioration du système de santé' },
].map((c, i) => (
))}
{/* Primary Access */}
{ setLoginType('patient'); setShowLogin(true); }} className="group p-6 md:p-8 rounded-2xl bg-white border-2 border-teal-100 hover:border-teal-300 hover:shadow-lg transition-all text-left">
Je suis patient·e ou proche
Signalez une expérience, consultez les évaluations, trouvez de l'aide et des soignants sensibilisés.
Accéder
{ setLoginType('professional'); setShowLogin(true); }} className="group p-6 md:p-8 rounded-2xl bg-white border-2 border-amber-100 hover:border-amber-300 hover:shadow-lg transition-all text-left">
Je suis professionnel·le de santé
Signalez des situations, rejoignez le réseau pair-aidance ASPR-GE, accédez aux formations.
Accéder
{/* Stats with reliability */}
Statistiques de l'observatoire
{stats.total}
Signalements
{stats.verified}
Vérifiés
{stats.avgDays}j
Temps moyen
{stats.institutions}
Établissements
{stats.reliability}%
Fiabilité
{/* How it works */}
Comment fonctionne l'observatoire ?
{[
{ icon: ClipboardList, title: '1. Signalement', desc: 'Remplissez le formulaire avec vérification préalable' },
{ icon: ShieldCheck, title: '2. Modération', desc: 'Examen par nos modérateurs indépendants (48h)' },
{ icon: MessageCircle, title: '3. Droit de réponse', desc: 'L\'établissement peut contester (14 jours)' },
{ icon: FileCheck, title: '4. Validation', desc: 'Publication après période probatoire' },
{ icon: BarChart3, title: '5. Impact', desc: 'Mise à jour des évaluations' },
].map((s, i) => (
))}
{/* Institutions */}
Évaluations des établissements
setView('open-data')} className="text-sm text-teal-600 flex items-center gap-1">
Open Data
{institutions.map(inst => (
setSelectedInst(inst)} className="w-full flex items-center gap-4 p-4 hover:bg-gray-50 text-left">
{inst.fullName}
{inst.city} • {inst.reports} signalements • {inst.reliability}% fiable
))}
{/* Open Data CTA */}
Open Data & API
Accédez aux données anonymisées de l'observatoire. Téléchargez les datasets ou utilisez notre API REST pour vos recherches et analyses.
setView('open-data')} className="px-6 py-3 rounded-xl bg-blue-600 text-white font-medium flex items-center gap-2">
Explorer
{/* Partners */}
En partenariat avec
{['Convivens Lab • HES-SO', 'FAANG', 'ASPR-GE', 'Suisse Drépano', 'KimboCare', 'Centre LAVI'].map(p => (
{p}
))}
{/* Footer */}
{showLogin &&
}
{selectedInst &&
setSelectedInst(null)} />}
);
// === OPEN DATA PAGE ===
const OpenDataPage = () => (
Données ouvertes
Accédez librement aux données anonymisées de l'observatoire pour vos recherches, analyses et applications.
{/* Datasets */}
Datasets disponibles
{[
{ name: 'Évaluations des établissements', desc: 'Notes, critères, tendances par établissement', format: 'CSV, JSON', size: '124 KB', updated: '30 jan 2025' },
{ name: 'Signalements anonymisés', desc: 'Types d\'incidents, services, périodes (anonymisé)', format: 'CSV, JSON', size: '2.3 MB', updated: '30 jan 2025' },
{ name: 'Scores DMS agrégés', desc: 'Résultats du questionnaire par établissement', format: 'CSV, JSON', size: '89 KB', updated: '30 jan 2025' },
{ name: 'Séries temporelles', desc: 'Évolution des évaluations dans le temps', format: 'CSV, JSON', size: '456 KB', updated: '30 jan 2025' },
].map((d, i) => (
{d.name}
{d.desc}
{d.format} • {d.size} • Màj: {d.updated}
Télécharger
))}
{/* API */}
API REST
Base URL
https://api.carewatch.ch/v1
Endpoints disponibles
{[
{ method: 'GET', endpoint: '/institutions', desc: 'Liste des établissements avec évaluations' },
{ method: 'GET', endpoint: '/institutions/{id}', desc: 'Détail d\'un établissement' },
{ method: 'GET', endpoint: '/reports/stats', desc: 'Statistiques agrégées des signalements' },
{ method: 'GET', endpoint: '/dms/scores', desc: 'Scores DMS par établissement' },
{ method: 'GET', endpoint: '/timeseries', desc: 'Données temporelles' },
].map((e, i) => (
{e.method}
{e.endpoint}
{e.desc}
))}
Exemple de requête
{`curl -X GET "https://api.carewatch.ch/v1/institutions" \\
-H "Accept: application/json"`}
{/* License */}
Licence et conditions
Les données sont publiées sous licence Creative Commons BY-NC-SA 4.0 .
Vous pouvez les utiliser librement pour des usages non commerciaux avec attribution.
• Attribution obligatoire : "Source: CareWatch.ch"
• Usage commercial : contactez-nous
• Pas de ré-identification des individus
);
// === ABOUT PAGE ===
const AboutPage = () => (
Notre mission
CareWatch est l'Observatoire Suisse de l'Équité des Soins, une plateforme indépendante et participative dédiée à l'amélioration de la qualité et de l'équité dans les établissements de santé suisses.
Protection contre les abus
Pour garantir la fiabilité des évaluations et protéger les établissements contre les signalements abusifs, nous avons mis en place plusieurs mécanismes :
{[
{ icon: ShieldCheck, title: 'Vérification préalable', desc: 'Code postal et charte de bonne conduite' },
{ icon: Eye, title: 'Modération indépendante', desc: 'Examen de chaque signalement sous 48h' },
{ icon: MessageCircle, title: 'Droit de réponse', desc: '14 jours pour contester un signalement' },
{ icon: Clock, title: 'Période probatoire', desc: 'Impact sur la note différé de 30 jours' },
{ icon: Scale, title: 'Score de fiabilité', desc: 'Pondération selon cohérence et vérification' },
].map((m, i) => (
))}
Nos partenaires
{resourceOrgs.map(o => (
))}
Contact
contact@carewatch.ch
Convivens Lab, HEG Genève, HES-SO
);
// === METHODOLOGY PAGE ===
const MethodologyPage = () => (
Comment sont calculées les évaluations ?
1. Score de fiabilité
Chaque établissement dispose d'un score de fiabilité (0-100%) calculé selon :
• Nombre de signalements vérifiés vs contestés
• Cohérence entre les signalements
• Taux de preuves fournies
• Historique des contestations validées
2. Questionnaire DMS
Le questionnaire "Discrimination in Medical Settings" est un outil validé scientifiquement pour mesurer les expériences de discrimination dans les soins.
3. Niveaux de notation
{Object.entries(ratings).map(([k, r]) => (
))}
4. Processus anti-bashing
• Vérification préalable obligatoire (code postal, charte)
• Modération sous 48h par équipe indépendante
• Droit de contestation pour l'établissement (14 jours)
• Impact sur la note différé de 30 jours
• Signalements pondérés par le score de fiabilité
);
// === SIDEBAR ===
const Sidebar = ({ type }) => {
const menus = {
patient: [
{ icon: Home, label: 'Accueil', v: 'patient-home' },
{ icon: Plus, label: 'Signaler', v: 'new-report' },
{ icon: ClipboardList, label: 'Mes signalements', v: 'my-reports' },
{ icon: Shield, label: 'Mes droits', v: 'rights' },
{ icon: Building, label: 'Établissements', v: 'institutions' },
{ icon: Stethoscope, label: 'Soignants', v: 'healthcare-pros' },
{ icon: Users, label: 'Ressources', v: 'resources' },
],
professional: [
{ icon: Home, label: 'Accueil', v: 'professional-home' },
{ icon: Plus, label: 'Signaler', v: 'new-report' },
{ icon: Users, label: 'Pair-aidance', v: 'peer-support' },
{ icon: Building, label: 'Établissements', v: 'institutions' },
{ icon: BookOpen, label: 'Formations', v: 'formations' },
],
admin: [
{ icon: BarChart3, label: 'Dashboard', v: 'dashboard' },
{ icon: Building, label: 'Établissements', v: 'institutions' },
{ icon: ClipboardList, label: 'Signalements', v: 'admin-reports' },
{ icon: UserCog, label: 'Utilisateurs', v: 'users' },
{ icon: Database, label: 'Open Data', v: 'open-data' },
{ icon: Settings, label: 'Paramètres', v: 'settings' },
],
};
const items = menus[type] || menus.patient;
return (
{sidebarOpen &&
CareWatch }
{items.map(i => (
setView(i.v)} className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl ${view === i.v ? 'bg-teal-50 text-teal-700' : 'text-gray-600 hover:bg-gray-50'}`}>
{sidebarOpen && {i.label} }
))}
setSidebarOpen(!sidebarOpen)} className="w-full flex items-center gap-3 px-3 py-2 rounded-xl text-gray-400 hover:bg-gray-50">
{sidebarOpen ? : }
{ setView('landing'); setUserType(null); }} className="w-full flex items-center gap-3 px-3 py-2 rounded-xl text-gray-400 hover:bg-gray-50">
{sidebarOpen && Déconnexion }
);
};
// === MOBILE NAV ===
const MobileNav = ({ type }) => {
const items = type === 'patient'
? [{ icon: Home, v: 'patient-home' }, { icon: Plus, v: 'new-report' }, { icon: ClipboardList, v: 'my-reports' }, { icon: Shield, v: 'rights' }]
: type === 'professional'
? [{ icon: Home, v: 'professional-home' }, { icon: Plus, v: 'new-report' }, { icon: Users, v: 'peer-support' }]
: [{ icon: BarChart3, v: 'dashboard' }, { icon: Building, v: 'institutions' }, { icon: ClipboardList, v: 'admin-reports' }];
return (
{items.map(i => setView(i.v)} className={`p-3 rounded-xl ${view === i.v ? 'text-teal-600' : 'text-gray-400'}`}> )}
);
};
// === PATIENT HOME ===
const PatientHome = () => (
Bienvenue
Signalez une expérience, consultez vos droits ou trouvez de l'aide.
{ setReportStep(0); setReportData({}); setDmsAnswers({}); setView('new-report'); }} className="px-6 py-3 rounded-xl bg-teal-600 text-white font-medium flex items-center gap-2">
Faire un signalement
{[
{ icon: Shield, label: 'Mes droits', v: 'rights', color: 'teal' },
{ icon: ClipboardList, label: 'Mes signalements', v: 'my-reports', color: 'amber' },
{ icon: Stethoscope, label: 'Soignants', v: 'healthcare-pros', color: 'emerald' },
{ icon: Users, label: 'Ressources', v: 'resources', color: 'rose' },
].map(item => (
setView(item.v)} className="p-4 rounded-xl bg-white border hover:border-gray-200 text-left">
{item.label}
))}
);
// === MY REPORTS ===
const MyReportsView = () => (
Mes signalements
{myReports.length === 0 ? (
Aucun signalement
setView('new-report')} className="mt-4 text-teal-600 font-medium">Faire un signalement
) : (
{myReports.map(r => (
))}
)}
);
// === RIGHTS VIEW ===
const RightsView = () => (
Vos droits
{[
{ title: 'Droit à des soins de qualité', desc: 'Sans discrimination basée sur l\'origine, la couleur de peau, la religion.' },
{ title: 'Droit au respect', desc: 'Votre dignité et vos convictions doivent être respectées.' },
{ title: 'Droit à l\'information', desc: 'Information claire sur votre état et les traitements.' },
{ title: 'Droit de refuser', desc: 'Vous pouvez refuser un traitement après information.' },
{ title: 'Droit de porter plainte', desc: 'Signalement auprès de l\'établissement ou du médecin cantonal.' },
].map((r, i) => (
))}
);
// === HEALTHCARE PROS ===
const HealthcareProsView = () => (
Soignants sensibilisés
{healthcarePros.map(p => (
{p.name.split(' ').map(n => n[0]).join('')}
{p.name}
{p.specialty}
{p.institution}
{p.rating}
))}
);
// === RESOURCES ===
const ResourcesView = () => (
Ressources
{resourceOrgs.map(o => (
{o.name}
{o.fullName}
{o.services.map(s => {s} )}
{o.phone}
))}
);
// === INSTITUTIONS ===
const InstitutionsView = () => (
Établissements
{institutions.map(i => (
setSelectedInst(i)} className="w-full bg-white rounded-xl border p-4 flex items-center gap-4 text-left hover:border-gray-200">
{i.fullName}
{i.city} • {i.reliability}% fiable
))}
{selectedInst &&
setSelectedInst(null)} />}
);
// === DASHBOARD ===
const Dashboard = () => (
Dashboard
{[
{ label: 'Signalements', value: stats.total, color: 'teal' },
{ label: 'Vérifiés', value: stats.verified, color: 'emerald' },
{ label: 'En modération', value: 67, color: 'amber' },
{ label: 'Fiabilité', value: stats.reliability + '%', color: 'blue' },
].map(s => (
))}
À surveiller
{institutions.filter(i => i.rating === 'inadequate' || i.rating === 'requires_improvement').map(i => (
))}
);
// === RENDER ===
const renderContent = () => {
switch (view) {
case 'landing': return ;
case 'about': return ;
case 'methodology': return ;
case 'open-data': return ;
case 'patient-home': return ;
case 'professional-home': return ;
case 'new-report': return ;
case 'my-reports': return ;
case 'rights': return ;
case 'healthcare-pros': return ;
case 'resources': return ;
case 'institutions': return ;
case 'dashboard': return ;
default: return Vue: {view}
;
}
};
if (['landing', 'about', 'methodology', 'open-data'].includes(view)) return renderContent();
return (
{view.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
2
{renderContent()}
{selectedInst &&
setSelectedInst(null)} />}
);
}