metis-desk/modules/companies/src/web/components/companies-page.tsx

152 lines
5.0 KiB
TypeScript

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 { Input } from "@repo/shadcn-ui/components/input";
import { Label } from "@repo/shadcn-ui/components/label";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@repo/shadcn-ui/components/table";
import { useQuery } from "@tanstack/react-query";
import type { ColumnDef } from "@tanstack/react-table";
import { getCoreRowModel, getFilteredRowModel, useReactTable } from "@tanstack/react-table";
import { useId, useState } from "react";
import { useTranslation } from "react-i18next";
import type { CompanySummaryDTO } from "../../common";
import { companyApi, companyKeys } from "../api/company-api";
import { CompanyDetail } from "./company-detail";
import { CompanyFeedback } from "./company-feedback";
import { CompanyForm } from "./company-form";
const EMPTY_COMPANIES: CompanySummaryDTO[] = [];
const COMPANY_COLUMNS: ColumnDef<CompanySummaryDTO>[] = [
{ accessorKey: "name" },
{ accessorKey: "active", enableGlobalFilter: false },
];
export function CompaniesPage({
csrfToken,
onSelectCompany,
}: {
csrfToken: string;
onSelectCompany?: (id: string) => void;
}) {
const { t } = useTranslation("companies");
const query = useQuery({ queryKey: companyKeys.list, queryFn: companyApi.list });
const [selectedId, setSelectedId] = useState<string | undefined>();
const [createOpen, setCreateOpen] = useState(false);
const [search, setSearch] = useState("");
const id = useId();
const table = useReactTable({
data: query.data?.companies ?? EMPTY_COMPANIES,
columns: COMPANY_COLUMNS,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
state: { globalFilter: search.trim() },
globalFilterFn: "includesString",
});
const selectCompany = onSelectCompany ?? setSelectedId;
if (selectedId && !onSelectCompany) {
return (
<CompanyDetail
csrfToken={csrfToken}
id={selectedId}
key={selectedId}
onBack={() => setSelectedId(undefined)}
/>
);
}
const companies = table.getRowModel().rows.map((row) => row.original);
return (
<div className="space-y-6">
<div className="flex flex-wrap items-start justify-between gap-4">
<div>
<h1 className="text-2xl font-semibold">{t("title")}</h1>
<p className="mt-2 text-muted-foreground">{t("help")}</p>
</div>
<Button onClick={() => setCreateOpen(true)}>{t("newCompany")}</Button>
</div>
<div className="space-y-2">
<Label htmlFor={id}>{t("search")}</Label>
<Input
className="max-w-md"
id={id}
onChange={(event) => setSearch(event.target.value)}
placeholder={t("searchPlaceholder")}
value={search}
/>
</div>
{query.isPending && <p role="status">{t("loading")}</p>}
<CompanyFeedback error={query.error} />
{query.isError && (
<Button
onClick={() => {
query.refetch().catch(() => undefined);
}}
variant="outline"
>
{t("retry")}
</Button>
)}
{query.data && (
<Card>
<CardContent className="pt-6">
{companies.length === 0 ? (
<p className="text-muted-foreground">{t(search ? "noResults" : "empty")}</p>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>{t("name")}</TableHead>
<TableHead>{t("status")}</TableHead>
<TableHead>{t("actions")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{companies.map((company) => (
<TableRow key={company.id}>
<TableCell className="font-medium">{company.name}</TableCell>
<TableCell>{t(company.active ? "active" : "inactive")}</TableCell>
<TableCell>
<Button
onClick={() => selectCompany(company.id)}
size="sm"
variant="outline"
>
{t("open")}
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</CardContent>
</Card>
)}
<FormDialog
cancelLabel={t("cancel")}
formActions
onOpenChange={setCreateOpen}
open={createOpen}
title={t("newCompany")}
>
<CompanyForm
csrfToken={csrfToken}
onCreated={(companyId) => {
setCreateOpen(false);
selectCompany(companyId);
}}
showTitle={false}
/>
</FormDialog>
</div>
);
}