From 55e44ab21c5ad10c2aa4230768388aa5516d49b0 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 3 Dec 2025 22:24:48 +0100 Subject: [PATCH] fix carte --- apps/backend/package-lock.json | 79 +++++++++++++++++++ apps/backend/package.json | 3 + .../app/dashboard/search-advanced/page.tsx | 38 ++++++++- apps/frontend/app/demo-carte/page.tsx | 72 +++++++++++++++++ apps/frontend/package-lock.json | 49 ++++++++++++ apps/frontend/package.json | 3 + apps/frontend/src/components/PortRouteMap.tsx | 53 +++++++++++++ 7 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 apps/frontend/app/demo-carte/page.tsx create mode 100644 apps/frontend/src/components/PortRouteMap.tsx diff --git a/apps/backend/package-lock.json b/apps/backend/package-lock.json index 74aa707..62d7fb2 100644 --- a/apps/backend/package-lock.json +++ b/apps/backend/package-lock.json @@ -25,6 +25,7 @@ "@nestjs/websockets": "^10.4.20", "@sentry/node": "^10.19.0", "@sentry/profiling-node": "^10.19.0", + "@types/leaflet": "^1.9.21", "@types/mjml": "^4.7.4", "@types/nodemailer": "^7.0.2", "@types/opossum": "^8.1.9", @@ -40,6 +41,7 @@ "helmet": "^7.2.0", "ioredis": "^5.8.1", "joi": "^17.11.0", + "leaflet": "^1.9.4", "mjml": "^4.16.1", "nestjs-pino": "^4.4.1", "nodemailer": "^7.0.9", @@ -53,6 +55,7 @@ "pino": "^8.17.1", "pino-http": "^8.6.0", "pino-pretty": "^10.3.0", + "react-leaflet": "^5.0.0", "reflect-metadata": "^0.1.14", "rxjs": "^7.8.1", "socket.io": "^4.8.1", @@ -3827,6 +3830,17 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@react-leaflet/core": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz", + "integrity": "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ==", + "license": "Hippocratic-2.1", + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, "node_modules/@sentry-internal/node-cpu-profiler": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@sentry-internal/node-cpu-profiler/-/node-cpu-profiler-2.2.0.tgz", @@ -4965,6 +4979,12 @@ "@types/send": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -5047,6 +5067,15 @@ "@types/node": "*" } }, + "node_modules/@types/leaflet": { + "version": "1.9.21", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz", + "integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/methods": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", @@ -11135,6 +11164,12 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -13387,6 +13422,29 @@ "node": ">= 0.8" } }, + "node_modules/react": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", + "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", + "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.1" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -13394,6 +13452,20 @@ "dev": true, "license": "MIT" }, + "node_modules/react-leaflet": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0.tgz", + "integrity": "sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw==", + "license": "Hippocratic-2.1", + "dependencies": { + "@react-leaflet/core": "^3.0.0" + }, + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -13815,6 +13887,13 @@ "node": ">=10" } }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT", + "peer": true + }, "node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", diff --git a/apps/backend/package.json b/apps/backend/package.json index e2c5fb8..41b911f 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -41,6 +41,7 @@ "@nestjs/websockets": "^10.4.20", "@sentry/node": "^10.19.0", "@sentry/profiling-node": "^10.19.0", + "@types/leaflet": "^1.9.21", "@types/mjml": "^4.7.4", "@types/nodemailer": "^7.0.2", "@types/opossum": "^8.1.9", @@ -56,6 +57,7 @@ "helmet": "^7.2.0", "ioredis": "^5.8.1", "joi": "^17.11.0", + "leaflet": "^1.9.4", "mjml": "^4.16.1", "nestjs-pino": "^4.4.1", "nodemailer": "^7.0.9", @@ -69,6 +71,7 @@ "pino": "^8.17.1", "pino-http": "^8.6.0", "pino-pretty": "^10.3.0", + "react-leaflet": "^5.0.0", "reflect-metadata": "^0.1.14", "rxjs": "^7.8.1", "socket.io": "^4.8.1", diff --git a/apps/frontend/app/dashboard/search-advanced/page.tsx b/apps/frontend/app/dashboard/search-advanced/page.tsx index 8c4fcb3..53be855 100644 --- a/apps/frontend/app/dashboard/search-advanced/page.tsx +++ b/apps/frontend/app/dashboard/search-advanced/page.tsx @@ -6,10 +6,17 @@ 'use client'; -import { useState } from 'react'; +import { useState, useMemo } from 'react'; import { useRouter } from 'next/navigation'; import { useQuery } from '@tanstack/react-query'; import { searchPorts, Port } from '@/lib/api/ports'; +import dynamic from 'next/dynamic'; + +// Import dynamique pour éviter les erreurs SSR avec Leaflet +const PortRouteMap = dynamic(() => import('@/components/PortRouteMap'), { + ssr: false, + loading: () =>
Chargement de la carte...
, +}); interface Package { type: 'caisse' | 'colis' | 'palette' | 'autre'; @@ -85,6 +92,8 @@ export default function AdvancedSearchPage() { const [destinationSearch, setDestinationSearch] = useState(''); const [showOriginDropdown, setShowOriginDropdown] = useState(false); const [showDestinationDropdown, setShowDestinationDropdown] = useState(false); + const [selectedOriginPort, setSelectedOriginPort] = useState(null); + const [selectedDestinationPort, setSelectedDestinationPort] = useState(null); // Port autocomplete queries const { data: originPortsData } = useQuery({ @@ -209,6 +218,7 @@ export default function AdvancedSearchPage() { onClick={() => { setSearchForm({ ...searchForm, origin: port.code }); setOriginSearch(port.displayName); + setSelectedOriginPort(port); setShowOriginDropdown(false); }} className="w-full text-left px-4 py-3 hover:bg-blue-50 border-b border-gray-100 last:border-b-0" @@ -253,6 +263,7 @@ export default function AdvancedSearchPage() { onClick={() => { setSearchForm({ ...searchForm, destination: port.code }); setDestinationSearch(port.displayName); + setSelectedDestinationPort(port); setShowDestinationDropdown(false); }} className="w-full text-left px-4 py-3 hover:bg-blue-50 border-b border-gray-100 last:border-b-0" @@ -267,6 +278,31 @@ export default function AdvancedSearchPage() { )} + + {/* Carte interactive de la route maritime */} + {selectedOriginPort && selectedDestinationPort && ( +
+
+

+ Route maritime : {selectedOriginPort.name} → {selectedDestinationPort.name} +

+

+ Distance approximative et visualisation de la route +

+
+ +
+ )} ); diff --git a/apps/frontend/app/demo-carte/page.tsx b/apps/frontend/app/demo-carte/page.tsx new file mode 100644 index 0000000..0916d15 --- /dev/null +++ b/apps/frontend/app/demo-carte/page.tsx @@ -0,0 +1,72 @@ +"use client"; + +import dynamic from 'next/dynamic'; + +// Import dynamique pour éviter les erreurs SSR avec Leaflet +const PortRouteMap = dynamic(() => import('@/components/PortRouteMap'), { + ssr: false, + loading: () => ( +
+
+
+

