fix carte
This commit is contained in:
parent
7fc43444a9
commit
55e44ab21c
79
apps/backend/package-lock.json
generated
79
apps/backend/package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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: () => <div className="h-80 bg-gray-100 rounded-lg flex items-center justify-center">Chargement de la carte...</div>,
|
||||
});
|
||||
|
||||
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<Port | null>(null);
|
||||
const [selectedDestinationPort, setSelectedDestinationPort] = useState<Port | null>(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() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Carte interactive de la route maritime */}
|
||||
{selectedOriginPort && selectedDestinationPort && (
|
||||
<div className="mt-6 border border-gray-200 rounded-lg overflow-hidden">
|
||||
<div className="bg-gray-50 px-4 py-3 border-b border-gray-200">
|
||||
<h3 className="text-sm font-semibold text-gray-900">
|
||||
Route maritime : {selectedOriginPort.name} → {selectedDestinationPort.name}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Distance approximative et visualisation de la route
|
||||
</p>
|
||||
</div>
|
||||
<PortRouteMap
|
||||
portA={{
|
||||
lat: selectedOriginPort.coordinates.latitude,
|
||||
lng: selectedOriginPort.coordinates.longitude,
|
||||
}}
|
||||
portB={{
|
||||
lat: selectedDestinationPort.coordinates.latitude,
|
||||
lng: selectedDestinationPort.coordinates.longitude,
|
||||
}}
|
||||
height="400px"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
72
apps/frontend/app/demo-carte/page.tsx
Normal file
72
apps/frontend/app/demo-carte/page.tsx
Normal file
@ -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: () => (
|
||||
<div className="h-96 bg-gray-100 rounded-lg flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||
<p className="text-gray-600">Chargement de la carte...</p>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
export default function DemoPage() {
|
||||
const portA = { lat: 43.2965, lng: 5.3698 }; // Marseille
|
||||
const portB = { lat: 41.3851, lng: 2.1734 }; // Barcelone
|
||||
|
||||
return (
|
||||
<div className="w-full min-h-screen p-8 bg-gray-50">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">Démo Carte Maritime</h1>
|
||||
<p className="text-gray-600">
|
||||
Visualisation de la route entre Marseille et Barcelone
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
|
||||
<div className="bg-blue-600 px-6 py-4">
|
||||
<h2 className="text-white text-lg font-semibold">
|
||||
Route: Port de Marseille → Port de Barcelone
|
||||
</h2>
|
||||
<p className="text-blue-100 text-sm mt-1">
|
||||
Distance approximative: ~350 km par la mer
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<PortRouteMap portA={portA} portB={portB} height="600px" />
|
||||
|
||||
<div className="px-6 py-4 bg-gray-50 border-t">
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900 mb-2">📍 Port d'origine</h3>
|
||||
<p className="text-gray-600">Marseille, France</p>
|
||||
<p className="text-gray-500 text-xs">Lat: {portA.lat}, Lng: {portA.lng}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900 mb-2">📍 Port de destination</h3>
|
||||
<p className="text-gray-600">Barcelone, Espagne</p>
|
||||
<p className="text-gray-500 text-xs">Lat: {portB.lat}, Lng: {portB.lng}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<h3 className="text-blue-900 font-semibold mb-2">ℹ️ Informations</h3>
|
||||
<ul className="text-blue-800 text-sm space-y-1">
|
||||
<li>• Carte interactive OpenStreetMap</li>
|
||||
<li>• Marqueurs positionnés sur les ports</li>
|
||||
<li>• Ligne bleue représentant la route maritime</li>
|
||||
<li>• Zoom et navigation disponibles</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
49
apps/frontend/package-lock.json
generated
49
apps/frontend/package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
53
apps/frontend/src/components/PortRouteMap.tsx
Normal file
53
apps/frontend/src/components/PortRouteMap.tsx
Normal file
@ -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 (
|
||||
<div style={{ height }}>
|
||||
<MapContainer
|
||||
center={[center.lat, center.lng]}
|
||||
zoom={4}
|
||||
style={{ height: "100%", width: "100%" }}
|
||||
scrollWheelZoom={false}
|
||||
>
|
||||
<TileLayer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
/>
|
||||
|
||||
<Marker position={[portA.lat, portA.lng]} />
|
||||
<Marker position={[portB.lat, portB.lng]} />
|
||||
|
||||
<Polyline positions={positions} pathOptions={{ color: "#2563eb", weight: 3, opacity: 0.7 }} />
|
||||
</MapContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user