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

358 lines
12 KiB
TypeScript

import { AssetCatalogPage } from "@metis/assets/client";
import { ResourceCatalogPage, ResourcesPage } from "@metis/resources/client";
import { Button } from "@repo/shadcn-ui/components/button";
import {
EntityAction,
EntityListHeader,
EntityRowActions,
EntityTable,
} from "@repo/shadcn-ui/components/entity-list";
import { FormDialog } from "@repo/shadcn-ui/components/form-dialog";
import {
BoxIcon,
BriefcaseIcon,
KeyRoundIcon,
LayersIcon,
LayoutTemplateIcon,
MailIcon,
MapPinIcon,
UsersIcon,
} from "@repo/shadcn-ui/lib/icons";
import { useQuery } from "@tanstack/react-query";
import type { ReactNode } from "react";
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 { ApplicationConfigurationsPage } from "../applications/application-configurations-page";
import { CertificateConfigurationsPage } from "../certificates/certificate-configurations-page";
import { FolderMappingsPage } from "../folders/folder-mappings-page";
import { InterventionTemplatesPage } from "../intervention-templates/templates-page";
import { InterventionsPage } from "../interventions/interventions-page";
import { MailConfigurationsPage } from "../mail/mail-configurations-page";
import { MailboxRelations } from "../mail/mailbox-relations";
import { PrinterConfigurationsPage } from "../printers/printer-configurations-page";
import { ProcessesPage } from "../processes/processes-page";
import { WorkProfilesPage } from "../profiles/work-profiles-page";
import { RemoteConnectionsPage } from "../remote/remote-connections-page";
import { TeleworkAuthorizationsPage } from "../telework/telework-authorizations-page";
import { TicketsPage } from "../tickets/tickets-page";
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, i18n } = 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">
<EntityListHeader
actions={<Button onClick={() => setCreateOpen(true)}>{t("newSite")}</Button>}
locale={i18n.language}
title={t("sites")}
/>
<EntityTable
columns={[
{
id: "name",
label: t("name"),
value: (site) => site.name,
hideable: false,
render: (site) => <span className="font-medium">{site.name}</span>,
},
{
id: "address",
label: t("addressOptional"),
value: (site) => site.address,
render: (site) => (
<span className="whitespace-pre-wrap">{site.address ?? t("noAddress")}</span>
),
},
{
id: "actions",
label: t("actions"),
hideable: false,
render: (site) => (
<EntityRowActions label={t("actions")}>
<EntityAction onClick={() => setSelectedId(site.id)}>{t("edit")}</EntityAction>
</EntityRowActions>
),
},
]}
emptyMessage={t("noSites")}
items={company.sites}
locale={i18n.language}
title={t("sites")}
/>
<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 =
| "interventions"
| "intervention-templates"
| "tickets"
| "processes"
| "remote"
| "telework"
| "people"
| "departments"
| "sites"
| "access"
| "resource-catalog"
| "resources"
| "assets"
| "inventory"
| "workstations"
| "certificates"
| "applications"
| "printers"
| "folders"
| "mail"
| "profiles";
export function CompanyDetail({
id,
csrfToken,
onBack,
embedded = false,
accountNavigation,
}: {
id: string;
csrfToken: string;
onBack: () => void;
embedded?: boolean;
accountNavigation?: ReactNode;
}) {
const { t } = useTranslation("companies");
const { t: resourceTranslation } = useTranslation("resources");
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<CompanyTab>(embedded ? "inventory" : "people");
const company = query.data?.company;
const layout = embedded
? {
root: "company-space",
heading: "company-space-heading",
navigation: "company-space-navigation",
main: "company-space-main",
}
: {
root: "space-y-6",
heading: "flex flex-wrap items-start justify-between gap-4",
navigation: "flex flex-wrap gap-2",
main: undefined,
};
return (
<div className={layout.root}>
{!embedded && (
<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>
)}
{embedded && <footer className="company-space-account">{accountNavigation}</footer>}
{company && (
<>
<div className={layout.heading}>
<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={layout.navigation}>
{(
[
["inventory", assetTranslation("inventory"), BoxIcon],
["resources", resourceTranslation("inventory"), LayersIcon],
["workstations", assetTranslation("workstations"), BriefcaseIcon],
["profiles", t("profiles.title"), UsersIcon],
["telework", t("telework.title"), KeyRoundIcon],
["remote", t("remote.title"), KeyRoundIcon],
["processes", t("processes.title"), LayoutTemplateIcon],
["intervention-templates", t("interventionTemplates.title"), LayoutTemplateIcon],
["interventions", t("interventions.title"), LayoutTemplateIcon],
["tickets", t("tickets.title"), LayoutTemplateIcon],
["people", `${t("people")} (${company.people.length})`, UsersIcon],
[
"departments",
`${t("departments")} (${company.departments.length})`,
BriefcaseIcon,
],
["sites", `${t("sites")} (${company.sites.length})`, MapPinIcon],
["access", accessTranslation("title"), KeyRoundIcon],
["folders", t("folders.title"), MailIcon],
["certificates", t("certificates.title"), LayersIcon],
["applications", t("applications.title"), LayersIcon],
["printers", t("printers.title"), LayersIcon],
["mail", t("mail.title"), MailIcon],
["assets", assetTranslation("companyTitle"), LayoutTemplateIcon],
["resource-catalog", resourceTranslation("companyTitle"), LayoutTemplateIcon],
] as const
).map(([section, label, Icon]) => (
<Button
aria-pressed={tab === section}
key={section}
onClick={() => setTab(section)}
variant="ghost"
>
<Icon aria-hidden="true" className="size-4 shrink-0" />
{label}
</Button>
))}
</nav>
<main className={layout.main} key={tab}>
{embedded && <div className="company-space-breadcrumb">{company.name}</div>}
<CompanySection company={company} csrfToken={csrfToken} tab={tab} />
</main>
</>
)}
</div>
);
}
function CompanySection({
company,
csrfToken,
tab,
}: {
company: CompanyDTO;
csrfToken: string;
tab: CompanyTab;
}) {
switch (tab) {
case "intervention-templates":
return <InterventionTemplatesPage csrfToken={csrfToken} scope={company.id} />;
case "interventions":
return <InterventionsPage companyId={company.id} csrfToken={csrfToken} />;
case "tickets":
return <TicketsPage companyId={company.id} csrfToken={csrfToken} />;
case "processes":
return <ProcessesPage company={company} csrfToken={csrfToken} />;
case "remote":
return <RemoteConnectionsPage company={company} csrfToken={csrfToken} />;
case "telework":
return <TeleworkAuthorizationsPage company={company} csrfToken={csrfToken} />;
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 "profiles":
return <WorkProfilesPage company={company} csrfToken={csrfToken} />;
case "certificates":
return <CertificateConfigurationsPage company={company} csrfToken={csrfToken} />;
case "applications":
return <ApplicationConfigurationsPage company={company} csrfToken={csrfToken} />;
case "printers":
return <PrinterConfigurationsPage company={company} csrfToken={csrfToken} />;
case "folders":
return <FolderMappingsPage company={company} csrfToken={csrfToken} />;
case "mail":
return <MailConfigurationsPage company={company} csrfToken={csrfToken} />;
case "workstations":
return <CompanyInventory company={company} csrfToken={csrfToken} workstations />;
case "inventory":
return <CompanyInventory company={company} csrfToken={csrfToken} />;
case "resources":
return (
<ResourcesPage
companyId={company.id}
csrfToken={csrfToken}
relatedContent={(resource) =>
resource.values.integration?.kind === "mailbox" ? (
<MailboxRelations company={company} csrfToken={csrfToken} mailboxId={resource.id} />
) : null
}
/>
);
case "resource-catalog":
return <ResourceCatalogPage csrfToken={csrfToken} scope={company.id} />;
case "assets":
return <AssetCatalogPage csrfToken={csrfToken} scope={company.id} />;
}
}