diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5e2d6372..3dc8df18 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -33,6 +33,7 @@ "i18next": "^23.7.0", "i18next-icu": "^2.4.1", "immer": "^10.0.3", + "leaflet": "^1.9.4", "lucide-react": "^0.294.0", "papaparse": "^5.4.1", "react": "^18.2.0", @@ -43,6 +44,7 @@ "react-hook-form": "^7.48.0", "react-hot-toast": "^2.4.1", "react-i18next": "^13.5.0", + "react-leaflet": "^4.2.1", "react-router-dom": "^6.20.0", "recharts": "^2.10.0", "tailwind-merge": "^2.1.0", @@ -4038,6 +4040,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/@remix-run/router": { "version": "1.23.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", @@ -11597,6 +11610,13 @@ "node": ">=14.0.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", + "peer": true + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -13415,6 +13435,20 @@ "dev": true, "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-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 783e163f..9fb4df08 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -54,6 +54,7 @@ "i18next": "^23.7.0", "i18next-icu": "^2.4.1", "immer": "^10.0.3", + "leaflet": "^1.9.4", "lucide-react": "^0.294.0", "papaparse": "^5.4.1", "react": "^18.2.0", @@ -64,6 +65,7 @@ "react-hook-form": "^7.48.0", "react-hot-toast": "^2.4.1", "react-i18next": "^13.5.0", + "react-leaflet": "^4.2.1", "react-router-dom": "^6.20.0", "recharts": "^2.10.0", "tailwind-merge": "^2.1.0", diff --git a/frontend/src/components/maps/DistributionMap.tsx b/frontend/src/components/maps/DistributionMap.tsx index 3972fa5f..96b8660b 100644 --- a/frontend/src/components/maps/DistributionMap.tsx +++ b/frontend/src/components/maps/DistributionMap.tsx @@ -1,9 +1,12 @@ /* * Distribution Map Component for Enterprise Dashboard - * Shows delivery routes and shipment status across the network + * Shows delivery routes and shipment status across the network with real Leaflet map */ import React, { useState } from 'react'; +import { MapContainer, TileLayer, Marker, Polyline, Popup } from 'react-leaflet'; +import L from 'leaflet'; +import 'leaflet/dist/leaflet.css'; import { Card, CardContent, CardHeader, CardTitle } from '../ui/Card'; import { Badge } from '../ui/Badge'; import { Button } from '../ui/Button'; @@ -22,6 +25,14 @@ import { } from 'lucide-react'; import { useTranslation } from 'react-i18next'; +// Fix for default marker icons in Leaflet +delete (L.Icon.Default.prototype as any)._getIconUrl; +L.Icon.Default.mergeOptions({ + iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon-2x.png', + iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon.png', + shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png', +}); + interface RoutePoint { tenant_id: string; name: string; @@ -32,6 +43,7 @@ interface RoutePoint { estimated_arrival?: string; actual_arrival?: string; sequence: number; + id?: string; } interface RouteData { @@ -40,7 +52,7 @@ interface RouteData { total_distance_km: number; estimated_duration_minutes: number; status: 'planned' | 'in_progress' | 'completed' | 'cancelled'; - route_points?: RoutePoint[]; + route_sequence?: RoutePoint[]; // Backend returns route_sequence, not route_points } interface ShipmentStatusData { @@ -55,13 +67,22 @@ interface DistributionMapProps { shipments?: ShipmentStatusData; } +// Helper function to create custom markers +const createRouteMarker = (color: string, number: number) => { + return L.divIcon({ + className: 'custom-route-marker', + html: `
Visualización en tiempo real de la flota
++ {t('operations:map.description', 'Visualización en tiempo real de la flota')} +
Ruta {route.route_number}
-{route.formatted_driver_name || 'Sin conductor asignado'}
++ {t('operations:distribution.route_prefix', 'Ruta')} {route.route_number} +
++ {route.formatted_driver_name || t('operations:distribution.no_driver', 'Sin conductor asignado')} +
+ {t('operations:distribution.pending_count', 'Entregas Pendientes')} +
++ {t('operations:distribution.pending_desc', 'Aún por distribuir')} +
++ {t('operations:distribution.routes_desc', 'Gestión y seguimiento de rutas de distribución')} +
+| Ruta | -Estado | -Distancia | -Duración Est. | -Paradas | -Acciones | -||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| {route.route_number} | -
- |
- {route.total_distance_km?.toFixed(1) || '-'} km | -{route.estimated_duration_minutes || '-'} min | -{route.route_points?.length || 0} | -- } /> - | + +
| {t('operations:distribution.table.route', 'Ruta')} | +{t('operations:distribution.table.status', 'Estado')} | +{t('operations:distribution.table.distance', 'Distancia')} | +{t('operations:distribution.table.duration', 'Duración Est.')} | +{t('operations:distribution.table.stops', 'Paradas')} | +{t('operations:distribution.table.actions', 'Acciones')} |
|---|
No se encontraron rutas para esta fecha.
-+ {t('operations:distribution.no_routes_found', 'No se encontraron rutas para esta fecha.')} +
+Funcionalidad de listado detallado de envíos próximamente.
-+ {t('operations:distribution.shipments_desc', 'Funcionalidad de listado detallado de envíos próximamente.')} +
++ {t('operations:distribution.shipments_desc', 'Funcionalidad de listado detallado de envíos próximamente.')} +
++ Procesando servicios en paralelo... +
++ {creatingTier === 'enterprise' + ? 'Creando obrador central, outlets y sistema de distribución...' + : 'Personalizando tu panadería con datos reales...'} +
+