request detil page enhanced
This commit is contained in:
parent
ea6cd5151b
commit
ecf2556c64
@ -27,8 +27,12 @@ export function SLAProgressBar({
|
|||||||
isPaused = false,
|
isPaused = false,
|
||||||
testId = 'sla-progress'
|
testId = 'sla-progress'
|
||||||
}: SLAProgressBarProps) {
|
}: SLAProgressBarProps) {
|
||||||
|
// Pure presentational component - no business logic
|
||||||
// If request is closed/approved/rejected or no SLA data, show status message
|
// If request is closed/approved/rejected or no SLA data, show status message
|
||||||
if (!sla || requestStatus === 'approved' || requestStatus === 'rejected' || requestStatus === 'closed') {
|
// Check if SLA has required fields (percentageUsed or at least some data)
|
||||||
|
const hasValidSLA = sla && (sla.percentageUsed !== undefined || sla.percent !== undefined || sla.elapsedHours !== undefined);
|
||||||
|
|
||||||
|
if (!hasValidSLA || requestStatus === 'approved' || requestStatus === 'rejected' || requestStatus === 'closed') {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2" data-testid={`${testId}-status-only`}>
|
<div className="flex items-center gap-2" data-testid={`${testId}-status-only`}>
|
||||||
{requestStatus === 'closed' ? <Lock className="h-4 w-4 text-gray-600" /> :
|
{requestStatus === 'closed' ? <Lock className="h-4 w-4 text-gray-600" /> :
|
||||||
@ -47,7 +51,8 @@ export function SLAProgressBar({
|
|||||||
// Use percentage-based colors to match approver SLA tracker
|
// Use percentage-based colors to match approver SLA tracker
|
||||||
// Green: 0-50%, Amber: 50-75%, Orange: 75-100%, Red: 100%+ (breached)
|
// Green: 0-50%, Amber: 50-75%, Orange: 75-100%, Red: 100%+ (breached)
|
||||||
// Grey: When paused (frozen state)
|
// Grey: When paused (frozen state)
|
||||||
const percentageUsed = sla.percentageUsed || 0;
|
// Handle both full format (percentageUsed) and simplified format (percent)
|
||||||
|
const percentageUsed = sla.percentageUsed !== undefined ? sla.percentageUsed : (sla.percent || 0);
|
||||||
const rawStatus = sla.status || 'on_track';
|
const rawStatus = sla.status || 'on_track';
|
||||||
|
|
||||||
// Determine colors based on percentage (matching ApprovalStepCard logic)
|
// Determine colors based on percentage (matching ApprovalStepCard logic)
|
||||||
@ -117,12 +122,12 @@ export function SLAProgressBar({
|
|||||||
className={`text-xs ${colors.badge}`}
|
className={`text-xs ${colors.badge}`}
|
||||||
data-testid={`${testId}-badge`}
|
data-testid={`${testId}-badge`}
|
||||||
>
|
>
|
||||||
{sla.percentageUsed || 0}% elapsed {isPaused && '(frozen)'}
|
{percentageUsed}% elapsed {isPaused && '(frozen)'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Progress
|
<Progress
|
||||||
value={sla.percentageUsed || 0}
|
value={percentageUsed}
|
||||||
className="h-3 mb-2"
|
className="h-3 mb-2"
|
||||||
indicatorClassName={colors.progress}
|
indicatorClassName={colors.progress}
|
||||||
data-testid={`${testId}-bar`}
|
data-testid={`${testId}-bar`}
|
||||||
@ -130,7 +135,7 @@ export function SLAProgressBar({
|
|||||||
|
|
||||||
<div className="flex items-center justify-between text-xs mb-1">
|
<div className="flex items-center justify-between text-xs mb-1">
|
||||||
<span className="text-gray-600" data-testid={`${testId}-elapsed`}>
|
<span className="text-gray-600" data-testid={`${testId}-elapsed`}>
|
||||||
{sla.elapsedText || formatHoursMinutes(sla.elapsedHours || 0)} elapsed
|
{formatHoursMinutes(sla.elapsedHours || 0)} elapsed
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`font-semibold ${
|
className={`font-semibold ${
|
||||||
@ -146,7 +151,7 @@ export function SLAProgressBar({
|
|||||||
|
|
||||||
{sla.deadline && (
|
{sla.deadline && (
|
||||||
<p className="text-xs text-gray-500" data-testid={`${testId}-deadline`}>
|
<p className="text-xs text-gray-500" data-testid={`${testId}-deadline`}>
|
||||||
Due: {formatDateDDMMYYYY(sla.deadline, true)} • {sla.percentageUsed || 0}% elapsed
|
Due: {formatDateDDMMYYYY(sla.deadline, true)} • {percentageUsed}% elapsed
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -372,6 +372,9 @@ function CustomRequestDetailInner({ requestId: propRequestId, onBack, dynamicReq
|
|||||||
onRefresh={handleRefresh}
|
onRefresh={handleRefresh}
|
||||||
onShareSummary={handleShareSummary}
|
onShareSummary={handleShareSummary}
|
||||||
isInitiator={isInitiator}
|
isInitiator={isInitiator}
|
||||||
|
// Custom module: Business logic for preparing SLA data
|
||||||
|
slaData={request?.summary?.sla || request?.sla || null}
|
||||||
|
isPaused={request?.pauseInfo?.isPaused || false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
|
|||||||
@ -101,7 +101,12 @@ export function ClaimManagementOverviewTab({
|
|||||||
return (
|
return (
|
||||||
<div className={`space-y-6 ${className}`}>
|
<div className={`space-y-6 ${className}`}>
|
||||||
{/* Activity Information - Always visible */}
|
{/* Activity Information - Always visible */}
|
||||||
<ActivityInformationCard activityInfo={claimRequest.activityInfo} />
|
{/* Dealer-claim module: Business logic for preparing timestamp data */}
|
||||||
|
<ActivityInformationCard
|
||||||
|
activityInfo={claimRequest.activityInfo}
|
||||||
|
createdAt={apiRequest?.createdAt}
|
||||||
|
updatedAt={apiRequest?.updatedAt}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Dealer Information - Always visible */}
|
{/* Dealer Information - Always visible */}
|
||||||
<DealerInformationCard dealerInfo={claimRequest.dealerInfo} />
|
<DealerInformationCard dealerInfo={claimRequest.dealerInfo} />
|
||||||
|
|||||||
@ -9,8 +9,10 @@ import { useState, useEffect } from 'react';
|
|||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { TrendingUp, Clock, CheckCircle, CircleCheckBig, Upload, Mail, Download, Receipt, Activity } from 'lucide-react';
|
import { Progress } from '@/components/ui/progress';
|
||||||
import { formatDateTime } from '@/utils/dateFormatter';
|
import { TrendingUp, Clock, CheckCircle, CircleCheckBig, Upload, Mail, Download, Receipt, Activity, AlertTriangle, AlertOctagon } from 'lucide-react';
|
||||||
|
import { formatDateTime, formatDateDDMMYYYY } from '@/utils/dateFormatter';
|
||||||
|
import { formatHoursMinutes } from '@/utils/slaTracker';
|
||||||
import { DealerProposalSubmissionModal } from './modals';
|
import { DealerProposalSubmissionModal } from './modals';
|
||||||
import { InitiatorProposalApprovalModal } from './modals';
|
import { InitiatorProposalApprovalModal } from './modals';
|
||||||
import { DeptLeadIOApprovalModal } from './modals';
|
import { DeptLeadIOApprovalModal } from './modals';
|
||||||
@ -337,6 +339,17 @@ export function DealerClaimWorkflowTab({
|
|||||||
normalizedStatus = 'in_progress';
|
normalizedStatus = 'in_progress';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Business logic: Only show elapsed time for active or completed steps
|
||||||
|
// Waiting steps (future steps) should have elapsedHours = 0
|
||||||
|
// This ensures that when in step 1, only step 1 shows elapsed time, others show 0
|
||||||
|
const isWaiting = normalizedStatus === 'waiting';
|
||||||
|
const isActive = normalizedStatus === 'pending' || normalizedStatus === 'in_progress';
|
||||||
|
const isCompleted = normalizedStatus === 'approved' || normalizedStatus === 'rejected';
|
||||||
|
|
||||||
|
// Only calculate/show elapsed hours for active or completed steps
|
||||||
|
// For waiting steps, elapsedHours should be 0 (they haven't started yet)
|
||||||
|
const elapsedHours = isWaiting ? 0 : (step.elapsedHours || 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
step: step.step || index + 1,
|
step: step.step || index + 1,
|
||||||
title: stepTitles[index] || `Step ${step.step || index + 1}`,
|
title: stepTitles[index] || `Step ${step.step || index + 1}`,
|
||||||
@ -346,7 +359,7 @@ export function DealerClaimWorkflowTab({
|
|||||||
status: normalizedStatus as any,
|
status: normalizedStatus as any,
|
||||||
comment: step.comment || approval?.comment,
|
comment: step.comment || approval?.comment,
|
||||||
approvedAt: step.approvedAt || approval?.timestamp,
|
approvedAt: step.approvedAt || approval?.timestamp,
|
||||||
elapsedHours: step.elapsedHours,
|
elapsedHours, // Only non-zero for active/completed steps
|
||||||
ioDetails,
|
ioDetails,
|
||||||
dmsDetails,
|
dmsDetails,
|
||||||
einvoiceUrl: step.step === 7 ? (approval as any)?.einvoiceUrl : undefined,
|
einvoiceUrl: step.step === 7 ? (approval as any)?.einvoiceUrl : undefined,
|
||||||
@ -817,6 +830,19 @@ export function DealerClaimWorkflowTab({
|
|||||||
const isActive = (step.status === 'pending' || step.status === 'in_progress') && step.step === currentStep;
|
const isActive = (step.status === 'pending' || step.status === 'in_progress') && step.step === currentStep;
|
||||||
const isCompleted = step.status === 'approved';
|
const isCompleted = step.status === 'approved';
|
||||||
|
|
||||||
|
// Find approval data for this step to get SLA information
|
||||||
|
// First find the corresponding level in approvalFlow to get levelId
|
||||||
|
const stepLevel = approvalFlow.find((l: any) => (l.step || l.levelNumber || l.level_number) === step.step);
|
||||||
|
const approval = stepLevel?.levelId
|
||||||
|
? request?.approvals?.find((a: any) => a.levelId === stepLevel.levelId || a.level_id === stepLevel.levelId)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Check if step is paused
|
||||||
|
const isPaused = approval?.status === 'PAUSED' ||
|
||||||
|
(request?.pauseInfo?.isPaused &&
|
||||||
|
(request?.pauseInfo?.levelId === approval?.levelId ||
|
||||||
|
request?.pauseInfo?.level_id === approval?.levelId));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
@ -871,10 +897,11 @@ export function DealerClaimWorkflowTab({
|
|||||||
<p className="text-sm text-gray-500 mt-2 italic">{step.description}</p>
|
<p className="text-sm text-gray-500 mt-2 italic">{step.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<p className="text-xs text-gray-500">TAT: {step.tatHours}h</p>
|
<p className="text-xs text-gray-500">TAT: {formatHoursMinutes(step.tatHours)}</p>
|
||||||
{step.elapsedHours && (
|
{/* Only show elapsed time for active or completed steps, not for waiting steps */}
|
||||||
|
{step.elapsedHours && (isActive || isCompleted) && (
|
||||||
<p className="text-xs text-gray-600 font-medium">
|
<p className="text-xs text-gray-600 font-medium">
|
||||||
Elapsed: {step.elapsedHours}h
|
Elapsed: {formatHoursMinutes(step.elapsedHours)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -887,6 +914,96 @@ export function DealerClaimWorkflowTab({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Active Approver - SLA Time Tracking (Only show for current active step) */}
|
||||||
|
{isActive && approval?.sla && (
|
||||||
|
<div className="mt-3 space-y-3">
|
||||||
|
<div className="flex items-center justify-between text-xs">
|
||||||
|
<span className="text-gray-600">Due by:</span>
|
||||||
|
<span className="font-medium text-gray-900">
|
||||||
|
{approval.sla.deadline ? formatDateDDMMYYYY(approval.sla.deadline, true) : 'Not set'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Current Approver - Time Tracking */}
|
||||||
|
<div className={`border rounded-lg p-3 ${
|
||||||
|
isPaused ? 'bg-gray-100 border-gray-300' :
|
||||||
|
(approval.sla.percentageUsed || 0) >= 100 ? 'bg-red-50 border-red-200' :
|
||||||
|
(approval.sla.percentageUsed || 0) >= 75 ? 'bg-orange-50 border-orange-200' :
|
||||||
|
(approval.sla.percentageUsed || 0) >= 50 ? 'bg-amber-50 border-amber-200' :
|
||||||
|
'bg-green-50 border-green-200'
|
||||||
|
}`}>
|
||||||
|
<p className="text-xs font-semibold text-gray-900 mb-3 flex items-center gap-2">
|
||||||
|
<Clock className="w-4 h-4" />
|
||||||
|
Current Approver - Time Tracking {isPaused && '(Paused)'}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-2 text-xs mb-3">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-gray-600">Time elapsed since assigned:</span>
|
||||||
|
<span className="font-medium text-gray-900">{approval.sla.elapsedText || '0 hours'}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-gray-600">Time used:</span>
|
||||||
|
<span className="font-medium text-gray-900">
|
||||||
|
{approval.sla.elapsedText || '0 hours'} / {formatHoursMinutes(step.tatHours)} allocated
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Progress Bar */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
{(() => {
|
||||||
|
const percentUsed = approval.sla.percentageUsed || 0;
|
||||||
|
const getActiveIndicatorColor = () => {
|
||||||
|
if (isPaused) return 'bg-gray-500';
|
||||||
|
if (percentUsed >= 100) return 'bg-red-600';
|
||||||
|
if (percentUsed >= 75) return 'bg-orange-500';
|
||||||
|
if (percentUsed >= 50) return 'bg-amber-500';
|
||||||
|
return 'bg-green-600';
|
||||||
|
};
|
||||||
|
const getActiveTextColor = () => {
|
||||||
|
if (isPaused) return 'text-gray-600';
|
||||||
|
if (percentUsed >= 100) return 'text-red-600';
|
||||||
|
if (percentUsed >= 75) return 'text-orange-600';
|
||||||
|
if (percentUsed >= 50) return 'text-amber-600';
|
||||||
|
return 'text-green-600';
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Progress
|
||||||
|
value={percentUsed}
|
||||||
|
className="h-3"
|
||||||
|
indicatorClassName={getActiveIndicatorColor()}
|
||||||
|
/>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className={`text-xs font-semibold ${getActiveTextColor()}`}>
|
||||||
|
Progress: {Math.min(100, percentUsed)}% of TAT used
|
||||||
|
</span>
|
||||||
|
<span className="text-xs font-medium text-gray-700">
|
||||||
|
{approval.sla.remainingText || '0 hours'} remaining
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
|
||||||
|
{approval.sla.status === 'breached' && (
|
||||||
|
<p className="text-xs font-semibold text-center text-red-600 flex items-center justify-center gap-1.5">
|
||||||
|
<AlertOctagon className="w-4 h-4" />
|
||||||
|
Deadline Breached
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{approval.sla.status === 'critical' && (
|
||||||
|
<p className="text-xs font-semibold text-center text-orange-600 flex items-center justify-center gap-1.5">
|
||||||
|
<AlertTriangle className="w-4 h-4" />
|
||||||
|
Approaching Deadline
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* IO Organization Details (Step 3) - Show when step is approved and has IO details */}
|
{/* IO Organization Details (Step 3) - Show when step is approved and has IO details */}
|
||||||
{step.step === 3 && step.status === 'approved' && step.ioDetails && step.ioDetails.ioNumber && (
|
{step.step === 3 && step.status === 'approved' && step.ioDetails && step.ioDetails.ioNumber && (
|
||||||
<div className="mt-3 p-3 bg-blue-50 rounded-lg border border-blue-200">
|
<div className="mt-3 p-3 bg-blue-50 rounded-lg border border-blue-200">
|
||||||
|
|||||||
@ -7,13 +7,22 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|||||||
import { Calendar, MapPin, DollarSign, Receipt } from 'lucide-react';
|
import { Calendar, MapPin, DollarSign, Receipt } from 'lucide-react';
|
||||||
import { ClaimActivityInfo } from '@/pages/RequestDetail/types/claimManagement.types';
|
import { ClaimActivityInfo } from '@/pages/RequestDetail/types/claimManagement.types';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
|
import { formatDateTime } from '@/utils/dateFormatter';
|
||||||
|
|
||||||
interface ActivityInformationCardProps {
|
interface ActivityInformationCardProps {
|
||||||
activityInfo: ClaimActivityInfo;
|
activityInfo: ClaimActivityInfo;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
// Plug-and-play: Pass timestamps from module's business logic
|
||||||
|
createdAt?: string | Date;
|
||||||
|
updatedAt?: string | Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ActivityInformationCard({ activityInfo, className }: ActivityInformationCardProps) {
|
export function ActivityInformationCard({
|
||||||
|
activityInfo,
|
||||||
|
className,
|
||||||
|
createdAt,
|
||||||
|
updatedAt
|
||||||
|
}: ActivityInformationCardProps) {
|
||||||
// Defensive check: Ensure activityInfo exists
|
// Defensive check: Ensure activityInfo exists
|
||||||
if (!activityInfo) {
|
if (!activityInfo) {
|
||||||
console.warn('[ActivityInformationCard] activityInfo is missing');
|
console.warn('[ActivityInformationCard] activityInfo is missing');
|
||||||
@ -169,6 +178,24 @@ export function ActivityInformationCard({ activityInfo, className }: ActivityInf
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Timestamps - Similar to Request Details Card */}
|
||||||
|
{(createdAt || updatedAt) && (
|
||||||
|
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-gray-300">
|
||||||
|
{createdAt && (
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Created</label>
|
||||||
|
<p className="text-sm text-gray-900 font-medium mt-1">{formatDateTime(createdAt)}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{updatedAt && (
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Last Updated</label>
|
||||||
|
<p className="text-sm text-gray-900 font-medium mt-1">{formatDateTime(updatedAt)}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -410,6 +410,9 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
|
|||||||
onRefresh={handleRefresh}
|
onRefresh={handleRefresh}
|
||||||
onShareSummary={handleShareSummary}
|
onShareSummary={handleShareSummary}
|
||||||
isInitiator={isInitiator}
|
isInitiator={isInitiator}
|
||||||
|
// Dealer-claim module: Business logic for preparing SLA data
|
||||||
|
slaData={request?.summary?.sla || request?.sla || null}
|
||||||
|
isPaused={request?.pauseInfo?.isPaused || false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
|
|||||||
@ -331,6 +331,9 @@ export function useRequestDetails(
|
|||||||
documents: mappedDocuments,
|
documents: mappedDocuments,
|
||||||
spectators,
|
spectators,
|
||||||
summary, // Backend-provided SLA summary
|
summary, // Backend-provided SLA summary
|
||||||
|
// Ensure SLA is available at root level for RequestDetailHeader
|
||||||
|
// Backend provides full SLA in summary.sla with all required fields
|
||||||
|
sla: summary?.sla || wf.sla || null,
|
||||||
initiator: {
|
initiator: {
|
||||||
name: wf.initiator?.displayName || wf.initiator?.email,
|
name: wf.initiator?.displayName || wf.initiator?.email,
|
||||||
role: wf.initiator?.designation || undefined,
|
role: wf.initiator?.designation || undefined,
|
||||||
|
|||||||
@ -15,12 +15,23 @@ interface RequestDetailHeaderProps {
|
|||||||
onRefresh: () => void;
|
onRefresh: () => void;
|
||||||
onShareSummary?: () => void;
|
onShareSummary?: () => void;
|
||||||
isInitiator?: boolean;
|
isInitiator?: boolean;
|
||||||
|
// Plug-and-play: Pass SLA data directly from module
|
||||||
|
slaData?: any; // SLAData | null - passed from module's business logic
|
||||||
|
isPaused?: boolean; // Pass pause status from module
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RequestDetailHeader({ request, refreshing, onBack, onRefresh, onShareSummary, isInitiator }: RequestDetailHeaderProps) {
|
export function RequestDetailHeader({
|
||||||
|
request,
|
||||||
|
refreshing,
|
||||||
|
onBack,
|
||||||
|
onRefresh,
|
||||||
|
onShareSummary,
|
||||||
|
isInitiator,
|
||||||
|
slaData, // Module passes prepared SLA data
|
||||||
|
isPaused = false // Module passes pause status
|
||||||
|
}: RequestDetailHeaderProps) {
|
||||||
const priorityConfig = getPriorityConfig(request?.priority || 'standard');
|
const priorityConfig = getPriorityConfig(request?.priority || 'standard');
|
||||||
const statusConfig = getStatusConfig(request?.status || 'pending');
|
const statusConfig = getStatusConfig(request?.status || 'pending');
|
||||||
const isPaused = request?.pauseInfo?.isPaused || false;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-lg shadow-sm border border-gray-300 mb-4 sm:mb-6" data-testid="request-detail-header">
|
<div className="bg-white rounded-lg shadow-sm border border-gray-300 mb-4 sm:mb-6" data-testid="request-detail-header">
|
||||||
@ -129,17 +140,19 @@ export function RequestDetailHeader({ request, refreshing, onBack, onRefresh, on
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* SLA Progress Section */}
|
{/* SLA Progress Section - Plug-and-play: Module passes prepared SLA data */}
|
||||||
|
{slaData !== undefined && (
|
||||||
<div className={`px-3 sm:px-4 md:px-6 py-3 sm:py-4 border-b border-gray-200 ${
|
<div className={`px-3 sm:px-4 md:px-6 py-3 sm:py-4 border-b border-gray-200 ${
|
||||||
isPaused ? 'bg-gradient-to-r from-gray-100 to-gray-200' : 'bg-gradient-to-r from-blue-50 to-indigo-50'
|
isPaused ? 'bg-gradient-to-r from-gray-100 to-gray-200' : 'bg-gradient-to-r from-blue-50 to-indigo-50'
|
||||||
}`} data-testid="sla-section">
|
}`} data-testid="sla-section">
|
||||||
<SLAProgressBar
|
<SLAProgressBar
|
||||||
sla={request.summary?.sla || request.sla}
|
sla={slaData}
|
||||||
requestStatus={request.status}
|
requestStatus={request.status}
|
||||||
isPaused={isPaused}
|
isPaused={isPaused}
|
||||||
testId="request-sla"
|
testId="request-sla"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user