Re_Figma_Code/src/custom/pages/RequestDetail.tsx
2026-03-13 14:10:49 +05:30

759 lines
31 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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';
import { TokenManager } from '@/utils/tokenManager';
// Custom Request Components (import from index to get properly aliased exports)
import { CustomOverviewTab, CustomWorkflowTab } from '../index';
import { Form16OverviewTab } from '../components/request-detail/Form16OverviewTab';
import { Form16WorkflowTab } from '../components/request-detail/Form16WorkflowTab';
import { Form16QuickActions } from '../components/request-detail/Form16QuickActions';
// 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 (
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-6">
<div className="max-w-md w-full bg-white rounded-lg shadow-lg p-8 text-center">
<AlertTriangle className="w-16 h-16 text-red-500 mx-auto mb-4" />
<h2 className="text-2xl font-bold mb-2">Error Loading Request</h2>
<p className="text-gray-600 mb-4">{this.state.error?.message || 'An unexpected error occurred'}</p>
<Button onClick={() => window.location.reload()} className="mr-2">
Reload Page
</Button>
<Button variant="outline" onClick={() => window.history.back()}>
Go Back
</Button>
</div>
</div>
);
}
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<string | null>(null);
const [summaryDetails, setSummaryDetails] = useState<SummaryDetails | null>(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,
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<string, string> = {};
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);
const isForm16Request = (request?.templateType || request?.template_type || '').toString().toUpperCase() === 'FORM_16';
useEffect(() => {
if (isForm16Request && activeTab === 'worknotes') setActiveTab('overview');
}, [isForm16Request, activeTab]);
// 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 (
<div className="flex items-center justify-center h-screen bg-gray-50" data-testid="loading-state">
<div className="text-center">
<RefreshCw className="w-12 h-12 text-blue-600 animate-spin mx-auto mb-4" />
<p className="text-gray-600">Loading custom request details...</p>
</div>
</div>
);
}
// Access Denied state
if (accessDenied?.denied) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-6" data-testid="access-denied-state">
<div className="max-w-lg w-full bg-white rounded-2xl shadow-xl p-8 text-center">
<div className="w-20 h-20 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-6">
<ShieldX className="w-10 h-10 text-red-500" />
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-3">Access Denied</h2>
<p className="text-gray-600 mb-6 leading-relaxed">
{accessDenied.message}
</p>
<div className="flex gap-3 justify-center">
<Button
variant="outline"
onClick={onBack || (() => window.history.back())}
className="flex items-center gap-2"
>
<ArrowLeft className="w-4 h-4" />
Go Back
</Button>
<Button
onClick={() => window.location.href = '/dashboard'}
className="bg-blue-600 hover:bg-blue-700"
>
Go to Dashboard
</Button>
</div>
</div>
</div>
);
}
// Not Found state
if (!request) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-6" data-testid="not-found-state">
<div className="max-w-md w-full bg-white rounded-2xl shadow-xl p-8 text-center">
<div className="w-20 h-20 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-6">
<FileText className="w-10 h-10 text-gray-400" />
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-3">Custom Request Not Found</h2>
<p className="text-gray-600 mb-6">
The custom request you're looking for doesn't exist or may have been deleted.
</p>
<div className="flex gap-3 justify-center">
<Button
variant="outline"
onClick={onBack || (() => window.history.back())}
className="flex items-center gap-2"
>
<ArrowLeft className="w-4 h-4" />
Go Back
</Button>
<Button
onClick={() => window.location.href = '/dashboard'}
className="bg-blue-600 hover:bg-blue-700"
>
Go to Dashboard
</Button>
</div>
</div>
</div>
);
}
const isReUser = (user as any)?.role === 'MANAGEMENT' || (user as any)?.role === 'ADMIN';
// Quick Actions and Spectators are for RE users / employees / admins only; hide for dealers
const isDealer = (TokenManager.getUserData() as any)?.jobTitle === 'Dealer';
return (
<>
<div className="min-h-screen bg-gray-50" data-testid="custom-request-detail-page">
<div className="max-w-7xl mx-auto">
{isForm16Request && (
<p className="text-sm text-emerald-700 font-medium mb-2" data-testid="form16-details-heading">Form 16 Details</p>
)}
{/* Header Section */}
<RequestDetailHeader
request={request}
refreshing={refreshing}
onBack={onBack || (() => 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 */}
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full" data-testid="custom-request-detail-tabs">
<div className="mb-4 sm:mb-6">
<TabsList className="grid grid-cols-3 sm:grid-cols-6 lg:flex lg:flex-row h-auto bg-gray-100 p-1.5 sm:p-1 rounded-lg gap-1.5 sm:gap-1">
<TabsTrigger
value="overview"
className="flex items-center justify-center gap-1 sm:gap-1.5 rounded-md px-2 sm:px-3 py-2.5 sm:py-1.5 text-xs sm:text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-gray-900 data-[state=active]:shadow-sm text-gray-600 data-[state=active]:text-gray-900"
data-testid="tab-overview"
>
<ClipboardList className="w-3.5 h-3.5 sm:w-4 sm:h-4 flex-shrink-0" />
<span className="truncate">{isForm16Request ? 'Form 16' : 'Overview'}</span>
</TabsTrigger>
{isClosed && summaryDetails && (
<TabsTrigger
value="summary"
className="flex items-center justify-center gap-1 sm:gap-1.5 rounded-md px-2 sm:px-3 py-2.5 sm:py-1.5 text-xs sm:text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-gray-900 data-[state=active]:shadow-sm text-gray-600 data-[state=active]:text-gray-900"
data-testid="tab-summary"
>
<FileCheck className="w-3.5 h-3.5 sm:w-4 sm:h-4 flex-shrink-0" />
<span className="truncate">Summary</span>
</TabsTrigger>
)}
<TabsTrigger
value="workflow"
className="flex items-center justify-center gap-1 sm:gap-1.5 rounded-md px-2 sm:px-3 py-2.5 sm:py-1.5 text-xs sm:text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-gray-900 data-[state=active]:shadow-sm text-gray-600 data-[state=active]:text-gray-900"
data-testid="tab-workflow"
>
<TrendingUp className="w-3.5 h-3.5 sm:w-4 sm:h-4 flex-shrink-0" />
<span className="truncate">Workflow</span>
</TabsTrigger>
<TabsTrigger
value="documents"
className="flex items-center justify-center gap-1 sm:gap-1.5 rounded-md px-2 sm:px-3 py-2.5 sm:py-1.5 text-xs sm:text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-gray-900 data-[state=active]:shadow-sm text-gray-600 data-[state=active]:text-gray-900"
data-testid="tab-documents"
>
<FileText className="w-3.5 h-3.5 sm:w-4 sm:h-4 flex-shrink-0" />
<span className="truncate">Docs</span>
</TabsTrigger>
<TabsTrigger
value="activity"
className="flex items-center justify-center gap-1 sm:gap-1.5 rounded-md px-2 sm:px-3 py-2.5 sm:py-1.5 text-xs sm:text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-gray-900 data-[state=active]:shadow-sm text-gray-600 data-[state=active]:text-gray-900 col-span-1 sm:col-span-1"
data-testid="tab-activity"
>
<Activity className="w-3.5 h-3.5 sm:w-4 sm:h-4 flex-shrink-0" />
<span className="truncate">Activity</span>
</TabsTrigger>
{!isForm16Request && (
<TabsTrigger
value="worknotes"
className="flex items-center justify-center gap-1 sm:gap-1.5 rounded-md px-2 sm:px-3 py-2.5 sm:py-1.5 text-xs sm:text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-gray-900 data-[state=active]:shadow-sm text-gray-600 data-[state=active]:text-gray-900 relative col-span-2 sm:col-span-1"
data-testid="tab-worknotes"
>
<MessageSquare className="w-3.5 h-3.5 sm:w-4 sm:h-4 flex-shrink-0" />
<span className="truncate">Work Notes</span>
{unreadWorkNotes > 0 && (
<Badge
className="absolute -top-1 -right-1 h-5 w-5 rounded-full bg-red-500 text-white text-[10px] flex items-center justify-center p-0"
data-testid="worknotes-unread-badge"
>
{unreadWorkNotes > 9 ? '9+' : unreadWorkNotes}
</Badge>
)}
</TabsTrigger>
)}
</TabsList>
</div>
{/* Main Layout */}
<div className={activeTab === 'worknotes' && !isForm16Request ? '' : 'grid grid-cols-1 lg:grid-cols-3 gap-6'}>
{/* Left Column: Tab content */}
<div className={activeTab === 'worknotes' && !isForm16Request ? '' : 'lg:col-span-2'}>
<TabsContent value="overview" className="mt-0" data-testid="overview-tab-content">
{isForm16Request ? (
// Form 16: dedicated overview (prevents duplicate sections below the nav bar)
<Form16OverviewTab request={request} />
) : (
<CustomOverviewTab
request={request}
isInitiator={isInitiator}
needsClosure={needsClosure}
conclusionRemark={conclusionRemark}
setConclusionRemark={setConclusionRemark}
conclusionLoading={conclusionLoading}
conclusionSubmitting={conclusionSubmitting}
aiGenerated={aiGenerated}
handleGenerateConclusion={handleGenerateConclusion}
handleFinalizeConclusion={handleFinalizeConclusion}
onPause={handlePause}
onResume={handleResume}
onRetrigger={handleRetrigger}
currentUserIsApprover={!!currentApprovalLevel}
pausedByUserId={request?.pauseInfo?.pausedBy?.userId}
currentUserId={(user as any)?.userId}
generationAttempts={generationAttempts}
generationFailed={generationFailed}
maxAttemptsReached={maxAttemptsReached}
/>
)}
</TabsContent>
{isClosed && (
<TabsContent value="summary" className="mt-0" data-testid="summary-tab-content">
<SummaryTab
summary={summaryDetails}
loading={loadingSummary}
onShare={handleShareSummary}
isInitiator={isInitiator}
/>
</TabsContent>
)}
<TabsContent value="workflow" className="mt-0">
{isForm16Request ? (
<Form16WorkflowTab
request={request}
requestId={apiRequest?.requestId || requestIdentifier}
isReUser={isReUser}
onRefresh={refreshDetails}
/>
) : (
<CustomWorkflowTab
request={request}
user={user}
isInitiator={isInitiator}
onSkipApprover={(data) => {
if (!data.levelId) {
alert('Level ID not available');
return;
}
setSkipApproverData(data);
setShowSkipApproverModal(true);
}}
onRefresh={refreshDetails}
/>
)}
</TabsContent>
<TabsContent value="documents" className="mt-0">
<DocumentsTab
request={request}
workNoteAttachments={workNoteAttachments}
uploadingDocument={uploadingDocument}
documentPolicy={documentPolicy}
triggerFileInput={triggerFileInput}
setPreviewDocument={setPreviewDocument}
downloadDocument={downloadDocument}
/>
</TabsContent>
<TabsContent value="activity" className="mt-0">
<ActivityTab request={request} />
</TabsContent>
{!isForm16Request && (
<TabsContent value="worknotes" className="mt-0" forceMount={true} hidden={activeTab !== 'worknotes'}>
<WorkNotesTab
requestId={requestIdentifier}
requestTitle={request.title}
mergedMessages={mergedMessages}
setWorkNoteAttachments={setWorkNoteAttachments}
isInitiator={isInitiator}
isSpectator={isSpectator}
currentLevels={currentLevels}
onAddApprover={handleAddApprover}
maxApprovalLevels={systemPolicy.maxApprovalLevels}
onPolicyViolation={(violations) => setPolicyViolationModal({ open: true, violations })}
/>
</TabsContent>
)}
</div>
{/* Right Column: Quick Actions Sidebar RE users / employees / admins only; hidden for dealers */}
{!isDealer && activeTab !== 'worknotes' && (
<div className="space-y-4 sm:space-y-6">
{/* Form 16 RE actions Quick Actions section (Form 16 only, no change to shared workflow) */}
{isForm16Request && isReUser && (
<Form16QuickActions
requestId={apiRequest?.requestId || requestIdentifier}
request={request}
onRefresh={refreshDetails}
/>
)}
<QuickActionsSidebar
request={request}
isInitiator={isInitiator}
isSpectator={isSpectator}
currentApprovalLevel={currentApprovalLevel}
onAddApprover={() => 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}
hideApproveReject={isDealer}
/>
</div>
)}
</div>
</Tabs>
</div>
</div>
{/* Share Summary Modal */}
{showShareSummaryModal && summaryId && (
<ShareSummaryModal
isOpen={showShareSummaryModal}
onClose={() => setShowShareSummaryModal(false)}
summaryId={summaryId}
requestTitle={request?.title || 'N/A'}
onSuccess={() => {
refreshDetails();
setSharedRecipientsRefreshTrigger(prev => prev + 1);
}}
/>
)}
{/* Pause Modals */}
{showPauseModal && apiRequest?.requestId && (
<PauseModal
isOpen={showPauseModal}
onClose={() => setShowPauseModal(false)}
requestId={apiRequest.requestId}
levelId={currentApprovalLevel?.levelId || null}
onSuccess={handlePauseSuccess}
/>
)}
{showResumeModal && apiRequest?.requestId && (
<ResumeModal
isOpen={showResumeModal}
onClose={() => setShowResumeModal(false)}
requestId={apiRequest.requestId}
onSuccess={handleResumeSuccess}
/>
)}
{showRetriggerModal && apiRequest?.requestId && (
<RetriggerPauseModal
isOpen={showRetriggerModal}
onClose={() => setShowRetriggerModal(false)}
requestId={apiRequest.requestId}
approverName={request?.pauseInfo?.pausedBy?.name}
onSuccess={handleRetriggerSuccess}
/>
)}
{/* Modals */}
<RequestDetailModals
showApproveModal={showApproveModal}
showRejectModal={showRejectModal}
showAddApproverModal={showAddApproverModal}
showAddSpectatorModal={showAddSpectatorModal}
showSkipApproverModal={showSkipApproverModal}
showActionStatusModal={showActionStatusModal}
previewDocument={previewDocument}
documentError={documentError}
request={request}
skipApproverData={skipApproverData}
actionStatus={actionStatus}
existingParticipants={existingParticipants}
currentLevels={currentLevels}
maxApprovalLevels={systemPolicy.maxApprovalLevels}
onPolicyViolation={(violations) => 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 */}
<PolicyViolationModal
open={policyViolationModal.open}
onClose={() => 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 (
<RequestDetailErrorBoundary>
<CustomRequestDetailInner {...props} />
</RequestDetailErrorBoundary>
);
}