141 lines
7.1 KiB
TypeScript
141 lines
7.1 KiB
TypeScript
import React from 'react'
|
|
import { Activity, Calendar, CheckCircle, AlertTriangle, LineChart, PieChart, FileText } from 'lucide-react'
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/app/_components/ui/card"
|
|
import { Separator } from "@/app/_components/ui/separator"
|
|
import { cn } from "@/app/_lib/utils"
|
|
import { getMonthName } from "@/app/_utils/common"
|
|
import { SidebarSection } from "../components/sidebar-section"
|
|
import { StatCard } from "../components/stat-card"
|
|
import { CrimeTypeCard, ICrimeTypeCardProps } from "../components/crime-type-card"
|
|
import { ICrimeAnalytics } from '@/app/(pages)/(admin)/dashboard/crime-management/crime-overview/_hooks/use-crime-analytics'
|
|
import { MONTHS } from '@/app/_utils/const/common'
|
|
|
|
|
|
interface ISidebarStatisticsTabProps {
|
|
crimeStats: ICrimeAnalytics
|
|
selectedMonth?: number | "all"
|
|
selectedYear: number
|
|
}
|
|
|
|
export function SidebarStatisticsTab({
|
|
crimeStats,
|
|
selectedMonth = "all",
|
|
selectedYear
|
|
}: ISidebarStatisticsTabProps) {
|
|
const topCategories = crimeStats.categoryCounts ?
|
|
Object.entries(crimeStats.categoryCounts)
|
|
.sort((a, b) => b[1] - a[1])
|
|
.slice(0, 4)
|
|
.map(([type, count]) => {
|
|
const percentage = Math.round(((count) / crimeStats.totalIncidents) * 100) || 0
|
|
return { type, count, percentage }
|
|
}) : []
|
|
|
|
return (
|
|
<>
|
|
<Card className="bg-gradient-to-r from-sidebar-primary/30 to-sidebar-primary/20 border border-sidebar-primary/20 overflow-hidden">
|
|
<CardHeader className="p-3 pb-0">
|
|
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
<LineChart className="h-4 w-4 text-green-400" />
|
|
Monthly Incidents
|
|
</CardTitle>
|
|
<CardDescription className="text-xs text-white/60">{selectedYear}</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="p-3">
|
|
<div className="h-32 flex items-end gap-1 mt-2">
|
|
{crimeStats.incidentsByMonth.map((count: number, i: number) => {
|
|
const maxCount = Math.max(...crimeStats.incidentsByMonth)
|
|
const height = maxCount > 0 ? (count / maxCount) * 100 : 0
|
|
|
|
return (
|
|
<div
|
|
key={i}
|
|
className={cn(
|
|
"bg-gradient-to-t from-emerald-600 to-green-400 w-full rounded-t-md",
|
|
selectedMonth !== 'all' && i + 1 === Number(selectedMonth) ? "from-amber-500 to-amber-400" : ""
|
|
)}
|
|
style={{
|
|
height: `${Math.max(5, height)}%`,
|
|
opacity: 0.7 + (i / 24)
|
|
}}
|
|
title={`${getMonthName(i + 1)}: ${count} incidents`}
|
|
/>
|
|
)
|
|
})}
|
|
</div>
|
|
<div className="flex justify-between mt-2 text-[10px] text-white/60">
|
|
{MONTHS.map((month, i) => (
|
|
<span key={i} className={cn("w-1/12 text-center", selectedMonth !== 'all' && i + 1 === Number(selectedMonth) ? "text-amber-400" : "")}>
|
|
{month.substring(0, 3)}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<SidebarSection title="Crime Overview" icon={<Activity className="h-4 w-4 text-blue-400" />}>
|
|
<div className="space-y-3">
|
|
<StatCard
|
|
title="Total Incidents"
|
|
value={crimeStats.totalIncidents.toString()}
|
|
change={`${Object.keys(crimeStats.districts).length} districts`}
|
|
icon={<AlertTriangle className="h-4 w-4 text-blue-400" />}
|
|
bgColor="bg-gradient-to-r from-blue-900/30 to-blue-800/20"
|
|
/>
|
|
<StatCard
|
|
title={selectedMonth !== 'all' ?
|
|
`${getMonthName(Number(selectedMonth))} Cases` :
|
|
"Monthly Average"}
|
|
value={selectedMonth !== 'all' ?
|
|
crimeStats.totalIncidents.toString() :
|
|
Math.round(crimeStats.totalIncidents /
|
|
(crimeStats.incidentsByMonth.filter((c: number) => c > 0).length || 1)
|
|
).toString()}
|
|
change={selectedMonth !== 'all' ?
|
|
`in ${getMonthName(Number(selectedMonth))}` :
|
|
"per active month"}
|
|
isPositive={false}
|
|
icon={<Calendar className="h-4 w-4 text-amber-400" />}
|
|
bgColor="bg-gradient-to-r from-amber-900/30 to-amber-800/20"
|
|
/>
|
|
<StatCard
|
|
title="Clearance Rate"
|
|
value={`${crimeStats.clearanceRate}%`}
|
|
change="of cases resolved"
|
|
isPositive={crimeStats.clearanceRate > 50}
|
|
icon={<CheckCircle className="h-4 w-4 text-green-400" />}
|
|
bgColor="bg-gradient-to-r from-green-900/30 to-green-800/20"
|
|
/>
|
|
</div>
|
|
</SidebarSection>
|
|
|
|
<Separator className="bg-white/20 my-4" />
|
|
|
|
<SidebarSection title="Most Common Crimes" icon={<PieChart className="h-4 w-4 text-amber-400" />}>
|
|
<div className="space-y-3">
|
|
{topCategories.length > 0 ? (
|
|
topCategories.map((category: ICrimeTypeCardProps) => (
|
|
<CrimeTypeCard
|
|
key={category.type}
|
|
type={category.type}
|
|
count={category.count}
|
|
percentage={category.percentage}
|
|
/>
|
|
))
|
|
) : (
|
|
<Card className="bg-white/5 border-0 text-white shadow-none">
|
|
<CardContent className="p-4 text-center">
|
|
<div className="flex flex-col items-center gap-2">
|
|
<FileText className="h-6 w-6 text-white/40" />
|
|
<p className="text-sm text-white/70">No crime data available</p>
|
|
<p className="text-xs text-white/50">Try selecting a different time period</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
</SidebarSection>
|
|
</>
|
|
)
|
|
}
|