// ID Card Uploader Component const { useState, useRef, useEffect } = React; const IDCardUploader = ({ projectId, project, onUpdate }) => { const toast = useToast(); const [uploading, setUploading] = useState({ front: false, back: false }); const [extracting, setExtracting] = useState(false); const frontInputRef = useRef(); const backInputRef = useRef(); // Local state to track uploads without waiting for parent refresh const [localUploaded, setLocalUploaded] = useState({ front: !!project?.id_card_front_path, back: !!project?.id_card_back_path, frontAt: project?.id_card_front_uploaded_at, backAt: project?.id_card_back_uploaded_at }); // Sync local state with project prop when it changes useEffect(() => { setLocalUploaded({ front: !!project?.id_card_front_path, back: !!project?.id_card_back_path, frontAt: project?.id_card_front_uploaded_at, backAt: project?.id_card_back_uploaded_at }); }, [project?.id_card_front_path, project?.id_card_back_path, project?.id_card_front_uploaded_at, project?.id_card_back_uploaded_at]); const hasFront = localUploaded.front; const hasBack = localUploaded.back; const hasAnyImage = hasFront || hasBack; const handleUpload = async (side, file) => { if (!file) return; const validTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif']; if (!validTypes.includes(file.type)) { toast('Formato file non valido. Usa JPG, PNG, WebP o GIF', 'error'); return; } if (file.size > 10 * 1024 * 1024) { toast('File troppo grande. Massimo 10 MB', 'error'); return; } setUploading(prev => ({ ...prev, [side]: true })); try { await window.ApiUtils.uploadIdCardImage(projectId, side, file); // Update local state immediately to prevent blink const now = new Date().toISOString(); setLocalUploaded(prev => ({ ...prev, [side]: true, [side === 'front' ? 'frontAt' : 'backAt']: now })); toast(`Immagine ${side === 'front' ? 'fronte' : 'retro'} caricata`, 'success'); // Background refresh parent data onUpdate?.(); } catch (error) { toast(error.message || 'Errore durante il caricamento', 'error'); } finally { setUploading(prev => ({ ...prev, [side]: false })); } }; const handleDelete = async (side) => { try { await window.ApiUtils.deleteIdCardImage(projectId, side); // Update local state immediately to prevent blink setLocalUploaded(prev => ({ ...prev, [side]: false, [side === 'front' ? 'frontAt' : 'backAt']: null })); toast(`Immagine ${side === 'front' ? 'fronte' : 'retro'} eliminata`, 'success'); // Background refresh parent data onUpdate?.(); } catch (error) { toast(error.message || 'Errore durante l\'eliminazione', 'error'); } }; const handleExtract = async () => { if (!hasAnyImage) { toast('Carica almeno un\'immagine prima di estrarre', 'error'); return; } setExtracting(true); try { const result = await window.ApiUtils.extractIdCardData(projectId); toast('Dati estratti con successo!', 'success'); onUpdate?.(); } catch (error) { toast(error.message || 'Errore durante l\'estrazione', 'error'); } finally { setExtracting(false); } }; const DropZone = ({ side, hasImage, uploading, inputRef }) => { const label = side === 'front' ? 'Fronte' : 'Retro'; const uploadedAt = side === 'front' ? localUploaded.frontAt : localUploaded.backAt; return (
Caricamento...
> ) : ( <>Trascina o clicca
Max 10 MB
> )} handleUpload(side, e.target.files[0])} className="hidden" />