import { useMemo } from 'react'; import { Link } from 'react-router-dom'; import { AlertTriangle, Clock, TrendingUp, Users } from 'lucide-react'; import Layout from '../components/Layout'; import { useAnalytics, useUsers } from '../api/queries'; const SEV_NAMES: Record = { 1: 'SEV 1 — Critical', 2: 'SEV 2 — High', 3: 'SEV 3 — Medium', 4: 'SEV 4 — Low', 5: 'SEV 5 — Minimal', }; const SEV_COLORS: Record = { 1: 'bg-red-500', 2: 'bg-orange-400', 3: 'bg-yellow-400', 4: 'bg-blue-400', 5: 'bg-gray-500', }; function fmtHours(hours: number | null) { if (hours == null) return '—'; if (hours < 1) return `${Math.round(hours * 60)} min`; if (hours < 48) return `${hours.toFixed(1)} h`; return `${(hours / 24).toFixed(1)} d`; } export default function Dashboard() { const { data: a } = useAnalytics(30); const { data: users = [] } = useUsers(); const userById = useMemo( () => new Map(users.map((u) => [u.id, u.displayName])), [users], ); const totalOpen = a?.openBySeverity.reduce((sum, row) => sum + row.count, 0) ?? 0; const maxBucket = Math.max( ...Object.values(a?.ageBuckets ?? { d1: 0, d7: 0, d14: 0, older: 0 }), 1, ); const maxSeverity = Math.max(...(a?.openBySeverity.map((r) => r.count) ?? [1])); return ( Last 30 days

}> {!a ? (

Loading analytics…

) : (
} label="Open tickets" value={totalOpen.toString()} /> } label="Aging >7d" value={((a.ageBuckets.d14 ?? 0) + (a.ageBuckets.older ?? 0)).toString()} /> } label="Median resolution" value={fmtHours(a.medianResolutionHours)} /> } label="Assignees loaded" value={a.queueByAssignee.filter((q) => q.assigneeId).length.toString()} /> {/* Open by severity */}

Open by severity

{[1, 2, 3, 4, 5].map((sev) => { const row = a.openBySeverity.find((r) => r.severity === sev); const count = row?.count ?? 0; const pct = maxSeverity > 0 ? (count / maxSeverity) * 100 : 0; return (
{SEV_NAMES[sev]}
{count}
); })}
Go to open tickets →
{/* Age buckets */}

Age of open tickets

{[ { key: 'd1', label: '≤ 1 day' }, { key: 'd7', label: '≤ 7 days' }, { key: 'd14', label: '≤ 14 days' }, { key: 'older', label: '> 14 days' }, ].map(({ key, label }) => { const count = a.ageBuckets[key as keyof typeof a.ageBuckets] ?? 0; const pct = (count / maxBucket) * 100; return (
{label}
{count}
); })}
{/* Queue by assignee */}

Queue load by assignee

{a.queueByAssignee.length === 0 ? (

No open tickets right now.

) : (
{a.queueByAssignee .slice() .sort((x, y) => y.count - x.count) .map((row) => { const name = row.assigneeId ? userById.get(row.assigneeId) ?? 'Unknown' : 'Unassigned'; return (
{name} {row.count}
); })}
)}
)}
); } function Card({ icon, label, value, }: { icon: React.ReactNode; label: string; value: string; }) { return (
{icon} {label}

{value}

); }