107 lines
3.6 KiB
TypeScript
107 lines
3.6 KiB
TypeScript
import { Badge } from '@/components/ui/badge';
|
||
import { Progress } from '@/components/ui/progress';
|
||
import { Clock } from 'lucide-react';
|
||
|
||
export interface SLAData {
|
||
status: 'normal' | 'approaching' | 'critical' | 'breached';
|
||
percentageUsed: number;
|
||
elapsedText: string;
|
||
elapsedHours: number;
|
||
remainingText: string;
|
||
remainingHours: number;
|
||
deadline?: string;
|
||
}
|
||
|
||
interface SLAProgressBarProps {
|
||
sla: SLAData | null;
|
||
requestStatus: string;
|
||
testId?: string;
|
||
}
|
||
|
||
export function SLAProgressBar({
|
||
sla,
|
||
requestStatus,
|
||
testId = 'sla-progress'
|
||
}: SLAProgressBarProps) {
|
||
// If request is closed/approved/rejected or no SLA data, show status message
|
||
if (!sla || requestStatus === 'approved' || requestStatus === 'rejected' || requestStatus === 'closed') {
|
||
return (
|
||
<div className="flex items-center gap-2" data-testid={`${testId}-status-only`}>
|
||
<Clock className="h-4 w-4 text-gray-500" />
|
||
<span className="text-sm font-medium text-gray-700">
|
||
{requestStatus === 'closed' ? '🔒 Request Closed' :
|
||
requestStatus === 'approved' ? '✅ Request Approved' :
|
||
requestStatus === 'rejected' ? '❌ Request Rejected' : 'SLA Not Available'}
|
||
</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div data-testid={testId}>
|
||
<div className="flex items-center justify-between mb-2">
|
||
<div className="flex items-center gap-2">
|
||
<Clock className="h-4 w-4 text-blue-600" />
|
||
<span className="text-sm font-semibold text-gray-900">SLA Progress</span>
|
||
</div>
|
||
<Badge
|
||
className={`text-xs ${
|
||
sla.status === 'breached' ? 'bg-red-600 text-white animate-pulse' :
|
||
sla.status === 'critical' ? 'bg-orange-600 text-white' :
|
||
sla.status === 'approaching' ? 'bg-yellow-600 text-white' :
|
||
'bg-green-600 text-white'
|
||
}`}
|
||
data-testid={`${testId}-badge`}
|
||
>
|
||
{sla.percentageUsed || 0}% elapsed
|
||
</Badge>
|
||
</div>
|
||
|
||
<Progress
|
||
value={sla.percentageUsed || 0}
|
||
className={`h-3 mb-2 ${
|
||
sla.status === 'breached' ? '[&>div]:bg-red-600' :
|
||
sla.status === 'critical' ? '[&>div]:bg-orange-600' :
|
||
sla.status === 'approaching' ? '[&>div]:bg-yellow-600' :
|
||
'[&>div]:bg-green-600'
|
||
}`}
|
||
data-testid={`${testId}-bar`}
|
||
/>
|
||
|
||
<div className="flex items-center justify-between text-xs mb-1">
|
||
<span className="text-gray-600" data-testid={`${testId}-elapsed`}>
|
||
{sla.elapsedText || `${sla.elapsedHours || 0}h`} elapsed
|
||
</span>
|
||
<span
|
||
className={`font-semibold ${
|
||
sla.status === 'breached' ? 'text-red-600' :
|
||
sla.status === 'critical' ? 'text-orange-600' :
|
||
'text-gray-700'
|
||
}`}
|
||
data-testid={`${testId}-remaining`}
|
||
>
|
||
{sla.remainingText || `${sla.remainingHours || 0}h`} remaining
|
||
</span>
|
||
</div>
|
||
|
||
{sla.deadline && (
|
||
<p className="text-xs text-gray-500" data-testid={`${testId}-deadline`}>
|
||
Due: {new Date(sla.deadline).toLocaleString()} • {sla.percentageUsed || 0}% elapsed
|
||
</p>
|
||
)}
|
||
|
||
{sla.status === 'critical' && (
|
||
<p className="text-xs text-orange-600 font-semibold mt-1" data-testid={`${testId}-warning-critical`}>
|
||
⚠️ Approaching Deadline
|
||
</p>
|
||
)}
|
||
{sla.status === 'breached' && (
|
||
<p className="text-xs text-red-600 font-semibold mt-1" data-testid={`${testId}-warning-breached`}>
|
||
🔴 URGENT - Deadline Passed
|
||
</p>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|