Files
lda-wf/web/apps/console/src/presentation/InterruptDecisionForm.tsx
T

123 lines
3.9 KiB
TypeScript

import { useCallback, useState } from "react";
import type { RunFactsInterrupt } from "./demo-run-facts.js";
type InterruptDecisionFormProps = {
readonly interrupt: RunFactsInterrupt;
readonly runId: string;
readonly onSubmit?: ((selectedIssueIds: ReadonlyArray<string>, comment: string) => void) | undefined;
readonly onRequestRevision?: (() => void) | undefined;
readonly terminalOutcome?: "submitted" | "revision requested" | undefined;
readonly showReportPreview?: boolean;
};
export const InterruptDecisionForm = ({
interrupt,
runId,
onSubmit,
onRequestRevision,
terminalOutcome,
showReportPreview = true,
}: InterruptDecisionFormProps) => {
const [selectedIds, setSelectedIds] = useState<Set<string>>(() =>
new Set(interrupt.proposedIssues.map((issue) => issue.id)),
);
const [comment, setComment] = useState("Create the selected issue.");
const toggleIssue = useCallback((id: string) => {
setSelectedIds((prev) => {
const next = new Set(prev);
if (next.has(id)) next.delete(id);
else next.add(id);
return next;
});
}, []);
const handleSubmit = useCallback(
(e: React.FormEvent) => {
e.preventDefault();
onSubmit?.([...selectedIds], comment);
},
[selectedIds, comment, onSubmit],
);
if (terminalOutcome) {
return (
<div className="interrupt-decision-form" role="group" aria-label="operator resume decision">
<header className="interrupt-decision-form__header">
<span className="interrupt-decision-form__kind">{interrupt.kind}</span>
<span className="interrupt-decision-form__run-id">{runId}</span>
</header>
<p className="interrupt-decision-form__terminal">
Outcome: <strong>{terminalOutcome}</strong>
</p>
</div>
);
}
return (
<form
className="interrupt-decision-form"
role="group"
aria-label="operator resume decision"
onSubmit={handleSubmit}
>
<header className="interrupt-decision-form__header">
<span className="interrupt-decision-form__kind">{interrupt.kind}</span>
<span className="interrupt-decision-form__run-id">{runId}</span>
</header>
<dl className="interrupt-decision-form__meta">
<dt>Typed</dt>
<dd>{interrupt.typed ? "yes" : "no"}</dd>
<dt>Outcomes</dt>
<dd>{interrupt.outcomes.join(" / ")}</dd>
</dl>
{showReportPreview && interrupt.reportMarkdownPreview && (
<pre className="interrupt-decision-form__report-preview">
{interrupt.reportMarkdownPreview}
</pre>
)}
<fieldset className="interrupt-decision-form__issues">
<legend>Proposed issues</legend>
{interrupt.proposedIssues.map((issue) => (
<label key={issue.id} className="interrupt-decision-form__issue-row">
<input
type="checkbox"
checked={selectedIds.has(issue.id)}
onChange={() => toggleIssue(issue.id)}
/>
<span className="interrupt-decision-form__issue-title">{issue.title}</span>
<span className="interrupt-decision-form__issue-severity">{issue.severity}</span>
</label>
))}
</fieldset>
<label className="interrupt-decision-form__comment-label">
Resume comment
<textarea
className="interrupt-decision-form__comment"
value={comment}
onChange={(e) => setComment(e.target.value)}
rows={3}
/>
</label>
<div className="interrupt-decision-form__actions">
<button type="submit" className="interrupt-decision-form__submit" disabled={!onSubmit}>
Submit
</button>
<button
type="button"
className="interrupt-decision-form__cancel"
onClick={() => onRequestRevision?.()}
disabled={!onRequestRevision}
>
Request revision
</button>
</div>
</form>
);
};