mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-14 12:12:23 +00:00
Replace all @untitledui/icons imports across 55 files with equivalent
@fortawesome/pro-duotone-svg-icons icons, using FontAwesomeIcon wrappers
(FC<{ className?: string }>) for prop-based usage and inline replacements
for direct JSX usage. Drops unsupported Untitled UI-specific props
(strokeWidth, numeric size). TypeScript compiles clean with no errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
121 lines
4.6 KiB
TypeScript
121 lines
4.6 KiB
TypeScript
import type { FC } from 'react';
|
|
import { useMemo } from 'react';
|
|
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
import { faCircleCheck, faTriangleExclamation, faCircleExclamation } from '@fortawesome/pro-duotone-svg-icons';
|
|
import { cx } from '@/utils/cx';
|
|
import type { Lead } from '@/types/entities';
|
|
|
|
const CheckCircle: FC<{ className?: string }> = ({ className }) => <FontAwesomeIcon icon={faCircleCheck} className={className} />;
|
|
const AlertTriangle: FC<{ className?: string }> = ({ className }) => <FontAwesomeIcon icon={faTriangleExclamation} className={className} />;
|
|
const AlertCircle: FC<{ className?: string }> = ({ className }) => <FontAwesomeIcon icon={faCircleExclamation} className={className} />;
|
|
|
|
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: FC<{ className?: string }>; 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>
|
|
);
|
|
};
|