@voyantjs/resources-ui 0.30.7 → 0.31.0
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.
- package/README.md +41 -0
- package/dist/components/resource-allocation-detail-page.d.ts +15 -0
- package/dist/components/resource-allocation-detail-page.d.ts.map +1 -0
- package/dist/components/resource-allocation-detail-page.js +45 -0
- package/dist/components/resource-assignment-detail-page.d.ts +15 -0
- package/dist/components/resource-assignment-detail-page.d.ts.map +1 -0
- package/dist/components/resource-assignment-detail-page.js +47 -0
- package/dist/components/resource-detail-data.d.ts +34 -0
- package/dist/components/resource-detail-data.d.ts.map +1 -0
- package/dist/components/resource-detail-data.js +90 -0
- package/dist/components/resource-detail-page.d.ts +23 -0
- package/dist/components/resource-detail-page.d.ts.map +1 -0
- package/dist/components/resource-detail-page.js +77 -0
- package/dist/components/resource-detail-shared.d.ts +37 -0
- package/dist/components/resource-detail-shared.d.ts.map +1 -0
- package/dist/components/resource-detail-shared.js +51 -0
- package/dist/components/resource-pool-detail-page.d.ts +21 -0
- package/dist/components/resource-pool-detail-page.d.ts.map +1 -0
- package/dist/components/resource-pool-detail-page.js +65 -0
- package/dist/components/resources-page.d.ts +55 -0
- package/dist/components/resources-page.d.ts.map +1 -0
- package/dist/components/resources-page.js +174 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +114 -0
- package/dist/i18n/messages.d.ts +114 -0
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/i18n/ro.d.ts.map +1 -1
- package/dist/i18n/ro.js +114 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- 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"}
|