import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { X, AlertTriangle, CheckCircle2, TrendingUp, BarChart3 } from 'lucide-react'; import { DevAuditData, InspectionData } from '../../types'; import { backendApi } from '../../services/apiClient'; import { AUDIT_INSPECTION_SCHEMA, AUDIT_STATUS_LABELS, INSPECTION_SCORE_LABELS, calcCategoryAverage, calcComplexityIndexFromCategoryAverages, calcWearPercentFromCategoryAverages, calculateTariffFromAudit, formatCategoryOverall, normalizeRatingToScore, } from './auditInspectionSchema'; interface Props { auditId: string | null; onClose: () => void; onSaved: () => void; } const STATUS_OPTIONS: Array<'new' | 'in_progress' | 'completed'> = ['new', 'in_progress', 'completed']; export const AuditCardModal: React.FC = ({ auditId, onClose, onSaved }) => { const [audit, setAudit] = useState(null); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); const [form, setForm] = useState & { inspectionData?: InspectionData }>({}); const fetchAudit = useCallback(async () => { if (!auditId) return; try { setLoading(true); const data = await backendApi.getDevelopmentAudit(auditId); setAudit(data); setForm({ status: data.status, wearPercent: data.wearPercent, projectedMargin: data.projectedMargin, inspectionData: data.inspectionData ?? {}, }); } catch (e) { console.error(e); setAudit(null); } finally { setLoading(false); } }, [auditId]); useEffect(() => { fetchAudit(); }, [fetchAudit]); const handleSave = async () => { if (!auditId) return; try { setSaving(true); await backendApi.updateDevelopmentAudit(auditId, { status: form.status, wearPercent: computedWearPercent ?? form.wearPercent ?? 0, projectedMargin: form.projectedMargin, inspectionData: form.inspectionData ?? undefined, }); onSaved(); } catch (e) { console.error(e); alert('Ошибка сохранения'); } finally { setSaving(false); } }; const updateInspection = (categoryKey: string, subKey: string, field: string, value: unknown) => { setForm(prev => { const data = { ...(prev.inspectionData || {}) }; const cat = data[categoryKey as keyof InspectionData] || { subItems: [] }; const subItems = [...(cat.subItems || [])]; let item = subItems.find(s => s.key === subKey); if (!item) { item = { key: subKey, label: '', rating: null, description: null, noAccess: false, notPresent: false }; subItems.push(item); } if (field === 'rating' && value !== null && value !== '') { const n = typeof value === 'number' ? value : parseInt(String(value), 10); value = Number.isFinite(n) && n >= 1 && n <= 5 ? n : null; } item = { ...item, [field]: value }; const idx = subItems.findIndex(s => s.key === subKey); if (idx >= 0) subItems[idx] = item; else subItems.push(item); data[categoryKey as keyof InspectionData] = { ...cat, subItems }; return { ...prev, inspectionData: data }; }); }; const getSubItem = (categoryKey: string, subKey: string) => { const cat = (form.inspectionData || {})[categoryKey as keyof InspectionData]; const item = cat?.subItems?.find(s => s.key === subKey); return item; }; /** Итог по пункту = среднее арифметическое подпунктов (только 1–5, без noAccess/notPresent) */ const getCategoryOverall = (categoryKey: string): number | null => { const cat = (form.inspectionData || {})[categoryKey as keyof InspectionData]; return calcCategoryAverage(cat?.subItems || []); }; /** Износ % и индекс сложности из средних по пунктам осмотра: (5 − среднее) / 4 × 100 */ const categoryAverages = useMemo(() => { const data = form.inspectionData || {}; return AUDIT_INSPECTION_SCHEMA.map(cat => calcCategoryAverage((data[cat.key] as { subItems?: unknown[] })?.subItems || [])); }, [form.inspectionData]); const computedWearPercent = useMemo(() => calcWearPercentFromCategoryAverages(categoryAverages), [categoryAverages]); const computedComplexityIndex = useMemo(() => calcComplexityIndexFromCategoryAverages(categoryAverages), [categoryAverages]); /** Прогнозный тариф: автоматически при изменении маржи, износа и индекса сложности */ const computedTariff = useMemo(() => { const wear = computedWearPercent ?? 0; const complexity = computedComplexityIndex ?? 50; const margin = form.projectedMargin ?? 15; return calculateTariffFromAudit({ wearPercent: wear, complexityIndex: complexity, projectedMargin: margin }); }, [computedWearPercent, computedComplexityIndex, form.projectedMargin]); if (!auditId) return null; return (
e.stopPropagation()} >

{audit?.address ?? 'Аудит'}

{loading ? (
Загрузка...
) : audit ? (
{/* Статус */}
{/* Износ дома — только из данных пунктов осмотра (средние 1–5 → %), без ручного ввода */}

Износ дома, %

{computedWearPercent != null ? computedWearPercent : '—'} % {computedWearPercent != null && (computedWearPercent > 50 ? : )}

Из средних по пунктам осмотра (1–5)

Расчётная маржа, %

setForm(f => ({ ...f, projectedMargin: parseFloat(e.target.value) || 0 }))} className="w-24 text-2xl font-black border-0 bg-transparent p-0 focus:ring-0 text-primary-600" /> %
{/* Индекс сложности (из осмотра) и прогнозный тариф (автоматически при изменении маржи, износа, сложности) */}

Индекс сложности дома (0–100)

{computedComplexityIndex != null ? computedComplexityIndex : '—'} %

Из средних по пунктам осмотра (1–5)

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

{computedTariff} ₽/м²

Меняется при изменении маржи, износа и индекса сложности

{/* Пункты осмотра */}

Пункты осмотра

{AUDIT_INSPECTION_SCHEMA.map(cat => (
{cat.label} Среднее: {formatCategoryOverall(getCategoryOverall(cat.key))}
{cat.subItems.map(sub => { const item = getSubItem(cat.key, sub.key); const ratingVal = normalizeRatingToScore(item?.rating) ?? null; return (
{sub.label}