metis-desk/modules/companies/src/web/intervention-templates/templates-page.tsx
2026-10-08 14:14:01 +02:00

212 lines
7.1 KiB
TypeScript

import { Button } from "@repo/shadcn-ui/components/button";
import { Checkbox } from "@repo/shadcn-ui/components/checkbox";
import {
EntityAction,
EntityListHeader,
EntityRowActions,
EntityStatus,
EntityTable,
} from "@repo/shadcn-ui/components/entity-list";
import { FormDialog } from "@repo/shadcn-ui/components/form-dialog";
import { Label } from "@repo/shadcn-ui/components/label";
import { useQuery } from "@tanstack/react-query";
import { useId, useState } from "react";
import { useTranslation } from "react-i18next";
import { interventionTemplateApi, interventionTemplateKeys } from "./template-api";
import { TemplateSteps } from "./template-detail";
import { InterventionTemplateFeedback } from "./template-feedback";
import { InterventionTemplateForm, copyTemplateValues } from "./template-form";
import { TemplateHistory } from "./template-history";
export function InterventionTemplatesPage({
scope = "global",
csrfToken,
}: {
scope?: string;
csrfToken: string;
}) {
const { t, i18n } = useTranslation("companies");
const prefix = useId();
const query = useQuery({
queryKey: interventionTemplateKeys.list(scope),
queryFn: () => interventionTemplateApi.list(scope),
});
const [editor, setEditor] = useState<{ id?: string; copyId?: string }>();
const [search, setSearch] = useState("");
const [showInactive, setShowInactive] = useState(true);
const templates = query.data?.templates ?? [];
const selected = templates.find((item) => item.id === editor?.id);
const copied = templates.find((item) => item.id === editor?.copyId);
const visible = templates.filter(
(item) =>
(showInactive || item.values.active) &&
item.values.name.toLocaleLowerCase().includes(search.trim().toLocaleLowerCase())
);
return (
<div className="space-y-5">
<EntityListHeader
actions={
<>
{" "}
<Button disabled={!query.data || query.isError} onClick={() => setEditor({})}>
{t("interventionTemplates.new")}
</Button>{" "}
</>
}
description={t(
scope === "global"
? "interventionTemplates.scopeGlobal"
: "interventionTemplates.scopeCompany"
)}
locale={i18n.language}
title={t("interventionTemplates.title")}
/>
{query.isPending && <p role="status">{t("loading")}</p>}
<InterventionTemplateFeedback error={query.error} />
{query.isError && (
<Button
onClick={() => {
query.refetch().catch(() => undefined);
}}
variant="outline"
>
{t("retry")}
</Button>
)}
{query.data && !query.isError && (
<EntityTable
columns={[
{
id: "name",
label: t("name"),
value: (template) => template.values.name,
hideable: false,
render: (template) => <>{template.values.name}</>,
},
{
id: "scope",
label: t("interventionTemplates.global"),
value: (template) =>
t(
template.companyId
? "interventionTemplates.company"
: "interventionTemplates.global"
),
render: (template) => (
<>
{t(
template.companyId
? "interventionTemplates.company"
: "interventionTemplates.global"
)}
</>
),
},
{
id: "state",
label: t("status"),
value: (template) => t(template.values.active ? "active" : "inactive"),
render: (template) => (
<EntityStatus active={template.values.active}>
{t(template.values.active ? "active" : "inactive")}
</EntityStatus>
),
},
{
id: "steps",
label: t("interventions.steps"),
value: (template) => template.values.steps.length,
render: (template) => (
<details>
<summary className="cursor-pointer">
{t("interventions.steps")} ({template.values.steps.length})
</summary>
<TemplateSteps values={template.values} />
</details>
),
},
{
id: "history",
label: t("history"),
render: (template) => <TemplateHistory template={template} />,
},
{
id: "actions",
label: t("actions"),
hideable: false,
render: (template) => (
<EntityRowActions label={t("actions")}>
{" "}
<div className="contents">
{template.companyId === (scope === "global" ? null : scope) && (
<EntityAction onClick={() => setEditor({ id: template.id })}>
{t("edit")}
</EntityAction>
)}
<EntityAction onClick={() => setEditor({ copyId: template.id })}>
{t("interventionTemplates.copy")}
</EntityAction>
</div>{" "}
</EntityRowActions>
),
},
]}
emptyMessage={t("interventionTemplates.empty")}
filterKey={String(showInactive)}
filters={
<div className="flex gap-2 items-center">
<Checkbox
checked={showInactive}
id={`${prefix}-inactive`}
onCheckedChange={(value) => setShowInactive(value === true)}
/>
<Label htmlFor={`${prefix}-inactive`}>
{t("interventionTemplates.showInactive")}
</Label>
</div>
}
filtersApplied={!showInactive}
items={visible}
locale={i18n.language}
onClearFilters={() => setShowInactive(true)}
onSearchChange={setSearch}
search={search}
searchPlaceholder={t("interventionTemplates.filter")}
title={t("interventionTemplates.title")}
/>
)}
<FormDialog
cancelLabel={t("cancel")}
formActions
onOpenChange={(open) => {
if (!open) {
setEditor(undefined);
}
}}
open={editor !== undefined}
title={t(selected ? "interventionTemplates.edit" : "interventionTemplates.new")}
wide
>
{editor && (
<InterventionTemplateForm
csrfToken={csrfToken}
initialValues={
copied
? copyTemplateValues(
copied,
`${t("interventionTemplates.copyOf")} ${copied.values.name}`
)
: undefined
}
key={editor.id ?? editor.copyId ?? "new"}
onSaved={() => setEditor(undefined)}
scope={scope}
template={selected}
/>
)}
</FormDialog>
</div>
);
}