133 lines
4.6 KiB
TypeScript
133 lines
4.6 KiB
TypeScript
import { useListQuery } from "@metis/core/client";
|
|
import { Button } from "@repo/shadcn-ui/components/button";
|
|
import { useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import { catalogApi, catalogKeys } from "../../../catalog/shared/api";
|
|
import { InventoryFeedback } from "../../../shared";
|
|
import { infrastructureApi, infrastructureKeys } from "../api/infrastructure-api";
|
|
import { infrastructureRoles } from "../helpers/infrastructure-roles";
|
|
|
|
import { InfrastructureEditorDialog } from "./infrastructure-editor-dialog";
|
|
|
|
export function AssetInfrastructurePanel({
|
|
companyId,
|
|
assetId,
|
|
assetName,
|
|
templateId,
|
|
}: {
|
|
companyId: string;
|
|
assetId: string;
|
|
assetName: string;
|
|
templateId: string;
|
|
}) {
|
|
const { t, i18n } = useTranslation("assets");
|
|
const [editing, setEditing] = useState(false);
|
|
const query = useListQuery({
|
|
queryKey: infrastructureKeys.company(companyId),
|
|
queryFn: () => infrastructureApi.get(companyId),
|
|
});
|
|
const catalog = useListQuery({
|
|
queryKey: catalogKeys.scope(companyId),
|
|
queryFn: () => catalogApi.get(companyId),
|
|
});
|
|
const current = query.data?.items.find((item) => item.assetId === assetId);
|
|
|
|
return (
|
|
<div className="space-y-3">
|
|
{query.isPending && <p role="status">{t("loading")}</p>}
|
|
<InventoryFeedback error={query.error ?? catalog.error} />
|
|
{(query.isError || catalog.isError) && (
|
|
<Button
|
|
onClick={() => {
|
|
Promise.all([query.refetch(), catalog.refetch()]).catch(() => undefined);
|
|
}}
|
|
size="sm"
|
|
variant="outline"
|
|
>
|
|
{t("retry")}
|
|
</Button>
|
|
)}
|
|
{current?.values.roles.length ? (
|
|
<p className="text-sm">
|
|
{current.values.roles.map((role) => t(`infrastructure.roles.${role}`)).join(" · ")}
|
|
</p>
|
|
) : null}
|
|
{query.isSuccess && !current?.values.interfaces.length && (
|
|
<p className="text-sm text-muted-foreground">{t("infrastructure.emptyInterfaces")}</p>
|
|
)}
|
|
<ul className="space-y-3">
|
|
{current?.values.interfaces.map((entry) => (
|
|
<li className="rounded-md border p-3 text-sm" key={entry.id}>
|
|
<p className="font-medium">
|
|
{entry.name} · {t(`infrastructure.kinds.${entry.kind}`)}
|
|
</p>
|
|
<p>
|
|
{entry.addresses.length
|
|
? entry.addresses.map((address) => address.address).join(" · ")
|
|
: "—"}
|
|
</p>
|
|
<p className="text-muted-foreground">
|
|
{t("infrastructure.mac")}: {entry.mac || "—"}
|
|
</p>
|
|
{entry.notes && <p className="whitespace-pre-wrap">{entry.notes}</p>}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
<Button
|
|
disabled={!(query.isSuccess && catalog.isSuccess)}
|
|
onClick={() => setEditing(true)}
|
|
size="sm"
|
|
variant="outline"
|
|
>
|
|
{t("infrastructure.edit")}
|
|
</Button>
|
|
{current && (
|
|
<details>
|
|
<summary className="cursor-pointer text-sm font-medium">
|
|
{t("history")} ({current.history.length})
|
|
</summary>
|
|
<ol className="mt-3 space-y-3">
|
|
{[...current.history].reverse().map((revision) => (
|
|
<li className="space-y-2 rounded-md border p-3 text-sm" key={revision.id}>
|
|
<p>
|
|
{new Intl.DateTimeFormat(i18n.language, {
|
|
dateStyle: "short",
|
|
timeStyle: "short",
|
|
}).format(new Date(revision.recordedAt))}{" "}
|
|
· {revision.actorName}
|
|
</p>
|
|
<p>
|
|
{revision.values.roles
|
|
.map((role) => t(`infrastructure.roles.${role}`))
|
|
.join(" · ") || "—"}
|
|
</p>
|
|
<ul>
|
|
{revision.values.interfaces.map((entry) => (
|
|
<li key={entry.id}>
|
|
{entry.name} · {t(`infrastructure.kinds.${entry.kind}`)} ·{" "}
|
|
{entry.addresses.map((address) => address.address).join(" · ") || "—"} ·{" "}
|
|
{entry.mac || "—"}
|
|
{entry.notes && <p className="whitespace-pre-wrap">{entry.notes}</p>}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</li>
|
|
))}
|
|
</ol>
|
|
</details>
|
|
)}
|
|
{editing && (
|
|
<InfrastructureEditorDialog
|
|
assetId={assetId}
|
|
assetName={assetName}
|
|
companyId={companyId}
|
|
current={current}
|
|
initialRoles={infrastructureRoles(templateId, catalog.data?.items ?? [])}
|
|
onClose={() => setEditing(false)}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|