import React, { useState, useRef } from 'react'; import { X, Upload, FileSpreadsheet, Loader, CheckCircle, AlertCircle, Trash2 } from 'lucide-react'; const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api'; const PURPLE = '#A78BFA'; // --------------------------------------------------------------------------- // Styles // --------------------------------------------------------------------------- const OVERLAY_STYLE = { position: 'fixed', inset: 0, background: 'rgba(0, 0, 0, 0.7)', backdropFilter: 'blur(4px)', zIndex: 100, display: 'flex', alignItems: 'center', justifyContent: 'center', }; const MODAL_STYLE = { background: 'linear-gradient(180deg, #0F1A2E 0%, #0A1628 100%)', border: `1px solid ${PURPLE}40`, borderRadius: '1rem', width: '90%', maxWidth: '900px', maxHeight: '85vh', overflow: 'auto', padding: '2rem', boxShadow: `0 0 60px rgba(167, 139, 250, 0.15)`, }; const DROP_ZONE_STYLE = { border: `2px dashed ${PURPLE}50`, borderRadius: '0.75rem', padding: '3rem 2rem', textAlign: 'center', cursor: 'pointer', transition: 'border-color 0.2s, background 0.2s', }; const DROP_ZONE_ACTIVE = { ...DROP_ZONE_STYLE, borderColor: PURPLE, background: `${PURPLE}10`, }; const FILE_ROW_STYLE = { display: 'flex', alignItems: 'center', gap: '1rem', padding: '0.75rem 1rem', borderRadius: '0.5rem', background: 'rgba(15, 23, 42, 0.6)', border: '1px solid rgba(255,255,255,0.06)', marginBottom: '0.5rem', }; // phase: idle → uploading → preview → committing → done | error export default function MultiVerticalUploadModal({ onClose, onUploadComplete }) { const [phase, setPhase] = useState('idle'); const [files, setFiles] = useState([]); const [previewData, setPreviewData] = useState(null); const [error, setError] = useState(null); const [dragOver, setDragOver] = useState(false); const [commitResult, setCommitResult] = useState(null); const fileInputRef = useRef(null); // Handle file selection const handleFiles = (fileList) => { const newFiles = Array.from(fileList).filter(f => f.name.toLowerCase().endsWith('.xlsx')); if (newFiles.length === 0) { setError('Please select .xlsx files'); return; } setFiles(prev => { const existing = new Set(prev.map(f => f.name)); const unique = newFiles.filter(f => !existing.has(f.name)); return [...prev, ...unique]; }); setError(null); }; const removeFile = (index) => { setFiles(prev => prev.filter((_, i) => i !== index)); }; // Upload and preview const handlePreview = async () => { if (files.length === 0) return; setPhase('uploading'); setError(null); const formData = new FormData(); for (const file of files) { formData.append('files', file); } try { const res = await fetch(`${API_BASE}/compliance/vcl-multi/preview`, { method: 'POST', credentials: 'include', body: formData, }); const data = await res.json(); if (!res.ok) { setError(data.error || 'Upload failed'); setPhase('idle'); return; } setPreviewData(data); setPhase('preview'); } catch (err) { setError('Network error: ' + err.message); setPhase('idle'); } }; // Commit const handleCommit = async () => { if (!previewData || !previewData.files || previewData.files.length === 0) return; setPhase('committing'); setError(null); try { const res = await fetch(`${API_BASE}/compliance/vcl-multi/commit`, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ files: previewData.files.map(f => ({ tempFile: f.tempFile, vertical: f.vertical, report_date: f.report_date, filename: f.filename, })), }), }); const data = await res.json(); if (!res.ok) { setError(data.error || 'Commit failed'); setPhase('preview'); return; } setCommitResult(data); setPhase('done'); } catch (err) { setError('Network error: ' + err.message); setPhase('preview'); } }; // Remove a file from preview const removePreviewFile = (index) => { setPreviewData(prev => ({ ...prev, files: prev.files.filter((_, i) => i !== index), })); }; return (
| Vertical | Date | Items | New | Recurring | Resolved | |
|---|---|---|---|---|---|---|
| {f.vertical} | {f.report_date} | {f.total_items} | {f.diff.new_count} | {f.diff.recurring_count} | {f.diff.resolved_count} | {previewData.files.length > 1 && ( )} |
| Total | {previewData.files.reduce((s, f) => s + f.total_items, 0)} | {previewData.files.reduce((s, f) => s + f.diff.new_count, 0)} | {previewData.files.reduce((s, f) => s + f.diff.recurring_count, 0)} | {previewData.files.reduce((s, f) => s + f.diff.resolved_count, 0)} |