diff --git a/frontend/src/pages/dashboard/Company/ShipmentsMap/ShipmentsMapWidget.tsx b/frontend/src/pages/dashboard/Company/ShipmentsMap/ShipmentsMapWidget.tsx index 0b6d8fb..6eb0573 100644 --- a/frontend/src/pages/dashboard/Company/ShipmentsMap/ShipmentsMapWidget.tsx +++ b/frontend/src/pages/dashboard/Company/ShipmentsMap/ShipmentsMapWidget.tsx @@ -57,57 +57,126 @@ const getMarkerColor = (shipment: ShipmentWithGps): MarkerColor => { }; const ShipmentsMapWidget: React.FC = () => { - const [shipments, setShipments] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [hasError, setHasError] = useState(false); - - const icons = useMemo(() => ({ - blue: buildMarkerIcon('blue'), - orange: buildMarkerIcon('orange'), - red: buildMarkerIcon('red'), - }), []); - - useEffect(() => { - fixLeafletIcon(); - let cancelled = false; - - const run = async () => { - try { - setIsLoading(true); - setHasError(false); - const response = await shipmentApi.getAllInTransitWithGps(); - if (cancelled) return; - setShipments(response.data); - } catch { - if (cancelled) return; - setHasError(true); - } finally { - if (!cancelled) setIsLoading(false); - } - }; - - void run(); - - return () => { - cancelled = true; - }; - }, []); - - const bounds = useMemo(() => { - const latLngs = shipments - .filter((shipment): shipment is ShipmentWithGps => typeof shipment.lat === 'number' && typeof shipment.lng === 'number') - .map((shipment) => [shipment.lat as number, shipment.lng as number] as [number, number]); - - if (!latLngs.length) return undefined; - return L.latLngBounds(latLngs as L.LatLngExpression[]); - }, [shipments]); - - return ( -
-
-
-

Active Shipments

-

World overview of in-transit shipments

+ const [shipments, setShipments] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [hasError, setHasError] = useState(false); + + const icons = useMemo(() => { + return { + blue: buildMarkerIcon('blue'), + orange: buildMarkerIcon('orange'), + red: buildMarkerIcon('red'), + } as const; + }, []); + + useEffect(() => { + fixLeafletIcon(); + let cancelled = false; + + const run = async () => { + try { + setIsLoading(true); + setHasError(false); + const res = await shipmentApi.getAllInTransitWithGps(); + if (cancelled) return; + setShipments(res.data); + } catch { + if (cancelled) return; + setHasError(true); + } finally { + if (!cancelled) setIsLoading(false); + } + }; + + void run(); + + return () => { + cancelled = true; + }; + }, []); + + const bounds = useMemo(() => { + const latLngs: Array<[number, number]> = shipments + .filter((s: ShipmentWithGps) => typeof s.lat === 'number' && typeof s.lng === 'number') + .map((s: ShipmentWithGps) => [s.lat as number, s.lng as number]); + + + if (!latLngs.length) return undefined; + + const b = L.latLngBounds(latLngs as L.LatLngExpression[]); + return b; + }, [shipments]); + + return ( +
+
+
+

Active Shipments

+

World overview of in-transit shipments

+
+ {hasError ? ( +
Failed to load
+ ) : isLoading ? ( +
Loading…
+ ) : ( +
{shipments.length} active
+ )} +
+ +
+ + + + + {shipments + .filter((s) => typeof s.lat === 'number' && typeof s.lng === 'number') + .map((s) => { + const color = getMarkerColor(s); + const icon = icons[color]; + + return ( + + +
+
+ Shipment +
+
{s.trackingNumber ?? s.id}
+
+ {s.origin} → {s.destination} +
+
+
Status: {s.status}
+ + View + +
+
+
+
+ ); + })} +
+
+
{hasError ? (
Failed to load
diff --git a/frontend/src/pages/dashboard/Company/ShipmentsMap/index.ts b/frontend/src/pages/dashboard/Company/ShipmentsMap/index.ts new file mode 100644 index 0000000..ac50c90 --- /dev/null +++ b/frontend/src/pages/dashboard/Company/ShipmentsMap/index.ts @@ -0,0 +1 @@ +export { default as ShipmentsMapWidget } from './ShipmentsMapWidget';