feat: enhance crime analytics and sidebar components with improved type definitions and additional data handling
This commit is contained in:
parent
0de70f9057
commit
75df66a621
|
@ -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)
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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'}`}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
export const MONTHS = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
];
|
Loading…
Reference in New Issue