/** * Main CreateRequest component - Clean composition only * * This component orchestrates the CreateRequest flow by: * - Composing layout structure * - Wiring up hooks and components * - Handling top-level callbacks * * All business logic, API calls, and utilities are extracted to: * - hooks/ - Business logic and state management * - services/ - API operations * - utils/ - Pure utility functions * - components/ - UI components */ import { useState, useRef, useEffect, useCallback } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useAuth } from '@/contexts/AuthContext'; import { TemplateSelectionModal } from '@/components/modals/TemplateSelectionModal'; import { FilePreview } from '@/components/common/FilePreview'; import { PolicyViolationModal } from '@/components/modals/PolicyViolationModal'; import { downloadDocument } from '@/services/workflowApi'; // Custom Hooks import { useCreateRequestForm } from '@/hooks/useCreateRequestForm'; import { useWizardNavigation } from '@/hooks/useWizardNavigation'; import { useRequestModals } from './hooks/useRequestModals'; import { useCreateRequestSubmission } from './hooks/useCreateRequestSubmission'; import { useCreateRequestHandlers } from './hooks/useCreateRequestHandlers'; // Constants import { REQUEST_TEMPLATES } from './constants/requestTemplates'; // Components import { WizardStepper } from '@/components/workflow/CreateRequest/WizardStepper'; import { WizardFooter } from '@/components/workflow/CreateRequest/WizardFooter'; import { TemplateSelectionStep } from '@/components/workflow/CreateRequest/TemplateSelectionStep'; import { BasicInformationStep } from '@/components/workflow/CreateRequest/BasicInformationStep'; import { ApprovalWorkflowStep } from '@/components/workflow/CreateRequest/ApprovalWorkflowStep'; import { ParticipantsStep } from '@/components/workflow/CreateRequest/ParticipantsStep'; import { DocumentsStep } from '@/components/workflow/CreateRequest/DocumentsStep'; import { ReviewSubmitStep } from '@/components/workflow/CreateRequest/ReviewSubmitStep'; import { CreateRequestHeader } from './components/CreateRequestHeader'; import { CreateRequestContent } from './components/CreateRequestContent'; import { ValidationErrorModal } from './components/modals/ValidationErrorModal'; import { DocumentErrorModal } from './components/modals/DocumentErrorModal'; interface CreateRequestProps { onBack?: () => void; onSubmit?: (requestData: any) => void; requestId?: string; isEditMode?: boolean; } export function CreateRequest({ onBack, onSubmit, requestId: propRequestId, isEditMode = false, }: CreateRequestProps) { const params = useParams<{ requestId: string }>(); const navigate = useNavigate(); const editRequestId = params.requestId || propRequestId || ''; const isEditing = isEditMode && !!editRequestId; const { user } = useAuth(); // Form and state management hooks const { formData, updateFormData, selectedTemplate, setSelectedTemplate, loadingDraft, systemPolicy, documentPolicy, existingDocuments, setExistingDocuments, } = useCreateRequestForm(isEditing, editRequestId, REQUEST_TEMPLATES); const { currentStep, totalSteps, stepNames, isStepValid, nextStep: wizardNextStep, prevStep: wizardPrevStep, } = useWizardNavigation(isEditing, selectedTemplate, formData); // Document management state const [documents, setDocuments] = useState([]); const [documentsToDelete, setDocumentsToDelete] = useState([]); const fileInputRef = useRef(null); // Modal management const { validationModal, policyViolationModal, documentErrorModal, openValidationModal, closeValidationModal, closePolicyViolationModal, openDocumentErrorModal, closeDocumentErrorModal, } = useRequestModals(); // Submission logic const { submitting, savingDraft, handleSubmit, handleSaveDraft } = useCreateRequestSubmission({ formData, selectedTemplate, documents, documentsToDelete, user: user!, isEditing, editRequestId, onSubmit, }); // Event handlers const { showTemplateModal, setShowTemplateModal, previewDocument, selectTemplate, handleTemplateSelection, nextStep, prevStep, handlePreviewDocument, closePreview, } = useCreateRequestHandlers({ selectedTemplate, setSelectedTemplate, updateFormData, formData, currentStep, isStepValid, wizardNextStep, wizardPrevStep, user: user!, openValidationModal, onSubmit, }); // Handle back button: // - Steps 1, 3, or 4: Navigate back to previous screen (browser history) // - Other steps: Go to previous step in wizard const handleBackButton = useCallback(() => { if (currentStep === 1 || currentStep === 3 || currentStep === 4) { // On steps 1, 3, or 4, navigate back to previous screen using browser history if (onBack) { onBack(); } else { // Use window.history.back() as fallback for more reliable navigation if (window.history.length > 1) { window.history.back(); } else { // If no history, navigate to dashboard navigate('/dashboard', { replace: true }); } } } else { // On other steps (2, 5, 6), go to previous step in wizard prevStep(); } }, [currentStep, onBack, navigate, prevStep]); // Sync documents from formData only on initial mount (when loading draft) const isInitialMount = useRef(true); const documentsSyncedRef = useRef(false); useEffect(() => { // Only sync from formData on initial mount or when loading a draft if (isInitialMount.current && formData.documents && formData.documents.length > 0 && !documentsSyncedRef.current) { setDocuments(formData.documents); documentsSyncedRef.current = true; } isInitialMount.current = false; }, [formData.documents]); // Update formData.documents when documents change (one-way sync from local state to formData) // Use a ref to prevent circular updates const isUpdatingFromFormData = useRef(false); const prevDocumentsRef = useRef(documents); useEffect(() => { // Skip if we're currently syncing from formData if (isUpdatingFromFormData.current) { isUpdatingFromFormData.current = false; prevDocumentsRef.current = documents; return; } // Only update if documents actually changed if (prevDocumentsRef.current !== documents) { updateFormData('documents', documents); prevDocumentsRef.current = documents; } // eslint-disable-next-line react-hooks/exhaustive-deps }, [documents]); // Render step content const renderStepContent = () => { switch (currentStep) { case 1: return ( ); case 2: return ( ); case 3: return ( openValidationModal( error.type as 'error' | 'self-assign' | 'not-found', error.email, error.message ) } /> ); case 4: return ( openValidationModal( error.type as 'error' | 'self-assign' | 'not-found', error.email, error.message ) } initiatorEmail={(user as any)?.email || ''} /> ); case 5: return ( openDocumentErrorModal(errors)} fileInputRef={fileInputRef} /> ); case 6: return ( ); default: return null; } }; // Loading state if (loadingDraft) { return (

Loading draft...

); } // Main render return (
{renderStepContent()} {/* Modals */} setShowTemplateModal(false)} onSelectTemplate={handleTemplateSelection} /> {previewDocument && ( { if (previewDocument.file) { const link = document.createElement('a'); link.href = previewDocument.fileUrl; link.download = previewDocument.fileName; document.body.appendChild(link); link.click(); document.body.removeChild(link); } else if (previewDocument.documentId) { await downloadDocument(previewDocument.documentId); } }} attachmentId={previewDocument.documentId} /> )}
); }