/** * Custom Request Detail Screen * * Standalone, dedicated request detail screen for Custom requests. * This is a complete module that uses custom request specific components. * * LOCATION: src/custom/pages/RequestDetail.tsx * * IMPORTANT: This entire file and all its dependencies are in src/custom/ folder. * Deleting src/custom/ folder removes ALL custom request 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, } 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 { useConclusionRemark } from '@/hooks/useConclusionRemark'; import { useModalManager } from '@/hooks/useModalManager'; import { downloadDocument } from '@/services/workflowApi'; import { getPublicConfigurations, AdminConfiguration } from '@/services/adminApi'; import { PolicyViolationModal } from '@/components/modals/PolicyViolationModal'; // Custom Request Components (import from index to get properly aliased exports) import { CustomOverviewTab, CustomWorkflowTab } from '../index'; // Shared Components (from src/shared/) 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('Custom 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; } } /** * Custom RequestDetailInner Component */ function CustomRequestDetailInner({ 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); 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); const { conclusionRemark, setConclusionRemark, conclusionLoading, conclusionSubmitting, aiGenerated, handleGenerateConclusion, handleFinalizeConclusion, } = 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 needsClosure = (request?.status === 'approved' || request?.status === 'rejected') && isInitiator; 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]); // 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 custom request details...

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

Access Denied

{accessDenied.message}

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

Custom Request Not Found

The custom 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} // Custom 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 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} />
{/* 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={(user as any)?.userId} 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, }} /> ); } /** * Custom RequestDetail Component (Exported) */ export function CustomRequestDetail(props: RequestDetailProps) { return ( ); }