feat: enhance crime analytics and sidebar components with improved type definitions and additional data handling

This commit is contained in:
vergiLgood1 2025-05-04 05:04:19 +07:00
parent 0de70f9057
commit 75df66a621
9 changed files with 96 additions and 52 deletions

View File

@ -1,6 +1,19 @@
import { useMemo } from 'react';
import { ICrimes } from '@/app/_utils/types/crimes';
export interface ICrimeAnalytics {
todaysIncidents: number;
totalIncidents: number;
recentIncidents: any[];
filteredIncidents: any[];
categoryCounts: Record<string, number>;
districts: Record<string, number>;
incidentsByMonth: number[];
clearanceRate: number;
incidentsByMonthDetail: Record<string, any[]>;
availableMonths: string[];
}
export function useCrimeAnalytics(crimes: ICrimes[]) {
return useMemo(() => {
if (!crimes || !Array.isArray(crimes) || crimes.length === 0)

View File

@ -1,13 +1,14 @@
import React from 'react'
import { Card, CardContent } from "@/app/_components/ui/card"
import { crime_categories } from '@prisma/client'
interface CrimeTypeCardProps {
export interface ICrimeTypeCardProps {
type: string
count: number
percentage: number
}
export function CrimeTypeCard({ type, count, percentage }: CrimeTypeCardProps) {
export function CrimeTypeCard({ type, count, percentage }: ICrimeTypeCardProps) {
return (
<Card className="bg-white/5 hover:bg-white/10 border-0 text-white shadow-none transition-colors">
<CardContent className="p-3">

View File

@ -3,7 +3,7 @@ import { AlertTriangle, MapPin, Calendar } from 'lucide-react'
import { Badge } from "@/app/_components/ui/badge"
import { Card, CardContent } from "@/app/_components/ui/card"
interface EnhancedIncidentCardProps {
interface IIncidentCardProps {
title: string
time: string
location: string
@ -19,7 +19,7 @@ export function IncidentCard({
severity,
onClick,
showTimeAgo = true
}: EnhancedIncidentCardProps) {
}: IIncidentCardProps) {
const getBadgeColor = () => {
switch (severity) {
case "Low": return "bg-green-500/20 text-green-300";

View File

@ -1,12 +1,12 @@
import React from 'react'
interface SidebarSectionProps {
interface ISidebarSectionProps {
title: string
children: React.ReactNode
icon?: React.ReactNode
}
export function SidebarSection({ title, children, icon }: SidebarSectionProps) {
export function SidebarSection({ title, children, icon }: ISidebarSectionProps) {
return (
<div>
<h3 className="text-sm font-medium text-sidebar-foreground/90 mb-3 flex items-center gap-2 pl-1">

View File

@ -1,7 +1,7 @@
import React from 'react'
import { Card, CardContent } from "@/app/_components/ui/card"
interface StatCardProps {
interface IStatCardProps {
title: string
value: string
change: string
@ -17,7 +17,7 @@ export function StatCard({
isPositive = false,
icon,
bgColor = "bg-white/10"
}: StatCardProps) {
}: IStatCardProps) {
return (
<Card className={`${bgColor} hover:bg-white/15 border-0 text-white shadow-none transition-colors`}>
<CardContent className="p-3">

View File

@ -1,7 +1,7 @@
import React from 'react'
import { Card, CardContent } from "@/app/_components/ui/card"
interface SystemStatusCardProps {
interface ISystemStatusCardProps {
title: string
status: string
statusIcon: React.ReactNode
@ -19,7 +19,7 @@ export function SystemStatusCard({
updatedTime,
bgColor = "bg-sidebar-accent/20",
borderColor = "border-sidebar-border"
}: SystemStatusCardProps) {
}: ISystemStatusCardProps) {
return (
<Card className={`${bgColor} border ${borderColor} hover:border-sidebar-border/80 transition-colors`}>
<CardContent className="p-3 text-xs">

View File

@ -7,21 +7,43 @@ 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"
import { ICrimeAnalytics } from '@/app/(pages)/(admin)/dashboard/crime-management/crime-overview/_hooks/use-crime-analytics'
interface Incident {
id: string;
category: string;
address: string;
timestamp: string;
district?: string;
severity?: number;
status?: string;
description?: string;
location?: {
lat: number;
lng: number;
};
}
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
crimeStats: ICrimeAnalytics;
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: Incident) => void;
activeIncidentTab: string;
setActiveIncidentTab: (tab: string) => void;
}
interface CrimeCategory {
type: string;
count: number;
percentage: number;
}
export function SidebarIncidentsTab({
@ -41,12 +63,11 @@ export function SidebarIncidentsTab({
}: SidebarIncidentsTabProps) {
const topCategories = crimeStats.categoryCounts ?
Object.entries(crimeStats.categoryCounts)
.sort((a: any, b: any) => (b[1] as number) - (a[1] as number))
.sort((a, b) => b[1] - a[1])
.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 }
.map(([type, count]) => {
const percentage = Math.round((count / crimeStats.totalIncidents) * 100) || 0
return { type, count, percentage }
}) : []
return (
@ -166,7 +187,7 @@ export function SidebarIncidentsTab({
</Card>
) : (
<div className="space-y-3">
{crimeStats.recentIncidents.slice(0, 6).map((incident: any) => (
{crimeStats.recentIncidents.slice(0, 6).map((incident: Incident) => (
<IncidentCard
key={incident.id}
title={`${incident.category || 'Unknown'} in ${incident.address?.split(',')[0] || 'Unknown Location'}`}
@ -231,7 +252,7 @@ export function SidebarIncidentsTab({
</div>
<div className="space-y-2">
{paginatedIncidents.map((incident: any) => (
{paginatedIncidents.map((incident: Incident) => (
<IncidentCard
key={incident.id}
title={`${incident.category || 'Unknown'} in ${incident.address?.split(',')[0] || 'Unknown Location'}`}

View File

@ -6,10 +6,13 @@ 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 } from "../components/crime-type-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 SidebarStatisticsTabProps {
crimeStats: any
interface ISidebarStatisticsTabProps {
crimeStats: ICrimeAnalytics
selectedMonth?: number | "all"
selectedYear: number
}
@ -18,15 +21,14 @@ export function SidebarStatisticsTab({
crimeStats,
selectedMonth = "all",
selectedYear
}: SidebarStatisticsTabProps) {
}: ISidebarStatisticsTabProps) {
const topCategories = crimeStats.categoryCounts ?
Object.entries(crimeStats.categoryCounts)
.sort((a: any, b: any) => (b[1] as number) - (a[1] as number))
.sort((a, b) => b[1] - a[1])
.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 }
.map(([type, count]) => {
const percentage = Math.round(((count) / crimeStats.totalIncidents) * 100) || 0
return { type, count, percentage }
}) : []
return (
@ -62,18 +64,11 @@ export function SidebarStatisticsTab({
})}
</div>
<div className="flex justify-between mt-2 text-[10px] text-white/60">
<span>Jan</span>
<span>Feb</span>
<span>Mar</span>
<span>Apr</span>
<span>May</span>
<span>Jun</span>
<span>Jul</span>
<span>Aug</span>
<span>Sep</span>
<span>Oct</span>
<span>Nov</span>
<span>Dec</span>
{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>
@ -119,7 +114,7 @@ export function SidebarStatisticsTab({
<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: any) => (
topCategories.map((category: ICrimeTypeCardProps) => (
<CrimeTypeCard
key={category.type}
type={category.type}

View File

@ -0,0 +1,14 @@
export const MONTHS = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];