From 5c37790ab78b78be9662cc937455b785a3ade862 Mon Sep 17 00:00:00 2001 From: Roland HUON Date: Fri, 30 Jan 2026 09:36:16 +0100 Subject: [PATCH 01/12] feat: add font Arial for everything --- client/app/globals.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/app/globals.scss b/client/app/globals.scss index 4c9bf11..be07a3f 100644 --- a/client/app/globals.scss +++ b/client/app/globals.scss @@ -15,9 +15,7 @@ body { } body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; + font-family: "Arial", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } From 8479d7dfac6640c4db7b96e4b672ecece60f48d7 Mon Sep 17 00:00:00 2001 From: Roland HUON Date: Fri, 30 Jan 2026 09:54:16 +0100 Subject: [PATCH 02/12] refactor: clean architecture --- client/app/hopitaux/[id]/page.tsx | 6 ++---- client/app/hopitaux/page.tsx | 2 +- client/app/page.tsx | 3 +-- client/components/SearchBar.tsx | 1 - client/{app/components => components/home}/FAQSection.tsx | 0 .../components => components/hopitaux}/HospitalCard.tsx | 0 .../components => components/hopitaux}/HospitalList.tsx | 2 +- 7 files changed, 5 insertions(+), 9 deletions(-) rename client/{app/components => components/home}/FAQSection.tsx (100%) rename client/{app/hopitaux/components => components/hopitaux}/HospitalCard.tsx (100%) rename client/{app/hopitaux/components => components/hopitaux}/HospitalList.tsx (90%) diff --git a/client/app/hopitaux/[id]/page.tsx b/client/app/hopitaux/[id]/page.tsx index 58695ac..62eef2d 100644 --- a/client/app/hopitaux/[id]/page.tsx +++ b/client/app/hopitaux/[id]/page.tsx @@ -333,10 +333,8 @@ export default function HospitalDetailPage({ params }: { params: Promise<{ id: s })()} {!mockData && !accessibilityOptions && ( -
-

- Les spécifications de cet établissement ne sont pas encore disponibles. -

+
+
)}
diff --git a/client/app/hopitaux/page.tsx b/client/app/hopitaux/page.tsx index 0710cc4..781e2e0 100644 --- a/client/app/hopitaux/page.tsx +++ b/client/app/hopitaux/page.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useMemo } from 'react'; import Header from '@/components/Header'; -import HospitalList from './components/HospitalList'; +import HospitalList from '@/components/hopitaux/HospitalList'; import SearchBar from '@/components/SearchBar'; import MultiSelectFilter from '@/components/MultiSelectFilter'; import Loading from '@/components/Loading'; diff --git a/client/app/page.tsx b/client/app/page.tsx index 77dce60..4a57977 100644 --- a/client/app/page.tsx +++ b/client/app/page.tsx @@ -1,8 +1,7 @@ import Image from 'next/image' import Link from 'next/link'; import Header from '@/components/Header' -import MapWrapper from '@/components/MapWrapper' -import FAQSection from '@/app/components/FAQSection' +import FAQSection from '@/components/home/FAQSection' export default function Home() { return ( diff --git a/client/components/SearchBar.tsx b/client/components/SearchBar.tsx index f4d54fe..128e1e1 100644 --- a/client/components/SearchBar.tsx +++ b/client/components/SearchBar.tsx @@ -40,7 +40,6 @@ export default function SearchBar({ "rounded-full", "px-6 py-3", "shadow-sm", - "font-[Arial]", "focus-within:ring-4 focus-within:ring-red-600", className, ].join(" ")} diff --git a/client/app/components/FAQSection.tsx b/client/components/home/FAQSection.tsx similarity index 100% rename from client/app/components/FAQSection.tsx rename to client/components/home/FAQSection.tsx diff --git a/client/app/hopitaux/components/HospitalCard.tsx b/client/components/hopitaux/HospitalCard.tsx similarity index 100% rename from client/app/hopitaux/components/HospitalCard.tsx rename to client/components/hopitaux/HospitalCard.tsx diff --git a/client/app/hopitaux/components/HospitalList.tsx b/client/components/hopitaux/HospitalList.tsx similarity index 90% rename from client/app/hopitaux/components/HospitalList.tsx rename to client/components/hopitaux/HospitalList.tsx index f7f080a..0abcfdc 100644 --- a/client/app/hopitaux/components/HospitalList.tsx +++ b/client/components/hopitaux/HospitalList.tsx @@ -1,4 +1,4 @@ -import HospitalCard from "./HospitalCard"; +import HospitalCard from "@/components/hopitaux/HospitalCard"; import { HospitalWithMock } from "@/types/api"; import NotFoundData from "@/components/NotFoundData"; From 87e12d66eb8d5907095fecc9119d8d9ba688dd06 Mon Sep 17 00:00:00 2001 From: Roland HUON Date: Fri, 30 Jan 2026 10:42:27 +0100 Subject: [PATCH 03/12] feat: add metadata for SEO --- client/app/hopitaux/[id]/layout.tsx | 46 +++++++++++++++++++++++++++++ client/app/hopitaux/layout.tsx | 23 +++++++++++++++ client/app/layout.tsx | 7 +++-- client/app/map/layout.tsx | 23 +++++++++++++++ 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 client/app/hopitaux/[id]/layout.tsx create mode 100644 client/app/hopitaux/layout.tsx create mode 100644 client/app/map/layout.tsx diff --git a/client/app/hopitaux/[id]/layout.tsx b/client/app/hopitaux/[id]/layout.tsx new file mode 100644 index 0000000..4038dce --- /dev/null +++ b/client/app/hopitaux/[id]/layout.tsx @@ -0,0 +1,46 @@ +import type { Metadata } from 'next'; + +type Props = { + params: Promise<{ id: string }>; +}; + +async function getHospitalName(id: string): Promise { + const baseUrl = process.env.NEXT_PUBLIC_HOSPITALS_SINGLE_API_URL; + + if (!baseUrl) { + console.error('NEXT_PUBLIC_HOSPITALS_SINGLE_API_URL manquant'); + return null; + } + + const apiUrl = `${baseUrl}&rows=1&q=recordid:${id}`; + const res = await fetch(apiUrl, { next: { revalidate: 3600 } }); + + if (!res.ok) return null; + + const data = await res.json(); + return data.records?.[0]?.fields?.name ?? null; +} + +export async function generateMetadata( + props: Props +): Promise { + const params = await props.params; + const hospitalName = await getHospitalName(params.id); + + return { + title: hospitalName + ? `${hospitalName} – Urgences` + : `Détail de l'hôpital`, + description: hospitalName + ? `Consultez les informations de l'hôpital ${hospitalName}.` + : `Consultez les détails de l'hôpital.`, + }; +} + +export default function HospitalLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/client/app/hopitaux/layout.tsx b/client/app/hopitaux/layout.tsx new file mode 100644 index 0000000..8e2c0bf --- /dev/null +++ b/client/app/hopitaux/layout.tsx @@ -0,0 +1,23 @@ +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Liste des hôpitaux', + description: 'Consultez la liste des hôpitaux avec services d\'urgence les plus proches de votre position.', +} + +export default function HopitauxLayout({ + children, + }: { + children: React.ReactNode + }) { + return ( + + + + + + {children} + + + ) +} \ No newline at end of file diff --git a/client/app/layout.tsx b/client/app/layout.tsx index 67c8841..30f9e58 100644 --- a/client/app/layout.tsx +++ b/client/app/layout.tsx @@ -1,8 +1,11 @@ -import type { Metadata } from 'next' import './globals.scss' +import { Metadata } from 'next'; export const metadata: Metadata = { - title: 'Quelles Urgences', + title: { + default: 'Quelles Urgences', + template: '%s | Quelles Urgences', + }, description: 'Application de gestion des urgences', icons: { icon: '/images/logo/logo-red.svg', diff --git a/client/app/map/layout.tsx b/client/app/map/layout.tsx new file mode 100644 index 0000000..2c93c9b --- /dev/null +++ b/client/app/map/layout.tsx @@ -0,0 +1,23 @@ +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Carte des urgences', + description: 'Visualisez les hôpitaux avec services d\'urgence les plus proches de votre position.', +} + +export default function MapLayout({ + children, + }: { + children: React.ReactNode + }) { + return ( + + + + + + {children} + + + ) +} From 805b3e06ac8c87d5fb6cd1e955d8e067c241a826 Mon Sep 17 00:00:00 2001 From: Roland HUON Date: Fri, 30 Jan 2026 11:07:41 +0100 Subject: [PATCH 04/12] fix: issus with layout --- client/app/hopitaux/layout.tsx | 11 +---------- client/app/map/layout.tsx | 11 +---------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/client/app/hopitaux/layout.tsx b/client/app/hopitaux/layout.tsx index 8e2c0bf..8a7a02d 100644 --- a/client/app/hopitaux/layout.tsx +++ b/client/app/hopitaux/layout.tsx @@ -10,14 +10,5 @@ export default function HopitauxLayout({ }: { children: React.ReactNode }) { - return ( - - - - - - {children} - - - ) + return <>{children}; } \ No newline at end of file diff --git a/client/app/map/layout.tsx b/client/app/map/layout.tsx index 2c93c9b..b990b6f 100644 --- a/client/app/map/layout.tsx +++ b/client/app/map/layout.tsx @@ -10,14 +10,5 @@ export default function MapLayout({ }: { children: React.ReactNode }) { - return ( - - - - - - {children} - - - ) + return <>{children}; } From c2715fc7a69a4282d00a3d6e9d7c2d19572973e9 Mon Sep 17 00:00:00 2001 From: Roland HUON Date: Fri, 30 Jan 2026 11:10:31 +0100 Subject: [PATCH 05/12] feat: 3/4 base security --- client/next.config.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/client/next.config.js b/client/next.config.js index 4f19f47..e5e87e4 100644 --- a/client/next.config.js +++ b/client/next.config.js @@ -4,6 +4,27 @@ const nextConfig = { images: { qualities: [75, 100], }, + async headers() { + return [ + { + source: '/(.*)', + headers: [ + { + key: 'X-Content-Type-Options', + value: 'nosniff', + }, + { + key: 'X-Frame-Options', + value: 'DENY', + }, + { + key: 'Referrer-Policy', + value: 'strict-origin-when-cross-origin', + }, + ], + }, + ]; + }, } export default nextConfig \ No newline at end of file From b2f0ead0b0a993dbc99c22a0f9cc60a8d5334688 Mon Sep 17 00:00:00 2001 From: Roland HUON Date: Fri, 30 Jan 2026 09:36:16 +0100 Subject: [PATCH 06/12] feat: add font Arial for everything --- client/app/globals.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/app/globals.scss b/client/app/globals.scss index 4c9bf11..be07a3f 100644 --- a/client/app/globals.scss +++ b/client/app/globals.scss @@ -15,9 +15,7 @@ body { } body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; + font-family: "Arial", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } From db0907fbf49acaee881d6a12f032f8a963fd537c Mon Sep 17 00:00:00 2001 From: Roland HUON Date: Fri, 30 Jan 2026 09:54:16 +0100 Subject: [PATCH 07/12] refactor: clean architecture --- client/app/hopitaux/[id]/page.tsx | 6 ++---- client/app/hopitaux/page.tsx | 2 +- client/app/page.tsx | 3 +-- client/components/SearchBar.tsx | 1 - client/{app/components => components/home}/FAQSection.tsx | 0 .../components => components/hopitaux}/HospitalCard.tsx | 0 .../components => components/hopitaux}/HospitalList.tsx | 2 +- 7 files changed, 5 insertions(+), 9 deletions(-) rename client/{app/components => components/home}/FAQSection.tsx (100%) rename client/{app/hopitaux/components => components/hopitaux}/HospitalCard.tsx (100%) rename client/{app/hopitaux/components => components/hopitaux}/HospitalList.tsx (90%) diff --git a/client/app/hopitaux/[id]/page.tsx b/client/app/hopitaux/[id]/page.tsx index 7c0552d..88f86bf 100644 --- a/client/app/hopitaux/[id]/page.tsx +++ b/client/app/hopitaux/[id]/page.tsx @@ -361,10 +361,8 @@ export default function HospitalDetailPage({ params }: { params: Promise<{ id: s })()} {!mockData && !accessibilityOptions && ( -
-

- Les spécifications de cet établissement ne sont pas encore disponibles. -

+
+
)}
diff --git a/client/app/hopitaux/page.tsx b/client/app/hopitaux/page.tsx index 0710cc4..781e2e0 100644 --- a/client/app/hopitaux/page.tsx +++ b/client/app/hopitaux/page.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useMemo } from 'react'; import Header from '@/components/Header'; -import HospitalList from './components/HospitalList'; +import HospitalList from '@/components/hopitaux/HospitalList'; import SearchBar from '@/components/SearchBar'; import MultiSelectFilter from '@/components/MultiSelectFilter'; import Loading from '@/components/Loading'; diff --git a/client/app/page.tsx b/client/app/page.tsx index 77dce60..4a57977 100644 --- a/client/app/page.tsx +++ b/client/app/page.tsx @@ -1,8 +1,7 @@ import Image from 'next/image' import Link from 'next/link'; import Header from '@/components/Header' -import MapWrapper from '@/components/MapWrapper' -import FAQSection from '@/app/components/FAQSection' +import FAQSection from '@/components/home/FAQSection' export default function Home() { return ( diff --git a/client/components/SearchBar.tsx b/client/components/SearchBar.tsx index f4d54fe..128e1e1 100644 --- a/client/components/SearchBar.tsx +++ b/client/components/SearchBar.tsx @@ -40,7 +40,6 @@ export default function SearchBar({ "rounded-full", "px-6 py-3", "shadow-sm", - "font-[Arial]", "focus-within:ring-4 focus-within:ring-red-600", className, ].join(" ")} diff --git a/client/app/components/FAQSection.tsx b/client/components/home/FAQSection.tsx similarity index 100% rename from client/app/components/FAQSection.tsx rename to client/components/home/FAQSection.tsx diff --git a/client/app/hopitaux/components/HospitalCard.tsx b/client/components/hopitaux/HospitalCard.tsx similarity index 100% rename from client/app/hopitaux/components/HospitalCard.tsx rename to client/components/hopitaux/HospitalCard.tsx diff --git a/client/app/hopitaux/components/HospitalList.tsx b/client/components/hopitaux/HospitalList.tsx similarity index 90% rename from client/app/hopitaux/components/HospitalList.tsx rename to client/components/hopitaux/HospitalList.tsx index f7f080a..0abcfdc 100644 --- a/client/app/hopitaux/components/HospitalList.tsx +++ b/client/components/hopitaux/HospitalList.tsx @@ -1,4 +1,4 @@ -import HospitalCard from "./HospitalCard"; +import HospitalCard from "@/components/hopitaux/HospitalCard"; import { HospitalWithMock } from "@/types/api"; import NotFoundData from "@/components/NotFoundData"; From 8b906528c9c6b6379c7ffd11283bac59bed18801 Mon Sep 17 00:00:00 2001 From: Roland HUON Date: Fri, 30 Jan 2026 10:42:27 +0100 Subject: [PATCH 08/12] feat: add metadata for SEO --- client/app/hopitaux/[id]/layout.tsx | 46 +++++++++++++++++++++++++++++ client/app/hopitaux/layout.tsx | 23 +++++++++++++++ client/app/layout.tsx | 7 +++-- client/app/map/layout.tsx | 23 +++++++++++++++ 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 client/app/hopitaux/[id]/layout.tsx create mode 100644 client/app/hopitaux/layout.tsx create mode 100644 client/app/map/layout.tsx diff --git a/client/app/hopitaux/[id]/layout.tsx b/client/app/hopitaux/[id]/layout.tsx new file mode 100644 index 0000000..4038dce --- /dev/null +++ b/client/app/hopitaux/[id]/layout.tsx @@ -0,0 +1,46 @@ +import type { Metadata } from 'next'; + +type Props = { + params: Promise<{ id: string }>; +}; + +async function getHospitalName(id: string): Promise { + const baseUrl = process.env.NEXT_PUBLIC_HOSPITALS_SINGLE_API_URL; + + if (!baseUrl) { + console.error('NEXT_PUBLIC_HOSPITALS_SINGLE_API_URL manquant'); + return null; + } + + const apiUrl = `${baseUrl}&rows=1&q=recordid:${id}`; + const res = await fetch(apiUrl, { next: { revalidate: 3600 } }); + + if (!res.ok) return null; + + const data = await res.json(); + return data.records?.[0]?.fields?.name ?? null; +} + +export async function generateMetadata( + props: Props +): Promise { + const params = await props.params; + const hospitalName = await getHospitalName(params.id); + + return { + title: hospitalName + ? `${hospitalName} – Urgences` + : `Détail de l'hôpital`, + description: hospitalName + ? `Consultez les informations de l'hôpital ${hospitalName}.` + : `Consultez les détails de l'hôpital.`, + }; +} + +export default function HospitalLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/client/app/hopitaux/layout.tsx b/client/app/hopitaux/layout.tsx new file mode 100644 index 0000000..8e2c0bf --- /dev/null +++ b/client/app/hopitaux/layout.tsx @@ -0,0 +1,23 @@ +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Liste des hôpitaux', + description: 'Consultez la liste des hôpitaux avec services d\'urgence les plus proches de votre position.', +} + +export default function HopitauxLayout({ + children, + }: { + children: React.ReactNode + }) { + return ( + + + + + + {children} + + + ) +} \ No newline at end of file diff --git a/client/app/layout.tsx b/client/app/layout.tsx index 67c8841..30f9e58 100644 --- a/client/app/layout.tsx +++ b/client/app/layout.tsx @@ -1,8 +1,11 @@ -import type { Metadata } from 'next' import './globals.scss' +import { Metadata } from 'next'; export const metadata: Metadata = { - title: 'Quelles Urgences', + title: { + default: 'Quelles Urgences', + template: '%s | Quelles Urgences', + }, description: 'Application de gestion des urgences', icons: { icon: '/images/logo/logo-red.svg', diff --git a/client/app/map/layout.tsx b/client/app/map/layout.tsx new file mode 100644 index 0000000..2c93c9b --- /dev/null +++ b/client/app/map/layout.tsx @@ -0,0 +1,23 @@ +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Carte des urgences', + description: 'Visualisez les hôpitaux avec services d\'urgence les plus proches de votre position.', +} + +export default function MapLayout({ + children, + }: { + children: React.ReactNode + }) { + return ( + + + + + + {children} + + + ) +} From ae9756e6f7939c50bff72743d5e97bf10c7a5116 Mon Sep 17 00:00:00 2001 From: Roland HUON Date: Fri, 30 Jan 2026 11:07:41 +0100 Subject: [PATCH 09/12] fix: issus with layout --- client/app/hopitaux/layout.tsx | 11 +---------- client/app/map/layout.tsx | 11 +---------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/client/app/hopitaux/layout.tsx b/client/app/hopitaux/layout.tsx index 8e2c0bf..8a7a02d 100644 --- a/client/app/hopitaux/layout.tsx +++ b/client/app/hopitaux/layout.tsx @@ -10,14 +10,5 @@ export default function HopitauxLayout({ }: { children: React.ReactNode }) { - return ( - - - - - - {children} - - - ) + return <>{children}; } \ No newline at end of file diff --git a/client/app/map/layout.tsx b/client/app/map/layout.tsx index 2c93c9b..b990b6f 100644 --- a/client/app/map/layout.tsx +++ b/client/app/map/layout.tsx @@ -10,14 +10,5 @@ export default function MapLayout({ }: { children: React.ReactNode }) { - return ( - - - - - - {children} - - - ) + return <>{children}; } From 8d8fd61e972394b086d07d2207fa1b84baace154 Mon Sep 17 00:00:00 2001 From: Roland HUON Date: Fri, 30 Jan 2026 11:10:31 +0100 Subject: [PATCH 10/12] feat: 3/4 base security --- client/next.config.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/client/next.config.js b/client/next.config.js index 4f19f47..e5e87e4 100644 --- a/client/next.config.js +++ b/client/next.config.js @@ -4,6 +4,27 @@ const nextConfig = { images: { qualities: [75, 100], }, + async headers() { + return [ + { + source: '/(.*)', + headers: [ + { + key: 'X-Content-Type-Options', + value: 'nosniff', + }, + { + key: 'X-Frame-Options', + value: 'DENY', + }, + { + key: 'Referrer-Policy', + value: 'strict-origin-when-cross-origin', + }, + ], + }, + ]; + }, } export default nextConfig \ No newline at end of file From cadc640d489ddf6b8f997625fa57e86f94b0c471 Mon Sep 17 00:00:00 2001 From: Roland HUON Date: Fri, 30 Jan 2026 12:00:52 +0100 Subject: [PATCH 11/12] refactor: refactor if by switch --- client/app/hopitaux/page.tsx | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/client/app/hopitaux/page.tsx b/client/app/hopitaux/page.tsx index 781e2e0..88f9da8 100644 --- a/client/app/hopitaux/page.tsx +++ b/client/app/hopitaux/page.tsx @@ -128,26 +128,22 @@ export default function HopitauxPage() { if (selectedSpecifications.length > 0) { filtered = filtered.filter(hospital => { return selectedSpecifications.every(spec => { - if (spec === 'fire_fighter') { - return hospital.mockData?.fire_fighter; + switch (spec) { + case 'fire_fighter': + return hospital.mockData?.fire_fighter; + case 'social_worker': + return hospital.mockData?.social_worker; + case 'wheelchairAccessibleEntrance': + return hospital.accessibilityOptions?.wheelchairAccessibleEntrance; + case 'wheelchairAccessibleParking': + return hospital.accessibilityOptions?.wheelchairAccessibleParking; + case 'wheelchairAccessibleRestroom': + return hospital.accessibilityOptions?.wheelchairAccessibleRestroom; + case 'wheelchairAccessibleSeating': + return hospital.accessibilityOptions?.wheelchairAccessibleSeating; + default: + return false; } - if (spec === 'social_worker') { - return hospital.mockData?.social_worker; - } - if (spec === 'wheelchairAccessibleEntrance') { - return hospital.accessibilityOptions?.wheelchairAccessibleEntrance; - } - if (spec === 'wheelchairAccessibleParking') { - return hospital.accessibilityOptions?.wheelchairAccessibleParking; - } - if (spec === 'wheelchairAccessibleRestroom') { - return hospital.accessibilityOptions?.wheelchairAccessibleRestroom; - } - if (spec === 'wheelchairAccessibleSeating') { - return hospital.accessibilityOptions?.wheelchairAccessibleSeating; - } - - return false; }); }); } From b440046201af303e04b676f364ea989d9cad5d6e Mon Sep 17 00:00:00 2001 From: Roland HUON Date: Fri, 30 Jan 2026 12:12:13 +0100 Subject: [PATCH 12/12] fix: restore design markeurs --- client/components/MapComponent.tsx | 213 ++++++++++++++++++++++------- 1 file changed, 163 insertions(+), 50 deletions(-) diff --git a/client/components/MapComponent.tsx b/client/components/MapComponent.tsx index 21dce5c..dd9555d 100644 --- a/client/components/MapComponent.tsx +++ b/client/components/MapComponent.tsx @@ -93,7 +93,7 @@ function MapContent({ fullScreen = false, initialCenter, initialZoom, focusRecor const container = mapRef.current const initMap = async () => { - if (mapInstanceRef.current) return undefined + if (mapInstanceRef.current) return const L = (await import('leaflet')).default await import('leaflet.markercluster') @@ -178,21 +178,31 @@ function MapContent({ fullScreen = false, initialCenter, initialZoom, focusRecor map.addLayer(markerClusterGroup) markerClusterGroupRef.current = markerClusterGroup + // Fonction pour désactiver le focus sur les contrôles et clusters const disableMapFocus = () => { - const mapContainer = container.querySelector('.leaflet-container') as HTMLElement - if (mapContainer) { - mapContainer.setAttribute('tabindex', '-1') - } - + // Désactiver le focus sur les contrôles de la carte (zoom, etc.) const controlLinks = container.querySelectorAll('.leaflet-control a') controlLinks.forEach((link) => { (link as HTMLElement).setAttribute('tabindex', '-1') }) + + // Désactiver le focus sur les clusters + const clusters = container.querySelectorAll('.marker-cluster') + clusters.forEach((cluster) => { + (cluster as HTMLElement).setAttribute('tabindex', '-1') + }) + + // Désactiver le focus sur tous les marqueurs + const markers = container.querySelectorAll('.leaflet-marker-icon') + markers.forEach((marker) => { + (marker as HTMLElement).setAttribute('tabindex', '-1') + }) } const timers: NodeJS.Timeout[] = [] timers.push(setTimeout(disableMapFocus, 100)) timers.push(setTimeout(disableMapFocus, 500)) + timers.push(setTimeout(disableMapFocus, 1000)) const observer = new MutationObserver(disableMapFocus) observer.observe(container, { @@ -200,42 +210,144 @@ function MapContent({ fullScreen = false, initialCenter, initialZoom, focusRecor subtree: true }) - const createPopupHTML = (hospitalName: string, lat: number, lng: number): string => { + const formatDistance = (lat: number, lng: number): string | null => { + const userPos = userPositionRef.current + + const fromUser = + userPos != null + ? (() => { + const R = 6371e3 // metres + const toRad = (deg: number) => (deg * Math.PI) / 180 + const φ1 = toRad(userPos[0]) + const φ2 = toRad(lat) + const Δφ = toRad(lat - userPos[0]) + const Δλ = toRad(lng - userPos[1]) + + const a = + Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2) + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) + return R * c + })() + : null + + if (fromUser == null || Number.isNaN(fromUser)) return null + + if (fromUser >= 1000) { + const km = fromUser / 1000 + return `${km.toFixed(1)} km` + } + + const rounded = Math.round(fromUser / 50) * 50 + return `${rounded} m` + } + + const getPhone = (fields: Hospital['fields']): string | null => { + const anyFields = fields as Record + const phone = + (anyFields.phone as string | undefined) || + (anyFields.telephone as string | undefined) || + (anyFields.tel as string | undefined) + if (!phone) return null + return phone.trim() + } + + const createPopupHTML = (hospital: Hospital, lat: number, lng: number): string => { const userPos = userPositionRef.current let itineraryUrl = `https://www.google.com/maps/dir/?api=1&destination=${lat},${lng}` if (userPos) { itineraryUrl += `&origin=${userPos[0]},${userPos[1]}` } + const distanceLabel = formatDistance(lat, lng) + const phone = getPhone(hospital.fields) + const phoneHref = phone ? `tel:${phone.replace(/\s+/g, '')}` : null + const hospitalName = hospital.fields.name return ` -
-
+
+
${hospitalName}
- - Itinéraire - + ${ + distanceLabel + ? `
+ ${distanceLabel} +
` + : '' + } +
+ ${ + phone + ? ` + + 📞 + + ${phone} + ` + : '' + } + + Itinéraire + +
` } @@ -249,7 +361,7 @@ function MapContent({ fullScreen = false, initialCenter, initialZoom, focusRecor }) if (marker) { - const newPopupContent = createPopupHTML(hospital.fields.name, lat, lng) + const newPopupContent = createPopupHTML(hospital, lat, lng) marker.setPopupContent(newPopupContent) } }) @@ -283,17 +395,27 @@ function MapContent({ fullScreen = false, initialCenter, initialZoom, focusRecor const [lat, lng] = coords hospitalsDataRef.current.push({ hospital, coords }) - const popupContent = createPopupHTML(hospital.fields.name, lat, lng) + const popupContent = createPopupHTML(hospital, lat, lng) if (!mapInstanceRef.current) return - const marker = L.marker([lat, lng], { icon: redIcon }) + const marker = L.marker([lat, lng], { + icon: redIcon, + keyboard: false + }) .bindPopup(popupContent, { className: 'hospital-popup', closeButton: true, autoClose: false, closeOnClick: false, }) + .on('popupopen', () => { + // Désactiver le focus sur le bouton de fermeture du popup + const closeButton = container.querySelector('.leaflet-popup-close-button') as HTMLElement + if (closeButton) { + closeButton.setAttribute('tabindex', '-1') + } + }) const handleMouseOver = () => { marker.openPopup() @@ -371,7 +493,8 @@ function MapContent({ fullScreen = false, initialCenter, initialZoom, focusRecor } userMarkerRef.current = L.marker(PARIS_COORDS, { icon: userIcon, - zIndexOffset: 1000 + zIndexOffset: 1000, + keyboard: false }) .addTo(map) .bindPopup('Votre position (approximative)', { closeButton: false }) @@ -388,7 +511,8 @@ function MapContent({ fullScreen = false, initialCenter, initialZoom, focusRecor } userMarkerRef.current = L.marker([latitude, longitude], { icon: userIcon, - zIndexOffset: 1000 + zIndexOffset: 1000, + keyboard: false }) .addTo(map) .bindPopup('Votre position', { closeButton: false }) @@ -433,15 +557,10 @@ function MapContent({ fullScreen = false, initialCenter, initialZoom, focusRecor await loadHospitals(PARIS_COORDS[0], PARIS_COORDS[1]) } - // Retourner les ressources à nettoyer - return { observer, timers } + // Pas de return nécessaire } - let cleanupResources: { observer?: MutationObserver; timers?: NodeJS.Timeout[] } | undefined - - initMap().then((resources) => { - cleanupResources = resources - }) + initMap() return () => { markerEventHandlersRef.current.forEach((handlers, marker) => { @@ -465,12 +584,6 @@ function MapContent({ fullScreen = false, initialCenter, initialZoom, focusRecor mapInstanceRef.current.remove() mapInstanceRef.current = null } - if (cleanupResources?.observer) { - cleanupResources.observer.disconnect() - } - if (cleanupResources?.timers) { - cleanupResources.timers.forEach(timer => clearTimeout(timer)) - } } }, [])