feat: add initial data seeding for resources, roles, demographics, crime incidents, and geographic data

- Created resources data structure in `resources.ts`
- Added roles data structure in `roles.ts`
- Implemented seeding for crime categories and incidents with detailed logic in `crime-category.ts` and `crime-incident.ts`
- Developed demographic data seeding logic in `demographic.ts`
- Implemented geographic data seeding from GeoJSON files in `geographic.ts`
- Added permission seeding logic in `permission.ts`
- Created resource and role seeding scripts in `resource.ts` and `role.ts`
This commit is contained in:
vergiLgood1 2025-04-25 02:04:04 +07:00
parent 410535e1d9
commit 63b0721859
137 changed files with 29515 additions and 1782 deletions

View File

@ -14,7 +14,7 @@ import {
SidebarRail,
} from "@/app/_components/ui/sidebar";
import { NavPreMain } from "./navigations/nav-pre-main";
import { navData } from "@/prisma/data/nav";
import { navData } from "@/prisma/data/jsons/nav";
import { TeamSwitcher } from "../../../_components/team-switcher";
import { useGetCurrentUserQuery } from "../dashboard/user-management/_queries/queries";

View File

@ -23,8 +23,8 @@ import {
applyCookiePreferences,
} from "@/app/_utils/cookies/cookies-manager";
import { toast } from "sonner";
import { initialTimezones, TimezoneType } from "@/prisma/data/timezones";
import { languages, LanguageType } from "@/prisma/data/languages";
import { initialTimezones, TimezoneType } from "@/prisma/data/jsons/timezones";
import { languages, LanguageType } from "@/prisma/data/jsons/languages";
export default function PreferencesSettings() {
const [language, setLanguage] = useState("en-US");

View File

@ -0,0 +1,26 @@
import { Badge } from "@/app/_components/ui/badge"
import { Button } from "@/app/_components/ui/button"
import { Download, Calendar } from "lucide-react"
export default function AnalyticsHeader() {
return (
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-8">
<div>
<h2 className="text-3xl font-bold tracking-tight">Analytics & Reporting</h2>
<p className="text-muted-foreground">Crime statistics, trends, and performance metrics</p>
</div>
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4">
<div className="flex items-center gap-2">
<Badge variant="outline" className="bg-blue-100 text-blue-800 hover:bg-blue-100 flex items-center">
<Calendar className="h-3 w-3 mr-1" />
Apr 1 - Apr 30, 2023
</Badge>
</div>
<Button className="flex items-center">
<Download className="h-4 w-4 mr-2" />
Export Reports
</Button>
</div>
</div>
)
}

View File

@ -0,0 +1,79 @@
"use client"
import { Progress } from "@/app/_components/ui/progress"
import { Badge } from "@/app/_components/ui/badge"
import { TrendingUp, TrendingDown } from "lucide-react"
export default function CaseResolutionRates() {
const resolutionData = [
{
type: "Homicide",
rate: 72,
trend: "+5%",
direction: "up",
},
{
type: "Assault",
rate: 65,
trend: "+3%",
direction: "up",
},
{
type: "Robbery",
rate: 48,
trend: "-2%",
direction: "down",
},
{
type: "Theft",
rate: 35,
trend: "+1%",
direction: "up",
},
{
type: "Cybercrime",
rate: 28,
trend: "-4%",
direction: "down",
},
]
return (
<div className="mt-4 space-y-4">
<div className="flex items-center justify-between">
<div>
<div className="text-2xl font-bold">52%</div>
<div className="text-xs text-muted-foreground">Overall Case Clearance</div>
</div>
<Badge variant="outline" className="bg-green-100 text-green-800">
+2% from last month
</Badge>
</div>
<div className="space-y-3">
{resolutionData.map((item) => (
<div key={item.type}>
<div className="flex justify-between text-xs mb-1">
<span>{item.type}</span>
<div className="flex items-center">
<span>{item.rate}%</span>
<span className={`ml-1 ${item.direction === "up" ? "text-green-600" : "text-red-600"}`}>
{item.direction === "up" ? (
<TrendingUp className="h-3 w-3 ml-1" />
) : (
<TrendingDown className="h-3 w-3 ml-1" />
)}
</span>
</div>
</div>
<Progress
value={item.rate}
className="h-2"
indicatorClassName={item.rate > 60 ? "bg-green-500" : item.rate > 40 ? "bg-yellow-500" : "bg-red-500"}
/>
</div>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,198 @@
"use client"
import { useState } from "react"
import { Button } from "@/app/_components/ui/button"
import { Tabs, TabsList, TabsTrigger } from "@/app/_components/ui/tabs"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartConfig,
ChartLegendContent,
} from "@/app/_components/ui/chart"
import { Line, LineChart, XAxis, YAxis, CartesianGrid, ResponsiveContainer, Tooltip, BarChart } from "recharts"
const chartConfig = {
desktop: {
label: "Desktop",
color: "#2563eb",
},
mobile: {
label: "Mobile",
color: "#60a5fa",
},
} satisfies ChartConfig
export default function CrimeTrends() {
const [timeRange, setTimeRange] = useState("6m")
const [chartType, setChartType] = useState("all")
// Sample data for the chart
const data = [
{ month: "Jan", violent: 42, property: 85, cyber: 18, other: 30 },
{ month: "Feb", violent: 38, property: 78, cyber: 22, other: 28 },
{ month: "Mar", violent: 45, property: 82, cyber: 25, other: 32 },
{ month: "Apr", violent: 40, property: 75, cyber: 30, other: 35 },
{ month: "May", violent: 35, property: 72, cyber: 28, other: 30 },
{ month: "Jun", violent: 32, property: 68, cyber: 32, other: 28 },
]
return (
<div className="mt-4 h-[300px]">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-4">
<Tabs defaultValue={chartType} onValueChange={setChartType} className="w-full sm:w-auto">
<TabsList className="grid grid-cols-4 w-full sm:w-auto">
<TabsTrigger value="all">All Crimes</TabsTrigger>
<TabsTrigger value="violent">Violent</TabsTrigger>
<TabsTrigger value="property">Property</TabsTrigger>
<TabsTrigger value="cyber">Cyber</TabsTrigger>
</TabsList>
</Tabs>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => setTimeRange("3m")}
className={timeRange === "3m" ? "bg-muted" : ""}
>
3M
</Button>
<Button
variant="outline"
size="sm"
onClick={() => setTimeRange("6m")}
className={timeRange === "6m" ? "bg-muted" : ""}
>
6M
</Button>
<Button
variant="outline"
size="sm"
onClick={() => setTimeRange("1y")}
className={timeRange === "1y" ? "bg-muted" : ""}
>
1Y
</Button>
<Button
variant="outline"
size="sm"
onClick={() => setTimeRange("all")}
className={timeRange === "all" ? "bg-muted" : ""}
>
All
</Button>
</div>
</div>
<ChartContainer className="h-[250px]" config={chartConfig}>
<>
<ResponsiveContainer width="100%" height="100%">
<LineChart data={data} margin={{ top: 5, right: 10, left: 0, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis dataKey="month" className="text-xs" />
<YAxis className="text-xs" />
<Tooltip content={<CustomTooltip />} />
{chartType === "all" || chartType === "violent" ? (
<Line
type="monotone"
dataKey="violent"
stroke="#ef4444"
strokeWidth={2}
dot={{ r: 4 }}
activeDot={{ r: 6 }}
/>
) : null}
{chartType === "all" || chartType === "property" ? (
<Line
type="monotone"
dataKey="property"
stroke="#3b82f6"
strokeWidth={2}
dot={{ r: 4 }}
activeDot={{ r: 6 }}
/>
) : null}
{chartType === "all" || chartType === "cyber" ? (
<Line
type="monotone"
dataKey="cyber"
stroke="#10b981"
strokeWidth={2}
dot={{ r: 4 }}
activeDot={{ r: 6 }}
/>
) : null}
{chartType === "all" ? (
<Line
type="monotone"
dataKey="other"
stroke="#a855f7"
strokeWidth={2}
dot={{ r: 4 }}
activeDot={{ r: 6 }}
/>
) : null}
</LineChart>
</ResponsiveContainer>
<ChartLegend className="justify-center mt-2">
{chartType === "all" || chartType === "violent" ? (
<ChartLegendContent>
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full" style={{ backgroundColor: "#ef4444" }}></div>
<span>Violent Crime</span>
</div>
</ChartLegendContent>
) : null}
{chartType === "all" || chartType === "property" ? (
<ChartLegendContent>
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full" style={{ backgroundColor: "#3b82f6" }}></div>
<span>Property Crime</span>
</div>
</ChartLegendContent>
) : null}
{chartType === "all" || chartType === "cyber" ? (
<ChartLegendContent>
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full" style={{ backgroundColor: "#10b981" }}></div>
<span>Cybercrime</span>
</div>
</ChartLegendContent>
) : null}
{chartType === "all" ? (
<ChartLegendContent>
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full" style={{ backgroundColor: "#a855f7" }}></div>
<span>Other</span>
</div>
</ChartLegendContent>
) : null}
</ChartLegend>
</>
</ChartContainer>
</div>
)
}
function CustomTooltip({ active, payload, label }: any) {
if (active && payload && payload.length) {
return (
<ChartTooltip content={
<ChartTooltipContent>
<div className="font-medium">{label}</div>
{payload.map((entry: any, index: number) => (
<div key={`item-${index}`} className="flex items-center gap-2 text-sm">
<div className="h-2 w-2 rounded-full" style={{ backgroundColor: entry.color }}></div>
<span className="font-medium">{entry.name}:</span>
<span>{entry.value} incidents</span>
</div>
))}
</ChartTooltipContent>
} />
)
}
return null
}

View File

@ -0,0 +1,92 @@
import { Button } from "@/app/_components/ui/button"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/app/_components/ui/select"
import { Checkbox } from "@/app/_components/ui/checkbox"
import { Download, FileText } from "lucide-react"
export default function CustomReportBuilder() {
const reportTypes = [
{ id: "crime-stats", label: "Crime Statistics" },
{ id: "officer-perf", label: "Officer Performance" },
{ id: "response-time", label: "Response Times" },
{ id: "case-resolution", label: "Case Resolution" },
{ id: "geographic", label: "Geographic Analysis" },
]
const savedReports = [
{ name: "Monthly Crime Summary", date: "Apr 30, 2023", type: "PDF" },
{ name: "Q1 Performance Review", date: "Mar 31, 2023", type: "XLSX" },
]
return (
<div className="mt-4 space-y-4">
<div className="space-y-3">
<div>
<label className="text-xs text-muted-foreground mb-1 block">Report Type</label>
<Select>
<SelectTrigger>
<SelectValue placeholder="Select report type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="comprehensive">Comprehensive Report</SelectItem>
<SelectItem value="summary">Summary Report</SelectItem>
<SelectItem value="comparative">Comparative Analysis</SelectItem>
<SelectItem value="trend">Trend Analysis</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label className="text-xs text-muted-foreground mb-1 block">Time Period</label>
<Select>
<SelectTrigger>
<SelectValue placeholder="Select time period" />
</SelectTrigger>
<SelectContent>
<SelectItem value="current-month">Current Month</SelectItem>
<SelectItem value="previous-month">Previous Month</SelectItem>
<SelectItem value="quarter">Last Quarter</SelectItem>
<SelectItem value="year">Year to Date</SelectItem>
<SelectItem value="custom">Custom Range</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label className="text-xs text-muted-foreground mb-1 block">Include Sections</label>
<div className="grid grid-cols-2 gap-2 mt-1">
{reportTypes.map((type) => (
<div key={type.id} className="flex items-center space-x-2">
<Checkbox id={type.id} />
<label htmlFor={type.id} className="text-xs">
{type.label}
</label>
</div>
))}
</div>
</div>
<Button className="w-full mt-2">
<FileText className="h-4 w-4 mr-2" />
Generate Report
</Button>
</div>
<div className="border-t pt-3">
<h4 className="text-sm font-medium mb-2">Saved Reports</h4>
<div className="space-y-2">
{savedReports.map((report, index) => (
<div key={index} className="flex items-center justify-between p-2 border rounded-lg">
<div>
<div className="text-sm font-medium">{report.name}</div>
<div className="text-xs text-muted-foreground">{report.date}</div>
</div>
<Button variant="ghost" size="sm">
<Download className="h-4 w-4" />
</Button>
</div>
))}
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,98 @@
import { Badge } from "@/app/_components/ui/badge"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/app/_components/ui/select"
export default function GeographicalAnalysis() {
return (
<div className="mt-4 h-full">
<div className="flex justify-between items-center mb-4">
<Select defaultValue="all">
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Crime Type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Crimes</SelectItem>
<SelectItem value="violent">Violent Crimes</SelectItem>
<SelectItem value="property">Property Crimes</SelectItem>
<SelectItem value="cyber">Cybercrimes</SelectItem>
</SelectContent>
</Select>
<Select defaultValue="heat">
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Map Type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="heat">Heat Map</SelectItem>
<SelectItem value="pin">Pin Map</SelectItem>
<SelectItem value="district">District Map</SelectItem>
</SelectContent>
</Select>
</div>
<div className="h-[400px] rounded-md bg-slate-100 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 className="absolute top-2 right-2 bg-background/80 backdrop-blur-sm p-2 rounded-md text-xs font-medium">
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-red-500"></span> High Density
</div>
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-yellow-500"></span> Medium Density
</div>
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-green-500"></span> Low Density
</div>
</div>
<div className="z-10 bg-background/80 backdrop-blur-sm p-3 rounded-lg">
<span className="font-medium">Crime Hotspot Map</span>
<div className="text-xs text-muted-foreground mt-1">Showing data for April 2023</div>
</div>
</div>
<div className="mt-4 grid grid-cols-2 gap-2">
<div className="border rounded-lg p-3">
<h4 className="text-sm font-medium">Highest Crime Areas</h4>
<div className="mt-2 space-y-2">
<div className="flex justify-between items-center">
<span className="text-xs">Downtown District</span>
<Badge variant="outline" className="bg-red-100 text-red-800">
High
</Badge>
</div>
<div className="flex justify-between items-center">
<span className="text-xs">West Side Commercial</span>
<Badge variant="outline" className="bg-yellow-100 text-yellow-800">
Medium
</Badge>
</div>
<div className="flex justify-between items-center">
<span className="text-xs">North Transit Hub</span>
<Badge variant="outline" className="bg-yellow-100 text-yellow-800">
Medium
</Badge>
</div>
</div>
</div>
<div className="border rounded-lg p-3">
<h4 className="text-sm font-medium">Crime Reduction</h4>
<div className="mt-2 space-y-2">
<div className="flex justify-between items-center">
<span className="text-xs">East Residential</span>
<span className="text-xs text-green-600">-15% MoM</span>
</div>
<div className="flex justify-between items-center">
<span className="text-xs">South Park Area</span>
<span className="text-xs text-green-600">-8% MoM</span>
</div>
<div className="flex justify-between items-center">
<span className="text-xs">Central Business</span>
<span className="text-xs text-red-600">+5% MoM</span>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,145 @@
"use client"
import { Tabs, TabsList, TabsTrigger } from "@/app/_components/ui/tabs"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
ChartConfig,
} from "@/app/_components/ui/chart"
import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from "recharts"
import { BarChart } from "lucide-react"
const chartConfig = {
desktop: {
label: "Desktop",
color: "#2563eb",
},
mobile: {
label: "Mobile",
color: "#60a5fa",
},
} satisfies ChartConfig
export default function IncidentTypeBreakdown() {
// Sample data for the chart
const data = [
{ name: "Theft", value: 35, color: "#3b82f6" },
{ name: "Assault", value: 20, color: "#ef4444" },
{ name: "Vandalism", value: 15, color: "#f59e0b" },
{ name: "Fraud", value: 10, color: "#10b981" },
{ name: "Drugs", value: 8, color: "#8b5cf6" },
{ name: "Other", value: 12, color: "#6b7280" },
]
return (
<div className="mt-4">
<div className="flex justify-between items-center mb-4">
<Tabs defaultValue="pie">
<TabsList>
<TabsTrigger value="pie">Pie Chart</TabsTrigger>
<TabsTrigger value="bar">Bar Chart</TabsTrigger>
</TabsList>
</Tabs>
<Tabs defaultValue="all">
<TabsList>
<TabsTrigger value="all">All Time</TabsTrigger>
<TabsTrigger value="month">This Month</TabsTrigger>
<TabsTrigger value="week">This Week</TabsTrigger>
</TabsList>
</Tabs>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<ChartContainer config={chartConfig} className="h-[250px]">
<>
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie data={data} cx="50%" cy="50%" innerRadius={60} outerRadius={80} paddingAngle={2} dataKey="value">
{data.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip content={<CustomTooltip />} />
</PieChart>
</ResponsiveContainer>
<ChartLegend className="justify-center mt-2">
{data.map((item) => (
<ChartLegendContent key={item.name}>
<div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full" style={{ backgroundColor: item.color }}></span>
<span>{item.name}</span>
</div>
</ChartLegendContent>
))}
</ChartLegend>
</>
</ChartContainer>
<div className="space-y-3">
<div className="border rounded-lg p-3">
<h4 className="text-sm font-medium mb-2">Key Insights</h4>
<ul className="text-xs space-y-2">
<li className="flex items-start gap-2">
<span className="w-2 h-2 rounded-full bg-blue-500 mt-1"></span>
<span>
Theft accounts for the largest portion of incidents (35%), with a 5% increase from the previous month.
</span>
</li>
<li className="flex items-start gap-2">
<span className="w-2 h-2 rounded-full bg-red-500 mt-1"></span>
<span>Assault incidents have decreased by 3% compared to the previous month.</span>
</li>
<li className="flex items-start gap-2">
<span className="w-2 h-2 rounded-full bg-green-500 mt-1"></span>
<span>Fraud reports have increased by 8%, indicating a growing trend in financial crimes.</span>
</li>
</ul>
</div>
<div className="border rounded-lg p-3">
<h4 className="text-sm font-medium mb-2">Recommendations</h4>
<ul className="text-xs space-y-2">
<li className="flex items-start gap-2">
<span className="w-2 h-2 rounded-full bg-purple-500 mt-1"></span>
<span>Increase patrols in high-theft areas, particularly in commercial districts.</span>
</li>
<li className="flex items-start gap-2">
<span className="w-2 h-2 rounded-full bg-purple-500 mt-1"></span>
<span>Launch a public awareness campaign about fraud prevention.</span>
</li>
<li className="flex items-start gap-2">
<span className="w-2 h-2 rounded-full bg-purple-500 mt-1"></span>
<span>Continue community engagement programs that have helped reduce assault incidents.</span>
</li>
</ul>
</div>
</div>
</div>
</div>
)
}
function CustomTooltip({ active, payload }: any) {
if (active && payload && payload.length) {
return (
<ChartTooltip content={
<ChartTooltipContent>
<div className="font-medium">{payload[0].name}</div>
<div className="flex items-center gap-2 text-sm">
<span>{payload[0].value}% of total incidents</span>
</div>
</ChartTooltipContent>
}>
</ChartTooltip>
)
}
return null
}

View File

@ -0,0 +1,85 @@
"use client"
import { Progress } from "@/app/_components/ui/progress"
import { Award } from "lucide-react"
export default function OfficerPerformanceMetrics() {
const officers = [
{
name: "Emily Parker",
metric: "Case Clearance",
value: 92,
rank: 1,
},
{
name: "Michael Chen",
metric: "Response Time",
value: 88,
rank: 2,
},
{
name: "Sarah Johnson",
metric: "Evidence Processing",
value: 95,
rank: 3,
},
]
const departmentMetrics = [
{
name: "Cases Assigned",
value: 245,
change: "+12",
period: "this month",
},
{
name: "Cases Closed",
value: 182,
change: "+8",
period: "this month",
},
{
name: "Avg. Case Duration",
value: "18.5 days",
change: "-2.3",
period: "from last month",
},
]
return (
<div className="mt-4 space-y-4">
<div className="border rounded-lg p-3">
<h4 className="font-medium text-sm mb-3 flex items-center">
<Award className="h-4 w-4 mr-1" />
Top Performers
</h4>
<div className="space-y-3">
{officers.map((officer) => (
<div key={officer.name}>
<div className="flex justify-between text-sm mb-1">
<span>{officer.name}</span>
<span>
{officer.metric}: {officer.value}%
</span>
</div>
<Progress value={officer.value} className="h-2" />
</div>
))}
</div>
</div>
<div className="grid grid-cols-2 gap-2">
{departmentMetrics.map((metric) => (
<div key={metric.name} className="border rounded-lg p-3">
<div className="text-sm text-muted-foreground">{metric.name}</div>
<div className="text-xl font-bold mt-1">{metric.value}</div>
<div className="text-xs text-green-600 mt-1">
{metric.change} {metric.period}
</div>
</div>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,84 @@
import { Badge } from "@/app/_components/ui/badge"
import { AlertTriangle, TrendingUp } from "lucide-react"
export default function PredictiveAnalytics() {
const predictions = [
{
area: "Downtown District",
crimeType: "Theft",
riskLevel: "High",
confidence: "85%",
trend: "Increasing",
},
{
area: "West Side Commercial",
crimeType: "Vandalism",
riskLevel: "Medium",
confidence: "72%",
trend: "Stable",
},
{
area: "North Transit Hub",
crimeType: "Assault",
riskLevel: "Medium",
confidence: "68%",
trend: "Increasing",
},
]
return (
<div className="mt-4 space-y-3">
<div className="flex items-center justify-between">
<h4 className="text-sm font-medium">7-Day Forecast</h4>
<Badge variant="outline" className="bg-blue-100 text-blue-800">
AI-Powered
</Badge>
</div>
{predictions.map((prediction, index) => (
<div key={index} className="border rounded-lg p-3">
<div className="flex items-center gap-2">
<AlertTriangle
className={`h-4 w-4 ${prediction.riskLevel === "High" ? "text-red-500" : "text-yellow-500"}`}
/>
<h4 className="font-medium text-sm">{prediction.area}</h4>
<Badge
variant="outline"
className={
prediction.riskLevel === "High"
? "bg-red-100 text-red-800 ml-auto"
: "bg-yellow-100 text-yellow-800 ml-auto"
}
>
{prediction.riskLevel} Risk
</Badge>
</div>
<div className="mt-2 text-xs">
<div className="flex justify-between">
<span>Predicted Crime:</span>
<span className="font-medium">{prediction.crimeType}</span>
</div>
<div className="flex justify-between mt-1">
<span>Confidence:</span>
<span className="font-medium">{prediction.confidence}</span>
</div>
<div className="flex justify-between mt-1">
<span>Trend:</span>
<span
className={`font-medium flex items-center ${prediction.trend === "Increasing" ? "text-red-600" : "text-blue-600"}`}
>
{prediction.trend}
{prediction.trend === "Increasing" && <TrendingUp className="h-3 w-3 ml-1" />}
</span>
</div>
</div>
</div>
))}
<div className="text-xs text-center text-muted-foreground mt-2">
Predictions based on historical data, weather patterns, and local events
</div>
</div>
)
}

View File

@ -0,0 +1,103 @@
"use client"
import { Progress } from "@/app/_components/ui/progress"
import { Clock, TrendingDown } from "lucide-react"
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/app/_components/ui/chart"
import { Bar, BarChart, XAxis, YAxis, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts"
export default function ResponseTimeMetrics() {
// Sample data for the chart
const data = [
{ name: "Violent", time: 4.2 },
{ name: "Theft", time: 8.5 },
{ name: "Domestic", time: 5.1 },
{ name: "Traffic", time: 9.8 },
{ name: "Noise", time: 12.3 },
]
return (
<div className="mt-4 space-y-4">
<div className="flex items-center justify-between">
<div>
<div className="text-2xl font-bold">4.2m</div>
<div className="text-xs text-muted-foreground">Average Response Time</div>
</div>
<div className="text-xs text-green-600 flex items-center">
<TrendingDown className="h-3 w-3 mr-1" />
-0.3m from last month
</div>
</div>
<div className="space-y-2">
<div>
<div className="flex justify-between text-xs mb-1">
<span>Priority 1 (Emergency)</span>
<div className="flex items-center">
<Clock className="h-3 w-3 mr-1" />
<span>3.2 min avg</span>
</div>
</div>
<Progress value={80} className="h-2" indicatorClassName="bg-red-500" />
</div>
<div>
<div className="flex justify-between text-xs mb-1">
<span>Priority 2 (Urgent)</span>
<div className="flex items-center">
<Clock className="h-3 w-3 mr-1" />
<span>5.8 min avg</span>
</div>
</div>
<Progress value={70} className="h-2" indicatorClassName="bg-orange-500" />
</div>
<div>
<div className="flex justify-between text-xs mb-1">
<span>Priority 3 (Standard)</span>
<div className="flex items-center">
<Clock className="h-3 w-3 mr-1" />
<span>12.5 min avg</span>
</div>
</div>
<Progress value={60} className="h-2" indicatorClassName="bg-blue-500" />
</div>
</div>
<div className="h-[120px]">
<ChartContainer>
<ResponsiveContainer width="100%" height="100%">
<BarChart data={data} margin={{ top: 5, right: 5, left: 0, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis dataKey="name" className="text-xs" />
<YAxis className="text-xs" />
<Tooltip content={<CustomTooltip />} />
<Bar dataKey="time" fill="#3b82f6" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</ChartContainer>
</div>
<div className="text-xs text-center text-muted-foreground">Response time by incident type (minutes)</div>
</div>
)
}
function CustomTooltip({ active, payload, label }: any) {
if (active && payload && payload.length) {
return (
<ChartTooltip content={
<ChartTooltipContent>
<div className="font-medium">{label} Incidents</div>
<div className="flex items-center gap-2 text-sm">
<span>Avg. Response Time:</span>
<span className="font-medium">{payload[0].value} minutes</span>
</div>
</ChartTooltipContent>
}>
</ChartTooltip>
)
}
return null
}

View File

@ -0,0 +1,80 @@
"use client"
import { Badge } from "@/app/_components/ui/badge"
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/app/_components/ui/chart"
import { BarChart, Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from "recharts"
export default function TimeOfDayAnalysis() {
// Sample data for the chart
const data = [
{ name: "Morning (6AM-12PM)", value: 15, color: "#3b82f6" },
{ name: "Afternoon (12PM-6PM)", value: 25, color: "#f59e0b" },
{ name: "Evening (6PM-12AM)", value: 40, color: "#8b5cf6" },
{ name: "Night (12AM-6AM)", value: 20, color: "#1e293b" },
]
const highRiskTimes = [
{ day: "Friday", time: "10PM - 2AM", risk: "High" },
{ day: "Saturday", time: "11PM - 3AM", risk: "High" },
{ day: "Sunday", time: "12AM - 4AM", risk: "Medium" },
]
return (
<div className="mt-4 space-y-4">
<ChartContainer className="h-[150px]">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie data={data} cx="50%" cy="50%" innerRadius={30} outerRadius={50} paddingAngle={2} dataKey="value">
{data.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip content={<CustomTooltip />} />
</PieChart>
</ResponsiveContainer>
</ChartContainer>
<div className="text-xs text-center">
<div className="font-medium">Crime Distribution by Time of Day</div>
<div className="text-muted-foreground mt-1">Evening hours show highest incident rates</div>
</div>
<div className="border rounded-lg p-3">
<h4 className="text-sm font-medium mb-2">High-Risk Time Periods</h4>
<div className="space-y-2">
{highRiskTimes.map((item, index) => (
<div key={index} className="flex justify-between items-center">
<div className="text-xs">
<span className="font-medium">{item.day}:</span> {item.time}
</div>
<Badge
variant="outline"
className={item.risk === "High" ? "bg-red-100 text-red-800" : "bg-yellow-100 text-yellow-800"}
>
{item.risk} Risk
</Badge>
</div>
))}
</div>
</div>
</div>
)
}
function CustomTooltip({ active, payload }: any) {
if (active && payload && payload.length) {
return (
<ChartTooltip content={
<ChartTooltipContent>
<div className="font-medium">{payload[0].name}</div>
<div className="flex items-center gap-2 text-sm">
<span>{payload[0].value}% of incidents</span>
</div>
</ChartTooltipContent>
}>
</ChartTooltip>
)
}
return null
}

View File

@ -0,0 +1,101 @@
"use client"
import { BentoGrid, BentoGridItem } from "@/app/_components/ui/bento-grid"
import { BarChart3, TrendingUp, Map, Calendar, Clock, FileText, AlertTriangle, Filter } from "lucide-react"
import AnalyticsHeader from "./_components/analytics-header"
import CrimeTrends from "./_components/crime-trends"
import GeographicalAnalysis from "./_components/geographical-analysis"
import ResponseTimeMetrics from "./_components/response-time-metrics"
import CaseResolutionRates from "./_components/case-resolution-rates"
import IncidentTypeBreakdown from "./_components/incident-type-breakdown"
import OfficerPerformanceMetrics from "./_components/officer-performance-metrics"
import TimeOfDayAnalysis from "./_components/time-of-day-analysis"
import CustomReportBuilder from "./_components/custom-report-builder"
import PredictiveAnalytics from "./_components/predictive-analytics"
export default function AnalyticsReportingPage() {
return (
<div className="container py-4 min-h-screen">
<div className="max-w-7xl mx-auto">
<AnalyticsHeader />
<BentoGrid>
<BentoGridItem
title="Crime Trends"
description="Monthly and yearly crime statistics"
icon={<TrendingUp className="w-5 h-5" />}
colSpan="2"
>
<CrimeTrends />
</BentoGridItem>
<BentoGridItem
title="Geographical Analysis"
description="Crime hotspots and patterns"
icon={<Map className="w-5 h-5" />}
rowSpan="2"
>
<GeographicalAnalysis />
</BentoGridItem>
<BentoGridItem
title="Response Time Metrics"
description="Emergency response efficiency"
icon={<Clock className="w-5 h-5" />}
>
<ResponseTimeMetrics />
</BentoGridItem>
<BentoGridItem
title="Case Resolution Rates"
description="Clearance rates by crime type"
icon={<FileText className="w-5 h-5" />}
>
<CaseResolutionRates />
</BentoGridItem>
<BentoGridItem
title="Incident Type Breakdown"
description="Distribution of crime categories"
icon={<BarChart3 className="w-5 h-5" />}
colSpan="2"
>
<IncidentTypeBreakdown />
</BentoGridItem>
<BentoGridItem
title="Officer Performance Metrics"
description="Productivity and efficiency analysis"
icon={<BarChart3 className="w-5 h-5" />}
>
<OfficerPerformanceMetrics />
</BentoGridItem>
<BentoGridItem
title="Time of Day Analysis"
description="Crime patterns by hour and day"
icon={<Calendar className="w-5 h-5" />}
>
<TimeOfDayAnalysis />
</BentoGridItem>
<BentoGridItem
title="Predictive Analytics"
description="Crime forecasting and risk assessment"
icon={<AlertTriangle className="w-5 h-5" />}
>
<PredictiveAnalytics />
</BentoGridItem>
<BentoGridItem
title="Custom Report Builder"
description="Generate tailored reports"
icon={<Filter className="w-5 h-5" />}
>
<CustomReportBuilder />
</BentoGridItem>
</BentoGrid>
</div>
</div>
)
}

View File

@ -0,0 +1,68 @@
import { Badge } from "@/app/_components/ui/badge"
import { Calendar, MapPin } from "lucide-react"
export default function CommunityEvents() {
const events = [
{
title: "Community Safety Workshop",
date: "Apr 28, 2023",
time: "6:00 PM - 8:00 PM",
location: "Community Center",
type: "Workshop",
},
{
title: "Coffee with a Cop",
date: "May 5, 2023",
time: "9:00 AM - 11:00 AM",
location: "Downtown Café",
type: "Meet & Greet",
},
{
title: "Neighborhood Watch Meeting",
date: "May 12, 2023",
time: "7:00 PM - 8:30 PM",
location: "Public Library",
type: "Meeting",
},
]
return (
<div className="mt-4 space-y-3">
{events.map((event, index) => (
<div key={index} className="border rounded-lg p-3">
<div className="flex justify-between items-start">
<h4 className="font-medium text-sm">{event.title}</h4>
<Badge variant="outline" className="bg-blue-100 text-blue-800">
{event.type}
</Badge>
</div>
<div className="mt-2 space-y-1 text-xs text-muted-foreground">
<div className="flex items-center">
<Calendar className="h-3 w-3 mr-1" />
{event.date}, {event.time}
</div>
<div className="flex items-center">
<MapPin className="h-3 w-3 mr-1" />
{event.location}
</div>
</div>
<div className="mt-2 flex justify-between items-center">
<div className="flex -space-x-2">
{[1, 2, 3].map((i) => (
<div key={i} className="w-5 h-5 rounded-full border border-background bg-muted"></div>
))}
<div className="w-5 h-5 rounded-full border border-background bg-muted flex items-center justify-center text-[10px]">
+8
</div>
</div>
<button className="text-xs text-primary">RSVP</button>
</div>
</div>
))}
<button className="w-full text-sm text-primary mt-2 py-1">View All Events</button>
</div>
)
}

View File

@ -0,0 +1,50 @@
import { Button } from "@/app/_components/ui/button"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/app/_components/ui/select"
import { MessageSquare } from "lucide-react"
export default function CommunityFeedback() {
return (
<div className="mt-4 space-y-4">
<div className="text-xs text-muted-foreground">
We value your input! Share your thoughts, concerns, or suggestions to help us better serve the community.
</div>
<div className="space-y-3">
<div>
<label className="text-xs text-muted-foreground mb-1 block">Feedback Type</label>
<Select>
<SelectTrigger>
<SelectValue placeholder="Select feedback type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="suggestion">Suggestion</SelectItem>
<SelectItem value="concern">Concern</SelectItem>
<SelectItem value="compliment">Compliment</SelectItem>
<SelectItem value="question">Question</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label className="text-xs text-muted-foreground mb-1 block">Your Message</label>
<textarea
placeholder="Share your feedback..."
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px] ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
></textarea>
</div>
<div className="flex items-center gap-2">
<input type="checkbox" id="contact-me" className="rounded border-input" />
<label htmlFor="contact-me" className="text-xs">
I would like to be contacted about my feedback
</label>
</div>
<Button className="w-full flex items-center justify-center">
<MessageSquare className="h-4 w-4 mr-2" />
Submit Feedback
</Button>
</div>
</div>
)
}

View File

@ -0,0 +1,27 @@
import { Button } from "@/app/_components/ui/button"
import { Search, Phone } from "lucide-react"
export default function CommunityHeader() {
return (
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-8">
<div>
<h2 className="text-3xl font-bold tracking-tight">Community Engagement Portal</h2>
<p className="text-muted-foreground">Stay informed, connected, and engaged with your local law enforcement</p>
</div>
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-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 resources..."
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>
<Button className="flex items-center">
<Phone className="h-4 w-4 mr-2" />
Emergency: 911
</Button>
</div>
</div>
)
}

View File

@ -0,0 +1,72 @@
import { Badge } from "@/app/_components/ui/badge"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/app/_components/ui/select"
export default function CommunityMap() {
return (
<div className="mt-4 h-full">
<div className="flex justify-between items-center mb-4">
<Select defaultValue="all">
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Map View" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Incidents</SelectItem>
<SelectItem value="theft">Theft</SelectItem>
<SelectItem value="vandalism">Vandalism</SelectItem>
<SelectItem value="traffic">Traffic</SelectItem>
<SelectItem value="resources">Community Resources</SelectItem>
</SelectContent>
</Select>
<div className="flex gap-2">
<Badge variant="outline" className="bg-blue-100 text-blue-800">
Last 7 Days
</Badge>
</div>
</div>
<div className="h-[350px] rounded-md bg-slate-100 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 className="absolute top-2 right-2 bg-background/80 backdrop-blur-sm p-2 rounded-md text-xs font-medium">
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-red-500"></span> Theft
</div>
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-yellow-500"></span> Vandalism
</div>
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-blue-500"></span> Traffic
</div>
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-green-500"></span> Resources
</div>
</div>
<div className="z-10 bg-background/80 backdrop-blur-sm p-3 rounded-lg">
<span className="font-medium">Community Safety Map</span>
<div className="text-xs text-muted-foreground mt-1">Showing incidents from Apr 17-24, 2023</div>
</div>
</div>
<div className="mt-4 border rounded-lg p-3">
<h4 className="text-sm font-medium mb-2">Your Neighborhood</h4>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-xs">Downtown District</span>
<Badge variant="outline" className="bg-yellow-100 text-yellow-800">
Medium Risk
</Badge>
</div>
<div className="text-xs text-muted-foreground">Recent incidents: 5 thefts, 2 vandalism reports</div>
<div className="text-xs text-muted-foreground">
Trend: <span className="text-red-600">+12% from last month</span>
</div>
<div className="mt-2">
<button className="text-xs text-primary">View Safety Recommendations</button>
</div>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,117 @@
import { Badge } from "@/app/_components/ui/badge"
import { Button } from "@/app/_components/ui/button"
import { AlertTriangle, MapPin, Clock, Bell } from "lucide-react"
export default function CrimeAlerts() {
const alerts = [
{
id: "ALT-1234",
type: "Theft",
description:
"Multiple vehicle break-ins reported in the Downtown area. Please secure valuables and lock vehicles.",
location: "Downtown District",
date: "Apr 23, 2023",
time: "Posted 2 hours ago",
severity: "Medium",
},
{
id: "ALT-1235",
type: "Suspicious Activity",
description:
"Residents report unknown individuals going door-to-door claiming to represent utility companies. Always ask for proper identification.",
location: "North Residential Area",
date: "Apr 22, 2023",
time: "Posted 1 day ago",
severity: "Low",
},
{
id: "ALT-1236",
type: "Scam Alert",
description:
"Phone scammers impersonating police officers requesting payment for 'outstanding warrants'. Police will never request payment over the phone.",
location: "Citywide",
date: "Apr 21, 2023",
time: "Posted 2 days ago",
severity: "High",
},
]
return (
<div className="mt-4">
<div className="flex justify-between items-center mb-4">
<div className="flex gap-2">
<Button variant="outline" size="sm">
All Alerts
</Button>
<Button variant="outline" size="sm">
My Neighborhood
</Button>
</div>
<Button variant="outline" size="sm" className="flex items-center">
<Bell className="h-4 w-4 mr-2" />
Subscribe to Alerts
</Button>
</div>
<div className="space-y-4">
{alerts.map((alert) => (
<div
key={alert.id}
className="flex items-start gap-3 p-4 border rounded-lg hover:bg-muted/50 transition-colors"
>
<div
className={`w-10 h-10 rounded-full flex items-center justify-center shrink-0 ${
alert.severity === "High" ? "bg-red-100" : alert.severity === "Medium" ? "bg-yellow-100" : "bg-blue-100"
}`}
>
<AlertTriangle
className={`h-5 w-5 ${
alert.severity === "High"
? "text-red-600"
: alert.severity === "Medium"
? "text-yellow-600"
: "text-blue-600"
}`}
/>
</div>
<div className="flex-1 min-w-0">
<div className="flex flex-wrap items-center gap-2">
<h4 className="font-medium">{alert.type}</h4>
<Badge
variant="outline"
className={
alert.severity === "High"
? "bg-red-100 text-red-800"
: alert.severity === "Medium"
? "bg-yellow-100 text-yellow-800"
: "bg-blue-100 text-blue-800"
}
>
{alert.severity} Priority
</Badge>
</div>
<p className="text-sm mt-2">{alert.description}</p>
<div className="flex flex-wrap gap-x-4 gap-y-1 mt-3 text-xs text-muted-foreground">
<div className="flex items-center">
<MapPin className="h-3 w-3 mr-1" />
{alert.location}
</div>
<div className="flex items-center">
<Clock className="h-3 w-3 mr-1" />
{alert.time}
</div>
</div>
</div>
<Button size="sm" variant="outline">
Details
</Button>
</div>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,155 @@
"use client"
import { Tabs, TabsList, TabsTrigger } from "@/app/_components/ui/tabs"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
ChartConfig,
} from "@/app/_components/ui/chart"
import { Bar, BarChart, Line, LineChart, XAxis, YAxis, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts"
const chartConfig = {
desktop: {
label: "Desktop",
color: "#2563eb",
},
mobile: {
label: "Mobile",
color: "#60a5fa",
},
} satisfies ChartConfig
export default function CrimeStatistics() {
// Sample data for the chart
const monthlyData = [
{ month: "Jan", violent: 12, property: 45, other: 20 },
{ month: "Feb", violent: 10, property: 42, other: 18 },
{ month: "Mar", violent: 15, property: 48, other: 22 },
{ month: "Apr", violent: 8, property: 40, other: 15 },
{ month: "May", violent: 12, property: 38, other: 20 },
{ month: "Jun", violent: 10, property: 35, other: 18 },
]
const neighborhoodData = [
{ name: "Downtown", incidents: 45 },
{ name: "Westside", incidents: 32 },
{ name: "North", incidents: 28 },
{ name: "East", incidents: 22 },
{ name: "South", incidents: 18 },
]
return (
<div className="mt-4">
<div className="flex justify-between items-center mb-4">
<Tabs defaultValue="trends">
<TabsList>
<TabsTrigger value="trends">Crime Trends</TabsTrigger>
<TabsTrigger value="neighborhood">By Neighborhood</TabsTrigger>
</TabsList>
</Tabs>
<Tabs defaultValue="6m">
<TabsList>
<TabsTrigger value="3m">3 Months</TabsTrigger>
<TabsTrigger value="6m">6 Months</TabsTrigger>
<TabsTrigger value="1y">1 Year</TabsTrigger>
</TabsList>
</Tabs>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<ChartContainer config={chartConfig} className="h-[250px]">
<>
<ResponsiveContainer width="100%" height="100%">
<LineChart data={monthlyData} margin={{ top: 5, right: 10, left: 0, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis dataKey="month" className="text-xs" />
<YAxis className="text-xs" />
<Tooltip content={<CustomLineTooltip />} />
<Line type="monotone" dataKey="violent" stroke="#ef4444" strokeWidth={2} dot={{ r: 4 }} />
<Line type="monotone" dataKey="property" stroke="#3b82f6" strokeWidth={2} dot={{ r: 4 }} />
<Line type="monotone" dataKey="other" stroke="#10b981" strokeWidth={2} dot={{ r: 4 }} />
</LineChart>
</ResponsiveContainer>
<ChartLegend className="justify-center mt-2">
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full" style={{ backgroundColor: "#ef4444" }}></div>
<span>Violent Crime</span>
</div>
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full" style={{ backgroundColor: "#3b82f6" }}></div>
<span>Property Crime</span>
</div>
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full" style={{ backgroundColor: "#10b981" }}></div>
<span>Other</span>
</div>
</ChartLegend>
</>
</ChartContainer>
<ChartContainer className="h-[250px]">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={neighborhoodData} margin={{ top: 5, right: 10, left: 0, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis dataKey="name" className="text-xs" />
<YAxis className="text-xs" />
<Tooltip content={<CustomBarTooltip />} />
<Bar dataKey="incidents" fill="#3b82f6" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</ChartContainer>
</div>
<div className="mt-4 text-xs text-center text-muted-foreground">
Data source: City Police Department. Last updated: April 24, 2023
</div>
</div>
)
}
function CustomLineTooltip({ active, payload, label }: any) {
if (active && payload && payload.length) {
return (
<ChartTooltip content={
<ChartTooltipContent>
<div className="font-medium">{label}</div>
{payload.map((entry: any, index: number) => (
<div key={`item-${index}`} className="flex items-center gap-2 text-sm">
<div className="h-2 w-2 rounded-full" style={{ backgroundColor: entry.color }}></div>
<span className="font-medium">{entry.name}:</span>
<span>{entry.value} incidents</span>
</div>
))}
</ChartTooltipContent>
}>
</ChartTooltip>
)
}
return null
}
function CustomBarTooltip({ active, payload, label }: any) {
if (active && payload && payload.length) {
return (
<ChartTooltip content={
<ChartTooltipContent>
<div className="font-medium">{label} Neighborhood</div>
<div className="flex items-center gap-2 text-sm">
<span>Total Incidents:</span>
<span className="font-medium">{payload[0].value}</span>
</div>
</ChartTooltipContent>
}>
</ChartTooltip>
)
}
return null
}

View File

@ -0,0 +1,54 @@
"use client"
import { useState } from "react"
import { ChevronDown } from "lucide-react"
export default function FAQSection() {
const faqs = [
{
question: "How do I report a non-emergency incident?",
answer:
"You can use the 'Report an Incident' form on this portal, call the non-emergency number at 555-123-4567, or visit your local police station in person.",
},
{
question: "How can I join a Neighborhood Watch group?",
answer:
"Check the 'Neighborhood Watch' section for groups in your area. You can contact the coordinator directly or click 'Start or Join a Group' for more information.",
},
{
question: "What should I do if I witness a crime?",
answer:
"If it's an emergency or crime in progress, call 911 immediately. For non-emergencies, use the non-emergency line or the reporting form on this portal.",
},
{
question: "How can I request extra patrols in my neighborhood?",
answer:
"Contact your local precinct or submit a request through the 'Community Feedback' section of this portal.",
},
]
const [openIndex, setOpenIndex] = useState<number | null>(null)
const toggleFAQ = (index: number) => {
setOpenIndex(openIndex === index ? null : index)
}
return (
<div className="mt-4 space-y-2">
{faqs.map((faq, index) => (
<div key={index} className="border rounded-lg overflow-hidden">
<button
className="flex justify-between items-center w-full p-3 text-left text-sm font-medium"
onClick={() => toggleFAQ(index)}
>
{faq.question}
<ChevronDown className={`h-4 w-4 transition-transform ${openIndex === index ? "rotate-180" : ""}`} />
</button>
{openIndex === index && <div className="px-3 pb-3 text-xs text-muted-foreground">{faq.answer}</div>}
</div>
))}
<button className="w-full text-sm text-primary mt-2 py-1">View All FAQs</button>
</div>
)
}

View File

@ -0,0 +1,62 @@
import { Badge } from "@/app/_components/ui/badge"
import { Users, MapPin, Phone } from "lucide-react"
export default function NeighborhoodWatch() {
const groups = [
{
name: "Downtown Watch Group",
coordinator: "Sarah Johnson",
members: 24,
area: "Downtown District",
contact: "555-123-4567",
},
{
name: "Westside Neighbors",
coordinator: "Michael Chen",
members: 18,
area: "West Residential Area",
contact: "555-234-5678",
},
{
name: "Parkview Safety",
coordinator: "Emily Parker",
members: 15,
area: "Park District",
contact: "555-345-6789",
},
]
return (
<div className="mt-4 space-y-3">
{groups.map((group, index) => (
<div key={index} className="border rounded-lg p-3">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-full bg-muted flex items-center justify-center shrink-0">
<Users className="h-4 w-4" />
</div>
<div>
<h4 className="font-medium text-sm">{group.name}</h4>
<div className="text-xs text-muted-foreground">Coordinator: {group.coordinator}</div>
</div>
<Badge variant="outline" className="ml-auto">
{group.members} members
</Badge>
</div>
<div className="mt-2 space-y-1 text-xs text-muted-foreground pl-10">
<div className="flex items-center">
<MapPin className="h-3 w-3 mr-1" />
{group.area}
</div>
<div className="flex items-center">
<Phone className="h-3 w-3 mr-1" />
{group.contact}
</div>
</div>
</div>
))}
<button className="w-full text-sm text-primary mt-2 py-1">Start or Join a Group</button>
</div>
)
}

View File

@ -0,0 +1,62 @@
import { Button } from "@/app/_components/ui/button"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/app/_components/ui/select"
import { FileText, MapPin, Camera } from "lucide-react"
export default function ReportIncident() {
return (
<div className="mt-4 space-y-4">
<div className="text-xs text-muted-foreground">
Use this form to report non-emergency incidents. For emergencies, please call 911.
</div>
<div className="space-y-3">
<div>
<label className="text-xs text-muted-foreground mb-1 block">Incident Type</label>
<Select>
<SelectTrigger>
<SelectValue placeholder="Select incident type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="theft">Theft/Burglary</SelectItem>
<SelectItem value="vandalism">Vandalism</SelectItem>
<SelectItem value="suspicious">Suspicious Activity</SelectItem>
<SelectItem value="noise">Noise Complaint</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label className="text-xs text-muted-foreground mb-1 block">Location</label>
<div className="relative">
<MapPin className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<input
type="text"
placeholder="Enter address or location"
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>
<div>
<label className="text-xs text-muted-foreground mb-1 block">Description</label>
<textarea
placeholder="Describe what happened..."
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px] ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
></textarea>
</div>
<div className="flex gap-2">
<Button variant="outline" className="flex-1 flex items-center justify-center">
<Camera className="h-4 w-4 mr-2" />
Add Photo
</Button>
<Button className="flex-1 flex items-center justify-center">
<FileText className="h-4 w-4 mr-2" />
Submit Report
</Button>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,58 @@
import { Phone, Building, Heart, Shield } from "lucide-react"
export default function ResourceDirectory() {
const resources = [
{
name: "Police Non-Emergency",
category: "Law Enforcement",
contact: "555-123-4567",
icon: Shield,
},
{
name: "Victim Services",
category: "Support",
contact: "555-234-5678",
icon: Heart,
},
{
name: "City Hall",
category: "Government",
contact: "555-345-6789",
icon: Building,
},
{
name: "Mental Health Hotline",
category: "Support",
contact: "555-456-7890",
icon: Heart,
},
]
return (
<div className="mt-4 space-y-3">
{resources.map((resource, index) => (
<div key={index} className="border rounded-lg p-3">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-full bg-muted flex items-center justify-center shrink-0">
<resource.icon className="h-4 w-4" />
</div>
<div>
<h4 className="font-medium text-sm">{resource.name}</h4>
<div className="text-xs text-muted-foreground">{resource.category}</div>
</div>
</div>
<div className="mt-2 flex justify-between items-center">
<div className="flex items-center text-xs">
<Phone className="h-3 w-3 mr-1" />
{resource.contact}
</div>
<button className="text-xs text-primary">Call</button>
</div>
</div>
))}
<button className="w-full text-sm text-primary mt-2 py-1">View Full Directory</button>
</div>
)
}

View File

@ -0,0 +1,46 @@
import { Home, Car, Smartphone } from "lucide-react"
export default function SafetyTips() {
const categories = [
{
name: "Home Security",
icon: Home,
tips: ["Lock all doors and windows when away", "Install motion-sensor lighting", "Consider a security system"],
},
{
name: "Vehicle Protection",
icon: Car,
tips: ["Always lock your vehicle", "Don't leave valuables visible", "Park in well-lit areas"],
},
{
name: "Digital Safety",
icon: Smartphone,
tips: ["Use strong, unique passwords", "Be cautious of suspicious emails", "Keep software updated"],
},
]
return (
<div className="mt-4 space-y-3">
{categories.map((category) => (
<div key={category.name} className="border rounded-lg p-3">
<div className="flex items-center gap-2 mb-2">
<div className="w-8 h-8 rounded-full bg-muted flex items-center justify-center shrink-0">
<category.icon className="h-4 w-4" />
</div>
<h4 className="font-medium text-sm">{category.name}</h4>
</div>
<ul className="space-y-1 pl-10">
{category.tips.map((tip, index) => (
<li key={index} className="text-xs list-disc">
{tip}
</li>
))}
</ul>
</div>
))}
<button className="w-full text-sm text-primary mt-2 py-1">View All Safety Tips</button>
</div>
)
}

View File

@ -0,0 +1,110 @@
"use client"
import { BentoGrid, BentoGridItem } from "@/app/_components/ui/bento-grid"
import { Bell, Calendar, FileText, MapPin, MessageSquare, Phone, Users, HelpCircle, BarChart3 } from "lucide-react"
import CommunityHeader from "./_components/community-header"
import CrimeAlerts from "./_components/crime-alerts"
import SafetyTips from "./_components/safety-tips"
import CommunityEvents from "./_components/community-events"
import NeighborhoodWatch from "./_components/neighborhood-watch"
import ReportIncident from "./_components/report-incident"
import CrimeStatistics from "./_components/crime-statistics"
import ResourceDirectory from "./_components/resource-directory"
import FAQSection from "./_components/faq-section"
import CommunityFeedback from "./_components/community-feedback"
import CommunityMap from "./_components/community-map"
export default function CommunityEngagementPage() {
return (
<div className="container py-4 min-h-screen">
<div className="max-w-7xl mx-auto">
<CommunityHeader />
<BentoGrid>
<BentoGridItem
title="Crime Alerts"
description="Recent incidents in your area"
icon={<Bell className="w-5 h-5" />}
colSpan="2"
>
<CrimeAlerts />
</BentoGridItem>
<BentoGridItem
title="Community Map"
description="Explore your neighborhood"
icon={<MapPin className="w-5 h-5" />}
rowSpan="2"
>
<CommunityMap />
</BentoGridItem>
<BentoGridItem
title="Safety Tips"
description="Protect yourself and your property"
icon={<HelpCircle className="w-5 h-5" />}
>
<SafetyTips />
</BentoGridItem>
<BentoGridItem
title="Community Events"
description="Upcoming meetings and activities"
icon={<Calendar className="w-5 h-5" />}
>
<CommunityEvents />
</BentoGridItem>
<BentoGridItem
title="Neighborhood Watch"
description="Local groups and coordinators"
icon={<Users className="w-5 h-5" />}
>
<NeighborhoodWatch />
</BentoGridItem>
<BentoGridItem
title="Report an Incident"
description="Submit non-emergency reports"
icon={<FileText className="w-5 h-5" />}
>
<ReportIncident />
</BentoGridItem>
<BentoGridItem
title="Crime Statistics"
description="Data and trends for your area"
icon={<BarChart3 className="w-5 h-5" />}
colSpan="2"
>
<CrimeStatistics />
</BentoGridItem>
<BentoGridItem
title="Resource Directory"
description="Community services and contacts"
icon={<Phone className="w-5 h-5" />}
>
<ResourceDirectory />
</BentoGridItem>
<BentoGridItem
title="FAQ"
description="Common questions and answers"
icon={<HelpCircle className="w-5 h-5" />}
>
<FAQSection />
</BentoGridItem>
<BentoGridItem
title="Community Feedback"
description="Share your thoughts and suggestions"
icon={<MessageSquare className="w-5 h-5" />}
>
<CommunityFeedback />
</BentoGridItem>
</BentoGrid>
</div>
</div>
)
}

View File

@ -0,0 +1,69 @@
import { User } from "lucide-react"
import { Badge } from "@/app/_components/ui/badge"
export default function CaseAssignees() {
const assignees = [
{
id: "OFF-1234",
name: "Michael Chen",
role: "Lead Detective",
badge: "ID-5678",
contact: "555-123-4567",
status: "Active",
},
{
id: "OFF-2345",
name: "Sarah Johnson",
role: "Forensic Specialist",
badge: "ID-6789",
contact: "555-234-5678",
status: "Active",
},
{
id: "OFF-3456",
name: "Robert Wilson",
role: "Evidence Technician",
badge: "ID-7890",
contact: "555-345-6789",
status: "Active",
},
{
id: "OFF-4567",
name: "James Rodriguez",
role: "Patrol Officer",
badge: "ID-8901",
contact: "555-456-7890",
status: "Standby",
},
]
return (
<div className="space-y-3">
{assignees.map((officer) => (
<div key={officer.id} className="flex items-center gap-3 p-2 rounded-lg hover:bg-muted/50 transition-colors">
<div className="w-8 h-8 rounded-full bg-muted flex items-center justify-center shrink-0">
<User className="h-4 w-4" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h4 className="font-medium text-sm">{officer.name}</h4>
<Badge
variant="outline"
className={officer.status === "Active" ? "bg-green-100 text-green-800" : "bg-blue-100 text-blue-800"}
>
{officer.status}
</Badge>
</div>
<div className="text-xs text-muted-foreground">
{officer.role} Badge #{officer.badge}
</div>
</div>
</div>
))}
<button className="w-full text-sm text-primary mt-2 py-1">+ Assign Personnel</button>
</div>
)
}

View File

@ -0,0 +1,92 @@
import { Download, FileText, FilePlus, FileImage, FileSpreadsheet } from "lucide-react"
import { Badge } from "@/app/_components/ui/badge"
export default function CaseDocuments() {
const documents = [
{
id: "DOC-3421",
name: "Initial Police Report",
type: "PDF",
size: "1.2 MB",
uploadedBy: "James Rodriguez",
uploadDate: "Apr 15, 2023",
icon: FileText,
},
{
id: "DOC-3422",
name: "Autopsy Report",
type: "PDF",
size: "3.5 MB",
uploadedBy: "Dr. Lisa Wong",
uploadDate: "Apr 17, 2023",
icon: FileText,
},
{
id: "DOC-3423",
name: "Crime Scene Photos",
type: "ZIP",
size: "24.7 MB",
uploadedBy: "Robert Wilson",
uploadDate: "Apr 15, 2023",
icon: FileImage,
},
{
id: "DOC-3424",
name: "Witness Statement - Emily Parker",
type: "DOCX",
size: "285 KB",
uploadedBy: "Michael Chen",
uploadDate: "Apr 20, 2023",
icon: FilePlus,
},
{
id: "DOC-3425",
name: "Evidence Log",
type: "XLSX",
size: "420 KB",
uploadedBy: "Sarah Johnson",
uploadDate: "Apr 22, 2023",
icon: FileSpreadsheet,
},
]
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h3 className="text-lg font-semibold">Case Documents ({documents.length})</h3>
<button className="text-sm text-primary">Upload Document</button>
</div>
<div className="space-y-2">
{documents.map((doc) => (
<div
key={doc.id}
className="flex items-center gap-3 p-3 border rounded-lg hover:bg-muted/50 transition-colors"
>
<div className="w-10 h-10 rounded bg-muted flex items-center justify-center shrink-0">
<doc.icon className="h-5 w-5" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h4 className="font-medium truncate">{doc.name}</h4>
<Badge variant="outline">{doc.type}</Badge>
</div>
<div className="flex gap-4 mt-1 text-xs text-muted-foreground">
<span>{doc.size}</span>
<span>Uploaded: {doc.uploadDate}</span>
<span>By: {doc.uploadedBy}</span>
</div>
</div>
<button className="flex items-center text-sm text-primary shrink-0">
<Download className="h-4 w-4 mr-1" />
Download
</button>
</div>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,93 @@
import { FileText, ImageIcon, Package } from "lucide-react"
import { Badge } from "@/app/_components/ui/badge"
export default function CaseEvidence() {
const evidenceItems = [
{
id: "EV-4523",
type: "Weapon",
description: "Kitchen knife, 8-inch blade with wooden handle",
dateCollected: "Apr 18, 2023",
status: "Processing",
location: "Evidence Locker B-12",
icon: Package,
},
{
id: "EV-4524",
type: "Photograph",
description: "Crime scene photos - living room area",
dateCollected: "Apr 15, 2023",
status: "Analyzed",
location: "Digital Storage",
icon: ImageIcon,
},
{
id: "EV-4525",
type: "Document",
description: "Victim's personal diary",
dateCollected: "Apr 15, 2023",
status: "Analyzed",
location: "Evidence Locker A-7",
icon: FileText,
},
{
id: "EV-4526",
type: "DNA Sample",
description: "Blood sample from kitchen floor",
dateCollected: "Apr 15, 2023",
status: "Lab Analysis",
location: "Forensic Lab",
icon: Package,
},
]
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h3 className="text-lg font-semibold">Evidence Items ({evidenceItems.length})</h3>
<button className="text-sm text-primary">Add Evidence</button>
</div>
<div className="space-y-3">
{evidenceItems.map((item) => (
<div
key={item.id}
className="flex items-start gap-3 p-3 border rounded-lg hover:bg-muted/50 transition-colors"
>
<div className="w-10 h-10 rounded-full bg-muted flex items-center justify-center shrink-0">
<item.icon className="h-5 w-5" />
</div>
<div className="flex-1 min-w-0">
<div className="flex flex-wrap items-center gap-2">
<h4 className="font-medium">{item.id}</h4>
<Badge variant="outline">{item.type}</Badge>
<Badge
variant="outline"
className={
item.status === "Analyzed"
? "bg-green-100 text-green-800"
: item.status === "Processing"
? "bg-yellow-100 text-yellow-800"
: "bg-blue-100 text-blue-800"
}
>
{item.status}
</Badge>
</div>
<p className="text-sm mt-1">{item.description}</p>
<div className="flex flex-wrap gap-x-4 gap-y-1 mt-2 text-xs text-muted-foreground">
<span>Collected: {item.dateCollected}</span>
<span>Location: {item.location}</span>
</div>
</div>
<button className="text-sm text-primary shrink-0">View Details</button>
</div>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,47 @@
import { AlertTriangle, Calendar, Clock } from "lucide-react"
import { Badge } from "@/app/_components/ui/badge"
import { Button } from "@/app/_components/ui/button"
interface CaseHeaderProps {
caseId: string
title: string
status: string
priority: string
dateOpened: string
lastUpdated: string
}
export default function CaseHeader({ caseId, title, status, priority, dateOpened, lastUpdated }: CaseHeaderProps) {
return (
<div className="flex flex-col md:flex-row justify-between gap-4">
<div>
<div className="flex items-center gap-2">
<h1 className="text-2xl font-bold">Case #{caseId}</h1>
<Badge variant="outline" className="bg-blue-100 text-blue-800">
{status}
</Badge>
<Badge variant="outline" className="bg-red-100 text-red-800">
<AlertTriangle className="h-3 w-3 mr-1" />
{priority}
</Badge>
</div>
<h2 className="text-xl mt-1">{title}</h2>
<div className="flex items-center gap-4 mt-2 text-sm text-muted-foreground">
<div className="flex items-center">
<Calendar className="h-4 w-4 mr-1" />
Opened: {dateOpened}
</div>
<div className="flex items-center">
<Clock className="h-4 w-4 mr-1" />
Last updated: {lastUpdated}
</div>
</div>
</div>
<div className="flex items-start gap-2">
<Button>Update Status</Button>
<Button variant="outline">Export Case</Button>
</div>
</div>
)
}

View File

@ -0,0 +1,72 @@
import { User } from "lucide-react"
import { Button } from "@/app/_components/ui/button"
import { Textarea } from "@/app/_components/ui/textarea"
export default function CaseNotes() {
const notes = [
{
id: "N-5621",
content:
"Forensic analysis suggests the murder weapon was wiped clean before being discarded. No fingerprints were recovered, but DNA traces were found on the handle.",
author: "Sarah Johnson",
role: "Forensic Specialist",
timestamp: "Apr 22, 2023 • 15:10",
},
{
id: "N-5620",
content:
"Witness Emily Parker's statement corroborates the timeline established by the medical examiner. Victim was likely killed between 9:00 PM and 11:00 PM on April 14.",
author: "Michael Chen",
role: "Detective",
timestamp: "Apr 20, 2023 • 14:35",
},
{
id: "N-5619",
content:
"Background check on suspect John Doe shows prior assault charges that were dropped in 2021. Need to follow up with previous complainant.",
author: "Michael Chen",
role: "Detective",
timestamp: "Apr 19, 2023 • 10:22",
},
{
id: "N-5618",
content:
"Initial canvas of the neighborhood complete. Three potential witnesses identified and scheduled for interviews.",
author: "James Rodriguez",
role: "Patrol Officer",
timestamp: "Apr 16, 2023 • 08:45",
},
]
return (
<div className="space-y-6">
<div>
<h3 className="text-lg font-semibold mb-4">Case Notes</h3>
<div className="mb-6">
<Textarea placeholder="Add a note about this case..." className="min-h-[100px] mb-2" />
<div className="flex justify-end">
<Button>Add Note</Button>
</div>
</div>
<div className="space-y-4">
{notes.map((note) => (
<div key={note.id} className="p-3 border rounded-lg">
<div className="flex items-center gap-2 mb-2">
<div className="w-6 h-6 rounded-full bg-muted flex items-center justify-center shrink-0">
<User className="h-3 w-3" />
</div>
<span className="font-medium">{note.author}</span>
<span className="text-xs text-muted-foreground">({note.role})</span>
<span className="text-xs text-muted-foreground ml-auto">{note.timestamp}</span>
</div>
<p className="text-sm">{note.content}</p>
</div>
))}
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,59 @@
import { ArrowRight } from "lucide-react"
import { Badge } from "@/app/_components/ui/badge"
export default function CaseRelated() {
const relatedCases = [
{
id: "CR-7456",
title: "Assault - Downtown District",
date: "Mar 12, 2023",
status: "Closed",
relationship: "Same suspect",
},
{
id: "CR-7689",
title: "Breaking & Entering - Westside",
date: "Apr 02, 2023",
status: "Active",
relationship: "Similar MO",
},
{
id: "CR-7712",
title: "Theft - Downtown District",
date: "Apr 10, 2023",
status: "Active",
relationship: "Same location",
},
]
return (
<div className="space-y-3">
{relatedCases.map((case_) => (
<div key={case_.id} className="flex items-center gap-2 p-2 rounded-lg hover:bg-muted/50 transition-colors">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h4 className="font-medium text-sm">Case #{case_.id}</h4>
<Badge
variant="outline"
className={case_.status === "Active" ? "bg-blue-100 text-blue-800" : "bg-green-100 text-green-800"}
>
{case_.status}
</Badge>
</div>
<p className="text-xs truncate">{case_.title}</p>
<div className="flex justify-between text-xs text-muted-foreground mt-1">
<span>{case_.date}</span>
<span>{case_.relationship}</span>
</div>
</div>
<ArrowRight className="h-4 w-4 text-muted-foreground" />
</div>
))}
<button className="w-full text-sm text-primary mt-2 py-1">+ Link Related Case</button>
</div>
)
}

View File

@ -0,0 +1,78 @@
import { Circle } from "lucide-react"
export default function CaseTimeline() {
const timelineEvents = [
{
date: "Apr 22, 2023",
time: "14:30",
title: "Forensic Report Received",
description: "DNA analysis results from the lab confirm suspect's presence at the crime scene.",
user: "Sarah Johnson",
role: "Forensic Specialist",
},
{
date: "Apr 20, 2023",
time: "09:15",
title: "Witness Interview Conducted",
description: "Key witness provided detailed description of events and potential suspect.",
user: "Michael Chen",
role: "Detective",
},
{
date: "Apr 18, 2023",
time: "16:45",
title: "Evidence Collected",
description: "Weapon recovered from dumpster 2 blocks from crime scene. Sent for fingerprint analysis.",
user: "Robert Wilson",
role: "Evidence Technician",
},
{
date: "Apr 15, 2023",
time: "23:10",
title: "Case Opened",
description: "Officers responded to 911 call. Victim found deceased at the scene.",
user: "James Rodriguez",
role: "Patrol Officer",
},
]
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h3 className="text-lg font-semibold">Case Timeline</h3>
<button className="text-sm text-primary">Add Event</button>
</div>
<div className="relative">
{timelineEvents.map((event, index) => (
<div key={index} className="mb-8 relative pl-6">
{/* Timeline connector */}
{index < timelineEvents.length - 1 && (
<div className="absolute left-[0.4375rem] top-3 bottom-0 w-0.5 bg-muted" />
)}
{/* Timeline dot */}
<div className="absolute left-0 top-1">
<Circle className="h-3.5 w-3.5 fill-primary text-primary" />
</div>
<div className="flex flex-col sm:flex-row sm:items-start gap-2">
<div className="min-w-[140px] text-sm text-muted-foreground">
<div>{event.date}</div>
<div>{event.time}</div>
</div>
<div className="bg-muted/50 rounded-lg p-3 flex-1">
<h4 className="font-medium">{event.title}</h4>
<p className="text-sm mt-1">{event.description}</p>
<div className="text-xs text-muted-foreground mt-2">
Added by {event.user} ({event.role})
</div>
</div>
</div>
</div>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,87 @@
import { User } from "lucide-react"
import { Badge } from "@/app/_components/ui/badge"
export default function CaseWitnesses() {
const witnesses = [
{
id: "W-1201",
name: "Emily Parker",
type: "Eyewitness",
contactInfo: "555-123-4567",
interviewDate: "Apr 20, 2023",
status: "Interviewed",
reliability: "High",
notes: "Neighbor who heard argument and saw suspect leaving the scene.",
},
{
id: "W-1202",
name: "Thomas Grant",
type: "Character Witness",
contactInfo: "555-987-6543",
interviewDate: "Apr 19, 2023",
status: "Interviewed",
reliability: "Medium",
notes: "Victim's coworker who provided information about recent conflicts.",
},
{
id: "W-1203",
name: "Maria Sanchez",
type: "Eyewitness",
contactInfo: "555-456-7890",
interviewDate: "Pending",
status: "Scheduled",
reliability: "Unknown",
notes: "Passerby who may have seen suspect in the area before the incident.",
},
]
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h3 className="text-lg font-semibold">Witnesses & Interviews ({witnesses.length})</h3>
<button className="text-sm text-primary">Add Witness</button>
</div>
<div className="space-y-3">
{witnesses.map((witness) => (
<div
key={witness.id}
className="flex items-start gap-3 p-3 border rounded-lg hover:bg-muted/50 transition-colors"
>
<div className="w-10 h-10 rounded-full bg-muted flex items-center justify-center shrink-0">
<User className="h-5 w-5" />
</div>
<div className="flex-1 min-w-0">
<div className="flex flex-wrap items-center gap-2">
<h4 className="font-medium">{witness.name}</h4>
<Badge variant="outline">{witness.type}</Badge>
<Badge
variant="outline"
className={
witness.status === "Interviewed" ? "bg-green-100 text-green-800" : "bg-yellow-100 text-yellow-800"
}
>
{witness.status}
</Badge>
</div>
<div className="flex flex-wrap gap-x-4 gap-y-1 mt-1 text-xs text-muted-foreground">
<span>ID: {witness.id}</span>
<span>Contact: {witness.contactInfo}</span>
<span>Reliability: {witness.reliability}</span>
{witness.interviewDate !== "Pending" && <span>Interviewed: {witness.interviewDate}</span>}
</div>
<p className="text-sm mt-2">{witness.notes}</p>
</div>
<button className="text-sm text-primary shrink-0">
{witness.status === "Interviewed" ? "View Statement" : "Schedule Interview"}
</button>
</div>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,101 @@
"use client"
import { ArrowLeft, Calendar, FileText, MessageSquare, Paperclip, Shield, User } from "lucide-react"
import { Button } from "@/app/_components/ui/button"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/app/_components/ui/tabs"
import CaseHeader from "./_components/case-header"
import CaseTimeline from "./_components/case-timeline"
import CaseEvidence from "./_components/case-evidence"
import CaseWitnesses from "./_components/case-witnesses"
import CaseDocuments from "./_components/case-documents"
import CaseNotes from "./_components/case-notes"
import CaseAssignees from "./_components/case-assignees"
import CaseRelated from "./_components/case-related"
export default function CrimeIncidentPage() {
return (
<div className="container py-4 min-h-screen">
<div className="max-w-7xl mx-auto">
<div className="mb-6">
<Button variant="ghost" size="sm" className="mb-4">
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Cases
</Button>
<CaseHeader
caseId="CR-7823"
title="Homicide Investigation - Downtown District"
status="Active"
priority="Critical"
dateOpened="2023-04-15"
lastUpdated="2023-04-22"
/>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2">
<Tabs defaultValue="timeline" className="w-full">
<TabsList className="grid grid-cols-5 mb-4">
<TabsTrigger value="timeline">
<Calendar className="h-4 w-4 mr-2" />
Timeline
</TabsTrigger>
<TabsTrigger value="evidence">
<Paperclip className="h-4 w-4 mr-2" />
Evidence
</TabsTrigger>
<TabsTrigger value="witnesses">
<User className="h-4 w-4 mr-2" />
Witnesses
</TabsTrigger>
<TabsTrigger value="documents">
<FileText className="h-4 w-4 mr-2" />
Documents
</TabsTrigger>
<TabsTrigger value="notes">
<MessageSquare className="h-4 w-4 mr-2" />
Notes
</TabsTrigger>
</TabsList>
<TabsContent value="timeline" className="border rounded-lg p-4">
<CaseTimeline />
</TabsContent>
<TabsContent value="evidence" className="border rounded-lg p-4">
<CaseEvidence />
</TabsContent>
<TabsContent value="witnesses" className="border rounded-lg p-4">
<CaseWitnesses />
</TabsContent>
<TabsContent value="documents" className="border rounded-lg p-4">
<CaseDocuments />
</TabsContent>
<TabsContent value="notes" className="border rounded-lg p-4">
<CaseNotes />
</TabsContent>
</Tabs>
</div>
<div className="space-y-6">
<div className="border rounded-lg p-4">
<h3 className="text-lg font-semibold mb-4 flex items-center">
<Shield className="h-5 w-5 mr-2" />
Assigned Personnel
</h3>
<CaseAssignees />
</div>
<div className="border rounded-lg p-4">
<h3 className="text-lg font-semibold mb-4">Related Cases</h3>
<CaseRelated />
</div>
</div>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,84 @@
import { Badge } from "@/app/_components/ui/badge"
import { ArrowRight, Clock, User } from "lucide-react"
export default function ChainOfCustody() {
const custodyEvents = [
{
evidenceId: "EV-4523",
events: [
{
action: "Collected",
by: "Officer Wilson",
date: "Apr 18, 2023",
time: "16:45",
},
{
action: "Transferred",
by: "Officer Wilson",
to: "Evidence Room",
date: "Apr 18, 2023",
time: "18:30",
},
{
action: "Checked Out",
by: "Sarah Johnson",
date: "Apr 20, 2023",
time: "09:15",
},
{
action: "Returned",
by: "Sarah Johnson",
date: "Apr 22, 2023",
time: "14:30",
},
],
},
]
return (
<div className="mt-4 space-y-4">
<div className="flex justify-between items-center">
<h3 className="text-sm font-medium">Recent Activity</h3>
<button className="text-xs text-primary">View All</button>
</div>
{custodyEvents.map((item) => (
<div key={item.evidenceId} className="border rounded-lg p-3">
<div className="flex justify-between items-center mb-3">
<h4 className="font-medium text-sm">Evidence #{item.evidenceId}</h4>
<Badge variant="outline" className="bg-blue-100 text-blue-800">
{item.events.length} transfers
</Badge>
</div>
<div className="space-y-3">
{item.events.map((event, index) => (
<div key={index} className="flex items-center gap-2 text-sm">
<div className="w-6 h-6 rounded-full bg-muted flex items-center justify-center shrink-0">
{event.action === "Collected" || event.action === "Checked Out" ? (
<User className="h-3 w-3" />
) : (
<ArrowRight className="h-3 w-3" />
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-1">
<span className="font-medium">{event.action}</span>
<span>by {event.by}</span>
{event.to && <span>to {event.to}</span>}
</div>
</div>
<div className="text-xs text-muted-foreground flex items-center">
<Clock className="h-3 w-3 mr-1" />
{event.date}, {event.time}
</div>
</div>
))}
</div>
</div>
))}
</div>
)
}

View File

@ -0,0 +1,66 @@
import { Badge } from "@/app/_components/ui/badge"
import { Download, FileText, ImageIcon, Video } from "lucide-react"
export default function DigitalEvidence() {
const digitalItems = [
{
id: "EV-4524",
type: "Images",
description: "Crime scene photos (24 files)",
case: "CR-7823",
size: "156 MB",
format: "JPG",
icon: ImageIcon,
},
{
id: "EV-4530",
type: "Video",
description: "Security camera footage",
case: "CR-7825",
size: "1.2 GB",
format: "MP4",
icon: Video,
},
{
id: "EV-4532",
type: "Document",
description: "Forensic report",
case: "CR-7823",
size: "2.4 MB",
format: "PDF",
icon: FileText,
},
]
return (
<div className="mt-4 space-y-3">
{digitalItems.map((item) => (
<div key={item.id} className="flex items-center gap-3 p-2 border rounded-lg">
<div className="w-8 h-8 rounded bg-muted flex items-center justify-center shrink-0">
<item.icon className="h-4 w-4" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h4 className="font-medium text-sm">{item.id}</h4>
<Badge variant="outline">{item.format}</Badge>
</div>
<p className="text-xs truncate">{item.description}</p>
<div className="flex gap-2 text-xs text-muted-foreground mt-1">
<span>Case: {item.case}</span>
<span>{item.size}</span>
</div>
</div>
<button className="text-primary p-1 rounded-full hover:bg-muted">
<Download className="h-4 w-4" />
</button>
</div>
))}
<button className="w-full text-sm text-primary mt-2 py-1">Upload Digital Evidence</button>
</div>
)
}

View File

@ -0,0 +1,74 @@
import { Badge } from "@/app/_components/ui/badge"
import { FileText } from "lucide-react"
export default function EvidenceByCase() {
const cases = [
{
id: "CR-7823",
title: "Homicide Investigation",
evidenceCount: 12,
recentItems: [
{ id: "EV-4523", type: "Weapon" },
{ id: "EV-4524", type: "Photograph" },
{ id: "EV-4525", type: "Document" },
],
},
{
id: "CR-7825",
title: "Armed Robbery",
evidenceCount: 8,
recentItems: [
{ id: "EV-4527", type: "Fingerprint" },
{ id: "EV-4528", type: "Clothing" },
],
},
{
id: "CR-7830",
title: "Kidnapping",
evidenceCount: 15,
recentItems: [
{ id: "EV-4535", type: "Vehicle" },
{ id: "EV-4536", type: "DNA Sample" },
],
},
]
return (
<div className="mt-4 space-y-3">
{cases.map((case_) => (
<div key={case_.id} className="border rounded-lg p-3">
<div className="flex items-center gap-2 mb-2">
<div className="w-8 h-8 rounded bg-muted flex items-center justify-center shrink-0">
<FileText className="h-4 w-4" />
</div>
<div>
<h4 className="font-medium text-sm">Case #{case_.id}</h4>
<div className="text-xs text-muted-foreground truncate">{case_.title}</div>
</div>
<Badge variant="outline" className="ml-auto">
{case_.evidenceCount} items
</Badge>
</div>
<div className="flex flex-wrap gap-2 mt-3">
{case_.recentItems.map((item) => (
<div key={item.id} className="text-xs px-2 py-1 bg-muted rounded-md">
{item.id} ({item.type})
</div>
))}
{case_.evidenceCount > case_.recentItems.length && (
<div className="text-xs px-2 py-1 bg-muted rounded-md">
+{case_.evidenceCount - case_.recentItems.length} more
</div>
)}
</div>
</div>
))}
<button className="w-full text-sm text-primary mt-2 py-1">View All Cases</button>
</div>
)
}

View File

@ -0,0 +1,129 @@
import { Badge } from "@/app/_components/ui/badge"
import { FileText, ImageIcon, Package, Search } from "lucide-react"
export default function EvidenceCatalog() {
const evidenceItems = [
{
id: "EV-4523",
type: "Weapon",
description: "Kitchen knife, 8-inch blade with wooden handle",
case: "CR-7823",
dateCollected: "Apr 18, 2023",
status: "Processing",
location: "Evidence Locker B-12",
icon: Package,
},
{
id: "EV-4524",
type: "Photograph",
description: "Crime scene photos - living room area",
case: "CR-7823",
dateCollected: "Apr 15, 2023",
status: "Analyzed",
location: "Digital Storage",
icon: ImageIcon,
},
{
id: "EV-4525",
type: "Document",
description: "Victim's personal diary",
case: "CR-7823",
dateCollected: "Apr 15, 2023",
status: "Analyzed",
location: "Evidence Locker A-7",
icon: FileText,
},
{
id: "EV-4526",
type: "DNA Sample",
description: "Blood sample from kitchen floor",
case: "CR-7823",
dateCollected: "Apr 15, 2023",
status: "Lab Analysis",
location: "Forensic Lab",
icon: Package,
},
{
id: "EV-4527",
type: "Fingerprint",
description: "Prints lifted from doorknob",
case: "CR-7825",
dateCollected: "Apr 20, 2023",
status: "Processing",
location: "Forensic Lab",
icon: FileText,
},
{
id: "EV-4528",
type: "Clothing",
description: "Victim's jacket with possible blood stains",
case: "CR-7825",
dateCollected: "Apr 20, 2023",
status: "Lab Analysis",
location: "Evidence Locker C-5",
icon: Package,
},
]
return (
<div className="mt-4">
<div className="flex justify-between items-center mb-4">
<div className="flex gap-2">
<button className="text-sm font-medium">All</button>
<button className="text-sm text-muted-foreground">Physical</button>
<button className="text-sm text-muted-foreground">Digital</button>
</div>
<div className="relative">
<input
type="search"
placeholder="Search evidence..."
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"
/>
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
</div>
</div>
<div className="space-y-3">
{evidenceItems.map((item) => (
<div
key={item.id}
className="flex items-start gap-3 p-3 border rounded-lg hover:bg-muted/50 transition-colors"
>
<div className="w-10 h-10 rounded-full bg-muted flex items-center justify-center shrink-0">
<item.icon className="h-5 w-5" />
</div>
<div className="flex-1 min-w-0">
<div className="flex flex-wrap items-center gap-2">
<h4 className="font-medium">{item.id}</h4>
<Badge variant="outline">{item.type}</Badge>
<Badge
variant="outline"
className={
item.status === "Analyzed"
? "bg-green-100 text-green-800"
: item.status === "Processing"
? "bg-yellow-100 text-yellow-800"
: "bg-blue-100 text-blue-800"
}
>
{item.status}
</Badge>
</div>
<p className="text-sm mt-1">{item.description}</p>
<div className="flex flex-wrap gap-x-4 gap-y-1 mt-2 text-xs text-muted-foreground">
<span>Case: {item.case}</span>
<span>Collected: {item.dateCollected}</span>
<span>Location: {item.location}</span>
</div>
</div>
<button className="text-sm text-primary shrink-0">View Details</button>
</div>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,74 @@
import { Badge } from "@/app/_components/ui/badge"
import { AlertTriangle, Calendar } from "lucide-react"
export default function EvidenceDisposal() {
const disposalItems = [
{
id: "EV-3245",
type: "Clothing",
case: "CR-6578",
scheduledDate: "May 15, 2023",
disposalType: "Return to Owner",
status: "Scheduled",
},
{
id: "EV-3246",
type: "Drug Sample",
case: "CR-6580",
scheduledDate: "May 20, 2023",
disposalType: "Destruction",
status: "Pending Approval",
},
{
id: "EV-3250",
type: "Electronics",
case: "CR-6590",
scheduledDate: "May 25, 2023",
disposalType: "Return to Owner",
status: "Scheduled",
},
]
return (
<div className="mt-4 space-y-3">
{disposalItems.map((item) => (
<div key={item.id} className="flex items-center gap-3 p-2 border rounded-lg">
<div className="w-8 h-8 rounded-full bg-muted flex items-center justify-center shrink-0">
<AlertTriangle className="h-4 w-4" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h4 className="font-medium text-sm">{item.id}</h4>
<Badge variant="outline">{item.type}</Badge>
</div>
<div className="flex gap-2 text-xs text-muted-foreground mt-1">
<span>Case: {item.case}</span>
<span>Method: {item.disposalType}</span>
</div>
<div className="flex items-center gap-1 text-xs mt-1">
<Calendar className="h-3 w-3" />
<span>Scheduled: {item.scheduledDate}</span>
<Badge
variant="outline"
className={
item.status === "Scheduled"
? "bg-green-100 text-green-800 ml-2"
: "bg-yellow-100 text-yellow-800 ml-2"
}
>
{item.status}
</Badge>
</div>
</div>
<button className="text-xs text-primary shrink-0">Review</button>
</div>
))}
<button className="w-full text-sm text-primary mt-2 py-1">Schedule Disposal</button>
</div>
)
}

View File

@ -0,0 +1,24 @@
import { Badge } from "@/app/_components/ui/badge"
import { Button } from "@/app/_components/ui/button"
export default function EvidenceHeader() {
return (
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-8">
<div>
<h2 className="text-3xl font-bold tracking-tight">Evidence Management</h2>
<p className="text-muted-foreground">Track, analyze, and manage physical and digital evidence</p>
</div>
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4">
<div className="flex items-center gap-2">
<Badge variant="outline" className="bg-blue-100 text-blue-800 hover:bg-blue-100">
Items: 1,245
</Badge>
<Badge variant="outline" className="bg-yellow-100 text-yellow-800 hover:bg-yellow-100">
Processing: 32
</Badge>
</div>
<Button>Log New Evidence</Button>
</div>
</div>
)
}

View File

@ -0,0 +1,57 @@
import { Search } from "lucide-react"
import { Button } from "@/app/_components/ui/button"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/app/_components/ui/select"
export default function EvidenceSearch() {
return (
<div className="mt-4 space-y-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 by ID, description, or case..."
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="grid grid-cols-2 gap-2">
<div>
<label className="text-xs text-muted-foreground mb-1 block">Evidence Type</label>
<Select>
<SelectTrigger>
<SelectValue placeholder="All Types" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Types</SelectItem>
<SelectItem value="weapon">Weapon</SelectItem>
<SelectItem value="dna">DNA Sample</SelectItem>
<SelectItem value="document">Document</SelectItem>
<SelectItem value="photo">Photograph</SelectItem>
<SelectItem value="clothing">Clothing</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label className="text-xs text-muted-foreground mb-1 block">Status</label>
<Select>
<SelectTrigger>
<SelectValue placeholder="Any Status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Any Status</SelectItem>
<SelectItem value="processing">Processing</SelectItem>
<SelectItem value="analyzed">Analyzed</SelectItem>
<SelectItem value="stored">Stored</SelectItem>
<SelectItem value="disposal">Scheduled for Disposal</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<Button className="w-full">Search Evidence</Button>
<div className="text-xs text-muted-foreground">Recent searches: EV-4523, CR-7823, "knife", "blood sample"</div>
</div>
)
}

View File

@ -0,0 +1,98 @@
import { Badge } from "@/app/_components/ui/badge"
import { Progress } from "@/app/_components/ui/progress"
import { FlaskRoundIcon as Flask } from "lucide-react"
export default function LabAnalysis() {
const labItems = [
{
id: "EV-4526",
type: "DNA Sample",
description: "Blood sample from kitchen floor",
case: "CR-7823",
submittedDate: "Apr 15, 2023",
status: "Processing",
progress: 65,
estimatedCompletion: "Apr 25, 2023",
priority: "High",
},
{
id: "EV-4528",
type: "Clothing",
description: "Victim's jacket with possible blood stains",
case: "CR-7825",
submittedDate: "Apr 20, 2023",
status: "Queue",
progress: 10,
estimatedCompletion: "Apr 30, 2023",
priority: "Medium",
},
{
id: "EV-4527",
type: "Fingerprint",
description: "Prints lifted from doorknob",
case: "CR-7825",
submittedDate: "Apr 20, 2023",
status: "Processing",
progress: 40,
estimatedCompletion: "Apr 27, 2023",
priority: "High",
},
]
return (
<div className="mt-4">
<div className="flex justify-between items-center mb-4">
<div className="flex gap-2">
<button className="text-sm font-medium">All</button>
<button className="text-sm text-muted-foreground">Processing</button>
<button className="text-sm text-muted-foreground">Completed</button>
</div>
<button className="text-sm text-primary">Submit to Lab</button>
</div>
<div className="space-y-3">
{labItems.map((item) => (
<div
key={item.id}
className="flex items-start gap-3 p-3 border rounded-lg hover:bg-muted/50 transition-colors"
>
<div className="w-10 h-10 rounded-full bg-muted flex items-center justify-center shrink-0">
<Flask className="h-5 w-5" />
</div>
<div className="flex-1 min-w-0">
<div className="flex flex-wrap items-center gap-2">
<h4 className="font-medium">{item.id}</h4>
<Badge variant="outline">{item.type}</Badge>
<Badge
variant="outline"
className={item.priority === "High" ? "bg-red-100 text-red-800" : "bg-yellow-100 text-yellow-800"}
>
{item.priority}
</Badge>
</div>
<p className="text-sm mt-1">{item.description}</p>
<div className="mt-2">
<div className="flex justify-between text-xs mb-1">
<span>Analysis Progress</span>
<span>{item.progress}%</span>
</div>
<Progress value={item.progress} className="h-2" />
</div>
<div className="flex flex-wrap gap-x-4 gap-y-1 mt-2 text-xs text-muted-foreground">
<span>Case: {item.case}</span>
<span>Submitted: {item.submittedDate}</span>
<span>Est. Completion: {item.estimatedCompletion}</span>
</div>
</div>
<button className="text-sm text-primary shrink-0">View Results</button>
</div>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,82 @@
import { Badge } from "@/app/_components/ui/badge"
import { Progress } from "@/app/_components/ui/progress"
import { Package } from "lucide-react"
export default function StorageLocations() {
const locations = [
{
id: "A",
name: "Evidence Locker A",
capacity: 85,
items: 42,
securityLevel: "High",
},
{
id: "B",
name: "Evidence Locker B",
capacity: 90,
items: 81,
securityLevel: "High",
},
{
id: "C",
name: "Evidence Locker C",
capacity: 75,
items: 45,
securityLevel: "Medium",
},
{
id: "D",
name: "Digital Storage",
capacity: 40,
items: 32,
securityLevel: "High",
},
]
return (
<div className="mt-4 space-y-3">
{locations.map((location) => (
<div key={location.id} className="border rounded-lg p-3">
<div className="flex items-center gap-2 mb-2">
<div className="w-8 h-8 rounded bg-muted flex items-center justify-center shrink-0">
<Package className="h-4 w-4" />
</div>
<div>
<h4 className="font-medium text-sm">{location.name}</h4>
<div className="text-xs text-muted-foreground">Security: {location.securityLevel}</div>
</div>
<Badge
variant="outline"
className={
location.items / location.capacity > 0.9
? "bg-red-100 text-red-800 ml-auto"
: location.items / location.capacity > 0.7
? "bg-yellow-100 text-yellow-800 ml-auto"
: "bg-green-100 text-green-800 ml-auto"
}
>
{location.items}/{location.capacity} items
</Badge>
</div>
<Progress
value={(location.items / location.capacity) * 100}
className="h-2"
indicatorClassName={
location.items / location.capacity > 0.9
? "bg-red-500"
: location.items / location.capacity > 0.7
? "bg-yellow-500"
: "bg-green-500"
}
/>
</div>
))}
<button className="w-full text-sm text-primary mt-2 py-1">Manage Locations</button>
</div>
)
}

View File

@ -0,0 +1,92 @@
"use client"
import { BentoGrid, BentoGridItem } from "@/app/_components/ui/bento-grid"
import { Briefcase, FileText, Package, ImageIcon, Database, Clock, AlertTriangle, Search } from "lucide-react"
import EvidenceHeader from "./_components/evidence-header"
import EvidenceCatalog from "./_components/evidence-catalog"
import ChainOfCustody from "./_components/chain-of-custody"
import LabAnalysis from "./_components/lab-analysis"
import StorageLocations from "./_components/storage-locations"
import EvidenceByCase from "./_components/evidence-by-case"
import DigitalEvidence from "./_components/digital-evidence"
import EvidenceDisposal from "./_components/evidence-disposal"
import EvidenceSearch from "./_components/evidence-search"
export default function EvidenceManagementPage() {
return (
<div className="container py-4 min-h-screen">
<div className="max-w-7xl mx-auto">
<EvidenceHeader />
<BentoGrid>
<BentoGridItem
title="Evidence Catalog"
description="Recently logged items"
icon={<Briefcase className="w-5 h-5" />}
colSpan="2"
>
<EvidenceCatalog />
</BentoGridItem>
<BentoGridItem
title="Chain of Custody"
description="Evidence handling records"
icon={<Clock className="w-5 h-5" />}
>
<ChainOfCustody />
</BentoGridItem>
<BentoGridItem
title="Lab Analysis"
description="Processing status and results"
icon={<FileText className="w-5 h-5" />}
colSpan="2"
>
<LabAnalysis />
</BentoGridItem>
<BentoGridItem
title="Storage Locations"
description="Evidence storage management"
icon={<Package className="w-5 h-5" />}
>
<StorageLocations />
</BentoGridItem>
<BentoGridItem
title="Evidence by Case"
description="Items grouped by case"
icon={<Database className="w-5 h-5" />}
>
<EvidenceByCase />
</BentoGridItem>
<BentoGridItem
title="Digital Evidence"
description="Files, recordings, and media"
icon={<ImageIcon className="w-5 h-5" />}
>
<DigitalEvidence />
</BentoGridItem>
<BentoGridItem
title="Evidence Disposal"
description="Scheduled for destruction or return"
icon={<AlertTriangle className="w-5 h-5" />}
>
<EvidenceDisposal />
</BentoGridItem>
<BentoGridItem
title="Evidence Search"
description="Find items by ID, case, or type"
icon={<Search className="w-5 h-5" />}
>
<EvidenceSearch />
</BentoGridItem>
</BentoGrid>
</div>
</div>
)
}

View File

@ -0,0 +1,24 @@
import { Badge } from "@/app/_components/ui/badge"
import { Button } from "@/app/_components/ui/button"
export default function DepartmentHeader() {
return (
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-8">
<div>
<h2 className="text-3xl font-bold tracking-tight">Officer Management</h2>
<p className="text-muted-foreground">Personnel, scheduling, and department resources</p>
</div>
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4">
<div className="flex items-center gap-2">
<Badge variant="outline" className="bg-green-100 text-green-800 hover:bg-green-100">
On Duty: 18
</Badge>
<Badge variant="outline" className="bg-yellow-100 text-yellow-800 hover:bg-yellow-100">
Off Duty: 12
</Badge>
</div>
<Button>Add Officer</Button>
</div>
</div>
)
}

View File

@ -0,0 +1,65 @@
import { Badge } from "@/app/_components/ui/badge"
import { Car, Radio, Shield } from "lucide-react"
export default function EquipmentAssignments() {
const equipment = [
{
type: "Vehicle",
id: "VEH-1234",
assignedTo: "Emily Parker",
status: "In Service",
icon: Car,
},
{
type: "Radio",
id: "RAD-5678",
assignedTo: "Michael Chen",
status: "In Service",
icon: Radio,
},
{
type: "Body Camera",
id: "CAM-9012",
assignedTo: "Robert Wilson",
status: "Maintenance",
icon: Shield,
},
{
type: "Vehicle",
id: "VEH-3456",
assignedTo: "David Thompson",
status: "In Service",
icon: Car,
},
]
return (
<div className="mt-4 space-y-2">
{equipment.map((item, index) => (
<div key={index} className="flex items-center gap-3 p-2 border rounded-lg">
<div className="w-8 h-8 rounded bg-muted flex items-center justify-center shrink-0">
<item.icon className="h-4 w-4" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h4 className="font-medium text-sm">{item.type}</h4>
<span className="text-xs text-muted-foreground">#{item.id}</span>
</div>
<div className="text-xs text-muted-foreground">Assigned to: {item.assignedTo}</div>
</div>
<Badge
variant="outline"
className={item.status === "In Service" ? "bg-green-100 text-green-800" : "bg-yellow-100 text-yellow-800"}
>
{item.status}
</Badge>
</div>
))}
<button className="w-full text-sm text-primary mt-2 py-1">Manage Equipment</button>
</div>
)
}

View File

@ -0,0 +1,92 @@
import { Badge } from "@/app/_components/ui/badge"
import { FileText } from "lucide-react"
export default function IncidentReports() {
const reports = [
{
id: "IR-7823",
title: "Traffic Stop - Speeding",
officer: "David Thompson",
date: "Apr 22, 2023",
status: "Submitted",
location: "Highway 101, Mile 23",
},
{
id: "IR-7824",
title: "Domestic Disturbance",
officer: "Emily Parker",
date: "Apr 22, 2023",
status: "Pending Review",
location: "123 Main St, Apt 4B",
},
{
id: "IR-7825",
title: "Shoplifting",
officer: "James Rodriguez",
date: "Apr 21, 2023",
status: "Approved",
location: "Downtown Mall, Store #12",
},
{
id: "IR-7826",
title: "Noise Complaint",
officer: "Emily Parker",
date: "Apr 21, 2023",
status: "Approved",
location: "456 Oak Ave",
},
]
return (
<div className="mt-4">
<div className="flex justify-between items-center mb-4">
<div className="flex gap-2">
<button className="text-sm font-medium">All</button>
<button className="text-sm text-muted-foreground">Pending</button>
<button className="text-sm text-muted-foreground">Approved</button>
</div>
<button className="text-sm text-primary">New Report</button>
</div>
<div className="space-y-3">
{reports.map((report) => (
<div
key={report.id}
className="flex items-start gap-3 p-3 border rounded-lg hover:bg-muted/50 transition-colors"
>
<div className="w-10 h-10 rounded bg-muted flex items-center justify-center shrink-0">
<FileText className="h-5 w-5" />
</div>
<div className="flex-1 min-w-0">
<div className="flex flex-wrap items-center gap-2">
<h4 className="font-medium">{report.title}</h4>
<span className="text-sm text-muted-foreground">#{report.id}</span>
<Badge
variant="outline"
className={
report.status === "Approved"
? "bg-green-100 text-green-800"
: report.status === "Pending Review"
? "bg-yellow-100 text-yellow-800"
: "bg-blue-100 text-blue-800"
}
>
{report.status}
</Badge>
</div>
<div className="flex flex-wrap gap-x-4 gap-y-1 mt-1 text-xs text-muted-foreground">
<span>Officer: {report.officer}</span>
<span>Date: {report.date}</span>
<span>Location: {report.location}</span>
</div>
</div>
<button className="text-sm text-primary shrink-0">View</button>
</div>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,62 @@
import { Badge } from "@/app/_components/ui/badge"
import { Calendar } from "lucide-react"
export default function LeaveManagement() {
const leaveRequests = [
{
officer: "Sarah Johnson",
type: "Vacation",
dates: "May 10-15, 2023",
status: "Approved",
requestDate: "Apr 15, 2023",
},
{
officer: "James Rodriguez",
type: "Sick Leave",
dates: "Apr 25, 2023",
status: "Pending",
requestDate: "Apr 24, 2023",
},
{
officer: "Michael Chen",
type: "Personal",
dates: "May 5, 2023",
status: "Pending",
requestDate: "Apr 20, 2023",
},
]
return (
<div className="mt-4 space-y-3">
{leaveRequests.map((request, index) => (
<div key={index} className="flex items-start gap-3 p-2 border rounded-lg">
<div className="w-8 h-8 rounded bg-muted flex items-center justify-center shrink-0">
<Calendar className="h-4 w-4" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h4 className="font-medium text-sm">{request.officer}</h4>
<Badge
variant="outline"
className={
request.status === "Approved" ? "bg-green-100 text-green-800" : "bg-yellow-100 text-yellow-800"
}
>
{request.status}
</Badge>
</div>
<div className="text-xs text-muted-foreground">
{request.type} {request.dates}
</div>
<div className="text-xs text-muted-foreground mt-1">Requested: {request.requestDate}</div>
</div>
</div>
))}
<button className="w-full text-sm text-primary mt-2 py-1">Request Leave</button>
</div>
)
}

View File

@ -0,0 +1,55 @@
import { Progress } from "@/app/_components/ui/progress"
import { Award, TrendingUp } from "lucide-react"
export default function OfficerPerformance() {
const topPerformers = [
{ name: "Emily Parker", metric: "Case Clearance", value: 92 },
{ name: "Michael Chen", metric: "Response Time", value: 88 },
{ name: "Sarah Johnson", metric: "Evidence Processing", value: 95 },
]
return (
<div className="mt-4 space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="border rounded-lg p-3">
<div className="text-sm text-muted-foreground mb-1">Avg. Case Clearance</div>
<div className="text-2xl font-bold">68%</div>
<div className="text-xs text-green-600 flex items-center mt-1">
<TrendingUp className="h-3 w-3 mr-1" />
+5% from last month
</div>
</div>
<div className="border rounded-lg p-3">
<div className="text-sm text-muted-foreground mb-1">Avg. Response Time</div>
<div className="text-2xl font-bold">4.2m</div>
<div className="text-xs text-green-600 flex items-center mt-1">
<TrendingUp className="h-3 w-3 mr-1" />
-0.3m from last month
</div>
</div>
</div>
<div className="border rounded-lg p-3">
<h4 className="font-medium text-sm mb-3 flex items-center">
<Award className="h-4 w-4 mr-1" />
Top Performers
</h4>
<div className="space-y-3">
{topPerformers.map((performer, index) => (
<div key={index}>
<div className="flex justify-between text-sm mb-1">
<span>{performer.name}</span>
<span>
{performer.metric}: {performer.value}%
</span>
</div>
<Progress value={performer.value} className="h-2" />
</div>
))}
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,132 @@
import { User, Shield, Phone } from "lucide-react"
import { Badge } from "@/app/_components/ui/badge"
import { Button } from "@/app/_components/ui/button"
export default function OfficerRoster() {
const officers = [
{
id: "OFF-1234",
name: "Michael Chen",
badge: "ID-5678",
rank: "Detective",
unit: "Homicide",
status: "On Duty",
contact: "555-123-4567",
shift: "Day",
},
{
id: "OFF-2345",
name: "Sarah Johnson",
badge: "ID-6789",
rank: "Specialist",
unit: "Forensics",
status: "On Duty",
contact: "555-234-5678",
shift: "Day",
},
{
id: "OFF-3456",
name: "Robert Wilson",
badge: "ID-7890",
rank: "Technician",
unit: "Evidence",
status: "On Duty",
contact: "555-345-6789",
shift: "Day",
},
{
id: "OFF-4567",
name: "James Rodriguez",
badge: "ID-8901",
rank: "Officer",
unit: "Patrol",
status: "Off Duty",
contact: "555-456-7890",
shift: "Night",
},
{
id: "OFF-5678",
name: "Emily Parker",
badge: "ID-9012",
rank: "Sergeant",
unit: "Patrol",
status: "On Duty",
contact: "555-567-8901",
shift: "Day",
},
{
id: "OFF-6789",
name: "David Thompson",
badge: "ID-0123",
rank: "Officer",
unit: "Traffic",
status: "On Duty",
contact: "555-678-9012",
shift: "Day",
},
]
return (
<div className="mt-4">
<div className="flex justify-between items-center mb-4">
<div className="flex gap-2">
<Button variant="outline" size="sm">
All
</Button>
<Button variant="outline" size="sm">
On Duty
</Button>
<Button variant="outline" size="sm">
Off Duty
</Button>
</div>
<div className="relative">
<input
type="search"
placeholder="Search officers..."
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"
/>
<User className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{officers.map((officer) => (
<div
key={officer.id}
className="flex items-center gap-3 p-3 border rounded-lg hover:bg-muted/50 transition-colors"
>
<div className="w-10 h-10 rounded-full bg-muted flex items-center justify-center shrink-0">
<User className="h-5 w-5" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h4 className="font-medium">{officer.name}</h4>
<Badge
variant="outline"
className={
officer.status === "On Duty" ? "bg-green-100 text-green-800" : "bg-yellow-100 text-yellow-800"
}
>
{officer.status}
</Badge>
</div>
<div className="flex flex-wrap gap-x-3 gap-y-1 mt-1 text-xs text-muted-foreground">
<div className="flex items-center">
<Shield className="h-3 w-3 mr-1" />
{officer.rank} {officer.unit}
</div>
<div className="flex items-center">
<Phone className="h-3 w-3 mr-1" />
{officer.contact}
</div>
</div>
</div>
</div>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,85 @@
import { Calendar, Clock } from "lucide-react"
export default function ShiftSchedule() {
const days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
const shifts = [
{ time: "Day (7AM-3PM)", color: "bg-blue-100" },
{ time: "Evening (3PM-11PM)", color: "bg-purple-100" },
{ time: "Night (11PM-7AM)", color: "bg-indigo-100" },
]
// Sample schedule data
const schedule = {
"OFF-1234": [0, 0, 0, 0, 0, null, null], // Day shift Mon-Fri
"OFF-2345": [0, 0, 0, 0, 0, null, null], // Day shift Mon-Fri
"OFF-3456": [0, 0, 0, 0, 0, null, null], // Day shift Mon-Fri
"OFF-4567": [null, null, 2, 2, 2, 2, 2], // Night shift Wed-Sun
"OFF-5678": [0, 0, 0, 0, 0, null, null], // Day shift Mon-Fri
"OFF-6789": [1, 1, 1, 1, 1, null, null], // Evening shift Mon-Fri
}
const officers = [
{ id: "OFF-1234", name: "Michael Chen" },
{ id: "OFF-2345", name: "Sarah Johnson" },
{ id: "OFF-3456", name: "Robert Wilson" },
{ id: "OFF-4567", name: "James Rodriguez" },
{ id: "OFF-5678", name: "Emily Parker" },
{ id: "OFF-6789", name: "David Thompson" },
]
return (
<div className="mt-4">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center">
<Calendar className="h-4 w-4 mr-2" />
<span className="font-medium">Week of April 24-30, 2023</span>
</div>
<div className="flex gap-1">
<button className="p-1 rounded hover:bg-muted">
<Clock className="h-4 w-4" />
</button>
</div>
</div>
<div className="border rounded-lg overflow-hidden">
<div className="grid grid-cols-8 border-b bg-muted/50">
<div className="p-2 font-medium text-sm border-r">Officer</div>
{days.map((day, i) => (
<div key={day} className="p-2 font-medium text-sm text-center">
{day}
</div>
))}
</div>
{officers.map((officer) => (
<div key={officer.id} className="grid grid-cols-8 border-b last:border-b-0">
<div className="p-2 text-sm border-r truncate">{officer.name}</div>
{days.map((day, dayIndex) => {
const shiftIndex = schedule[officer.id]?.[dayIndex]
const shift = shifts[shiftIndex]
return (
<div key={`${officer.id}-${day}`} className="p-2 text-xs text-center">
{shift ? (
<div className={`${shift.color} rounded-md py-1 px-2`}>{shift.time.split(" ")[0]}</div>
) : (
<div className="text-muted-foreground">Off</div>
)}
</div>
)
})}
</div>
))}
</div>
<div className="mt-4 flex flex-wrap gap-2">
{shifts.map((shift) => (
<div key={shift.time} className="flex items-center text-xs">
<div className={`w-3 h-3 rounded-full ${shift.color} mr-1`}></div>
{shift.time}
</div>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,67 @@
import { Badge } from "@/app/_components/ui/badge"
import { Shield, Users } from "lucide-react"
export default function SpecializedUnits() {
const units = [
{
name: "SWAT",
members: 8,
status: "Available",
lead: "Capt. Anderson",
},
{
name: "K-9 Unit",
members: 5,
status: "On Call",
lead: "Lt. Martinez",
},
{
name: "Narcotics",
members: 6,
status: "Deployed",
lead: "Sgt. Williams",
},
]
return (
<div className="mt-4 space-y-3">
{units.map((unit, index) => (
<div key={index} className="flex items-start gap-3 p-2 border rounded-lg">
<div className="w-8 h-8 rounded bg-muted flex items-center justify-center shrink-0">
<Shield className="h-4 w-4" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h4 className="font-medium text-sm">{unit.name}</h4>
<Badge
variant="outline"
className={
unit.status === "Available"
? "bg-green-100 text-green-800"
: unit.status === "On Call"
? "bg-blue-100 text-blue-800"
: "bg-yellow-100 text-yellow-800"
}
>
{unit.status}
</Badge>
</div>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<div className="flex items-center">
<Users className="h-3 w-3 mr-1" />
{unit.members} members
</div>
<div>Lead: {unit.lead}</div>
</div>
</div>
<button className="text-xs text-primary shrink-0">Details</button>
</div>
))}
<button className="w-full text-sm text-primary mt-2 py-1">View All Units</button>
</div>
)
}

View File

@ -0,0 +1,61 @@
import { Badge } from "@/app/_components/ui/badge"
import { Progress } from "@/app/_components/ui/progress"
export default function TrainingStatus() {
const trainings = [
{
name: "Firearms Qualification",
dueDate: "May 15, 2023",
status: "Completed",
completion: 100,
},
{
name: "De-escalation Techniques",
dueDate: "June 10, 2023",
status: "In Progress",
completion: 60,
},
{
name: "Emergency Response",
dueDate: "July 22, 2023",
status: "Not Started",
completion: 0,
},
{
name: "Evidence Handling",
dueDate: "May 30, 2023",
status: "In Progress",
completion: 75,
},
]
return (
<div className="mt-4 space-y-3">
{trainings.map((training, index) => (
<div key={index} className="border rounded-lg p-3">
<div className="flex justify-between items-start mb-2">
<h4 className="font-medium text-sm">{training.name}</h4>
<Badge
variant="outline"
className={
training.status === "Completed"
? "bg-green-100 text-green-800"
: training.status === "In Progress"
? "bg-yellow-100 text-yellow-800"
: "bg-red-100 text-red-800"
}
>
{training.status}
</Badge>
</div>
<Progress value={training.completion} className="h-2 mb-2" />
<div className="text-xs text-muted-foreground">Due: {training.dueDate}</div>
</div>
))}
<button className="w-full text-sm text-primary mt-2 py-1">View All Trainings</button>
</div>
)
}

View File

@ -0,0 +1,92 @@
"use client"
import { BentoGrid, BentoGridItem } from "@/app/_components/ui/bento-grid"
import { Shield, Users, Calendar, Award, Clock, FileText, Briefcase } from "lucide-react"
import DepartmentHeader from "./_components/department-header"
import OfficerRoster from "./_components/officer-roster"
import ShiftSchedule from "./_components/shift-schedule"
import OfficerPerformance from "./_components/officer-performance"
import TrainingStatus from "./_components/training-status"
import EquipmentAssignments from "./_components/equipment-assignments"
import IncidentReports from "./_components/incident-reports"
import LeaveManagement from "./_components/leave-management"
import SpecializedUnits from "./_components/specialized-units"
export default function OfficerManagementPage() {
return (
<div className="container py-4 min-h-screen">
<div className="max-w-7xl mx-auto">
<DepartmentHeader />
<BentoGrid>
<BentoGridItem
title="Officer Roster"
description="Active personnel and status"
icon={<Shield className="w-5 h-5" />}
colSpan="2"
>
<OfficerRoster />
</BentoGridItem>
<BentoGridItem
title="Shift Schedule"
description="Current and upcoming shifts"
icon={<Calendar className="w-5 h-5" />}
rowSpan="2"
>
<ShiftSchedule />
</BentoGridItem>
<BentoGridItem
title="Officer Performance"
description="Case clearance and metrics"
icon={<Award className="w-5 h-5" />}
>
<OfficerPerformance />
</BentoGridItem>
<BentoGridItem
title="Training Status"
description="Certifications and requirements"
icon={<Award className="w-5 h-5" />}
>
<TrainingStatus />
</BentoGridItem>
<BentoGridItem
title="Equipment Assignments"
description="Issued gear and vehicles"
icon={<Briefcase className="w-5 h-5" />}
>
<EquipmentAssignments />
</BentoGridItem>
<BentoGridItem
title="Incident Reports"
description="Recently filed reports"
icon={<FileText className="w-5 h-5" />}
colSpan="2"
>
<IncidentReports />
</BentoGridItem>
<BentoGridItem
title="Leave Management"
description="Time-off requests and approvals"
icon={<Clock className="w-5 h-5" />}
>
<LeaveManagement />
</BentoGridItem>
<BentoGridItem
title="Specialized Units"
description="Tactical teams and special operations"
icon={<Users className="w-5 h-5" />}
>
<SpecializedUnits />
</BentoGridItem>
</BentoGrid>
</div>
</div>
)
}

View File

@ -0,0 +1,143 @@
import { Badge } from "@/app/_components/ui/badge"
import { Button } from "@/app/_components/ui/button"
import { AlertTriangle, MapPin, Clock, Users } from "lucide-react"
export default function ActiveIncidents() {
const incidents = [
{
id: "INC-4523",
type: "Domestic Disturbance",
location: "123 Main St, Apt 4B",
priority: "High",
status: "Units Responding",
timeReceived: "10:23 AM",
responseTime: "2m 15s",
assignedUnits: ["Unit 12", "Unit 15"],
},
{
id: "INC-4524",
type: "Traffic Accident",
location: "Interstate 95, Mile 42",
priority: "Medium",
status: "On Scene",
timeReceived: "10:15 AM",
responseTime: "5m 30s",
assignedUnits: ["Unit 8", "Unit 22"],
},
{
id: "INC-4525",
type: "Burglary",
location: "456 Oak Ave",
priority: "Medium",
status: "Units Responding",
timeReceived: "10:05 AM",
responseTime: "4m 45s",
assignedUnits: ["Unit 17"],
},
{
id: "INC-4526",
type: "Medical Emergency",
location: "789 Pine St",
priority: "Critical",
status: "On Scene",
timeReceived: "9:58 AM",
responseTime: "3m 10s",
assignedUnits: ["Unit 5", "Unit 9", "Ambulance 3"],
},
]
return (
<div className="mt-4">
<div className="flex justify-between items-center mb-4">
<div className="flex gap-2">
<Button variant="outline" size="sm">
All
</Button>
<Button variant="outline" size="sm">
Critical
</Button>
<Button variant="outline" size="sm">
High
</Button>
<Button variant="outline" size="sm">
Medium
</Button>
</div>
<div className="text-sm text-muted-foreground">Showing {incidents.length} active incidents</div>
</div>
<div className="space-y-3">
{incidents.map((incident) => (
<div
key={incident.id}
className="flex items-start gap-3 p-3 border rounded-lg hover:bg-muted/50 transition-colors"
>
<div
className={`w-10 h-10 rounded-full flex items-center justify-center shrink-0 ${
incident.priority === "Critical"
? "bg-red-100"
: incident.priority === "High"
? "bg-orange-100"
: "bg-yellow-100"
}`}
>
<AlertTriangle
className={`h-5 w-5 ${
incident.priority === "Critical"
? "text-red-600"
: incident.priority === "High"
? "text-orange-600"
: "text-yellow-600"
}`}
/>
</div>
<div className="flex-1 min-w-0">
<div className="flex flex-wrap items-center gap-2">
<h4 className="font-medium">{incident.type}</h4>
<span className="text-sm text-muted-foreground">#{incident.id}</span>
<Badge
variant="outline"
className={
incident.priority === "Critical"
? "bg-red-100 text-red-800"
: incident.priority === "High"
? "bg-orange-100 text-orange-800"
: "bg-yellow-100 text-yellow-800"
}
>
{incident.priority}
</Badge>
<Badge variant="outline" className="bg-blue-100 text-blue-800">
{incident.status}
</Badge>
</div>
<div className="flex flex-wrap gap-x-4 gap-y-1 mt-2 text-xs text-muted-foreground">
<div className="flex items-center">
<MapPin className="h-3 w-3 mr-1" />
{incident.location}
</div>
<div className="flex items-center">
<Clock className="h-3 w-3 mr-1" />
Received: {incident.timeReceived} (Response: {incident.responseTime})
</div>
<div className="flex items-center">
<Users className="h-3 w-3 mr-1" />
Units: {incident.assignedUnits.join(", ")}
</div>
</div>
</div>
<div className="flex flex-col gap-2">
<Button size="sm">Update</Button>
<Button size="sm" variant="outline">
Details
</Button>
</div>
</div>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,87 @@
import { Badge } from "@/app/_components/ui/badge"
import { Radio, MessageSquare, User } from "lucide-react"
export default function DispatchCommunications() {
const communications = [
{
id: 1,
type: "Radio",
from: "Unit 12",
message: "Arriving on scene at 123 Main St.",
time: "10:32 AM",
priority: "Normal",
},
{
id: 2,
type: "Radio",
from: "Unit 8",
message: "Requesting additional unit for traffic control at accident scene.",
time: "10:28 AM",
priority: "High",
},
{
id: 3,
type: "Message",
from: "Dispatch",
message: "Be advised, suspect description updated for INC-4525.",
time: "10:25 AM",
priority: "Normal",
},
{
id: 4,
type: "Radio",
from: "Unit 5",
message: "Medical assistance required at 789 Pine St.",
time: "10:20 AM",
priority: "High",
},
]
return (
<div className="mt-4 space-y-3">
<div className="flex justify-between items-center">
<div className="text-sm font-medium">Recent Communications</div>
<Badge variant="outline" className="bg-blue-100 text-blue-800">
Channel: Main Dispatch
</Badge>
</div>
{communications.map((comm) => (
<div key={comm.id} className="border rounded-lg p-2">
<div className="flex items-center gap-2">
<div className="w-6 h-6 rounded-full bg-muted flex items-center justify-center shrink-0">
{comm.type === "Radio" ? <Radio className="h-3 w-3" /> : <MessageSquare className="h-3 w-3" />}
</div>
<div className="flex items-center gap-1">
<span className="font-medium text-xs">{comm.from}</span>
<span className="text-xs text-muted-foreground"> {comm.time}</span>
</div>
<Badge
variant="outline"
className={
comm.priority === "High" ? "bg-red-100 text-red-800 ml-auto" : "bg-blue-100 text-blue-800 ml-auto"
}
>
{comm.priority}
</Badge>
</div>
<p className="text-xs mt-1 ml-8">{comm.message}</p>
</div>
))}
<div className="flex items-center gap-2 mt-2">
<div className="w-8 h-8 rounded-full bg-muted flex items-center justify-center shrink-0">
<User className="h-4 w-4" />
</div>
<input
type="text"
placeholder="Send message to all units..."
className="flex-1 text-sm rounded-md border border-input bg-background px-3 py-1 ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
/>
</div>
</div>
)
}

View File

@ -0,0 +1,29 @@
import { Badge } from "@/app/_components/ui/badge"
import { Button } from "@/app/_components/ui/button"
import { AlertTriangle, Phone } from "lucide-react"
export default function DispatchHeader() {
return (
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-8">
<div>
<h2 className="text-3xl font-bold tracking-tight">Resource Dispatch Center</h2>
<p className="text-muted-foreground">Emergency response coordination and unit management</p>
</div>
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4">
<div className="flex items-center gap-2">
<Badge variant="outline" className="bg-green-100 text-green-800 hover:bg-green-100">
Active Units: 18/24
</Badge>
<Badge variant="outline" className="bg-red-100 text-red-800 hover:bg-red-100 flex items-center">
<AlertTriangle className="h-3 w-3 mr-1" />
High Call Volume
</Badge>
</div>
<Button className="flex items-center">
<Phone className="h-4 w-4 mr-2" />
New Dispatch
</Button>
</div>
</div>
)
}

View File

@ -0,0 +1,110 @@
import { Badge } from "@/app/_components/ui/badge"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/app/_components/ui/select"
export default function DispatchMap() {
return (
<div className="mt-4 h-full">
<div className="flex justify-between items-center mb-4">
<div className="flex gap-2">
<Badge variant="outline" className="bg-blue-100 text-blue-800">
Available Units
</Badge>
<Badge variant="outline" className="bg-green-100 text-green-800">
On Scene
</Badge>
<Badge variant="outline" className="bg-yellow-100 text-yellow-800">
Responding
</Badge>
<Badge variant="outline" className="bg-red-100 text-red-800">
Incidents
</Badge>
</div>
<Select defaultValue="all">
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Filter Units" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Units</SelectItem>
<SelectItem value="patrol">Patrol Units</SelectItem>
<SelectItem value="traffic">Traffic Units</SelectItem>
<SelectItem value="k9">K-9 Units</SelectItem>
<SelectItem value="medical">Medical Units</SelectItem>
</SelectContent>
</Select>
</div>
<div className="h-[400px] rounded-md bg-slate-100 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 className="absolute top-2 right-2 bg-background/80 backdrop-blur-sm p-2 rounded-md text-xs font-medium">
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-blue-500"></span> Available Units: 12
</div>
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-yellow-500"></span> Responding: 6
</div>
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-green-500"></span> On Scene: 6
</div>
<div className="flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-red-500"></span> Active Incidents: 4
</div>
</div>
<div className="z-10 bg-background/80 backdrop-blur-sm p-3 rounded-lg">
<span className="font-medium">Real-Time Dispatch Map</span>
<div className="text-xs text-muted-foreground mt-1">Last updated: Just now</div>
</div>
</div>
<div className="mt-4 grid grid-cols-2 gap-2">
<div className="border rounded-lg p-3">
<h4 className="text-sm font-medium">Coverage Analysis</h4>
<div className="mt-2 space-y-2">
<div className="flex justify-between items-center">
<span className="text-xs">Downtown District</span>
<Badge variant="outline" className="bg-green-100 text-green-800">
Good
</Badge>
</div>
<div className="flex justify-between items-center">
<span className="text-xs">West Side</span>
<Badge variant="outline" className="bg-yellow-100 text-yellow-800">
Limited
</Badge>
</div>
<div className="flex justify-between items-center">
<span className="text-xs">North Area</span>
<Badge variant="outline" className="bg-red-100 text-red-800">
Understaffed
</Badge>
</div>
</div>
</div>
<div className="border rounded-lg p-3">
<h4 className="text-sm font-medium">Response Zones</h4>
<div className="mt-2 space-y-2">
<div className="flex justify-between items-center">
<span className="text-xs">Zone 1 (Downtown)</span>
<span className="text-xs">4 units</span>
</div>
<div className="flex justify-between items-center">
<span className="text-xs">Zone 2 (West)</span>
<span className="text-xs">2 units</span>
</div>
<div className="flex justify-between items-center">
<span className="text-xs">Zone 3 (North)</span>
<span className="text-xs">1 unit</span>
</div>
<div className="flex justify-between items-center">
<span className="text-xs">Zone 4 (East)</span>
<span className="text-xs">3 units</span>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,73 @@
import { Badge } from "@/app/_components/ui/badge"
import { Clock, FileText } from "lucide-react"
export default function IncidentHistory() {
const closedIncidents = [
{
id: "INC-4520",
type: "Traffic Stop",
location: "Highway 101, Mile 35",
resolution: "Citation Issued",
timeReceived: "9:15 AM",
timeClosed: "9:45 AM",
units: ["Unit 22"],
},
{
id: "INC-4521",
type: "Alarm Activation",
location: "First National Bank",
resolution: "False Alarm",
timeReceived: "9:22 AM",
timeClosed: "9:40 AM",
units: ["Unit 7", "Unit 10"],
},
{
id: "INC-4522",
type: "Welfare Check",
location: "234 Cedar Lane",
resolution: "Assistance Provided",
timeReceived: "9:30 AM",
timeClosed: "9:55 AM",
units: ["Unit 15"],
},
]
return (
<div className="mt-4 space-y-3">
<div className="flex justify-between items-center">
<div className="text-sm font-medium">Recently Closed</div>
<button className="text-xs text-primary">View All</button>
</div>
{closedIncidents.map((incident) => (
<div key={incident.id} className="border rounded-lg p-2">
<div className="flex items-center gap-2">
<div className="w-6 h-6 rounded bg-muted flex items-center justify-center shrink-0">
<FileText className="h-3 w-3" />
</div>
<div>
<div className="flex items-center gap-1">
<span className="font-medium text-xs">{incident.type}</span>
<span className="text-xs text-muted-foreground">#{incident.id}</span>
</div>
<div className="text-xs text-muted-foreground">{incident.location}</div>
</div>
<Badge variant="outline" className="bg-green-100 text-green-800 ml-auto">
{incident.resolution}
</Badge>
</div>
<div className="flex justify-between mt-2 text-xs text-muted-foreground">
<div className="flex items-center">
<Clock className="h-3 w-3 mr-1" />
{incident.timeReceived} - {incident.timeClosed}
</div>
<div>Units: {incident.units.join(", ")}</div>
</div>
</div>
))}
</div>
)
}

View File

@ -0,0 +1,86 @@
import { Badge } from "@/app/_components/ui/badge"
import { Button } from "@/app/_components/ui/button"
import { MapPin, Clock, ArrowRight } from "lucide-react"
export default function PriorityQueue() {
const queuedCalls = [
{
id: "INC-4527",
type: "Noise Complaint",
location: "567 Elm St, Apt 12",
priority: "Low",
timeReceived: "10:28 AM",
waitTime: "5m 12s",
},
{
id: "INC-4528",
type: "Suspicious Person",
location: "Main Street Park",
priority: "Medium",
timeReceived: "10:25 AM",
waitTime: "8m 35s",
},
{
id: "INC-4529",
type: "Shoplifting",
location: "Downtown Mall, Store #5",
priority: "Medium",
timeReceived: "10:20 AM",
waitTime: "13m 40s",
},
]
return (
<div className="mt-4 space-y-3">
<div className="flex justify-between items-center">
<div className="text-sm font-medium">Pending Calls: {queuedCalls.length}</div>
<Badge variant="outline" className="bg-yellow-100 text-yellow-800">
Avg Wait: 9m 15s
</Badge>
</div>
{queuedCalls.map((call) => (
<div key={call.id} className="border rounded-lg p-3">
<div className="flex items-center gap-2">
<div
className={`w-2 h-2 rounded-full ${
call.priority === "Low" ? "bg-blue-500" : call.priority === "Medium" ? "bg-yellow-500" : "bg-red-500"
}`}
></div>
<h4 className="font-medium text-sm">{call.type}</h4>
<Badge
variant="outline"
className={
call.priority === "Low"
? "bg-blue-100 text-blue-800 ml-auto"
: call.priority === "Medium"
? "bg-yellow-100 text-yellow-800 ml-auto"
: "bg-red-100 text-red-800 ml-auto"
}
>
{call.priority}
</Badge>
</div>
<div className="flex flex-wrap gap-x-4 gap-y-1 mt-2 text-xs text-muted-foreground">
<div className="flex items-center">
<MapPin className="h-3 w-3 mr-1" />
{call.location}
</div>
<div className="flex items-center">
<Clock className="h-3 w-3 mr-1" />
Waiting: {call.waitTime}
</div>
</div>
<div className="flex justify-end mt-2">
<Button size="sm" className="flex items-center">
Dispatch
<ArrowRight className="ml-1 h-3 w-3" />
</Button>
</div>
</div>
))}
</div>
)
}

View File

@ -0,0 +1,75 @@
import { Badge } from "@/app/_components/ui/badge"
import { Progress } from "@/app/_components/ui/progress"
import { Car, Truck, Ambulance, Shield } from "lucide-react"
export default function ResourceAvailability() {
const resources = [
{
type: "Patrol Cars",
total: 24,
available: 12,
icon: Car,
},
{
type: "K-9 Units",
total: 4,
available: 2,
icon: Shield,
},
{
type: "Ambulances",
total: 8,
available: 5,
icon: Ambulance,
},
{
type: "SWAT Team",
total: 1,
available: 1,
icon: Truck,
},
]
return (
<div className="mt-4 space-y-3">
{resources.map((resource) => (
<div key={resource.type} className="border rounded-lg p-3">
<div className="flex items-center gap-2 mb-2">
<div className="w-8 h-8 rounded bg-muted flex items-center justify-center shrink-0">
<resource.icon className="h-4 w-4" />
</div>
<div>
<h4 className="font-medium text-sm">{resource.type}</h4>
</div>
<Badge
variant="outline"
className={
resource.available / resource.total > 0.5
? "bg-green-100 text-green-800 ml-auto"
: resource.available / resource.total > 0.25
? "bg-yellow-100 text-yellow-800 ml-auto"
: "bg-red-100 text-red-800 ml-auto"
}
>
{resource.available}/{resource.total} Available
</Badge>
</div>
<Progress
value={(resource.available / resource.total) * 100}
className="h-2"
indicatorClassName={
resource.available / resource.total > 0.5
? "bg-green-500"
: resource.available / resource.total > 0.25
? "bg-yellow-500"
: "bg-red-500"
}
/>
</div>
))}
</div>
)
}

View File

@ -0,0 +1,97 @@
"use client"
import { Progress } from "@/app/_components/ui/progress"
import { Clock } from "lucide-react"
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/app/_components/ui/chart"
import { Bar, BarChart, XAxis, YAxis, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts"
export default function ResponseTimes() {
// Sample data for the chart
const data = [
{ name: "Critical", time: 3.2 },
{ name: "High", time: 5.8 },
{ name: "Medium", time: 8.5 },
{ name: "Low", time: 12.3 },
]
return (
<div className="mt-4 space-y-4">
<div className="flex items-center justify-between">
<div>
<div className="text-2xl font-bold">4.2m</div>
<div className="text-xs text-muted-foreground">Average Response Time</div>
</div>
<div className="text-xs text-green-600 flex items-center">
<Clock className="h-3 w-3 mr-1" />
Target: 5.0m
</div>
</div>
<div className="space-y-2">
<div>
<div className="flex justify-between text-xs mb-1">
<span>Priority 1 (Critical)</span>
<div className="flex items-center">
<Clock className="h-3 w-3 mr-1" />
<span>3.2 min avg</span>
</div>
</div>
<Progress value={80} className="h-2" indicatorClassName="bg-red-500" />
</div>
<div>
<div className="flex justify-between text-xs mb-1">
<span>Priority 2 (High)</span>
<div className="flex items-center">
<Clock className="h-3 w-3 mr-1" />
<span>5.8 min avg</span>
</div>
</div>
<Progress value={70} className="h-2" indicatorClassName="bg-orange-500" />
</div>
<div>
<div className="flex justify-between text-xs mb-1">
<span>Priority 3 (Medium)</span>
<div className="flex items-center">
<Clock className="h-3 w-3 mr-1" />
<span>8.5 min avg</span>
</div>
</div>
<Progress value={60} className="h-2" indicatorClassName="bg-blue-500" />
</div>
</div>
<div className="h-[100px]">
<ChartContainer>
<ResponsiveContainer width="100%" height="100%">
<BarChart data={data} margin={{ top: 5, right: 5, left: 0, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis dataKey="name" className="text-xs" />
<YAxis className="text-xs" />
<Tooltip content={<CustomTooltip />} />
<Bar dataKey="time" fill="#3b82f6" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</ChartContainer>
</div>
</div>
)
}
function CustomTooltip({ active, payload, label }: any) {
if (active && payload && payload.length) {
return (
<ChartTooltip content={
<ChartTooltipContent>
<div className="font-medium">{label} Priority</div>
<div className="flex items-center gap-2 text-sm">
<span>Avg. Response Time:</span>
<span className="font-medium">{payload[0].value} minutes</span>
</div>
</ChartTooltipContent>
}>
</ChartTooltip>
)
}
return null
}

View File

@ -0,0 +1,80 @@
import { Badge } from "@/app/_components/ui/badge"
import { Calendar, Clock } from "lucide-react"
export default function ShiftSchedule() {
const currentShift = {
name: "Day Shift",
hours: "7:00 AM - 3:00 PM",
supervisor: "Sgt. Parker",
officers: 18,
status: "Active",
}
const upcomingShift = {
name: "Evening Shift",
hours: "3:00 PM - 11:00 PM",
supervisor: "Sgt. Rodriguez",
officers: 16,
status: "Upcoming",
}
return (
<div className="mt-4 space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center">
<Calendar className="h-4 w-4 mr-2" />
<span className="font-medium text-sm">Current Schedule</span>
</div>
<div className="text-xs text-muted-foreground">April 24, 2023</div>
</div>
<div className="border rounded-lg p-3">
<div className="flex justify-between items-center">
<h4 className="font-medium">{currentShift.name}</h4>
<Badge variant="outline" className="bg-green-100 text-green-800">
{currentShift.status}
</Badge>
</div>
<div className="mt-2 space-y-1 text-sm">
<div className="flex items-center">
<Clock className="h-3 w-3 mr-1 text-muted-foreground" />
<span>{currentShift.hours}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Supervisor:</span>
<span>{currentShift.supervisor}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Officers on duty:</span>
<span>{currentShift.officers}</span>
</div>
</div>
</div>
<div className="border rounded-lg p-3">
<div className="flex justify-between items-center">
<h4 className="font-medium">{upcomingShift.name}</h4>
<Badge variant="outline" className="bg-blue-100 text-blue-800">
{upcomingShift.status}
</Badge>
</div>
<div className="mt-2 space-y-1 text-sm">
<div className="flex items-center">
<Clock className="h-3 w-3 mr-1 text-muted-foreground" />
<span>{upcomingShift.hours}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Supervisor:</span>
<span>{upcomingShift.supervisor}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Officers scheduled:</span>
<span>{upcomingShift.officers}</span>
</div>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,65 @@
import { Badge } from "@/app/_components/ui/badge"
import { Car, User } from "lucide-react"
export default function UnitStatus() {
const units = [
{ id: "Unit 5", officer: "Parker", status: "On Scene", location: "789 Pine St" },
{ id: "Unit 8", officer: "Rodriguez", status: "On Scene", location: "I-95, Mile 42" },
{ id: "Unit 9", officer: "Johnson", status: "On Scene", location: "789 Pine St" },
{ id: "Unit 12", officer: "Chen", status: "Responding", location: "123 Main St" },
{ id: "Unit 15", officer: "Wilson", status: "Responding", location: "123 Main St" },
{ id: "Unit 17", officer: "Thompson", status: "Responding", location: "456 Oak Ave" },
]
return (
<div className="mt-4 space-y-3">
<div className="grid grid-cols-2 gap-2 text-center">
<div className="border rounded-lg p-2">
<div className="text-2xl font-bold">12</div>
<div className="text-xs text-muted-foreground">Available</div>
</div>
<div className="border rounded-lg p-2">
<div className="text-2xl font-bold">12</div>
<div className="text-xs text-muted-foreground">Assigned</div>
</div>
</div>
<div className="space-y-2">
{units.map((unit) => (
<div key={unit.id} className="flex items-center gap-2 p-2 border rounded-lg">
<div className="w-8 h-8 rounded-full bg-muted flex items-center justify-center shrink-0">
<Car className="h-4 w-4" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h4 className="font-medium text-sm">{unit.id}</h4>
<div className="text-xs text-muted-foreground flex items-center">
<User className="h-3 w-3 mr-1" />
{unit.officer}
</div>
</div>
<div className="text-xs text-muted-foreground truncate">{unit.location}</div>
</div>
<Badge
variant="outline"
className={
unit.status === "On Scene"
? "bg-green-100 text-green-800"
: unit.status === "Responding"
? "bg-yellow-100 text-yellow-800"
: "bg-blue-100 text-blue-800"
}
>
{unit.status}
</Badge>
</div>
))}
</div>
<button className="w-full text-sm text-primary mt-2 py-1">View All Units</button>
</div>
)
}

View File

@ -0,0 +1,101 @@
"use client"
import { BentoGrid, BentoGridItem } from "@/app/_components/ui/bento-grid"
import { MapPin, Phone, Users, Car, Clock, AlertTriangle, Radio, Calendar, MessageSquare } from "lucide-react"
import DispatchHeader from "./_components/dispatch-header"
import ActiveIncidents from "./_components/active-incidents"
import DispatchMap from "./_components/dispatch-map"
import UnitStatus from "./_components/unit-status"
import ResponseTimes from "./_components/response-times"
import PriorityQueue from "./_components/priority-queue"
import ResourceAvailability from "./_components/resource-availability"
import ShiftSchedule from "./_components/shift-schedule"
import DispatchCommunications from "./_components/dispatch-communications"
import IncidentHistory from "./_components/incident-history"
export default function ResourceDispatchPage() {
return (
<div className="container py-4 min-h-screen">
<div className="max-w-7xl mx-auto">
<DispatchHeader />
<BentoGrid>
<BentoGridItem
title="Active Incidents"
description="Currently responding calls"
icon={<AlertTriangle className="w-5 h-5" />}
colSpan="2"
>
<ActiveIncidents />
</BentoGridItem>
<BentoGridItem
title="Dispatch Map"
description="Real-time unit locations"
icon={<MapPin className="w-5 h-5" />}
rowSpan="2"
colSpan="2"
>
<DispatchMap />
</BentoGridItem>
<BentoGridItem
title="Unit Status"
description="Available and assigned units"
icon={<Users className="w-5 h-5" />}
>
<UnitStatus />
</BentoGridItem>
<BentoGridItem
title="Response Times"
description="Current performance metrics"
icon={<Clock className="w-5 h-5" />}
>
<ResponseTimes />
</BentoGridItem>
<BentoGridItem
title="Priority Queue"
description="Pending dispatch requests"
icon={<Phone className="w-5 h-5" />}
>
<PriorityQueue />
</BentoGridItem>
<BentoGridItem
title="Resource Availability"
description="Vehicles and specialized equipment"
icon={<Car className="w-5 h-5" />}
>
<ResourceAvailability />
</BentoGridItem>
<BentoGridItem
title="Shift Schedule"
description="Current and upcoming shifts"
icon={<Calendar className="w-5 h-5" />}
>
<ShiftSchedule />
</BentoGridItem>
<BentoGridItem
title="Dispatch Communications"
description="Recent radio traffic and messages"
icon={<Radio className="w-5 h-5" />}
>
<DispatchCommunications />
</BentoGridItem>
<BentoGridItem
title="Incident History"
description="Recently closed calls"
icon={<MessageSquare className="w-5 h-5" />}
>
<IncidentHistory />
</BentoGridItem>
</BentoGrid>
</div>
</div>
)
}

View File

@ -10,89 +10,102 @@ import { createClient } from "@/app/_utils/supabase/server"
import db from "@/prisma/db";
export async function signInPasswordless(formData: FormData) {
const instrumentationService = getInjection("IInstrumentationService")
return await instrumentationService.instrumentServerAction("signIn", {
recordResponse: true
const instrumentationService = getInjection('IInstrumentationService');
return await instrumentationService.instrumentServerAction(
'signIn',
{
recordResponse: true,
},
async () => {
async () => {
try {
const email = formData.get('email')?.toString();
try {
const email = formData.get("email")?.toString()
const signInPasswordlessController = getInjection(
'ISignInPasswordlessController'
);
return await signInPasswordlessController({ email });
const signInPasswordlessController = getInjection("ISignInPasswordlessController")
return await signInPasswordlessController({ email })
// if (email) {
// redirect(`/verify-otp?email=${encodeURIComponent(email)}`)
// }
} catch (err) {
if (err instanceof InputParseError) {
return { error: err.message };
}
// if (email) {
// redirect(`/verify-otp?email=${encodeURIComponent(email)}`)
// }
if (err instanceof AuthenticationError) {
return { error: 'Invalid credential. Please try again.' };
}
} catch (err) {
if (err instanceof InputParseError) {
return { error: err.message }
}
if (
err instanceof UnauthenticatedError ||
err instanceof NotFoundError
) {
return {
error: err.message,
};
}
if (err instanceof AuthenticationError) {
return { error: "Invalid credential. Please try again." }
}
const crashReporterService = getInjection('ICrashReporterService');
crashReporterService.report(err);
if (err instanceof UnauthenticatedError || err instanceof NotFoundError) {
return {
error: 'User not found. Please tell your admin to create an account for you.',
};
}
const crashReporterService = getInjection('ICrashReporterService');
crashReporterService.report(err);
return {
error:
'An error happened. The developers have been notified. Please try again later.',
};
}
})
return {
error:
'An error happened. The developers have been notified. Please try again later.',
};
}
}
);
}
export async function signInWithPassword(formData: FormData) {
const instrumentationService = getInjection("IInstrumentationService")
return await instrumentationService.instrumentServerAction("signInWithPassword", {
recordResponse: true
}, async () => {
try {
const email = formData.get("email")?.toString()
const password = formData.get("password")?.toString()
const instrumentationService = getInjection('IInstrumentationService');
return await instrumentationService.instrumentServerAction(
'signInWithPassword',
{
recordResponse: true,
},
async () => {
try {
const email = formData.get('email')?.toString();
const password = formData.get('password')?.toString();
console.log("woi:", email + " " + password)
// console.log("woi:", email + " " + password)
const signInWithPasswordController = getInjection("ISignInWithPasswordController")
await signInWithPasswordController({ email, password })
const signInWithPasswordController = getInjection(
'ISignInWithPasswordController'
);
await signInWithPasswordController({ email, password });
return { success: true }
} catch (err) {
if (err instanceof InputParseError) {
return { error: err.message }
}
if (err instanceof AuthenticationError) {
return { error: "Invalid credential. Please try again." }
}
if (err instanceof UnauthenticatedError || err instanceof NotFoundError) {
return {
error: 'User not found. Please tell your admin to create an account for you.',
};
}
const crashReporterService = getInjection('ICrashReporterService');
crashReporterService.report(err);
return {
error:
'An error happened. The developers have been notified. Please try again later.',
};
return { success: true };
} catch (err) {
if (err instanceof InputParseError) {
return { error: err.message };
}
})
if (err instanceof AuthenticationError) {
return { error: 'Invalid credential. Please try again.' };
}
if (
err instanceof UnauthenticatedError ||
err instanceof NotFoundError
) {
return {
error: err.message,
};
}
const crashReporterService = getInjection('ICrashReporterService');
crashReporterService.report(err);
return {
error:
'An error happened. The developers have been notified. Please try again later.',
};
}
}
);
}
// export async function signUp(formData: FormData) {
// const instrumentationService = getInjection("IInstrumentationService")

View File

@ -0,0 +1,364 @@
"use client"
import { cn } from "@/app/_lib/utils"
import * as React from "react"
import * as RechartsPrimitive from "recharts"
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: "", dark: ".dark" } as const
export type ChartConfig = {
[k in string]: {
label?: React.ReactNode
icon?: React.ComponentType
} & (
| { color?: string; theme?: never }
| { color?: never; theme: Record<keyof typeof THEMES, string> }
)
}
type ChartContextProps = {
config: ChartConfig
}
const ChartContext = React.createContext<ChartContextProps | null>(null)
function useChart() {
const context = React.useContext(ChartContext)
if (!context) {
throw new Error("useChart must be used within a <ChartContainer />")
}
return context
}
const ChartContainer = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
config?: ChartConfig // Jadikan opsional
children: React.ComponentProps<
typeof RechartsPrimitive.ResponsiveContainer
>["children"]
}
>(({ id, className, children, config = {}, ...props }, ref) => { // Nilai default adalah objek kosong
const uniqueId = React.useId()
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
return (
<ChartContext.Provider value={{ config }}>
<div
data-chart={chartId}
ref={ref}
className={cn(
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
className
)}
{...props}
>
<ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer>
{children}
</RechartsPrimitive.ResponsiveContainer>
</div>
</ChartContext.Provider>
)
})
ChartContainer.displayName = "Chart"
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
const colorConfig = Object.entries(config).filter(
([, config]) => config.theme || config.color
)
if (!colorConfig.length) {
return null
}
return (
<style
dangerouslySetInnerHTML={{
__html: Object.entries(THEMES)
.map(
([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
.map(([key, itemConfig]) => {
const color =
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
itemConfig.color
return color ? ` --color-${key}: ${color};` : null
})
.join("\n")}
}
`
)
.join("\n"),
}}
/>
)
}
const ChartTooltip = RechartsPrimitive.Tooltip
const ChartTooltipContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
React.ComponentProps<"div"> & {
hideLabel?: boolean
hideIndicator?: boolean
indicator?: "line" | "dot" | "dashed"
nameKey?: string
labelKey?: string
}
>(
(
{
active,
payload,
className,
indicator = "dot",
hideLabel = false,
hideIndicator = false,
label,
labelFormatter,
labelClassName,
formatter,
color,
nameKey,
labelKey,
},
ref
) => {
const { config } = useChart()
const tooltipLabel = React.useMemo(() => {
if (hideLabel || !payload?.length) {
return null
}
const [item] = payload
const key = `${labelKey || item?.dataKey || item?.name || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const value =
!labelKey && typeof label === "string"
? config[label as keyof typeof config]?.label || label
: itemConfig?.label
if (labelFormatter) {
return (
<div className={cn("font-medium", labelClassName)}>
{labelFormatter(value, payload)}
</div>
)
}
if (!value) {
return null
}
return <div className={cn("font-medium", labelClassName)}>{value}</div>
}, [
label,
labelFormatter,
payload,
hideLabel,
labelClassName,
config,
labelKey,
])
if (!active || !payload?.length) {
return null
}
const nestLabel = payload.length === 1 && indicator !== "dot"
return (
<div
ref={ref}
className={cn(
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
className
)}
>
{!nestLabel ? tooltipLabel : null}
<div className="grid gap-1.5">
{payload.map((item, index) => {
const key = `${nameKey || item.name || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const indicatorColor = color || item.payload.fill || item.color
return (
<div
key={item.dataKey}
className={cn(
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
indicator === "dot" && "items-center"
)}
>
{formatter && item?.value !== undefined && item.name ? (
formatter(item.value, item.name, item, index, item.payload)
) : (
<>
{itemConfig?.icon ? (
<itemConfig.icon />
) : (
!hideIndicator && (
<div
className={cn(
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
{
"h-2.5 w-2.5": indicator === "dot",
"w-1": indicator === "line",
"w-0 border-[1.5px] border-dashed bg-transparent":
indicator === "dashed",
"my-0.5": nestLabel && indicator === "dashed",
}
)}
style={
{
"--color-bg": indicatorColor,
"--color-border": indicatorColor,
} as React.CSSProperties
}
/>
)
)}
<div
className={cn(
"flex flex-1 justify-between leading-none",
nestLabel ? "items-end" : "items-center"
)}
>
<div className="grid gap-1.5">
{nestLabel ? tooltipLabel : null}
<span className="text-muted-foreground">
{itemConfig?.label || item.name}
</span>
</div>
{item.value && (
<span className="font-mono font-medium tabular-nums text-foreground">
{item.value.toLocaleString()}
</span>
)}
</div>
</>
)}
</div>
)
})}
</div>
</div>
)
}
)
ChartTooltipContent.displayName = "ChartTooltip"
const ChartLegend = RechartsPrimitive.Legend
const ChartLegendContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> &
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean
nameKey?: string
}
>(
(
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
ref
) => {
const { config } = useChart()
if (!payload?.length) {
return null
}
return (
<div
ref={ref}
className={cn(
"flex items-center justify-center gap-4",
verticalAlign === "top" ? "pb-3" : "pt-3",
className
)}
>
{payload.map((item) => {
const key = `${nameKey || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
return (
<div
key={item.value}
className={cn(
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
)}
>
{itemConfig?.icon && !hideIcon ? (
<itemConfig.icon />
) : (
<div
className="h-2 w-2 shrink-0 rounded-[2px]"
style={{
backgroundColor: item.color,
}}
/>
)}
{itemConfig?.label}
</div>
)
})}
</div>
)
}
)
ChartLegendContent.displayName = "ChartLegend"
// Helper to extract item config from a payload.
function getPayloadConfigFromPayload(
config: ChartConfig,
payload: unknown,
key: string
) {
if (typeof payload !== "object" || payload === null) {
return undefined
}
const payloadPayload =
"payload" in payload &&
typeof payload.payload === "object" &&
payload.payload !== null
? payload.payload
: undefined
let configLabelKey: string = key
if (
key in payload &&
typeof payload[key as keyof typeof payload] === "string"
) {
configLabelKey = payload[key as keyof typeof payload] as string
} else if (
payloadPayload &&
key in payloadPayload &&
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
) {
configLabelKey = payloadPayload[
key as keyof typeof payloadPayload
] as string
}
return configLabelKey in config
? config[configLabelKey]
: config[key as keyof typeof config]
}
export {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
ChartStyle,
}

View File

@ -90,7 +90,6 @@
--tertiary: 0 0% 12%; /* #1F1F1F1 */
--tertiary-foreground: 0 0% 85%; /* #d9d9d9 */
--tertiary-border: 0 0% 20%; /* #333333 */
/* Muted: abu-abu gelap untuk teks pendukung */
--muted: 0 0% 20%; /* #333333 */
@ -131,10 +130,25 @@
}
@layer base {
:root {
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
.dark {
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
}

View File

@ -10,10 +10,10 @@ import db from '../../prisma/db';
import { v4 as uuidv4 } from 'uuid';
import * as crypto from 'crypto';
// Maintain a registry of used IDs to prevent duplicates
// Used to track generated IDs
const usedIdRegistry = new Set<string>();
// Type definition for the global counter
// Add type definition for global counter
declare global {
var __idCounter: number;
}
@ -359,6 +359,21 @@ export const createRoute = (
return `${baseRoute}?${queryString}`;
};
// Format date helper function
function formatDateV2(date: Date, formatStr: string): string {
const pad = (num: number) => String(num).padStart(2, '0');
return formatStr
.replace('yyyy', String(date.getFullYear()))
.replace('MM', pad(date.getMonth() + 1))
.replace('dd', pad(date.getDate()))
.replace('HH', pad(date.getHours()))
.replace('mm', pad(date.getMinutes()))
.replace('ss', pad(date.getSeconds()));
}
/**
* Universal Custom ID Generator
* Creates structured, readable IDs for any system or entity
@ -377,23 +392,19 @@ export const createRoute = (
* @param {boolean} options.upperCase - Convert result to uppercase
* @returns {string} - Generated custom ID
*/
/**
* Generate a unique ID with multiple options to reduce collision risk
*/
export function generateId(
options: {
prefix?: string;
segments?: {
codes?: string[];
year?: number;
year?: number | boolean; // Year diubah menjadi number | boolean
sequentialDigits?: number;
includeDate?: boolean;
dateFormat?: string;
includeTime?: boolean;
includeMilliseconds?: boolean;
};
format?: string;
format?: string | null;
separator?: string;
upperCase?: boolean;
randomSequence?: boolean;
@ -402,13 +413,18 @@ export function generateId(
maxRetries?: number;
} = {}
): string {
// Default options
// Jika uniquenessStrategy tidak diatur dan randomSequence = false,
// gunakan counter sebagai strategi default
if (!options.uniquenessStrategy && options.randomSequence === false) {
options.uniquenessStrategy = 'counter';
}
const config = {
prefix: options.prefix || 'ID',
segments: {
codes: options.segments?.codes || [],
year: options.segments?.year,
sequentialDigits: options.segments?.sequentialDigits || 6, // Increased to 6
year: options.segments?.year, // Akan diproses secara kondisional nanti
sequentialDigits: options.segments?.sequentialDigits || 6,
includeDate: options.segments?.includeDate ?? false,
dateFormat: options.segments?.dateFormat || 'yyyyMMdd',
includeTime: options.segments?.includeTime ?? false,
@ -423,108 +439,124 @@ export function generateId(
maxRetries: options.maxRetries || 10,
};
// Static counter for sequential IDs (module-level)
// Initialize global counter if not exists
if (typeof globalThis.__idCounter === 'undefined') {
globalThis.__idCounter = 0;
}
// Get current date and time with high precision
const now = new Date();
// Format date based on selected format
// Generate date string if needed
let dateString = '';
if (config.segments.includeDate) {
dateString = format(now, config.segments.dateFormat);
}
// Format time if included (with higher precision)
// Generate time string if needed
let timeString = '';
if (config.segments.includeTime) {
timeString = format(now, 'HHmmss');
// Add milliseconds for even more uniqueness
if (config.segments.includeMilliseconds) {
timeString += now.getMilliseconds().toString().padStart(3, '0');
}
}
// Generate sequence based on strategy
// Generate sequential number based on uniqueness strategy
let sequentialNum: string;
switch (config.uniquenessStrategy) {
case 'uuid':
// Use first part of UUID for high uniqueness
sequentialNum = uuidv4().split('-')[0];
break;
case 'timestamp':
// Use high-precision timestamp
sequentialNum = `${now.getTime()}${Math.floor(Math.random() * 1000)}`;
sequentialNum = sequentialNum.slice(-config.segments.sequentialDigits);
break;
case 'counter':
// Use an incrementing counter
sequentialNum = (++globalThis.__idCounter)
.toString()
.padStart(config.segments.sequentialDigits, '0');
break;
case 'hash':
// Create a hash from the current time and options
const hashSource = `${now.getTime()}-${JSON.stringify(options)}-${Math.random()}`;
const hash = crypto.createHash('sha256').update(hashSource).digest('hex');
sequentialNum = hash.substring(0, config.segments.sequentialDigits);
break;
default:
// Standard random sequence with improved randomness
if (config.randomSequence) {
const randomBytes = crypto.randomBytes(4);
const randomNum = parseInt(randomBytes.toString('hex'), 16);
sequentialNum = (
randomNum % Math.pow(10, config.segments.sequentialDigits)
)
.toString()
.padStart(config.segments.sequentialDigits, '0');
} else {
try {
switch (config.uniquenessStrategy) {
case 'uuid':
sequentialNum = uuidv4().split('-')[0];
break;
case 'timestamp':
sequentialNum = `${now.getTime()}${Math.floor(Math.random() * 1000)}`;
sequentialNum = sequentialNum.slice(-config.segments.sequentialDigits);
break;
case 'counter':
sequentialNum = (++globalThis.__idCounter)
.toString()
.padStart(config.segments.sequentialDigits, '0');
}
break;
case 'hash':
const hashSource = `${now.getTime()}-${JSON.stringify(options)}-${Math.random()}`;
const hash = crypto
.createHash('sha256')
.update(hashSource)
.digest('hex');
sequentialNum = hash.substring(0, config.segments.sequentialDigits);
break;
default:
if (config.randomSequence) {
const randomBytes = crypto.randomBytes(4);
const randomNum = parseInt(randomBytes.toString('hex'), 16);
sequentialNum = (
randomNum % Math.pow(10, config.segments.sequentialDigits)
)
.toString()
.padStart(config.segments.sequentialDigits, '0');
} else {
sequentialNum = (++globalThis.__idCounter)
.toString()
.padStart(config.segments.sequentialDigits, '0');
}
}
} catch (error) {
console.error('Error generating sequential number:', error);
// Fallback to timestamp strategy if other methods fail
sequentialNum = `${now.getTime()}`.slice(-config.segments.sequentialDigits);
}
// Prepare all components
// Determine if year should be included and what value to use
let yearValue = null;
if (config.segments.year !== undefined || config.segments.year != false) {
if (typeof config.segments.year === 'number') {
yearValue = String(config.segments.year);
} else if (config.segments.year === true) {
yearValue = format(now, 'yyyy');
}
// if year is false, yearValue remains null and won't be included
} else {
// Default behavior (backward compatibility)
yearValue = format(now, 'yyyy');
}
// Prepare components for ID assembly
const components = {
prefix: config.prefix,
codes: config.segments.codes.join(config.separator),
year: config.segments.year || format(now, 'yyyy'),
codes:
config.segments.codes.length > 0
? config.segments.codes.join(config.separator)
: '',
// year: yearValue,
sequence: sequentialNum,
date: dateString,
time: timeString,
};
// Build the ID based on custom format if provided
let result: string;
// Use custom format if provided
if (config.format) {
let customID = config.format;
for (const [key, value] of Object.entries(components)) {
if (value) {
const placeholder = `{${key}}`;
customID = customID.replace(placeholder, String(value));
customID = customID.replace(
new RegExp(placeholder, 'g'),
String(value)
);
}
}
// Clean up any unused placeholders
// Remove unused placeholders
customID = customID.replace(/{[^}]+}/g, '');
// Clean up any consecutive separators
// Clean up separators
const escapedSeparator = config.separator.replace(
/[-\/\\^$*+?.()|[\]{}]/g,
'\\$&'
);
const separatorRegex = new RegExp(`${escapedSeparator}+`, 'g');
customID = customID.replace(separatorRegex, config.separator);
// Remove leading/trailing separators
customID = customID.replace(
new RegExp(`^${escapedSeparator}|${escapedSeparator}$`, 'g'),
''
@ -532,30 +564,34 @@ export function generateId(
result = config.upperCase ? customID.toUpperCase() : customID;
} else {
// Default structured build if no format specified
// Assemble ID from parts
const parts = [];
if (components.prefix) parts.push(components.prefix);
if (components.codes) parts.push(components.codes);
if (components.year) parts.push(components.year);
if (components.sequence) parts.push(components.sequence);
// if (components.year) parts.push(components.year);
if (components.date) parts.push(components.date);
if (components.time) parts.push(components.time);
if (components.sequence) parts.push(components.sequence);
result = parts.join(config.separator);
if (config.upperCase) result = result.toUpperCase();
}
// Check for collisions and retry if necessary
// Handle collisions if required
if (config.retryOnCollision) {
let retryCount = 0;
let originalResult = result;
while (usedIdRegistry.has(result) && retryCount < config.maxRetries) {
retryCount++;
// Try adding a unique suffix
const suffix = crypto.randomBytes(2).toString('hex');
result = `${originalResult}${config.separator}${suffix}`;
try {
const suffix = crypto.randomBytes(2).toString('hex');
result = `${originalResult}${config.separator}${suffix}`;
} catch (error) {
console.error('Error generating collision suffix:', error);
// Simple fallback if crypto fails
result = `${originalResult}${config.separator}${Date.now().toString(36)}`;
}
}
if (retryCount >= config.maxRetries) {
@ -565,20 +601,16 @@ export function generateId(
}
}
// Register this ID to prevent future duplicates
// Register the ID and maintain registry size
usedIdRegistry.add(result);
// Periodically clean up the registry to prevent memory leaks (optional)
if (usedIdRegistry.size > 10000) {
// This is a simple implementation - in production you might want a more sophisticated strategy
const entriesToKeep = Array.from(usedIdRegistry).slice(-5000);
usedIdRegistry.clear();
entriesToKeep.forEach((id) => usedIdRegistry.add(id));
}
return result;
return result.trim();
}
/**
* Gets the last ID from a specified table and column.
* @param tableName - The name of the table to query.

View File

@ -22,37 +22,40 @@ export const updateSession = async (request: NextRequest) => {
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) =>
request.cookies.set(name, value),
request.cookies.set(name, value)
);
response = NextResponse.next({
request,
});
cookiesToSet.forEach(({ name, value, options }) =>
response.cookies.set(name, value, options),
response.cookies.set(name, value, options)
);
},
},
},
}
);
// This will refresh session if expired - required for Server Components
// https://supabase.com/docs/guides/auth/server-side/nextjs
const user = await supabase.auth.getUser();
if (request.nextUrl.pathname === "/" && user.error) {
return NextResponse.redirect(new URL("/sign-in", request.url));
// console.log('user', user);
console.log('api', process.env.NEXT_PUBLIC_SUPABASE_URL);
console.log('anon', process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY);
if (request.nextUrl.pathname === '/' && user.error) {
return NextResponse.redirect(new URL('/sign-in', request.url));
}
// protected routes
if (request.nextUrl.pathname.startsWith("/dashboard") && user.error) {
return NextResponse.redirect(new URL("/sign-in", request.url));
if (request.nextUrl.pathname.startsWith('/dashboard') && user.error) {
return NextResponse.redirect(new URL('/sign-in', request.url));
}
if (request.nextUrl.pathname === "/" && !user.error) {
return NextResponse.redirect(new URL("/dashboard", request.url));
if (request.nextUrl.pathname === '/' && !user.error) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
return response;
} catch (e) {
// If you are here, a Supabase client could not be created!

View File

@ -38,6 +38,8 @@
"autoprefixer": "10.4.20",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"csv-parse": "^5.6.0",
"csv-parser": "^3.2.0",
"date-fns": "^3.6.0",
"embla-carousel-react": "^8.5.2",
"input-otp": "^1.4.2",
@ -8278,6 +8280,24 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
"node_modules/csv-parse": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.6.0.tgz",
"integrity": "sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==",
"license": "MIT"
},
"node_modules/csv-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.2.0.tgz",
"integrity": "sha512-fgKbp+AJbn1h2dcAHKIdKNSSjfp43BZZykXsCjzALjKy80VXQNHPFJ6T9Afwdzoj24aMkq8GwDS7KGcDPpejrA==",
"license": "MIT",
"bin": {
"csv-parser": "bin/csv-parser"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/d3-array": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",

View File

@ -44,6 +44,8 @@
"autoprefixer": "10.4.20",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"csv-parse": "^5.6.0",
"csv-parser": "^3.2.0",
"date-fns": "^3.6.0",
"embla-carousel-react": "^8.5.2",
"input-otp": "^1.4.2",

View File

@ -1,270 +0,0 @@
export const crimeCategoriesData = [
{
name: "Terhadap Ketertiban Umum",
description: "Kejahatan yang mengganggu ketertiban umum seperti unjuk rasa ilegal atau kerusuhan."
},
{
name: "Membahayakan Kam Umum",
description: "Tindakan yang membahayakan keamanan umum, termasuk penggunaan bahan peledak secara ilegal."
},
{
name: "Pembakaran",
description: "Tindakan pembakaran yang disengaja terhadap properti atau bangunan."
},
{
name: "Kebakaran / Meletus",
description: "Kejadian kebakaran atau ledakan yang menimbulkan kerusakan atau korban."
},
{
name: "Member Suap",
description: "Memberikan suap kepada pejabat publik atau pihak lain untuk keuntungan pribadi."
},
{
name: "Sumpah Palsu",
description: "Memberikan keterangan tidak benar di bawah sumpah dalam proses hukum."
},
{
name: "Pemalsuan Materai",
description: "Pembuatan atau penggunaan materai palsu untuk dokumen resmi."
},
{
name: "Pemalsuan Surat",
description: "Pemalsuan dokumen atau surat dengan tujuan menipu."
},
{
name: "Perzinahan",
description: "Hubungan seksual antara orang yang salah satunya sudah terikat pernikahan dengan orang lain."
},
{
name: "Perkosaan",
description: "Pemaksaan hubungan seksual tanpa persetujuan korban."
},
{
name: "Perjudian",
description: "Kegiatan taruhan yang dilarang oleh hukum."
},
{
name: "Penghinaan",
description: "Tindakan menghina atau merendahkan martabat orang lain secara lisan atau tulisan."
},
{
name: "Penculikan",
description: "Pengambilan seseorang secara paksa atau tanpa izin untuk tujuan tertentu."
},
{
name: "Perbuatan Tidak Menyenangkan",
description: "Tindakan yang menyebabkan ketidaknyamanan atau ketakutan pada orang lain."
},
{
name: "Pembunuhan",
description: "Tindakan menghilangkan nyawa orang lain secara sengaja."
},
{
name: "Penganiayaan Ringan",
description: "Tindakan kekerasan fisik ringan yang tidak menyebabkan luka berat."
},
{
name: "Penganiayaan Berat",
description: "Kekerasan fisik yang menyebabkan luka berat pada korban."
},
{
name: "Kelalaian Akibatkan Orang Mati",
description: "Kelalaian yang menyebabkan kematian seseorang."
},
{
name: "Kelalaian Akibatkan Orang Luka",
description: "Kelalaian yang menyebabkan seseorang terluka."
},
{
name: "Pencurian Biasa",
description: "Pencurian yang dilakukan tanpa kekerasan atau perencanaan khusus."
},
{
name: "Curat",
description: "Pencurian dengan pemberatan seperti membobol rumah atau bangunan."
},
{
name: "Curingan",
description: "Pencurian ringan terhadap barang-barang bernilai kecil."
},
{
name: "Curas",
description: "Pencurian dengan kekerasan atau ancaman kekerasan."
},
{
name: "Curanmor",
description: "Pencurian kendaraan bermotor."
},
{
name: "Pengeroyokan",
description: "Tindakan kekerasan oleh beberapa orang terhadap satu atau lebih korban."
},
{
name: "Premanisme",
description: "Tindakan intimidasi atau kekerasan oleh kelompok preman."
},
{
name: "Pemerasan Dan Pengancaman",
description: "Memaksa orang lain menyerahkan sesuatu melalui ancaman."
},
{
name: "Penggelapan",
description: "Penguasaan barang milik orang lain yang dipercayakan, namun tidak dikembalikan."
},
{
name: "Penipuan",
description: "Tindakan menipu untuk mendapatkan keuntungan pribadi."
},
{
name: "Pengrusakan",
description: "Merusak barang milik orang lain secara sengaja."
},
{
name: "Kenakalan Remaja",
description: "Perilaku menyimpang dari norma oleh anak remaja seperti tawuran atau balap liar."
},
{
name: "Menerima Suap",
description: "Menerima imbalan untuk mempengaruhi keputusan atau tindakan."
},
{
name: "Penadahan",
description: "Membeli, menyimpan, atau menjual barang hasil kejahatan."
},
{
name: "Pekerjakan Anak",
description: "Mempekerjakan anak di bawah umur dalam pekerjaan yang dilarang oleh hukum."
},
{
name: "Agraria",
description: "Sengketa dan kejahatan terkait kepemilikan dan penggunaan lahan."
},
{
name: "Peradilan Anak",
description: "Proses hukum yang melibatkan anak sebagai pelaku tindak pidana."
},
{
name: "Perlindungan Anak",
description: "Upaya perlindungan anak dari kekerasan, eksploitasi, dan penelantaran."
},
{
name: "PKDRT",
description: "Tindak kekerasan dalam rumah tangga baik fisik maupun psikis."
},
{
name: "Perlindungan TKI",
description: "Perlindungan hukum terhadap Tenaga Kerja Indonesia di luar negeri."
},
{
name: "Perlindungan Saksi Korban",
description: "Perlindungan bagi saksi atau korban kejahatan dalam proses hukum."
},
{
name: "PTPPO",
description: "Perdagangan orang, termasuk eksploitasi tenaga kerja dan seksual."
},
{
name: "Pornografi",
description: "Produksi, distribusi, atau kepemilikan materi pornografi yang melanggar hukum."
},
{
name: "Sistem Peradilan Anak",
description: "Kerangka hukum dan institusi yang menangani kejahatan oleh anak."
},
{
name: "Penyelenggaraan Pemilu",
description: "Kejahatan yang berkaitan dengan pelaksanaan pemilihan umum."
},
{
name: "Pemerintah Daerah",
description: "Tindak pidana yang dilakukan atau melibatkan pejabat pemerintah daerah."
},
{
name: "Keimigrasian",
description: "Kejahatan yang berkaitan dengan dokumen atau proses imigrasi."
},
{
name: "Ekstradisi",
description: "Permintaan penyerahan pelaku kejahatan antar negara."
},
{
name: "Lahgun Senpi/Handak/Sajam",
description: "Penyalahgunaan senjata api, bahan peledak, atau senjata tajam."
},
{
name: "Pidum Lainnya",
description: "Tindak pidana umum lainnya yang tidak termasuk dalam kategori tertentu."
},
{
name: "Money Loudering",
description: "Pencucian uang hasil kejahatan agar tampak legal."
},
{
name: "Trafficking In Person",
description: "Perdagangan manusia untuk eksploitasi tenaga kerja atau seksual."
},
{
name: "Selundup Senpi",
description: "Penyelundupan senjata api secara ilegal."
},
{
name: "Trans Ekonomi Crime",
description: "Kejahatan ekonomi lintas negara atau lintas batas hukum nasional."
},
{
name: "Illegal Logging",
description: "Penebangan hutan secara ilegal tanpa izin resmi."
},
{
name: "Illegal Mining",
description: "Penambangan tanpa izin yang melanggar hukum."
},
{
name: "Illegal Fishing",
description: "Penangkapan ikan secara ilegal tanpa izin atau merusak lingkungan."
},
{
name: "BBM Illegal",
description: "Distribusi bahan bakar minyak tanpa izin atau bersubsidi secara ilegal."
},
{
name: "Niaga Pupuk",
description: "Penyalahgunaan distribusi atau niaga pupuk bersubsidi."
},
{
name: "ITE",
description: "Kejahatan yang dilakukan melalui sistem elektronik dan internet."
},
{
name: "Satwa",
description: "Tindak kejahatan terhadap satwa dilindungi dan perdagangan ilegal hewan."
},
{
name: "Upal",
description: "Pemalsuan dan peredaran uang palsu."
},
{
name: "Fidusia",
description: "Kejahatan terkait jaminan fidusia, seperti penggelapan barang fidusia."
},
{
name: "Perlindungan Konsumen",
description: "Pelanggaran hak konsumen atau penipuan dalam transaksi perdagangan."
},
{
name: "Pidter Lainnya",
description: "Tindak pidana tertentu lainnya yang tidak diklasifikasikan secara spesifik."
},
{
name: "Korupsi",
description: "Penyalahgunaan kekuasaan publik untuk keuntungan pribadi."
},
{
name: "Konflik Etnis",
description: "Pertikaian antar kelompok etnis yang memicu kekerasan atau kerusuhan."
},
{
name: "Separatisme",
description: "Gerakan pemisahan wilayah dari negara untuk membentuk pemerintahan sendiri."
}
]

View File

@ -0,0 +1,33 @@
Nama Unit,Alamat,Telepon,lat,long
Polres Jember,"Jl. R.A. Kartini No.17, Sawahan Cantian, Kepatihan, Kec. Patrang, Kabupaten Jember, Jawa Timur 68137",331-484285,-8.17092173103588,113.70548051820701
Polsek Kaliwates,"Jl. Hayam Wuruk No.153 Kelurahan Sempusari Kecamatan Kaliwates Kabupaten Jember, Jawa Timur",0331-484026,-8.186524646936217,113.67250084656763
Polsek Sumbersari,"Jl. MT Haryono, Sumbersari, Kabupaten Jember, Jawa Timur 68124",0331-330647,-8.183296304066134,113.74236702752034
Polsek Patrang,"Jl. Slamet Riyadi No.48, Patrang, Jember, Jawa Timur 68111",0331-422569,-8.149057988295857,113.72303569447361
Polsek Arjasa,"Jl. Supriadi No.101, Krajan Selatan, Patemon, Kec. Pakusari, Kabupaten Jember, Jawa Timur 68191",0331-540116,-8.122196807958975,113.74731612680222
Polsek Jelbuk,"Leces II, Sukojember, Kec. Jelbuk, Kabupaten Jember, Jawa Timur 68192",0331-540110,-8.068867091094502,113.76484762708661
Polsek Kalisat,"Jl. DR. Wahidin, Krajan II, Kalisat, Kec. Kalisat, Kabupaten Jember, Jawa Timur 68161",0331-591110,-8.128279710551354,113.81238843263388
Polsek Sukowono,"Jl. Chairil Anwar, Krajan, Cumedak, Jember, Kabupaten Jember, Jawa Timur 64194",0331-566210,-8.051079803763889,113.83506285032118
Polsek Sempolan,"Krajan, Sumberjati, Kec. Silo, Kabupaten Jember, Jawa Timur 68184",0331-521010,-8.186214080516393,113.87653656806364
Polsek Sumber Jambe,"Jl. PB. Sudirman No.81, Pasar, Sumberjambe, Kec. Sumberjambe, Kabupaten Jember, Jawa Timur 68195",0331-566268,-8.067557248871932,113.9001121479762
Polsek Ledokombo,"Jl. Bungur Ledokombo No.114, Pasar, Ledokombo, Kec. Ledokombo, Kabupaten Jember, Jawa Timur 68196",0331-591011,-8.135526826985,113.87370140934554
Polsek Pakusari,"Jl. Prambanan, Krajan, Kertosari, Kec. Pakusari, Kabupaten Jember, Jawa Timur 68181",0331-4436043,-8.164534108294927,113.76611131781641
Polsek Jenggawah,"Jl. Kawi No. 23 Jenggawah, Kab. Jember, Propinsi Jawa Timur 68171",0331-757330,-8.256791120691595,113.6522206707149
Polsek Mayang,"Jl. Banyuwangi, Mayang, Majang, Jawa Timur 68182",0331-591512,-8.177356153143444,113.79956203127406
Polsek Mumbulsari,"Jl. Budi Utomo No.16, Mumbulsari, Kabupaten Jember, Jawa Timur 68174",0331-793262,-8.252854089767183,113.74221255216453
Polsek Tempurejo,"Jl. KH. Abdurrahman, Tempurejo, Jember, Jawa Timur 68173",0331-757410,-8.300592195854021,113.68858041964509
Polsek Rambipuji,"Jl. Dharmawangsa 47 Rambipuji, Curahancar, Rambipuji, Kec. Rambipuji, Kabupaten Jember, Jawa Timur 68152",0331-711430,-8.20428708115188,113.61328234847777
Polsek Sukorambi,"Jl. Mujahir No. 5, Sukorambi, Jember Lor, Patrang, Jember, Jawa Timur 68118",0331-489523,-8.169930311064727,113.6605586889699
Polsek Panti,"Jalan Panglima Besar Sudirman 19 Desa Panti Kecamatan Panti, Jember Jawa Timur 68153",0331-711330,-8.17194959166762,113.62022456331853
Polsek Bangsalsari,"Jl. Jenderal Ahmad Yani No.16, Kalisatan, Bangsalsari, Kec. Bangsalsari, Kabupaten Jember, Jawa Timur 68154",0331-711401,-8.200901080105067,113.5338098619667
Polsek Balung,"Jl. Rambipuji-Balung, Jember, Jawa Timur 68152",0331-621210,-8.269718539539422,113.54065466193254
Polsek Ambulu,"Jl. Raya Suyitman, Ambulu, Jember, Jawa Timur 68172",0336-881007,-8.344079809223068,113.60731912704142
Polsek Wuluhan,"Jl. Ambulu, Wuluhan, Kabupaten Jember, Jawa Timur 68162",0336-881003,-8.338356022692308,113.55182787728441
Polsek Puger,"Jl. Achmad Yani 57 Puger, Umbulsari, Kabupaten Jember, Jawa Timur 68164",0336-721119,-8.366494602192043,113.47296865214837
Polsek Gumukmas,"Jl. Achmad Yani 89 Gumukmas, Gumukmas, Jember, Jawa Timur 68165",0336-321391,-8.315087051246818,113.41296867361133
Polsek Kencong,"Jl. Diponegoro, No. 35, Kencong, Jember 68167",0336-321210,-8.279794453467883,113.37664036194629
Polsek Tanggul,"Jl. Urip Sumoharjo N0.50 Tanggul 68155, Tanggul Wetan, Tanggul, Jawa Timur 68155",0336-441110,-8.16605008923455,113.46115044482765
Polsek Sumberbaru,"Jl. Panglima Besar Sudirman, No. 3, Sumberbaru, Jember, Jawa Timur 68173",0336-324210,-8.119255727431387,113.39383676380444
Polsek Semboro,"Jl. Telomoyo Dusun Semboro pasar, Desa Semboro, Jember, Jawa Timur 68157",0336-444200,-8.206242110979067,113.43480704846874
Polsek Umbulsari,"Jl. Ahmad Yani No.44, Umbulsari, Jember, Jawa Timur 68166",0336-321191,-8.263912399533561,113.44837139079566
Polsek Jombang,"Jl. KH. Dewantara No. 88 Jombang, Jember, Jawa Timur 68168",0336-321100,-8.245938093985266,113.32178564847065
Polsek Ajung,"Ajung Kulon, Ajung, Kec. Ajung, Kabupaten Jember, Jawa Timur 68175",,-8.215016859569301,113.66807277175127
1 Nama Unit Alamat Telepon lat long
2 Polres Jember Jl. R.A. Kartini No.17, Sawahan Cantian, Kepatihan, Kec. Patrang, Kabupaten Jember, Jawa Timur 68137 331-484285 -8.17092173103588 113.70548051820701
3 Polsek Kaliwates Jl. Hayam Wuruk No.153 Kelurahan Sempusari Kecamatan Kaliwates Kabupaten Jember, Jawa Timur 0331-484026 -8.186524646936217 113.67250084656763
4 Polsek Sumbersari Jl. MT Haryono, Sumbersari, Kabupaten Jember, Jawa Timur 68124 0331-330647 -8.183296304066134 113.74236702752034
5 Polsek Patrang Jl. Slamet Riyadi No.48, Patrang, Jember, Jawa Timur 68111 0331-422569 -8.149057988295857 113.72303569447361
6 Polsek Arjasa Jl. Supriadi No.101, Krajan Selatan, Patemon, Kec. Pakusari, Kabupaten Jember, Jawa Timur 68191 0331-540116 -8.122196807958975 113.74731612680222
7 Polsek Jelbuk Leces II, Sukojember, Kec. Jelbuk, Kabupaten Jember, Jawa Timur 68192 0331-540110 -8.068867091094502 113.76484762708661
8 Polsek Kalisat Jl. DR. Wahidin, Krajan II, Kalisat, Kec. Kalisat, Kabupaten Jember, Jawa Timur 68161 0331-591110 -8.128279710551354 113.81238843263388
9 Polsek Sukowono Jl. Chairil Anwar, Krajan, Cumedak, Jember, Kabupaten Jember, Jawa Timur 64194 0331-566210 -8.051079803763889 113.83506285032118
10 Polsek Sempolan Krajan, Sumberjati, Kec. Silo, Kabupaten Jember, Jawa Timur 68184 0331-521010 -8.186214080516393 113.87653656806364
11 Polsek Sumber Jambe Jl. PB. Sudirman No.81, Pasar, Sumberjambe, Kec. Sumberjambe, Kabupaten Jember, Jawa Timur 68195 0331-566268 -8.067557248871932 113.9001121479762
12 Polsek Ledokombo Jl. Bungur Ledokombo No.114, Pasar, Ledokombo, Kec. Ledokombo, Kabupaten Jember, Jawa Timur 68196 0331-591011 -8.135526826985 113.87370140934554
13 Polsek Pakusari Jl. Prambanan, Krajan, Kertosari, Kec. Pakusari, Kabupaten Jember, Jawa Timur 68181 0331-4436043 -8.164534108294927 113.76611131781641
14 Polsek Jenggawah Jl. Kawi No. 23 Jenggawah, Kab. Jember, Propinsi Jawa Timur 68171 0331-757330 -8.256791120691595 113.6522206707149
15 Polsek Mayang Jl. Banyuwangi, Mayang, Majang, Jawa Timur 68182 0331-591512 -8.177356153143444 113.79956203127406
16 Polsek Mumbulsari Jl. Budi Utomo No.16, Mumbulsari, Kabupaten Jember, Jawa Timur 68174 0331-793262 -8.252854089767183 113.74221255216453
17 Polsek Tempurejo Jl. KH. Abdurrahman, Tempurejo, Jember, Jawa Timur 68173 0331-757410 -8.300592195854021 113.68858041964509
18 Polsek Rambipuji Jl. Dharmawangsa 47 Rambipuji, Curahancar, Rambipuji, Kec. Rambipuji, Kabupaten Jember, Jawa Timur 68152 0331-711430 -8.20428708115188 113.61328234847777
19 Polsek Sukorambi Jl. Mujahir No. 5, Sukorambi, Jember Lor, Patrang, Jember, Jawa Timur 68118 0331-489523 -8.169930311064727 113.6605586889699
20 Polsek Panti Jalan Panglima Besar Sudirman 19 Desa Panti Kecamatan Panti, Jember Jawa Timur 68153 0331-711330 -8.17194959166762 113.62022456331853
21 Polsek Bangsalsari Jl. Jenderal Ahmad Yani No.16, Kalisatan, Bangsalsari, Kec. Bangsalsari, Kabupaten Jember, Jawa Timur 68154 0331-711401 -8.200901080105067 113.5338098619667
22 Polsek Balung Jl. Rambipuji-Balung, Jember, Jawa Timur 68152 0331-621210 -8.269718539539422 113.54065466193254
23 Polsek Ambulu Jl. Raya Suyitman, Ambulu, Jember, Jawa Timur 68172 0336-881007 -8.344079809223068 113.60731912704142
24 Polsek Wuluhan Jl. Ambulu, Wuluhan, Kabupaten Jember, Jawa Timur 68162 0336-881003 -8.338356022692308 113.55182787728441
25 Polsek Puger Jl. Achmad Yani 57 Puger, Umbulsari, Kabupaten Jember, Jawa Timur 68164 0336-721119 -8.366494602192043 113.47296865214837
26 Polsek Gumukmas Jl. Achmad Yani 89 Gumukmas, Gumukmas, Jember, Jawa Timur 68165 0336-321391 -8.315087051246818 113.41296867361133
27 Polsek Kencong Jl. Diponegoro, No. 35, Kencong, Jember 68167 0336-321210 -8.279794453467883 113.37664036194629
28 Polsek Tanggul Jl. Urip Sumoharjo N0.50 Tanggul 68155, Tanggul Wetan, Tanggul, Jawa Timur 68155 0336-441110 -8.16605008923455 113.46115044482765
29 Polsek Sumberbaru Jl. Panglima Besar Sudirman, No. 3, Sumberbaru, Jember, Jawa Timur 68173 0336-324210 -8.119255727431387 113.39383676380444
30 Polsek Semboro Jl. Telomoyo Dusun Semboro pasar, Desa Semboro, Jember, Jawa Timur 68157 0336-444200 -8.206242110979067 113.43480704846874
31 Polsek Umbulsari Jl. Ahmad Yani No.44, Umbulsari, Jember, Jawa Timur 68166 0336-321191 -8.263912399533561 113.44837139079566
32 Polsek Jombang Jl. KH. Dewantara No. 88 Jombang, Jember, Jawa Timur 68168 0336-321100 -8.245938093985266 113.32178564847065
33 Polsek Ajung Ajung Kulon, Ajung, Kec. Ajung, Kabupaten Jember, Jawa Timur 68175 -8.215016859569301 113.66807277175127

View File

@ -0,0 +1,34 @@
KESATUAN,JAN,,FEB,,MAR,,APR,,MEI,,JUN,,JUL,,AGT,,SEP,,OKT,,NOV,,DES,,JUMLAH,
,CT,CC,CT,CC,CT,CC,CT,CC,CT,CC,CT,CC,CT,CC,CT,CC,CT,CC,CT,CC,CT,CC,CT,CC,CT,CC
RESKRIM ,58,39,40,26,46,35,45,29,33,22,46,32,45,32,36,27,42,28,44,35,45,33,27,23,507,361
SEK ARJASA,3,3,1,1,5,4,3,1,4,2,2,1,1,0,3,3,4,3,2,1,0,0,1,1,29,20
SEK PAKUSARI,7,7,5,4,3,2,2,1,5,4,1,1,4,1,6,4,3,2,1,1,2,2,0,0,39,29
SEK KALISAT,2,2,3,2,4,3,8,4,7,3,5,1,12,4,14,9,14,11,6,3,7,4,2,1,84,47
SEK SUKOWONO,3,3,4,4,2,2,2,1,5,3,5,1,7,3,3,3,2,3,2,1,1,1,2,2,38,27
SEK LEDOKOMBO,1,1,2,1,2,2,1,0,1,0,0,0,2,1,2,1,5,2,3,2,2,1,1,1,22,12
SEK SUMBERJAMBE,7,7,4,4,4,4,7,4,7,6,3,1,4,3,6,4,3,3,4,4,5,5,1,1,55,46
SEK MAYANG,0,0,1,0,1,1,0,0,2,2,1,1,2,1,1,1,1,0,1,0,0,0,1,0,11,6
SEK MUMBULSARI,3,3,3,3,2,2,4,4,2,2,1,1,2,2,1,1,2,2,5,5,3,2,0,0,28,27
SEK TEMPUREJO,3,3,0,0,1,0,1,1,5,2,1,1,4,4,0,0,0,0,1,1,3,2,0,0,19,14
SEK SEMPOLAN,1,1,0,0,3,1,4,2,5,2,2,1,2,1,2,2,1,1,3,3,2,0,1,1,26,15
SEK RAMBIPUJI,4,4,6,5,6,3,8,7,8,6,4,3,4,4,12,8,16,9,9,1,15,5,4,2,96,57
SEK PANTI,3,1,1,1,3,3,3,1,9,5,4,3,7,3,4,2,5,4,9,2,4,2,4,3,56,30
SEK KALIWATES,3,3,2,1,10,7,9,7,6,4,5,3,5,3,3,2,2,1,2,1,8,5,1,0,56,37
SEK JENGGAWAH,3,3,6,5,4,4,9,8,4,4,2,2,7,7,6,5,4,4,2,2,4,4,4,3,55,51
SEK BALUNG,3,3,3,2,5,4,3,2,8,8,5,4,4,3,4,3,6,5,7,7,5,4,3,2,56,47
SEK AMBULU,1,1,1,0,1,1,3,3,2,2,4,3,6,3,3,2,4,3,0,0,5,3,1,1,31,22
SEK WULUHAN,1,1,4,3,4,3,5,5,4,4,7,6,3,3,4,3,4,2,4,3,6,6,2,2,48,41
SEK TANGGUL,4,2,4,3,4,2,7,3,5,4,4,3,7,5,4,3,7,7,8,7,6,4,1,0,61,43
SEK BANGSALSARI,4,4,4,4,6,5,3,2,5,5,3,3,5,4,2,2,4,4,3,3,2,2,0,0,41,38
SEK SUMBERBARU,7,5,4,2,3,1,4,1,3,1,5,3,6,6,4,3,3,3,6,5,6,5,0,0,51,35
SEK KENCONG,4,3,1,0,1,1,0,0,0,0,0,0,1,1,2,2,0,0,2,2,0,0,0,0,11,9
SEK GUMUKMAS,6,6,1,1,1,1,3,3,0,0,1,1,0,0,2,2,0,0,1,1,0,0,1,0,16,15
SEK UMBULSARI,1,1,2,2,1,1,4,4,8,6,1,1,2,3,1,0,3,2,2,0,1,1,2,2,28,23
SEK PUGER,4,4,6,6,4,4,5,5,2,2,5,5,4,4,0,0,3,3,7,7,2,2,1,1,43,43
SEK SUMBERSARI,3,3,2,2,4,4,4,4,3,2,5,3,3,1,3,0,4,2,5,5,1,1,1,1,38,28
SEK PATRANG,13,10,5,4,5,4,4,2,4,3,2,2,5,3,1,0,3,3,1,1,3,1,0,0,46,33
SEK JELBUK,6,5,1,1,3,3,3,2,2,2,2,1,3,4,2,1,1,1,4,5,3,3,2,1,32,29
SEK SUKORAMBI,0,0,1,1,1,0,4,4,2,2,1,1,2,2,1,1,0,0,0,0,2,2,0,0,14,13
SEK SEMBORO,3,3,2,2,2,1,3,3,2,2,1,1,4,4,3,3,3,3,3,3,5,4,3,3,34,32
SEK JOMBANG,0,0,3,3,1,1,1,1,1,1,0,0,3,2,1,0,4,4,1,0,0,0,1,2,16,14
SEK AJUNG,,,,,,,,,,,,,,,,,,,,,,,,,0,0
1 KESATUAN JAN FEB MAR APR MEI JUN JUL AGT SEP OKT NOV DES JUMLAH
2 CT CC CT CC CT CC CT CC CT CC CT CC CT CC CT CC CT CC CT CC CT CC CT CC CT CC
3 RESKRIM 58 39 40 26 46 35 45 29 33 22 46 32 45 32 36 27 42 28 44 35 45 33 27 23 507 361
4 SEK ARJASA 3 3 1 1 5 4 3 1 4 2 2 1 1 0 3 3 4 3 2 1 0 0 1 1 29 20
5 SEK PAKUSARI 7 7 5 4 3 2 2 1 5 4 1 1 4 1 6 4 3 2 1 1 2 2 0 0 39 29
6 SEK KALISAT 2 2 3 2 4 3 8 4 7 3 5 1 12 4 14 9 14 11 6 3 7 4 2 1 84 47
7 SEK SUKOWONO 3 3 4 4 2 2 2 1 5 3 5 1 7 3 3 3 2 3 2 1 1 1 2 2 38 27
8 SEK LEDOKOMBO 1 1 2 1 2 2 1 0 1 0 0 0 2 1 2 1 5 2 3 2 2 1 1 1 22 12
9 SEK SUMBERJAMBE 7 7 4 4 4 4 7 4 7 6 3 1 4 3 6 4 3 3 4 4 5 5 1 1 55 46
10 SEK MAYANG 0 0 1 0 1 1 0 0 2 2 1 1 2 1 1 1 1 0 1 0 0 0 1 0 11 6
11 SEK MUMBULSARI 3 3 3 3 2 2 4 4 2 2 1 1 2 2 1 1 2 2 5 5 3 2 0 0 28 27
12 SEK TEMPUREJO 3 3 0 0 1 0 1 1 5 2 1 1 4 4 0 0 0 0 1 1 3 2 0 0 19 14
13 SEK SEMPOLAN 1 1 0 0 3 1 4 2 5 2 2 1 2 1 2 2 1 1 3 3 2 0 1 1 26 15
14 SEK RAMBIPUJI 4 4 6 5 6 3 8 7 8 6 4 3 4 4 12 8 16 9 9 1 15 5 4 2 96 57
15 SEK PANTI 3 1 1 1 3 3 3 1 9 5 4 3 7 3 4 2 5 4 9 2 4 2 4 3 56 30
16 SEK KALIWATES 3 3 2 1 10 7 9 7 6 4 5 3 5 3 3 2 2 1 2 1 8 5 1 0 56 37
17 SEK JENGGAWAH 3 3 6 5 4 4 9 8 4 4 2 2 7 7 6 5 4 4 2 2 4 4 4 3 55 51
18 SEK BALUNG 3 3 3 2 5 4 3 2 8 8 5 4 4 3 4 3 6 5 7 7 5 4 3 2 56 47
19 SEK AMBULU 1 1 1 0 1 1 3 3 2 2 4 3 6 3 3 2 4 3 0 0 5 3 1 1 31 22
20 SEK WULUHAN 1 1 4 3 4 3 5 5 4 4 7 6 3 3 4 3 4 2 4 3 6 6 2 2 48 41
21 SEK TANGGUL 4 2 4 3 4 2 7 3 5 4 4 3 7 5 4 3 7 7 8 7 6 4 1 0 61 43
22 SEK BANGSALSARI 4 4 4 4 6 5 3 2 5 5 3 3 5 4 2 2 4 4 3 3 2 2 0 0 41 38
23 SEK SUMBERBARU 7 5 4 2 3 1 4 1 3 1 5 3 6 6 4 3 3 3 6 5 6 5 0 0 51 35
24 SEK KENCONG 4 3 1 0 1 1 0 0 0 0 0 0 1 1 2 2 0 0 2 2 0 0 0 0 11 9
25 SEK GUMUKMAS 6 6 1 1 1 1 3 3 0 0 1 1 0 0 2 2 0 0 1 1 0 0 1 0 16 15
26 SEK UMBULSARI 1 1 2 2 1 1 4 4 8 6 1 1 2 3 1 0 3 2 2 0 1 1 2 2 28 23
27 SEK PUGER 4 4 6 6 4 4 5 5 2 2 5 5 4 4 0 0 3 3 7 7 2 2 1 1 43 43
28 SEK SUMBERSARI 3 3 2 2 4 4 4 4 3 2 5 3 3 1 3 0 4 2 5 5 1 1 1 1 38 28
29 SEK PATRANG 13 10 5 4 5 4 4 2 4 3 2 2 5 3 1 0 3 3 1 1 3 1 0 0 46 33
30 SEK JELBUK 6 5 1 1 3 3 3 2 2 2 2 1 3 4 2 1 1 1 4 5 3 3 2 1 32 29
31 SEK SUKORAMBI 0 0 1 1 1 0 4 4 2 2 1 1 2 2 1 1 0 0 0 0 2 2 0 0 14 13
32 SEK SEMBORO 3 3 2 2 2 1 3 3 2 2 1 1 4 4 3 3 3 3 3 3 5 4 3 3 34 32
33 SEK JOMBANG 0 0 3 3 1 1 1 1 1 1 0 0 3 2 1 0 4 4 1 0 0 0 1 2 16 14
34 SEK AJUNG 0 0

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,156 @@
district_id,district_name,number_of_crime,level,score,method,year
350901,Jombang,16,low,86,kmeans,2020
350901,Jombang,32,low,71,kmeans,2021
350901,Jombang,23,low,79,kmeans,2022
350901,Jombang,26,low,77,kmeans,2023
350901,Jombang,21,low,81,kmeans,2024
350902,Kencong,11,low,90,kmeans,2020
350902,Kencong,18,low,84,kmeans,2021
350902,Kencong,17,low,85,kmeans,2022
350902,Kencong,21,low,81,kmeans,2023
350902,Kencong,24,low,78,kmeans,2024
350903,Sumberbaru,51,high,54,kmeans,2020
350903,Sumberbaru,37,high,67,kmeans,2021
350903,Sumberbaru,23,high,79,kmeans,2022
350903,Sumberbaru,23,high,79,kmeans,2023
350903,Sumberbaru,23,high,79,kmeans,2024
350904,Gumukmas,16,low,86,kmeans,2020
350904,Gumukmas,27,low,76,kmeans,2021
350904,Gumukmas,18,low,84,kmeans,2022
350904,Gumukmas,10,low,91,kmeans,2023
350904,Gumukmas,20,low,82,kmeans,2024
350905,Umbulsari,28,high,75,kmeans,2020
350905,Umbulsari,26,low,77,kmeans,2021
350905,Umbulsari,24,low,78,kmeans,2022
350905,Umbulsari,21,low,81,kmeans,2023
350905,Umbulsari,16,low,86,kmeans,2024
350906,Tanggul,61,high,45,kmeans,2020
350906,Tanggul,58,high,47,kmeans,2021
350906,Tanggul,61,high,45,kmeans,2022
350906,Tanggul,55,high,50,kmeans,2023
350906,Tanggul,31,high,72,kmeans,2024
350907,Semboro,34,low,69,kmeans,2020
350907,Semboro,23,low,79,kmeans,2021
350907,Semboro,19,low,83,kmeans,2022
350907,Semboro,9,low,92,kmeans,2023
350907,Semboro,9,low,92,kmeans,2024
350908,Puger,43,high,61,kmeans,2020
350908,Puger,57,high,48,kmeans,2021
350908,Puger,33,high,70,kmeans,2022
350908,Puger,26,high,77,kmeans,2023
350908,Puger,21,high,81,kmeans,2024
350909,Bangsalsari,41,high,63,kmeans,2020
350909,Bangsalsari,41,high,63,kmeans,2021
350909,Bangsalsari,34,high,69,kmeans,2022
350909,Bangsalsari,13,low,89,kmeans,2023
350909,Bangsalsari,25,high,78,kmeans,2024
350910,Balung,56,high,49,kmeans,2020
350910,Balung,74,high,33,kmeans,2021
350910,Balung,54,high,51,kmeans,2022
350910,Balung,55,high,50,kmeans,2023
350910,Balung,39,high,65,kmeans,2024
350911,Wuluhan,48,high,56,kmeans,2020
350911,Wuluhan,70,high,36,kmeans,2021
350911,Wuluhan,45,high,59,kmeans,2022
350911,Wuluhan,26,high,77,kmeans,2023
350911,Wuluhan,27,high,76,kmeans,2024
350912,Ambulu,31,high,72,kmeans,2020
350912,Ambulu,39,high,65,kmeans,2021
350912,Ambulu,26,high,77,kmeans,2022
350912,Ambulu,31,high,72,kmeans,2023
350912,Ambulu,30,high,73,kmeans,2024
350913,Rambipuji,96,high,12,kmeans,2020
350913,Rambipuji,109,high,0,kmeans,2021
350913,Rambipuji,32,low,71,kmeans,2022
350913,Rambipuji,20,low,82,kmeans,2023
350913,Rambipuji,21,low,81,kmeans,2024
350914,Panti,56,high,49,kmeans,2020
350914,Panti,28,low,75,kmeans,2021
350914,Panti,22,low,80,kmeans,2022
350914,Panti,24,low,78,kmeans,2023
350914,Panti,9,low,92,kmeans,2024
350915,Sukorambi,14,low,88,kmeans,2020
350915,Sukorambi,27,low,76,kmeans,2021
350915,Sukorambi,14,low,88,kmeans,2022
350915,Sukorambi,18,low,84,kmeans,2023
350915,Sukorambi,4,low,97,kmeans,2024
350916,Jenggawah,55,high,50,kmeans,2020
350916,Jenggawah,51,high,54,kmeans,2021
350916,Jenggawah,34,low,69,kmeans,2022
350916,Jenggawah,57,high,48,kmeans,2023
350916,Jenggawah,38,high,66,kmeans,2024
350917,Ajung,0,low,100,kmeans,2020
350917,Ajung,16,low,86,kmeans,2021
350917,Ajung,23,low,79,kmeans,2022
350917,Ajung,24,low,78,kmeans,2023
350917,Ajung,25,low,78,kmeans,2024
350918,Tempurejo,19,low,83,kmeans,2020
350918,Tempurejo,14,low,88,kmeans,2021
350918,Tempurejo,18,low,84,kmeans,2022
350918,Tempurejo,10,low,91,kmeans,2023
350918,Tempurejo,13,low,89,kmeans,2024
350919,Kaliwates,56,medium,49,kmeans,2020
350919,Kaliwates,63,medium,43,kmeans,2021
350919,Kaliwates,36,medium,67,kmeans,2022
350919,Kaliwates,23,medium,79,kmeans,2023
350919,Kaliwates,16,medium,86,kmeans,2024
350920,Patrang,46,medium,58,kmeans,2020
350920,Patrang,88,medium,20,kmeans,2021
350920,Patrang,42,medium,62,kmeans,2022
350920,Patrang,16,medium,86,kmeans,2023
350920,Patrang,10,medium,91,kmeans,2024
350921,Sumbersari,38,high,66,kmeans,2020
350921,Sumbersari,52,medium,53,kmeans,2021
350921,Sumbersari,59,medium,46,kmeans,2022
350921,Sumbersari,35,medium,68,kmeans,2023
350921,Sumbersari,33,medium,70,kmeans,2024
350922,Arjasa,29,low,74,kmeans,2020
350922,Arjasa,47,low,57,kmeans,2021
350922,Arjasa,19,low,83,kmeans,2022
350922,Arjasa,10,low,91,kmeans,2023
350922,Arjasa,11,low,90,kmeans,2024
350923,Mumbulsari,28,high,75,kmeans,2020
350923,Mumbulsari,27,low,76,kmeans,2021
350923,Mumbulsari,23,low,79,kmeans,2022
350923,Mumbulsari,11,low,90,kmeans,2023
350923,Mumbulsari,10,low,91,kmeans,2024
350924,Pakusari,39,low,65,kmeans,2020
350924,Pakusari,52,low,53,kmeans,2021
350924,Pakusari,34,low,69,kmeans,2022
350924,Pakusari,12,low,89,kmeans,2023
350924,Pakusari,15,low,87,kmeans,2024
350925,Jelbuk,32,low,71,kmeans,2020
350925,Jelbuk,55,low,50,kmeans,2021
350925,Jelbuk,16,low,86,kmeans,2022
350925,Jelbuk,16,low,86,kmeans,2023
350925,Jelbuk,13,low,89,kmeans,2024
350926,Mayang,11,low,90,kmeans,2020
350926,Mayang,35,low,68,kmeans,2021
350926,Mayang,20,low,82,kmeans,2022
350926,Mayang,13,low,89,kmeans,2023
350926,Mayang,10,low,91,kmeans,2024
350927,Kalisat,84,high,23,kmeans,2020
350927,Kalisat,95,high,13,kmeans,2021
350927,Kalisat,68,high,38,kmeans,2022
350927,Kalisat,13,low,89,kmeans,2023
350927,Kalisat,10,low,91,kmeans,2024
350928,Ledokombo,22,low,80,kmeans,2020
350928,Ledokombo,45,low,59,kmeans,2021
350928,Ledokombo,17,low,85,kmeans,2022
350928,Ledokombo,13,low,89,kmeans,2023
350928,Ledokombo,6,low,95,kmeans,2024
350929,Sukowono,38,high,66,kmeans,2020
350929,Sukowono,46,low,58,kmeans,2021
350929,Sukowono,34,low,69,kmeans,2022
350929,Sukowono,25,low,78,kmeans,2023
350929,Sukowono,28,low,75,kmeans,2024
350930,Silo,26,high,77,kmeans,2020
350930,Silo,39,high,65,kmeans,2021
350930,Silo,28,high,75,kmeans,2022
350930,Silo,29,high,74,kmeans,2023
350930,Silo,21,low,81,kmeans,2024
350931,Sumberjambe,55,high,50,kmeans,2020
350931,Sumberjambe,20,low,82,kmeans,2021
350931,Sumberjambe,12,low,89,kmeans,2022
350931,Sumberjambe,16,low,86,kmeans,2023
350931,Sumberjambe,6,low,95,kmeans,2024
1 district_id district_name number_of_crime level score method year
2 350901 Jombang 16 low 86 kmeans 2020
3 350901 Jombang 32 low 71 kmeans 2021
4 350901 Jombang 23 low 79 kmeans 2022
5 350901 Jombang 26 low 77 kmeans 2023
6 350901 Jombang 21 low 81 kmeans 2024
7 350902 Kencong 11 low 90 kmeans 2020
8 350902 Kencong 18 low 84 kmeans 2021
9 350902 Kencong 17 low 85 kmeans 2022
10 350902 Kencong 21 low 81 kmeans 2023
11 350902 Kencong 24 low 78 kmeans 2024
12 350903 Sumberbaru 51 high 54 kmeans 2020
13 350903 Sumberbaru 37 high 67 kmeans 2021
14 350903 Sumberbaru 23 high 79 kmeans 2022
15 350903 Sumberbaru 23 high 79 kmeans 2023
16 350903 Sumberbaru 23 high 79 kmeans 2024
17 350904 Gumukmas 16 low 86 kmeans 2020
18 350904 Gumukmas 27 low 76 kmeans 2021
19 350904 Gumukmas 18 low 84 kmeans 2022
20 350904 Gumukmas 10 low 91 kmeans 2023
21 350904 Gumukmas 20 low 82 kmeans 2024
22 350905 Umbulsari 28 high 75 kmeans 2020
23 350905 Umbulsari 26 low 77 kmeans 2021
24 350905 Umbulsari 24 low 78 kmeans 2022
25 350905 Umbulsari 21 low 81 kmeans 2023
26 350905 Umbulsari 16 low 86 kmeans 2024
27 350906 Tanggul 61 high 45 kmeans 2020
28 350906 Tanggul 58 high 47 kmeans 2021
29 350906 Tanggul 61 high 45 kmeans 2022
30 350906 Tanggul 55 high 50 kmeans 2023
31 350906 Tanggul 31 high 72 kmeans 2024
32 350907 Semboro 34 low 69 kmeans 2020
33 350907 Semboro 23 low 79 kmeans 2021
34 350907 Semboro 19 low 83 kmeans 2022
35 350907 Semboro 9 low 92 kmeans 2023
36 350907 Semboro 9 low 92 kmeans 2024
37 350908 Puger 43 high 61 kmeans 2020
38 350908 Puger 57 high 48 kmeans 2021
39 350908 Puger 33 high 70 kmeans 2022
40 350908 Puger 26 high 77 kmeans 2023
41 350908 Puger 21 high 81 kmeans 2024
42 350909 Bangsalsari 41 high 63 kmeans 2020
43 350909 Bangsalsari 41 high 63 kmeans 2021
44 350909 Bangsalsari 34 high 69 kmeans 2022
45 350909 Bangsalsari 13 low 89 kmeans 2023
46 350909 Bangsalsari 25 high 78 kmeans 2024
47 350910 Balung 56 high 49 kmeans 2020
48 350910 Balung 74 high 33 kmeans 2021
49 350910 Balung 54 high 51 kmeans 2022
50 350910 Balung 55 high 50 kmeans 2023
51 350910 Balung 39 high 65 kmeans 2024
52 350911 Wuluhan 48 high 56 kmeans 2020
53 350911 Wuluhan 70 high 36 kmeans 2021
54 350911 Wuluhan 45 high 59 kmeans 2022
55 350911 Wuluhan 26 high 77 kmeans 2023
56 350911 Wuluhan 27 high 76 kmeans 2024
57 350912 Ambulu 31 high 72 kmeans 2020
58 350912 Ambulu 39 high 65 kmeans 2021
59 350912 Ambulu 26 high 77 kmeans 2022
60 350912 Ambulu 31 high 72 kmeans 2023
61 350912 Ambulu 30 high 73 kmeans 2024
62 350913 Rambipuji 96 high 12 kmeans 2020
63 350913 Rambipuji 109 high 0 kmeans 2021
64 350913 Rambipuji 32 low 71 kmeans 2022
65 350913 Rambipuji 20 low 82 kmeans 2023
66 350913 Rambipuji 21 low 81 kmeans 2024
67 350914 Panti 56 high 49 kmeans 2020
68 350914 Panti 28 low 75 kmeans 2021
69 350914 Panti 22 low 80 kmeans 2022
70 350914 Panti 24 low 78 kmeans 2023
71 350914 Panti 9 low 92 kmeans 2024
72 350915 Sukorambi 14 low 88 kmeans 2020
73 350915 Sukorambi 27 low 76 kmeans 2021
74 350915 Sukorambi 14 low 88 kmeans 2022
75 350915 Sukorambi 18 low 84 kmeans 2023
76 350915 Sukorambi 4 low 97 kmeans 2024
77 350916 Jenggawah 55 high 50 kmeans 2020
78 350916 Jenggawah 51 high 54 kmeans 2021
79 350916 Jenggawah 34 low 69 kmeans 2022
80 350916 Jenggawah 57 high 48 kmeans 2023
81 350916 Jenggawah 38 high 66 kmeans 2024
82 350917 Ajung 0 low 100 kmeans 2020
83 350917 Ajung 16 low 86 kmeans 2021
84 350917 Ajung 23 low 79 kmeans 2022
85 350917 Ajung 24 low 78 kmeans 2023
86 350917 Ajung 25 low 78 kmeans 2024
87 350918 Tempurejo 19 low 83 kmeans 2020
88 350918 Tempurejo 14 low 88 kmeans 2021
89 350918 Tempurejo 18 low 84 kmeans 2022
90 350918 Tempurejo 10 low 91 kmeans 2023
91 350918 Tempurejo 13 low 89 kmeans 2024
92 350919 Kaliwates 56 medium 49 kmeans 2020
93 350919 Kaliwates 63 medium 43 kmeans 2021
94 350919 Kaliwates 36 medium 67 kmeans 2022
95 350919 Kaliwates 23 medium 79 kmeans 2023
96 350919 Kaliwates 16 medium 86 kmeans 2024
97 350920 Patrang 46 medium 58 kmeans 2020
98 350920 Patrang 88 medium 20 kmeans 2021
99 350920 Patrang 42 medium 62 kmeans 2022
100 350920 Patrang 16 medium 86 kmeans 2023
101 350920 Patrang 10 medium 91 kmeans 2024
102 350921 Sumbersari 38 high 66 kmeans 2020
103 350921 Sumbersari 52 medium 53 kmeans 2021
104 350921 Sumbersari 59 medium 46 kmeans 2022
105 350921 Sumbersari 35 medium 68 kmeans 2023
106 350921 Sumbersari 33 medium 70 kmeans 2024
107 350922 Arjasa 29 low 74 kmeans 2020
108 350922 Arjasa 47 low 57 kmeans 2021
109 350922 Arjasa 19 low 83 kmeans 2022
110 350922 Arjasa 10 low 91 kmeans 2023
111 350922 Arjasa 11 low 90 kmeans 2024
112 350923 Mumbulsari 28 high 75 kmeans 2020
113 350923 Mumbulsari 27 low 76 kmeans 2021
114 350923 Mumbulsari 23 low 79 kmeans 2022
115 350923 Mumbulsari 11 low 90 kmeans 2023
116 350923 Mumbulsari 10 low 91 kmeans 2024
117 350924 Pakusari 39 low 65 kmeans 2020
118 350924 Pakusari 52 low 53 kmeans 2021
119 350924 Pakusari 34 low 69 kmeans 2022
120 350924 Pakusari 12 low 89 kmeans 2023
121 350924 Pakusari 15 low 87 kmeans 2024
122 350925 Jelbuk 32 low 71 kmeans 2020
123 350925 Jelbuk 55 low 50 kmeans 2021
124 350925 Jelbuk 16 low 86 kmeans 2022
125 350925 Jelbuk 16 low 86 kmeans 2023
126 350925 Jelbuk 13 low 89 kmeans 2024
127 350926 Mayang 11 low 90 kmeans 2020
128 350926 Mayang 35 low 68 kmeans 2021
129 350926 Mayang 20 low 82 kmeans 2022
130 350926 Mayang 13 low 89 kmeans 2023
131 350926 Mayang 10 low 91 kmeans 2024
132 350927 Kalisat 84 high 23 kmeans 2020
133 350927 Kalisat 95 high 13 kmeans 2021
134 350927 Kalisat 68 high 38 kmeans 2022
135 350927 Kalisat 13 low 89 kmeans 2023
136 350927 Kalisat 10 low 91 kmeans 2024
137 350928 Ledokombo 22 low 80 kmeans 2020
138 350928 Ledokombo 45 low 59 kmeans 2021
139 350928 Ledokombo 17 low 85 kmeans 2022
140 350928 Ledokombo 13 low 89 kmeans 2023
141 350928 Ledokombo 6 low 95 kmeans 2024
142 350929 Sukowono 38 high 66 kmeans 2020
143 350929 Sukowono 46 low 58 kmeans 2021
144 350929 Sukowono 34 low 69 kmeans 2022
145 350929 Sukowono 25 low 78 kmeans 2023
146 350929 Sukowono 28 low 75 kmeans 2024
147 350930 Silo 26 high 77 kmeans 2020
148 350930 Silo 39 high 65 kmeans 2021
149 350930 Silo 28 high 75 kmeans 2022
150 350930 Silo 29 high 74 kmeans 2023
151 350930 Silo 21 low 81 kmeans 2024
152 350931 Sumberjambe 55 high 50 kmeans 2020
153 350931 Sumberjambe 20 low 82 kmeans 2021
154 350931 Sumberjambe 12 low 89 kmeans 2022
155 350931 Sumberjambe 16 low 86 kmeans 2023
156 350931 Sumberjambe 6 low 95 kmeans 2024

View File

@ -0,0 +1,311 @@
export const crimeCategoriesData = [
{
name: 'Terhadap Ketertiban Umum',
description:
'Kejahatan yang mengganggu ketertiban umum seperti unjuk rasa ilegal atau kerusuhan.',
},
{
name: 'Membahayakan Kam Umum',
description:
'Tindakan yang membahayakan keamanan umum, termasuk penggunaan bahan peledak secara ilegal.',
},
{
name: 'Pembakaran',
description:
'Tindakan pembakaran yang disengaja terhadap properti atau bangunan.',
},
{
name: 'Kebakaran / Meletus',
description:
'Kejadian kebakaran atau ledakan yang menimbulkan kerusakan atau korban.',
},
{
name: 'Member Suap',
description:
'Memberikan suap kepada pejabat publik atau pihak lain untuk keuntungan pribadi.',
},
{
name: 'Sumpah Palsu',
description:
'Memberikan keterangan tidak benar di bawah sumpah dalam proses hukum.',
},
{
name: 'Pemalsuan Materai',
description: 'Pembuatan atau penggunaan materai palsu untuk dokumen resmi.',
},
{
name: 'Pemalsuan Surat',
description: 'Pemalsuan dokumen atau surat dengan tujuan menipu.',
},
{
name: 'Perzinahan',
description:
'Hubungan seksual antara orang yang salah satunya sudah terikat pernikahan dengan orang lain.',
},
{
name: 'Perkosaan',
description: 'Pemaksaan hubungan seksual tanpa persetujuan korban.',
},
{
name: 'Perjudian',
description: 'Kegiatan taruhan yang dilarang oleh hukum.',
},
{
name: 'Penghinaan',
description:
'Tindakan menghina atau merendahkan martabat orang lain secara lisan atau tulisan.',
},
{
name: 'Penculikan',
description:
'Pengambilan seseorang secara paksa atau tanpa izin untuk tujuan tertentu.',
},
{
name: 'Perbuatan Tidak Menyenangkan',
description:
'Tindakan yang menyebabkan ketidaknyamanan atau ketakutan pada orang lain.',
},
{
name: 'Pembunuhan',
description: 'Tindakan menghilangkan nyawa orang lain secara sengaja.',
},
{
name: 'Penganiayaan Ringan',
description:
'Tindakan kekerasan fisik ringan yang tidak menyebabkan luka berat.',
},
{
name: 'Penganiayaan Berat',
description: 'Kekerasan fisik yang menyebabkan luka berat pada korban.',
},
{
name: 'Kelalaian Akibatkan Orang Mati',
description: 'Kelalaian yang menyebabkan kematian seseorang.',
},
{
name: 'Kelalaian Akibatkan Orang Luka',
description: 'Kelalaian yang menyebabkan seseorang terluka.',
},
{
name: 'Pencurian Biasa',
description:
'Pencurian yang dilakukan tanpa kekerasan atau perencanaan khusus.',
},
{
name: 'Curat',
description:
'Pencurian dengan pemberatan seperti membobol rumah atau bangunan.',
},
{
name: 'Curingan',
description: 'Pencurian ringan terhadap barang-barang bernilai kecil.',
},
{
name: 'Curas',
description: 'Pencurian dengan kekerasan atau ancaman kekerasan.',
},
{
name: 'Curanmor',
description: 'Pencurian kendaraan bermotor.',
},
{
name: 'Pengeroyokan',
description:
'Tindakan kekerasan oleh beberapa orang terhadap satu atau lebih korban.',
},
{
name: 'Premanisme',
description: 'Tindakan intimidasi atau kekerasan oleh kelompok preman.',
},
{
name: 'Pemerasan Dan Pengancaman',
description: 'Memaksa orang lain menyerahkan sesuatu melalui ancaman.',
},
{
name: 'Penggelapan',
description:
'Penguasaan barang milik orang lain yang dipercayakan, namun tidak dikembalikan.',
},
{
name: 'Penipuan',
description: 'Tindakan menipu untuk mendapatkan keuntungan pribadi.',
},
{
name: 'Pengrusakan',
description: 'Merusak barang milik orang lain secara sengaja.',
},
{
name: 'Kenakalan Remaja',
description:
'Perilaku menyimpang dari norma oleh anak remaja seperti tawuran atau balap liar.',
},
{
name: 'Menerima Suap',
description: 'Menerima imbalan untuk mempengaruhi keputusan atau tindakan.',
},
{
name: 'Penadahan',
description: 'Membeli, menyimpan, atau menjual barang hasil kejahatan.',
},
{
name: 'Pekerjakan Anak',
description:
'Mempekerjakan anak di bawah umur dalam pekerjaan yang dilarang oleh hukum.',
},
{
name: 'Agraria',
description:
'Sengketa dan kejahatan terkait kepemilikan dan penggunaan lahan.',
},
{
name: 'Peradilan Anak',
description:
'Proses hukum yang melibatkan anak sebagai pelaku tindak pidana.',
},
{
name: 'Perlindungan Anak',
description:
'Upaya perlindungan anak dari kekerasan, eksploitasi, dan penelantaran.',
},
{
name: 'PKDRT',
description:
'Tindak kekerasan dalam rumah tangga baik fisik maupun psikis.',
},
{
name: 'Perlindungan TKI',
description:
'Perlindungan hukum terhadap Tenaga Kerja Indonesia di luar negeri.',
},
{
name: 'Perlindungan Saksi Korban',
description:
'Perlindungan bagi saksi atau korban kejahatan dalam proses hukum.',
},
{
name: 'PTPPO',
description:
'Perdagangan orang, termasuk eksploitasi tenaga kerja dan seksual.',
},
{
name: 'Pornografi',
description:
'Produksi, distribusi, atau kepemilikan materi pornografi yang melanggar hukum.',
},
{
name: 'Sistem Peradilan Anak',
description:
'Kerangka hukum dan institusi yang menangani kejahatan oleh anak.',
},
{
name: 'Penyelenggaraan Pemilu',
description: 'Kejahatan yang berkaitan dengan pelaksanaan pemilihan umum.',
},
{
name: 'Pemerintah Daerah',
description:
'Tindak pidana yang dilakukan atau melibatkan pejabat pemerintah daerah.',
},
{
name: 'Keimigrasian',
description:
'Kejahatan yang berkaitan dengan dokumen atau proses imigrasi.',
},
{
name: 'Ekstradisi',
description: 'Permintaan penyerahan pelaku kejahatan antar negara.',
},
{
name: 'Lahgun Senpi/Handak/Sajam',
description:
'Penyalahgunaan senjata api, bahan peledak, atau senjata tajam.',
},
{
name: 'Pidum Lainnya',
description:
'Tindak pidana umum lainnya yang tidak termasuk dalam kategori tertentu.',
},
{
name: 'Money Loudering',
description: 'Pencucian uang hasil kejahatan agar tampak legal.',
},
{
name: 'Trafficking In Person',
description:
'Perdagangan manusia untuk eksploitasi tenaga kerja atau seksual.',
},
{
name: 'Selundup Senpi',
description: 'Penyelundupan senjata api secara ilegal.',
},
{
name: 'Trans Ekonomi Crime',
description:
'Kejahatan ekonomi lintas negara atau lintas batas hukum nasional.',
},
{
name: 'Illegal Logging',
description: 'Penebangan hutan secara ilegal tanpa izin resmi.',
},
{
name: 'Illegal Mining',
description: 'Penambangan tanpa izin yang melanggar hukum.',
},
{
name: 'Illegal Fishing',
description:
'Penangkapan ikan secara ilegal tanpa izin atau merusak lingkungan.',
},
{
name: 'BBM Illegal',
description:
'Distribusi bahan bakar minyak tanpa izin atau bersubsidi secara ilegal.',
},
{
name: 'Niaga Pupuk',
description: 'Penyalahgunaan distribusi atau niaga pupuk bersubsidi.',
},
{
name: 'ITE',
description:
'Kejahatan yang dilakukan melalui sistem elektronik dan internet.',
},
{
name: 'Satwa',
description:
'Tindak kejahatan terhadap satwa dilindungi dan perdagangan ilegal hewan.',
},
{
name: 'Upal',
description: 'Pemalsuan dan peredaran uang palsu.',
},
{
name: 'Fidusia',
description:
'Kejahatan terkait jaminan fidusia, seperti penggelapan barang fidusia.',
},
{
name: 'Perlindungan Konsumen',
description:
'Pelanggaran hak konsumen atau penipuan dalam transaksi perdagangan.',
},
{
name: 'Pidter Lainnya',
description:
'Tindak pidana tertentu lainnya yang tidak diklasifikasikan secara spesifik.',
},
{
name: 'Korupsi',
description: 'Penyalahgunaan kekuasaan publik untuk keuntungan pribadi.',
},
{
name: 'Konflik Etnis',
description:
'Pertikaian antar kelompok etnis yang memicu kekerasan atau kerusuhan.',
},
{
name: 'Separatisme',
description:
'Gerakan pemisahan wilayah dari negara untuk membentuk pemerintahan sendiri.',
},
];

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
[
{
"year": 2020,
"crime_total": 1721,
"crime_cleared": 1419,
"clearance_rate": 82.45
},
{
"year": 2021,
"crime_total": 1975,
"crime_cleared": 1725,
"clearance_rate": 87.34
},
{
"year": 2022,
"crime_total": 2928,
"crime_cleared": 2646,
"clearance_rate": 90.37
},
{
"year": 2023,
"crime_total": 2302,
"crime_cleared": 1988,
"clearance_rate": 86.36
},
{
"year": 2024,
"crime_total": 2366,
"crime_cleared": 1962,
"clearance_rate": 82.92
}
]

Some files were not shown because too many files have changed in this diff Show More