@voyantjs/availability-ui 0.30.6 → 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 +77 -7
- package/dist/components/availability-page.d.ts +50 -0
- package/dist/components/availability-page.d.ts.map +1 -0
- package/dist/components/availability-page.js +248 -0
- package/dist/components/availability-rule-detail-page.d.ts +240 -0
- package/dist/components/availability-rule-detail-page.d.ts.map +1 -0
- package/dist/components/availability-rule-detail-page.js +71 -0
- package/dist/components/availability-slot-detail-page.d.ts +583 -0
- package/dist/components/availability-slot-detail-page.d.ts.map +1 -0
- package/dist/components/availability-slot-detail-page.js +105 -0
- package/dist/components/availability-start-time-detail-page.d.ts +235 -0
- package/dist/components/availability-start-time-detail-page.d.ts.map +1 -0
- package/dist/components/availability-start-time-detail-page.js +80 -0
- package/dist/i18n/index.d.ts +2 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +1 -0
- package/dist/i18n/provider.d.ts +1504 -0
- package/dist/i18n/provider.d.ts.map +1 -0
- package/dist/i18n/provider.js +88 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/package.json +16 -5
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { formatDateTime, getPickupPointsQueryOptions, getProductQueryOptions, getSlotAssignmentsQueryOptions, getSlotBookingsQueryOptions, getSlotCloseoutsQueryOptions, getSlotPickupsQueryOptions, getSlotQueryOptions, getSlotResourcesQueryOptions, slotStatusVariant, useAvailabilitySlotMutation, useVoyantAvailabilityContext, } from "@voyantjs/availability-react";
|
|
5
|
+
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, cn, } from "@voyantjs/ui/components";
|
|
6
|
+
import { ArrowLeft, CalendarDays, Package, Trash2, Truck, Wrench } from "lucide-react";
|
|
7
|
+
import { useAvailabilityUiI18nOrDefault } from "../i18n/index.js";
|
|
8
|
+
import { getSlotStatusLabel } from "./availability-columns.js";
|
|
9
|
+
import { AvailabilitySlotDetailSkeleton } from "./availability-skeletons.js";
|
|
10
|
+
export function getAvailabilitySlotDetailQueryOptions(client, id) {
|
|
11
|
+
return getSlotQueryOptions(client, id);
|
|
12
|
+
}
|
|
13
|
+
export function getAvailabilitySlotProductQueryOptions(client, productId) {
|
|
14
|
+
return getProductQueryOptions(client, productId);
|
|
15
|
+
}
|
|
16
|
+
export function getAvailabilitySlotPickupsQueryOptions(client, id) {
|
|
17
|
+
return getSlotPickupsQueryOptions(client, id, { limit: 25, offset: 0 });
|
|
18
|
+
}
|
|
19
|
+
export function getAvailabilitySlotPickupPointsQueryOptions(client, productId) {
|
|
20
|
+
return getPickupPointsQueryOptions(client, {
|
|
21
|
+
productId: productId ?? undefined,
|
|
22
|
+
limit: 25,
|
|
23
|
+
offset: 0,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export function getAvailabilitySlotCloseoutsQueryOptions(client, id) {
|
|
27
|
+
return getSlotCloseoutsQueryOptions(client, id, { limit: 25, offset: 0 });
|
|
28
|
+
}
|
|
29
|
+
export function getAvailabilitySlotAssignmentsQueryOptions(client, id) {
|
|
30
|
+
return getSlotAssignmentsQueryOptions(client, id, { limit: 25, offset: 0 });
|
|
31
|
+
}
|
|
32
|
+
export function getAvailabilitySlotResourcesQueryOptions(client) {
|
|
33
|
+
return getSlotResourcesQueryOptions(client, { limit: 25, offset: 0 });
|
|
34
|
+
}
|
|
35
|
+
export function getAvailabilitySlotBookingsQueryOptions(client) {
|
|
36
|
+
return getSlotBookingsQueryOptions(client, { limit: 25, offset: 0 });
|
|
37
|
+
}
|
|
38
|
+
export async function loadAvailabilitySlotDetailPage(queryClient, client, id) {
|
|
39
|
+
const slotData = await queryClient.ensureQueryData(getAvailabilitySlotDetailQueryOptions(client, id));
|
|
40
|
+
return Promise.all([
|
|
41
|
+
Promise.resolve(slotData),
|
|
42
|
+
queryClient.ensureQueryData(getAvailabilitySlotPickupsQueryOptions(client, id)),
|
|
43
|
+
queryClient.ensureQueryData(getAvailabilitySlotCloseoutsQueryOptions(client, id)),
|
|
44
|
+
queryClient.ensureQueryData(getAvailabilitySlotAssignmentsQueryOptions(client, id)),
|
|
45
|
+
queryClient.ensureQueryData(getAvailabilitySlotResourcesQueryOptions(client)),
|
|
46
|
+
queryClient.ensureQueryData(getAvailabilitySlotBookingsQueryOptions(client)),
|
|
47
|
+
queryClient.ensureQueryData(getAvailabilitySlotProductQueryOptions(client, slotData.data.productId)),
|
|
48
|
+
queryClient.ensureQueryData(getAvailabilitySlotPickupPointsQueryOptions(client, slotData.data.productId)),
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
export function AvailabilitySlotDetailPage({ id, className, onBack, onDeleted, onOpenProduct, onOpenStartTime, confirmAction = (message) => globalThis.confirm?.(message) ?? true, }) {
|
|
52
|
+
const client = useVoyantAvailabilityContext();
|
|
53
|
+
const i18n = useAvailabilityUiI18nOrDefault();
|
|
54
|
+
const messages = i18n.messages;
|
|
55
|
+
const detailMessages = messages.details;
|
|
56
|
+
const noValue = detailMessages.noValue;
|
|
57
|
+
const slotMutation = useAvailabilitySlotMutation();
|
|
58
|
+
const { data: slotData, isPending } = useQuery(getAvailabilitySlotDetailQueryOptions(client, id));
|
|
59
|
+
const slot = slotData?.data;
|
|
60
|
+
const productQuery = useQuery({
|
|
61
|
+
...getAvailabilitySlotProductQueryOptions(client, slot?.productId ?? ""),
|
|
62
|
+
enabled: Boolean(slot?.productId),
|
|
63
|
+
});
|
|
64
|
+
const slotPickupsQuery = useQuery(getAvailabilitySlotPickupsQueryOptions(client, id));
|
|
65
|
+
const pickupPointsQuery = useQuery({
|
|
66
|
+
...getAvailabilitySlotPickupPointsQueryOptions(client, slot?.productId ?? ""),
|
|
67
|
+
enabled: Boolean(slot?.productId),
|
|
68
|
+
});
|
|
69
|
+
const closeoutsQuery = useQuery(getAvailabilitySlotCloseoutsQueryOptions(client, id));
|
|
70
|
+
const assignmentsQuery = useQuery(getAvailabilitySlotAssignmentsQueryOptions(client, id));
|
|
71
|
+
const resourcesQuery = useQuery(getAvailabilitySlotResourcesQueryOptions(client));
|
|
72
|
+
const bookingsQuery = useQuery(getAvailabilitySlotBookingsQueryOptions(client));
|
|
73
|
+
if (isPending) {
|
|
74
|
+
return _jsx(AvailabilitySlotDetailSkeleton, {});
|
|
75
|
+
}
|
|
76
|
+
if (!slot) {
|
|
77
|
+
return (_jsx(DetailEmptyState, { message: detailMessages.slot.notFound, backLabel: detailMessages.backToAvailability, onBack: onBack }));
|
|
78
|
+
}
|
|
79
|
+
const pickupPointById = new Map((pickupPointsQuery.data?.data ?? []).map((item) => [item.id, item]));
|
|
80
|
+
const resourceById = new Map((resourcesQuery.data?.data ?? []).map((item) => [item.id, item]));
|
|
81
|
+
const bookingById = new Map((bookingsQuery.data?.data ?? []).map((item) => [item.id, item]));
|
|
82
|
+
async function deleteSlot(currentSlot) {
|
|
83
|
+
if (!confirmAction(detailMessages.slot.deleteConfirm))
|
|
84
|
+
return;
|
|
85
|
+
await slotMutation.remove.mutateAsync(currentSlot.id);
|
|
86
|
+
if (onDeleted)
|
|
87
|
+
onDeleted();
|
|
88
|
+
else
|
|
89
|
+
onBack?.();
|
|
90
|
+
}
|
|
91
|
+
return (_jsxs("div", { className: cn("flex flex-col gap-6 p-6", className), children: [_jsxs("div", { className: "flex flex-col gap-4 md:flex-row md:items-start md:justify-between", children: [_jsxs("div", { className: "flex items-start gap-4", children: [onBack ? (_jsx(Button, { variant: "ghost", size: "icon", onClick: onBack, "aria-label": detailMessages.backToAvailability, children: _jsx(ArrowLeft, { "data-icon": true, "aria-hidden": "true" }) })) : null, _jsxs("div", { className: "flex-1", children: [_jsxs("h1", { className: "text-2xl font-bold tracking-tight", children: [slot.dateLocal, " \u00B7 ", formatDateTime(slot.startsAt)] }), _jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-2", children: [_jsx(Badge, { variant: slotStatusVariant[slot.status], children: getSlotStatusLabel(slot.status, messages) }), _jsx(Badge, { variant: "outline", children: slot.timezone })] })] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [slot.productId && onOpenProduct ? (_jsxs(Button, { variant: "outline", onClick: () => onOpenProduct(slot.productId), children: [_jsx(Package, { "data-icon": "inline-start", "aria-hidden": "true" }), detailMessages.openProduct] })) : null, _jsxs(Button, { variant: "destructive", onClick: () => void deleteSlot(slot), disabled: slotMutation.remove.isPending, children: [_jsx(Trash2, { "data-icon": "inline-start", "aria-hidden": "true" }), detailMessages.delete] })] })] }), _jsxs("div", { className: "grid gap-6 md:grid-cols-2", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: detailMessages.slot.detailsTitle }) }), _jsxs(CardContent, { className: "flex flex-col gap-3 text-sm", children: [_jsx(DetailLine, { label: messages.productLabel, children: productQuery.data?.data.name ?? slot.productId }), _jsx(DetailLine, { label: detailMessages.slot.ruleLabel, children: slot.availabilityRuleId ?? noValue }), _jsx(DetailLine, { label: detailMessages.slot.startTimeIdLabel, children: slot.startTimeId && onOpenStartTime ? (_jsx(Button, { variant: "link", className: "h-auto p-0", onClick: () => onOpenStartTime(slot.startTimeId ?? ""), children: slot.startTimeId })) : ((slot.startTimeId ?? noValue)) }), _jsx(DetailLine, { label: detailMessages.slot.endsAtLabel, children: formatDateTime(slot.endsAt) }), _jsx(DetailLine, { label: detailMessages.slot.unlimitedLabel, children: slot.unlimited ? detailMessages.yes : detailMessages.no }), _jsx(DetailLine, { label: detailMessages.slot.pastCutoffLabel, children: slot.pastCutoff ? detailMessages.yes : detailMessages.no }), _jsx(DetailLine, { label: detailMessages.slot.tooEarlyLabel, children: slot.tooEarly ? detailMessages.yes : detailMessages.no })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: detailMessages.slot.capacityStateTitle }) }), _jsxs(CardContent, { className: "flex flex-col gap-3 text-sm", children: [_jsx(DetailLine, { label: detailMessages.slot.initialPaxLabel, children: slot.initialPax ?? noValue }), _jsx(DetailLine, { label: messages.remainingPaxLabel, children: slot.remainingPax ?? noValue }), _jsx(DetailLine, { label: detailMessages.slot.initialPickupsLabel, children: slot.initialPickups ?? noValue }), _jsx(DetailLine, { label: detailMessages.slot.remainingPickupsLabel, children: slot.remainingPickups ?? noValue }), _jsx(DetailLine, { label: detailMessages.slot.remainingResourcesLabel, children: slot.remainingResources ?? noValue }), _jsx(DetailLine, { label: detailMessages.createdLabel, children: i18n.formatDateTime(slot.createdAt) }), _jsx(DetailLine, { label: detailMessages.updatedLabel, children: i18n.formatDateTime(slot.updatedAt) })] })] })] }), slot.notes ? (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: detailMessages.notesTitle }) }), _jsx(CardContent, { className: "text-sm whitespace-pre-wrap", children: slot.notes })] })) : null, _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center gap-2", children: [_jsx(Truck, { className: "size-4", "aria-hidden": "true" }), _jsx(CardTitle, { children: detailMessages.slot.pickupCapacityTitle })] }), _jsx(CardContent, { className: "flex flex-col gap-3 text-sm", children: (slotPickupsQuery.data?.data.length ?? 0) === 0 ? (_jsx("p", { className: "text-muted-foreground", children: detailMessages.slot.pickupCapacityEmpty })) : (slotPickupsQuery.data?.data.map((pickup) => {
|
|
92
|
+
const point = pickupPointById.get(pickup.pickupPointId);
|
|
93
|
+
return (_jsxs("div", { className: "rounded-md border p-3", children: [_jsx("div", { className: "font-medium", children: point?.name ?? pickup.pickupPointId }), _jsx("div", { className: "text-muted-foreground", children: point?.locationText ?? detailMessages.slot.noLocationText }), _jsxs("div", { className: "mt-2", children: [detailMessages.slot.initialLabel, ": ", pickup.initialCapacity ?? noValue, " \u00B7", " ", detailMessages.slot.remainingLabel, ": ", pickup.remainingCapacity ?? noValue] })] }, pickup.id));
|
|
94
|
+
})) })] }), _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: detailMessages.slot.resourceAssignmentsTitle })] }), _jsx(CardContent, { className: "flex flex-col gap-3 text-sm", children: (assignmentsQuery.data?.data.length ?? 0) === 0 ? (_jsx("p", { className: "text-muted-foreground", children: detailMessages.slot.resourceAssignmentsEmpty })) : (assignmentsQuery.data?.data.map((assignment) => (_jsxs("div", { className: "rounded-md border p-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Badge, { variant: "outline", className: "capitalize", children: assignment.status }), _jsx("span", { children: resourceById.get(assignment.resourceId ?? "")?.name ??
|
|
95
|
+
assignment.resourceId ??
|
|
96
|
+
detailMessages.slot.unassignedResource })] }), _jsxs("div", { className: "mt-2 text-muted-foreground", children: [detailMessages.slot.bookingLabel, ":", " ", bookingById.get(assignment.bookingId ?? "")?.bookingNumber ??
|
|
97
|
+
assignment.bookingId ??
|
|
98
|
+
noValue] }), _jsxs("div", { className: "text-muted-foreground", children: [detailMessages.slot.poolLabel, ": ", assignment.poolId ?? noValue, " \u00B7", " ", detailMessages.slot.releasedLabel, ": ", formatDateTime(assignment.releasedAt)] }), assignment.notes ? (_jsx("div", { className: "mt-2 whitespace-pre-wrap", children: assignment.notes })) : null] }, assignment.id)))) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center gap-2", children: [_jsx(CalendarDays, { className: "size-4", "aria-hidden": "true" }), _jsx(CardTitle, { children: detailMessages.slot.relatedCloseoutsTitle })] }), _jsx(CardContent, { className: "flex flex-col gap-3 text-sm", children: (closeoutsQuery.data?.data.length ?? 0) === 0 ? (_jsx("p", { className: "text-muted-foreground", children: detailMessages.slot.relatedCloseoutsEmpty })) : (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: [detailMessages.slot.createdByLabel, ": ", closeout.createdBy ?? noValue] }), closeout.reason ? (_jsx("div", { className: "mt-2 whitespace-pre-wrap", children: closeout.reason })) : null] }, closeout.id)))) })] })] }));
|
|
99
|
+
}
|
|
100
|
+
function DetailLine({ label, children }) {
|
|
101
|
+
return (_jsxs("div", { children: [_jsxs("span", { className: "text-muted-foreground", children: [label, ":"] }), " ", _jsx("span", { children: children })] }));
|
|
102
|
+
}
|
|
103
|
+
function DetailEmptyState({ message, backLabel, onBack, }) {
|
|
104
|
+
return (_jsxs("div", { className: "flex flex-col items-center justify-center gap-4 py-12", children: [_jsx("p", { className: "text-muted-foreground", children: message }), onBack ? (_jsx(Button, { variant: "outline", onClick: onBack, children: backLabel })) : null] }));
|
|
105
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import type { QueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { type VoyantAvailabilityContextValue } from "@voyantjs/availability-react";
|
|
3
|
+
export interface AvailabilityStartTimeDetailPageProps {
|
|
4
|
+
id: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
onBack?: () => void;
|
|
7
|
+
onDeleted?: () => void;
|
|
8
|
+
onOpenProduct?: (productId: string) => void;
|
|
9
|
+
onOpenSlot?: (slotId: string) => void;
|
|
10
|
+
confirmAction?: (message: string) => boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function getAvailabilityStartTimeDetailQueryOptions(client: VoyantAvailabilityContextValue, id: string | null | undefined): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<{
|
|
13
|
+
data: {
|
|
14
|
+
id: string;
|
|
15
|
+
productId: string;
|
|
16
|
+
optionId: string | null;
|
|
17
|
+
facilityId: string | null;
|
|
18
|
+
label: string | null;
|
|
19
|
+
startTimeLocal: string;
|
|
20
|
+
durationMinutes: number | null;
|
|
21
|
+
sortOrder: number;
|
|
22
|
+
active: boolean;
|
|
23
|
+
productName?: string | null | undefined;
|
|
24
|
+
createdAt?: string | undefined;
|
|
25
|
+
updatedAt?: string | undefined;
|
|
26
|
+
};
|
|
27
|
+
}, Error, {
|
|
28
|
+
data: {
|
|
29
|
+
id: string;
|
|
30
|
+
productId: string;
|
|
31
|
+
optionId: string | null;
|
|
32
|
+
facilityId: string | null;
|
|
33
|
+
label: string | null;
|
|
34
|
+
startTimeLocal: string;
|
|
35
|
+
durationMinutes: number | null;
|
|
36
|
+
sortOrder: number;
|
|
37
|
+
active: boolean;
|
|
38
|
+
productName?: string | null | undefined;
|
|
39
|
+
createdAt?: string | undefined;
|
|
40
|
+
updatedAt?: string | undefined;
|
|
41
|
+
};
|
|
42
|
+
}, readonly ["voyant", "availability", "start-times", "detail", string]>, "queryFn"> & {
|
|
43
|
+
queryFn?: import("@tanstack/react-query").QueryFunction<{
|
|
44
|
+
data: {
|
|
45
|
+
id: string;
|
|
46
|
+
productId: string;
|
|
47
|
+
optionId: string | null;
|
|
48
|
+
facilityId: string | null;
|
|
49
|
+
label: string | null;
|
|
50
|
+
startTimeLocal: string;
|
|
51
|
+
durationMinutes: number | null;
|
|
52
|
+
sortOrder: number;
|
|
53
|
+
active: boolean;
|
|
54
|
+
productName?: string | null | undefined;
|
|
55
|
+
createdAt?: string | undefined;
|
|
56
|
+
updatedAt?: string | undefined;
|
|
57
|
+
};
|
|
58
|
+
}, readonly ["voyant", "availability", "start-times", "detail", string], never> | undefined;
|
|
59
|
+
} & {
|
|
60
|
+
queryKey: readonly ["voyant", "availability", "start-times", "detail", string] & {
|
|
61
|
+
[dataTagSymbol]: {
|
|
62
|
+
data: {
|
|
63
|
+
id: string;
|
|
64
|
+
productId: string;
|
|
65
|
+
optionId: string | null;
|
|
66
|
+
facilityId: string | null;
|
|
67
|
+
label: string | null;
|
|
68
|
+
startTimeLocal: string;
|
|
69
|
+
durationMinutes: number | null;
|
|
70
|
+
sortOrder: number;
|
|
71
|
+
active: boolean;
|
|
72
|
+
productName?: string | null | undefined;
|
|
73
|
+
createdAt?: string | undefined;
|
|
74
|
+
updatedAt?: string | undefined;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
[dataTagErrorSymbol]: Error;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
export declare function getAvailabilityStartTimeSlotsQueryOptions(client: VoyantAvailabilityContextValue, id: string | null | undefined): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<{
|
|
81
|
+
data: {
|
|
82
|
+
id: string;
|
|
83
|
+
productId: string;
|
|
84
|
+
itineraryId: string | null;
|
|
85
|
+
optionId: string | null;
|
|
86
|
+
facilityId: string | null;
|
|
87
|
+
availabilityRuleId: string | null;
|
|
88
|
+
startTimeId: string | null;
|
|
89
|
+
dateLocal: string;
|
|
90
|
+
startsAt: string;
|
|
91
|
+
endsAt: string | null;
|
|
92
|
+
timezone: string;
|
|
93
|
+
status: "cancelled" | "open" | "closed" | "sold_out";
|
|
94
|
+
unlimited: boolean;
|
|
95
|
+
initialPax: number | null;
|
|
96
|
+
remainingPax: number | null;
|
|
97
|
+
nights: number | null;
|
|
98
|
+
days: number | null;
|
|
99
|
+
notes: string | null;
|
|
100
|
+
productName?: string | null | undefined;
|
|
101
|
+
}[];
|
|
102
|
+
total: number;
|
|
103
|
+
limit: number;
|
|
104
|
+
offset: number;
|
|
105
|
+
}, Error, {
|
|
106
|
+
data: {
|
|
107
|
+
id: string;
|
|
108
|
+
productId: string;
|
|
109
|
+
itineraryId: string | null;
|
|
110
|
+
optionId: string | null;
|
|
111
|
+
facilityId: string | null;
|
|
112
|
+
availabilityRuleId: string | null;
|
|
113
|
+
startTimeId: string | null;
|
|
114
|
+
dateLocal: string;
|
|
115
|
+
startsAt: string;
|
|
116
|
+
endsAt: string | null;
|
|
117
|
+
timezone: string;
|
|
118
|
+
status: "cancelled" | "open" | "closed" | "sold_out";
|
|
119
|
+
unlimited: boolean;
|
|
120
|
+
initialPax: number | null;
|
|
121
|
+
remainingPax: number | null;
|
|
122
|
+
nights: number | null;
|
|
123
|
+
days: number | null;
|
|
124
|
+
notes: string | null;
|
|
125
|
+
productName?: string | null | undefined;
|
|
126
|
+
}[];
|
|
127
|
+
total: number;
|
|
128
|
+
limit: number;
|
|
129
|
+
offset: number;
|
|
130
|
+
}, readonly ["voyant", "availability", "slots", "list", import("@voyantjs/availability-react/query-keys").AvailabilitySlotsListFilters]>, "queryFn"> & {
|
|
131
|
+
queryFn?: import("@tanstack/react-query").QueryFunction<{
|
|
132
|
+
data: {
|
|
133
|
+
id: string;
|
|
134
|
+
productId: string;
|
|
135
|
+
itineraryId: string | null;
|
|
136
|
+
optionId: string | null;
|
|
137
|
+
facilityId: string | null;
|
|
138
|
+
availabilityRuleId: string | null;
|
|
139
|
+
startTimeId: string | null;
|
|
140
|
+
dateLocal: string;
|
|
141
|
+
startsAt: string;
|
|
142
|
+
endsAt: string | null;
|
|
143
|
+
timezone: string;
|
|
144
|
+
status: "cancelled" | "open" | "closed" | "sold_out";
|
|
145
|
+
unlimited: boolean;
|
|
146
|
+
initialPax: number | null;
|
|
147
|
+
remainingPax: number | null;
|
|
148
|
+
nights: number | null;
|
|
149
|
+
days: number | null;
|
|
150
|
+
notes: string | null;
|
|
151
|
+
productName?: string | null | undefined;
|
|
152
|
+
}[];
|
|
153
|
+
total: number;
|
|
154
|
+
limit: number;
|
|
155
|
+
offset: number;
|
|
156
|
+
}, readonly ["voyant", "availability", "slots", "list", import("@voyantjs/availability-react/query-keys").AvailabilitySlotsListFilters], never> | undefined;
|
|
157
|
+
} & {
|
|
158
|
+
queryKey: readonly ["voyant", "availability", "slots", "list", import("@voyantjs/availability-react/query-keys").AvailabilitySlotsListFilters] & {
|
|
159
|
+
[dataTagSymbol]: {
|
|
160
|
+
data: {
|
|
161
|
+
id: string;
|
|
162
|
+
productId: string;
|
|
163
|
+
itineraryId: string | null;
|
|
164
|
+
optionId: string | null;
|
|
165
|
+
facilityId: string | null;
|
|
166
|
+
availabilityRuleId: string | null;
|
|
167
|
+
startTimeId: string | null;
|
|
168
|
+
dateLocal: string;
|
|
169
|
+
startsAt: string;
|
|
170
|
+
endsAt: string | null;
|
|
171
|
+
timezone: string;
|
|
172
|
+
status: "cancelled" | "open" | "closed" | "sold_out";
|
|
173
|
+
unlimited: boolean;
|
|
174
|
+
initialPax: number | null;
|
|
175
|
+
remainingPax: number | null;
|
|
176
|
+
nights: number | null;
|
|
177
|
+
days: number | null;
|
|
178
|
+
notes: string | null;
|
|
179
|
+
productName?: string | null | undefined;
|
|
180
|
+
}[];
|
|
181
|
+
total: number;
|
|
182
|
+
limit: number;
|
|
183
|
+
offset: number;
|
|
184
|
+
};
|
|
185
|
+
[dataTagErrorSymbol]: Error;
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
export declare function loadAvailabilityStartTimeDetailPage(queryClient: QueryClient, client: VoyantAvailabilityContextValue, id: string): Promise<[{
|
|
189
|
+
data: {
|
|
190
|
+
id: string;
|
|
191
|
+
productId: string;
|
|
192
|
+
optionId: string | null;
|
|
193
|
+
facilityId: string | null;
|
|
194
|
+
label: string | null;
|
|
195
|
+
startTimeLocal: string;
|
|
196
|
+
durationMinutes: number | null;
|
|
197
|
+
sortOrder: number;
|
|
198
|
+
active: boolean;
|
|
199
|
+
productName?: string | null | undefined;
|
|
200
|
+
createdAt?: string | undefined;
|
|
201
|
+
updatedAt?: string | undefined;
|
|
202
|
+
};
|
|
203
|
+
}, {
|
|
204
|
+
data: {
|
|
205
|
+
id: string;
|
|
206
|
+
productId: string;
|
|
207
|
+
itineraryId: string | null;
|
|
208
|
+
optionId: string | null;
|
|
209
|
+
facilityId: string | null;
|
|
210
|
+
availabilityRuleId: string | null;
|
|
211
|
+
startTimeId: string | null;
|
|
212
|
+
dateLocal: string;
|
|
213
|
+
startsAt: string;
|
|
214
|
+
endsAt: string | null;
|
|
215
|
+
timezone: string;
|
|
216
|
+
status: "cancelled" | "open" | "closed" | "sold_out";
|
|
217
|
+
unlimited: boolean;
|
|
218
|
+
initialPax: number | null;
|
|
219
|
+
remainingPax: number | null;
|
|
220
|
+
nights: number | null;
|
|
221
|
+
days: number | null;
|
|
222
|
+
notes: string | null;
|
|
223
|
+
productName?: string | null | undefined;
|
|
224
|
+
}[];
|
|
225
|
+
total: number;
|
|
226
|
+
limit: number;
|
|
227
|
+
offset: number;
|
|
228
|
+
}, {
|
|
229
|
+
data: {
|
|
230
|
+
id: string;
|
|
231
|
+
name: string;
|
|
232
|
+
};
|
|
233
|
+
}]>;
|
|
234
|
+
export declare function AvailabilityStartTimeDetailPage({ id, className, onBack, onDeleted, onOpenProduct, onOpenSlot, confirmAction, }: AvailabilityStartTimeDetailPageProps): import("react/jsx-runtime").JSX.Element;
|
|
235
|
+
//# sourceMappingURL=availability-start-time-detail-page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"availability-start-time-detail-page.d.ts","sourceRoot":"","sources":["../../src/components/availability-start-time-detail-page.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,EAYL,KAAK,8BAA8B,EACpC,MAAM,8BAA8B,CAAA;AAyBrC,MAAM,WAAW,oCAAoC;IACnD,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3C,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAA;CAC7C;AAED,wBAAgB,0CAA0C,CACxD,MAAM,EAAE,8BAA8B,EACtC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAa9B;AAED,wBAAgB,yCAAyC,CACvD,MAAM,EAAE,8BAA8B,EACtC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAG9B;AAED,wBAAsB,mCAAmC,CACvD,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,8BAA8B,EACtC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAWX;AAED,wBAAgB,+BAA+B,CAAC,EAC9C,EAAE,EACF,SAAS,EACT,MAAM,EACN,SAAS,EACT,aAAa,EACb,UAAU,EACV,aAAkE,GACnE,EAAE,oCAAoC,2CA+HtC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { queryOptions, useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { availabilityQueryKeys, availabilityStartTimeRecordSchema, fetchWithValidation, formatDateTime, getProductQueryOptions, getSlotsQueryOptions, singleEnvelope, useAvailabilityStartTimeMutation, useVoyantAvailabilityContext, } from "@voyantjs/availability-react";
|
|
5
|
+
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, cn, } from "@voyantjs/ui/components";
|
|
6
|
+
import { ArrowLeft, Clock3, Package, Trash2 } from "lucide-react";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { useAvailabilityUiI18nOrDefault } from "../i18n/index.js";
|
|
9
|
+
import { getSlotStatusLabel } from "./availability-columns.js";
|
|
10
|
+
import { AvailabilityStartTimeDetailSkeleton } from "./availability-skeletons.js";
|
|
11
|
+
const availabilityStartTimeDetailResponse = singleEnvelope(availabilityStartTimeRecordSchema.extend({
|
|
12
|
+
createdAt: z.string().optional(),
|
|
13
|
+
updatedAt: z.string().optional(),
|
|
14
|
+
}));
|
|
15
|
+
export function getAvailabilityStartTimeDetailQueryOptions(client, id) {
|
|
16
|
+
return queryOptions({
|
|
17
|
+
queryKey: availabilityQueryKeys.startTimeDetail(id ?? ""),
|
|
18
|
+
queryFn: async () => {
|
|
19
|
+
if (!id)
|
|
20
|
+
throw new Error("getAvailabilityStartTimeDetailQueryOptions requires an id");
|
|
21
|
+
return fetchWithValidation(`/v1/availability/start-times/${id}`, availabilityStartTimeDetailResponse, client);
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export function getAvailabilityStartTimeSlotsQueryOptions(client, id) {
|
|
26
|
+
return getSlotsQueryOptions(client, { startTimeId: id ?? undefined, limit: 25, offset: 0 });
|
|
27
|
+
}
|
|
28
|
+
export async function loadAvailabilityStartTimeDetailPage(queryClient, client, id) {
|
|
29
|
+
const startTimeData = await queryClient.ensureQueryData(getAvailabilityStartTimeDetailQueryOptions(client, id));
|
|
30
|
+
return Promise.all([
|
|
31
|
+
Promise.resolve(startTimeData),
|
|
32
|
+
queryClient.ensureQueryData(getAvailabilityStartTimeSlotsQueryOptions(client, id)),
|
|
33
|
+
queryClient.ensureQueryData(getProductQueryOptions(client, startTimeData.data.productId)),
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
export function AvailabilityStartTimeDetailPage({ id, className, onBack, onDeleted, onOpenProduct, onOpenSlot, confirmAction = (message) => globalThis.confirm?.(message) ?? true, }) {
|
|
37
|
+
const client = useVoyantAvailabilityContext();
|
|
38
|
+
const i18n = useAvailabilityUiI18nOrDefault();
|
|
39
|
+
const messages = i18n.messages;
|
|
40
|
+
const detailMessages = messages.details;
|
|
41
|
+
const noValue = detailMessages.noValue;
|
|
42
|
+
const startTimeMutation = useAvailabilityStartTimeMutation();
|
|
43
|
+
const { data: startTimeData, isPending } = useQuery(getAvailabilityStartTimeDetailQueryOptions(client, id));
|
|
44
|
+
const startTime = startTimeData?.data;
|
|
45
|
+
const productQuery = useQuery({
|
|
46
|
+
...getProductQueryOptions(client, startTime?.productId ?? ""),
|
|
47
|
+
enabled: Boolean(startTime?.productId),
|
|
48
|
+
});
|
|
49
|
+
const slotsQuery = useQuery(getAvailabilityStartTimeSlotsQueryOptions(client, id));
|
|
50
|
+
if (isPending) {
|
|
51
|
+
return _jsx(AvailabilityStartTimeDetailSkeleton, {});
|
|
52
|
+
}
|
|
53
|
+
if (!startTime) {
|
|
54
|
+
return (_jsx(DetailEmptyState, { message: detailMessages.startTime.notFound, backLabel: detailMessages.backToAvailability, onBack: onBack }));
|
|
55
|
+
}
|
|
56
|
+
async function deleteStartTime(currentStartTime) {
|
|
57
|
+
if (!confirmAction(detailMessages.startTime.deleteConfirm))
|
|
58
|
+
return;
|
|
59
|
+
await startTimeMutation.remove.mutateAsync(currentStartTime.id);
|
|
60
|
+
if (onDeleted)
|
|
61
|
+
onDeleted();
|
|
62
|
+
else
|
|
63
|
+
onBack?.();
|
|
64
|
+
}
|
|
65
|
+
return (_jsxs("div", { className: cn("flex flex-col gap-6 p-6", className), children: [_jsxs("div", { className: "flex flex-col gap-4 md:flex-row md:items-start md:justify-between", children: [_jsxs("div", { className: "flex items-start gap-4", children: [onBack ? (_jsx(Button, { variant: "ghost", size: "icon", onClick: onBack, "aria-label": detailMessages.backToAvailability, children: _jsx(ArrowLeft, { "data-icon": true, "aria-hidden": "true" }) })) : null, _jsxs("div", { className: "flex-1", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: startTime.label ?? detailMessages.startTime.fallbackTitle }), _jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-2", children: [_jsx(Badge, { variant: "outline", children: startTime.startTimeLocal }), _jsx(Badge, { variant: startTime.active ? "default" : "secondary", children: startTime.active ? messages.statusActive : messages.statusInactive })] })] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [onOpenProduct ? (_jsxs(Button, { variant: "outline", onClick: () => onOpenProduct(startTime.productId), children: [_jsx(Package, { "data-icon": "inline-start", "aria-hidden": "true" }), detailMessages.openProduct] })) : null, _jsxs(Button, { variant: "destructive", onClick: () => void deleteStartTime(startTime), disabled: startTimeMutation.remove.isPending, children: [_jsx(Trash2, { "data-icon": "inline-start", "aria-hidden": "true" }), detailMessages.delete] })] })] }), _jsxs("div", { className: "grid gap-6 md:grid-cols-2", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: detailMessages.startTime.detailsTitle }) }), _jsxs(CardContent, { className: "flex flex-col gap-3 text-sm", children: [_jsx(DetailLine, { label: messages.productLabel, children: productQuery.data?.data.name ?? startTime.productId }), _jsx(DetailLine, { label: messages.labelLabel, children: startTime.label ?? noValue }), _jsx(DetailLine, { label: messages.durationLabel, children: startTime.durationMinutes == null ? noValue : `${startTime.durationMinutes} min` }), _jsx(DetailLine, { label: detailMessages.startTime.sortOrderLabel, children: startTime.sortOrder }), _jsx(DetailLine, { label: detailMessages.createdLabel, children: formatOptionalDateTime(startTime.createdAt, noValue, i18n.formatDateTime) }), _jsx(DetailLine, { label: detailMessages.updatedLabel, children: formatOptionalDateTime(startTime.updatedAt, noValue, i18n.formatDateTime) })] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center gap-2", children: [_jsx(Clock3, { className: "size-4", "aria-hidden": "true" }), _jsx(CardTitle, { children: detailMessages.startTime.generatedSlotsTitle })] }), _jsx(CardContent, { className: "flex flex-col gap-3 text-sm", children: (slotsQuery.data?.data.length ?? 0) === 0 ? (_jsx("p", { className: "text-muted-foreground", children: detailMessages.startTime.generatedSlotsEmpty })) : (slotsQuery.data?.data.map((slot) => (_jsx(SlotButton, { slot: slot, onOpenSlot: onOpenSlot }, slot.id)))) })] })] })] }));
|
|
66
|
+
}
|
|
67
|
+
function SlotButton({ slot, onOpenSlot, }) {
|
|
68
|
+
const messages = useAvailabilityUiI18nOrDefault().messages;
|
|
69
|
+
const noValue = messages.details.noValue;
|
|
70
|
+
return (_jsxs("button", { type: "button", className: "block w-full rounded-md border p-3 text-left hover:bg-muted/40", onClick: () => onOpenSlot?.(slot.id), children: [_jsxs("div", { className: "font-medium", children: [slot.dateLocal, " \u00B7 ", formatDateTime(slot.startsAt)] }), _jsxs("div", { className: "text-muted-foreground", children: [messages.statusLabel, ": ", getSlotStatusLabel(slot.status, messages), " \u00B7", " ", messages.remainingPaxLabel, ": ", slot.remainingPax ?? noValue] })] }));
|
|
71
|
+
}
|
|
72
|
+
function formatOptionalDateTime(value, fallback, formatter) {
|
|
73
|
+
return value ? formatter(value) : fallback;
|
|
74
|
+
}
|
|
75
|
+
function DetailLine({ label, children }) {
|
|
76
|
+
return (_jsxs("div", { children: [_jsxs("span", { className: "text-muted-foreground", children: [label, ":"] }), " ", _jsx("span", { children: children })] }));
|
|
77
|
+
}
|
|
78
|
+
function DetailEmptyState({ message, backLabel, onBack, }) {
|
|
79
|
+
return (_jsxs("div", { className: "flex flex-col items-center justify-center gap-4 py-12", children: [_jsx("p", { className: "text-muted-foreground", children: message }), onBack ? (_jsx(Button, { variant: "outline", onClick: onBack, children: backLabel })) : null] }));
|
|
80
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { type AvailabilityUiMessageOverrides, type AvailabilityUiMessages, AvailabilityUiMessagesProvider, availabilityUiEn, availabilityUiMessageDefinitions, availabilityUiRo, getAvailabilityUiI18n, resolveAvailabilityUiMessages, useAvailabilityUiI18n, useAvailabilityUiI18nOrDefault, useAvailabilityUiMessages, useAvailabilityUiMessagesOrDefault, } from "./provider.js";
|
|
2
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/i18n/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,8BAA8B,EACnC,KAAK,sBAAsB,EAC3B,8BAA8B,EAC9B,gBAAgB,EAChB,gCAAgC,EAChC,gBAAgB,EAChB,qBAAqB,EACrB,6BAA6B,EAC7B,qBAAqB,EACrB,8BAA8B,EAC9B,yBAAyB,EACzB,kCAAkC,GACnC,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AvailabilityUiMessagesProvider, availabilityUiEn, availabilityUiMessageDefinitions, availabilityUiRo, getAvailabilityUiI18n, resolveAvailabilityUiMessages, useAvailabilityUiI18n, useAvailabilityUiI18nOrDefault, useAvailabilityUiMessages, useAvailabilityUiMessagesOrDefault, } from "./provider.js";
|