import React, { useState, useEffect } from 'react'; import { DevAuditData } from '../../types'; import { Download, BarChart3, AlertTriangle, CheckCircle2, TrendingUp, Info, ChevronRight, Briefcase } from 'lucide-react'; import { backendApi } from '../../services/apiClient'; import { AuditCardModal } from './AuditCardModal'; import { AUDIT_INSPECTION_SCHEMA, AUDIT_STATUS_LABELS, calcCategoryAverage, formatCategoryOverall } from './auditInspectionSchema'; import type { InspectionData } from '../../types'; const PIPELINE_SEARCH_EVENT = 'mkd-pipeline-search-request'; interface TechnicalAuditProps { onNavigate?: (tab: string) => void; } export const TechnicalAudit: React.FC = ({ onNavigate }) => { const [audits, setAudits] = useState([]); const [loading, setLoading] = useState(true); const [selectedAuditId, setSelectedAuditId] = useState(null); const fetchAudits = async () => { try { setLoading(true); const data = await backendApi.getDevelopmentAudits(); setAudits(data); } catch (error) { console.error('Error fetching audits:', error); setAudits([]); } finally { setLoading(false); } }; useEffect(() => { fetchAudits(); }, []); const handleDownloadDefectList = async (e: React.MouseEvent, auditId: string) => { e.stopPropagation(); try { const result = await backendApi.getAuditDefectList(auditId); if (result.url) { window.open(result.url, '_blank'); } else { alert('Дефектная ведомость будет сгенерирована на основе данных аудита'); } } catch (error) { console.error('Error downloading defect list:', error); alert('Ошибка при загрузке дефектной ведомости'); } }; const getStatusBadgeClass = (status: string) => { if (status === 'completed') return 'bg-emerald-50 text-emerald-600'; if (status === 'in_progress') return 'bg-amber-50 text-amber-600'; return 'bg-slate-100 text-slate-600'; }; /** Среднее по пункту осмотра (1–5) из inspectionData; null — нет данных */ const getCategoryAverage = (audit: DevAuditData, categoryKey: string): number | null => { const data = (audit.inspectionData ?? {}) as InspectionData; const cat = data[categoryKey as keyof InspectionData]; const subItems = cat?.subItems ?? []; return calcCategoryAverage(subItems); }; /** Для списка: статус по среднему 1–5 или null → "—" */ const getCategoryDisplay = (audit: DevAuditData, categoryKey: string): { status: 'good' | 'fair' | 'bad' | null; label: string } => { const avg = getCategoryAverage(audit, categoryKey); if (avg == null) return { status: null, label: '—' }; if (avg >= 4) return { status: 'good', label: formatCategoryOverall(avg).split(' — ')[1] ?? 'Отлично' }; if (avg >= 2.5) return { status: 'fair', label: formatCategoryOverall(avg).split(' — ')[1] ?? 'Удовл.' }; return { status: 'bad', label: formatCategoryOverall(avg).split(' — ')[1] ?? 'Плохо' }; }; /** Есть ли в аудите хотя бы одна оценка по осмотру (не пустой/новый аудит) */ const hasInspectionRatings = (audit: DevAuditData): boolean => { const data = (audit.inspectionData ?? {}) as InspectionData; return AUDIT_INSPECTION_SCHEMA.some(cat => getCategoryAverage(audit, cat.key) != null); }; return (
{loading ? (

Загрузка...

) : audits.length === 0 ? (

Нет данных об аудитах

Аудит создаётся автоматически при переходе объекта воронки на этап «Анализ».

{onNavigate && ( )}
) : ( <>
{audits.map(audit => (
setSelectedAuditId(audit.id)} onKeyDown={(e) => e.key === 'Enter' && setSelectedAuditId(audit.id)} className="bg-white rounded-[2.5rem] border border-slate-200 shadow-sm overflow-hidden flex flex-col group hover:border-primary-400 hover:shadow-md transition-all cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500" >

{audit.address}

{AUDIT_STATUS_LABELS[audit.status] || 'Новый'}

Износ дома

50 ? 'text-red-600' : 'text-emerald-600'}`}> {hasInspectionRatings(audit) && audit.wearPercent != null ? `${audit.wearPercent}%` : '—'}

{hasInspectionRatings(audit) && (audit.wearPercent ?? 0) > 50 ? : }

Расчетная маржа

{audit.projectedMargin != null ? `${audit.projectedMargin}%` : '—'}

{AUDIT_INSPECTION_SCHEMA.map(cat => { const { status, label } = getCategoryDisplay(audit, cat.key); return ( ); })}

Прогнозный тариф

{hasInspectionRatings(audit) && audit.calculatedTariff != null ? audit.calculatedTariff : '—'} ₽/м²

{onNavigate && ( )}
Точка безубыточности: 28.5 ₽/м²
))}
)} setSelectedAuditId(null)} onSaved={() => { setSelectedAuditId(null); fetchAudits(); }} /> {audits.length > 0 && (

Автоматическое создание аудитов

Технический аудит создаётся автоматически при попадании объекта воронки на этап «Анализ». Данные аудита используются для расчёта вероятности успеха и прогнозного тарифа.

)}
); }; const AuditRow = ({ label, status, displayLabel }: { label: string; status: 'good' | 'fair' | 'bad' | null; displayLabel?: string }) => (
{label} {status == null ? (displayLabel ?? '—') : (displayLabel ?? (status === 'bad' ? 'Плохое' : status === 'good' ? 'Хорошее' : 'Удовл.'))}
);