import React, { useState, useEffect } from 'react'; import { ResidentReport, Building, WorkPhoto } from '../../types'; import { apiClient } from '../../services/apiClient'; import { FileText, Sparkles, Send, Download, Eye, CheckCircle2, History, Bot, X, Loader2, Calendar, Link2, Key } from 'lucide-react'; import { BuildingReportPage } from './BuildingReportPage'; export const ResidentReports: React.FC = () => { const [reports, setReports] = useState([]); const [isLoading, setIsLoading] = useState(true); const [selectedReportId, setSelectedReportId] = useState(null); const [showAccessKeyModal, setShowAccessKeyModal] = useState(false); useEffect(() => { loadReports(); }, []); const loadReports = async () => { try { setIsLoading(true); const data = await apiClient.get('/pr/reports'); setReports(data); } catch (err) { console.error('Error loading reports:', err); setReports([]); } finally { setIsLoading(false); } }; const handlePublish = async (reportId: string | number) => { try { // При публикации отчет обновляется актуальными данными await apiClient.post(`/pr/reports/${reportId}/publish`); await loadReports(); alert('Отчет обновлен актуальными данными и опубликован'); } catch (err: any) { console.error('Error publishing report:', err); alert(`Ошибка публикации отчета: ${err.message || 'Неизвестная ошибка'}`); } }; // Если выбран отчет для просмотра, показываем страницу отчета if (selectedReportId) { // Для демо используем BuildingReportPage if (selectedReportId === 'demo') { return ( setSelectedReportId(null)} mode="portal" reportId="demo" /> ); } // Для реальных отчетов используем BuildingReportPage с данными из БД const report = reports.find(r => r.id === selectedReportId); if (report) { // Определяем текущий месяц из отчета или используем текущий месяц const now = new Date(); const currentMonthName = now.toLocaleDateString('ru-RU', { month: 'long' }); const currentYear = now.getFullYear(); const initialMonth = report.month || `${currentMonthName} ${currentYear}`; return ( setSelectedReportId(null)} mode="portal" reportId={report.buildingId} // Используем buildingId для загрузки данных по месяцам /> ); } // Fallback, если отчет не найден return (

Отчет не найден

); } return (
{/* Header */}

Реестр ежемесячных отчетов

Отчеты создаются автоматически 1 числа каждого месяца

{/* List of Reports */} {isLoading ? (
) : (
{reports.map(report => (

{report.address || `Отчет #${report.id}`}

{report.month} • {report.status === 'published' ? 'Опубликован' : 'Черновик'}

{(report.liveStats || (report.content && typeof report.content === 'object')) && (
{ const collected = report.liveStats?.fundsCollected ?? (report.content as any)?.finances?.collected ?? report.content?.finances?.income ?? 0; return `${(collected / 1000000).toFixed(1)}M ₽`; })()} />
)}
{report.status === 'draft' ? ( ) : (
Опубликован
)}
))}
)} {/* Модальное окно с ключом доступа */} {showAccessKeyModal && ( setShowAccessKeyModal(false)} reportId="demo" /> )}
); }; const ReportStat = ({ label, value }: { label: string; value: string | number }) => (

{label}

{value}

); interface ReportCreateFormProps { onClose: () => void; onSuccess: () => void; selectedBuilding: Building | null; onBuildingSelect: (building: Building | null) => void; } const ReportCreateForm: React.FC = ({ onClose, onSuccess, selectedBuilding, onBuildingSelect }) => { const [buildings, setBuildings] = useState([]); const [formData, setFormData] = useState({ building_id: '', month: '', period_start: '', period_end: '', createForAll: false, // Создать для всех домов selectedBuildings: [] as string[] // Выбранные дома для массового создания }); const [isSubmitting, setIsSubmitting] = useState(false); const [reportData, setReportData] = useState(null); const [isGenerating, setIsGenerating] = useState(false); const [creationProgress, setCreationProgress] = useState<{ total: number; completed: number; current: string; } | null>(null); useEffect(() => { loadBuildings(); }, []); useEffect(() => { if (formData.building_id && formData.period_start && formData.period_end) { loadReportData(); } }, [formData.building_id, formData.period_start, formData.period_end]); const loadBuildings = async () => { try { const data = await apiClient.get('/buildings'); setBuildings(data); } catch (err) { console.error('Error loading buildings:', err); } }; const loadReportData = async () => { try { setIsGenerating(true); // Загружаем данные для отчета const building = buildings.find(b => b.id === formData.building_id); if (!building) return; // Заявки const applications = await apiClient.get(`/applications`).catch(() => []); // Финансовые данные const financialData = await apiClient.get(`/finance/buildings/${formData.building_id}/summary`).catch(() => null); // Фото отчеты const workPhotos = await apiClient.get(`/pr/work-photos?building_id=${formData.building_id}`).catch(() => []); setReportData({ building, applications: Array.isArray(applications) ? applications : [], financialData, workPhotos: Array.isArray(workPhotos) ? workPhotos : [] }); } catch (err) { console.error('Error loading report data:', err); } finally { setIsGenerating(false); } }; const handleGenerate = async () => { if (!formData.period_start || !formData.period_end) { alert('Заполните период отчета'); return; } if (!formData.createForAll && !formData.building_id) { alert('Выберите дом или включите создание для всех домов'); return; } try { setIsGenerating(true); setIsSubmitting(true); if (formData.createForAll) { // Массовое создание для всех домов const targetBuildings = formData.selectedBuildings.length > 0 ? formData.selectedBuildings : buildings.map(b => b.id); setCreationProgress({ total: targetBuildings.length, completed: 0, current: 'Начало создания...' }); const result = await apiClient.post<{ success: boolean; reportsCreated: number; reports: Array<{ buildingId: string; reportId: number; updated: boolean }>; }>('/pr/reports/bulk-create', { month: formData.month, period_start: formData.period_start, period_end: formData.period_end, building_ids: targetBuildings }); setCreationProgress({ total: targetBuildings.length, completed: result.reportsCreated, current: 'Завершено' }); setTimeout(() => { setCreationProgress(null); alert(`Успешно создано отчетов: ${result.reportsCreated} из ${targetBuildings.length}`); onSuccess(); }, 1000); } else { // Создание для одного дома const report = await apiClient.post('/pr/reports', { building_id: formData.building_id, month: formData.month, period_start: formData.period_start, period_end: formData.period_end }); // Генерируем контент await apiClient.post(`/pr/reports/${report.id}/generate`); onSuccess(); } } catch (err: any) { console.error('Error creating report:', err); alert(`Ошибка создания отчета: ${err.message || 'Неизвестная ошибка'}`); } finally { setIsGenerating(false); setIsSubmitting(false); setCreationProgress(null); } }; const selectedBuildingData = buildings.find(b => b.id === formData.building_id); return (

Создать отчет жителям

{/* Опция создания для всех домов */}
{/* Форма выбора дома и периода */}
{!formData.createForAll ? (
) : (
{buildings.map(b => ( ))}

Выбрано: {formData.selectedBuildings.length || 'Все'} домов

)}
setFormData({ ...formData, month: e.target.value })} className="w-full p-3 border border-slate-200 rounded-xl text-sm" placeholder="Май 2024" required />
setFormData({ ...formData, period_start: e.target.value })} className="w-full p-3 border border-slate-200 rounded-xl text-sm" required />
setFormData({ ...formData, period_end: e.target.value })} className="w-full p-3 border border-slate-200 rounded-xl text-sm" required />
{/* Прогресс создания */} {creationProgress && (

Создание отчетов

{creationProgress.completed} / {creationProgress.total}
{creationProgress.current && (

Обрабатывается: {creationProgress.current}

)}
)} {/* Данные отчета */} {isGenerating && !creationProgress && (
)} {reportData && !isGenerating && (

Данные для отчета

{/* Информация о доме */} {reportData.building && (
Информация о доме

{reportData.building.passport?.address || 'Адрес не указан'}

{reportData.building.passport?.apartmentsCount && (

Квартир: {reportData.building.passport.apartmentsCount}

)}
)} {/* Заявки */} {reportData.applications && (
Заявки за период

Всего: {reportData.applications.length}

)} {/* Финансы */} {reportData.financialData && (
Финансы

Доходы: {reportData.financialData.totalIncome || 0} ₽

Расходы: {reportData.financialData.totalExpenses || 0} ₽

)} {/* Фото отчеты */} {reportData.workPhotos && reportData.workPhotos.length > 0 && (
Фото отчеты

Работ: {reportData.workPhotos.length}

)}
)} {/* Кнопки */}
); }; // Модальное окно с ключом доступа interface AccessKeyModalProps { onClose: () => void; reportId: string | number; } const AccessKeyModal: React.FC = ({ onClose, reportId }) => { const accessKey = `mkd-${String(reportId)}-key`; // Уникальный ключ на каждый отчёт (демо) const link = `${window.location.origin}/reports/${reportId}?mode=published&key=${accessKey}`; const [copied, setCopied] = useState(false); const [showKey, setShowKey] = useState(false); const handleCopyLink = () => { navigator.clipboard.writeText(link); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const handleCopyKey = () => { navigator.clipboard.writeText(accessKey); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return (

Ключ доступа к отчету

Важно: Сохраните ключ доступа в безопасном месте. Он потребуется для просмотра опубликованной версии отчета.

); };