From 0d54fcc893ac2ca3e96298e4bf6b1b930e1f5901 Mon Sep 17 00:00:00 2001 From: GGlamer <52128225+Gamer201760@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:48:50 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D1=84=D0=BE=D1=80=D0=BC=D0=B0=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B7=D0=B1=D0=BE=D1=80=D0=B0=20=D1=81=20=D0=B8=D1=81?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=B8=D0=B5=D0=B9=20=D1=80=D0=B5=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B9,=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=BC=20=D0=BA=D0=B0=D1=80=D1=82=D0=BE=D1=87=D0=BA=D0=B8=20?= =?UTF-8?q?=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=B4=D0=BB=D1=8F=20D-?= =?UTF-8?q?=D0=BA=D0=BE=D0=B4=D0=BE=D0=B2=20=D0=B8=20=D0=BA=D0=BB=D1=8E?= =?UTF-8?q?=D1=87=D0=BE=D0=BC=20=D0=BF=D0=BE=D0=B2=D1=82=D0=BE=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/debrief/FindingReview.tsx | 38 +++++++++++++------ frontend/src/shared/types/generated.ts | 4 +- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/frontend/src/features/debrief/FindingReview.tsx b/frontend/src/features/debrief/FindingReview.tsx index 7465dbb..ef47f23 100644 --- a/frontend/src/features/debrief/FindingReview.tsx +++ b/frontend/src/features/debrief/FindingReview.tsx @@ -3,7 +3,7 @@ // Балл пересчитывает сервер теми же метриками и весами (lct-35): здесь только // решение и его причина. Причина обязательна — она остаётся в отчёте и в CSV/PDF. -import { useState } from "react"; +import { useRef, useState } from "react"; import type { ErrorCode, FindingDecision, SessionReport } from "@/shared/types/generated"; @@ -34,7 +34,13 @@ export function FindingReview({ report, onChange }: { : cards.length ? [...CALL_CODES, ...DDS_CODES] : CALL_CODES; const [reasons, setReasons] = useState>({}); const [code, setCode] = useState(codes[0]); - const [card, setCard] = useState(cards.length ? "1" : ""); + // В ДДС все метрики принадлежат карточкам очереди. В связке 112 → ДДС + // E-коды относятся к занятию, а D-код без карточки — к живой карточке звонка. + const [card, setCard] = useState(report.exercise === "dds" && cards.length ? "1" : ""); + const cardChoice = cards.length > 0 && (report.exercise === "dds" || code.startsWith("D")); + // Один ключ на отметку, пока она не сохранена: повтор после сбоя сети + // не добавит её второй раз. + const pendingId = useRef(null); const [fact, setFact] = useState(""); const [norm, setNorm] = useState(""); const [busy, setBusy] = useState(false); @@ -71,10 +77,14 @@ export function FindingReview({ report, onChange }: { setError("Для отметки нужны факт и норма."); return; } + pendingId.current ??= crypto.randomUUID(); + const clientId = pendingId.current; void run(async () => { const updated = await send(base, { - code, fact: fact.trim(), norm: norm.trim(), card: card ? Number(card) : null, + code, fact: fact.trim(), norm: norm.trim(), card: cardChoice && card ? Number(card) : null, + client_id: clientId, }); + pendingId.current = null; setFact(""); setNorm(""); return updated; @@ -97,9 +107,11 @@ export function FindingReview({ report, onChange }: { {finding.summary}
{finding.fact}{finding.norm && ` · ${finding.norm}`}
- {finding.review &&
- {finding.review.decision === "dismissed" ? "Снята" : "Подтверждена"}: {finding.review.reason} -
} + {(finding.reviews ?? []).map((review, reviewIndex, all) =>
+ {review.decision === "dismissed" ? "Снята" : "Подтверждена"} ({review.author},{" "} + {new Date(review.at).toLocaleString("ru-RU")}): {review.reason} +
)}
Добавить отметку
- {cards.length > 0 && }