-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathpage.tsx
More file actions
223 lines (214 loc) · 7.91 KB
/
Copy pathpage.tsx
File metadata and controls
223 lines (214 loc) · 7.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
import Link from "next/link";
import {
ApiErrorPanel,
EmptyState,
MissingDataPanel,
StatusBadge,
} from "../components/demo-state";
import {
ApiClientError,
type RcaCapaDraft,
formatApiError,
getApiBaseUrl,
workbenchApi,
} from "../../lib/api-client";
import { DraftCopyButton } from "./draft-copy-button";
export const dynamic = "force-dynamic";
type RcaCapaDraftPageProps = {
searchParams: Promise<{
detection_id?: string;
}>;
};
export default async function RcaCapaDraftPage({
searchParams,
}: RcaCapaDraftPageProps) {
const { detection_id: detectionId } = await searchParams;
const result = await loadDraft(detectionId);
return (
<section className="content-panel">
<span className="demo-label">Demo-generated draft</span>
<div>
<h1>RCA/CAPA Draft</h1>
<p className="lead">
Preview investigation-ready RCA/CAPA draft language for the selected
external-source Process Sentinel detection. Draft content is
human-review required and is not automatically submitted to QMS or MES
systems.
</p>
</div>
{!result.ok && result.notFound ? (
<MissingDataPanel
nextStep="Open a current runtime detection and use its RCA/CAPA draft link, or check Demo-Factory and the FIP Docker Compose stack if the event store was reset."
text={
<>
The local FIP API did not return an RCA/CAPA draft for{" "}
<code>{result.detectionId}</code>. Open a current runtime detection
and use its RCA/CAPA draft link.
</>
}
title="Draft not found"
/>
) : null}
{!result.ok && !result.notFound ? <ApiErrorPanel message={result.message} /> : null}
{result.ok && !result.draft ? (
<EmptyState
nextStep="Start Demo-Factory, then start the FIP Docker Compose stack and open the draft from a current detection detail page."
text="The local API is reachable, but no external-source detection is available for draft preview yet."
title="No detection available for draft preview"
/>
) : null}
{result.ok && result.draft ? (
<div className="data-stack">
<div className="decision-note">
This RCA/CAPA draft is generated from runtime detection, evidence,
and recommendation state. It is advisory decision support for human
review only; it is not a validated production record and it does not
submit anything to QMS or MES.
</div>
<article className="data-card rca-draft-card">
<div className="draft-heading">
<div>
<span className="status-label">Draft preview</span>
<h2>{result.draft.title}</h2>
</div>
<DraftCopyButton draftText={formatDraftForCopy(result.draft)} />
</div>
<dl className="metric-grid">
<div>
<dt>Detection ID</dt>
<dd>{result.draft.detection_id}</dd>
</div>
<div>
<dt>Human review</dt>
<dd>
<StatusBadge
tone={result.draft.human_review_required ? "warning" : "success"}
value={result.draft.human_review_required ? "Required" : "Not required"}
/>
</dd>
</div>
<div>
<dt>System submission</dt>
<dd>
<StatusBadge tone="draft" value="Not submitted to QMS" />
</dd>
</div>
<div>
<dt>Source</dt>
<dd>
<StatusBadge tone="info" value="External-source event store" />
</dd>
</div>
</dl>
<section className="draft-section" aria-labelledby="problem-statement-heading">
<span className="status-label">Problem statement</span>
<h3 id="problem-statement-heading">Problem statement</h3>
<p>{result.draft.problem_statement}</p>
</section>
<section className="draft-section" aria-labelledby="evidence-summary-heading">
<span className="status-label">Evidence summary</span>
<h3 id="evidence-summary-heading">Evidence summary</h3>
<ul>
{result.draft.evidence_summary.map((summary) => (
<li key={summary}>{summary}</li>
))}
</ul>
</section>
<section
className="draft-section"
aria-labelledby="recommended-containment-heading"
>
<span className="status-label">Recommended containment</span>
<h3 id="recommended-containment-heading">Recommended containment</h3>
<p>{result.draft.recommended_containment}</p>
</section>
<section className="draft-section" aria-labelledby="capa-placeholder-heading">
<span className="status-label">CAPA placeholder</span>
<h3 id="capa-placeholder-heading">CAPA placeholder</h3>
<p>{result.draft.capa_placeholder}</p>
</section>
</article>
<section className="workflow-links" aria-label="RCA/CAPA workflow links">
<article className="data-card compact">
<span className="status-label">Linked detection</span>
<h2>Detection detail</h2>
<p>Return to the detection and evidence timeline that generated this draft.</p>
<Link
className="secondary-action"
href={`/detections/${result.draft.detection_id}`}
>
Open detection
</Link>
</article>
<article className="data-card compact">
<span className="status-label">Governed review</span>
<h2>Recommendation review</h2>
<p>Review the governed recommendation before using draft language.</p>
<Link
className="secondary-action"
href={`/recommendations?detection_id=${encodeURIComponent(
result.draft.detection_id,
)}`}
>
Open recommendation
</Link>
</article>
</section>
</div>
) : null}
<div className="api-panel">
<p>
Configured API target: <code>{getApiBaseUrl()}</code>
</p>
</div>
</section>
);
}
async function loadDraft(detectionId?: string) {
try {
const selectedDetectionId = detectionId ?? (await getFirstDetectionId());
if (selectedDetectionId === undefined) {
return { draft: null, ok: true as const };
}
const draft = await workbenchApi.getRcaCapaDraft(selectedDetectionId);
return { draft, ok: true as const };
} catch (error) {
return {
detectionId: detectionId ?? "selected detection",
message: formatApiError(error),
notFound: isMissingDraft(error),
ok: false as const,
};
}
}
async function getFirstDetectionId(): Promise<string | undefined> {
const detections = await workbenchApi.listDetections();
return detections[0]?.detection_id;
}
function isMissingDraft(error: unknown): boolean {
return (
error instanceof ApiClientError &&
(error.status === 404 || error.code === "detection_not_found")
);
}
function formatDraftForCopy(draft: RcaCapaDraft): string {
return [
draft.title,
"",
`Detection ID: ${draft.detection_id}`,
"Human review required: yes",
"Demo-generated draft; not submitted to QMS/MES.",
"",
"Problem statement",
draft.problem_statement,
"",
"Evidence summary",
...draft.evidence_summary.map((summary) => `- ${summary}`),
"",
"Recommended containment",
draft.recommended_containment,
"",
"CAPA placeholder",
draft.capa_placeholder,
].join("\n");
}