/** * Dealer Claim Request Detail Screen * * Standalone, dedicated request detail screen for Dealer Claim requests. * This is a complete module that uses dealer claim specific components. * * LOCATION: src/dealer-claim/pages/RequestDetail.tsx * * IMPORTANT: This entire file and all its dependencies are in src/dealer-claim/ folder. * Deleting src/dealer-claim/ folder removes ALL dealer claim related code. */ import { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { Component, ErrorInfo, ReactNode } from 'react'; import { Button } from '@/components/ui/button'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { ClipboardList, TrendingUp, FileText, Activity, MessageSquare, AlertTriangle, FileCheck, ShieldX, RefreshCw, ArrowLeft, DollarSign, } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; // Context and hooks import { useAuth } from '@/contexts/AuthContext'; import { useRequestDetails } from '@/hooks/useRequestDetails'; import { useRequestSocket } from '@/hooks/useRequestSocket'; import { useDocumentUpload } from '@/hooks/useDocumentUpload'; import { useModalManager } from '@/hooks/useModalManager'; import { useConclusionRemark } from '@/hooks/useConclusionRemark'; import { downloadDocument } from '@/services/workflowApi'; import { getSocket, joinUserRoom } from '@/utils/socket'; // Dealer Claim Components (import from index to get properly aliased exports) import { DealerClaimOverviewTab, DealerClaimWorkflowTab, IOTab } from '../index'; // Shared Components import { SharedComponents } from '@/shared/components'; const { DocumentsTab, ActivityTab, WorkNotesTab, SummaryTab, RequestDetailHeader, QuickActionsSidebar, RequestDetailModals } = SharedComponents; // Other components import { ShareSummaryModal } from '@/components/modals/ShareSummaryModal'; import { getSummaryDetails, getSummaryByRequestId, type SummaryDetails } from '@/services/summaryApi'; import { toast } from 'sonner'; import { RequestDetailProps } from '@/pages/RequestDetail/types/requestDetail.types'; import { PauseModal } from '@/components/workflow/PauseModal'; import { ResumeModal } from '@/components/workflow/ResumeModal'; import { RetriggerPauseModal } from '@/components/workflow/RetriggerPauseModal'; /** * Error Boundary Component */ class RequestDetailErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean; error: Error | null }> { constructor(props: { children: ReactNode }) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error: Error) { return { hasError: true, error }; } override componentDidCatch(error: Error, errorInfo: ErrorInfo) { console.error('Dealer Claim RequestDetail Error:', error, errorInfo); } override render() { if (this.state.hasError) { return (

Error Loading Request

{this.state.error?.message || 'An unexpected error occurred'}

); } return this.props.children; } } /** * Dealer Claim RequestDetailInner Component */ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynamicRequests = [] }: RequestDetailProps) { const params = useParams<{ requestId: string }>(); const requestIdentifier = params.requestId || propRequestId || ''; const urlParams = new URLSearchParams(window.location.search); const initialTab = urlParams.get('tab') || 'overview'; const [activeTab, setActiveTab] = useState(initialTab); const [showShareSummaryModal, setShowShareSummaryModal] = useState(false); const [summaryId, setSummaryId] = useState(null); const [summaryDetails, setSummaryDetails] = useState(null); const [loadingSummary, setLoadingSummary] = useState(false); const [sharedRecipientsRefreshTrigger, setSharedRecipientsRefreshTrigger] = useState(0); const [showPauseModal, setShowPauseModal] = useState(false); const [showResumeModal, setShowResumeModal] = useState(false); const [showRetriggerModal, setShowRetriggerModal] = useState(false); const { user } = useAuth(); // Custom hooks const { request, apiRequest, loading: requestLoading, refreshing, refreshDetails, currentApprovalLevel, isSpectator, isInitiator, existingParticipants, accessDenied, } = useRequestDetails(requestIdentifier, dynamicRequests, user); // Determine if user is department lead (find dynamically by levelName, not hardcoded step number) const currentUserId = (user as any)?.userId || ''; const currentUserEmail = (user as any)?.email?.toLowerCase() || ''; // Use approvalFlow (transformed) or approvals (raw) - both have step/levelNumber const approvalFlow = apiRequest?.approvalFlow || []; const approvals = apiRequest?.approvals || []; // Find Department Lead step dynamically by levelName (handles step shifts when approvers are added) const deptLeadLevel = approvalFlow.find((level: any) => { const levelName = (level.levelName || level.level_name || '').toLowerCase(); return levelName.includes('department lead'); }) || approvals.find((level: any) => { const levelName = (level.levelName || level.level_name || '').toLowerCase(); return levelName.includes('department lead'); }) || approvalFlow.find((level: any) => (level.step || level.levelNumber || level.level_number) === 3 ) || approvals.find((level: any) => (level.levelNumber || level.level_number) === 3 ); // Fallback to step 3 for backwards compatibility const deptLeadUserId = deptLeadLevel?.approverId || deptLeadLevel?.approver_id || deptLeadLevel?.approver?.userId; const deptLeadEmail = (deptLeadLevel?.approverEmail || deptLeadLevel?.approver_email || deptLeadLevel?.approver?.email || '').toLowerCase().trim(); // User is department lead if they match the Department Lead approver (regardless of status or step number) const isDeptLead = (deptLeadUserId && deptLeadUserId === currentUserId) || (deptLeadEmail && currentUserEmail && deptLeadEmail === currentUserEmail); // IO tab visibility for dealer claims // Show IO tab only for department lead (found dynamically, not hardcoded to step 3) const showIOTab = isDeptLead; const { mergedMessages, unreadWorkNotes, workNoteAttachments, setWorkNoteAttachments, } = useRequestSocket(requestIdentifier, apiRequest, activeTab, user); const { uploadingDocument, triggerFileInput, previewDocument, setPreviewDocument, documentPolicy, documentError, setDocumentError, } = useDocumentUpload(apiRequest, refreshDetails); const { showApproveModal, setShowApproveModal, showRejectModal, setShowRejectModal, showAddApproverModal, setShowAddApproverModal, showAddSpectatorModal, setShowAddSpectatorModal, showSkipApproverModal, setShowSkipApproverModal, showActionStatusModal, setShowActionStatusModal, skipApproverData, setSkipApproverData, actionStatus, setActionStatus, handleApproveConfirm, handleRejectConfirm, handleAddApprover, handleSkipApprover, handleAddSpectator, } = useModalManager(requestIdentifier, currentApprovalLevel, refreshDetails); // Closure functionality - only for initiator when request is approved/rejected // Check both lowercase and uppercase status values const requestStatus = (request?.status || apiRequest?.status || '').toLowerCase(); const needsClosure = (requestStatus === 'approved' || requestStatus === 'rejected') && isInitiator; // Debug logging console.debug('[DealerClaimRequestDetail] Closure check:', { requestStatus, requestStatusRaw: request?.status, apiRequestStatusRaw: apiRequest?.status, isInitiator, needsClosure, }); const { conclusionRemark, setConclusionRemark, conclusionLoading, conclusionSubmitting, aiGenerated, handleGenerateConclusion, handleFinalizeConclusion, } = useConclusionRemark( request, requestIdentifier, isInitiator, refreshDetails, onBack, setActionStatus, setShowActionStatusModal ); // Auto-switch tab when URL query parameter changes useEffect(() => { const urlParams = new URLSearchParams(window.location.search); const tabParam = urlParams.get('tab'); if (tabParam) { setActiveTab(tabParam); } }, [requestIdentifier]); const handleRefresh = () => { refreshDetails(); }; // Pause handlers const handlePause = () => { setShowPauseModal(true); }; const handleResume = () => { setShowResumeModal(true); }; const handleResumeSuccess = async () => { await refreshDetails(); }; const handleRetrigger = () => { setShowRetriggerModal(true); }; const handlePauseSuccess = async () => { await refreshDetails(); }; const handleRetriggerSuccess = async () => { await refreshDetails(); }; const handleShareSummary = async () => { if (!apiRequest?.requestId) { toast.error('Request ID not found'); return; } if (!summaryId) { toast.error('Summary not available. Please ensure the request is closed and the summary has been generated.'); return; } setShowShareSummaryModal(true); }; const isClosed = request?.status === 'closed' || (request?.status === 'approved' && !isInitiator) || (request?.status === 'rejected' && !isInitiator); // Fetch summary details if request is closed useEffect(() => { const fetchSummaryDetails = async () => { if (!isClosed || !apiRequest?.requestId) { setSummaryDetails(null); setSummaryId(null); return; } try { setLoadingSummary(true); const summary = await getSummaryByRequestId(apiRequest.requestId); if (summary?.summaryId) { setSummaryId(summary.summaryId); try { const details = await getSummaryDetails(summary.summaryId); setSummaryDetails(details); } catch (error: any) { console.error('Failed to fetch summary details:', error); setSummaryDetails(null); setSummaryId(null); } } else { setSummaryDetails(null); setSummaryId(null); } } catch (error: any) { setSummaryDetails(null); setSummaryId(null); } finally { setLoadingSummary(false); } }; fetchSummaryDetails(); }, [isClosed, apiRequest?.requestId]); // Listen for credit note notifications and trigger silent refresh useEffect(() => { if (!currentUserId || !apiRequest?.requestId) return; const socket = getSocket(); if (!socket) return; joinUserRoom(socket, currentUserId); const handleNewNotification = (data: { notification: any }) => { const notif = data?.notification; if (!notif) return; const notifRequestId = notif.requestId || notif.request_id; const notifRequestNumber = notif.metadata?.requestNumber || notif.metadata?.request_number; if (notifRequestId !== apiRequest.requestId && notifRequestNumber !== requestIdentifier && notifRequestNumber !== apiRequest.requestNumber) return; // Check for credit note metadata if (notif.metadata?.creditNoteNumber || notif.metadata?.credit_note_number) { refreshDetails(); } }; socket.on('notification:new', handleNewNotification); return () => { socket.off('notification:new', handleNewNotification); }; }, [currentUserId, apiRequest?.requestId, requestIdentifier, refreshDetails]); // Get current levels for WorkNotesTab const currentLevels = (request?.approvalFlow || []) .filter((flow: any) => flow && typeof flow.step === 'number') .map((flow: any) => ({ levelNumber: flow.step || 0, approverName: flow.approver || 'Unknown', status: flow.status || 'pending', tatHours: flow.tatHours || 24, })); // Loading state if (requestLoading && !request && !apiRequest) { return (

Loading dealer claim request details...

); } // Access Denied state if (accessDenied?.denied) { return (

