feat: modify map

This commit is contained in:
2026-03-19 18:07:37 +05:30
parent 32b2c3c6ab
commit 02c5425fce
10 changed files with 341 additions and 158 deletions

View File

@@ -3,7 +3,8 @@
import { useEffect, useState } from 'react';
import dynamic from 'next/dynamic';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Loader2, Milestone } from 'lucide-react';
import { Loader2, Milestone, Maximize2, Minimize2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Detection } from '@/types';
import { DETECTION_TYPES } from '@/constants/detectionModeConfig';
@@ -35,6 +36,8 @@ export function DashboardMap({
const [detections, setDetections] = useState<Detection[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [isMaximized, setIsMaximized] = useState(false);
const [selectedTypes, setSelectedTypes] = useState<string[]>([]);
useEffect(() => {
if (!projectSummary) {
@@ -62,8 +65,10 @@ export function DashboardMap({
for (const [chnName, chn] of Object.entries(chainagesToProcess)) {
if (!chn) continue;
const chainageDetections = (chn as any).detections || [];
filteredDetections.push(...chainageDetections);
const chainageDetections = ((chn as any).detections || []).filter(
(d: any) => (d.type || d.class || '').toLowerCase() !== 'good_sign_board'
);
filteredDetections.push(...chainageDetections);
}
}
@@ -76,50 +81,93 @@ export function DashboardMap({
}
}, [projectSummary, selectedPackageId, selectedChainageId]);
// Filter detections with valid GPS coordinates
const validDetections = detections.filter((d) => d.latitude && d.longitude);
// Handle detection type toggle
const toggleType = (typeId: string) => {
setSelectedTypes((prev) => {
if (prev.includes(typeId)) {
const next = prev.filter((id) => id !== typeId);
return next.length === 0 ? [] : next;
} else {
return [...prev, typeId];
}
});
};
// Filter detections with valid GPS coordinates and selected types
const validDetections = detections.filter((d) => {
const hasGps = d.latitude && d.longitude;
if (!hasGps) return false;
if (selectedTypes.length === 0) return true;
const typeId = (d.type || d.class || '').toLowerCase();
return selectedTypes.includes(typeId);
});
return (
<Card className={`overflow-hidden ${className}`}>
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2 rounded-md bg-secondary text-secondary-foreground flex items-center justify-center">
<Milestone className="h-5 w-5" />
</div>
<div>
<CardTitle className="text-base font-bold">Detection Map</CardTitle>
<p className="text-xs text-muted-foreground">
{isLoading
? 'Loading...'
: `${validDetections.length} detections with GPS coordinates`}
</p>
</div>
</div>
{/* Dynamic Color Legend */}
<div className="flex flex-wrap items-center justify-end gap-x-4 gap-y-1 text-[10px] max-w-[60%]">
{Object.values(DETECTION_TYPES).map((type) => {
const count = validDetections.filter(
(d) => (d.type || d.class || '').toLowerCase() === type.id.toLowerCase()
).length;
if (count === 0 && (type.id.includes('culvert') || type.id === 'drain_issue')) return null;
return (
<div key={type.id} className="flex items-center gap-1">
<div className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: type.color }} />
<span className="text-muted-foreground font-medium">
{type.label} ({count})
</span>
</div>
);
})}
<Card
className={`overflow-hidden transition-all duration-300 p-2 rounded-lg ${
isMaximized ? 'fixed inset-0 z-100 m-0 rounded-none bg-background' : className
}`}
>
<CardContent className="p-0 relative rounded-lg">
{/* Top Right Actions */}
<div className="absolute top-4 right-4 z-1000 pointer-events-none">
<div className="pointer-events-auto">
<Button
variant="default"
size="icon"
onClick={() => setIsMaximized(!isMaximized)}
title={isMaximized ? "Exit Fullscreen" : "Maximize Map"}
>
{isMaximized ? <Minimize2 className="h-5 w-5" /> : <Maximize2 className="h-5 w-5" />}
</Button>
</div>
</div>
</CardHeader>
<CardContent className="p-0">
<div className="h-[500px] p-2 w-full relative">
{/* Bottom Left Legend */}
<div className="absolute bottom-4 left-4 z-1000 pointer-events-none">
<div className="pointer-events-auto bg-zinc-950/80 backdrop-blur-xl border rounded-md p-2">
<div className="flex flex-wrap items-center gap-2 max-w-4xl">
{Object.values(DETECTION_TYPES).map((type) => {
const typeId = type.id.toLowerCase();
const count = detections.filter(
(d) => (d.type || d.class || '').toLowerCase() === typeId && d.latitude && d.longitude
).length;
if (
count === 0 &&
(type.id.includes('culvert') || type.id === 'drain_issue')
)
return null;
if (type.id === 'good_sign_board') return null;
const isSelected = selectedTypes.length === 0 || selectedTypes.includes(typeId);
return (
<button
key={type.id}
onClick={() => toggleType(typeId)}
className={`flex items-center gap-2 px-2 py-1.5 text-zinc-100 rounded-full transition-all text-xs ${
isSelected
? 'bg-zinc-800/80 '
: 'bg-transparent '
}`}
>
<div
className="w-3 h-3 rounded-sm"
style={{ backgroundColor: type.color }}
/>
<span>
{type.label} ({count})
</span>
</button>
);
})}
</div>
</div>
</div>
<div className={`${isMaximized ? 'h-screen' : 'h-[600px]'} w-full relative transition-all`}>
{isLoading ? (
<div className="h-full w-full flex items-center justify-center bg-muted/50">
<Loader2 className="h-8 w-8 text-primary animate-spin" />
@@ -140,6 +188,9 @@ export function DashboardMap({
) : (
<DashboardMapContent detections={validDetections} />
)}
{/* Bottom Left Label Overlay */}
</div>
</CardContent>
</Card>