test: implement resource template propagation tests for resource revisions feat: create prepareResourceTemplateUpdates function for handling resource template updates feat: add resource revision specifications for managing resource revisions in the database test: add tests for entity column visibility and local storage handling feat: implement entity column visibility storage for managing column preferences feat: create synchronizeTemplateStructure utility for managing template field and section synchronization
125 lines
4.8 KiB
TypeScript
125 lines
4.8 KiB
TypeScript
import { BrowserLink, EntityNotFound, useBrowserSearchParams } from "@metis/core/client";
|
|
import { EntityAction, EntityRowActions } from "@repo/rdx-ui/components/entity-list";
|
|
import { ChevronLeftIcon, PencilIcon, Trash2Icon } from "@repo/rdx-ui/lib/icons";
|
|
import { Button } from "@repo/shadcn-ui/components/button";
|
|
import type { ReactNode } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import type { AssetDTO } from "../../../../common";
|
|
import { AssetTemplateIcon } from "../../../catalog-templates/shared/ui/asset-template-icon";
|
|
import { InventoryFeedback } from "../../../shared";
|
|
import { AssetEditorDialog, DeleteAssetDialog } from "../../shared";
|
|
import type { InventoryLookups } from "../../shared/types";
|
|
import { useAssetDetailPageController } from "../controllers/use-asset-detail-page-controller";
|
|
|
|
import { AssetDetailContent } from "./asset-detail-content";
|
|
|
|
export function AssetDetailPage({
|
|
companyId,
|
|
assetId,
|
|
lookups,
|
|
relatedContent,
|
|
}: {
|
|
companyId: string;
|
|
assetId: string;
|
|
lookups: InventoryLookups;
|
|
relatedContent?: (asset: AssetDTO) => ReactNode;
|
|
}) {
|
|
const { t } = useTranslation("assets");
|
|
const { searchParams } = useBrowserSearchParams();
|
|
const search = searchParams.toString();
|
|
const backHref = `/companies/${companyId}/assets${search ? `?${search}` : ""}`;
|
|
const c = useAssetDetailPageController(companyId, assetId, backHref);
|
|
const { asset } = c;
|
|
const template = c.catalog.data?.items.find((item) => item.id === asset?.values.templateId);
|
|
|
|
if (c.inventory.isSuccess && !asset) {
|
|
return <EntityNotFound />;
|
|
}
|
|
|
|
return (
|
|
<div className="@container min-w-0 space-y-5">
|
|
<BrowserLink className="inline-flex items-center gap-1 text-sm" href={backHref}>
|
|
<ChevronLeftIcon aria-hidden="true" className="size-4" />
|
|
{t("assetDetail.back")}
|
|
</BrowserLink>
|
|
{c.inventory.isPending && <p role="status">{t("loading")}</p>}
|
|
<InventoryFeedback error={c.inventory.error ?? c.catalog.error} />
|
|
{(c.inventory.isError || c.catalog.isError) && (
|
|
<Button
|
|
onClick={() => {
|
|
Promise.all([c.inventory.refetch(), c.catalog.refetch()]).catch(() => undefined);
|
|
}}
|
|
variant="outline"
|
|
>
|
|
{t("retry")}
|
|
</Button>
|
|
)}
|
|
{asset && (
|
|
<>
|
|
<header className="flex flex-wrap items-start justify-between gap-4 rounded-xl border border-border/70 bg-card p-5 sm:p-6">
|
|
<div className="flex min-w-0 items-start gap-3">
|
|
<span className="flex size-12 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary">
|
|
<AssetTemplateIcon
|
|
className="size-6"
|
|
icon={template?.values.kind === "template" ? template.values.icon : undefined}
|
|
/>
|
|
</span>
|
|
<div className="min-w-0 space-y-2">
|
|
<h1 className="break-words text-2xl font-semibold">{asset.values.name}</h1>
|
|
<p className="text-sm text-muted-foreground">
|
|
{asset.specification.templateName} · {t(asset.displayStatus)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<EntityRowActions
|
|
label={t("actions")}
|
|
primaryAction={{
|
|
label: t("editAsset"),
|
|
icon: <PencilIcon aria-hidden="true" />,
|
|
onClick: () => c.setEditing(true),
|
|
disabled: !c.catalog.isSuccess,
|
|
}}
|
|
>
|
|
<EntityAction
|
|
className="text-destructive data-highlighted:text-destructive"
|
|
onClick={() => c.setDeleting(true)}
|
|
>
|
|
<Trash2Icon aria-hidden="true" />
|
|
{t("deleteAsset")}
|
|
</EntityAction>
|
|
</EntityRowActions>
|
|
</header>
|
|
<AssetDetailContent
|
|
asset={asset}
|
|
assets={c.inventory.data?.assets ?? []}
|
|
lookups={lookups}
|
|
relationships={c.inventory.data?.relationships ?? []}
|
|
>
|
|
{relatedContent?.(asset)}
|
|
</AssetDetailContent>
|
|
{c.editing && c.catalog.data && (
|
|
<AssetEditorDialog
|
|
asset={asset}
|
|
companyId={companyId}
|
|
items={c.catalog.data.items}
|
|
lookups={lookups}
|
|
onClose={() => c.setEditing(false)}
|
|
onSaved={() => c.setEditing(false)}
|
|
/>
|
|
)}
|
|
<DeleteAssetDialog
|
|
asset={asset}
|
|
errorCode={c.remove.error instanceof Error ? c.remove.error.message : undefined}
|
|
onClose={() => c.setDeleting(false)}
|
|
onConfirm={async () => {
|
|
await c.remove.mutateAsync();
|
|
}}
|
|
open={c.deleting}
|
|
/>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|