setup husky prettier eslint
This commit is contained in:
@@ -1,152 +1,153 @@
|
||||
"use client"
|
||||
'use client';
|
||||
|
||||
import { useEffect } from "react"
|
||||
import { MapContainer, TileLayer, Polyline, CircleMarker, Popup, useMap } from "react-leaflet"
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
import { LatLngBounds, LatLng } from "leaflet"
|
||||
import "leaflet/dist/leaflet.css"
|
||||
import { useEffect } from 'react';
|
||||
import { MapContainer, TileLayer, Polyline, CircleMarker, Popup, useMap } from 'react-leaflet';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { LatLngBounds, LatLng } from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
|
||||
type Detection = {
|
||||
id: number
|
||||
type: string
|
||||
class: string
|
||||
confidence: number
|
||||
latitude: number
|
||||
longitude: number
|
||||
frame_number: number
|
||||
}
|
||||
id: number;
|
||||
type: string;
|
||||
class: string;
|
||||
confidence: number;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
frame_number: number;
|
||||
};
|
||||
|
||||
type MapModalProps = {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
detections: Detection[]
|
||||
detectionType: "pothole-detection" | "sign-board-detection" | "pot-sign-detection"
|
||||
}
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
detections: Detection[];
|
||||
detectionType: 'pothole-detection' | 'sign-board-detection' | 'pot-sign-detection';
|
||||
};
|
||||
|
||||
// Component to auto-fit map bounds to show all markers
|
||||
function FitBounds({ bounds }: { bounds: LatLngBounds }) {
|
||||
const map = useMap()
|
||||
const map = useMap();
|
||||
|
||||
useEffect(() => {
|
||||
if (!map || !bounds.isValid()) return
|
||||
useEffect(() => {
|
||||
if (!map || !bounds.isValid()) return;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
map.invalidateSize()
|
||||
map.fitBounds(bounds, {
|
||||
padding: [50, 50],
|
||||
maxZoom: 16,
|
||||
animate: true
|
||||
})
|
||||
}, 200)
|
||||
const timer = setTimeout(() => {
|
||||
map.invalidateSize();
|
||||
map.fitBounds(bounds, {
|
||||
padding: [50, 50],
|
||||
maxZoom: 16,
|
||||
animate: true,
|
||||
});
|
||||
}, 200);
|
||||
|
||||
return () => clearTimeout(timer)
|
||||
}, [bounds, map])
|
||||
return () => clearTimeout(timer);
|
||||
}, [bounds, map]);
|
||||
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function MapModal({ open, onClose, detections, detectionType }: MapModalProps) {
|
||||
const validDetections = detections.filter(d => d.latitude && d.longitude)
|
||||
|
||||
if (validDetections.length === 0) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-4xl h-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Detection Map</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground text-sm font-medium">
|
||||
No GPS data available for detections.
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
const routeCoordinates: [number, number][] = validDetections.map(d => [d.latitude, d.longitude])
|
||||
const bounds = new LatLngBounds(new LatLng(routeCoordinates[0][0], routeCoordinates[0][1]), new LatLng(routeCoordinates[0][0], routeCoordinates[0][1]))
|
||||
routeCoordinates.forEach(coord => bounds.extend(new LatLng(coord[0], coord[1])))
|
||||
const center: [number, number] = [(bounds.getNorth() + bounds.getSouth()) / 2, (bounds.getEast() + bounds.getWest()) / 2]
|
||||
|
||||
const isCombined = detectionType === "pot-sign-detection"
|
||||
const isPothole = detectionType === "pothole-detection"
|
||||
const validDetections = detections.filter((d) => d.latitude && d.longitude);
|
||||
|
||||
if (validDetections.length === 0) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-6xl h-[85vh] p-0 flex flex-col overflow-hidden border-none shadow-2xl">
|
||||
<DialogHeader className="px-6 py-4 border-b shrink-0">
|
||||
<DialogTitle className="text-xl font-bold">
|
||||
{isCombined ? "Combined" : isPothole ? "Pothole" : "Signboard"} Detection Map
|
||||
</DialogTitle>
|
||||
<p className="text-xs text-muted-foreground font-medium">
|
||||
{validDetections.length} points of interest identified with GPS data
|
||||
</p>
|
||||
</DialogHeader>
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-4xl h-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Detection Map</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground text-sm font-medium">
|
||||
No GPS data available for detections.
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
<div className="flex-1 w-full relative bg-muted/20">
|
||||
<MapContainer
|
||||
center={center}
|
||||
zoom={13}
|
||||
className="h-full w-full"
|
||||
scrollWheelZoom={true}
|
||||
>
|
||||
<TileLayer
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
const routeCoordinates: [number, number][] = validDetections.map((d) => [
|
||||
d.latitude,
|
||||
d.longitude,
|
||||
]);
|
||||
const bounds = new LatLngBounds(
|
||||
new LatLng(routeCoordinates[0][0], routeCoordinates[0][1]),
|
||||
new LatLng(routeCoordinates[0][0], routeCoordinates[0][1]),
|
||||
);
|
||||
routeCoordinates.forEach((coord) => bounds.extend(new LatLng(coord[0], coord[1])));
|
||||
const center: [number, number] = [
|
||||
(bounds.getNorth() + bounds.getSouth()) / 2,
|
||||
(bounds.getEast() + bounds.getWest()) / 2,
|
||||
];
|
||||
|
||||
<Polyline
|
||||
positions={routeCoordinates}
|
||||
color="#3b82f6"
|
||||
weight={4}
|
||||
opacity={0.6}
|
||||
/>
|
||||
const isCombined = detectionType === 'pot-sign-detection';
|
||||
const isPothole = detectionType === 'pothole-detection';
|
||||
|
||||
{validDetections.map((detection, idx) => {
|
||||
const type = (detection.type || "").toLowerCase()
|
||||
const colors: Record<string, { fill: string, stroke: string }> = {
|
||||
'pothole': { fill: '#ef4444', stroke: '#b91c1c' },
|
||||
'defected_sign_board': { fill: '#3b82f6', stroke: '#1d4ed8' },
|
||||
'road_crack': { fill: '#f59e0b', stroke: '#b45309' },
|
||||
'damaged_road_marking': { fill: '#6366f1', stroke: '#4338ca' },
|
||||
'good_sign_board': { fill: '#10b981', stroke: '#047857' }
|
||||
}
|
||||
const color = colors[type] || { fill: '#64748b', stroke: '#475569' }
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-6xl h-[85vh] p-0 flex flex-col overflow-hidden border-none shadow-2xl">
|
||||
<DialogHeader className="px-6 py-4 border-b shrink-0">
|
||||
<DialogTitle className="text-xl font-bold">
|
||||
{isCombined ? 'Combined' : isPothole ? 'Pothole' : 'Signboard'} Detection Map
|
||||
</DialogTitle>
|
||||
<p className="text-xs text-muted-foreground font-medium">
|
||||
{validDetections.length} points of interest identified with GPS data
|
||||
</p>
|
||||
</DialogHeader>
|
||||
|
||||
return (
|
||||
<CircleMarker
|
||||
key={`${detection.id}-${idx}`}
|
||||
center={[detection.latitude, detection.longitude]}
|
||||
radius={7}
|
||||
fillColor={color.fill}
|
||||
color={color.stroke}
|
||||
weight={2}
|
||||
opacity={1}
|
||||
fillOpacity={0.9}
|
||||
>
|
||||
<Popup>
|
||||
<div className="text-[11px] space-y-2 p-1">
|
||||
<div className="font-bold border-b pb-1 capitalize">
|
||||
{(detection.type || "").replace(/_/g, " ")} #{detection.id}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<span className="text-muted-foreground">Frame:</span>
|
||||
<span className="font-semibold text-right">{detection.frame_number}</span>
|
||||
<span className="text-muted-foreground">Confidence:</span>
|
||||
<span className="font-semibold text-right">{(detection.confidence * 100).toFixed(0)}%</span>
|
||||
</div>
|
||||
<div className="font-mono bg-muted p-1.5 rounded border text-center text-[9px]">
|
||||
{detection.latitude.toFixed(6)}, {detection.longitude.toFixed(6)}
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
</CircleMarker>
|
||||
)
|
||||
})}
|
||||
<FitBounds bounds={bounds} />
|
||||
</MapContainer>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
<div className="flex-1 w-full relative bg-muted/20">
|
||||
<MapContainer center={center} zoom={13} className="h-full w-full" scrollWheelZoom={true}>
|
||||
<TileLayer
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
|
||||
<Polyline positions={routeCoordinates} color="#3b82f6" weight={4} opacity={0.6} />
|
||||
|
||||
{validDetections.map((detection, idx) => {
|
||||
const type = (detection.type || '').toLowerCase();
|
||||
const colors: Record<string, { fill: string; stroke: string }> = {
|
||||
pothole: { fill: '#ef4444', stroke: '#b91c1c' },
|
||||
defected_sign_board: { fill: '#3b82f6', stroke: '#1d4ed8' },
|
||||
road_crack: { fill: '#f59e0b', stroke: '#b45309' },
|
||||
damaged_road_marking: { fill: '#6366f1', stroke: '#4338ca' },
|
||||
good_sign_board: { fill: '#10b981', stroke: '#047857' },
|
||||
};
|
||||
const color = colors[type] || { fill: '#64748b', stroke: '#475569' };
|
||||
|
||||
return (
|
||||
<CircleMarker
|
||||
key={`${detection.id}-${idx}`}
|
||||
center={[detection.latitude, detection.longitude]}
|
||||
radius={7}
|
||||
fillColor={color.fill}
|
||||
color={color.stroke}
|
||||
weight={2}
|
||||
opacity={1}
|
||||
fillOpacity={0.9}
|
||||
>
|
||||
<Popup>
|
||||
<div className="text-[11px] space-y-2 p-1">
|
||||
<div className="font-bold border-b pb-1 capitalize">
|
||||
{(detection.type || '').replace(/_/g, ' ')} #{detection.id}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<span className="text-muted-foreground">Frame:</span>
|
||||
<span className="font-semibold text-right">{detection.frame_number}</span>
|
||||
<span className="text-muted-foreground">Confidence:</span>
|
||||
<span className="font-semibold text-right">
|
||||
{(detection.confidence * 100).toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="font-mono bg-muted p-1.5 rounded border text-center text-[9px]">
|
||||
{detection.latitude.toFixed(6)}, {detection.longitude.toFixed(6)}
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
</CircleMarker>
|
||||
);
|
||||
})}
|
||||
<FitBounds bounds={bounds} />
|
||||
</MapContainer>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user