@voyantjs/resources-ui 0.30.7 → 0.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +41 -0
  2. package/dist/components/resource-allocation-detail-page.d.ts +15 -0
  3. package/dist/components/resource-allocation-detail-page.d.ts.map +1 -0
  4. package/dist/components/resource-allocation-detail-page.js +45 -0
  5. package/dist/components/resource-assignment-detail-page.d.ts +15 -0
  6. package/dist/components/resource-assignment-detail-page.d.ts.map +1 -0
  7. package/dist/components/resource-assignment-detail-page.js +47 -0
  8. package/dist/components/resource-detail-data.d.ts +34 -0
  9. package/dist/components/resource-detail-data.d.ts.map +1 -0
  10. package/dist/components/resource-detail-data.js +90 -0
  11. package/dist/components/resource-detail-page.d.ts +23 -0
  12. package/dist/components/resource-detail-page.d.ts.map +1 -0
  13. package/dist/components/resource-detail-page.js +77 -0
  14. package/dist/components/resource-detail-shared.d.ts +37 -0
  15. package/dist/components/resource-detail-shared.d.ts.map +1 -0
  16. package/dist/components/resource-detail-shared.js +51 -0
  17. package/dist/components/resource-pool-detail-page.d.ts +21 -0
  18. package/dist/components/resource-pool-detail-page.d.ts.map +1 -0
  19. package/dist/components/resource-pool-detail-page.js +65 -0
  20. package/dist/components/resources-page.d.ts +55 -0
  21. package/dist/components/resources-page.d.ts.map +1 -0
  22. package/dist/components/resources-page.js +174 -0
  23. package/dist/i18n/en.d.ts.map +1 -1
  24. package/dist/i18n/en.js +114 -0
  25. package/dist/i18n/messages.d.ts +114 -0
  26. package/dist/i18n/messages.d.ts.map +1 -1
  27. package/dist/i18n/ro.d.ts.map +1 -1
  28. package/dist/i18n/ro.js +114 -0
  29. package/dist/index.d.ts +7 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +7 -0
  32. package/package.json +7 -7
package/README.md CHANGED
@@ -12,6 +12,47 @@ pnpm add @voyantjs/resources-ui @voyantjs/resources-react @voyantjs/ui @tanstack
12
12
 
13
13
  All components accept a `className` prop and merge it with `cn()`. Wrap or compose to extend; use the registry copy-paste path (`npx shadcn add @voyant/...`) for components you want to fork outright.
14
14
 
15
+ ## Components
16
+
17
+ - `ResourcesPage` composes the resources overview and all primary/secondary tabs with package data hooks, shared filters, row selection state, and app-provided bulk mutation/navigation handlers.
18
+ - `ResourcesOverview`, `ResourcesTab`, `PoolsTab`, `AllocationsTab`, `AssignmentsTab`, and `CloseoutsTab` remain exported for apps that need a custom page shell.
19
+ - `ResourceDetailPage`, `ResourcePoolDetailPage`, `ResourceAllocationDetailPage`, and `ResourceAssignmentDetailPage` compose package-owned detail views for the four resources surfaces. They use `@voyantjs/resources-react` hooks for detail/list data where available and expose app callbacks for routing and delete mutations.
20
+ - `ensureResourceDetailPageData`, `ensureResourcePoolDetailPageData`, `ensureResourceAllocationDetailPageData`, and `ensureResourceAssignmentDetailPageData` are exported for route loaders that want to prefetch the same package data.
21
+ - `ResourceDetailField`, `ResourceDetailCard`, `ResourceDetailHeader`, the four detail skeletons, and `useResourcePoolMembers` are exported for apps that need custom detail shells while preserving the package sections/helpers.
22
+
23
+ ## Detail pages
24
+
25
+ The detail pages deliberately keep router and mutation behavior app-owned:
26
+
27
+ ```tsx
28
+ import {
29
+ ResourceDetailPage,
30
+ ensureResourceDetailPageData,
31
+ } from "@voyantjs/resources-ui"
32
+ import { defaultFetcher, resourcesQueryKeys } from "@voyantjs/resources-react"
33
+
34
+ const resourcesClient = { baseUrl: "/api", fetcher: defaultFetcher }
35
+
36
+ export const loader = ({ queryClient, id }) =>
37
+ ensureResourceDetailPageData(queryClient, resourcesClient, id)
38
+
39
+ export function RouteComponent({ id, navigate, queryClient, api }) {
40
+ return (
41
+ <ResourceDetailPage
42
+ id={id}
43
+ onBack={() => navigate("/resources")}
44
+ onOpenSupplier={(supplierId) => navigate(`/suppliers/${supplierId}`)}
45
+ onOpenAssignment={(assignmentId) => navigate(`/resources/assignments/${assignmentId}`)}
46
+ onDelete={async (resource) => {
47
+ await api.delete(`/v1/resources/resources/${resource.id}`)
48
+ await queryClient.invalidateQueries({ queryKey: resourcesQueryKeys.resources() })
49
+ navigate("/resources")
50
+ }}
51
+ />
52
+ )
53
+ }
54
+ ```
55
+
15
56
  ## I18n
16
57
 
17
58
  Components render English by default. To localize them, wrap your UI in
