import React, { useState, useRef } from 'react'; import { Upload, File, X, Loader2, CheckCircle2, AlertCircle } from 'lucide-react'; import { getAuthToken } from '../../services/apiClient'; interface ReportUploaderProps { onUploadSuccess?: (reportId: number, jobId: string) => void; onUploadError?: (error: string) => void; onClose?: () => void; onViewReports?: () => void; presetReportType?: 'salary' | 'balance_sheet_20' | 'balance_sheet_76' | 'balance_sheet' | 'bank_statement' | 'debtors' | 'other'; reportTypeName?: string; } export const ReportUploader: React.FC = ({ onUploadSuccess, onUploadError, onClose, onViewReports, presetReportType, reportTypeName }) => { const [selectedFile, setSelectedFile] = useState(null); const [reportType, setReportType] = useState<'debtors' | 'balance_sheet' | 'salary' | 'balance_sheet_20' | 'balance_sheet_76' | 'bank_statement' | 'other'>( presetReportType || 'other' ); const [isUploading, setIsUploading] = useState(false); const [uploadStatus, setUploadStatus] = useState<'idle' | 'success' | 'error'>('idle'); const [errorMessage, setErrorMessage] = useState(''); const fileInputRef = useRef(null); const handleFileSelect = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (file) { const ext = file.name.split('.').pop()?.toLowerCase(); if (ext === 'csv' || ext === 'xlsx' || ext === 'xls') { setSelectedFile(file); setUploadStatus('idle'); setErrorMessage(''); } else { setErrorMessage('Неподдерживаемый формат файла. Разрешены только CSV и XLSX'); setUploadStatus('error'); } } }; const handleDrop = (event: React.DragEvent) => { event.preventDefault(); const file = event.dataTransfer.files[0]; if (file) { const ext = file.name.split('.').pop()?.toLowerCase(); if (ext === 'csv' || ext === 'xlsx' || ext === 'xls') { setSelectedFile(file); setUploadStatus('idle'); setErrorMessage(''); } else { setErrorMessage('Неподдерживаемый формат файла. Разрешены только CSV и XLSX'); setUploadStatus('error'); } } }; const handleDragOver = (event: React.DragEvent) => { event.preventDefault(); }; const handleRemoveFile = () => { setSelectedFile(null); setUploadStatus('idle'); setErrorMessage(''); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; const handleUpload = async () => { if (!selectedFile) return; if (!getAuthToken()) { setErrorMessage('Требуется авторизация. Выполните вход в систему.'); setUploadStatus('error'); onUploadError?.('Требуется авторизация'); return; } setIsUploading(true); setUploadStatus('idle'); setErrorMessage(''); try { const formData = new FormData(); formData.append('file', selectedFile); formData.append('uploadedBy', 'Current User'); // Можно получить из контекста formData.append('reportType', getBackendReportType()); // Отправляем также детальный тип для обработки на бэкенде if (reportType === 'balance_sheet_20' || reportType === 'balance_sheet_76' || reportType === 'salary' || reportType === 'bank_statement') { formData.append('detailedReportType', reportType); } const apiBase = import.meta.env.VITE_API_BASE_URL || ''; const uploadUrl = apiBase ? `${apiBase.replace(/\/$/, '')}/finance/upload-report` : '/api/finance/upload-report'; const token = getAuthToken(); const response = await fetch(uploadUrl, { method: 'POST', headers: token ? { Authorization: `Bearer ${token}` } : {}, body: formData }); if (!response.ok) { const error = await response.json().catch(() => ({})); const msg = error.details || error.error || 'Ошибка загрузки файла'; throw new Error(msg); } const result = await response.json(); setUploadStatus('success'); if (onUploadSuccess) { onUploadSuccess(result.reportId, result.jobId); // Если есть обработка (jobId), не закрываем модальное окно - оно закроется автоматически при переходе к processing // Если обработки нет, закрываем через 2 секунды if (!result.jobId && onClose) { setTimeout(() => { handleRemoveFile(); onClose(); }, 2000); } else { // Очищаем файл, но не закрываем модальное окно (будет переход к processing) setTimeout(() => { handleRemoveFile(); }, 2000); } } else { // Если нет callback, просто очищаем файл setTimeout(() => { handleRemoveFile(); }, 2000); } } catch (error: any) { setUploadStatus('error'); setErrorMessage(error.message || 'Произошла ошибка при загрузке файла'); if (onUploadError) { onUploadError(error.message); } } finally { setIsUploading(false); } }; const formatFileSize = (bytes: number) => { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; }; // Маппинг типов для отправки на бэкенд const getBackendReportType = (): string => { if (reportType === 'balance_sheet_20' || reportType === 'balance_sheet_76' || reportType === 'balance_sheet') { return 'balance_sheet'; } if (reportType === 'salary' || reportType === 'bank_statement') { return 'other'; // Пока что salary и bank_statement идут как 'other', можно расширить позже } return reportType; }; return (

{reportTypeName ? `Загрузить: ${reportTypeName}` : 'Загрузить отчеты из 1С / Банка'}

{onClose && ( )}
{/* Выбор типа отчета (скрыт, если предустановлен) */} {!presetReportType && (
)} {presetReportType && (

Тип отчета: {reportTypeName || presetReportType}

)} {/* Drag & Drop Zone */}
{!selectedFile ? ( <>

Перетащите файл сюда или нажмите для выбора

Поддерживаются форматы: CSV, XLSX (макс. 50 МБ)

) : (

{selectedFile.name}

{formatFileSize(selectedFile.size)}

{uploadStatus === 'success' && (
Файл успешно загружен!
{onViewReports && ( )}
)} {uploadStatus === 'error' && (
{errorMessage}
)}
)}
{/* Информация */}

Важно: Убедитесь, что файл содержит колонку с адресами домов. Адреса должны совпадать с адресами домов в системе.

); };