// Documents page — backed by the Go API (real upload / list / download / delete). // Files are stored as-is; text/OCR extraction (incl. scans) is the AI layer's job. function Documents() { const [docs, setDocs] = React.useState([]); const [selected, setSelected] = React.useState(new Set()); const [dragOver, setDragOver] = React.useState(false); const [filterType, setFilterType] = React.useState("all"); const [filterDate, setFilterDate] = React.useState("any"); const [query, setQuery] = React.useState(""); const [uploading, setUploading] = React.useState(0); const [error, setError] = React.useState(""); const [aiDoc, setAiDoc] = React.useState(null); const [analyzeNow, setAnalyzeNow] = React.useState(false); const fileInputRef = React.useRef(null); // Merge an analysis result (summary/analyzedAt) back into the list row. const applyAnalysis = (id, patch) => setDocs(prev => prev.map(d => (d.id === id ? { ...d, ...patch } : d))); React.useEffect(() => { if (window.API && window.API.listDocuments) { window.API.listDocuments().then(setDocs).catch(() => setDocs(DOCS)); } else { setDocs(DOCS); } }, []); const inRange = (dstr, mode) => { if (mode === "any") return true; const m = (dstr || "").match(/(\d{2})\.(\d{2})\.(\d{4})/); if (!m) return true; const d = new Date(+m[3], +m[2] - 1, +m[1]); const days = (Date.now() - d.getTime()) / 86400000; if (mode === "today") return days < 1; if (mode === "week") return days < 7; return true; }; const filtered = docs.filter(d => { if (filterType !== "all" && d.type !== filterType) return false; if (query && !d.name.toLowerCase().includes(query.toLowerCase())) return false; if (!inRange(d.date, filterDate)) return false; return true; }); const allSelected = filtered.length > 0 && filtered.every(d => selected.has(d.id)); const toggleAll = () => { if (allSelected) setSelected(new Set()); else setSelected(new Set(filtered.map(d => d.id))); }; const toggleOne = (id) => { const s = new Set(selected); s.has(id) ? s.delete(id) : s.add(id); setSelected(s); }; const uploadFiles = async (fileList) => { const files = Array.from(fileList || []); if (!files.length || !window.API) return; setError(""); setUploading(files.length); try { const created = await window.API.uploadDocuments(files); setDocs(prev => [...created, ...prev]); // Optionally kick off AI analysis right away (otherwise it's lazy on first open). if (analyzeNow && window.API.analyzeDocument) { created.forEach(d => { window.API.analyzeDocument(d.id) .then(r => applyAnalysis(d.id, { summary: r.summary, analyzedAt: r.analyzedAt })) .catch(() => {}); }); } } catch (e) { setError("Не удалось загрузить: " + String((e && e.message) || e)); } finally { setUploading(0); } }; const removeSelected = async () => { const ids = [...selected]; setDocs(prev => prev.filter(d => !selected.has(d.id))); setSelected(new Set()); if (window.API) { for (const id of ids) { try { await window.API.deleteDocument(id); } catch (_) {} } } }; const handleDrop = (e) => { e.preventDefault(); setDragOver(false); uploadFiles(e.dataTransfer.files); }; const handleFileInput = (e) => { uploadFiles(e.target.files); e.target.value = ""; }; const demoFile = () => { const f = new File( ["Демо-файл CRM\nЗагружен для проверки конвейера документов.\n"], "Демо_файл.txt", { type: "text/plain" } ); uploadFiles([f]); }; const downloadUrl = (d) => (window.API && window.API.docDownloadUrl ? window.API.docDownloadUrl(d.id) : "#"); return (
{docs.length} файлов в библиотеке{uploading ? ` · загрузка ${uploading}…` : ""}
или нажмите для выбора · Excel, PDF, DOCX, CSV, фото/сканы · ИИ извлечёт текст и резюме
{error}
}| Имя файла | Тип | Загружен | Размер | Статус | ||
|---|---|---|---|---|---|---|
| {docs.length === 0 ? "Пока нет загруженных документов — перетащите файл выше" : "По текущим фильтрам ничего не найдено"} | ||||||
|
|
{d.type || "file"} | {d.date} | {d.size} | |||
{doc.name}
Документ ещё не проанализирован
ИИ извлечёт текст (включая распознавание сканов) и составит краткое резюме. Результат сохранится.
ИИ обрабатывает документ…