Chargement de la carte...

+
+
+ ), +}); + +export default function DemoPage() { + const portA = { lat: 43.2965, lng: 5.3698 }; // Marseille + const portB = { lat: 41.3851, lng: 2.1734 }; // Barcelone + + return ( +
+
+
+

Démo Carte Maritime

+

+ Visualisation de la route entre Marseille et Barcelone +

+
+ +
+
+

+ Route: Port de Marseille → Port de Barcelone +

+

+ Distance approximative: ~350 km par la mer +

+
+ + + +
+
+
+

📍 Port d'origine

+

Marseille, France

+

Lat: {portA.lat}, Lng: {portA.lng}

+
+
+

📍 Port de destination

+

Barcelone, Espagne

+

Lat: {portB.lat}, Lng: {portB.lng}

+
+
+
+
+ +
+

ℹ️ Informations

+
    +
  • • Carte interactive OpenStreetMap
  • +
  • • Marqueurs positionnés sur les ports
  • +
  • • Ligne bleue représentant la route maritime
  • +
  • • Zoom et navigation disponibles
  • +
+
+
+
+ ); +} diff --git a/apps/frontend/package-lock.json b/apps/frontend/package-lock.json index ab3d9be..2c862b3 100644 --- a/apps/frontend/package-lock.json +++ b/apps/frontend/package-lock.json @@ -18,17 +18,20 @@ "@tanstack/react-query": "^5.90.2", "@tanstack/react-table": "^8.21.3", "@tanstack/react-virtual": "^3.13.12", + "@types/leaflet": "^1.9.21", "axios": "^1.12.2", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "date-fns": "^4.1.0", "file-saver": "^2.0.5", "framer-motion": "^12.23.24", + "leaflet": "^1.9.4", "lucide-react": "^0.294.0", "next": "14.0.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.64.0", + "react-leaflet": "^4.2.1", "recharts": "^3.2.1", "tailwind-merge": "^2.1.0", "tailwindcss-animate": "^1.0.7", @@ -2317,6 +2320,17 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, + "node_modules/@react-leaflet/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz", + "integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==", + "license": "Hippocratic-2.1", + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@reduxjs/toolkit": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.0.tgz", @@ -2710,6 +2724,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -2766,6 +2786,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/leaflet": { + "version": "1.9.21", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz", + "integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/node": { "version": "20.19.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.19.tgz", @@ -8228,6 +8257,12 @@ "node": ">=0.10" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -9552,6 +9587,20 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "license": "MIT" }, + "node_modules/react-leaflet": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz", + "integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==", + "license": "Hippocratic-2.1", + "dependencies": { + "@react-leaflet/core": "^2.1.0" + }, + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/react-redux": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", diff --git a/apps/frontend/package.json b/apps/frontend/package.json index d14ac53..4aa2e93 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -24,17 +24,20 @@ "@tanstack/react-query": "^5.90.2", "@tanstack/react-table": "^8.21.3", "@tanstack/react-virtual": "^3.13.12", + "@types/leaflet": "^1.9.21", "axios": "^1.12.2", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "date-fns": "^4.1.0", "file-saver": "^2.0.5", "framer-motion": "^12.23.24", + "leaflet": "^1.9.4", "lucide-react": "^0.294.0", "next": "14.0.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.64.0", + "react-leaflet": "^4.2.1", "recharts": "^3.2.1", "tailwind-merge": "^2.1.0", "tailwindcss-animate": "^1.0.7", diff --git a/apps/frontend/src/components/PortRouteMap.tsx b/apps/frontend/src/components/PortRouteMap.tsx new file mode 100644 index 0000000..ea2654f --- /dev/null +++ b/apps/frontend/src/components/PortRouteMap.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { MapContainer, TileLayer, Polyline, Marker } from "react-leaflet"; +import "leaflet/dist/leaflet.css"; +import L from "leaflet"; + +interface PortRouteMapProps { + portA: { lat: number; lng: number }; + portB: { lat: number; lng: number }; + height?: string; +} + +// Fix Leaflet marker icons +const DefaultIcon = L.icon({ + iconUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png", + shadowUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png", + iconSize: [25, 41], + iconAnchor: [12, 41], +}); +L.Marker.prototype.options.icon = DefaultIcon; + +export default function PortRouteMap({ portA, portB, height = "500px" }: PortRouteMapProps) { + const center = { + lat: (portA.lat + portB.lat) / 2, + lng: (portA.lng + portB.lng) / 2, + }; + + const positions: [number, number][] = [ + [portA.lat, portA.lng], + [portB.lat, portB.lng], + ]; + + return ( +
+ + + + + + + + +
+ ); +}