235 lines
11 KiB
TypeScript
235 lines
11 KiB
TypeScript
"use client"
|
|
|
|
import React, { useState } from "react"
|
|
import { AlertTriangle, BarChart, ChevronRight, MapPin, Skull, Shield, FileText } from "lucide-react"
|
|
import { Separator } from "@/app/_components/ui/separator"
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/app/_components/ui/card"
|
|
import { cn } from "@/app/_lib/utils"
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/app/_components/ui/tabs"
|
|
|
|
interface CrimeSidebarProps {
|
|
className?: string
|
|
defaultCollapsed?: boolean
|
|
}
|
|
|
|
export default function CrimeSidebar({ className, defaultCollapsed = true }: CrimeSidebarProps) {
|
|
const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed)
|
|
const [activeTab, setActiveTab] = useState("incidents")
|
|
|
|
return (
|
|
<div className={cn(
|
|
"absolute top-0 left-0 h-full z-10 transition-all duration-300 ease-in-out bg-background backdrop-blur-sm border-r border-white/10 ",
|
|
isCollapsed ? "translate-x-[-100%]" : "translate-x-0",
|
|
className
|
|
)}>
|
|
<div className="relative h-full flex items-stretch">
|
|
{/* Main Sidebar Content */}
|
|
<div className="bg-background backdrop-blur-sm border-r border-white/10 h-full w-[320px]">
|
|
<div className="p-4 text-white h-full flex flex-col">
|
|
<CardHeader className="p-0 pb-2">
|
|
<CardTitle className="text-xl font-semibold flex items-center gap-2">
|
|
<AlertTriangle className="h-5 w-5" />
|
|
Crime Analysis
|
|
</CardTitle>
|
|
</CardHeader>
|
|
|
|
<Tabs defaultValue="incidents" className="w-full" value={activeTab} onValueChange={setActiveTab}>
|
|
<TabsList className="w-full mb-2 bg-black/30">
|
|
<TabsTrigger value="incidents" className="flex-1">Incidents</TabsTrigger>
|
|
<TabsTrigger value="statistics" className="flex-1">Statistics</TabsTrigger>
|
|
<TabsTrigger value="reports" className="flex-1">Reports</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<div className="flex-1 overflow-y-auto overflow-x-hidden pr-1 max-h-[calc(100vh-10rem)]">
|
|
<TabsContent value="incidents" className="m-0 p-0">
|
|
<SidebarSection title="Recent Incidents" icon={<AlertTriangle className="h-4 w-4 text-red-400" />}>
|
|
<div className="space-y-2">
|
|
{[1, 2, 3, 4, 5, 6].map((i) => (
|
|
<IncidentCard key={i} />
|
|
))}
|
|
</div>
|
|
</SidebarSection>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="statistics" className="m-0 p-0">
|
|
<SidebarSection title="Crime Overview" icon={<BarChart className="h-4 w-4 text-blue-400" />}>
|
|
<div className="space-y-2">
|
|
<StatCard title="Total Incidents" value="254" change="+12%" />
|
|
<StatCard title="Hot Zones" value="6" change="+2" />
|
|
<StatCard title="Case Clearance" value="68%" change="+5%" isPositive />
|
|
</div>
|
|
</SidebarSection>
|
|
|
|
<Separator className="bg-white/20 my-4" />
|
|
|
|
<SidebarSection title="Most Reported" icon={<Skull className="h-4 w-4 text-amber-400" />}>
|
|
<div className="space-y-2">
|
|
<CrimeTypeCard type="Theft" count={42} percentage={23} />
|
|
<CrimeTypeCard type="Assault" count={28} percentage={15} />
|
|
<CrimeTypeCard type="Vandalism" count={19} percentage={10} />
|
|
<CrimeTypeCard type="Burglary" count={15} percentage={8} />
|
|
</div>
|
|
</SidebarSection>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="reports" className="m-0 p-0">
|
|
<SidebarSection title="Recent Reports" icon={<FileText className="h-4 w-4 text-indigo-400" />}>
|
|
<div className="space-y-2">
|
|
<ReportCard
|
|
title="Monthly Crime Summary"
|
|
date="June 15, 2024"
|
|
author="Dept. Analysis Team"
|
|
/>
|
|
<ReportCard
|
|
title="High Risk Areas Analysis"
|
|
date="June 12, 2024"
|
|
author="Regional Coordinator"
|
|
/>
|
|
<ReportCard
|
|
title="Case Resolution Statistics"
|
|
date="June 10, 2024"
|
|
author="Investigation Unit"
|
|
/>
|
|
<ReportCard
|
|
title="Quarterly Report Q2 2024"
|
|
date="June 1, 2024"
|
|
author="Crime Analysis Department"
|
|
/>
|
|
</div>
|
|
</SidebarSection>
|
|
</TabsContent>
|
|
</div>
|
|
</Tabs>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Toggle Button - always visible and positioned correctly */}
|
|
<button
|
|
onClick={() => setIsCollapsed(!isCollapsed)}
|
|
className={cn(
|
|
"absolute h-12 w-8 bg-background backdrop-blur-sm border-t border-b border-r border-white/10 flex items-center justify-center",
|
|
"top-1/2 -translate-y-1/2 transition-all duration-300 ease-in-out",
|
|
isCollapsed ? "-right-8 rounded-r-md" : "left-[320px] rounded-r-md",
|
|
)}
|
|
aria-label={isCollapsed ? "Expand sidebar" : "Collapse sidebar"}
|
|
>
|
|
<ChevronRight
|
|
className={cn("h-5 w-5 text-white/80 transition-transform",
|
|
!isCollapsed && "rotate-180")}
|
|
/>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Helper components for sidebar content
|
|
|
|
interface SidebarSectionProps {
|
|
title: string
|
|
children: React.ReactNode
|
|
icon?: React.ReactNode
|
|
}
|
|
|
|
function SidebarSection({ title, children, icon }: SidebarSectionProps) {
|
|
return (
|
|
<div>
|
|
<h3 className="text-sm font-medium text-white/80 mb-2 flex items-center gap-1.5">
|
|
{icon}
|
|
{title}
|
|
</h3>
|
|
{children}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function IncidentCard() {
|
|
return (
|
|
<Card className="bg-white/10 border-0 text-white shadow-none">
|
|
<CardContent className="p-3 text-xs">
|
|
<div className="flex items-start gap-2">
|
|
<AlertTriangle className="h-4 w-4 text-red-400 shrink-0 mt-0.5" />
|
|
<div>
|
|
<p className="font-medium">Theft reported at Jalan Srikandi</p>
|
|
<div className="flex items-center gap-2 mt-1 text-white/60">
|
|
<MapPin className="h-3 w-3" />
|
|
<span>Jombang District</span>
|
|
</div>
|
|
<div className="mt-1 text-white/60">3 hours ago</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
interface StatCardProps {
|
|
title: string
|
|
value: string
|
|
change: string
|
|
isPositive?: boolean
|
|
}
|
|
|
|
function StatCard({ title, value, change, isPositive = false }: StatCardProps) {
|
|
return (
|
|
<Card className="bg-white/10 border-0 text-white shadow-none">
|
|
<CardContent className="p-3">
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-xs text-white/70">{title}</span>
|
|
<span className={`text-xs ${isPositive ? "text-green-400" : "text-red-400"}`}>{change}</span>
|
|
</div>
|
|
<div className="text-xl font-bold mt-1">{value}</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
interface CrimeTypeCardProps {
|
|
type: string
|
|
count: number
|
|
percentage: number
|
|
}
|
|
|
|
function CrimeTypeCard({ type, count, percentage }: CrimeTypeCardProps) {
|
|
return (
|
|
<Card className="bg-white/10 border-0 text-white shadow-none">
|
|
<CardContent className="p-3">
|
|
<div className="flex justify-between items-center">
|
|
<span className="font-medium">{type}</span>
|
|
<span className="text-sm text-white/70">{count} cases</span>
|
|
</div>
|
|
<div className="mt-2 h-1.5 bg-white/20 rounded-full overflow-hidden">
|
|
<div className="bg-blue-500 h-full rounded-full" style={{ width: `${percentage}%` }}></div>
|
|
</div>
|
|
<div className="mt-1 text-xs text-white/70 text-right">{percentage}%</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
interface ReportCardProps {
|
|
title: string
|
|
date: string
|
|
author: string
|
|
}
|
|
|
|
function ReportCard({ title, date, author }: ReportCardProps) {
|
|
return (
|
|
<Card className="bg-white/10 border-0 text-white shadow-none">
|
|
<CardContent className="p-3 text-xs">
|
|
<div className="flex items-start gap-2">
|
|
<FileText className="h-4 w-4 text-indigo-400 shrink-0 mt-0.5" />
|
|
<div>
|
|
<p className="font-medium">{title}</p>
|
|
<div className="flex items-center gap-2 mt-1 text-white/60">
|
|
<Shield className="h-3 w-3" />
|
|
<span>{author}</span>
|
|
</div>
|
|
<div className="mt-1 text-white/60">{date}</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|