From 92d65374ef1a0f153b8e6b16d97f1470a89b9bd8 Mon Sep 17 00:00:00 2001 From: kitos Date: Tue, 14 Jul 2026 12:00:53 +0200 Subject: [PATCH] fix(tests): stop requiring Containment Time when result is Not Contained MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Containment Time was gated on detection alone, so picking "Not Contained" still showed the field and required it before saving — there's no containment moment to record when nothing was contained. Now only shown and required when the result is Contained or Partially Contained; a stale value from a prior Contained selection is cleared when the result changes away from it, so it can't get silently resubmitted once hidden. --- .../src/components/test-detail/TeamTabs.tsx | 48 +++++++++++-------- frontend/src/pages/TestDetailPage.tsx | 15 +++++- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/test-detail/TeamTabs.tsx b/frontend/src/components/test-detail/TeamTabs.tsx index c2baed2..513e015 100644 --- a/frontend/src/components/test-detail/TeamTabs.tsx +++ b/frontend/src/components/test-detail/TeamTabs.tsx @@ -189,6 +189,12 @@ export default function TeamTabs({ ? blueDraft.detection_result === "detected" || blueDraft.detection_result === "partially_detected" : test.detection_result === "detected" || test.detection_result === "partially_detected"; + // Containment Time only makes sense when something was actually contained — + // "Not Contained" has no containment moment to record. + const isContained = canEditBlue + ? blueDraft.containment_result === "contained" || blueDraft.containment_result === "partially_contained" + : test.containment_result === "contained" || test.containment_result === "partially_contained"; + // Hint messages shown to operators when editing is locked const redLockedHint = test.state === "draft" && role === "red_tech" @@ -520,7 +526,9 @@ export default function TeamTabs({ )} - {/* Detection & Containment times — only when detected/partially_detected */} + {/* Detection & Containment times — Detection Time whenever detected; + Containment Time only when something was actually contained — + "Not Contained" has no containment moment to record. */} {isDetected && (
@@ -541,24 +549,26 @@ export default function TeamTabs({

)}
-
- - {canEditBlue ? ( - onBlueFieldChange("containment_time", e.target.value)} - className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-200 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" - /> - ) : ( -

- {test.containment_time ? new Date(test.containment_time + "Z").toUTCString().replace(" GMT", " UTC") : "—"} -

- )} -
+ {isContained && ( +
+ + {canEditBlue ? ( + onBlueFieldChange("containment_time", e.target.value)} + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-200 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" + /> + ) : ( +

+ {test.containment_time ? new Date(test.containment_time + "Z").toUTCString().replace(" GMT", " UTC") : "—"} +

+ )} +
+ )}
)} diff --git a/frontend/src/pages/TestDetailPage.tsx b/frontend/src/pages/TestDetailPage.tsx index f493a89..852fdd1 100644 --- a/frontend/src/pages/TestDetailPage.tsx +++ b/frontend/src/pages/TestDetailPage.tsx @@ -446,7 +446,17 @@ export default function TestDetailPage() { }; const handleBlueFieldChange = (field: string, value: string) => { - setBlueDraft((prev) => ({ ...prev, [field]: value })); + setBlueDraft((prev) => { + const next = { ...prev, [field]: value }; + // Containment Time only applies when something was actually + // contained — clear a stale value left over from a previous + // "Contained"/"Partially Contained" selection so it doesn't get + // silently resubmitted once the field is hidden again. + if (field === "containment_result" && value !== "contained" && value !== "partially_contained") { + next.containment_time = ""; + } + return next; + }); }; const handleUploadEvidence = async (file: File, team: TeamSide) => { @@ -624,8 +634,9 @@ export default function TestDetailPage() {