Access Denied

{accessDenied.message}

); } // Not Found state if (!request) { return (

Dealer Claim Request Not Found

The dealer claim request you're looking for doesn't exist or may have been deleted.

); } return ( <>
{/* Header Section */} window.history.back())} 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 */}
Overview {isClosed && summaryDetails && ( Summary )} Workflow {showIOTab && ( IO )} Docs Activity Work Notes {unreadWorkNotes > 0 && ( {unreadWorkNotes > 9 ? '9+' : unreadWorkNotes} )}
{/* Main Layout */}
{/* Left Column: Tab content */}
{isClosed && ( )} { if (!data.levelId) { alert('Level ID not available'); return; } setSkipApproverData(data); setShowSkipApproverModal(true); }} onRefresh={refreshDetails} /> {showIOTab && ( )}
{/* Right Column: Quick Actions Sidebar */} {activeTab !== 'worknotes' && ( setShowAddApproverModal(true)} onAddSpectator={() => setShowAddSpectatorModal(true)} onApprove={() => setShowApproveModal(true)} onReject={() => setShowRejectModal(true)} onPause={handlePause} onResume={handleResume} onRetrigger={handleRetrigger} summaryId={summaryId} refreshTrigger={sharedRecipientsRefreshTrigger} pausedByUserId={request?.pauseInfo?.pausedBy?.userId} currentUserId={currentUserId} apiRequest={apiRequest} /> )}
{/* Share Summary Modal */} {showShareSummaryModal && summaryId && ( setShowShareSummaryModal(false)} summaryId={summaryId} requestTitle={request?.title || 'N/A'} onSuccess={() => { refreshDetails(); setSharedRecipientsRefreshTrigger(prev => prev + 1); }} /> )} {/* Pause Modals */} {showPauseModal && apiRequest?.requestId && ( setShowPauseModal(false)} requestId={apiRequest.requestId} levelId={currentApprovalLevel?.levelId || null} onSuccess={handlePauseSuccess} /> )} {showResumeModal && apiRequest?.requestId && ( setShowResumeModal(false)} requestId={apiRequest.requestId} onSuccess={handleResumeSuccess} /> )} {showRetriggerModal && apiRequest?.requestId && ( setShowRetriggerModal(false)} requestId={apiRequest.requestId} approverName={request?.pauseInfo?.pausedBy?.name} onSuccess={handleRetriggerSuccess} /> )} {/* Modals */} ); } /** * Dealer Claim RequestDetail Component (Exported) */ export function DealerClaimRequestDetail(props: RequestDetailProps) { return ( ); }