MIF_E31221222/sigap-website/app/_components/map/sidebar/tabs/incidents-tab.tsx

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>
</>
)
}