metis-desk/modules/companies/src/web/components/dashboard-details-dialog.tsx

250 lines
8.1 KiB
TypeScript
Raw Normal View History

import { Button } from "@repo/shadcn-ui/components/button";
import { EntityTable } from "@repo/shadcn-ui/components/entity-list";
import { FormDialog } from "@repo/shadcn-ui/components/form-dialog";
import { entityListLabels } from "@repo/shadcn-ui/lib/entity-list";
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import type { DashboardIssue } from "../../common/dashboard";
2026-10-08 14:43:34 +00:00
import type { DashboardNavigation } from "./dashboard-api";
import { dashboardApi, dashboardKeys, entryDestination } from "./dashboard-api";
import { BlockState } from "./dashboard-block";
export function DashboardDetailsDialog({
companyId,
details,
onClose,
onNavigate,
}: {
companyId?: string | undefined;
details: {
kind?: "asset" | "resource";
issue?: DashboardIssue;
work?: boolean;
activity?: boolean;
companies?: boolean;
};
onClose: () => void;
onNavigate: (destination: DashboardNavigation) => void;
}) {
const { t, i18n } = useTranslation("companies");
const [page, setPage] = useState(1);
const block = details.work ? "work" : details.activity ? "activity" : "details";
const query = useQuery({
queryKey: dashboardKeys.block(block, companyId, page, details.issue ?? details.kind),
enabled: !details.companies,
queryFn: ({ signal }) => {
if (details.work) {
return dashboardApi.work(signal, companyId, page);
}
if (details.activity) {
return dashboardApi.activity(signal, companyId);
}
return dashboardApi.details(signal, {
companyId,
page,
kind: details.kind,
issue: details.issue,
});
},
});
const companies = useQuery({
queryKey: dashboardKeys.block("companies", companyId),
enabled: Boolean(details.companies),
queryFn: ({ signal }) => dashboardApi.companies(signal, companyId),
});
const title = t(detailsTitle(details));
const labels = entityListLabels(i18n.language);
const total = query.data?.total ?? 0;
const range = labels.range(
total ? (page - 1) * 10 + 1 : 0,
Math.min(page * 10, total),
total,
t("dashboard.records")
);
return (
<FormDialog
cancelLabel={t("cancel")}
onOpenChange={(open) => {
if (!open) {
onClose();
}
}}
open
title={title}
wide
>
{details.companies ? (
<BlockState
empty={!companies.data?.items.length}
emptyText={t("noCompanies")}
query={companies}
>
<EntityTable
columns={[
{
id: "name",
label: t("name"),
value: (item) => item.name,
render: (item) => (
<button
className="dashboard-link"
onClick={() =>
onNavigate({ area: "company", id: item.id, section: "inventory" })
}
type="button"
>
{item.name}
</button>
),
},
{
id: "tickets",
label: t("dashboard.openTickets"),
value: (item) => item.tickets,
render: (item) => item.tickets,
},
{
id: "interventions",
label: t("dashboard.pendingInterventions"),
value: (item) => item.interventions,
render: (item) => item.interventions,
},
{
id: "assets",
label: t("dashboard.registeredAssets"),
value: (item) => item.assets,
render: (item) => item.assets,
},
{
id: "people",
label: t("people"),
value: (item) => item.people,
render: (item) => item.people,
},
]}
entityLabel={t("title")}
items={companies.data?.items ?? []}
locale={i18n.language}
title={title}
/>
</BlockState>
) : (
<>
<BlockState
empty={!query.data?.items.length}
emptyText={t("dashboard.noMatches")}
query={query}
>
<p className="text-sm text-muted-foreground">{range}</p>
<div className="overflow-x-auto dashboard-panel !p-0 !border-0 !shadow-none">
<table>
<caption className="sr-only">{title}</caption>
<thead>
<tr>
<th scope="col">{t("name")}</th>
<th scope="col">{t("tickets.company")}</th>
{details.activity && <th scope="col">{t("dashboard.recordedAt")}</th>}
{details.activity && <th scope="col">{t("dashboard.recordedBy")}</th>}
{details.work && <th scope="col">{t("tickets.state")}</th>}
</tr>
</thead>
<tbody>
{query.data?.items.map((item) => (
<tr key={item.eventId ?? item.id}>
<td>
<button
className="dashboard-link"
onClick={() => onNavigate(entryDestination(item))}
type="button"
>
{item.reference ? `${item.reference} · ` : ""}
{item.name}
</button>
</td>
<td>{item.companyName}</td>
{details.activity && (
<td>
{item.recordedAt
? new Intl.DateTimeFormat(i18n.language, {
dateStyle: "medium",
timeStyle: "short",
}).format(new Date(item.recordedAt))
: "—"}
</td>
)}
{details.activity && <td>{item.actorName ?? "—"}</td>}
{details.work && (
<td>
{t(
(item.kind === "ticket" ? "tickets.states." : "interventions.states.") +
item.state
)}
</td>
)}
</tr>
))}
</tbody>
</table>
</div>
</BlockState>
{!details.activity && query.isSuccess && (
<footer className="flex flex-wrap items-center justify-between gap-3 border-t pt-4 text-sm">
<span>{range}</span>
<div className="flex items-center gap-2">
<Button
aria-label={labels.previous}
disabled={page === 1}
onClick={() => setPage(page - 1)}
size="sm"
variant="outline"
>
‹
</Button>
<span>
{labels.page} {page}
</span>
<Button
aria-label={labels.next}
disabled={page * 10 >= total}
onClick={() => setPage(page + 1)}
size="sm"
variant="outline"
>
›
</Button>
</div>
</footer>
)}
</>
)}
</FormDialog>
);
}
function detailsTitle(details: {
issue?: DashboardIssue;
kind?: "asset" | "resource";
work?: boolean;
activity?: boolean;
}) {
if (details.issue) {
return `dashboard.issues.${details.issue}`;
}
if (details.kind === "asset") {
return "dashboard.registeredAssets";
}
if (details.kind === "resource") {
return "dashboard.registeredResources";
}
if (details.work) {
return "dashboard.pendingWork";
}
if (details.activity) {
return "dashboard.recentActivity";
}
return "dashboard.companySummary";
}