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)'}
- {sla.elapsedText || formatHoursMinutes(sla.elapsedHours || 0)} elapsed + {formatHoursMinutes(sla.elapsedHours || 0)} elapsed - Due: {formatDateDDMMYYYY(sla.deadline, true)} • {sla.percentageUsed || 0}% elapsed + Due: {formatDateDDMMYYYY(sla.deadline, true)} • {percentageUsed}% elapsed

)} 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 && ( +
+ +
+ )} ); }