2026-10-07 07:34:18 +00:00
|
|
|
import { Button } from "@repo/shadcn-ui/components/button";
|
2026-10-09 21:11:31 +00:00
|
|
|
import { useState } from "react";
|
2026-10-07 07:34:18 +00:00
|
|
|
import { Card, CardContent } from "@repo/shadcn-ui/components/card";
|
|
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
|
|
2026-10-08 22:27:19 +00:00
|
|
|
import { ProcessFeedback } from "../../shared";
|
|
|
|
|
import { processReferenceTitle, processTaskTitle } from "../../shared/helpers";
|
2026-10-08 17:25:02 +00:00
|
|
|
import { ProcessEditDialog } from "../../update";
|
2026-10-08 22:27:19 +00:00
|
|
|
import { useProcessDetailController } from "../controllers";
|
|
|
|
|
import type { ProcessDetailProps } from "../types";
|
|
|
|
|
|
|
|
|
|
export function ProcessDetail(props: ProcessDetailProps) {
|
2026-10-09 21:11:31 +00:00
|
|
|
const { process, person, csrfToken, onDelete, onDeleted } = props;
|
2026-10-08 22:27:19 +00:00
|
|
|
const { edit, setEdit, refresh, open, resolved } = useProcessDetailController(props);
|
2026-10-07 07:34:18 +00:00
|
|
|
const { t, i18n } = useTranslation("companies");
|
2026-10-09 21:11:31 +00:00
|
|
|
const [deleting, setDeleting] = useState(false);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-07 07:34:18 +00:00
|
|
|
return (
|
|
|
|
|
<div className="space-y-5">
|
|
|
|
|
<header className="flex flex-wrap items-start justify-between gap-3">
|
|
|
|
|
<div>
|
|
|
|
|
<h2 className="text-xl font-semibold">{process.values.name}</h2>
|
|
|
|
|
<p>
|
|
|
|
|
{person} · {t(`processes.${process.values.kind}`)} ·{" "}
|
|
|
|
|
{t(`processes.${process.values.state}`)}
|
|
|
|
|
</p>
|
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
|
|
|
{t("processes.progress", { resolved, total: process.values.tasks.length })}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
{open && (
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
{process.values.kind !== "onboarding" && (
|
|
|
|
|
<Button
|
|
|
|
|
disabled={refresh.isPending}
|
|
|
|
|
onClick={() => refresh.mutate()}
|
|
|
|
|
variant="outline"
|
|
|
|
|
>
|
|
|
|
|
{t("processes.refresh")}
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
<Button disabled={refresh.isPending} onClick={() => setEdit(true)}>
|
|
|
|
|
{t("processes.update")}
|
|
|
|
|
</Button>
|
2026-10-09 21:11:31 +00:00
|
|
|
<Button
|
|
|
|
|
disabled={deleting}
|
|
|
|
|
variant="destructive"
|
|
|
|
|
onClick={() => {
|
2026-10-10 10:01:58 +00:00
|
|
|
if (!window.confirm(t("processes.deleteConfirm", { name: process.values.name })))
|
|
|
|
|
return;
|
2026-10-09 21:11:31 +00:00
|
|
|
setDeleting(true);
|
|
|
|
|
onDelete?.(process.id);
|
|
|
|
|
onDeleted?.();
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{t("processes.delete")}
|
|
|
|
|
</Button>
|
2026-10-07 07:34:18 +00:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</header>
|
|
|
|
|
<p className="text-sm text-muted-foreground">{t("processes.resolutionHelp")}</p>
|
|
|
|
|
<ProcessFeedback error={refresh.error} />
|
|
|
|
|
{process.values.notes && <p className="whitespace-pre-wrap">{process.values.notes}</p>}
|
|
|
|
|
<Card>
|
|
|
|
|
<CardContent className="divide-y pt-3">
|
|
|
|
|
{process.values.tasks.map((task) => {
|
|
|
|
|
const recorded = Boolean(
|
|
|
|
|
task.reference &&
|
|
|
|
|
process.review.some(
|
|
|
|
|
(ref) => ref.kind === task.reference?.kind && ref.id === task.reference?.id
|
|
|
|
|
)
|
|
|
|
|
);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-07 07:34:18 +00:00
|
|
|
return (
|
|
|
|
|
<section className="space-y-2 py-3" key={task.id}>
|
|
|
|
|
<h3 className="font-medium">{processTaskTitle(task, t)}</h3>
|
|
|
|
|
<p className="text-sm">{t(`processes.${task.status}`)}</p>
|
|
|
|
|
{task.reference && (
|
|
|
|
|
<p
|
|
|
|
|
className={
|
|
|
|
|
recorded ? "text-sm text-amber-700" : "text-sm text-muted-foreground"
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
{t(recorded ? "processes.stillRecorded" : "processes.notInReview")}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
{task.notes && <p className="text-sm whitespace-pre-wrap">{task.notes}</p>}
|
|
|
|
|
</section>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
<details className="rounded-md border p-4">
|
|
|
|
|
<summary className="cursor-pointer font-medium">
|
|
|
|
|
{t("processes.currentReview", { count: process.review.length })}
|
|
|
|
|
</summary>
|
|
|
|
|
<p className="text-sm mt-2">{t("processes.currentReviewHelp")}</p>
|
|
|
|
|
<ol className="list-disc pl-5 text-sm mt-2">
|
|
|
|
|
{process.review.map((ref) => (
|
|
|
|
|
<li key={`${ref.kind}:${ref.id}`}>{processReferenceTitle(ref, t)}</li>
|
|
|
|
|
))}
|
|
|
|
|
</ol>
|
|
|
|
|
</details>
|
|
|
|
|
<details className="rounded-md border p-4">
|
|
|
|
|
<summary className="cursor-pointer font-medium">{t("processes.history")}</summary>
|
|
|
|
|
<ol className="space-y-3 mt-3">
|
|
|
|
|
{[...process.history].reverse().map((r) => (
|
|
|
|
|
<li className="rounded border p-3 text-sm" key={r.id}>
|
|
|
|
|
<p className="font-medium">
|
|
|
|
|
{new Intl.DateTimeFormat(i18n.language, {
|
|
|
|
|
dateStyle: "medium",
|
|
|
|
|
timeStyle: "short",
|
|
|
|
|
}).format(new Date(r.recordedAt))}{" "}
|
|
|
|
|
· {r.actorName}
|
|
|
|
|
</p>
|
|
|
|
|
<p>
|
|
|
|
|
{r.values.name} · {t(`processes.${r.values.state}`)}
|
|
|
|
|
</p>
|
|
|
|
|
{r.values.notes && <p className="whitespace-pre-wrap">{r.values.notes}</p>}
|
|
|
|
|
<ol className="list-decimal pl-5 mt-2">
|
|
|
|
|
{r.values.tasks.map((task) => (
|
|
|
|
|
<li key={task.id}>
|
|
|
|
|
{processTaskTitle(task, t)} · {t(`processes.${task.status}`)}
|
|
|
|
|
{task.notes && <p className="whitespace-pre-wrap">{task.notes}</p>}
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ol>
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ol>
|
|
|
|
|
</details>
|
2026-10-08 14:43:34 +00:00
|
|
|
<ProcessEditDialog
|
|
|
|
|
csrfToken={csrfToken}
|
2026-10-07 07:34:18 +00:00
|
|
|
onOpenChange={setEdit}
|
2026-10-08 14:43:34 +00:00
|
|
|
onSaved={() => setEdit(false)}
|
2026-10-07 07:34:18 +00:00
|
|
|
open={edit}
|
2026-10-08 14:43:34 +00:00
|
|
|
process={process}
|
|
|
|
|
/>
|
2026-10-07 07:34:18 +00:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|