From ecf2556c6492bde5ee769df4f8d2c8f0e89bc06d Mon Sep 17 00:00:00 2001
From: laxmanhalaki
Date: Wed, 17 Dec 2025 13:05:27 +0530
Subject: [PATCH] request detil page enhanced
---
.../sla/SLAProgressBar/SLAProgressBar.tsx | 17 ++-
src/custom/pages/RequestDetail.tsx | 3 +
.../components/request-detail/OverviewTab.tsx | 7 +-
.../components/request-detail/WorkflowTab.tsx | 129 +++++++++++++++++-
.../claim-cards/ActivityInformationCard.tsx | 29 +++-
src/dealer-claim/pages/RequestDetail.tsx | 3 +
src/hooks/useRequestDetails.ts | 3 +
.../components/RequestDetailHeader.tsx | 39 ++++--
8 files changed, 203 insertions(+), 27 deletions(-)
diff --git a/src/components/sla/SLAProgressBar/SLAProgressBar.tsx b/src/components/sla/SLAProgressBar/SLAProgressBar.tsx
index 6bc67bd..4e394b1 100644
--- a/src/components/sla/SLAProgressBar/SLAProgressBar.tsx
+++ b/src/components/sla/SLAProgressBar/SLAProgressBar.tsx
@@ -27,8 +27,12 @@ export function SLAProgressBar({
isPaused = false,
testId = 'sla-progress'
}: SLAProgressBarProps) {
+ // Pure presentational component - no business logic
// 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 (
{requestStatus === 'closed' ? :
@@ -47,7 +51,8 @@ export function SLAProgressBar({
// Use percentage-based colors to match approver SLA tracker
// Green: 0-50%, Amber: 50-75%, Orange: 75-100%, Red: 100%+ (breached)
// 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';
// Determine colors based on percentage (matching ApprovalStepCard logic)
@@ -117,12 +122,12 @@ export function SLAProgressBar({
className={`text-xs ${colors.badge}`}
data-testid={`${testId}-badge`}
>
- {sla.percentageUsed || 0}% elapsed {isPaused && '(frozen)'}
+ {percentageUsed}% elapsed {isPaused && '(frozen)'}
)}
diff --git a/src/custom/pages/RequestDetail.tsx b/src/custom/pages/RequestDetail.tsx
index 07e903a..7b910ba 100644
--- a/src/custom/pages/RequestDetail.tsx
+++ b/src/custom/pages/RequestDetail.tsx
@@ -372,6 +372,9 @@ function CustomRequestDetailInner({ requestId: propRequestId, onBack, dynamicReq
onRefresh={handleRefresh}
onShareSummary={handleShareSummary}
isInitiator={isInitiator}
+ // Custom module: Business logic for preparing SLA data
+ slaData={request?.summary?.sla || request?.sla || null}
+ isPaused={request?.pauseInfo?.isPaused || false}
/>
{/* Tabs */}
diff --git a/src/dealer-claim/components/request-detail/OverviewTab.tsx b/src/dealer-claim/components/request-detail/OverviewTab.tsx
index b49ee7d..485a96a 100644
--- a/src/dealer-claim/components/request-detail/OverviewTab.tsx
+++ b/src/dealer-claim/components/request-detail/OverviewTab.tsx
@@ -101,7 +101,12 @@ export function ClaimManagementOverviewTab({
return (
{/* Activity Information - Always visible */}
-
+ {/* Dealer-claim module: Business logic for preparing timestamp data */}
+
{/* Dealer Information - Always visible */}
diff --git a/src/dealer-claim/components/request-detail/WorkflowTab.tsx b/src/dealer-claim/components/request-detail/WorkflowTab.tsx
index fe8c713..d22f6d4 100644
--- a/src/dealer-claim/components/request-detail/WorkflowTab.tsx
+++ b/src/dealer-claim/components/request-detail/WorkflowTab.tsx
@@ -9,8 +9,10 @@ import { useState, useEffect } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
-import { TrendingUp, Clock, CheckCircle, CircleCheckBig, Upload, Mail, Download, Receipt, Activity } from 'lucide-react';
-import { formatDateTime } from '@/utils/dateFormatter';
+import { Progress } from '@/components/ui/progress';
+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 { InitiatorProposalApprovalModal } from './modals';
import { DeptLeadIOApprovalModal } from './modals';
@@ -337,6 +339,17 @@ export function DealerClaimWorkflowTab({
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 {
step: step.step || index + 1,
title: stepTitles[index] || `Step ${step.step || index + 1}`,
@@ -346,7 +359,7 @@ export function DealerClaimWorkflowTab({
status: normalizedStatus as any,
comment: step.comment || approval?.comment,
approvedAt: step.approvedAt || approval?.timestamp,
- elapsedHours: step.elapsedHours,
+ elapsedHours, // Only non-zero for active/completed steps
ioDetails,
dmsDetails,
einvoiceUrl: step.step === 7 ? (approval as any)?.einvoiceUrl : undefined,
@@ -816,6 +829,19 @@ export function DealerClaimWorkflowTab({
// Step is active if it's pending or in_progress and matches currentStep
const isActive = (step.status === 'pending' || step.status === 'in_progress') && step.step === currentStep;
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 (
{step.description}
-
TAT: {step.tatHours}h
- {step.elapsedHours && (
+
TAT: {formatHoursMinutes(step.tatHours)}
+ {/* Only show elapsed time for active or completed steps, not for waiting steps */}
+ {step.elapsedHours && (isActive || isCompleted) && (
- Elapsed: {step.elapsedHours}h
+ Elapsed: {formatHoursMinutes(step.elapsedHours)}
)}
@@ -887,6 +914,96 @@ export function DealerClaimWorkflowTab({
)}
+ {/* Active Approver - SLA Time Tracking (Only show for current active step) */}
+ {isActive && approval?.sla && (
+
+
+ Due by:
+
+ {approval.sla.deadline ? formatDateDDMMYYYY(approval.sla.deadline, true) : 'Not set'}
+
+
+
+ {/* Current Approver - Time Tracking */}
+
= 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'
+ }`}>
+
+
+ Current Approver - Time Tracking {isPaused && '(Paused)'}
+
+
+
+
+ Time elapsed since assigned:
+ {approval.sla.elapsedText || '0 hours'}
+
+
+ Time used:
+
+ {approval.sla.elapsedText || '0 hours'} / {formatHoursMinutes(step.tatHours)} allocated
+
+
+
+
+ {/* Progress Bar */}
+
+ {(() => {
+ 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: {Math.min(100, percentUsed)}% of TAT used
+
+
+ {approval.sla.remainingText || '0 hours'} remaining
+
+
+ >
+ );
+ })()}
+
+ {approval.sla.status === 'breached' && (
+
+
+ Deadline Breached
+
+ )}
+ {approval.sla.status === 'critical' && (
+
+
+ Approaching Deadline
+
+ )}
+
+
+
+ )}
+
{/* 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 && (
diff --git a/src/dealer-claim/components/request-detail/claim-cards/ActivityInformationCard.tsx b/src/dealer-claim/components/request-detail/claim-cards/ActivityInformationCard.tsx
index 96bc679..79a7306 100644
--- a/src/dealer-claim/components/request-detail/claim-cards/ActivityInformationCard.tsx
+++ b/src/dealer-claim/components/request-detail/claim-cards/ActivityInformationCard.tsx
@@ -7,13 +7,22 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Calendar, MapPin, DollarSign, Receipt } from 'lucide-react';
import { ClaimActivityInfo } from '@/pages/RequestDetail/types/claimManagement.types';
import { format } from 'date-fns';
+import { formatDateTime } from '@/utils/dateFormatter';
interface ActivityInformationCardProps {
activityInfo: ClaimActivityInfo;
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
if (!activityInfo) {
console.warn('[ActivityInformationCard] activityInfo is missing');
@@ -169,6 +178,24 @@ export function ActivityInformationCard({ activityInfo, className }: ActivityInf
)}
+
+ {/* Timestamps - Similar to Request Details Card */}
+ {(createdAt || updatedAt) && (
+
+ {createdAt && (
+
+
+
{formatDateTime(createdAt)}
+
+ )}
+ {updatedAt && (
+
+
+
{formatDateTime(updatedAt)}
+
+ )}
+
+ )}
);
diff --git a/src/dealer-claim/pages/RequestDetail.tsx b/src/dealer-claim/pages/RequestDetail.tsx
index 75c8088..dfe3f35 100644
--- a/src/dealer-claim/pages/RequestDetail.tsx
+++ b/src/dealer-claim/pages/RequestDetail.tsx
@@ -410,6 +410,9 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
onRefresh={handleRefresh}
onShareSummary={handleShareSummary}
isInitiator={isInitiator}
+ // Dealer-claim module: Business logic for preparing SLA data
+ slaData={request?.summary?.sla || request?.sla || null}
+ isPaused={request?.pauseInfo?.isPaused || false}
/>
{/* Tabs */}
diff --git a/src/hooks/useRequestDetails.ts b/src/hooks/useRequestDetails.ts
index 6d21f6e..8b5d439 100644
--- a/src/hooks/useRequestDetails.ts
+++ b/src/hooks/useRequestDetails.ts
@@ -331,6 +331,9 @@ export function useRequestDetails(
documents: mappedDocuments,
spectators,
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: {
name: wf.initiator?.displayName || wf.initiator?.email,
role: wf.initiator?.designation || undefined,
diff --git a/src/pages/RequestDetail/components/RequestDetailHeader.tsx b/src/pages/RequestDetail/components/RequestDetailHeader.tsx
index 288f1ea..0a88c9e 100644
--- a/src/pages/RequestDetail/components/RequestDetailHeader.tsx
+++ b/src/pages/RequestDetail/components/RequestDetailHeader.tsx
@@ -15,12 +15,23 @@ interface RequestDetailHeaderProps {
onRefresh: () => void;
onShareSummary?: () => void;
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 statusConfig = getStatusConfig(request?.status || 'pending');
- const isPaused = request?.pauseInfo?.isPaused || false;
return (
@@ -129,17 +140,19 @@ export function RequestDetailHeader({ request, refreshing, onBack, onRefresh, on
- {/* SLA Progress Section */}
-
-
-
+ {/* SLA Progress Section - Plug-and-play: Module passes prepared SLA data */}
+ {slaData !== undefined && (
+
+
+
+ )}
);
}