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

342 lines
12 KiB
TypeScript
Raw Normal View History

import { AssetCatalogPage } from "@metis/assets/client";
2026-10-05 11:13:51 +00:00
import { ResourceCatalogPage, ResourcesPage } from "@metis/resources/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 {
BoxIcon,
BriefcaseIcon,
KeyRoundIcon,
2026-10-05 11:13:51 +00:00
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";
2026-10-05 11:13:51 +00:00
import { FolderMappingsPage } from "../folders/folder-mappings-page";
2026-10-07 11:03:01 +00:00
import { InterventionTemplatesPage } from "../intervention-templates/templates-page";
import { InterventionsPage } from "../interventions/interventions-page";
import { MailConfigurationsPage } from "../mail/mail-configurations-page";
2026-10-05 15:01:32 +00:00
import { MailboxRelations } from "../mail/mailbox-relations";
import { PrinterConfigurationsPage } from "../printers/printer-configurations-page";
import { ProcessesPage } from "../processes/processes-page";
2026-10-05 07:53:29 +00:00
import { WorkProfilesPage } from "../profiles/work-profiles-page";
2026-10-06 16:44:21 +00:00
import { RemoteConnectionsPage } from "../remote/remote-connections-page";
import { TeleworkAuthorizationsPage } from "../telework/telework-authorizations-page";
2026-10-07 09:41:56 +00:00
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 } = 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>
);
}
2026-10-04 19:04:14 +00:00
type CompanyTab =
2026-10-07 11:03:01 +00:00
| "interventions"
| "intervention-templates"
2026-10-07 09:41:56 +00:00
| "tickets"
| "processes"
2026-10-06 16:44:21 +00:00
| "remote"
| "telework"
2026-10-04 19:04:14 +00:00
| "people"
| "departments"
| "sites"
| "access"
2026-10-05 11:13:51 +00:00
| "resource-catalog"
| "resources"
2026-10-04 19:04:14 +00:00
| "assets"
| "inventory"
| "workstations"
| "certificates"
| "applications"
2026-10-05 15:01:32 +00:00
| "printers"
2026-10-05 10:02:04 +00:00
| "folders"
2026-10-05 07:53:29 +00:00
| "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");
2026-10-05 11:13:51 +00:00
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],
2026-10-05 11:13:51 +00:00
["resources", resourceTranslation("inventory"), LayersIcon],
2026-10-04 19:04:14 +00:00
["workstations", assetTranslation("workstations"), BriefcaseIcon],
2026-10-05 07:53:29 +00:00
["profiles", t("profiles.title"), UsersIcon],
["telework", t("telework.title"), KeyRoundIcon],
2026-10-06 16:44:21 +00:00
["remote", t("remote.title"), KeyRoundIcon],
["processes", t("processes.title"), LayoutTemplateIcon],
2026-10-07 11:03:01 +00:00
["intervention-templates", t("interventionTemplates.title"), LayoutTemplateIcon],
["interventions", t("interventions.title"), LayoutTemplateIcon],
2026-10-07 09:41:56 +00:00
["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],
2026-10-05 11:13:51 +00:00
["folders", t("folders.title"), MailIcon],
["certificates", t("certificates.title"), LayersIcon],
["applications", t("applications.title"), LayersIcon],
2026-10-05 15:01:32 +00:00
["printers", t("printers.title"), LayersIcon],
["mail", t("mail.title"), MailIcon],
["assets", assetTranslation("companyTitle"), LayoutTemplateIcon],
2026-10-05 11:13:51 +00:00
["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) {
2026-10-07 11:03:01 +00:00
case "intervention-templates":
return <InterventionTemplatesPage csrfToken={csrfToken} scope={company.id} />;
case "interventions":
return <InterventionsPage companyId={company.id} csrfToken={csrfToken} />;
2026-10-07 09:41:56 +00:00
case "tickets":
return <TicketsPage companyId={company.id} csrfToken={csrfToken} />;
case "processes":
return <ProcessesPage company={company} csrfToken={csrfToken} />;
2026-10-06 16:44:21 +00:00
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} />
);
2026-10-05 07:53:29 +00:00
case "profiles":
return <WorkProfilesPage company={company} csrfToken={csrfToken} />;
case "certificates":
return <CertificateConfigurationsPage company={company} csrfToken={csrfToken} />;
case "applications":
return <ApplicationConfigurationsPage company={company} csrfToken={csrfToken} />;
2026-10-05 15:01:32 +00:00
case "printers":
return <PrinterConfigurationsPage company={company} csrfToken={csrfToken} />;
2026-10-05 10:02:04 +00:00
case "folders":
2026-10-05 11:13:51 +00:00
return <FolderMappingsPage company={company} csrfToken={csrfToken} />;
case "mail":
return <MailConfigurationsPage company={company} csrfToken={csrfToken} />;
2026-10-04 19:04:14 +00:00
case "workstations":
return <CompanyInventory company={company} csrfToken={csrfToken} workstations />;
case "inventory":
return <CompanyInventory company={company} csrfToken={csrfToken} />;
2026-10-05 11:13:51 +00:00
case "resources":
2026-10-05 15:01:32 +00:00
return (
<ResourcesPage
companyId={company.id}
csrfToken={csrfToken}
relatedContent={(resource) =>
resource.values.integration?.kind === "mailbox" ? (
<MailboxRelations company={company} csrfToken={csrfToken} mailboxId={resource.id} />
) : null
}
/>
);
2026-10-05 11:13:51 +00:00
case "resource-catalog":
return <ResourceCatalogPage csrfToken={csrfToken} scope={company.id} />;
case "assets":
return <AssetCatalogPage csrfToken={csrfToken} scope={company.id} />;
}
}