refactor: ♻️ refactor crime management into small components
This commit is contained in:
parent
c6f803b08c
commit
2bf335d59b
|
@ -0,0 +1,24 @@
|
||||||
|
export default function ActiveOfficers() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex -space-x-2 mt-4">
|
||||||
|
{[1, 2, 3, 4, 5].map((i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="w-10 h-10 rounded-full border-2 border-background bg-slate-200 flex items-center justify-center text-xs font-medium"
|
||||||
|
>
|
||||||
|
{i}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="w-10 h-10 rounded-full border-2 border-background bg-primary text-primary-foreground flex items-center justify-center text-xs font-medium">
|
||||||
|
+12
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Total on duty:</span>
|
||||||
|
<span className="font-medium">18/24 officers</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { Search } from "lucide-react"
|
||||||
|
|
||||||
|
export default function CaseSearch() {
|
||||||
|
const recentSearches = ["CR-7823", "CR-7825", "John Doe", "Jane Smith"]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-4">
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
placeholder="Search case number or name..."
|
||||||
|
className="w-full rounded-md border border-input bg-background px-9 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 text-xs text-muted-foreground">Recent searches: {recentSearches.join(", ")}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Progress } from "@/app/_components/ui/progress"
|
||||||
|
|
||||||
|
export default function CrimeStatistics() {
|
||||||
|
const statistics = [
|
||||||
|
{ name: "Violent Crime", value: 65, change: "+12%", color: "bg-red-500", textColor: "text-red-600" },
|
||||||
|
{ name: "Property Crime", value: 42, change: "-8%", color: "bg-yellow-500", textColor: "text-green-600" },
|
||||||
|
{ name: "Cybercrime", value: 78, change: "+23%", color: "bg-blue-500", textColor: "text-red-600" },
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4 mt-4">
|
||||||
|
{statistics.map((stat) => (
|
||||||
|
<div key={stat.name}>
|
||||||
|
<div className="flex justify-between mb-1 text-sm">
|
||||||
|
<span>{stat.name}</span>
|
||||||
|
<span className={stat.textColor}>{stat.change}</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={stat.value} className="h-2 bg-slate-200" indicatorClassName={stat.color} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { Badge } from "@/app/_components/ui/badge"
|
||||||
|
|
||||||
|
export default function DashboardHeader() {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between mb-8">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-3xl font-bold tracking-tight">Crime Management Dashboard</h2>
|
||||||
|
<p className="text-muted-foreground">Overview of current cases, incidents, and department status</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Badge variant="outline" className="bg-green-100 text-green-800 hover:bg-green-100">
|
||||||
|
System: Online
|
||||||
|
</Badge>
|
||||||
|
<Badge variant="outline" className="bg-blue-100 text-blue-800 hover:bg-blue-100">
|
||||||
|
Alert Level: Normal
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Progress } from "@/app/_components/ui/progress"
|
||||||
|
|
||||||
|
export default function DepartmentPerformance() {
|
||||||
|
const metrics = [
|
||||||
|
{ name: "Case Clearance Rate", value: 68, display: "68%" },
|
||||||
|
{ name: "Response Time", value: 85, display: "4.2 min avg" },
|
||||||
|
{ name: "Evidence Processing", value: 72, display: "72%" },
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4 mt-4">
|
||||||
|
{metrics.map((metric) => (
|
||||||
|
<div key={metric.name}>
|
||||||
|
<div className="flex justify-between mb-1 text-sm">
|
||||||
|
<span>{metric.name}</span>
|
||||||
|
<span>{metric.display}</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={metric.value} className="h-2" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
export default function EmergencyCalls() {
|
||||||
|
const callMetrics = [
|
||||||
|
{
|
||||||
|
label: "Current Hour",
|
||||||
|
value: "24",
|
||||||
|
badge: { text: "High", className: "bg-yellow-100 text-yellow-800" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Average Wait",
|
||||||
|
value: "1:42",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Operators Available",
|
||||||
|
value: "4/6",
|
||||||
|
badge: { text: "Understaffed", className: "bg-red-100 text-red-800" },
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2 mt-4">
|
||||||
|
{callMetrics.map((metric) => (
|
||||||
|
<div key={metric.label} className="flex justify-between items-center">
|
||||||
|
<span className="text-sm">{metric.label}</span>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span className="text-lg font-bold mr-1">{metric.value}</span>
|
||||||
|
{metric.badge && (
|
||||||
|
<span className={`text-xs px-1.5 py-0.5 rounded-full ${metric.badge.className}`}>
|
||||||
|
{metric.badge.text}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { FileText } from "lucide-react"
|
||||||
|
import { Badge } from "@/app/_components/ui/badge"
|
||||||
|
|
||||||
|
export default function EvidenceTracking() {
|
||||||
|
const evidenceItems = [
|
||||||
|
{ id: "EV-4523", type: "Weapon", case: "CR-7823", status: "Processing" },
|
||||||
|
{ id: "EV-4525", type: "Digital Media", case: "CR-7825", status: "Secured" },
|
||||||
|
{ id: "EV-4527", type: "DNA Sample", case: "CR-7830", status: "Lab Analysis" },
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2 mt-4">
|
||||||
|
{evidenceItems.map((evidence) => (
|
||||||
|
<div key={evidence.id} className="flex items-center gap-2 rounded-lg p-2">
|
||||||
|
<div className="w-8 h-8 rounded bg-slate-200 flex items-center justify-center">
|
||||||
|
<FileText className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="text-sm font-medium truncate">{evidence.id}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{evidence.type} • Case #{evidence.case}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Badge variant="outline" className="text-xs">
|
||||||
|
{evidence.status}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { AlertTriangle } from "lucide-react"
|
||||||
|
import { Badge } from "@/app/_components/ui/badge"
|
||||||
|
|
||||||
|
export default function HighPriorityCases() {
|
||||||
|
const cases = [
|
||||||
|
{ id: "CR-7823", type: "Homicide", location: "Downtown", priority: "Critical", time: "2h ago" },
|
||||||
|
{ id: "CR-7825", type: "Armed Robbery", location: "North District", priority: "High", time: "4h ago" },
|
||||||
|
{ id: "CR-7830", type: "Kidnapping", location: "West Side", priority: "Critical", time: "6h ago" },
|
||||||
|
{ id: "CR-7832", type: "Assault", location: "East District", priority: "Medium", time: "8h ago" },
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||||
|
{cases.map((case_) => (
|
||||||
|
<div key={case_.id} className="flex items-center gap-3 rounded-lg p-3 bg-red-50 border border-red-100">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center shrink-0">
|
||||||
|
<AlertTriangle className="h-5 w-5 text-red-600" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<p className="text-sm font-medium">Case #{case_.id}</p>
|
||||||
|
<Badge variant="outline" className="bg-red-100 text-red-800 hover:bg-red-100">
|
||||||
|
{case_.priority}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{case_.type} • {case_.location} • {case_.time}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { User } from "lucide-react"
|
||||||
|
import { Badge } from "@/app/_components/ui/badge"
|
||||||
|
|
||||||
|
export default function PersonsOfInterest() {
|
||||||
|
const persons = [
|
||||||
|
{ id: "POI-3421", name: "John Doe", case: "CR-7823", status: "Wanted" },
|
||||||
|
{ id: "POI-3422", name: "Jane Smith", case: "CR-7825", status: "In Custody" },
|
||||||
|
{ id: "POI-3423", name: "Robert Johnson", case: "CR-7830", status: "Under Surveillance" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const getStatusClass = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case "Wanted":
|
||||||
|
return "bg-red-100 text-red-800"
|
||||||
|
case "In Custody":
|
||||||
|
return "bg-green-100 text-green-800"
|
||||||
|
default:
|
||||||
|
return "bg-yellow-100 text-yellow-800"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2 mt-4">
|
||||||
|
{persons.map((person) => (
|
||||||
|
<div key={person.id} className="flex items-center gap-2 rounded-lg p-2">
|
||||||
|
<div className="w-8 h-8 rounded-full bg-slate-200 flex items-center justify-center">
|
||||||
|
<User className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="text-sm font-medium truncate">{person.name}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{person.id} • Case #{person.case}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Badge variant="outline" className={getStatusClass(person.status)}>
|
||||||
|
{person.status}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
export default function RecentArrests() {
|
||||||
|
const crimeTypes = ["Assault", "Theft", "DUI", "Drugs", "Trespassing", "Vandalism"]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="text-2xl font-bold">14</div>
|
||||||
|
<div className="text-xs px-2 py-1 rounded-full bg-green-100 text-green-800">+3 from yesterday</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-3 gap-2 mt-4">
|
||||||
|
{crimeTypes.map((crime) => (
|
||||||
|
<div key={crime} className="px-2 py-1 rounded-md text-xs font-medium text-center">
|
||||||
|
{crime}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,289 +1,110 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
|
import { AlertTriangle, BarChart3, Briefcase, Clock, MapPin, Search, Shield, User, Users } from "lucide-react"
|
||||||
|
import { BentoGrid, BentoGridItem, BentoGridItemProps, GridSpan } from "@/app/_components/ui/bento-grid"
|
||||||
|
|
||||||
import { AlertTriangle, BarChart3, Briefcase, FileText, MapPin, Shield, User, Users, Clock, Search } from "lucide-react"
|
|
||||||
import { Badge } from "@/app/_components/ui/badge"
|
|
||||||
import { BentoGrid, BentoGridItem } from "@/app/_components/ui/bento-grid"
|
|
||||||
import { Progress } from "@/app/_components/ui/progress"
|
|
||||||
import CrimeMap from "@/app/_components/map/crime-map"
|
import CrimeMap from "@/app/_components/map/crime-map"
|
||||||
|
import YearSelector from "@/app/_components/map/controls/year-selector"
|
||||||
|
import DashboardHeader from "./_components/dashboard-header"
|
||||||
|
import CrimeStatistics from "./_components/crime-stats"
|
||||||
|
import ActiveOfficers from "./_components/active-officer"
|
||||||
|
import HighPriorityCases from "./_components/high-priority-case"
|
||||||
|
import EvidenceTracking from "./_components/evidence-tracking"
|
||||||
|
import PersonsOfInterest from "./_components/person-interest"
|
||||||
|
import DepartmentPerformance from "./_components/departement-performance"
|
||||||
|
import RecentArrests from "./_components/recent-arrest"
|
||||||
|
import EmergencyCalls from "./_components/emergency-call"
|
||||||
|
import CaseSearch from "./_components/case-search"
|
||||||
|
|
||||||
|
const bentoGridItems: BentoGridItemProps[] = [
|
||||||
|
{
|
||||||
|
title: "Incident Map",
|
||||||
|
description: "Recent crime locations in the district",
|
||||||
|
icon: <MapPin className="w-5 h-5" />,
|
||||||
|
colSpan: "2",
|
||||||
|
rowSpan: "2",
|
||||||
|
suffixMenu: <YearSelector years={[2020, 2021, 2022, 2023, 2024]} selectedYear="" onChange={() => { }} />,
|
||||||
|
component: <CrimeMap />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Crime Statistics",
|
||||||
|
description: "Weekly crime rate analysis",
|
||||||
|
icon: <BarChart3 className="w-5 h-5" />,
|
||||||
|
component: <CrimeStatistics />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Active Officers",
|
||||||
|
description: "Personnel currently on duty",
|
||||||
|
icon: <Shield className="w-5 h-5" />,
|
||||||
|
component: <ActiveOfficers />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "High Priority Cases",
|
||||||
|
description: "Cases requiring immediate attention",
|
||||||
|
icon: <AlertTriangle className="w-5 h-5 text-red-500" />,
|
||||||
|
colSpan: "2",
|
||||||
|
component: <HighPriorityCases />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Evidence Tracking",
|
||||||
|
description: "Recently logged evidence items",
|
||||||
|
icon: <Briefcase className="w-5 h-5" />,
|
||||||
|
component: <EvidenceTracking />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Persons of Interest",
|
||||||
|
description: "Individuals under investigation",
|
||||||
|
icon: <User className="w-5 h-5" />,
|
||||||
|
component: <PersonsOfInterest />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Department Performance",
|
||||||
|
description: "Case resolution metrics",
|
||||||
|
icon: <BarChart3 className="w-5 h-5" />,
|
||||||
|
component: <DepartmentPerformance />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Recent Arrests",
|
||||||
|
description: "Last 24 hours",
|
||||||
|
icon: <Users className="w-5 h-5" />,
|
||||||
|
component: <RecentArrests />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Emergency Calls",
|
||||||
|
description: "911 call volume",
|
||||||
|
icon: <Clock className="w-5 h-5" />,
|
||||||
|
component: <EmergencyCalls />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Case Search",
|
||||||
|
description: "Quick access to case files",
|
||||||
|
icon: <Search className="w-5 h-5" />,
|
||||||
|
component: <CaseSearch />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default function CrimeManagement() {
|
export default function CrimeManagement() {
|
||||||
return (
|
return (
|
||||||
<div className="container py-4 min-h-screen">
|
<div className="container py-4 min-h-screen">
|
||||||
<div className="max-w-7xl mx-auto">
|
<div className="max-w-7xl mx-auto">
|
||||||
<div className="flex items-center justify-between mb-8">
|
<DashboardHeader />
|
||||||
<div>
|
|
||||||
<h2 className="text-3xl font-bold tracking-tight">Crime Management Dashboard</h2>
|
|
||||||
<p className="text-muted-foreground">Overview of current cases, incidents, and department status</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Badge variant="outline" className="bg-green-100 text-green-800 hover:bg-green-100">
|
|
||||||
System: Online
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="outline" className="bg-blue-100 text-blue-800 hover:bg-blue-100">
|
|
||||||
Alert Level: Normal
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<BentoGrid>
|
<BentoGrid>
|
||||||
<BentoGridItem
|
{bentoGridItems.map((item, index) => (
|
||||||
title="Incident Map"
|
<BentoGridItem
|
||||||
description="Recent crime locations in the district"
|
key={index}
|
||||||
icon={<MapPin className="w-5 h-5" />}
|
title={item.title}
|
||||||
colSpan="2"
|
description={item.description}
|
||||||
rowSpan="2"
|
icon={item.icon}
|
||||||
>
|
colSpan={item.colSpan}
|
||||||
{/* <div className="mt-4 rounded-md border flex items-center justify-center relative overflow-hidden">
|
rowSpan={item.rowSpan}
|
||||||
<div className="absolute inset-0 opacity-50 bg-[url('/placeholder.svg?height=400&width=600')] bg-center bg-cover"></div>
|
suffixMenu={item.suffixMenu}
|
||||||
</div> */}
|
>
|
||||||
<CrimeMap />
|
{item.component}
|
||||||
</BentoGridItem>
|
</BentoGridItem>
|
||||||
|
))}
|
||||||
<BentoGridItem
|
|
||||||
title="Crime Statistics"
|
|
||||||
description="Weekly crime rate analysis"
|
|
||||||
icon={<BarChart3 className="w-5 h-5" />}
|
|
||||||
>
|
|
||||||
<div className="space-y-4 mt-4">
|
|
||||||
<div>
|
|
||||||
<div className="flex justify-between mb-1 text-sm">
|
|
||||||
<span>Violent Crime</span>
|
|
||||||
<span className="text-red-600">+12%</span>
|
|
||||||
</div>
|
|
||||||
<Progress value={65} className="h-2 bg-slate-200" indicatorClassName="bg-red-500" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="flex justify-between mb-1 text-sm">
|
|
||||||
<span>Property Crime</span>
|
|
||||||
<span className="text-green-600">-8%</span>
|
|
||||||
</div>
|
|
||||||
<Progress value={42} className="h-2 bg-slate-200" indicatorClassName="bg-yellow-500" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="flex justify-between mb-1 text-sm">
|
|
||||||
<span>Cybercrime</span>
|
|
||||||
<span className="text-red-600">+23%</span>
|
|
||||||
</div>
|
|
||||||
<Progress value={78} className="h-2 bg-slate-200" indicatorClassName="bg-blue-500" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BentoGridItem>
|
|
||||||
|
|
||||||
<BentoGridItem
|
|
||||||
title="Active Officers"
|
|
||||||
description="Personnel currently on duty"
|
|
||||||
icon={<Shield className="w-5 h-5" />}
|
|
||||||
>
|
|
||||||
<div className="flex -space-x-2 mt-4">
|
|
||||||
{[1, 2, 3, 4, 5].map((i) => (
|
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
className="w-10 h-10 rounded-full border-2 border-background bg-slate-200 flex items-center justify-center text-xs font-medium"
|
|
||||||
>
|
|
||||||
{i}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div className="w-10 h-10 rounded-full border-2 border-background bg-primary text-primary-foreground flex items-center justify-center text-xs font-medium">
|
|
||||||
+12
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-4 flex justify-between text-sm">
|
|
||||||
<span className="text-muted-foreground">Total on duty:</span>
|
|
||||||
<span className="font-medium">18/24 officers</span>
|
|
||||||
</div>
|
|
||||||
</BentoGridItem>
|
|
||||||
|
|
||||||
<BentoGridItem
|
|
||||||
title="High Priority Cases"
|
|
||||||
description="Cases requiring immediate attention"
|
|
||||||
icon={<AlertTriangle className="w-5 h-5 text-red-500" />}
|
|
||||||
colSpan="2"
|
|
||||||
>
|
|
||||||
<div className="space-y-3 mt-4">
|
|
||||||
{[
|
|
||||||
{ id: "CR-7823", type: "Homicide", location: "Downtown", priority: "Critical", time: "2h ago" },
|
|
||||||
{ id: "CR-7825", type: "Armed Robbery", location: "North District", priority: "High", time: "4h ago" },
|
|
||||||
{ id: "CR-7830", type: "Kidnapping", location: "West Side", priority: "Critical", time: "6h ago" },
|
|
||||||
].map((case_) => (
|
|
||||||
<div key={case_.id} className="flex items-center gap-3 rounded-lg p-3 bg-red-50 border border-red-100">
|
|
||||||
<div className="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center shrink-0">
|
|
||||||
<AlertTriangle className="h-5 w-5 text-red-600" />
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<p className="text-sm font-medium">Case #{case_.id}</p>
|
|
||||||
<Badge variant="outline" className="bg-red-100 text-red-800 hover:bg-red-100">
|
|
||||||
{case_.priority}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{case_.type} • {case_.location} • {case_.time}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</BentoGridItem>
|
|
||||||
|
|
||||||
<BentoGridItem
|
|
||||||
title="Evidence Tracking"
|
|
||||||
description="Recently logged evidence items"
|
|
||||||
icon={<Briefcase className="w-5 h-5" />}
|
|
||||||
>
|
|
||||||
<div className="space-y-2 mt-4">
|
|
||||||
{[
|
|
||||||
{ id: "EV-4523", type: "Weapon", case: "CR-7823", status: "Processing" },
|
|
||||||
{ id: "EV-4525", type: "Digital Media", case: "CR-7825", status: "Secured" },
|
|
||||||
{ id: "EV-4527", type: "DNA Sample", case: "CR-7830", status: "Lab Analysis" },
|
|
||||||
].map((evidence) => (
|
|
||||||
<div key={evidence.id} className="flex items-center gap-2 rounded-lg p-2">
|
|
||||||
<div className="w-8 h-8 rounded bg-slate-200 flex items-center justify-center">
|
|
||||||
<FileText className="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<p className="text-sm font-medium truncate">{evidence.id}</p>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{evidence.type} • Case #{evidence.case}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Badge variant="outline" className="text-xs">
|
|
||||||
{evidence.status}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</BentoGridItem>
|
|
||||||
|
|
||||||
<BentoGridItem
|
|
||||||
title="Persons of Interest"
|
|
||||||
description="Individuals under investigation"
|
|
||||||
icon={<User className="w-5 h-5" />}
|
|
||||||
>
|
|
||||||
<div className="space-y-2 mt-4">
|
|
||||||
{[
|
|
||||||
{ id: "POI-3421", name: "John Doe", case: "CR-7823", status: "Wanted" },
|
|
||||||
{ id: "POI-3422", name: "Jane Smith", case: "CR-7825", status: "In Custody" },
|
|
||||||
{ id: "POI-3423", name: "Robert Johnson", case: "CR-7830", status: "Under Surveillance" },
|
|
||||||
].map((person) => (
|
|
||||||
<div key={person.id} className="flex items-center gap-2 rounded-lg p-2">
|
|
||||||
<div className="w-8 h-8 rounded-full bg-slate-200 flex items-center justify-center">
|
|
||||||
<User className="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<p className="text-sm font-medium truncate">{person.name}</p>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{person.id} • Case #{person.case}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Badge
|
|
||||||
variant="outline"
|
|
||||||
className={
|
|
||||||
person.status === "Wanted"
|
|
||||||
? "bg-red-100 text-red-800"
|
|
||||||
: person.status === "In Custody"
|
|
||||||
? "bg-green-100 text-green-800"
|
|
||||||
: "bg-yellow-100 text-yellow-800"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{person.status}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</BentoGridItem>
|
|
||||||
|
|
||||||
<BentoGridItem
|
|
||||||
title="Department Performance"
|
|
||||||
description="Case resolution metrics"
|
|
||||||
icon={<BarChart3 className="w-5 h-5" />}
|
|
||||||
>
|
|
||||||
<div className="space-y-4 mt-4">
|
|
||||||
<div>
|
|
||||||
<div className="flex justify-between mb-1 text-sm">
|
|
||||||
<span>Case Clearance Rate</span>
|
|
||||||
<span>68%</span>
|
|
||||||
</div>
|
|
||||||
<Progress value={68} className="h-2" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="flex justify-between mb-1 text-sm">
|
|
||||||
<span>Response Time</span>
|
|
||||||
<span>4.2 min avg</span>
|
|
||||||
</div>
|
|
||||||
<Progress value={85} className="h-2" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="flex justify-between mb-1 text-sm">
|
|
||||||
<span>Evidence Processing</span>
|
|
||||||
<span>72%</span>
|
|
||||||
</div>
|
|
||||||
<Progress value={72} className="h-2" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BentoGridItem>
|
|
||||||
|
|
||||||
<BentoGridItem title="Recent Arrests" description="Last 24 hours" icon={<Users className="w-5 h-5" />}>
|
|
||||||
<div className="mt-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="text-2xl font-bold">14</div>
|
|
||||||
<div className="text-xs px-2 py-1 rounded-full bg-green-100 text-green-800">+3 from yesterday</div>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-3 gap-2 mt-4">
|
|
||||||
{["Assault", "Theft", "DUI", "Drugs", "Trespassing", "Vandalism"].map((crime) => (
|
|
||||||
<div key={crime} className="px-2 py-1 rounded-md text-xs font-medium text-center">
|
|
||||||
{crime}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BentoGridItem>
|
|
||||||
|
|
||||||
<BentoGridItem title="Emergency Calls" description="911 call volume" icon={<Clock className="w-5 h-5" />}>
|
|
||||||
<div className="space-y-2 mt-4">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="text-sm">Current Hour</span>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<span className="text-lg font-bold mr-1">24</span>
|
|
||||||
<span className="text-xs px-1.5 py-0.5 rounded-full bg-yellow-100 text-yellow-800">High</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="text-sm">Average Wait</span>
|
|
||||||
<span className="text-lg font-bold">1:42</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="text-sm">Operators Available</span>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<span className="text-lg font-bold mr-1">4/6</span>
|
|
||||||
<span className="text-xs px-1.5 py-0.5 rounded-full bg-red-100 text-red-800">Understaffed</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BentoGridItem>
|
|
||||||
|
|
||||||
<BentoGridItem
|
|
||||||
title="Case Search"
|
|
||||||
description="Quick access to case files"
|
|
||||||
icon={<Search className="w-5 h-5" />}
|
|
||||||
>
|
|
||||||
<div className="mt-4">
|
|
||||||
<div className="relative">
|
|
||||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
|
||||||
<input
|
|
||||||
type="search"
|
|
||||||
placeholder="Search case number or name..."
|
|
||||||
className="w-full rounded-md border border-input bg-background px-9 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mt-3 text-xs text-muted-foreground">
|
|
||||||
Recent searches: CR-7823, CR-7825, John Doe, Jane Smith
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BentoGridItem>
|
|
||||||
</BentoGrid>
|
</BentoGrid>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { cn } from "@/app/_lib/utils"
|
||||||
import type React from "react"
|
import type React from "react"
|
||||||
import type { HTMLAttributes } from "react"
|
import type { HTMLAttributes } from "react"
|
||||||
|
|
||||||
|
// Define specific types for colSpan and rowSpan
|
||||||
|
export type GridSpan = "1" | "2" | "3"
|
||||||
|
|
||||||
interface BentoGridProps extends HTMLAttributes<HTMLDivElement> {
|
interface BentoGridProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
className?: string
|
className?: string
|
||||||
|
@ -16,15 +18,17 @@ export function BentoGrid({ className, children, ...props }: BentoGridProps) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BentoGridItemProps extends HTMLAttributes<HTMLDivElement> {
|
export interface BentoGridItemProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
className?: string
|
className?: string
|
||||||
title?: string
|
title?: string
|
||||||
description?: string
|
description?: string
|
||||||
header?: React.ReactNode
|
header?: React.ReactNode
|
||||||
icon?: React.ReactNode
|
icon?: React.ReactNode
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
colSpan?: "1" | "2" | "3"
|
colSpan?: GridSpan
|
||||||
rowSpan?: "1" | "2" | "3"
|
rowSpan?: GridSpan
|
||||||
|
suffixMenu?: React.ReactNode
|
||||||
|
component?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BentoGridItem({
|
export function BentoGridItem({
|
||||||
|
@ -36,6 +40,7 @@ export function BentoGridItem({
|
||||||
children,
|
children,
|
||||||
colSpan = "1",
|
colSpan = "1",
|
||||||
rowSpan = "1",
|
rowSpan = "1",
|
||||||
|
suffixMenu,
|
||||||
...props
|
...props
|
||||||
}: BentoGridItemProps) {
|
}: BentoGridItemProps) {
|
||||||
return (
|
return (
|
||||||
|
@ -51,16 +56,19 @@ export function BentoGridItem({
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{header && <div className="mb-4">{header}</div>}
|
{header && <div className="mb-4">{header}</div>}
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center justify-between gap-3 mb-4">
|
||||||
{icon && (
|
<div className="flex items-center gap-3">
|
||||||
<div className="p-2 w-10 h-10 shrink-0 rounded-full flex items-center justify-center bg-muted">{icon}</div>
|
{icon && (
|
||||||
)}
|
<div className="p-2 w-10 h-10 shrink-0 rounded-full flex items-center justify-center bg-muted">{icon}</div>
|
||||||
{(title || description) && (
|
)}
|
||||||
<div className="space-y-1">
|
{(title || description) && (
|
||||||
{title && <h3 className="font-semibold tracking-tight">{title}</h3>}
|
<div className="space-y-1">
|
||||||
{description && <p className="text-sm text-muted-foreground">{description}</p>}
|
{title && <h3 className="font-semibold tracking-tight">{title}</h3>}
|
||||||
</div>
|
{description && <p className="text-sm text-muted-foreground">{description}</p>}
|
||||||
)}
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{suffixMenu && <div>{suffixMenu}</div>}
|
||||||
</div>
|
</div>
|
||||||
{children && <div>{children}</div>}
|
{children && <div>{children}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { IDistrictGeoData } from "./crime-management";
|
import { IDistrictGeoData } from "./district";
|
||||||
|
|
||||||
export interface IGeoJSONPolygon {
|
export interface IGeoJSONPolygon {
|
||||||
type: 'Polygon' | 'MultiPolygon';
|
type: 'Polygon' | 'MultiPolygon';
|
||||||
|
|
Loading…
Reference in New Issue