metis-desk/modules/companies/src/web/processes/process-detail.tsx

155 lines
5.8 KiB
TypeScript
Raw Normal View History

import { Button } from "@repo/shadcn-ui/components/button";
import { Card, CardContent } from "@repo/shadcn-ui/components/card";
import { FormDialog } from "@repo/shadcn-ui/components/form-dialog";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import type { PersonProcessDTO } from "../../common/processes";
import { processApi, processKeys } from "./process-api";
import { ProcessEditForm } from "./process-edit-form";
import { ProcessFeedback } from "./process-feedback";
import { processReferenceTitle, processTaskTitle } from "./process-labels";
export function ProcessDetail({
process,
person,
csrfToken,
}: {
process: PersonProcessDTO;
person: string;
csrfToken: string;
}) {
const { t, i18n } = useTranslation("companies");
const [edit, setEdit] = useState(false);
const client = useQueryClient();
const refresh = useMutation({
mutationFn: () =>
processApi.refresh(
process.companyId,
process.id,
process.history.at(-1)?.id ?? "",
csrfToken
),
onSuccess: async () => {
await client.invalidateQueries({ queryKey: processKeys.company(process.companyId) });
},
});
const open = process.values.state === "open";
const resolved = process.values.tasks.filter((task) => task.status !== "pending").length;
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>
</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
)
);
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>
<FormDialog
cancelLabel={t("cancel")}
formActions
onOpenChange={setEdit}
open={edit}
title={t("processes.update")}
wide
>
{edit && (
<ProcessEditForm csrfToken={csrfToken} onSaved={() => setEdit(false)} process={process} />
)}
</FormDialog>
</div>
);
}