metis-desk/modules/companies/src/web/components/company-detail.tsx

235 lines
7.9 KiB
TypeScript
Raw Normal View History

import { AssetCatalogPage } from "@metis/assets/client";
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 { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import type { CompanyDTO } from "../../common";
import { AccessWorkspacePage } from "../access/access-workspace";
import { companyApi, companyKeys } from "../api/company-api";
import { CompanyFeedback } from "./company-feedback";
import { CompanyForm } from "./company-form";
import { CompanyInventory } from "./company-inventory";
import { DepartmentSection } from "./department-section";
import { PeopleSection } from "./people-section";
import { SiteForm } from "./site-form";
function SiteSection({ company, csrfToken }: { company: CompanyDTO; csrfToken: string }) {
const { t } = useTranslation("companies");
const [createOpen, setCreateOpen] = useState(false);
const [selectedId, setSelectedId] = useState<string | undefined>();
const selected = company.sites.find((site) => site.id === selectedId);
return (
<div className="space-y-6">
<Card>
<CardContent className="pt-6">
<div className="mb-4 flex items-center justify-between gap-4">
<h2 className="text-lg font-semibold">{t("sites")}</h2>
<Button onClick={() => setCreateOpen(true)}>{t("newSite")}</Button>
</div>
{company.sites.length === 0 ? (
<p className="text-muted-foreground">{t("noSites")}</p>
) : (
<div className="divide-y">
{company.sites.map((site) => (
<div className="flex items-start justify-between gap-4 py-4" key={site.id}>
<div className="min-w-0">
<h3 className="font-medium">{site.name}</h3>
<p className="mt-2 whitespace-pre-wrap break-words text-sm text-muted-foreground">
{site.address ?? t("noAddress")}
</p>
</div>
<Button onClick={() => setSelectedId(site.id)} size="sm" variant="outline">
{t("edit")}
</Button>
</div>
))}
</div>
)}
</CardContent>
</Card>
<FormDialog
cancelLabel={t("cancel")}
formActions
onOpenChange={setCreateOpen}
open={createOpen}
title={t("newSite")}
>
<SiteForm
companyId={company.id}
csrfToken={csrfToken}
onSaved={() => setCreateOpen(false)}
showTitle={false}
/>
</FormDialog>
<FormDialog
cancelLabel={t("cancel")}
formActions
onOpenChange={(open) => {
if (!open) {
setSelectedId(undefined);
}
}}
open={selected !== undefined}
title={t("editSite")}
>
{selected && (
<SiteForm
companyId={company.id}
csrfToken={csrfToken}
key={selected.id}
onSaved={() => setSelectedId(undefined)}
showTitle={false}
site={selected}
/>
)}
</FormDialog>
</div>
);
}
type CompanyTab = "people" | "departments" | "sites" | "access" | "assets" | "inventory";
export function CompanyDetail({
id,
csrfToken,
onBack,
}: {
id: string;
csrfToken: string;
onBack: () => void;
}) {
const { t } = useTranslation("companies");
const { t: assetTranslation } = useTranslation("assets");
const { t: accessTranslation } = useTranslation("access");
const query = useQuery({ queryKey: companyKeys.detail(id), queryFn: () => companyApi.get(id) });
const [editOpen, setEditOpen] = useState(false);
const [tab, setTab] = useState<
"people" | "departments" | "sites" | "access" | "assets" | "inventory"
>("people");
const company = query.data?.company;
return (
<div className="space-y-6">
<Button onClick={onBack} variant="outline">
{t("back")}
</Button>
{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>
)}
{company && (
<>
<div className="flex flex-wrap items-start justify-between gap-4">
<div>
<h1 className="text-2xl font-semibold">{company.name}</h1>
<p className="mt-1 text-sm text-muted-foreground">
{t(company.active ? "activeCompany" : "inactiveCompany")}
</p>
</div>
<Button onClick={() => setEditOpen(true)} variant="outline">
{t("editCompany")}
</Button>
</div>
<FormDialog
cancelLabel={t("cancel")}
formActions
onOpenChange={setEditOpen}
open={editOpen}
title={t("editCompany")}
>
<CompanyForm
company={company}
csrfToken={csrfToken}
key={company.id}
onSaved={() => setEditOpen(false)}
showTitle={false}
/>
</FormDialog>
<nav aria-label={t("companySections")} className="flex flex-wrap gap-2">
<Button
aria-pressed={tab === "people"}
onClick={() => setTab("people")}
variant={tab === "people" ? "secondary" : "outline"}
>
{t("people")} ({company.people.length})
</Button>
<Button
aria-pressed={tab === "departments"}
onClick={() => setTab("departments")}
variant={tab === "departments" ? "secondary" : "outline"}
>
{t("departments")} ({company.departments.length})
</Button>
<Button
aria-pressed={tab === "sites"}
onClick={() => setTab("sites")}
variant={tab === "sites" ? "secondary" : "outline"}
>
{t("sites")} ({company.sites.length})
</Button>
<Button
aria-pressed={tab === "access"}
onClick={() => setTab("access")}
variant={tab === "access" ? "secondary" : "outline"}
>
{accessTranslation("title")}
</Button>
<Button
aria-pressed={tab === "assets"}
onClick={() => setTab("assets")}
variant={tab === "assets" ? "secondary" : "outline"}
>
{assetTranslation("companyTitle")}
</Button>
<Button
aria-pressed={tab === "inventory"}
onClick={() => setTab("inventory")}
variant={tab === "inventory" ? "secondary" : "outline"}
>
{assetTranslation("inventory")}
</Button>
</nav>
<CompanySection company={company} csrfToken={csrfToken} tab={tab} />
</>
)}
</div>
);
}
function CompanySection({
company,
csrfToken,
tab,
}: {
company: CompanyDTO;
csrfToken: string;
tab: CompanyTab;
}) {
switch (tab) {
case "people":
return <PeopleSection company={company} csrfToken={csrfToken} />;
case "departments":
return <DepartmentSection company={company} csrfToken={csrfToken} />;
case "sites":
return <SiteSection company={company} csrfToken={csrfToken} />;
case "access":
return (
<AccessWorkspacePage csrfToken={csrfToken} people={company.people} scope={company.id} />
);
case "inventory":
return <CompanyInventory company={company} csrfToken={csrfToken} />;
case "assets":
return <AssetCatalogPage csrfToken={csrfToken} scope={company.id} />;
}
}