Workflow-App/Workflow/components/ProcessActions.tsx

106 lines
3.7 KiB
TypeScript
Raw Permalink Normal View History

// components/ProcessActions.tsx
// Client component that drives the Process / Archive / Reopen buttons
// for a single onboarding submission. Optimistically disables itself
// while the API call is in flight and revalidates the page on success.
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
interface Props {
id: string;
status: "draft" | "submitted" | "processing" | "processed" | "rejected";
directorCount: number;
hasDuplicates: boolean;
}
export default function ProcessActions({
id,
status,
directorCount,
hasDuplicates,
}: Props) {
const router = useRouter();
const [busy, setBusy] = useState<"process" | "archive" | "reopen" | null>(null);
const [error, setError] = useState<string | null>(null);
async function call(action: "process" | "archive" | "reopen") {
setBusy(action);
setError(null);
try {
const res = await fetch(`/api/onboarding/${id}/${action}`, { method: "POST" });
if (!res.ok) {
const body = await res.json().catch(() => ({ error: "Unknown error" }));
throw new Error(body.error ?? `Request failed (${res.status})`);
}
router.refresh();
} catch (err) {
setError(err instanceof Error ? err.message : "Action failed");
} finally {
setBusy(null);
}
}
const isDraft = status === "draft";
const isSubmitted = status === "submitted";
const isProcessed = status === "processed";
const isArchived = status === "rejected";
const canProcess = isSubmitted && directorCount > 0;
return (
<div className="bg-surface border border-border rounded-lg p-5 space-y-3" data-od-id="process-actions">
<h2 className="text-sm font-semibold text-fg uppercase tracking-wider">Actions</h2>
{hasDuplicates && canProcess ? (
<p className="text-xs text-warn font-medium bg-[color-mix(in_oklch,var(--warn)_10%,white)] border border-[color:var(--warn)]/30 rounded-md p-2">
Duplicate IDs detected — review before processing.
</p>
) : null}
{error ? (
<p className="text-xs text-danger font-medium bg-[color-mix(in_oklch,var(--danger)_8%,white)] border border-[color:var(--danger)]/30 rounded-md p-2">
{error}
</p>
) : null}
{(isSubmitted || isDraft) ? (
<button
type="button"
onClick={() => call("process")}
disabled={!canProcess || busy !== null}
className="w-full px-4 py-2.5 rounded-md bg-accent text-accent-on text-sm font-bold hover:bg-accent-hover disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{busy === "process" ? "Processing…" : `Create ${directorCount} director record${directorCount === 1 ? "" : "s"}`}
</button>
) : null}
{isProcessed ? (
<button
type="button"
onClick={() => call("reopen")}
disabled={busy !== null}
className="w-full px-4 py-2.5 rounded-md border border-border text-sm font-semibold text-fg hover:bg-surface-warm disabled:opacity-50 transition-colors"
>
{busy === "reopen" ? "Reopening…" : "Reopen submission"}
</button>
) : null}
{(isSubmitted || isProcessed || isDraft) && !isArchived ? (
<button
type="button"
onClick={() => call("archive")}
disabled={busy !== null}
className="w-full px-4 py-2.5 rounded-md border border-border text-sm font-semibold text-muted hover:text-danger hover:border-danger disabled:opacity-50 transition-colors"
>
{busy === "archive" ? "Archiving…" : "Archive"}
</button>
) : null}
{isArchived ? (
<p className="text-xs text-muted italic">This submission has been archived.</p>
) : null}
</div>
);
}