feat: build Admin Team Dashboard with scoreboard, funnel, SLA, ROI, and integration health

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-16 18:28:00 +05:30
parent 26c352e2cc
commit e9ac6e598a
6 changed files with 533 additions and 5 deletions

View File

@@ -0,0 +1,114 @@
import { useMemo } from 'react';
import { CheckCircle, AlertTriangle, AlertCircle } from '@untitledui/icons';
import { cx } from '@/utils/cx';
import type { Lead } from '@/types/entities';
interface SlaMetricsProps {
leads: Lead[];
}
const SLA_TARGET_HOURS = 2;
export const SlaMetrics = ({ leads }: SlaMetricsProps) => {
const metrics = useMemo(() => {
const responseTimes: number[] = [];
let withinSla = 0;
let total = leads.length;
for (const lead of leads) {
if (lead.createdAt && lead.firstContactedAt) {
const created = new Date(lead.createdAt).getTime();
const contacted = new Date(lead.firstContactedAt).getTime();
const diffHours = (contacted - created) / (1000 * 60 * 60);
responseTimes.push(diffHours);
if (diffHours <= SLA_TARGET_HOURS) {
withinSla++;
}
}
// Leads without firstContactedAt are counted as outside SLA
}
const avgHours =
responseTimes.length > 0
? responseTimes.reduce((sum, h) => sum + h, 0) / responseTimes.length
: 0;
const slaPercent = total > 0 ? (withinSla / total) * 100 : 0;
return { avgHours, withinSla, total, slaPercent };
}, [leads]);
const getTargetStatus = (): { icon: typeof CheckCircle; label: string; colorClass: string } => {
const diff = metrics.avgHours - SLA_TARGET_HOURS;
if (diff <= 0) {
return {
icon: CheckCircle,
label: 'Below target',
colorClass: 'text-success-primary',
};
}
if (diff <= 0.5) {
return {
icon: AlertTriangle,
label: 'Near target',
colorClass: 'text-warning-primary',
};
}
return {
icon: AlertCircle,
label: 'Above target',
colorClass: 'text-error-primary',
};
};
const status = getTargetStatus();
const StatusIcon = status.icon;
return (
<div className="rounded-2xl border border-secondary bg-primary p-5">
<h3 className="text-sm font-bold text-primary">Response SLA</h3>
<div className="mt-4 flex items-end gap-3">
<span className="text-display-sm font-bold text-primary">
{metrics.avgHours.toFixed(1)}h
</span>
<div className="mb-1 flex items-center gap-1">
<span className="text-xs text-tertiary">Target: {SLA_TARGET_HOURS}h</span>
<StatusIcon className={cx('size-4', status.colorClass)} />
</div>
</div>
<span className={cx('mt-1 block text-xs font-medium', status.colorClass)}>
{status.label}
</span>
<div className="mt-4">
<div className="flex items-center justify-between text-xs text-tertiary">
<span>SLA Compliance</span>
<span className="font-medium text-primary">{Math.round(metrics.slaPercent)}%</span>
</div>
<div className="mt-1.5 h-2 w-full overflow-hidden rounded-full bg-tertiary">
<div
className={cx(
'h-full rounded-full transition-all',
metrics.slaPercent >= 80
? 'bg-success-500'
: metrics.slaPercent >= 60
? 'bg-warning-500'
: 'bg-error-500',
)}
style={{ width: `${metrics.slaPercent}%` }}
/>
</div>
<span className="mt-1.5 block text-xs text-tertiary">
{metrics.withinSla} of {metrics.total} leads within SLA ({Math.round(metrics.slaPercent)}%)
</span>
</div>
</div>
);
};