/**
* 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 { getPublicConfigurations, AdminConfiguration } from '@/services/adminApi';
import { PolicyViolationModal } from '@/components/modals/PolicyViolationModal';
import { getSocket, joinUserRoom } from '@/utils/socket';
import { isClaimManagementRequest } from '@/utils/claimRequestUtils';
// 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 [systemPolicy, setSystemPolicy] = useState<{
maxApprovalLevels: number;
maxParticipants: number;
allowSpectators: boolean;
maxSpectators: number;
}>({
maxApprovalLevels: 10,
maxParticipants: 50,
allowSpectators: true,
maxSpectators: 20
});
const [policyViolationModal, setPolicyViolationModal] = useState<{
open: boolean;
violations: Array<{ type: string; message: string; currentValue?: number; maxValue?: number }>;
}>({
open: false,
violations: []
});
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 || '';
// IO tab visibility for dealer claims
// Show IO tab for initiator (Requestor Evaluation level) - initiator can now fetch and block IO
const showIOTab = isInitiator;
const {
mergedMessages,
unreadWorkNotes,
workNoteAttachments,
setWorkNoteAttachments,
} = useRequestSocket(requestIdentifier, apiRequest, activeTab, user);
const {
uploadingDocument,
triggerFileInput,
previewDocument,
setPreviewDocument,
documentPolicy,
documentError,
setDocumentError,
} = useDocumentUpload(apiRequest, refreshDetails);
// State to temporarily store approval level for modal (used for additional approvers)
const [temporaryApprovalLevel, setTemporaryApprovalLevel] = useState(null);
// Use temporary level if set, otherwise use currentApprovalLevel
const effectiveApprovalLevel = temporaryApprovalLevel || currentApprovalLevel;
const {
showApproveModal,
setShowApproveModal,
showRejectModal,
setShowRejectModal,
showAddApproverModal,
setShowAddApproverModal,
showAddSpectatorModal,
setShowAddSpectatorModal,
showSkipApproverModal,
setShowSkipApproverModal,
showActionStatusModal,
setShowActionStatusModal,
skipApproverData,
setSkipApproverData,
actionStatus,
setActionStatus,
handleApproveConfirm: originalHandleApproveConfirm,
handleRejectConfirm: originalHandleRejectConfirm,
handleAddApprover,
handleSkipApprover,
handleAddSpectator,
} = useModalManager(requestIdentifier, effectiveApprovalLevel, refreshDetails);
// Wrapper handlers that clear temporary level after action
const handleApproveConfirm = async (description: string) => {
await originalHandleApproveConfirm(description);
setTemporaryApprovalLevel(null);
};
const handleRejectConfirm = async (description: string) => {
await originalHandleRejectConfirm(description);
setTemporaryApprovalLevel(null);
};
// 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;
// Closure check completed
const {
conclusionRemark,
setConclusionRemark,
conclusionLoading,
conclusionSubmitting,
aiGenerated,
handleGenerateConclusion,
handleFinalizeConclusion,
generationAttempts,
generationFailed,
maxAttemptsReached,
} = useConclusionRemark(
request,
requestIdentifier,
isInitiator,
refreshDetails,
onBack,
setActionStatus,
setShowActionStatusModal
);
// Load system policy on mount
useEffect(() => {
const loadSystemPolicy = async () => {
try {
const systemSettingsConfigs = await getPublicConfigurations('SYSTEM_SETTINGS');
const workflowSharingConfigs = await getPublicConfigurations('WORKFLOW_SHARING');
const allConfigs = [...systemSettingsConfigs, ...workflowSharingConfigs];
const configMap: Record = {};
allConfigs.forEach((c: AdminConfiguration) => {
configMap[c.configKey] = c.configValue;
});
setSystemPolicy({
maxApprovalLevels: parseInt(configMap['MAX_APPROVAL_LEVELS'] || '10'),
maxParticipants: parseInt(configMap['MAX_PARTICIPANTS_PER_REQUEST'] || '50'),
allowSpectators: configMap['ALLOW_ADD_SPECTATOR']?.toLowerCase() === 'true',
maxSpectators: parseInt(configMap['MAX_SPECTATORS_PER_REQUEST'] || '20')
});
} catch (error) {
console.error('Failed to load system policy:', error);
}
};
loadSystemPolicy();
}, []);
// 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';
// 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 && (
)}
setPolicyViolationModal({ open: true, violations })}
/>
{/* 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 */}
setPolicyViolationModal({ open: true, violations })}
setShowApproveModal={setShowApproveModal}
setShowRejectModal={setShowRejectModal}
setShowAddApproverModal={setShowAddApproverModal}
setShowAddSpectatorModal={setShowAddSpectatorModal}
setShowSkipApproverModal={setShowSkipApproverModal}
setShowActionStatusModal={setShowActionStatusModal}
setPreviewDocument={setPreviewDocument}
setDocumentError={setDocumentError}
setSkipApproverData={setSkipApproverData}
setActionStatus={setActionStatus}
handleApproveConfirm={handleApproveConfirm}
handleRejectConfirm={handleRejectConfirm}
handleAddApprover={handleAddApprover}
handleAddSpectator={handleAddSpectator}
handleSkipApprover={handleSkipApprover}
downloadDocument={downloadDocument}
documentPolicy={documentPolicy}
/>
{/* Policy Violation Modal */}
setPolicyViolationModal({ open: false, violations: [] })}
violations={policyViolationModal.violations}
policyDetails={{
maxApprovalLevels: systemPolicy.maxApprovalLevels,
maxParticipants: systemPolicy.maxParticipants,
allowSpectators: systemPolicy.allowSpectators,
maxSpectators: systemPolicy.maxSpectators,
}}
/>
>
);
}
/**
* Dealer Claim RequestDetail Component (Exported)
*/
export function DealerClaimRequestDetail(props: RequestDetailProps) {
return (
);
}