metis-desk/modules/resources/src/web/resources/detail/ui/resource-detail.tsx
David 0a34ddf752 feat: add local identity management features and UI components
- Implement folder account options helper for filtering access items based on person ID and date.
- Create FolderAccessAccountField component for selecting accounts with profile integration.
- Develop AssetLocalIdentities component for managing local identities associated with assets.
- Introduce LocalIdentityOwner component to display and manage local identity ownership.
- Add LocalIdentityEditor for creating, associating, and retiring local identities.
- Implement local identity API for fetching and executing local identity commands.
- Create tests for local identity UI components to ensure functionality and correctness.
- Add NetworkShareOptions class for structured and paginated SMB selection.
- Introduce NetworkShareAddress component for displaying and copying network share addresses.
- Create RemoteOptionList and RemoteOptionPicker components for remote option selection with pagination and search.
- Add tests for RemoteOptionList to validate rendering and behavior under various states.
2026-10-21 12:37:59 +02:00

120 lines
4.4 KiB
TypeScript

import { DetailField } from "@repo/rdx-ui/components/detail-field";
import { FieldSections } from "@repo/rdx-ui/components/field-sections";
import { FormSectionTitle } from "@repo/rdx-ui/components/form-section-title";
import { useTranslation } from "react-i18next";
import type { ResourceDTO, ResourceReferenceDTO } from "../../../../common/inventory";
import { IntegrationSummary } from "./integration-summary";
import { NetworkShareAddress } from "./network-share-address";
export function ResourceDetail({
resource,
references = [],
compact = false,
}: {
resource: ResourceDTO;
compact?: boolean;
references?: readonly ResourceReferenceDTO[];
}) {
const { t, i18n } = useTranslation("resources");
const display = (value: string | number | boolean | undefined) =>
value === undefined
? t("noValue")
: typeof value === "boolean"
? t(value ? "yes" : "no")
: String(value);
return (
<div className="@container space-y-5">
{!compact && <p className="text-sm text-muted-foreground">{t("snapshotNotice")}</p>}
{!compact && (
<dl className="grid gap-4 @min-[560px]:grid-cols-2">
<div>
<dt className="text-sm text-muted-foreground">{t("template")}</dt>
<dd>{resource.specification.templateName}</dd>
</div>
<div>
<dt className="text-sm text-muted-foreground">{t("status")}</dt>
<dd>{t(resource.values.active ? "active" : "inactive")}</dd>
</div>
</dl>
)}
<FieldSections
items={resource.specification.fields}
layout={compact ? "rows" : "fields"}
renderItem={(field) => (
<DetailField
key={field.fieldId}
label={
<>
{field.name}
{field.unit ? ` (${field.unit})` : ""}
</>
}
>
{resource.networkShare?.capability === "network:smb-share" &&
resource.values.characteristics[field.fieldId] === resource.networkShare.address ? (
<NetworkShareAddress address={resource.networkShare.address} />
) : (
display(resource.values.characteristics[field.fieldId])
)}
</DetailField>
)}
sections={resource.specification.sections}
ungroupedLabel={t("sections.ungrouped")}
values
/>
<IntegrationSummary integration={resource.values.integration} references={references} />
{resource.values.notes && (
<p className="whitespace-pre-wrap break-words">{resource.values.notes}</p>
)}
{!compact && (
<>
<FormSectionTitle>
{t("history")} ({resource.history.length})
</FormSectionTitle>
<ol className="space-y-4">
{[...resource.history].reverse().map((revision) => (
<li className="space-y-2 rounded-md border p-3 text-sm" key={revision.id}>
<p className="font-medium">
{new Intl.DateTimeFormat(i18n.language, {
dateStyle: "medium",
timeStyle: "short",
}).format(new Date(revision.recordedAt))}{" "}
· {t("recordedBy", { name: revision.actorName })}
</p>
<p>
{revision.values.name} · {t(revision.values.active ? "active" : "inactive")}
</p>
<FieldSections
items={(revision.specification ?? resource.specification).fields}
renderItem={(field) => (
<div key={field.fieldId}>
<dt className="text-muted-foreground">{field.name}</dt>
<dd className="whitespace-pre-wrap break-words">
{display(revision.values.characteristics[field.fieldId])}
</dd>
</div>
)}
sections={(revision.specification ?? resource.specification).sections}
ungroupedLabel={t("sections.ungrouped")}
values
/>
<IntegrationSummary
integration={revision.values.integration}
references={references}
/>
{revision.values.notes && (
<p className="whitespace-pre-wrap break-words">{revision.values.notes}</p>
)}
</li>
))}
</ol>
</>
)}
</div>
);
}