feat: Enhance unit popup to display nearby incidents and loading state
This commit is contained in:
parent
2c11cc5991
commit
834d4b02cf
|
@ -18,6 +18,14 @@ interface UnitsLayerProps {
|
|||
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) {
|
||||
const [loadedUnits, setLoadedUnits] = useState<IUnits[]>([])
|
||||
const loadedUnitsRef = useRef<IUnits[]>([])
|
||||
|
@ -28,6 +36,8 @@ export default function UnitsLayer({ crimes, units = [], filterCategory, visible
|
|||
const [selectedEntityId, setSelectedEntityId] = useState<string | undefined>()
|
||||
const [isUnitSelected, setIsUnitSelected] = useState<boolean>(false)
|
||||
const [selectedDistrictId, setSelectedDistrictId] = useState<string | undefined>()
|
||||
const [unitIncident, setUnitIncident] = useState<IDistrictIncidents[]>([])
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||
|
||||
// Use either provided units or loaded units
|
||||
const unitsData = useMemo(() => {
|
||||
|
@ -230,6 +240,39 @@ export default function UnitsLayer({ crimes, units = [], filterCategory, visible
|
|||
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
|
||||
map.flyTo({
|
||||
center: [unit.longitude || 0, unit.latitude || 0],
|
||||
|
@ -266,7 +309,7 @@ export default function UnitsLayer({ crimes, units = [], filterCategory, visible
|
|||
map.getCanvas().dispatchEvent(customEvent)
|
||||
document.dispatchEvent(customEvent)
|
||||
},
|
||||
[],
|
||||
[crimes], // Add crimes as a dependency
|
||||
)
|
||||
|
||||
// Handle incident click
|
||||
|
@ -441,6 +484,8 @@ export default function UnitsLayer({ crimes, units = [], filterCategory, visible
|
|||
setSelectedIncident(null)
|
||||
setSelectedEntityId(undefined)
|
||||
setSelectedDistrictId(undefined)
|
||||
setUnitIncident([])
|
||||
setIsLoading(false)
|
||||
|
||||
if (map && map.getLayer("units-connection-lines")) {
|
||||
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_id: selectedUnit.district_id,
|
||||
}}
|
||||
incidents={unitIncident}
|
||||
isLoadingIncidents={isLoading}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
@ -23,17 +23,26 @@ interface UnitPopupProps {
|
|||
district?: string
|
||||
district_id?: string
|
||||
}
|
||||
distances?: IDistanceResult[]
|
||||
isLoadingDistances?: boolean
|
||||
incidents?: IDistrictIncidents[]
|
||||
isLoadingIncidents?: boolean
|
||||
}
|
||||
|
||||
interface IDistrictIncidents {
|
||||
incident_id: string
|
||||
category_name: string
|
||||
incident_description: string
|
||||
distance_meters: number
|
||||
timestamp: Date
|
||||
}
|
||||
|
||||
|
||||
export default function UnitPopup({
|
||||
longitude,
|
||||
latitude,
|
||||
onClose,
|
||||
unit,
|
||||
distances = [],
|
||||
isLoadingDistances = false
|
||||
incidents = [],
|
||||
isLoadingIncidents = false
|
||||
}: UnitPopupProps) {
|
||||
|
||||
// Format distance to be more readable
|
||||
|
@ -53,12 +62,12 @@ export default function UnitPopup({
|
|||
closeOnClick={false}
|
||||
onClose={onClose}
|
||||
anchor="top"
|
||||
maxWidth="320px"
|
||||
maxWidth="420px"
|
||||
className="unit-popup z-50"
|
||||
>
|
||||
<div className="relative">
|
||||
<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">
|
||||
{/* Custom close button */}
|
||||
|
@ -114,7 +123,7 @@ export default function UnitPopup({
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Distances to incidents section */}
|
||||
{/* Incidents to incidents section */}
|
||||
<Separator className="my-3" />
|
||||
|
||||
<div>
|
||||
|
@ -123,16 +132,16 @@ export default function UnitPopup({
|
|||
Nearby Incidents
|
||||
</h4>
|
||||
|
||||
{isLoadingDistances ? (
|
||||
{isLoadingIncidents ? (
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-6 w-full" />
|
||||
<Skeleton className="h-6 w-full" />
|
||||
<Skeleton className="h-6 w-full" />
|
||||
</div>
|
||||
) : distances.length > 0 ? (
|
||||
) : incidents.length > 0 ? (
|
||||
<ScrollArea className="h-[120px] rounded-md border p-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>
|
||||
<p className="font-medium">{item.category_name || "Unknown"}</p>
|
||||
|
@ -140,7 +149,7 @@ export default function UnitPopup({
|
|||
{item.incident_description || "No description"}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="outline" className="ml-2 whitespace-nowrap">
|
||||
<Badge variant="outline" className="ml-2 whitespace-nowrap text-blue-600">
|
||||
{formatDistance(item.distance_meters)}
|
||||
</Badge>
|
||||
</div>
|
||||
|
@ -163,28 +172,7 @@ export default function UnitPopup({
|
|||
</div>
|
||||
</div>
|
||||
</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>
|
||||
</Popup>
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue