Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions Dta.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
```powershell
┌─────────────────────┐
│ Utilisateur │
└─────────┬───────────┘
┌──────────────────────────────┐
│ HOME │
│ Présentation du service │
│ Accès Liste / Carte │
└─────────┬────────────────────┘
┌─────────────────────────────────────────┐
│ Demande de géolocalisation navigateur │
└─────────┬───────────────┬───────────────┘
│ │
│ Acceptée │ Refusée / erreur
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Coordonnées GPS │ │ Fallback : Paris │
│ utilisateur │ │ (valeur par défaut)
└─────────┬────────┘ └─────────┬────────┘
│ │
└──────────┬───────────┘
┌────────────────────────────────┐
│ Calcul des établissements │
│ hospitaliers les plus proches │
└─────────┬──────────────────────┘
┌────────────────────────────────────────────────────┐
│ Agrégateur de données : HospitalWithMock │
├────────────────────────────────────────────────────┤
│ • Données Hôpital │
│ - Nom │
│ - Téléphone │
│ - Adresse │
│ - Coordonnées géographiques │
│ │
│ • Données Services (Mock) │
│ - Accès pompiers │
│ - Assistante sociale │
│ - Spécialités médicales │
│ │
│ • Données Accessibilité │
│ - PMR : parking, entrée, toilettes, assises │
└─────────┬──────────────────────────────────────────┘
┌───────────────────────────────┐
│ Exploration │
└─────────┬───────────┬─────────┘
│ │
▼ ▼
┌──────────────────┐ ┌────────────────────────────┐
│ Vue Liste │ │ Carte interactive Leaflet │
│ Filtrage dynamique│ │ Marqueurs d urgence │
└─────────┬────────┘ └─────────┬──────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────┐
│ Recherche & Filtres avancés │
├─────────────────────────────────────────────┤
│ • Recherche textuelle (nom hôpital) │
│ • Spécifications │
│ - Accès pompiers │
│ - Assistante sociale │
│ - Accessibilité PMR │
│ • Spécialisations médicales (15+) │
│ - Cardiologie, Rhumatologie, Gynécologie │
│ - Neurochirurgie, Chirurgie pédiatrique… │
└─────────┬───────────────────────────────────┘
┌──────────────────────────────────────┐
│ Fiche établissement hospitalier │
├──────────────────────────────────────┤
│ • Coordonnées & contact │
│ • Spécialités médicales │
│ • Accessibilité PMR │
│ • Flux d activité (Mock) │
│ → Indicateur d affluence │
└─────────┬────────────────────────────┘
┌──────────────────────────────────────┐
│ Prise de décision utilisateur │
│ Choix de l établissement le plus │
│ adapté à sa situation │
└──────────────────────────────────────┘

```
16 changes: 16 additions & 0 deletions client/app/globals.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@
@tailwind components;
@tailwind utilities;

:root {
--text-scale: 1;
/* Contraste et lisibilité : focus visible pour navigation clavier */
--focus-ring: 4px solid #dc2626;
--focus-ring-offset: 2px;
}

* {
box-sizing: border-box;
padding: 0;
margin: 0;
}

html {
font-size: calc(100% * var(--text-scale, 1));
}

