import React, { useState, useEffect, useCallback } from 'react'; import { ArrowLeft, Search, Loader2, Send, FileText } from 'lucide-react'; import { FinancialReport } from '../../types'; import { authFetch } from '../../services/apiClient'; export interface DebtorReportRow { id: number; reportId: number; rowIndex: number; account: string; responsibleName: string | null; objectAddress: string | null; monthsDebt: number | null; totalDebt: number; createdAt?: string; } interface DebtorReportDetailViewProps { report: FinancialReport; onBack: () => void; } function extractApartment(objectAddress: string | null, account: string): string { if (!objectAddress || !objectAddress.trim()) return account || '—'; const m = objectAddress.match(/кв\.\s*([^,;\s]+)/i) || objectAddress.match(/кв\.([^,;\s]+)/i); if (m) return m[1].trim(); const parts = objectAddress.split(/[,;]/).map(p => p.trim()).filter(Boolean); if (parts.length > 0) return parts[parts.length - 1]; return account || '—'; } export const DebtorReportDetailView: React.FC = ({ report, onBack }) => { const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(''); const [onlyWithDebt, setOnlyWithDebt] = useState(false); const [selectedIds, setSelectedIds] = useState>(new Set()); const [transferring, setTransferring] = useState(false); const fetchRows = useCallback(async () => { setLoading(true); try { const params = new URLSearchParams(); if (search.trim()) params.set('search', search.trim()); if (onlyWithDebt) params.set('minDebt', '0.01'); const url = `/api/finance/reports/${report.id}/debtor-rows${params.toString() ? '?' + params.toString() : ''}`; const res = await authFetch(url); if (!res.ok) throw new Error('Не удалось загрузить данные'); const data = await res.json(); setRows(data); } catch (e) { console.error(e); setRows([]); } finally { setLoading(false); } }, [report.id, search, onlyWithDebt]); useEffect(() => { fetchRows(); }, [fetchRows]); const toggleSelect = (id: number) => { setSelectedIds(prev => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }; const toggleSelectAll = () => { if (selectedIds.size === rows.length) { setSelectedIds(new Set()); } else { setSelectedIds(new Set(rows.map(r => r.id))); } }; const handleTransferToLegal = async () => { const selected = rows.filter(r => selectedIds.has(r.id)); if (selected.length === 0) { alert('Выберите хотя бы одну строку для передачи в досудебную работу.'); return; } setTransferring(true); let ok = 0; let err = 0; for (const row of selected) { const apartment = extractApartment(row.objectAddress, row.account); const debtMonths = row.monthsDebt != null ? row.monthsDebt : 0; if (debtMonths < 1) { err++; continue; } try { const res = await authFetch('/api/legal/debtors', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ buildingId: null, apartment, debtorName: row.responsibleName || undefined, address: row.objectAddress || row.account, debtAmount: row.totalDebt, debtMonths, }), }); if (res.ok) ok++; else err++; } catch { err++; } } setTransferring(false); setSelectedIds(new Set()); if (ok > 0) alert(`Передано в досудебную работу: ${ok}.${err > 0 ? ` Ошибок: ${err}.` : ''}`); if (err > 0 && ok === 0) alert('Не удалось передать. Проверьте, что у выбранных строк указаны месяцы задолженности.'); }; return (
{report.filename}
setSearch(e.target.value)} className="w-full pl-9 pr-4 py-2 border border-slate-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-primary-500" />
{selectedIds.size > 0 && ( )}
{loading ? (
) : rows.length === 0 ? (
Нет данных по заданным фильтрам.
) : (
{rows.map(row => ( ))}
0 && selectedIds.size === rows.length} onChange={toggleSelectAll} className="rounded border-slate-300 text-primary-600" /> Лицевой счёт Ответственный Объект учета Месяцев Сумма задолженности
toggleSelect(row.id)} className="rounded border-slate-300 text-primary-600" /> {row.account} {row.responsibleName ?? '—'} {row.objectAddress ?? '—'} {row.monthsDebt != null ? row.monthsDebt : '—'} 0 ? 'text-red-600' : 'text-slate-600'}> {typeof row.totalDebt === 'number' ? row.totalDebt.toLocaleString('ru-RU', { minimumFractionDigits: 2 }) : row.totalDebt} ₽
)}
{!loading && rows.length > 0 && (

Всего строк: {rows.length}

)}
); };