"use client"; import { useState } from "react"; import type { ComponentType } from "react"; import { AlertOctagon, AlertTriangle, CheckCircle2, Clock, HelpCircle, XCircle, type LucideProps, } from "lucide-react"; import type { GateVerdict, GateVerdictLabel } from "@/types/site-finder"; import { SectionLabel } from "@/components/ui/SectionLabel"; interface GateVerdictBannerProps { verdict: GateVerdict | null | undefined; } interface VerdictColor { bg: string; border: string; Icon: ComponentType; iconBg: string; } const COLOR_BY_LABEL: Record = { Можно: { bg: "#ecfdf5", border: "#34d399", Icon: CheckCircle2, iconBg: "#10b981", }, Нельзя: { bg: "#fef2f2", border: "#f87171", Icon: XCircle, iconBg: "#ef4444", }, "С ограничениями": { bg: "#fffbeb", border: "#fbbf24", Icon: AlertTriangle, iconBg: "#f59e0b", }, "Нужна проверка": { bg: "#f9fafb", border: "#9ca3af", Icon: HelpCircle, iconBg: "#6b7280", }, }; const SOURCE_LABEL: Record = { nspd_dump: "данные NSPD актуальны", nspd_dump_partial: "данные NSPD устарели (>180 дней)", no_data: "данные NSPD не подгружены", }; // Человеческий ЗАГОЛОВОК для сырых gate-кодов из backend (gate_verdict.py). // Бэкенд отдаёт {code, detail}; detail уже человекочитаемый — мы поверх него // показываем короткий жирный заголовок по коду, а сам код оставляем мелким // тех-тегом (для отладки). Семантика 1:1 с compute_gate_verdict. const GATE_CODE_TITLE: Record = { PZZ_NOT_RESIDENTIAL: "Зона не под жильё", PZZ_UNKNOWN: "Зона ПЗЗ не определена", ZOUIT_CAD_SZZ: "Санитарно-защитная зона (СЗЗ)", ZOUIT_CAD_OTHER: "Охранная зона (ЗОУИТ)", ZOUIT_CAD_BLOCKER: "Охранная зона инж.сетей", ZOUIT_NETWORK_OBREMENENIE: "Сетевое обременение (инж.сети)", ZOUIT_ENGINEERING_PARTIAL: "Охранная зона инж.сетей (частично)", ZOUIT_OVERLAP_SUB17: "Охранная зона инж.сетей", NO_NSPD_DUMP: "Данные НСПД не подгружены", NO_ENGINEERING_NEARBY: "Инж.сети рядом не найдены", }; // Динамические ZOUIT-коды (ZOUIT_SUB17, ZOUIT_OVERLAP_SUB17, ZOUIT_OVERLAP_SUBxx) // несут суффикс subcategory — резолвим по префиксу к одному заголовку. const GATE_CODE_PREFIX_TITLE: Array<[string, string]> = [ ["ZOUIT_OVERLAP_SUB", "Охранная зона инж.сетей"], ["ZOUIT_SUB", "Зона с особыми условиями (ЗОУИТ)"], ]; /** * Человеческий заголовок для gate-кода. Сначала точное совпадение, затем * префиксная резолюция (ZOUIT_SUB.. / ZOUIT_OVERLAP_SUB..), и graceful fallback — * сам код как заголовок (как было до #1953), чтобы новый backend-код не «терялся». */ function gateCodeTitle(code: string): string { const exact = GATE_CODE_TITLE[code]; if (exact) return exact; for (const [prefix, title] of GATE_CODE_PREFIX_TITLE) { if (code.startsWith(prefix)) return title; } return code; } function blockerCountLabel(n: number): string { if (n === 1) return "1 блокер"; if (n >= 2 && n <= 4) return `${n} блокера`; return `${n} блокеров`; } function warningCountLabel(n: number): string { if (n === 1) return "1 предупреждение"; if (n >= 2 && n <= 4) return `${n} предупреждения`; return `${n} предупреждений`; } /** * Один пункт блокера/предупреждения: жирный человеческий заголовок (по коду) + * detail из backend (полное предложение) + сам код мелким приглушённым моно-тегом * (для отладки, не в глаза). Цвет текста наследуется от родительского
    . */ function GateItem({ code, detail }: { code: string; detail: string }) { const title = gateCodeTitle(code); return (
  • {title}
    {detail &&
    {detail}
    } {code}
  • ); } export function GateVerdictBanner({ verdict }: GateVerdictBannerProps) { const [expanded, setExpanded] = useState(false); if (!verdict) return null; const color = COLOR_BY_LABEL[verdict.verdict_label]; const Icon = color.Icon; const hasDetails = verdict.blockers.length > 0 || verdict.warnings.length > 0; const iconSx = { flexShrink: 0 } as const; const sourceHint = verdict.source === "nspd_dump_partial" ? ( <> устарели ) : verdict.source === "no_data" ? ( <> нет данных ) : ( <> свежие ); const summaryParts: string[] = []; if (verdict.blockers.length > 0) summaryParts.push(blockerCountLabel(verdict.blockers.length)); if (verdict.warnings.length > 0) summaryParts.push(warningCountLabel(verdict.warnings.length)); const summaryText = summaryParts.length > 0 ? summaryParts.join(" · ") : "Без замечаний"; return (
    {/* Header row */}
    МКД: {verdict.verdict_label}
    {summaryText} {" · "} {sourceHint}
    {hasDetails && ( )}
    {/* Expanded details */} {expanded && (
    {verdict.blockers.length > 0 && (
    Блокеры
      {verdict.blockers.map((b) => ( ))}
    )} {verdict.warnings.length > 0 && (
    Предупреждения
      {verdict.warnings.map((w) => ( ))}
    )} {verdict.checks_performed.length > 0 && (
    Проверки: {verdict.checks_performed.join(" · ")}
    )}
    )}
    ); }