feat: Enhance unit popup to display nearby incidents and loading state

This commit is contained in:
vergiLgood1 2025-05-14 08:56:08 +07:00
parent 2c11cc5991
commit 834d4b02cf
2 changed files with 69 additions and 34 deletions

View File

@ -18,6 +18,14 @@ interface UnitsLayerProps {
map?: mapboxgl.Map | null map?: mapboxgl.Map | null
} }
interface IDistrictIncidents {
incident_id: string
category_name: string
incident_description: string
distance_meters: number
timestamp: Date
}
export default function UnitsLayer({ crimes, units = [], filterCategory, visible = false, map }: UnitsLayerProps) { export default function UnitsLayer({ crimes, units = [], filterCategory, visible = false, map }: UnitsLayerProps) {
const [loadedUnits, setLoadedUnits] = useState<IUnits[]>([]) const [loadedUnits, setLoadedUnits] = useState<IUnits[]>([])
const loadedUnitsRef = useRef<IUnits[]>([]) const loadedUnitsRef = useRef<IUnits[]>([])
@ -28,6 +36,8 @@ export default function UnitsLayer({ crimes, units = [], filterCategory, visible
const [selectedEntityId, setSelectedEntityId] = useState<string | undefined>() const [selectedEntityId, setSelectedEntityId] = useState<string | undefined>()
const [isUnitSelected, setIsUnitSelected] = useState<boolean>(false) const [isUnitSelected, setIsUnitSelected] = useState<boolean>(false)
const [selectedDistrictId, setSelectedDistrictId] = useState<string | undefined>() const [selectedDistrictId, setSelectedDistrictId] = useState<string | undefined>()
const [unitIncident, setUnitIncident] = useState<IDistrictIncidents[]>([])
const [isLoading, setIsLoading] = useState<boolean>(false)
// Use either provided units or loaded units // Use either provided units or loaded units
const unitsData = useMemo(() => { const unitsData = useMemo(() => {
@ -230,6 +240,39 @@ export default function UnitsLayer({ crimes, units = [], filterCategory, visible
return; return;
} }
// Find all incidents in the same district as the unit
const districtIncidents: IDistrictIncidents[] = []
crimes.forEach(crime => {
// Check if this crime is in the same district as the unit
console.log("Checking district ID:", crime.district_id, "against unit district ID:", unit.district_id);
if (crime.districts.name === unit.district_name) {
crime.crime_incidents.forEach(incident => {
if (incident.locations && typeof incident.locations.distance_to_unit !== 'undefined') {
districtIncidents.push({
incident_id: incident.id,
category_name: incident.crime_categories.name,
incident_description: incident.description || 'No description',
distance_meters: incident.locations.distance_to_unit!,
timestamp: incident.timestamp
})
}
})
}
})
// Sort by distance (closest first)
districtIncidents.sort((a, b) => a.distance_meters - b.distance_meters)
console.log("Sorted district incidents:", districtIncidents);
// Update the state with the distance results
setUnitIncident(districtIncidents)
// Fly to the unit location // Fly to the unit location
map.flyTo({ map.flyTo({
center: [unit.longitude || 0, unit.latitude || 0], center: [unit.longitude || 0, unit.latitude || 0],
@ -266,7 +309,7 @@ export default function UnitsLayer({ crimes, units = [], filterCategory, visible
map.getCanvas().dispatchEvent(customEvent) map.getCanvas().dispatchEvent(customEvent)
document.dispatchEvent(customEvent) document.dispatchEvent(customEvent)
}, },
[], [crimes], // Add crimes as a dependency
) )
// Handle incident click // Handle incident click
@ -441,6 +484,8 @@ export default function UnitsLayer({ crimes, units = [], filterCategory, visible
setSelectedIncident(null) setSelectedIncident(null)
setSelectedEntityId(undefined) setSelectedEntityId(undefined)
setSelectedDistrictId(undefined) setSelectedDistrictId(undefined)
setUnitIncident([])
setIsLoading(false)
if (map && map.getLayer("units-connection-lines")) { if (map && map.getLayer("units-connection-lines")) {
map.setFilter("units-connection-lines", ["has", "unit_id"]) map.setFilter("units-connection-lines", ["has", "unit_id"])
@ -540,6 +585,8 @@ export default function UnitsLayer({ crimes, units = [], filterCategory, visible
district: selectedUnit.district_name || "No district", district: selectedUnit.district_name || "No district",
district_id: selectedUnit.district_id, district_id: selectedUnit.district_id,
}} }}
incidents={unitIncident}
isLoadingIncidents={isLoading}
/> />
)} )}

View File

@ -23,17 +23,26 @@ interface UnitPopupProps {
district?: string district?: string
district_id?: string district_id?: string
} }
distances?: IDistanceResult[] incidents?: IDistrictIncidents[]
isLoadingDistances?: boolean isLoadingIncidents?: boolean
} }
interface IDistrictIncidents {
incident_id: string
category_name: string
incident_description: string
distance_meters: number
timestamp: Date
}
export default function UnitPopup({ export default function UnitPopup({
longitude, longitude,
latitude, latitude,
onClose, onClose,
unit, unit,
distances = [], incidents = [],
isLoadingDistances = false isLoadingIncidents = false
}: UnitPopupProps) { }: UnitPopupProps) {
// Format distance to be more readable // Format distance to be more readable
@ -53,12 +62,12 @@ export default function UnitPopup({
closeOnClick={false} closeOnClick={false}
onClose={onClose} onClose={onClose}
anchor="top" anchor="top"
maxWidth="320px" maxWidth="420px"
className="unit-popup z-50" className="unit-popup z-50"
> >
<div className="relative"> <div className="relative">
<Card <Card
className="bg-background p-0 w-full max-w-[320px] shadow-xl border-0 overflow-hidden border-l-4 border-l-blue-700" className="bg-background p-0 w-full max-w-[420px] shadow-xl border-0 overflow-hidden border-l-4 border-l-blue-700"
> >
<div className="p-4 relative"> <div className="p-4 relative">
{/* Custom close button */} {/* Custom close button */}
@ -114,7 +123,7 @@ export default function UnitPopup({
)} )}
</div> </div>
{/* Distances to incidents section */} {/* Incidents to incidents section */}
<Separator className="my-3" /> <Separator className="my-3" />
<div> <div>
@ -123,16 +132,16 @@ export default function UnitPopup({
Nearby Incidents Nearby Incidents
</h4> </h4>
{isLoadingDistances ? ( {isLoadingIncidents ? (
<div className="space-y-2"> <div className="space-y-2">
<Skeleton className="h-6 w-full" /> <Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" /> <Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" /> <Skeleton className="h-6 w-full" />
</div> </div>
) : distances.length > 0 ? ( ) : incidents.length > 0 ? (
<ScrollArea className="h-[120px] rounded-md border p-2"> <ScrollArea className="h-[120px] rounded-md border p-2">
<div className="space-y-2"> <div className="space-y-2">
{distances.map((item) => ( {incidents.map((item) => (
<div key={item.incident_id} className="flex justify-between items-center text-xs border-b pb-1"> <div key={item.incident_id} className="flex justify-between items-center text-xs border-b pb-1">
<div> <div>
<p className="font-medium">{item.category_name || "Unknown"}</p> <p className="font-medium">{item.category_name || "Unknown"}</p>
@ -140,7 +149,7 @@ export default function UnitPopup({
{item.incident_description || "No description"} {item.incident_description || "No description"}
</p> </p>
</div> </div>
<Badge variant="outline" className="ml-2 whitespace-nowrap"> <Badge variant="outline" className="ml-2 whitespace-nowrap text-blue-600">
{formatDistance(item.distance_meters)} {formatDistance(item.distance_meters)}
</Badge> </Badge>
</div> </div>
@ -163,28 +172,7 @@ export default function UnitPopup({
</div> </div>
</div> </div>
</Card> </Card>
{/* Connection line */}
<div
className="absolute bottom-0 left-1/2 transform -translate-x-1/2 translate-y-full"
style={{
width: '2px',
height: '20px',
backgroundColor: 'red',
boxShadow: '0 0 4px rgba(0, 0, 0, 0.3)'
}}
/>
{/* Connection dot */}
<div
className="absolute bottom-0 left-1/2 transform -translate-x-1/2 translate-y-full"
style={{
width: '6px',
height: '6px',
backgroundColor: 'red',
borderRadius: '50%',
marginTop: '20px',
boxShadow: '0 0 4px rgba(0, 0, 0, 0.3)'
}}
/>
</div> </div>
</Popup> </Popup>
) )