286 lines
16 KiB
TypeScript
286 lines
16 KiB
TypeScript
import React from 'react'
|
|
import { AlertTriangle, AlertCircle, Clock, Shield, MapPin, ChevronLeft, ChevronRight, FileText, Calendar } from 'lucide-react'
|
|
import { Card, CardContent } from "@/app/_components/ui/card"
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/app/_components/ui/tabs"
|
|
import { Badge } from "@/app/_components/ui/badge"
|
|
import { Button } from "@/app/_components/ui/button"
|
|
import { formatMonthKey, getIncidentSeverity, getMonthName, getTimeAgo } from "@/app/_utils/common"
|
|
import { SystemStatusCard } from "../components/system-status-card"
|
|
import { IncidentCard } from "../components/incident-card"
|
|
|
|
interface SidebarIncidentsTabProps {
|
|
crimeStats: any
|
|
formattedDate: string
|
|
formattedTime: string
|
|
location: string
|
|
selectedMonth?: number | "all"
|
|
selectedYear: number
|
|
selectedCategory: string | "all"
|
|
getTimePeriodDisplay: () => string
|
|
paginationState: Record<string, number>
|
|
handlePageChange: (monthKey: string, direction: 'next' | 'prev') => void
|
|
handleIncidentClick: (incident: any) => void
|
|
activeIncidentTab: string
|
|
setActiveIncidentTab: (tab: string) => void
|
|
}
|
|
|
|
export function SidebarIncidentsTab({
|
|
crimeStats,
|
|
formattedDate,
|
|
formattedTime,
|
|
location,
|
|
selectedMonth = "all",
|
|
selectedYear,
|
|
selectedCategory,
|
|
getTimePeriodDisplay,
|
|
paginationState,
|
|
handlePageChange,
|
|
handleIncidentClick,
|
|
activeIncidentTab,
|
|
setActiveIncidentTab
|
|
}: SidebarIncidentsTabProps) {
|
|
const topCategories = crimeStats.categoryCounts ?
|
|
Object.entries(crimeStats.categoryCounts)
|
|
.sort((a: any, b: any) => (b[1] as number) - (a[1] as number))
|
|
.slice(0, 4)
|
|
.map(([type, count]: [string, unknown]) => {
|
|
const countAsNumber = count as number;
|
|
const percentage = Math.round(((countAsNumber) / crimeStats.totalIncidents) * 100) || 0
|
|
return { type, count: countAsNumber, percentage }
|
|
}) : []
|
|
|
|
return (
|
|
<>
|
|
{/* Enhanced info card */}
|
|
<Card className="bg-gradient-to-r from-sidebar-primary/30 to-sidebar-primary/20 border border-sidebar-primary/20 overflow-hidden">
|
|
<CardContent className="p-4 text-sm relative">
|
|
<div className="absolute top-0 right-0 w-24 h-24 bg-sidebar-primary/10 rounded-full -translate-y-1/2 translate-x-1/2"></div>
|
|
<div className="absolute bottom-0 left-0 w-16 h-16 bg-sidebar-primary/10 rounded-full translate-y-1/2 -translate-x-1/2"></div>
|
|
|
|
<div className="flex items-center justify-between mb-3">
|
|
<div className="flex items-center gap-2">
|
|
<Calendar className="h-4 w-4 text-sidebar-primary" />
|
|
<span className="font-medium">{formattedDate}</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Clock className="h-4 w-4 text-sidebar-primary" />
|
|
<span>{formattedTime}</span>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2 mb-3">
|
|
<MapPin className="h-4 w-4 text-sidebar-primary" />
|
|
<span className="text-sidebar-foreground/70">{location}</span>
|
|
</div>
|
|
<div className="flex items-center gap-2 bg-sidebar-accent/30 p-2 rounded-lg">
|
|
<AlertTriangle className="h-5 w-5 text-amber-400" />
|
|
<span>
|
|
<strong>{crimeStats.totalIncidents || 0}</strong> incidents reported
|
|
{selectedMonth !== 'all' ? ` in ${getMonthName(Number(selectedMonth))}` : ` in ${selectedYear}`}
|
|
</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Enhanced stat cards */}
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<SystemStatusCard
|
|
title="Total Cases"
|
|
status={`${crimeStats?.totalIncidents || 0}`}
|
|
statusIcon={<AlertCircle className="h-4 w-4 text-green-400" />}
|
|
statusColor="text-green-400"
|
|
updatedTime={getTimePeriodDisplay()}
|
|
bgColor="bg-gradient-to-br from-sidebar-accent/30 to-sidebar-accent/20"
|
|
borderColor="border-sidebar-border"
|
|
/>
|
|
<SystemStatusCard
|
|
title="Recent Cases"
|
|
status={`${crimeStats?.recentIncidents?.length || 0}`}
|
|
statusIcon={<Clock className="h-4 w-4 text-amber-400" />}
|
|
statusColor="text-amber-400"
|
|
updatedTime="Last 30 days"
|
|
bgColor="bg-gradient-to-br from-sidebar-accent/30 to-sidebar-accent/20"
|
|
borderColor="border-sidebar-border"
|
|
/>
|
|
<SystemStatusCard
|
|
title="Top Category"
|
|
status={topCategories.length > 0 ? topCategories[0].type : "None"}
|
|
statusIcon={<Shield className="h-4 w-4 text-green-400" />}
|
|
statusColor="text-green-400"
|
|
bgColor="bg-gradient-to-br from-sidebar-accent/30 to-sidebar-accent/20"
|
|
borderColor="border-sidebar-border"
|
|
/>
|
|
<SystemStatusCard
|
|
title="Districts"
|
|
status={`${Object.keys(crimeStats.districts).length}`}
|
|
statusIcon={<MapPin className="h-4 w-4 text-purple-400" />}
|
|
statusColor="text-purple-400"
|
|
updatedTime="Affected areas"
|
|
bgColor="bg-gradient-to-br from-sidebar-accent/30 to-sidebar-accent/20"
|
|
borderColor="border-sidebar-border"
|
|
/>
|
|
</div>
|
|
|
|
{/* Nested tabs for Recent and History */}
|
|
<Tabs
|
|
defaultValue="recent"
|
|
value={activeIncidentTab}
|
|
onValueChange={setActiveIncidentTab}
|
|
className="w-full"
|
|
>
|
|
<div className="flex items-center justify-between mb-3">
|
|
<h3 className="text-sm font-medium text-sidebar-foreground/90 flex items-center gap-2 pl-1">
|
|
<AlertTriangle className="h-4 w-4 text-red-400" />
|
|
Incident Reports
|
|
</h3>
|
|
<TabsList className="bg-sidebar-accent p-0.5 rounded-md h-7">
|
|
<TabsTrigger
|
|
value="recent"
|
|
className="text-xs px-3 py-0.5 h-6 rounded-sm data-[state=active]:bg-sidebar-primary data-[state=active]:text-sidebar-primary-foreground"
|
|
>
|
|
Recent
|
|
</TabsTrigger>
|
|
<TabsTrigger
|
|
value="history"
|
|
className="text-xs px-3 py-0.5 h-6 rounded-sm data-[state=active]:bg-sidebar-primary data-[state=active]:text-sidebar-primary-foreground"
|
|
>
|
|
History
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
</div>
|
|
|
|
{/* Recent Incidents Tab Content */}
|
|
<TabsContent value="recent" className="m-0 p-0">
|
|
{crimeStats.recentIncidents.length === 0 ? (
|
|
<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">
|
|
<AlertCircle className="h-6 w-6 text-white/40" />
|
|
<p className="text-sm text-white/70">
|
|
{selectedCategory !== "all"
|
|
? `No ${selectedCategory} incidents found`
|
|
: "No recent incidents reported"}
|
|
</p>
|
|
<p className="text-xs text-white/50">Try adjusting your filters or checking back later</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{crimeStats.recentIncidents.slice(0, 6).map((incident: any) => (
|
|
<IncidentCard
|
|
key={incident.id}
|
|
title={`${incident.category || 'Unknown'} in ${incident.address?.split(',')[0] || 'Unknown Location'}`}
|
|
time={incident.timestamp ? getTimeAgo(incident.timestamp) : 'Unknown time'}
|
|
location={incident.address?.split(',').slice(1, 3).join(', ') || 'Unknown Location'}
|
|
severity={getIncidentSeverity(incident)}
|
|
onClick={() => handleIncidentClick(incident)}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</TabsContent>
|
|
|
|
{/* History Incidents Tab Content */}
|
|
<TabsContent value="history" className="m-0 p-0">
|
|
{crimeStats.availableMonths && crimeStats.availableMonths.length === 0 ? (
|
|
<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">
|
|
{selectedCategory !== "all"
|
|
? `No ${selectedCategory} incidents found in the selected period`
|
|
: "No incidents found in the selected period"}
|
|
</p>
|
|
<p className="text-xs text-white/50">Try adjusting your filters</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
<div className="space-y-6">
|
|
<div className="flex justify-between items-center mb-1">
|
|
<span className="text-xs text-white/60">
|
|
Showing incidents from {crimeStats.availableMonths.length} {crimeStats.availableMonths.length === 1 ? 'month' : 'months'}
|
|
</span>
|
|
<Badge variant="outline" className="h-5 text-[10px]">
|
|
{selectedCategory !== "all" ? selectedCategory : "All Categories"}
|
|
</Badge>
|
|
</div>
|
|
|
|
{crimeStats.availableMonths.map((monthKey: string) => {
|
|
const incidents = crimeStats.incidentsByMonthDetail[monthKey] || []
|
|
const pageSize = 5
|
|
const currentPage = paginationState[monthKey] || 0
|
|
const totalPages = Math.ceil(incidents.length / pageSize)
|
|
const startIdx = currentPage * pageSize
|
|
const endIdx = startIdx + pageSize
|
|
const paginatedIncidents = incidents.slice(startIdx, endIdx)
|
|
|
|
if (incidents.length === 0) return null
|
|
|
|
return (
|
|
<div key={monthKey} className="mb-5">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<div className="flex items-center gap-1.5">
|
|
<Calendar className="h-3.5 w-3.5 text-amber-400" />
|
|
<h4 className="font-medium text-xs">{formatMonthKey(monthKey)}</h4>
|
|
</div>
|
|
<Badge variant="secondary" className="h-5 text-[10px]">
|
|
{incidents.length} incident{incidents.length !== 1 ? 's' : ''}
|
|
</Badge>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
{paginatedIncidents.map((incident: any) => (
|
|
<IncidentCard
|
|
key={incident.id}
|
|
title={`${incident.category || 'Unknown'} in ${incident.address?.split(',')[0] || 'Unknown Location'}`}
|
|
time={incident.timestamp ? new Date(incident.timestamp).toLocaleDateString() : 'Unknown date'}
|
|
location={incident.address?.split(',').slice(1, 3).join(', ') || 'Unknown Location'}
|
|
severity={getIncidentSeverity(incident)}
|
|
onClick={() => handleIncidentClick(incident)}
|
|
showTimeAgo={false}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
{totalPages > 1 && (
|
|
<div className="flex items-center justify-between mt-2">
|
|
<span className="text-xs text-white/50">
|
|
Page {currentPage + 1} of {totalPages}
|
|
</span>
|
|
<div className="flex gap-1">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="h-7 px-2 py-1 text-[10px]"
|
|
disabled={currentPage === 0}
|
|
onClick={() => handlePageChange(monthKey, 'prev')}
|
|
>
|
|
<ChevronLeft className="h-3 w-3 mr-1" />
|
|
Prev
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="h-7 px-2 py-1 text-[10px]"
|
|
disabled={currentPage >= totalPages - 1}
|
|
onClick={() => handlePageChange(monthKey, 'next')}
|
|
>
|
|
Next
|
|
<ChevronRight className="h-3 w-3 ml-1" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
</TabsContent>
|
|
</Tabs>
|
|
</>
|
|
)
|
|
}
|