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"
|
||||
|
||||
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 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() {
|
||||
return (
|
||||
<div className="container py-4 min-h-screen">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<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>
|
||||
<DashboardHeader />
|
||||
|
||||
<BentoGrid>
|
||||
{bentoGridItems.map((item, index) => (
|
||||
<BentoGridItem
|
||||
title="Incident Map"
|
||||
description="Recent crime locations in the district"
|
||||
icon={<MapPin className="w-5 h-5" />}
|
||||
colSpan="2"
|
||||
rowSpan="2"
|
||||
key={index}
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
icon={item.icon}
|
||||
colSpan={item.colSpan}
|
||||
rowSpan={item.rowSpan}
|
||||
suffixMenu={item.suffixMenu}
|
||||
>
|
||||
{/* <div className="mt-4 rounded-md border flex items-center justify-center relative overflow-hidden">
|
||||
<div className="absolute inset-0 opacity-50 bg-[url('/placeholder.svg?height=400&width=600')] bg-center bg-cover"></div>
|
||||
</div> */}
|
||||
<CrimeMap />
|
||||
{item.component}
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ import { cn } from "@/app/_lib/utils"
|
|||
import type React 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> {
|
||||
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
|
||||
title?: string
|
||||
description?: string
|
||||
header?: React.ReactNode
|
||||
icon?: React.ReactNode
|
||||
children?: React.ReactNode
|
||||
colSpan?: "1" | "2" | "3"
|
||||
rowSpan?: "1" | "2" | "3"
|
||||
colSpan?: GridSpan
|
||||
rowSpan?: GridSpan
|
||||
suffixMenu?: React.ReactNode
|
||||
component?: React.ReactNode
|
||||
}
|
||||
|
||||
export function BentoGridItem({
|
||||
|
@ -36,6 +40,7 @@ export function BentoGridItem({
|
|||
children,
|
||||
colSpan = "1",
|
||||
rowSpan = "1",
|
||||
suffixMenu,
|
||||
...props
|
||||
}: BentoGridItemProps) {
|
||||
return (
|
||||
|
@ -51,7 +56,8 @@ export function BentoGridItem({
|
|||
{...props}
|
||||
>
|
||||
{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">
|
||||
<div className="flex items-center gap-3">
|
||||
{icon && (
|
||||
<div className="p-2 w-10 h-10 shrink-0 rounded-full flex items-center justify-center bg-muted">{icon}</div>
|
||||
)}
|
||||
|
@ -62,6 +68,8 @@ export function BentoGridItem({
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
{suffixMenu && <div>{suffixMenu}</div>}
|
||||
</div>
|
||||
{children && <div>{children}</div>}
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IDistrictGeoData } from "./crime-management";
|
||||
import { IDistrictGeoData } from "./district";
|
||||
|
||||
export interface IGeoJSONPolygon {
|
||||
type: 'Polygon' | 'MultiPolygon';
|
||||
|
|
Loading…
Reference in New Issue