139 lines
4.2 KiB
TypeScript
139 lines
4.2 KiB
TypeScript
import { Badge } from '@/components/ui/badge';
|
|
import { Progress } from '@/components/ui/progress';
|
|
import { Star } from 'lucide-react';
|
|
|
|
export interface CriticalAlertData {
|
|
requestId: string;
|
|
requestNumber: string;
|
|
title: string;
|
|
priority: string;
|
|
totalTATHours: number;
|
|
originalTATHours: number;
|
|
breachCount: number;
|
|
currentLevel: number;
|
|
totalLevels: number;
|
|
}
|
|
|
|
interface CriticalAlertCardProps {
|
|
alert: CriticalAlertData;
|
|
onNavigate?: (requestNumber: string) => void;
|
|
testId?: string;
|
|
}
|
|
|
|
// Utility functions
|
|
const calculateProgress = (alert: CriticalAlertData) => {
|
|
if (!alert.originalTATHours || alert.originalTATHours === 0) return 0;
|
|
|
|
const originalTAT = alert.originalTATHours;
|
|
const remainingTAT = alert.totalTATHours;
|
|
|
|
// If breached (negative remaining), show 100%
|
|
if (remainingTAT <= 0) return 100;
|
|
|
|
// Calculate elapsed time
|
|
const elapsedTAT = originalTAT - remainingTAT;
|
|
|
|
// Calculate percentage used
|
|
const percentageUsed = (elapsedTAT / originalTAT) * 100;
|
|
|
|
// Ensure it's between 0 and 100
|
|
return Math.min(100, Math.max(0, Math.round(percentageUsed)));
|
|
};
|
|
|
|
const formatRemainingTime = (alert: CriticalAlertData) => {
|
|
if (alert.totalTATHours === undefined || alert.totalTATHours === null) return 'N/A';
|
|
|
|
const hours = alert.totalTATHours;
|
|
|
|
// If TAT is breached (negative or zero)
|
|
if (hours <= 0) {
|
|
const overdue = Math.abs(hours);
|
|
if (overdue < 1) return `Breached`;
|
|
if (overdue < 24) return `${Math.round(overdue)}h overdue`;
|
|
return `${Math.round(overdue / 24)}d overdue`;
|
|
}
|
|
|
|
// If TAT is still remaining
|
|
if (hours < 1) return `${Math.round(hours * 60)}min left`;
|
|
if (hours < 24) return `${Math.round(hours)}h left`;
|
|
return `${Math.round(hours / 24)}d left`;
|
|
};
|
|
|
|
export function CriticalAlertCard({
|
|
alert,
|
|
onNavigate,
|
|
testId = 'critical-alert-card'
|
|
}: CriticalAlertCardProps) {
|
|
const progress = calculateProgress(alert);
|
|
|
|
return (
|
|
<div
|
|
className="p-3 sm:p-4 bg-red-50 rounded-lg sm:rounded-xl border border-red-100 hover:shadow-md transition-all duration-200 cursor-pointer"
|
|
onClick={() => onNavigate?.(alert.requestNumber)}
|
|
data-testid={`${testId}-${alert.requestId}`}
|
|
>
|
|
<div className="flex items-start justify-between gap-2 mb-2 sm:mb-3">
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-1 sm:gap-2 mb-1 flex-wrap">
|
|
<p
|
|
className="font-semibold text-xs sm:text-sm text-gray-900"
|
|
data-testid={`${testId}-request-number`}
|
|
>
|
|
{alert.requestNumber}
|
|
</p>
|
|
{alert.priority === 'express' && (
|
|
<Star
|
|
className="h-3 w-3 text-red-500 flex-shrink-0"
|
|
data-testid={`${testId}-priority-icon`}
|
|
/>
|
|
)}
|
|
{alert.breachCount > 0 && (
|
|
<Badge
|
|
variant="destructive"
|
|
className="text-xs"
|
|
data-testid={`${testId}-breach-count`}
|
|
>
|
|
{alert.breachCount}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
<p
|
|
className="text-xs sm:text-sm text-gray-700 line-clamp-2"
|
|
data-testid={`${testId}-title`}
|
|
>
|
|
{alert.title}
|
|
</p>
|
|
</div>
|
|
<Badge
|
|
variant="outline"
|
|
className="text-xs bg-white border-red-200 text-red-700 font-medium whitespace-nowrap"
|
|
data-testid={`${testId}-remaining-time`}
|
|
>
|
|
{formatRemainingTime(alert)}
|
|
</Badge>
|
|
</div>
|
|
<div className="space-y-1 sm:space-y-2">
|
|
<div className="flex justify-between text-xs text-gray-600">
|
|
<span>TAT Used</span>
|
|
<span
|
|
className="font-medium"
|
|
data-testid={`${testId}-progress-percentage`}
|
|
>
|
|
{progress}%
|
|
</span>
|
|
</div>
|
|
<Progress
|
|
value={progress}
|
|
className={`h-1.5 sm:h-2 ${
|
|
progress >= 80 ? '[&>div]:bg-red-600' :
|
|
progress >= 50 ? '[&>div]:bg-orange-500' :
|
|
'[&>div]:bg-green-600'
|
|
}`}
|
|
data-testid={`${testId}-progress-bar`}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|