import { useState, useEffect } from 'react'; import { uploadDocument } from '@/services/documentApi'; import { getPublicConfigurations, AdminConfiguration } from '@/services/adminApi'; import { toast } from 'sonner'; /** * Custom Hook: useDocumentUpload * * Purpose: Manages document upload functionality with loading states * * Responsibilities: * - Handles file input change events * - Validates file selection * - Uploads document to backend * - Triggers refresh after successful upload * - Manages upload loading state * - Handles errors with user-friendly messages * * @param apiRequest - Current request object (contains requestId for upload) * @param refreshDetails - Function to refresh request data after upload * @returns Object with upload handler, trigger function, and loading state */ export function useDocumentUpload( apiRequest: any, refreshDetails: () => Promise ) { // State: Indicates if document is currently being uploaded const [uploadingDocument, setUploadingDocument] = useState(false); // State: Stores document for preview modal const [previewDocument, setPreviewDocument] = useState<{ fileName: string; fileType: string; documentId: string; fileSize?: number; } | null>(null); // Document policy state const [documentPolicy, setDocumentPolicy] = useState<{ maxFileSizeMB: number; allowedFileTypes: string[]; }>({ maxFileSizeMB: 10, allowedFileTypes: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'jpg', 'jpeg', 'png', 'gif'] }); // Document validation error state const [documentError, setDocumentError] = useState<{ show: boolean; errors: Array<{ fileName: string; reason: string }>; }>({ show: false, errors: [] }); // Fetch document policy on mount useEffect(() => { const loadDocumentPolicy = async () => { try { const configs = await getPublicConfigurations('DOCUMENT_POLICY'); const configMap: Record = {}; configs.forEach((c: AdminConfiguration) => { configMap[c.configKey] = c.configValue; }); const maxFileSizeMB = parseInt(configMap['MAX_FILE_SIZE_MB'] || '10'); const allowedFileTypesStr = configMap['ALLOWED_FILE_TYPES'] || 'pdf,doc,docx,xls,xlsx,ppt,pptx,jpg,jpeg,png,gif'; const allowedFileTypes = allowedFileTypesStr.split(',').map(ext => ext.trim().toLowerCase()); setDocumentPolicy({ maxFileSizeMB, allowedFileTypes }); } catch (error) { console.error('Failed to load document policy:', error); // Use defaults if loading fails } }; loadDocumentPolicy(); }, []); /** * Function: validateFile * * Purpose: Validate file against document policy * * @param file - File to validate * @returns Validation result with reason if invalid */ const validateFile = (file: File): { valid: boolean; reason?: string } => { // Check file size const maxSizeBytes = documentPolicy.maxFileSizeMB * 1024 * 1024; if (file.size > maxSizeBytes) { return { valid: false, reason: `File size exceeds the maximum allowed size of ${documentPolicy.maxFileSizeMB}MB. Current size: ${(file.size / (1024 * 1024)).toFixed(2)}MB` }; } // Check file extension const fileName = file.name.toLowerCase(); const fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1); if (!documentPolicy.allowedFileTypes.includes(fileExtension)) { return { valid: false, reason: `File type "${fileExtension}" is not allowed. Allowed types: ${documentPolicy.allowedFileTypes.join(', ')}` }; } return { valid: true }; }; /** * Function: handleDocumentUpload * * Purpose: Process file upload when user selects a file * * Process: * 1. Validate file selection * 2. Validate against document policy * 3. Get request UUID (required for backend API) * 4. Upload file to backend * 5. Refresh request details to show new document * 6. Clear file input for next upload * 7. Show success/error messages * * @param event - File input change event */ const handleDocumentUpload = async (event: React.ChangeEvent) => { const files = event.target.files; // Validate: Check if file is selected if (!files || files.length === 0) return; const fileArray = Array.from(files); // Validate all files against document policy const validationErrors: Array<{ fileName: string; reason: string }> = []; const validFiles: File[] = []; fileArray.forEach(file => { const validation = validateFile(file); if (!validation.valid) { validationErrors.push({ fileName: file.name, reason: validation.reason || 'Unknown validation error' }); } else { validFiles.push(file); } }); // If there are validation errors, show modal if (validationErrors.length > 0) { setDocumentError({ show: true, errors: validationErrors }); } // If no valid files, stop here if (validFiles.length === 0) { if (event.target) { event.target.value = ''; } return; } setUploadingDocument(true); try { // Upload only the first valid file (backend currently supports single file) const file = validFiles[0]; // Validate: Ensure request ID is available // Note: Backend requires UUID, not request number const requestId = apiRequest?.requestId; if (!requestId) { toast.error('Request ID not found'); return; } // API Call: Upload document to backend // Document type is 'SUPPORTING' (as opposed to 'REQUIRED') if (file) { await uploadDocument(file, requestId, 'SUPPORTING'); } // Refresh: Reload request details to show newly uploaded document // This also updates the activity timeline await refreshDetails(); // Success feedback if (validFiles.length < fileArray.length) { toast.warning(`${validFiles.length} of ${fileArray.length} file(s) were uploaded. ${validationErrors.length} file(s) were rejected.`); } else { toast.success('Document uploaded successfully'); } } catch (error: any) { console.error('[useDocumentUpload] Upload error:', error); // Error feedback with backend error message if available toast.error(error?.response?.data?.error || 'Failed to upload document'); } finally { setUploadingDocument(false); // Cleanup: Clear the file input to allow re-uploading same file if (event.target) { event.target.value = ''; } } }; /** * Function: triggerFileInput * * Purpose: Programmatically open file picker dialog * * Process: * 1. Create temporary file input element * 2. Configure accepted file types based on document policy * 3. Attach upload handler * 4. Trigger click to open file picker */ const triggerFileInput = () => { const input = document.createElement('input'); input.type = 'file'; input.accept = documentPolicy.allowedFileTypes.map(ext => `.${ext}`).join(','); input.onchange = handleDocumentUpload as any; input.click(); }; return { uploadingDocument, handleDocumentUpload, triggerFileInput, previewDocument, setPreviewDocument, documentPolicy, documentError, setDocumentError }; }