import { Card, Text, Table, Badge, ActionIcon, Drawer, Box, Group, Stack, Alert } from '@pikku/mantine/core'; import { IconArrowRight, IconEye, IconMaximize, IconMinimize, IconPlus, IconRotateClockwise, IconX } from '@tabler/icons-react'; import React, { useState, useEffect, useRef } from 'react'; import type { DB } from '@heygermany/sdk'; import { DocumentStatuses } from '@heygermany/sdk'; import { getFieldComponent } from './DocumentFieldFactory'; import { pikku } from '@/pikku/http'; import { useUpdateDocumentVerification } from '@/hooks/backoffice/useUpdateDocumentVerification'; import { useUploadBackOfficeCandidateFiles } from '@/hooks/backoffice/useUploadBackOfficeCandidateFiles'; import { UseMutationResult } from '@tanstack/react-query'; import { DocumentStatusSelect } from './DocumentStatusSelect'; import { useI18n } from '@/context/i18n-provider'; import { asI18n } from '@pikku/react'; type VerificationDocument = { documentId: string; documentStatus: DB.DocumentStatus | null; label?: string; }; type DocumentVerificationCardProps = { category: DB.CandidateDocumentType; candidateId: string; documents: VerificationDocument[]; aiExtractedData: Record; finalData: Record; mutationHook: UseMutationResult; verbose?: boolean; requiredFields?: string[]; }; export function DocumentVerificationCard({ candidateId, documents, category, aiExtractedData, finalData, mutationHook, verbose = false, requiredFields = [], }: DocumentVerificationCardProps) { const [documentUrl, setDocumentUrl] = useState(null); const [documentPreviewTitle, setDocumentPreviewTitle] = useState('Document Preview'); const [formData, setFormData] = useState>({}); const [isFullscreen, setIsFullscreen] = useState(false); const [rotation, setRotation] = useState(0); const [isUploading, setIsUploading] = useState(false); const [uploadError, setUploadError] = useState(null); const [deletingDocumentId, setDeletingDocumentId] = useState(null); const [deleteError, setDeleteError] = useState(null); const updateDocumentVerification = useUpdateDocumentVerification(candidateId); const { uploadFiles, deleteFile } = useUploadBackOfficeCandidateFiles(candidateId, category); const t = useI18n(); const fileInputRef = useRef(null); const showDocument = async (documentId: string, label: string) => { const response = await pikku().get('/backoffice/candidate/:candidateId/document/:documentId', { documentId, candidateId }); setDocumentPreviewTitle(label); setDocumentUrl(response.url) } const allFields = new Set([ ...Object.keys(aiExtractedData || {}), ...Object.keys(finalData || {}) ]); // Show only required fields when not in verbose mode const fieldsToShow = verbose ? Array.from(allFields) : requiredFields.filter(field => allFields.has(field)); const getFieldLabel = (fieldName: string) => { return fieldName.charAt(0).toUpperCase() + fieldName.slice(1).replace(/([A-Z])/g, ' $1'); }; useEffect(() => { setFormData({ ...finalData }); }, [finalData]); const handleCopyOver = (fieldName: string) => { const aiValue = aiExtractedData?.[fieldName]; if (aiValue !== undefined) { const updatedData = { ...formData, [fieldName]: aiValue }; setFormData(updatedData); mutationHook.mutate({ [fieldName]: aiValue }); } }; const handleFieldChange = (fieldName: string, value: any) => { setFormData((currentData) => ({ ...currentData, [fieldName]: value, })); mutationHook.mutate({ [fieldName]: value }); }; const handleFileSelection = async (event: React.ChangeEvent) => { const selectedFiles = Array.from(event.target.files || []); if (!selectedFiles.length) { return; } setUploadError(null); setIsUploading(true); try { await uploadFiles(selectedFiles); } catch (error) { console.error('Error uploading files in backoffice:', error); setUploadError('Failed to upload document(s). Please try again.'); } finally { setIsUploading(false); event.target.value = ''; } }; const handleDeleteDocument = async (documentId: string, label: string) => { const confirmed = window.confirm(`Delete ${label}?`); if (!confirmed) { return; } setDeleteError(null); setDeletingDocumentId(documentId); try { await deleteFile(documentId); } catch (error) { console.error('Error deleting file in backoffice:', error); setDeleteError('Failed to delete document. Please try again.'); } finally { setDeletingDocumentId(null); } }; return ( <> {documents.length > 0 ? ( documents.map((document, index) => { const label = document.label || `Document ${index + 1}`; return ( showDocument(document.documentId, label)} > {asI18n(label)} handleDeleteDocument(document.documentId, label)} disabled={deletingDocumentId === document.documentId} aria-label={asI18n(`Delete ${label}`)} title={asI18n(`Delete ${label}`)} > updateDocumentVerification.mutate({ documentId: document.documentId, documentStatus: status })} /> ); }) ) : ( {t('backoffice.documents.nofilesavailable' as any)} )} fileInputRef.current?.click()} > {asI18n(isUploading ? t('backoffice.documents.uploading' as any) : t('backoffice.documents.uploaddocuments' as any))} {uploadError ? ( {asI18n(uploadError)} ) : null} {deleteError ? ( {asI18n(deleteError)} ) : null} {fieldsToShow.length > 0 ? ( Field AI Value User Value {fieldsToShow.map((fieldName) => { const FieldComponent = getFieldComponent(category, fieldName); return ( {asI18n(getFieldLabel(fieldName))} { }} // Read-only disabled={true} /> handleCopyOver(fieldName)} disabled={!aiExtractedData?.[fieldName]} > handleFieldChange(fieldName, value)} /> ); })}
) : ( {asI18n('No required fields')} )}
{ setDocumentUrl(null); setIsFullscreen(false); setRotation(0); }} position="right" size={isFullscreen ? "100%" : "60%"} title={ {asI18n(documentPreviewTitle)} setRotation((prev) => (prev + 90) % 360)} title={asI18n('Rotate image')} > setIsFullscreen(!isFullscreen)} title={asI18n(isFullscreen ? 'Exit fullscreen' : 'Fullscreen')} > {isFullscreen ? : } } > {documentUrl && (