html,
body {
max-width: 100vw;
Expand All @@ -20,6 +31,11 @@ body {
-moz-osx-font-smoothing: grayscale;
}

/* Cohérence pour les lecteurs d'écran : éléments décoratifs masqués à l'annonce */
[aria-hidden="true"] {
@apply pointer-events-none;
}

a {
color: inherit;
text-decoration: none;
Expand Down
6 changes: 3 additions & 3 deletions client/app/hopitaux/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export default function HospitalDetailPage({ params }: { params: Promise<{ id: s
return (
<>
<Header />
<main className="min-h-screen bg-gradient-to-br from-slate-50 via-slate-100 to-slate-50">
<main id="main-content" className="min-h-screen bg-gradient-to-br from-slate-50 via-slate-100 to-slate-50" tabIndex={-1}>
<div className="px-4 py-6 sm:px-6 max-w-2xl mx-auto pb-8">
<Loading message="Chargement..." ariaLabel="Chargement des caractéristiques de l'hôpital" />
</div>
Expand All @@ -152,7 +152,7 @@ export default function HospitalDetailPage({ params }: { params: Promise<{ id: s
return (
<>
<Header />
<main className="min-h-screen bg-gradient-to-br from-slate-50 via-slate-100 to-slate-50">
<main id="main-content" className="min-h-screen bg-gradient-to-br from-slate-50 via-slate-100 to-slate-50" tabIndex={-1}>
<div className="px-4 py-6 sm:px-6 max-w-2xl mx-auto pb-8">
<ErrorMessage message={error || 'Hôpital non trouvé'} />
<Link
Expand Down Expand Up @@ -195,7 +195,7 @@ export default function HospitalDetailPage({ params }: { params: Promise<{ id: s
return (
<>
<Header />
<main className="bg-white">
<main id="main-content" className="bg-white" tabIndex={-1}>
<section className="relative shadow-[0_4px_4px_rgba(0,0,0,0.25)]" aria-label="En-tête de la page">
<div className="absolute top-0 left-0 w-full h-full">
<Image
Expand Down
37 changes: 35 additions & 2 deletions client/app/hopitaux/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import SearchBar from '@/components/SearchBar';
import MultiSelectFilter from '@/components/MultiSelectFilter';
import Loading from '@/components/Loading';
import ErrorMessage from '@/components/ErrorMessage';
import AccessibilityBar from '@/components/AccessibilityBar';
import { getHospitals } from '@/app/api/hospitals/route';
import { sortByRecommendation } from '@/lib/recommendation';
import type { Professionnal, PlaceDetails, MockHospitalData, HospitalWithMock } from '@/types/api';
export const dynamic = 'force-dynamic';

Expand Down Expand Up @@ -159,10 +161,14 @@ export default function HopitauxPage() {
return filtered;
}, [hospitals, searchQuery, selectedSpecifications, selectedSpecializations]);

const { sorted: sortedHospitals, recommendedRecordId } = useMemo(() => {
return sortByRecommendation(filteredHospitals, selectedSpecializations, null);
}, [filteredHospitals, selectedSpecializations]);

return (
<>
<Header />
<main className="min-h-screen bg-gradient-to-br from-slate-50 via-slate-100 to-slate-50">
<main id="main-content" className="min-h-screen bg-gradient-to-br from-slate-50 via-slate-100 to-slate-50" tabIndex={-1}>
<div className="px-4 py-6 sm:px-6 max-w-4xl mx-auto pb-8">
<h1 className="sr-only text-2xl md:text-3xl font-bold text-primary mb-6 text-center">
Hôpitaux avec services d&apos;urgence
Expand Down Expand Up @@ -196,10 +202,37 @@ export default function HopitauxPage() {
</div>
)}

{/* En gros c'est un texte qui permet d'affiché un texte qui explique de pourquoi Le site recommandé est mis
en évidence visuellement et textuellement. C'est pour ça que je le laisse en commentaire pour l'instant, mais
je pense que c'est important de le remettre à terme, peut-être en l'intégrant dans une section "Comment ça marche ?"
ou "Pourquoi ce classement ?"
*/}
{
/* {!loading && !error && sortedHospitals.length > 0 && (
<section
className="mb-6 p-4 bg-white/80 rounded-xl border-2 border-primary text-left"
aria-labelledby="critères-affichage"
>
<h2 id="critères-affichage" className="text-base font-bold text-primary mb-2">
Critères d&apos;affichage
</h2>
<p className="text-sm text-black leading-relaxed">
La liste est triée selon : <strong>distance</strong>, <strong>trafic</strong> (affluence), <strong>spécialités médicales</strong> et <strong>accessibilité</strong> (entrée, parking, toilettes, places assises). Le site <strong>recommandé</strong> est mis en évidence en premier avec un bandeau « Recommandé ».
</p>
<div className="mt-4 pt-4 border-t border-primary/30">
<AccessibilityBar />
</div>
</section>
)} */
}

{loading && <Loading message="Localisation en cours..." ariaLabel="Chargement des hôpitaux à proximité" />}
{error && <ErrorMessage message={error} />}
{!loading && !error && (
<HospitalList hospitals={filteredHospitals} />
<HospitalList
hospitals={sortedHospitals}
recommendedRecordId={recommendedRecordId}
/>
)}
</div>
</main>
Expand Down
9 changes: 8 additions & 1 deletion client/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import './globals.scss'
import { Metadata } from 'next';

import TextScaleInit from '@/components/TextScaleInit';
export const metadata: Metadata = {
title: {
default: 'Quelles Urgences',
Expand All @@ -23,6 +23,13 @@ export default function RootLayout({
<link rel="icon" href="/favicon.ico" />
</head>
<body className="pb-20 md:pb-0 md:pl-64">
<TextScaleInit />
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-[100] focus:px-4 focus:py-2 focus:bg-primary focus:text-white focus:ring-4 focus:ring-red-600 focus:rounded-lg"
>
Aller au contenu principal
</a>
{children}
</body>
</html>
Expand Down
2 changes: 1 addition & 1 deletion client/app/map/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default function MapPage() {
</div>
</div>
{/* Desktop: Normal layout */}
<main className="hidden md:block min-h-screen bg-gradient-to-br from-slate-50 via-slate-100 to-slate-50">
<main id="main-content" className="hidden md:block min-h-screen bg-gradient-to-br from-slate-50 via-slate-100 to-slate-50" tabIndex={-1}>
<div className="px-4 py-6 sm:px-6 max-w-4xl mx-auto pb-8">
<h1 className="text-2xl md:text-3xl font-bold text-primary mb-6 text-center">
Carte des urgences
Expand Down
2 changes: 1 addition & 1 deletion client/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default function Home() {
return (
<>
<Header />
<main className="bg-white">
<main id="main-content" className="bg-white" tabIndex={-1}>
<section className="relative shadow-[0_4px_4px_rgba(0,0,0,0.25)]" aria-label="En-tête de la page">
<div className="absolute top-0 left-0 w-full h-full">
<Image
Expand Down
64 changes: 64 additions & 0 deletions client/components/AccessibilityBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use client';

import { useState, useEffect } from 'react';

const STORAGE_KEY = 'urgences-text-scale';
const MIN = 0.9;
const MAX = 1.5;
const STEP = 0.15;

function getStoredScale(): number {
if (typeof window === 'undefined') return 1;
try {
const v = localStorage.getItem(STORAGE_KEY);
if (v != null) {
const n = parseFloat(v);
if (n >= MIN && n <= MAX) return n;
}
} catch {
// ignore
}
return 1;
}

export default function AccessibilityBar() {
const [scale, setScale] = useState(() => getStoredScale());

useEffect(() => {
document.documentElement.style.setProperty('--text-scale', String(scale));
try {
localStorage.setItem(STORAGE_KEY, String(scale));
} catch {
// ignore
}
}, [scale]);

const increase = () => setScale((s) => Math.min(MAX, s + STEP));
const decrease = () => setScale((s) => Math.max(MIN, s - STEP));

return (
<div className="flex items-center gap-2 flex-wrap" role="group" aria-label="Taille du texte">
<button
type="button"
onClick={decrease}
disabled={scale <= MIN}
className="min-w-[44px] min-h-[44px] px-3 py-2 rounded-lg border-2 border-primary bg-white text-primary font-bold hover:bg-primary hover:text-white focus:outline-none focus:ring-4 focus:ring-red-600 disabled:opacity-50 disabled:cursor-not-allowed"
aria-label="Réduire la taille du texte"
>
A−
</button>
<span className="text-sm font-medium text-black" aria-live="polite">
Taille du texte
</span>
<button
type="button"
onClick={increase}
disabled={scale >= MAX}
className="min-w-[44px] min-h-[44px] px-3 py-2 rounded-lg border-2 border-primary bg-white text-primary font-bold hover:bg-primary hover:text-white focus:outline-none focus:ring-4 focus:ring-red-600 disabled:opacity-50 disabled:cursor-not-allowed"
aria-label="Agrandir la taille du texte"
>
A+
</button>
</div>
);
}
29 changes: 29 additions & 0 deletions client/components/TextScaleInit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client';

import { useEffect } from 'react';

const STORAGE_KEY = 'urgences-text-scale';
const MIN = 0.9;
const MAX = 1.5;

function getStoredScale(): number {
if (typeof window === 'undefined') return 1;
try {
const v = localStorage.getItem(STORAGE_KEY);
if (v != null) {
const n = parseFloat(v);
if (n >= MIN && n <= MAX) return n;
}
} catch {
// ignore
}
return 1;
}

/** Applique la taille de texte sauvegardée au chargement (pour toutes les pages). */
export default function TextScaleInit() {
useEffect(() => {
document.documentElement.style.setProperty('--text-scale', String(getStoredScale()));
}, []);
return null;
}
Loading
Loading