diff --git a/app/(protected)/management/page.tsx b/app/(protected)/management/page.tsx index 26d197f..b927405 100644 --- a/app/(protected)/management/page.tsx +++ b/app/(protected)/management/page.tsx @@ -1,9 +1,139 @@ -// app/(protected)/management/page.tsx -export default function ManagementHome() { +export const dynamic = 'force-dynamic' + +import { createClient } from '@/lib/supabase/server' +import { redirect } from 'next/navigation' +import { StatCard } from '@/components/dashboard/stat-card' + +export default async function ManagementPage() { + const supabase = await createClient() + const { data: { user } } = await supabase.auth.getUser() + if (!user) redirect('/login') + + const { data: profile } = await supabase.from('users').select('role').eq('id', user.id).single() + if (!profile || !['management', 'admin'].includes(profile.role)) redirect('/') + + const now = new Date() + const thisMonthStart = new Date(now.getFullYear(), now.getMonth(), 1).toISOString() + const lastMonthStart = new Date(now.getFullYear(), now.getMonth() - 1, 1).toISOString() + const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString() + + const [ + { data: allIncidents }, + { data: thisMonthIncidents }, + { data: lastMonthIncidents }, + { data: recentIncidents }, + { data: overdueCapas }, + ] = await Promise.all([ + supabase.from('incidents').select('id, severity, status, incident_type, medical_status, sites (name)'), + supabase.from('incidents').select('id').gte('reported_at', thisMonthStart), + supabase.from('incidents').select('id').gte('reported_at', lastMonthStart).lt('reported_at', thisMonthStart), + supabase.from('incidents').select('incident_type').gte('reported_at', thirtyDaysAgo), + supabase.from('capa_actions').select('id').eq('status', 'overdue'), + ]) + + const rows = allIncidents ?? [] + const totalThisMonth = thisMonthIncidents?.length ?? 0 + const totalLastMonth = lastMonthIncidents?.length ?? 0 + const monthDelta = totalThisMonth - totalLastMonth + + const ltiCount = rows.filter(r => r.medical_status === 'lti').length + const overdueCount = overdueCapas?.length ?? 0 + + // Severity distribution + const severityDist: Record = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 } + for (const r of rows) { + if (r.severity && r.severity >= 1 && r.severity <= 5) { + severityDist[r.severity] = (severityDist[r.severity] ?? 0) + 1 + } + } + const severityMax = Math.max(...Object.values(severityDist), 1) + + // Leading vs lagging (last 30 days) + const recent = recentIncidents ?? [] + const leadingCount = recent.filter(r => ['hazard', 'near_miss'].includes(r.incident_type)).length + const laggingCount = recent.filter(r => r.incident_type === 'injury').length + + // Site comparison + const siteMap: Record = {} + for (const r of rows) { + const name = (r.sites as unknown as { name: string } | null)?.name ?? 'Unknown' + siteMap[name] = (siteMap[name] ?? 0) + 1 + } + const by_site = Object.entries(siteMap).map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count) + + const SEVERITY_LABELS: Record = { 1: 'Minor', 2: 'Low', 3: 'Moderate', 4: 'Serious', 5: 'Critical' } + const SEVERITY_COLORS: Record = { + 1: 'bg-green-400', 2: 'bg-yellow-400', 3: 'bg-orange-400', 4: 'bg-red-500', 5: 'bg-red-700', + } + return ( -
-

Management Dashboard

-

Phase 1: KPI dashboard coming here.

+
+

Management Dashboard

+ + {/* Summary stats */} +
+ = 0 ? `+${monthDelta} vs last month` : `${monthDelta} vs last month`} + accent={monthDelta > 0 ? 'yellow' : 'green'} + /> + 0 ? 'red' : 'green'} sub="lost-time injuries" /> + 0 ? 'red' : 'green'} sub="past due date" /> + +
+ + {/* Leading vs lagging */} +
+

Leading vs Lagging — Last 30 Days

+

Leading: hazard + near-miss · Lagging: injuries

+
+
+

{leadingCount}

+

Leading (Hazards + Near Misses)

+
+
+

{laggingCount}

+

Lagging (Injuries)

+
+
+
+ + {/* Severity distribution */} +
+

Severity Distribution (All Time)

+
+ {([5, 4, 3, 2, 1] as const).map(sev => ( +
+ {SEVERITY_LABELS[sev]} +
+
0 ? `${(severityDist[sev] / severityMax) * 100}%` : '0%' }} + /> +
+ {severityDist[sev]} +
+ ))} +
+
+ + {/* Site comparison */} +
+

Incidents by Site

+ {by_site.length === 0 ? ( +

No data

+ ) : ( +
+ {by_site.map(({ name, count }) => ( +
+ {name} + {count} +
+ ))} +
+ )} +
) }