123 lines
4.9 KiB
TypeScript
123 lines
4.9 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect, useState } from "react"
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/app/_components/ui/card"
|
|
import { Skeleton } from "@/app/_components/ui/skeleton"
|
|
import DistrictLayer, { type DistrictFeature } from "./layers/district-layer"
|
|
import MapLegend from "./controls/map-legend"
|
|
import YearSelector from "./controls/year-selector"
|
|
import CrimeMarker, { type CrimeIncident } from "./markers/crime-marker"
|
|
import { useToast } from "@/app/_hooks/use-toast"
|
|
import MapView from "./map"
|
|
import { useQuery } from "@tanstack/react-query"
|
|
import { getAvailableYears } from "@/app/(pages)/(admin)/dashboard/crime-management/crime-overview/action"
|
|
|
|
const years = [2020, 2021, 2022, 2023, 2024, 2025]
|
|
|
|
export default function CrimeMap() {
|
|
const [loading, setLoading] = useState(false)
|
|
const [year, setYear] = useState<string>(new Date().getFullYear().toString())
|
|
const [availableYears, setAvailableYears] = useState<number[]>(years)
|
|
const [districtData, setDistrictData] = useState<DistrictFeature[]>([])
|
|
const [crimeIncidents, setCrimeIncidents] = useState<CrimeIncident[]>([])
|
|
const [showIncidents, setShowIncidents] = useState(false)
|
|
const { toast } = useToast()
|
|
|
|
// fetch available years (example function)
|
|
// const { data: year } = useQuery({
|
|
// queryKey: ["available-years"],
|
|
// queryFn: getAvailableYears,
|
|
// })
|
|
|
|
// Fetch crime incidents (example function)
|
|
const fetchCrimeIncidents = async (districtId: string) => {
|
|
try {
|
|
// This would be replaced with an actual API call
|
|
// const response = await fetch(`/api/crime-incidents?districtId=${districtId}&year=${year}`)
|
|
// const data = await response.json()
|
|
|
|
// For demonstration, we'll create some sample data
|
|
const sampleIncidents: CrimeIncident[] = [
|
|
{
|
|
id: "1",
|
|
latitude: -8.1842 + (Math.random() - 0.5) * 0.1,
|
|
longitude: 113.7031 + (Math.random() - 0.5) * 0.1,
|
|
description: "Theft incident",
|
|
date: "2023-05-15",
|
|
category: "Theft",
|
|
},
|
|
{
|
|
id: "2",
|
|
latitude: -8.1842 + (Math.random() - 0.5) * 0.1,
|
|
longitude: 113.7031 + (Math.random() - 0.5) * 0.1,
|
|
description: "Vandalism",
|
|
date: "2023-06-22",
|
|
category: "Property Crime",
|
|
},
|
|
{
|
|
id: "3",
|
|
latitude: -8.1842 + (Math.random() - 0.5) * 0.1,
|
|
longitude: 113.7031 + (Math.random() - 0.5) * 0.1,
|
|
description: "Assault",
|
|
date: "2023-07-10",
|
|
category: "Violent Crime",
|
|
},
|
|
]
|
|
|
|
setCrimeIncidents(sampleIncidents)
|
|
setShowIncidents(true)
|
|
} catch (error) {
|
|
console.error("Error fetching crime incidents:", error)
|
|
}
|
|
}
|
|
|
|
const handleDistrictClick = (feature: any) => {
|
|
const districtId = feature.properties.id
|
|
const districtName = feature.properties.name
|
|
|
|
toast({
|
|
title: `Selected: ${districtName}`,
|
|
description: "Loading crime incidents for this district...",
|
|
})
|
|
|
|
fetchCrimeIncidents(districtId)
|
|
}
|
|
|
|
const handleIncidentClick = (incident: CrimeIncident) => {
|
|
toast({
|
|
title: "Crime Incident",
|
|
description: `${incident.description} on ${incident.date}`,
|
|
})
|
|
}
|
|
|
|
return (
|
|
<Card className="w-full">
|
|
{/* <CardHeader className="flex flex-row items-center justify-between">
|
|
<CardTitle>Crime Rate Map - Jember Regency</CardTitle>
|
|
<YearSelector years={availableYears} selectedYear={year} onChange={setYear} />
|
|
</CardHeader> */}
|
|
<CardContent className="p-0">
|
|
{loading ? (
|
|
<Skeleton className="w-full rounded-md" />
|
|
) : (
|
|
<div className="relative">
|
|
<MapView mapStyle="mapbox://styles/mapbox/dark-v11">
|
|
{/* District Layer */}
|
|
<DistrictLayer data={districtData} onClick={handleDistrictClick} />
|
|
|
|
{/* Crime Incident Markers */}
|
|
{showIncidents &&
|
|
crimeIncidents.map((incident) => (
|
|
<CrimeMarker key={incident.id} incident={incident} onClick={handleIncidentClick} />
|
|
))}
|
|
|
|
{/* Map Legend */}
|
|
{/* <MapLegend position="bottom-right" /> */}
|
|
</MapView>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|