@@ -0,0 +1,15 @@
1
+ import { type ResourceAllocationDetail } from "@voyantjs/resources-react";
2
+ import { type ConfirmAction } from "./resource-detail-shared.js";
3
+ export interface ResourceAllocationDetailPageProps {
4
+ id: string;
5
+ className?: string;
6
+ deleting?: boolean;
7
+ onBack?: () => void;
8
+ onDelete?: (allocation: ResourceAllocationDetail) => Promise<void> | void;
9
+ onOpenPool?: (poolId: string) => void;
10
+ onOpenProduct?: (productId: string) => void;
11
+ confirmAction?: ConfirmAction;
12
+ }
13
+ export declare function ResourceAllocationDetailPage({ className, confirmAction, deleting, id, onBack, onDelete, onOpenPool, onOpenProduct, }: ResourceAllocationDetailPageProps): import("react/jsx-runtime").JSX.Element;
14
+ export declare function ResourceAllocationDetailSkeleton(): import("react/jsx-runtime").JSX.Element;
15
+ //# sourceMappingURL=resource-allocation-detail-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-allocation-detail-page.d.ts","sourceRoot":"","sources":["../../src/components/resource-allocation-detail-page.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,wBAAwB,EAM9B,MAAM,2BAA2B,CAAA;AAKlC,OAAO,EAEL,KAAK,aAAa,EAKnB,MAAM,6BAA6B,CAAA;AAEpC,MAAM,WAAW,iCAAiC;IAChD,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,wBAAwB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACzE,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3C,aAAa,CAAC,EAAE,aAAa,CAAA;CAC9B;AAED,wBAAgB,4BAA4B,CAAC,EAC3C,SAAS,EACT,aAAa,EACb,QAAQ,EACR,EAAE,EACF,MAAM,EACN,QAAQ,EACR,UAAU,EACV,aAAa,GACd,EAAE,iCAAiC,2CA2HnC;AAED,wBAAgB,gCAAgC,4CAI/C"}
@@ -0,0 +1,45 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { labelById, useAllocation, usePool, useProducts, useRules, useStartTimes, } from "@voyantjs/resources-react";
4
+ import { Badge, Button, cn } from "@voyantjs/ui/components";
5
+ import { Package, Wrench } from "lucide-react";
6
+ import { useResourcesUiI18nOrDefault } from "../i18n/index.js";
7
+ import { ResourceDetailSkeleton as BaseResourceDetailSkeleton, ResourceDetailCard, ResourceDetailField, ResourceDetailHeader, ResourceDetailState, } from "./resource-detail-shared.js";
8
+ export function ResourceAllocationDetailPage({ className, confirmAction, deleting, id, onBack, onDelete, onOpenPool, onOpenProduct, }) {
9
+ const i18n = useResourcesUiI18nOrDefault();
10
+ const m = i18n.messages;
11
+ const page = m.detailPages;
12
+ const allocationQuery = useAllocation(id);
13
+ const allocation = allocationQuery.data;
14
+ const poolQuery = usePool(allocation?.poolId);
15
+ const productsQuery = useProducts({ limit: 25 });
16
+ const rulesQuery = useRules({
17
+ enabled: Boolean(allocation?.productId),
18
+ productId: allocation?.productId,
19
+ limit: 25,
20
+ });
21
+ const startTimesQuery = useStartTimes({
22
+ enabled: Boolean(allocation?.productId),
23
+ productId: allocation?.productId,
24
+ limit: 25,
25
+ });
26
+ if (allocationQuery.isPending) {
27
+ return _jsx(ResourceAllocationDetailSkeleton, {});
28
+ }
29
+ if (allocationQuery.isError) {
30
+ return (_jsx(ResourceDetailState, { className: className, message: page.allocation.loadFailed, onBack: onBack }));
31
+ }
32
+ if (!allocation) {
33
+ return (_jsx(ResourceDetailState, { className: className, message: page.allocation.notFound, onBack: onBack }));
34
+ }
35
+ const rule = rulesQuery.data?.data.find((entry) => entry.id === allocation.availabilityRuleId);
36
+ const startTime = startTimesQuery.data?.data.find((entry) => entry.id === allocation.startTimeId);
37
+ return (_jsxs("div", { "data-slot": "resource-allocation-detail-page", className: cn("flex flex-col gap-6 p-6", className), children: [_jsx(ResourceDetailHeader, { title: page.allocation.pageTitle, deleteConfirmName: allocation.id, deleteConfirmTemplate: page.allocation.deleteConfirm, deleteErrorMessage: page.allocation.deleteFailed, deleting: deleting, confirmAction: confirmAction, onBack: onBack, onDelete: onDelete ? () => onDelete(allocation) : undefined, badges: _jsxs(_Fragment, { children: [_jsx(Badge, { variant: "outline", children: m.common.allocationModeLabels[allocation.allocationMode] }), _jsxs(Badge, { variant: "secondary", children: [page.common.quantity, " ", i18n.formatNumber(allocation.quantityRequired)] })] }), actions: _jsxs(_Fragment, { children: [onOpenPool ? (_jsxs(Button, { type: "button", variant: "outline", onClick: () => onOpenPool(allocation.poolId), children: [_jsx(Wrench, { "data-icon": "inline-start", "aria-hidden": "true" }), page.common.openPool] })) : null, onOpenProduct ? (_jsxs(Button, { type: "button", variant: "outline", onClick: () => onOpenProduct(allocation.productId), children: [_jsx(Package, { "data-icon": "inline-start", "aria-hidden": "true" }), page.common.openProduct] })) : null] }) }), _jsx(ResourceDetailCard, { title: page.allocation.detailsTitle, children: _jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [_jsx(ResourceDetailField, { label: page.common.pool, children: poolQuery.data?.name ?? allocation.poolId }), _jsx(ResourceDetailField, { label: page.common.product, children: labelById(productsQuery.data?.data ?? [], allocation.productId) ||
38
+ allocation.productId }), _jsx(ResourceDetailField, { label: page.allocation.rule, children: rule?.recurrenceRule ?? allocation.availabilityRuleId ?? page.common.noRule }), _jsx(ResourceDetailField, { label: page.allocation.startTime, children: startTime?.label ??
39
+ startTime?.startTimeLocal ??
40
+ allocation.startTimeId ??
41
+ page.common.noStartTime }), _jsx(ResourceDetailField, { label: page.allocation.priority, children: i18n.formatNumber(allocation.priority) }), _jsx(ResourceDetailField, { label: page.common.created, children: i18n.formatDateTime(allocation.createdAt) }), _jsx(ResourceDetailField, { label: page.common.updated, children: i18n.formatDateTime(allocation.updatedAt) })] }) })] }));
42
+ }
43
+ export function ResourceAllocationDetailSkeleton() {
44
+ return (_jsx(BaseResourceDetailSkeleton, { actionCount: 3, detailRows: 7, showNotes: false, stackedCards: 0 }));
45
+ }
@@ -0,0 +1,15 @@
1
+ import { type ResourceSlotAssignmentDetail } from "@voyantjs/resources-react";
2
+ import { type ConfirmAction } from "./resource-detail-shared.js";
3
+ export interface ResourceAssignmentDetailPageProps {
4
+ id: string;
5
+ className?: string;
6
+ deleting?: boolean;
7
+ onBack?: () => void;
8
+ onDelete?: (assignment: ResourceSlotAssignmentDetail) => Promise<void> | void;
9
+ onOpenResource?: (resourceId: string) => void;
10
+ onOpenSlot?: (slotId: string) => void;
11
+ confirmAction?: ConfirmAction;
12
+ }
13
+ export declare function ResourceAssignmentDetailPage({ className, confirmAction, deleting, id, onBack, onDelete, onOpenResource, onOpenSlot, }: ResourceAssignmentDetailPageProps): import("react/jsx-runtime").JSX.Element;
14
+ export declare function ResourceAssignmentDetailSkeleton(): import("react/jsx-runtime").JSX.Element;
15
+ //# sourceMappingURL=resource-assignment-detail-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-assignment-detail-page.d.ts","sourceRoot":"","sources":["../../src/components/resource-assignment-detail-page.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,4BAA4B,EAOlC,MAAM,2BAA2B,CAAA;AAMlC,OAAO,EAEL,KAAK,aAAa,EAKnB,MAAM,6BAA6B,CAAA;AAEpC,MAAM,WAAW,iCAAiC;IAChD,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,4BAA4B,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC7E,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7C,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC,aAAa,CAAC,EAAE,aAAa,CAAA;CAC9B;AAED,wBAAgB,4BAA4B,CAAC,EAC3C,SAAS,EACT,aAAa,EACb,QAAQ,EACR,EAAE,EACF,MAAM,EACN,QAAQ,EACR,cAAc,EACd,UAAU,GACX,EAAE,iCAAiC,2CAkInC;AAED,wBAAgB,gCAAgC,4CAI/C"}
@@ -0,0 +1,47 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { labelById, useAssignment, useBookings, usePool, useProducts, useResource, useSlots, } from "@voyantjs/resources-react";
4
+ import { Badge, Button, cn } from "@voyantjs/ui/components";
5
+ import { CalendarDays, Wrench } from "lucide-react";
6
+ import { useResourcesUiI18nOrDefault } from "../i18n/index.js";
7
+ import { formatDateTimeOrFallback, formatResourceSlotLabel } from "../i18n/utils.js";
8
+ import { ResourceDetailSkeleton as BaseResourceDetailSkeleton, ResourceDetailCard, ResourceDetailField, ResourceDetailHeader, ResourceDetailState, } from "./resource-detail-shared.js";
9
+ export function ResourceAssignmentDetailPage({ className, confirmAction, deleting, id, onBack, onDelete, onOpenResource, onOpenSlot, }) {
10
+ const i18n = useResourcesUiI18nOrDefault();
11
+ const m = i18n.messages;
12
+ const page = m.detailPages;
13
+ const assignmentQuery = useAssignment(id);
14
+ const assignment = assignmentQuery.data;
15
+ const slotsQuery = useSlots({ limit: 25 });
16
+ const slot = slotsQuery.data?.data.find((entry) => entry.id === assignment?.slotId);
17
+ const poolQuery = usePool(assignment?.poolId);
18
+ const resourceQuery = useResource(assignment?.resourceId);
19
+ const bookingsQuery = useBookings({ limit: 25 });
20
+ const productsQuery = useProducts({ limit: 25 });
21
+ if (assignmentQuery.isPending) {
22
+ return _jsx(ResourceAssignmentDetailSkeleton, {});
23
+ }
24
+ if (assignmentQuery.isError) {
25
+ return (_jsx(ResourceDetailState, { className: className, message: page.assignment.loadFailed, onBack: onBack }));
26
+ }
27
+ if (!assignment) {
28
+ return (_jsx(ResourceDetailState, { className: className, message: page.assignment.notFound, onBack: onBack }));
29
+ }
30
+ const slotLabel = slot
31
+ ? formatResourceSlotLabel(slot, {
32
+ template: m.common.slotLabel,
33
+ formatDate: i18n.formatDate,
34
+ })
35
+ : assignment.slotId;
36
+ return (_jsxs("div", { "data-slot": "resource-assignment-detail-page", className: cn("flex flex-col gap-6 p-6", className), children: [_jsx(ResourceDetailHeader, { title: page.assignment.pageTitle, deleteConfirmName: assignment.id, deleteConfirmTemplate: page.assignment.deleteConfirm, deleteErrorMessage: page.assignment.deleteFailed, deleting: deleting, confirmAction: confirmAction, onBack: onBack, onDelete: onDelete ? () => onDelete(assignment) : undefined, badges: _jsxs(_Fragment, { children: [_jsx(Badge, { variant: "outline", children: m.common.assignmentStatusLabels[assignment.status] }), _jsx(Badge, { variant: "secondary", children: slotLabel })] }), actions: _jsxs(_Fragment, { children: [onOpenSlot ? (_jsxs(Button, { type: "button", variant: "outline", onClick: () => onOpenSlot(assignment.slotId), children: [_jsx(CalendarDays, { "data-icon": "inline-start", "aria-hidden": "true" }), page.common.openSlot] })) : null, assignment.resourceId && onOpenResource ? (_jsxs(Button, { type: "button", variant: "outline", onClick: () => onOpenResource(assignment.resourceId), children: [_jsx(Wrench, { "data-icon": "inline-start", "aria-hidden": "true" }), page.common.openResource] })) : null] }) }), _jsx(ResourceDetailCard, { title: page.assignment.detailsTitle, children: _jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [_jsx(ResourceDetailField, { label: page.common.slot, children: slotLabel }), _jsx(ResourceDetailField, { label: page.common.product, children: slot?.productId
37
+ ? labelById(productsQuery.data?.data ?? [], slot.productId)
38
+ : page.common.noValue }), _jsx(ResourceDetailField, { label: page.common.pool, children: poolQuery.data?.name ?? assignment.poolId ?? page.common.noPool }), _jsx(ResourceDetailField, { label: page.common.resource, children: resourceQuery.data?.name ?? assignment.resourceId ?? page.common.noResource }), _jsx(ResourceDetailField, { label: page.common.booking, children: assignment.bookingId
39
+ ? labelById(bookingsQuery.data?.data ?? [], assignment.bookingId)
40
+ : page.common.noBooking }), _jsx(ResourceDetailField, { label: page.assignment.assignedBy, children: assignment.assignedBy ?? page.common.noValue }), _jsx(ResourceDetailField, { label: page.assignment.released, children: formatDateTimeOrFallback(assignment.releasedAt, {
41
+ fallback: page.common.noValue,
42
+ formatDateTime: i18n.formatDateTime,
43
+ }) }), _jsx(ResourceDetailField, { label: page.common.created, children: i18n.formatDateTime(assignment.createdAt) }), _jsx(ResourceDetailField, { label: page.common.updated, children: i18n.formatDateTime(assignment.updatedAt) })] }) }), assignment.notes ? (_jsx(ResourceDetailCard, { title: page.common.notes, children: _jsx("p", { className: "whitespace-pre-wrap", children: assignment.notes }) })) : null] }));
44
+ }
45
+ export function ResourceAssignmentDetailSkeleton() {
46
+ return (_jsx(BaseResourceDetailSkeleton, { actionCount: 3, detailRows: 9, showNotes: false, stackedCards: 0 }));
47
+ }
@@ -0,0 +1,34 @@
1
+ import type { QueryClient } from "@tanstack/react-query";
2
+ import { type VoyantResourcesContextValue } from "@voyantjs/resources-react";
3
+ export type ResourcePoolMemberRow = {
4
+ id: string;
5
+ poolId: string;
6
+ resourceId: string;
7
+ };
8
+ type ListResponse<T> = {
9
+ data: T[];
10
+ total: number;
11
+ limit: number;
12
+ offset: number;
13
+ };
14
+ type PoolMemberFilters = {
15
+ poolId?: string | undefined;
16
+ resourceId?: string | undefined;
17
+ limit?: number | undefined;
18
+ offset?: number | undefined;
19
+ };
20
+ export declare function getResourcePoolMembersQueryOptions(client: VoyantResourcesContextValue, filters?: PoolMemberFilters): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<ListResponse<ResourcePoolMemberRow>, Error, ListResponse<ResourcePoolMemberRow>, readonly ["voyant", "resources", "pool-members", "list", PoolMemberFilters]>, "queryFn"> & {
21
+ queryFn?: import("@tanstack/react-query").QueryFunction<ListResponse<ResourcePoolMemberRow>, readonly ["voyant", "resources", "pool-members", "list", PoolMemberFilters], never> | undefined;
22
+ } & {
23
+ queryKey: readonly ["voyant", "resources", "pool-members", "list", PoolMemberFilters] & {
24
+ [dataTagSymbol]: ListResponse<ResourcePoolMemberRow>;
25
+ [dataTagErrorSymbol]: Error;
26
+ };
27
+ };
28
+ export declare function useResourcePoolMembers(filters?: PoolMemberFilters): import("@tanstack/react-query").UseQueryResult<ListResponse<ResourcePoolMemberRow>, Error>;
29
+ export declare function ensureResourceDetailPageData(queryClient: QueryClient, client: VoyantResourcesContextValue, id: string): Promise<void>;
30
+ export declare function ensureResourcePoolDetailPageData(queryClient: QueryClient, client: VoyantResourcesContextValue, id: string): Promise<void>;
31
+ export declare function ensureResourceAllocationDetailPageData(queryClient: QueryClient, client: VoyantResourcesContextValue, id: string): Promise<void>;
32
+ export declare function ensureResourceAssignmentDetailPageData(queryClient: QueryClient, client: VoyantResourcesContextValue, id: string): Promise<void>;
33
+ export {};
34
+ //# sourceMappingURL=resource-detail-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-detail-data.d.ts","sourceRoot":"","sources":["../../src/components/resource-detail-data.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,EAkBL,KAAK,2BAA2B,EACjC,MAAM,2BAA2B,CAAA;AAIlC,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,KAAK,YAAY,CAAC,CAAC,IAAI;IACrB,IAAI,EAAE,CAAC,EAAE,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,KAAK,iBAAiB,GAAG;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC5B,CAAA;AAmBD,wBAAgB,kCAAkC,CAChD,MAAM,EAAE,2BAA2B,EACnC,OAAO,GAAE,iBAAsB;;;;;;;EAiBhC;AAED,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,iBAAsB,8FAQrE;AAED,wBAAsB,4BAA4B,CAChD,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,2BAA2B,EACnC,EAAE,EAAE,MAAM,iBAmBX;AAED,wBAAsB,gCAAgC,CACpD,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,2BAA2B,EACnC,EAAE,EAAE,MAAM,iBAmBX;AAED,wBAAsB,sCAAsC,CAC1D,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,2BAA2B,EACnC,EAAE,EAAE,MAAM,iBAcX;AAED,wBAAsB,sCAAsC,CAC1D,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,2BAA2B,EACnC,EAAE,EAAE,MAAM,iBAeX"}
@@ -0,0 +1,90 @@
1
+ "use client";
2
+ import { queryOptions, useQuery } from "@tanstack/react-query";
3
+ import { getAllocationQueryOptions, getAllocationsQueryOptions, getAssignmentQueryOptions, getAssignmentsQueryOptions, getBookingsQueryOptions, getCloseoutsQueryOptions, getPoolQueryOptions, getPoolsQueryOptions, getProductsQueryOptions, getResourceQueryOptions, getResourcesQueryOptions, getRulesQueryOptions, getSlotsQueryOptions, getStartTimesQueryOptions, getSuppliersQueryOptions, resourcesQueryKeys, useVoyantResourcesContext, } from "@voyantjs/resources-react";
4
+ const DETAIL_LIMIT = 25;
5
+ function joinUrl(baseUrl, path) {
6
+ const trimmedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
7
+ const trimmedPath = path.startsWith("/") ? path : `/${path}`;
8
+ return `${trimmedBase}${trimmedPath}`;
9
+ }
10
+ async function fetchJson(client, path) {
11
+ const response = await client.fetcher(joinUrl(client.baseUrl, path));
12
+ if (!response.ok) {
13
+ const body = await response.text();
14
+ throw new Error(body || `Voyant API error: ${response.status} ${response.statusText}`);
15
+ }
16
+ return (await response.json());
17
+ }
18
+ export function getResourcePoolMembersQueryOptions(client, filters = {}) {
19
+ return queryOptions({
20
+ queryKey: [...resourcesQueryKeys.all, "pool-members", "list", filters],
21
+ queryFn: () => {
22
+ const params = new URLSearchParams();
23
+ if (filters.poolId)
24
+ params.set("poolId", filters.poolId);
25
+ if (filters.resourceId)
26
+ params.set("resourceId", filters.resourceId);
27
+ if (filters.limit !== undefined)
28
+ params.set("limit", String(filters.limit));
29
+ if (filters.offset !== undefined)
30
+ params.set("offset", String(filters.offset));
31
+ const qs = params.toString();
32
+ return fetchJson(client, `/v1/resources/pool-members${qs ? `?${qs}` : ""}`);
33
+ },
34
+ });
35
+ }
36
+ export function useResourcePoolMembers(filters = {}) {
37
+ const client = useVoyantResourcesContext();
38
+ const enabled = Boolean(filters.poolId || filters.resourceId);
39
+ return useQuery({
40
+ ...getResourcePoolMembersQueryOptions(client, filters),
41
+ enabled,
42
+ });
43
+ }
44
+ export async function ensureResourceDetailPageData(queryClient, client, id) {
45
+ await queryClient.ensureQueryData(getResourceQueryOptions(client, id));
46
+ await Promise.all([
47
+ queryClient.ensureQueryData(getResourcePoolMembersQueryOptions(client, { resourceId: id, limit: DETAIL_LIMIT })),
48
+ queryClient.ensureQueryData(getPoolsQueryOptions(client, { limit: DETAIL_LIMIT })),
49
+ queryClient.ensureQueryData(getAssignmentsQueryOptions(client, { resourceId: id, limit: DETAIL_LIMIT })),
50
+ queryClient.ensureQueryData(getSlotsQueryOptions(client, { limit: DETAIL_LIMIT })),
51
+ queryClient.ensureQueryData(getBookingsQueryOptions(client, { limit: DETAIL_LIMIT })),
52
+ queryClient.ensureQueryData(getCloseoutsQueryOptions(client, { resourceId: id, limit: DETAIL_LIMIT })),
53
+ queryClient.ensureQueryData(getSuppliersQueryOptions(client, { limit: DETAIL_LIMIT })),
54
+ ]);
55
+ }
56
+ export async function ensureResourcePoolDetailPageData(queryClient, client, id) {
57
+ await queryClient.ensureQueryData(getPoolQueryOptions(client, id));
58
+ await Promise.all([
59
+ queryClient.ensureQueryData(getResourcePoolMembersQueryOptions(client, { poolId: id, limit: DETAIL_LIMIT })),
60
+ queryClient.ensureQueryData(getResourcesQueryOptions(client, { limit: DETAIL_LIMIT })),
61
+ queryClient.ensureQueryData(getAllocationsQueryOptions(client, { poolId: id, limit: DETAIL_LIMIT })),
62
+ queryClient.ensureQueryData(getAssignmentsQueryOptions(client, { poolId: id, limit: DETAIL_LIMIT })),
63
+ queryClient.ensureQueryData(getSlotsQueryOptions(client, { limit: DETAIL_LIMIT })),
64
+ queryClient.ensureQueryData(getBookingsQueryOptions(client, { limit: DETAIL_LIMIT })),
65
+ queryClient.ensureQueryData(getProductsQueryOptions(client, { limit: DETAIL_LIMIT })),
66
+ ]);
67
+ }
68
+ export async function ensureResourceAllocationDetailPageData(queryClient, client, id) {
69
+ const allocation = await queryClient.ensureQueryData(getAllocationQueryOptions(client, id));
70
+ await Promise.all([
71
+ queryClient.ensureQueryData(getPoolQueryOptions(client, allocation.poolId)),
72
+ queryClient.ensureQueryData(getProductsQueryOptions(client, { limit: DETAIL_LIMIT })),
73
+ queryClient.ensureQueryData(getRulesQueryOptions(client, { productId: allocation.productId, limit: DETAIL_LIMIT })),
74
+ queryClient.ensureQueryData(getStartTimesQueryOptions(client, { productId: allocation.productId, limit: DETAIL_LIMIT })),
75
+ ]);
76
+ }
77
+ export async function ensureResourceAssignmentDetailPageData(queryClient, client, id) {
78
+ const assignment = await queryClient.ensureQueryData(getAssignmentQueryOptions(client, id));
79
+ await Promise.all([
80
+ assignment.poolId
81
+ ? queryClient.ensureQueryData(getPoolQueryOptions(client, assignment.poolId))
82
+ : Promise.resolve(),
83
+ assignment.resourceId
84
+ ? queryClient.ensureQueryData(getResourceQueryOptions(client, assignment.resourceId))
85
+ : Promise.resolve(),
86
+ queryClient.ensureQueryData(getSlotsQueryOptions(client, { limit: DETAIL_LIMIT })),
87
+ queryClient.ensureQueryData(getBookingsQueryOptions(client, { limit: DETAIL_LIMIT })),
88
+ queryClient.ensureQueryData(getProductsQueryOptions(client, { limit: DETAIL_LIMIT })),
89
+ ]);
90
+ }
@@ -0,0 +1,23 @@
1
+ import { type ResourceDetail, type ResourcePoolRow, type ResourceSlotAssignmentRow } from "@voyantjs/resources-react";
2
+ import { type ConfirmAction } from "./resource-detail-shared.js";
3
+ export interface ResourceDetailPageProps {
4
+ id: string;
5
+ className?: string;
6
+ deleting?: boolean;
7
+ onBack?: () => void;
8
+ onDelete?: (resource: ResourceDetail) => Promise<void> | void;
9
+ onOpenSupplier?: (supplierId: string) => void;
10
+ onOpenAssignment?: (assignmentId: string) => void;
11
+ confirmAction?: ConfirmAction;
12
+ }
13
+ export declare function ResourceDetailPage({ className, confirmAction, deleting, id, onBack, onDelete, onOpenAssignment, onOpenSupplier, }: ResourceDetailPageProps): import("react/jsx-runtime").JSX.Element;
14
+ export declare function ResourceDetailSkeleton(): import("react/jsx-runtime").JSX.Element;
15
+ export declare function ResourceAssignmentSummary({ assignment, bookingLabel, noValue, onOpenAssignment, pool, slotLabel, }: {
16
+ assignment: ResourceSlotAssignmentRow;
17
+ bookingLabel: string;
18
+ noValue: string;
19
+ onOpenAssignment?: (assignmentId: string) => void;
20
+ pool?: ResourcePoolRow | undefined;
21
+ slotLabel: string;
22
+ }): import("react/jsx-runtime").JSX.Element;
23
+ //# sourceMappingURL=resource-detail-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-detail-page.d.ts","sourceRoot":"","sources":["../../src/components/resource-detail-page.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,yBAAyB,EAQ/B,MAAM,2BAA2B,CAAA;AAelC,OAAO,EAEL,KAAK,aAAa,EAKnB,MAAM,6BAA6B,CAAA;AAEpC,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC7D,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7C,gBAAgB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAA;IACjD,aAAa,CAAC,EAAE,aAAa,CAAA;CAC9B;AAED,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,aAAa,EACb,QAAQ,EACR,EAAE,EACF,MAAM,EACN,QAAQ,EACR,gBAAgB,EAChB,cAAc,GACf,EAAE,uBAAuB,2CAmMzB;AAED,wBAAgB,sBAAsB,4CAErC;AAMD,wBAAgB,yBAAyB,CAAC,EACxC,UAAU,EACV,YAAY,EACZ,OAAO,EACP,gBAAgB,EAChB,IAAI,EACJ,SAAS,GACV,EAAE;IACD,UAAU,EAAE,yBAAyB,CAAA;IACrC,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,gBAAgB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAA;IACjD,IAAI,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IAClC,SAAS,EAAE,MAAM,CAAA;CAClB,2CA4CA"}
@@ -0,0 +1,77 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { labelById, useAssignments, useBookings, useCloseouts, usePools, useResource, useSlots, useSuppliers, } from "@voyantjs/resources-react";
4
+ import { Badge, Button, Card, CardContent, CardHeader, CardTitle, cn, } from "@voyantjs/ui/components";
5
+ import { Package, Users, Wrench } from "lucide-react";
6
+ import { useResourcesUiI18nOrDefault } from "../i18n/index.js";
7
+ import { formatDateTimeOrFallback, formatResourceSlotLabel } from "../i18n/utils.js";
8
+ import { useResourcePoolMembers } from "./resource-detail-data.js";
9
+ import { ResourceDetailSkeleton as BaseResourceDetailSkeleton, ResourceDetailCard, ResourceDetailField, ResourceDetailHeader, ResourceDetailState, } from "./resource-detail-shared.js";
10
+ export function ResourceDetailPage({ className, confirmAction, deleting, id, onBack, onDelete, onOpenAssignment, onOpenSupplier, }) {
11
+ const i18n = useResourcesUiI18nOrDefault();
12
+ const m = i18n.messages;
13
+ const page = m.detailPages;
14
+ const resourceQuery = useResource(id);
15
+ const suppliersQuery = useSuppliers({ limit: 25 });
16
+ const poolMembersQuery = useResourcePoolMembers({ resourceId: id, limit: 25 });
17
+ const poolsQuery = usePools({ limit: 25 });
18
+ const assignmentsQuery = useAssignmentsByResource(id);
19
+ const slotsQuery = useSlots({ limit: 25 });
20
+ const bookingsQuery = useBookings({ limit: 25 });
21
+ const closeoutsQuery = useCloseouts({ resourceId: id, limit: 25 });
22
+ if (resourceQuery.isPending) {
23
+ return _jsx(ResourceDetailSkeleton, {});
24
+ }
25
+ if (resourceQuery.isError) {
26
+ return (_jsx(ResourceDetailState, { className: className, message: page.resource.loadFailed, onBack: onBack }));
27
+ }
28
+ const resource = resourceQuery.data;
29
+ if (!resource) {
30
+ return (_jsx(ResourceDetailState, { className: className, message: page.resource.notFound, onBack: onBack }));
31
+ }
32
+ const pools = poolsQuery.data?.data ?? [];
33
+ const slots = slotsQuery.data?.data ?? [];
34
+ const bookings = bookingsQuery.data?.data ?? [];
35
+ const poolsById = new Map(pools.map((pool) => [pool.id, pool]));
36
+ const slotsById = new Map(slots.map((slot) => [slot.id, slot]));
37
+ const bookingsById = new Map(bookings.map((booking) => [booking.id, booking]));
38
+ const supplierLabel = resource.supplierId
39
+ ? labelById(suppliersQuery.data?.data ?? [], resource.supplierId)
40
+ : page.resource.noSupplierAssigned;
41
+ return (_jsxs("div", { "data-slot": "resource-detail-page", className: cn("flex flex-col gap-6 p-6", className), children: [_jsx(ResourceDetailHeader, { title: resource.name, deleteConfirmName: resource.name, deleteConfirmTemplate: page.resource.deleteConfirm, deleteErrorMessage: page.resource.deleteFailed, deleting: deleting, confirmAction: confirmAction, onBack: onBack, onDelete: onDelete ? () => onDelete(resource) : undefined, badges: _jsxs(_Fragment, { children: [_jsx(Badge, { variant: "outline", children: m.common.resourceKindLabels[resource.kind] }), _jsx(Badge, { variant: resource.active ? "default" : "secondary", children: resource.active ? m.common.active : m.common.inactive })] }), actions: resource.supplierId && onOpenSupplier ? (_jsxs(Button, { type: "button", variant: "outline", onClick: () => onOpenSupplier(resource.supplierId), children: [_jsx(Users, { "data-icon": "inline-start", "aria-hidden": "true" }), page.common.openSupplier] })) : null }), _jsxs("div", { className: "grid gap-6 md:grid-cols-2", children: [_jsxs(ResourceDetailCard, { title: page.resource.detailsTitle, children: [_jsx(ResourceDetailField, { label: page.common.supplier, children: supplierLabel }), _jsx(ResourceDetailField, { label: page.common.code, children: resource.code ?? page.common.noValue }), _jsx(ResourceDetailField, { label: page.common.capacity, children: resource.capacity ?? page.common.noValue }), _jsx(ResourceDetailField, { label: page.common.created, children: i18n.formatDateTime(resource.createdAt) }), _jsx(ResourceDetailField, { label: page.common.updated, children: i18n.formatDateTime(resource.updatedAt) })] }), resource.notes ? (_jsx(ResourceDetailCard, { title: page.common.notes, children: _jsx("p", { className: "whitespace-pre-wrap", children: resource.notes }) })) : null] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center gap-2", children: [_jsx(Package, { className: "size-4", "aria-hidden": "true" }), _jsx(CardTitle, { children: page.resource.poolMembershipsTitle })] }), _jsx(CardContent, { className: "flex flex-col gap-3 text-sm", children: (poolMembersQuery.data?.data.length ?? 0) === 0 ? (_jsx("p", { className: "text-muted-foreground", children: page.resource.poolMembershipsEmpty })) : (poolMembersQuery.data?.data.map((member) => {
42
+ const pool = poolsById.get(member.poolId);
43
+ return (_jsxs("div", { className: "rounded-md border p-3", children: [_jsx("div", { className: "font-medium", children: pool?.name ?? member.poolId }), _jsxs("div", { className: "text-muted-foreground", children: [page.common.product, ": ", pool?.productId ?? page.common.noValue] })] }, member.id));
44
+ })) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center gap-2", children: [_jsx(Wrench, { className: "size-4", "aria-hidden": "true" }), _jsx(CardTitle, { children: page.resource.assignmentsTitle })] }), _jsx(CardContent, { className: "flex flex-col gap-3 text-sm", children: (assignmentsQuery.data?.data.length ?? 0) === 0 ? (_jsx("p", { className: "text-muted-foreground", children: page.resource.assignmentsEmpty })) : (assignmentsQuery.data?.data.map((assignment) => (_jsx(ResourceAssignmentSummary, { assignment: assignment, pool: undefined, slotLabel: slotsById.get(assignment.slotId)
45
+ ? formatResourceSlotLabel(slotsById.get(assignment.slotId), {
46
+ template: m.common.slotLabel,
47
+ formatDate: i18n.formatDate,
48
+ })
49
+ : assignment.slotId, bookingLabel: bookingsById.get(assignment.bookingId ?? "")?.bookingNumber ??
50
+ assignment.bookingId ??
51
+ page.common.noBooking, noValue: page.common.noValue, onOpenAssignment: onOpenAssignment }, assignment.id)))) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: page.resource.closeoutsTitle }) }), _jsx(CardContent, { className: "flex flex-col gap-3 text-sm", children: (closeoutsQuery.data?.data.length ?? 0) === 0 ? (_jsx("p", { className: "text-muted-foreground", children: page.resource.closeoutsEmpty })) : (closeoutsQuery.data?.data.map((closeout) => (_jsxs("div", { className: "rounded-md border p-3", children: [_jsx("div", { className: "font-medium", children: closeout.dateLocal }), _jsxs("div", { className: "text-muted-foreground", children: [formatDateTimeOrFallback(closeout.startsAt, {
52
+ fallback: page.common.noValue,
53
+ formatDateTime: i18n.formatDateTime,
54
+ }), " ", page.common.to, " ", formatDateTimeOrFallback(closeout.endsAt, {
55
+ fallback: page.common.noValue,
56
+ formatDateTime: i18n.formatDateTime,
57
+ })] }), _jsxs("div", { className: "text-muted-foreground", children: [page.resource.createdBy, ": ", closeout.createdBy ?? page.common.noValue] }), closeout.reason ? (_jsx("div", { className: "mt-2 whitespace-pre-wrap", children: closeout.reason })) : null] }, closeout.id)))) })] })] }));
58
+ }
59
+ export function ResourceDetailSkeleton() {
60
+ return _jsx(BaseResourceDetailSkeleton, { actionCount: 2, detailRows: 5, stackedCards: 3 });
61
+ }
62
+ function useAssignmentsByResource(resourceId) {
63
+ return useAssignments({ resourceId, limit: 25 });
64
+ }
65
+ export function ResourceAssignmentSummary({ assignment, bookingLabel, noValue, onOpenAssignment, pool, slotLabel, }) {
66
+ const i18n = useResourcesUiI18nOrDefault();
67
+ const m = i18n.messages;
68
+ const page = m.detailPages;
69
+ const content = (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Badge, { variant: "outline", children: m.common.assignmentStatusLabels[assignment.status] }), _jsx("span", { children: slotLabel })] }), _jsxs("div", { className: "mt-2 text-muted-foreground", children: [page.common.booking, ": ", bookingLabel] }), pool ? (_jsxs("div", { className: "text-muted-foreground", children: [page.common.pool, ": ", pool.name] })) : null, _jsxs("div", { className: "text-muted-foreground", children: [page.resource.assignedBy, ": ", assignment.assignedBy ?? noValue, " · ", page.resource.released, ":", " ", formatDateTimeOrFallback(assignment.releasedAt, {
70
+ fallback: noValue,
71
+ formatDateTime: i18n.formatDateTime,
72
+ })] }), assignment.notes ? _jsx("div", { className: "mt-2 whitespace-pre-wrap", children: assignment.notes }) : null] }));
73
+ if (!onOpenAssignment) {
74
+ return _jsx("div", { className: "rounded-md border p-3", children: content });
75
+ }
76
+ return (_jsx("button", { type: "button", className: "block w-full rounded-md border p-3 text-left hover:bg-muted/40", onClick: () => onOpenAssignment(assignment.id), children: content }));
77
+ }
@@ -0,0 +1,37 @@
1
+ import type { ReactNode } from "react";
2
+ export type ConfirmAction = (message: string) => boolean;
3
+ export declare const defaultConfirmAction: ConfirmAction;
4
+ export declare function ResourceDetailField({ label, children }: {
5
+ label: string;
6
+ children: ReactNode;
7
+ }): import("react/jsx-runtime").JSX.Element;
8
+ export declare function ResourceDetailState({ className, message, onBack, }: {
9
+ className?: string;
10
+ message: string;
11
+ onBack?: () => void;
12
+ }): import("react/jsx-runtime").JSX.Element;
13
+ export declare function ResourceDetailCard({ children, className, title, }: {
14
+ children: ReactNode;
15
+ className?: string;
16
+ title: string;
17
+ }): import("react/jsx-runtime").JSX.Element;
18
+ export declare function ResourceDetailHeader({ actions, badges, className, confirmAction, deleteConfirmName, deleteConfirmTemplate, deleteErrorMessage, deleting: deletingProp, onBack, onDelete, title, }: {
19
+ actions?: ReactNode;
20
+ badges?: ReactNode;
21
+ className?: string;
22
+ confirmAction?: ConfirmAction;
23
+ deleteConfirmName: string;
24
+ deleteConfirmTemplate: string;
25
+ deleteErrorMessage: string;
26
+ deleting?: boolean;
27
+ onBack?: () => void;
28
+ onDelete?: () => Promise<void> | void;
29
+ title: string;
30
+ }): import("react/jsx-runtime").JSX.Element;
31
+ export declare function ResourceDetailSkeleton({ actionCount, detailRows, showNotes, stackedCards, }: {
32
+ actionCount: number;
33
+ detailRows: number;
34
+ showNotes?: boolean;
35
+ stackedCards?: number;
36
+ }): import("react/jsx-runtime").JSX.Element;
37
+ //# sourceMappingURL=resource-detail-shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-detail-shared.d.ts","sourceRoot":"","sources":["../../src/components/resource-detail-shared.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAA;AAExD,eAAO,MAAM,oBAAoB,EAAE,aACI,CAAA;AAEvC,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,2CAO9F;AAED,wBAAgB,mBAAmB,CAAC,EAClC,SAAS,EACT,OAAO,EACP,MAAM,GACP,EAAE;IACD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CACpB,2CAaA;AAED,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,SAAS,EACT,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;CACd,2CASA;AAED,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,MAAM,EACN,SAAS,EACT,aAAoC,EACpC,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,QAAQ,EAAE,YAAY,EACtB,MAAM,EACN,QAAQ,EACR,KAAK,GACN,EAAE;IACD,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,iBAAiB,EAAE,MAAM,CAAA;IACzB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACrC,KAAK,EAAE,MAAM,CAAA;CACd,2CA6DA;AAED,wBAAgB,sBAAsB,CAAC,EACrC,WAAW,EACX,UAAU,EACV,SAAgB,EAChB,YAAgB,GACjB,EAAE;IACD,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,2CA8EA"}
@@ -0,0 +1,51 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { formatMessage } from "@voyantjs/i18n";
4
+ import { Button, Card, CardContent, CardHeader, CardTitle, cn } from "@voyantjs/ui/components";
5
+ import { Skeleton } from "@voyantjs/ui/components/skeleton";
6
+ import { ArrowLeft, Loader2, Trash2 } from "lucide-react";
7
+ import { useState } from "react";
8
+ import { useResourcesUiMessagesOrDefault } from "../i18n/index.js";
9
+ export const defaultConfirmAction = (message) => globalThis.confirm?.(message) ?? true;
10
+ export function ResourceDetailField({ label, children }) {
11
+ return (_jsxs("div", { className: "grid gap-1 sm:grid-cols-[10rem_minmax(0,1fr)] sm:gap-3", children: [_jsx("span", { className: "text-muted-foreground", children: label }), _jsx("span", { className: "min-w-0 break-words", children: children })] }));
12
+ }
13
+ export function ResourceDetailState({ className, message, onBack, }) {
14
+ const messages = useResourcesUiMessagesOrDefault();
15
+ return (_jsxs("div", { className: cn("flex flex-col items-center justify-center gap-4 py-12", className), children: [_jsx("p", { className: "text-muted-foreground", children: message }), onBack ? (_jsx(Button, { variant: "outline", onClick: onBack, children: messages.detailPages.common.backToResources })) : null] }));
16
+ }
17
+ export function ResourceDetailCard({ children, className, title, }) {
18
+ return (_jsxs(Card, { className: className, children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: title }) }), _jsx(CardContent, { className: "flex flex-col gap-3 text-sm", children: children })] }));
19
+ }
20
+ export function ResourceDetailHeader({ actions, badges, className, confirmAction = defaultConfirmAction, deleteConfirmName, deleteConfirmTemplate, deleteErrorMessage, deleting: deletingProp, onBack, onDelete, title, }) {
21
+ const messages = useResourcesUiMessagesOrDefault();
22
+ const [deletingState, setDeletingState] = useState(false);
23
+ const [deleteError, setDeleteError] = useState(null);
24
+ const deleting = deletingProp || deletingState;
25
+ async function handleDelete() {
26
+ if (!onDelete)
27
+ return;
28
+ setDeleteError(null);
29
+ const confirmed = confirmAction(formatMessage(deleteConfirmTemplate, { name: deleteConfirmName }));
30
+ if (!confirmed)
31
+ return;
32
+ setDeletingState(true);
33
+ try {
34
+ await onDelete();
35
+ }
36
+ catch (error) {
37
+ setDeleteError(error instanceof Error ? error.message : deleteErrorMessage);
38
+ }
39
+ finally {
40
+ setDeletingState(false);
41
+ }
42
+ }
43
+ return (_jsxs("div", { className: cn("flex flex-col gap-3", className), children: [_jsxs("div", { className: "flex flex-col gap-4 md:flex-row md:items-start md:justify-between", children: [_jsxs("div", { className: "flex min-w-0 items-start gap-3", children: [onBack ? (_jsxs(Button, { type: "button", variant: "ghost", size: "icon", onClick: onBack, children: [_jsx(ArrowLeft, { "aria-hidden": "true" }), _jsx("span", { className: "sr-only", children: messages.detailPages.common.backToResources })] })) : null, _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("h1", { className: "truncate text-2xl font-bold tracking-tight", children: title }), badges ? _jsx("div", { className: "mt-1 flex flex-wrap items-center gap-2", children: badges }) : null] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [actions, onDelete ? (_jsxs(Button, { type: "button", variant: "destructive", onClick: () => void handleDelete(), disabled: deleting, children: [deleting ? (_jsx(Loader2, { "data-icon": "inline-start", className: "animate-spin", "aria-hidden": "true" })) : (_jsx(Trash2, { "data-icon": "inline-start", "aria-hidden": "true" })), messages.detailPages.common.delete] })) : null] })] }), deleteError ? _jsx("p", { className: "text-sm text-destructive", children: deleteError }) : null] }));
44
+ }
45
+ export function ResourceDetailSkeleton({ actionCount, detailRows, showNotes = true, stackedCards = 2, }) {
46
+ return (_jsxs("div", { className: "flex flex-col gap-6 p-6", children: [_jsxs("div", { className: "flex items-center gap-4", children: [_jsx(Skeleton, { className: "size-9 rounded-md" }), _jsxs("div", { className: "flex flex-1 flex-col gap-2", children: [_jsx(Skeleton, { className: "h-7 w-56" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Skeleton, { className: "h-5 w-16 rounded-full" }), _jsx(Skeleton, { className: "h-5 w-20 rounded-full" })] })] }), Array.from({ length: actionCount }).map((_, index) => (_jsx(Skeleton
47
+ // biome-ignore lint/suspicious/noArrayIndexKey: stable placeholder
48
+ , { className: "h-9 w-28" }, index)))] }), _jsxs("div", { className: "grid gap-6 md:grid-cols-2", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(Skeleton, { className: "h-5 w-36" }) }), _jsx(CardContent, { className: "flex flex-col gap-3", children: Array.from({ length: detailRows }).map((_, index) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Skeleton, { className: "h-3.5 w-28" }), _jsx(Skeleton, { className: "h-3.5 w-40" })] }, index))) })] }), showNotes ? (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(Skeleton, { className: "h-5 w-16" }) }), _jsxs(CardContent, { className: "flex flex-col gap-2", children: [_jsx(Skeleton, { className: "h-3.5 w-full" }), _jsx(Skeleton, { className: "h-3.5 w-3/4" }), _jsx(Skeleton, { className: "h-3.5 w-2/3" })] })] })) : null] }), Array.from({ length: stackedCards }).map((_, cardIndex) => (_jsxs(Card
49
+ // biome-ignore lint/suspicious/noArrayIndexKey: stable placeholder
50
+ , { children: [_jsxs(CardHeader, { className: "flex flex-row items-center gap-2", children: [_jsx(Skeleton, { className: "size-4" }), _jsx(Skeleton, { className: "h-5 w-36" })] }), _jsx(CardContent, { className: "flex flex-col gap-3", children: Array.from({ length: 2 }).map((_, rowIndex) => (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Skeleton, { className: "h-4 w-48" }), _jsx(Skeleton, { className: "h-3 w-64 max-w-full" })] }, rowIndex))) })] }, cardIndex)))] }));
51
+ }
@@ -0,0 +1,21 @@
1
+ import { type ResourceAllocationRow, type ResourcePoolDetail } from "@voyantjs/resources-react";
2
+ import { type ConfirmAction } from "./resource-detail-shared.js";
3
+ export interface ResourcePoolDetailPageProps {
4
+ id: string;
5
+ className?: string;
6
+ deleting?: boolean;
7
+ onBack?: () => void;
8
+ onDelete?: (pool: ResourcePoolDetail) => Promise<void> | void;
9
+ onOpenAllocation?: (allocationId: string) => void;
10
+ onOpenProduct?: (productId: string) => void;
11
+ onOpenResource?: (resourceId: string) => void;
12
+ onOpenAssignment?: (assignmentId: string) => void;
13
+ confirmAction?: ConfirmAction;
14
+ }
15
+ export declare function ResourcePoolDetailPage({ className, confirmAction, deleting, id, onBack, onDelete, onOpenAllocation, onOpenAssignment, onOpenProduct, onOpenResource, }: ResourcePoolDetailPageProps): import("react/jsx-runtime").JSX.Element;
16
+ export declare function ResourcePoolDetailSkeleton(): import("react/jsx-runtime").JSX.Element;
17
+ export declare function PoolAllocationSummary({ allocation, onOpenAllocation, }: {
18
+ allocation: ResourceAllocationRow;
19
+ onOpenAllocation?: (allocationId: string) => void;
20
+ }): import("react/jsx-runtime").JSX.Element;
21
+ //# sourceMappingURL=resource-pool-detail-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-pool-detail-page.d.ts","sourceRoot":"","sources":["../../src/components/resource-pool-detail-page.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EAQxB,MAAM,2BAA2B,CAAA;AAgBlC,OAAO,EAEL,KAAK,aAAa,EAKnB,MAAM,6BAA6B,CAAA;AAEpC,MAAM,WAAW,2BAA2B;IAC1C,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC7D,gBAAgB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAA;IACjD,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3C,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7C,gBAAgB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAA;IACjD,aAAa,CAAC,EAAE,aAAa,CAAA;CAC9B;AAED,wBAAgB,sBAAsB,CAAC,EACrC,SAAS,EACT,aAAa,EACb,QAAQ,EACR,EAAE,EACF,MAAM,EACN,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,cAAc,GACf,EAAE,2BAA2B,2CA0L7B;AAED,wBAAgB,0BAA0B,4CAEzC;AAED,wBAAgB,qBAAqB,CAAC,EACpC,UAAU,EACV,gBAAgB,GACjB,EAAE;IACD,UAAU,EAAE,qBAAqB,CAAA;IACjC,gBAAgB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAA;CAClD,2CAsCA"}