@voyantjs/availability-react 0.105.2 → 0.107.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 +161 -1
- package/dist/admin/availability-index-host.d.ts +12 -0
- package/dist/admin/availability-index-host.d.ts.map +1 -0
- package/dist/admin/availability-index-host.js +125 -0
- package/dist/admin/availability-page-data.d.ts +9 -0
- package/dist/admin/availability-page-data.d.ts.map +1 -0
- package/dist/admin/availability-page-data.js +25 -0
- package/dist/admin/index.d.ts +69 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +73 -0
- package/dist/admin/option-resource-templates-panel.d.ts +22 -0
- package/dist/admin/option-resource-templates-panel.d.ts.map +1 -0
- package/dist/admin/option-resource-templates-panel.js +251 -0
- package/dist/admin/rule-detail-host.d.ts +14 -0
- package/dist/admin/rule-detail-host.d.ts.map +1 -0
- package/dist/admin/rule-detail-host.js +27 -0
- package/dist/admin/slot-detail-host.d.ts +29 -0
- package/dist/admin/slot-detail-host.d.ts.map +1 -0
- package/dist/admin/slot-detail-host.js +110 -0
- package/dist/admin/start-time-detail-host.d.ts +15 -0
- package/dist/admin/start-time-detail-host.d.ts.map +1 -0
- package/dist/admin/start-time-detail-host.js +37 -0
- package/dist/components/availability-columns.d.ts +42 -0
- package/dist/components/availability-columns.d.ts.map +1 -0
- package/dist/components/availability-columns.js +182 -0
- package/dist/components/availability-dialogs.d.ts +236 -0
- package/dist/components/availability-dialogs.d.ts.map +1 -0
- package/dist/components/availability-dialogs.js +369 -0
- package/dist/components/availability-overview.d.ts +54 -0
- package/dist/components/availability-overview.d.ts.map +1 -0
- package/dist/components/availability-overview.js +50 -0
- package/dist/components/availability-page.d.ts +32 -0
- package/dist/components/availability-page.d.ts.map +1 -0
- package/dist/components/availability-page.js +128 -0
- package/dist/components/availability-rule-detail-page.d.ts +251 -0
- package/dist/components/availability-rule-detail-page.d.ts.map +1 -0
- package/dist/components/availability-rule-detail-page.js +74 -0
- package/dist/components/availability-section-header.d.ts +8 -0
- package/dist/components/availability-section-header.d.ts.map +1 -0
- package/dist/components/availability-section-header.js +7 -0
- package/dist/components/availability-skeletons.d.ts +6 -0
- package/dist/components/availability-skeletons.d.ts.map +1 -0
- package/dist/components/availability-skeletons.js +34 -0
- package/dist/components/availability-slot-detail-page.d.ts +974 -0
- package/dist/components/availability-slot-detail-page.d.ts.map +1 -0
- package/dist/components/availability-slot-detail-page.js +383 -0
- package/dist/components/availability-start-time-detail-page.d.ts +246 -0
- package/dist/components/availability-start-time-detail-page.d.ts.map +1 -0
- package/dist/components/availability-start-time-detail-page.js +83 -0
- package/dist/components/availability-tabs.d.ts +152 -0
- package/dist/components/availability-tabs.d.ts.map +1 -0
- package/dist/components/availability-tabs.js +192 -0
- package/dist/components/slot-status-tone.d.ts +15 -0
- package/dist/components/slot-status-tone.d.ts.map +1 -0
- package/dist/components/slot-status-tone.js +18 -0
- package/dist/form-resolver.d.ts +4 -0
- package/dist/form-resolver.d.ts.map +1 -0
- package/dist/form-resolver.js +40 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/use-availability-batch-mutations.d.ts +193 -0
- package/dist/hooks/use-availability-batch-mutations.d.ts.map +1 -0
- package/dist/hooks/use-availability-batch-mutations.js +53 -0
- package/dist/hooks/use-availability-closeout-mutation.d.ts +34 -0
- package/dist/hooks/use-availability-closeout-mutation.d.ts.map +1 -0
- package/dist/hooks/use-availability-closeout-mutation.js +38 -0
- package/dist/hooks/use-availability-pickup-point-mutation.d.ts +35 -0
- package/dist/hooks/use-availability-pickup-point-mutation.d.ts.map +1 -0
- package/dist/hooks/use-availability-pickup-point-mutation.js +38 -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 +2003 -0
- package/dist/i18n/provider.d.ts.map +1 -0
- package/dist/i18n/provider.js +102 -0
- package/dist/schemas.d.ts +63 -2
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +22 -2
- package/dist/ui.d.ts +13 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +12 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +3 -0
- package/package.json +92 -9
- package/src/styles.css +11 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { Button, cn, Label } from "@voyantjs/ui/components";
|
|
5
|
+
import { AsyncCombobox } from "@voyantjs/ui/components/async-combobox";
|
|
6
|
+
import { CalendarProvider, CalendarView, } from "@voyantjs/ui/components/big-calendar";
|
|
7
|
+
import { DateRangePicker } from "@voyantjs/ui/components/date-picker";
|
|
8
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components/select";
|
|
9
|
+
import { ToggleGroup, ToggleGroupItem } from "@voyantjs/ui/components/toggle-group";
|
|
10
|
+
import { CalendarDays, List, Plus } from "lucide-react";
|
|
11
|
+
import { useState } from "react";
|
|
12
|
+
import { useAvailabilityUiMessagesOrDefault } from "../i18n/index.js";
|
|
13
|
+
import { availabilityQueryKeys, useAvailabilitySlotMutation, useProducts, useRules, useSlots, useStartTimes, } from "../index.js";
|
|
14
|
+
import { AvailabilitySlotDialog, } from "./availability-dialogs.js";
|
|
15
|
+
import { AvailabilityBodySkeleton } from "./availability-skeletons.js";
|
|
16
|
+
import { AvailabilitySlotsTab, } from "./availability-tabs.js";
|
|
17
|
+
const noopId = (_id) => undefined;
|
|
18
|
+
export function AvailabilityPage({ className, defaultView = "list", bulkActionTarget = null, onBulkUpdate, onBulkDelete, onSlotOpen = noopId, onSlotSubmit, slots: pageSlots, }) {
|
|
19
|
+
const messages = useAvailabilityUiMessagesOrDefault();
|
|
20
|
+
const toolbar = messages.toolbar;
|
|
21
|
+
const queryClient = useQueryClient();
|
|
22
|
+
const slotMutation = useAvailabilitySlotMutation();
|
|
23
|
+
const [productFilter, setProductFilter] = useState("all");
|
|
24
|
+
const [productSearch, setProductSearch] = useState("");
|
|
25
|
+
const [slotStatusFilter, setSlotStatusFilter] = useState("all");
|
|
26
|
+
const [slotDateRange, setSlotDateRange] = useState(null);
|
|
27
|
+
const [view, setView] = useState(defaultView);
|
|
28
|
+
const [calendarView, setCalendarView] = useState("month");
|
|
29
|
+
const [slotSelection, setSlotSelection] = useState({});
|
|
30
|
+
const [slotDialogOpen, setSlotDialogOpen] = useState(false);
|
|
31
|
+
const [editingSlot, setEditingSlot] = useState();
|
|
32
|
+
const productIdFilter = productFilter === "all" ? undefined : productFilter;
|
|
33
|
+
const slotStatusFilterParam = slotStatusFilter === "all" ? undefined : slotStatusFilter;
|
|
34
|
+
const productsQuery = useProducts({ search: productSearch || undefined, limit: 25, offset: 0 });
|
|
35
|
+
// Rules + start times back the slot create/edit dialog. Eager-load so the
|
|
36
|
+
// dialog opens with full options the first time, but keep the queries cheap
|
|
37
|
+
// (no filters). Slots query honors the page filters so server returns the
|
|
38
|
+
// matching first page rather than a stale 25-row prefix.
|
|
39
|
+
const rulesQuery = useRules({ limit: 25, offset: 0 });
|
|
40
|
+
const startTimesQuery = useStartTimes({ limit: 25, offset: 0 });
|
|
41
|
+
// Date range is filtered client-side via matchesDateRange. The server's
|
|
42
|
+
// startsAtFrom expects an ISO datetime, but the date picker yields a
|
|
43
|
+
// yyyy-MM-dd string — passing it through gets rejected by the validator.
|
|
44
|
+
const slotsQuery = useSlots({
|
|
45
|
+
limit: 25,
|
|
46
|
+
offset: 0,
|
|
47
|
+
productId: productIdFilter,
|
|
48
|
+
status: slotStatusFilterParam,
|
|
49
|
+
});
|
|
50
|
+
const products = productsQuery.data?.data ?? [];
|
|
51
|
+
const rules = rulesQuery.data?.data ?? [];
|
|
52
|
+
const startTimes = startTimesQuery.data?.data ?? [];
|
|
53
|
+
const slots = slotsQuery.data?.data ?? [];
|
|
54
|
+
const matchesProduct = (productId) => productFilter === "all" || productId === productFilter;
|
|
55
|
+
const matchesDateRange = (date, range) => (!range?.from || date >= range.from) && (!range?.to || date <= range.to); // i18n-literal-ok comparison expression
|
|
56
|
+
const productFilteredSlots = slots.filter((slot) => matchesProduct(slot.productId));
|
|
57
|
+
const filteredSlots = productFilteredSlots.filter((slot) => (slotStatusFilter === "all" || slot.status === slotStatusFilter) &&
|
|
58
|
+
matchesDateRange(slot.dateLocal, slotDateRange));
|
|
59
|
+
const selectedProduct = products.find((product) => product.id === productFilter) ?? null;
|
|
60
|
+
const slotStatusToColor = {
|
|
61
|
+
open: "green",
|
|
62
|
+
closed: "gray",
|
|
63
|
+
sold_out: "red",
|
|
64
|
+
cancelled: "yellow",
|
|
65
|
+
};
|
|
66
|
+
const calendarEvents = filteredSlots.map((slot) => {
|
|
67
|
+
const productName = products.find((product) => product.id === slot.productId)?.name;
|
|
68
|
+
return {
|
|
69
|
+
id: slot.id,
|
|
70
|
+
startDate: slot.startsAt,
|
|
71
|
+
endDate: slot.endsAt ?? slot.startsAt,
|
|
72
|
+
title: productName ?? slot.productName ?? messages.slotFallbackTitle,
|
|
73
|
+
description: slot.notes ?? "",
|
|
74
|
+
color: slotStatusToColor[slot.status],
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
const filtersHaveValues = productFilter !== "all" ||
|
|
78
|
+
slotStatusFilter !== "all" ||
|
|
79
|
+
Boolean(slotDateRange?.from) ||
|
|
80
|
+
Boolean(slotDateRange?.to);
|
|
81
|
+
const refreshAll = async () => {
|
|
82
|
+
await queryClient.invalidateQueries({ queryKey: availabilityQueryKeys.all });
|
|
83
|
+
};
|
|
84
|
+
const handleSlotSubmit = onSlotSubmit ??
|
|
85
|
+
(async (payload, context) => {
|
|
86
|
+
if (context.isEditing) {
|
|
87
|
+
if (!context.id)
|
|
88
|
+
throw new Error("AvailabilityPage slot edit requires an id.");
|
|
89
|
+
await slotMutation.update.mutateAsync({
|
|
90
|
+
id: context.id,
|
|
91
|
+
input: payload,
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
await slotMutation.create.mutateAsync(payload);
|
|
96
|
+
});
|
|
97
|
+
const closeSlotDialog = () => {
|
|
98
|
+
setSlotDialogOpen(false);
|
|
99
|
+
setEditingSlot(undefined);
|
|
100
|
+
};
|
|
101
|
+
const isLoading = productsQuery.isPending ||
|
|
102
|
+
rulesQuery.isPending ||
|
|
103
|
+
startTimesQuery.isPending ||
|
|
104
|
+
slotsQuery.isPending;
|
|
105
|
+
return (_jsxs("div", { className: cn("flex flex-col gap-6 p-6", className), children: [_jsxs("div", { className: "flex flex-col gap-3 md:flex-row md:items-start md:justify-between", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: messages.title }), _jsx("p", { className: "text-sm text-muted-foreground", children: messages.description })] }), _jsxs("div", { className: "flex items-center gap-2", children: [pageSlots?.headerEnd, _jsxs(Button, { onClick: () => {
|
|
106
|
+
setEditingSlot(undefined);
|
|
107
|
+
setSlotDialogOpen(true);
|
|
108
|
+
}, children: [_jsx(Plus, { className: "mr-2 size-4" }), messages.tabs.slots.actionLabel] })] })] }), pageSlots?.beforeFilters, _jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-end sm:justify-between", children: [_jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-end", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "availability-product-filter", className: "text-xs", children: messages.productLabel }), _jsx(AsyncCombobox, { value: productFilter === "all" ? null : productFilter, onChange: (value) => setProductFilter(value ?? "all"), items: products, selectedItem: selectedProduct, getKey: (product) => product.id, getLabel: (product) => product.name, onSearchChange: setProductSearch, placeholder: messages.allProducts, emptyText: productsQuery.isFetching
|
|
109
|
+
? messages.productsComboboxSearching
|
|
110
|
+
: messages.productsComboboxEmpty, triggerClassName: "w-full sm:w-64" })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "availability-slot-status", className: "text-xs", children: messages.statusLabel }), _jsxs(Select, { value: slotStatusFilter, onValueChange: (value) => setSlotStatusFilter(value ?? "all"), children: [_jsx(SelectTrigger, { id: "availability-slot-status", className: "w-full sm:w-44", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: toolbar.statusAll }), _jsx(SelectItem, { value: "open", children: messages.statusOpen }), _jsx(SelectItem, { value: "closed", children: messages.statusClosed }), _jsx(SelectItem, { value: "sold_out", children: messages.statusSoldOut }), _jsx(SelectItem, { value: "cancelled", children: messages.statusCancelled })] })] })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-xs", children: toolbar.dateRangeLabel }), _jsx(DateRangePicker, { value: slotDateRange, onChange: setSlotDateRange, className: "w-full sm:w-72", placeholder: toolbar.dateRangePlaceholder })] }), filtersHaveValues ? (_jsx(Button, { variant: "outline", size: "sm", onClick: () => {
|
|
111
|
+
setProductFilter("all");
|
|
112
|
+
setSlotStatusFilter("all");
|
|
113
|
+
setSlotDateRange(null);
|
|
114
|
+
}, children: toolbar.reset })) : null] }), _jsxs(ToggleGroup, { value: [view], onValueChange: (values) => {
|
|
115
|
+
const next = values[values.length - 1];
|
|
116
|
+
if (next === "list" || next === "calendar")
|
|
117
|
+
setView(next);
|
|
118
|
+
}, variant: "outline", "aria-label": messages.title, children: [_jsxs(ToggleGroupItem, { value: "list", "aria-label": messages.tabSlots, children: [_jsx(List, { className: "mr-2 size-4" }), messages.tabSlots] }), _jsxs(ToggleGroupItem, { value: "calendar", "aria-label": messages.tabCalendar, children: [_jsx(CalendarDays, { className: "mr-2 size-4" }), messages.tabCalendar] })] })] }), pageSlots?.afterFilters, isLoading ? (_jsx(AvailabilityBodySkeleton, {})) : view === "list" ? (_jsx(AvailabilitySlotsTab, { messages: messages, products: products, filteredSlots: filteredSlots, slotSelection: slotSelection, setSlotSelection: setSlotSelection, bulkActionTarget: bulkActionTarget, handleBulkUpdate: onBulkUpdate, handleBulkDelete: onBulkDelete, onCreate: () => {
|
|
119
|
+
setEditingSlot(undefined);
|
|
120
|
+
setSlotDialogOpen(true);
|
|
121
|
+
}, onOpenRoute: onSlotOpen, onEdit: (row) => {
|
|
122
|
+
setEditingSlot(row);
|
|
123
|
+
setSlotDialogOpen(true);
|
|
124
|
+
}, hideHeader: true, asPanel: false, hideBulkDelete: true, bulkStatusSelect: true })) : (_jsx(CalendarProvider, { events: calendarEvents, onEventClick: (event) => onSlotOpen(event.id), children: _jsx(CalendarView, { view: calendarView, onViewChange: setCalendarView, onDayClick: () => setCalendarView("day") }) })), _jsx(AvailabilitySlotDialog, { messages: messages, open: slotDialogOpen, onOpenChange: setSlotDialogOpen, slot: editingSlot, products: products, rules: rules, startTimes: startTimes, onSubmit: handleSlotSubmit, onSuccess: () => {
|
|
125
|
+
closeSlotDialog();
|
|
126
|
+
void refreshAll();
|
|
127
|
+
} }), pageSlots?.dialogs] }));
|
|
128
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type { QueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { type VoyantAvailabilityContextValue } from "../index.js";
|
|
3
|
+
export interface AvailabilityRuleDetailPageProps {
|
|
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 getAvailabilityRuleDetailQueryOptions(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
|
+
timezone: string;
|
|
19
|
+
recurrenceRule: string;
|
|
20
|
+
maxCapacity: number;
|
|
21
|
+
maxPickupCapacity: number | null;
|
|
22
|
+
minTotalPax: number | null;
|
|
23
|
+
cutoffMinutes: number | null;
|
|
24
|
+
earlyBookingLimitMinutes: number | null;
|
|
25
|
+
active: boolean;
|
|
26
|
+
productName?: string | null | undefined;
|
|
27
|
+
};
|
|
28
|
+
}, Error, {
|
|
29
|
+
data: {
|
|
30
|
+
id: string;
|
|
31
|
+
productId: string;
|
|
32
|
+
optionId: string | null;
|
|
33
|
+
facilityId: string | null;
|
|
34
|
+
timezone: string;
|
|
35
|
+
recurrenceRule: string;
|
|
36
|
+
maxCapacity: number;
|
|
37
|
+
maxPickupCapacity: number | null;
|
|
38
|
+
minTotalPax: number | null;
|
|
39
|
+
cutoffMinutes: number | null;
|
|
40
|
+
earlyBookingLimitMinutes: number | null;
|
|
41
|
+
active: boolean;
|
|
42
|
+
productName?: string | null | undefined;
|
|
43
|
+
};
|
|
44
|
+
}, readonly ["voyant", "availability", "rules", "detail", string]>, "queryFn"> & {
|
|
45
|
+
queryFn?: import("@tanstack/react-query").QueryFunction<{
|
|
46
|
+
data: {
|
|
47
|
+
id: string;
|
|
48
|
+
productId: string;
|
|
49
|
+
optionId: string | null;
|
|
50
|
+
facilityId: string | null;
|
|
51
|
+
timezone: string;
|
|
52
|
+
recurrenceRule: string;
|
|
53
|
+
maxCapacity: number;
|
|
54
|
+
maxPickupCapacity: number | null;
|
|
55
|
+
minTotalPax: number | null;
|
|
56
|
+
cutoffMinutes: number | null;
|
|
57
|
+
earlyBookingLimitMinutes: number | null;
|
|
58
|
+
active: boolean;
|
|
59
|
+
productName?: string | null | undefined;
|
|
60
|
+
};
|
|
61
|
+
}, readonly ["voyant", "availability", "rules", "detail", string], never> | undefined;
|
|
62
|
+
} & {
|
|
63
|
+
queryKey: readonly ["voyant", "availability", "rules", "detail", string] & {
|
|
64
|
+
[dataTagSymbol]: {
|
|
65
|
+
data: {
|
|
66
|
+
id: string;
|
|
67
|
+
productId: string;
|
|
68
|
+
optionId: string | null;
|
|
69
|
+
facilityId: string | null;
|
|
70
|
+
timezone: string;
|
|
71
|
+
recurrenceRule: string;
|
|
72
|
+
maxCapacity: number;
|
|
73
|
+
maxPickupCapacity: number | null;
|
|
74
|
+
minTotalPax: number | null;
|
|
75
|
+
cutoffMinutes: number | null;
|
|
76
|
+
earlyBookingLimitMinutes: number | null;
|
|
77
|
+
active: boolean;
|
|
78
|
+
productName?: string | null | undefined;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
[dataTagErrorSymbol]: Error;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
export declare function getAvailabilityRuleSlotsQueryOptions(client: VoyantAvailabilityContextValue, id: string | null | undefined): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<{
|
|
85
|
+
data: {
|
|
86
|
+
id: string;
|
|
87
|
+
productId: string;
|
|
88
|
+
itineraryId: string | null;
|
|
89
|
+
optionId: string | null;
|
|
90
|
+
facilityId: string | null;
|
|
91
|
+
availabilityRuleId: string | null;
|
|
92
|
+
startTimeId: string | null;
|
|
93
|
+
dateLocal: string;
|
|
94
|
+
startsAt: string;
|
|
95
|
+
endsAt: string | null;
|
|
96
|
+
timezone: string;
|
|
97
|
+
status: "cancelled" | "open" | "closed" | "sold_out";
|
|
98
|
+
unlimited: boolean;
|
|
99
|
+
initialPax: number | null;
|
|
100
|
+
remainingPax: number | null;
|
|
101
|
+
nights: number | null;
|
|
102
|
+
days: number | null;
|
|
103
|
+
notes: string | null;
|
|
104
|
+
productName?: string | null | undefined;
|
|
105
|
+
endDateLocal?: string | null | undefined;
|
|
106
|
+
}[];
|
|
107
|
+
total: number;
|
|
108
|
+
limit: number;
|
|
109
|
+
offset: number;
|
|
110
|
+
}, Error, {
|
|
111
|
+
data: {
|
|
112
|
+
id: string;
|
|
113
|
+
productId: string;
|
|
114
|
+
itineraryId: string | null;
|
|
115
|
+
optionId: string | null;
|
|
116
|
+
facilityId: string | null;
|
|
117
|
+
availabilityRuleId: string | null;
|
|
118
|
+
startTimeId: string | null;
|
|
119
|
+
dateLocal: string;
|
|
120
|
+
startsAt: string;
|
|
121
|
+
endsAt: string | null;
|
|
122
|
+
timezone: string;
|
|
123
|
+
status: "cancelled" | "open" | "closed" | "sold_out";
|
|
124
|
+
unlimited: boolean;
|
|
125
|
+
initialPax: number | null;
|
|
126
|
+
remainingPax: number | null;
|
|
127
|
+
nights: number | null;
|
|
128
|
+
days: number | null;
|
|
129
|
+
notes: string | null;
|
|
130
|
+
productName?: string | null | undefined;
|
|
131
|
+
endDateLocal?: string | null | undefined;
|
|
132
|
+
}[];
|
|
133
|
+
total: number;
|
|
134
|
+
limit: number;
|
|
135
|
+
offset: number;
|
|
136
|
+
}, readonly ["voyant", "availability", "slots", "list", import("../query-keys.js").AvailabilitySlotsListFilters]>, "queryFn"> & {
|
|
137
|
+
queryFn?: import("@tanstack/react-query").QueryFunction<{
|
|
138
|
+
data: {
|
|
139
|
+
id: string;
|
|
140
|
+
productId: string;
|
|
141
|
+
itineraryId: string | null;
|
|
142
|
+
optionId: string | null;
|
|
143
|
+
facilityId: string | null;
|
|
144
|
+
availabilityRuleId: string | null;
|
|
145
|
+
startTimeId: string | null;
|
|
146
|
+
dateLocal: string;
|
|
147
|
+
startsAt: string;
|
|
148
|
+
endsAt: string | null;
|
|
149
|
+
timezone: string;
|
|
150
|
+
status: "cancelled" | "open" | "closed" | "sold_out";
|
|
151
|
+
unlimited: boolean;
|
|
152
|
+
initialPax: number | null;
|
|
153
|
+
remainingPax: number | null;
|
|
154
|
+
nights: number | null;
|
|
155
|
+
days: number | null;
|
|
156
|
+
notes: string | null;
|
|
157
|
+
productName?: string | null | undefined;
|
|
158
|
+
endDateLocal?: string | null | undefined;
|
|
159
|
+
}[];
|
|
160
|
+
total: number;
|
|
161
|
+
limit: number;
|
|
162
|
+
offset: number;
|
|
163
|
+
}, readonly ["voyant", "availability", "slots", "list", import("../query-keys.js").AvailabilitySlotsListFilters], never> | undefined;
|
|
164
|
+
} & {
|
|
165
|
+
queryKey: readonly ["voyant", "availability", "slots", "list", import("../query-keys.js").AvailabilitySlotsListFilters] & {
|
|
166
|
+
[dataTagSymbol]: {
|
|
167
|
+
data: {
|
|
168
|
+
id: string;
|
|
169
|
+
productId: string;
|
|
170
|
+
itineraryId: string | null;
|
|
171
|
+
optionId: string | null;
|
|
172
|
+
facilityId: string | null;
|
|
173
|
+
availabilityRuleId: string | null;
|
|
174
|
+
startTimeId: string | null;
|
|
175
|
+
dateLocal: string;
|
|
176
|
+
startsAt: string;
|
|
177
|
+
endsAt: string | null;
|
|
178
|
+
timezone: string;
|
|
179
|
+
status: "cancelled" | "open" | "closed" | "sold_out";
|
|
180
|
+
unlimited: boolean;
|
|
181
|
+
initialPax: number | null;
|
|
182
|
+
remainingPax: number | null;
|
|
183
|
+
nights: number | null;
|
|
184
|
+
days: number | null;
|
|
185
|
+
notes: string | null;
|
|
186
|
+
productName?: string | null | undefined;
|
|
187
|
+
endDateLocal?: string | null | undefined;
|
|
188
|
+
}[];
|
|
189
|
+
total: number;
|
|
190
|
+
limit: number;
|
|
191
|
+
offset: number;
|
|
192
|
+
};
|
|
193
|
+
[dataTagErrorSymbol]: Error;
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
export declare function loadAvailabilityRuleDetailPage(queryClient: QueryClient, client: VoyantAvailabilityContextValue, id: string): Promise<[{
|
|
197
|
+
data: {
|
|
198
|
+
id: string;
|
|
199
|
+
productId: string;
|
|
200
|
+
optionId: string | null;
|
|
201
|
+
facilityId: string | null;
|
|
202
|
+
timezone: string;
|
|
203
|
+
recurrenceRule: string;
|
|
204
|
+
maxCapacity: number;
|
|
205
|
+
maxPickupCapacity: number | null;
|
|
206
|
+
minTotalPax: number | null;
|
|
207
|
+
cutoffMinutes: number | null;
|
|
208
|
+
earlyBookingLimitMinutes: number | null;
|
|
209
|
+
active: boolean;
|
|
210
|
+
productName?: string | null | undefined;
|
|
211
|
+
};
|
|
212
|
+
}, {
|
|
213
|
+
data: {
|
|
214
|
+
id: string;
|
|
215
|
+
productId: string;
|
|
216
|
+
itineraryId: string | null;
|
|
217
|
+
optionId: string | null;
|
|
218
|
+
facilityId: string | null;
|
|
219
|
+
availabilityRuleId: string | null;
|
|
220
|
+
startTimeId: string | null;
|
|
221
|
+
dateLocal: string;
|
|
222
|
+
startsAt: string;
|
|
223
|
+
endsAt: string | null;
|
|
224
|
+
timezone: string;
|
|
225
|
+
status: "cancelled" | "open" | "closed" | "sold_out";
|
|
226
|
+
unlimited: boolean;
|
|
227
|
+
initialPax: number | null;
|
|
228
|
+
remainingPax: number | null;
|
|
229
|
+
nights: number | null;
|
|
230
|
+
days: number | null;
|
|
231
|
+
notes: string | null;
|
|
232
|
+
productName?: string | null | undefined;
|
|
233
|
+
endDateLocal?: string | null | undefined;
|
|
234
|
+
}[];
|
|
235
|
+
total: number;
|
|
236
|
+
limit: number;
|
|
237
|
+
offset: number;
|
|
238
|
+
}, {
|
|
239
|
+
data: {
|
|
240
|
+
id: string;
|
|
241
|
+
name: string;
|
|
242
|
+
sellCurrency: string | null;
|
|
243
|
+
productType: {
|
|
244
|
+
id: string;
|
|
245
|
+
name: string;
|
|
246
|
+
code: string | null;
|
|
247
|
+
} | null;
|
|
248
|
+
};
|
|
249
|
+
}]>;
|
|
250
|
+
export declare function AvailabilityRuleDetailPage({ id, className, onBack, onDeleted, onOpenProduct, onOpenSlot, confirmAction, }: AvailabilityRuleDetailPageProps): import("react/jsx-runtime").JSX.Element;
|
|
251
|
+
//# sourceMappingURL=availability-rule-detail-page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"availability-rule-detail-page.d.ts","sourceRoot":"","sources":["../../src/components/availability-rule-detail-page.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAaxD,OAAO,EAWL,KAAK,8BAA8B,EACpC,MAAM,aAAa,CAAA;AAIpB,MAAM,WAAW,+BAA+B;IAC9C,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,qCAAqC,CACnD,MAAM,EAAE,8BAA8B,EACtC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAa9B;AAED,wBAAgB,oCAAoC,CAClD,MAAM,EAAE,8BAA8B,EACtC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAG9B;AAED,wBAAsB,8BAA8B,CAClD,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,8BAA8B,EACtC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAWX;AAED,wBAAgB,0BAA0B,CAAC,EACzC,EAAE,EACF,SAAS,EACT,MAAM,EACN,SAAS,EACT,aAAa,EACb,UAAU,EACV,aAAkE,GACnE,EAAE,+BAA+B,2CAsIjC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
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 { Badge, Button, Card, CardContent, CardHeader, CardTitle, cn, } from "@voyantjs/ui/components";
|
|
5
|
+
import { ArrowLeft, CalendarDays, Package, Trash2 } from "lucide-react";
|
|
6
|
+
import { useAvailabilityUiMessagesOrDefault } from "../i18n/index.js";
|
|
7
|
+
import { availabilityQueryKeys, availabilityRuleSingleResponse, fetchWithValidation, getProductQueryOptions, getSlotsQueryOptions, slotLocalStart, useAvailabilityRuleMutation, useVoyantAvailabilityContext, } from "../index.js";
|
|
8
|
+
import { getSlotStatusLabel } from "./availability-columns.js";
|
|
9
|
+
import { AvailabilityRuleDetailSkeleton } from "./availability-skeletons.js";
|
|
10
|
+
export function getAvailabilityRuleDetailQueryOptions(client, id) {
|
|
11
|
+
return queryOptions({
|
|
12
|
+
queryKey: availabilityQueryKeys.ruleDetail(id ?? ""),
|
|
13
|
+
queryFn: async () => {
|
|
14
|
+
if (!id)
|
|
15
|
+
throw new Error("getAvailabilityRuleDetailQueryOptions requires an id");
|
|
16
|
+
return fetchWithValidation(`/v1/availability/rules/${id}`, availabilityRuleSingleResponse, client);
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export function getAvailabilityRuleSlotsQueryOptions(client, id) {
|
|
21
|
+
return getSlotsQueryOptions(client, { availabilityRuleId: id ?? undefined, limit: 25, offset: 0 });
|
|
22
|
+
}
|
|
23
|
+
export async function loadAvailabilityRuleDetailPage(queryClient, client, id) {
|
|
24
|
+
const ruleData = await queryClient.ensureQueryData(getAvailabilityRuleDetailQueryOptions(client, id));
|
|
25
|
+
return Promise.all([
|
|
26
|
+
Promise.resolve(ruleData),
|
|
27
|
+
queryClient.ensureQueryData(getAvailabilityRuleSlotsQueryOptions(client, id)),
|
|
28
|
+
queryClient.ensureQueryData(getProductQueryOptions(client, ruleData.data.productId)),
|
|
29
|
+
]);
|
|
30
|
+
}
|
|
31
|
+
export function AvailabilityRuleDetailPage({ id, className, onBack, onDeleted, onOpenProduct, onOpenSlot, confirmAction = (message) => globalThis.confirm?.(message) ?? true, }) {
|
|
32
|
+
const client = useVoyantAvailabilityContext();
|
|
33
|
+
const messages = useAvailabilityUiMessagesOrDefault();
|
|
34
|
+
const detailMessages = messages.details;
|
|
35
|
+
const noValue = detailMessages.noValue;
|
|
36
|
+
const ruleMutation = useAvailabilityRuleMutation();
|
|
37
|
+
const { data: ruleData, isPending } = useQuery(getAvailabilityRuleDetailQueryOptions(client, id));
|
|
38
|
+
const rule = ruleData?.data;
|
|
39
|
+
const productQuery = useQuery({
|
|
40
|
+
...getProductQueryOptions(client, rule?.productId ?? ""),
|
|
41
|
+
enabled: Boolean(rule?.productId),
|
|
42
|
+
});
|
|
43
|
+
const slotsQuery = useQuery(getAvailabilityRuleSlotsQueryOptions(client, id));
|
|
44
|
+
if (isPending) {
|
|
45
|
+
return _jsx(AvailabilityRuleDetailSkeleton, {});
|
|
46
|
+
}
|
|
47
|
+
if (!rule) {
|
|
48
|
+
return (_jsx(DetailEmptyState, { message: detailMessages.rule.notFound, backLabel: detailMessages.backToAvailability, onBack: onBack }));
|
|
49
|
+
}
|
|
50
|
+
async function deleteRule(currentRule) {
|
|
51
|
+
if (!confirmAction(detailMessages.rule.deleteConfirm))
|
|
52
|
+
return;
|
|
53
|
+
await ruleMutation.remove.mutateAsync(currentRule.id);
|
|
54
|
+
if (onDeleted)
|
|
55
|
+
onDeleted();
|
|
56
|
+
else
|
|
57
|
+
onBack?.();
|
|
58
|
+
}
|
|
59
|
+
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: detailMessages.rule.pageTitle }), _jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-2", children: [_jsx(Badge, { variant: rule.active ? "default" : "secondary", children: rule.active ? messages.statusActive : messages.statusInactive }), _jsx(Badge, { variant: "outline", children: rule.timezone })] })] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [onOpenProduct ? (_jsxs(Button, { variant: "outline", onClick: () => onOpenProduct(rule.productId), children: [_jsx(Package, { "data-icon": "inline-start", "aria-hidden": "true" }), detailMessages.openProduct] })) : null, _jsxs(Button, { variant: "destructive", onClick: () => void deleteRule(rule), disabled: ruleMutation.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.rule.detailsTitle }) }), _jsxs(CardContent, { className: "flex flex-col gap-3 text-sm", children: [_jsx(DetailLine, { label: messages.productLabel, children: productQuery.data?.data.name ?? rule.productId }), _jsxs("div", { children: [_jsxs("span", { className: "text-muted-foreground", children: [messages.recurrenceLabel, ":"] }), _jsx("pre", { className: "mt-1 overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs", children: rule.recurrenceRule })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: detailMessages.rule.capacityPolicyTitle }) }), _jsxs(CardContent, { className: "flex flex-col gap-3 text-sm", children: [_jsx(DetailLine, { label: messages.maxPaxLabel, children: rule.maxCapacity }), _jsx(DetailLine, { label: detailMessages.rule.maxPickupCapacityLabel, children: rule.maxPickupCapacity ?? noValue }), _jsx(DetailLine, { label: detailMessages.rule.minTotalPaxLabel, children: rule.minTotalPax ?? noValue }), _jsx(DetailLine, { label: detailMessages.rule.cutoffMinutesLabel, children: rule.cutoffMinutes ?? noValue }), _jsx(DetailLine, { label: detailMessages.rule.earlyBookingLimitLabel, children: rule.earlyBookingLimitMinutes ?? noValue })] })] })] }), _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.rule.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.rule.generatedSlotsEmpty })) : (slotsQuery.data?.data.map((slot) => (_jsx(SlotButton, { slot: slot, onOpenSlot: onOpenSlot }, slot.id)))) })] })] }));
|
|
60
|
+
}
|
|
61
|
+
function SlotButton({ slot, onOpenSlot, }) {
|
|
62
|
+
const messages = useAvailabilityUiMessagesOrDefault();
|
|
63
|
+
const noValue = messages.details.noValue;
|
|
64
|
+
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: "flex items-center gap-2", children: [_jsx(Badge, { variant: "outline", children: getSlotStatusLabel(slot.status, messages) }), _jsx("span", { children: formatSlotLocalDateTime(slotLocalStart(slot)) })] }), _jsxs("div", { className: "mt-2 text-muted-foreground", children: [messages.remainingPaxLabel, ": ", slot.remainingPax ?? noValue] })] }));
|
|
65
|
+
}
|
|
66
|
+
function formatSlotLocalDateTime(value) {
|
|
67
|
+
return `${value.date} ${value.time}`;
|
|
68
|
+
}
|
|
69
|
+
function DetailLine({ label, children }) {
|
|
70
|
+
return (_jsxs("div", { children: [_jsxs("span", { className: "text-muted-foreground", children: [label, ":"] }), " ", _jsx("span", { children: children })] }));
|
|
71
|
+
}
|
|
72
|
+
function DetailEmptyState({ message, backLabel, onBack, }) {
|
|
73
|
+
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] }));
|
|
74
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface AvailabilitySectionHeaderProps {
|
|
2
|
+
actionLabel: string;
|
|
3
|
+
description: string;
|
|
4
|
+
onAction: () => void;
|
|
5
|
+
title: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function AvailabilitySectionHeader({ actionLabel, description, onAction, title, }: AvailabilitySectionHeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
//# sourceMappingURL=availability-section-header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"availability-section-header.d.ts","sourceRoot":"","sources":["../../src/components/availability-section-header.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,8BAA8B;IAC7C,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,yBAAyB,CAAC,EACxC,WAAW,EACX,WAAW,EACX,QAAQ,EACR,KAAK,GACN,EAAE,8BAA8B,2CAahC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Button } from "@voyantjs/ui/components";
|
|
4
|
+
import { Plus } from "lucide-react";
|
|
5
|
+
export function AvailabilitySectionHeader({ actionLabel, description, onAction, title, }) {
|
|
6
|
+
return (_jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold", children: title }), _jsx("p", { className: "text-sm text-muted-foreground", children: description })] }), _jsxs(Button, { onClick: onAction, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), actionLabel] })] }));
|
|
7
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function AvailabilityBodySkeleton(): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function AvailabilityPageSkeleton(): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
export declare function AvailabilityRuleDetailSkeleton(): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
export declare function AvailabilityStartTimeDetailSkeleton(): import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
export declare function AvailabilitySlotDetailSkeleton(): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
//# sourceMappingURL=availability-skeletons.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"availability-skeletons.d.ts","sourceRoot":"","sources":["../../src/components/availability-skeletons.tsx"],"names":[],"mappings":"AAYA,wBAAgB,wBAAwB,4CAsEvC;AAED,wBAAgB,wBAAwB,4CAUvC;AAED,wBAAgB,8BAA8B,4CAmE7C;AAED,wBAAgB,mCAAmC,4CAqDlD;AAED,wBAAgB,8BAA8B,4CA6D7C"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Card, CardContent, CardHeader } from "@voyantjs/ui/components/card";
|
|
3
|
+
import { Skeleton } from "@voyantjs/ui/components/skeleton";
|
|
4
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyantjs/ui/components/table";
|
|
5
|
+
import { useAvailabilityUiMessagesOrDefault } from "../i18n/index.js";
|
|
6
|
+
export function AvailabilityBodySkeleton() {
|
|
7
|
+
const messages = useAvailabilityUiMessagesOrDefault().page.skeleton;
|
|
8
|
+
return (_jsxs("div", { className: "flex flex-col gap-6", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Skeleton, { className: "h-9 w-full max-w-sm" }), _jsx(Skeleton, { className: "h-9 w-44" })] }), _jsx("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-4", children: Array.from({ length: 4 }).map((_, i) => (_jsxs(Card, { children: [_jsx(CardHeader, { className: "pb-2", children: _jsx(Skeleton, { className: "h-3.5 w-28" }) }), _jsxs(CardContent, { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-7 w-20" }), _jsx(Skeleton, { className: "h-3 w-40" })] })] }, i))) }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(Skeleton, { className: "h-5 w-52" }) }), _jsx(CardContent, { className: "space-y-2", children: Array.from({ length: 3 }).map((_, i) => (_jsxs("div", { className: "flex items-center justify-between rounded-md border px-3 py-2", children: [_jsx(Skeleton, { className: "h-4 w-48" }), _jsx(Skeleton, { className: "h-4 w-32" })] }, i))) })] }), _jsxs("div", { className: "flex items-center gap-1 border-b", children: [_jsx(Skeleton, { className: "h-9 w-20" }), _jsx(Skeleton, { className: "h-9 w-20" }), _jsx(Skeleton, { className: "h-9 w-28" }), _jsx(Skeleton, { className: "h-9 w-24" }), _jsx(Skeleton, { className: "h-9 w-32" })] }), _jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: messages.date }), _jsx(TableHead, { children: messages.product }), _jsx(TableHead, { children: messages.status }), _jsx(TableHead, { children: messages.remaining }), _jsx(TableHead, { children: messages.capacity })] }) }), _jsx(SkeletonTableRows, { rows: 6, columns: 5, columnWidths: ["w-24", "w-40", "w-16", "w-16", "w-16"] })] }) })] }));
|
|
9
|
+
}
|
|
10
|
+
export function AvailabilityPageSkeleton() {
|
|
11
|
+
return (_jsxs("div", { className: "flex flex-col gap-6 p-6", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-7 w-40" }), _jsx(Skeleton, { className: "h-4 w-96" })] }), _jsx(AvailabilityBodySkeleton, {})] }));
|
|
12
|
+
}
|
|
13
|
+
export function AvailabilityRuleDetailSkeleton() {
|
|
14
|
+
return (_jsxs("div", { className: "flex flex-col gap-6 p-6", children: [_jsxs("div", { className: "flex items-center gap-4", children: [_jsx(Skeleton, { className: "h-9 w-9 rounded-md" }), _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Skeleton, { className: "h-7 w-48" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Skeleton, { className: "h-5 w-14 rounded-full" }), _jsx(Skeleton, { className: "h-5 w-24 rounded" })] })] }), _jsx(Skeleton, { className: "h-9 w-32" }), _jsx(Skeleton, { className: "h-9 w-24" })] }), _jsxs("div", { className: "grid gap-6 md:grid-cols-2", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(Skeleton, { className: "h-5 w-24" }) }), _jsxs(CardContent, { className: "grid gap-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Skeleton, { className: "h-3.5 w-20" }), _jsx(Skeleton, { className: "h-3.5 w-40" })] }), _jsx(Skeleton, { className: "h-3.5 w-24" }), _jsx(Skeleton, { className: "h-24 w-full rounded-md" })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(Skeleton, { className: "h-5 w-32" }) }), _jsx(CardContent, { className: "grid gap-3", children: Array.from({ length: 5 }).map((_, i) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Skeleton, { className: "h-3.5 w-32" }), _jsx(Skeleton, { className: "h-3.5 w-16" })] }, i))) })] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center gap-2", children: [_jsx(Skeleton, { className: "h-4 w-4" }), _jsx(Skeleton, { className: "h-5 w-36" })] }), _jsx(CardContent, { className: "space-y-2", children: Array.from({ length: 4 }).map((_, i) => (_jsxs("div", { className: "flex items-center justify-between rounded-md border px-3 py-2", children: [_jsx(Skeleton, { className: "h-4 w-48" }), _jsx(Skeleton, { className: "h-5 w-16 rounded-full" })] }, i))) })] })] }));
|
|
15
|
+
}
|
|
16
|
+
export function AvailabilityStartTimeDetailSkeleton() {
|
|
17
|
+
return (_jsxs("div", { className: "flex flex-col gap-6 p-6", children: [_jsxs("div", { className: "flex items-center gap-4", children: [_jsx(Skeleton, { className: "h-9 w-9 rounded-md" }), _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Skeleton, { className: "h-7 w-56" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Skeleton, { className: "h-5 w-16 rounded" }), _jsx(Skeleton, { className: "h-5 w-14 rounded-full" })] })] }), _jsx(Skeleton, { className: "h-9 w-32" }), _jsx(Skeleton, { className: "h-9 w-24" })] }), _jsxs("div", { className: "grid gap-6 md:grid-cols-2", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(Skeleton, { className: "h-5 w-40" }) }), _jsx(CardContent, { className: "grid gap-3", children: Array.from({ length: 6 }).map((_, i) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Skeleton, { className: "h-3.5 w-24" }), _jsx(Skeleton, { className: "h-3.5 w-32" })] }, i))) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center gap-2", children: [_jsx(Skeleton, { className: "h-4 w-4" }), _jsx(Skeleton, { className: "h-5 w-36" })] }), _jsx(CardContent, { className: "space-y-3", children: Array.from({ length: 3 }).map((_, i) => (_jsxs("div", { className: "rounded-md border p-3 space-y-1.5", children: [_jsx(Skeleton, { className: "h-4 w-48" }), _jsx(Skeleton, { className: "h-3 w-32" })] }, i))) })] })] })] }));
|
|
18
|
+
}
|
|
19
|
+
export function AvailabilitySlotDetailSkeleton() {
|
|
20
|
+
return (_jsxs("div", { className: "flex flex-col gap-6 p-6", children: [_jsxs("div", { className: "flex items-center gap-4", children: [_jsx(Skeleton, { className: "h-9 w-9 rounded-md" }), _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Skeleton, { className: "h-7 w-72" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Skeleton, { className: "h-5 w-16 rounded-full" }), _jsx(Skeleton, { className: "h-5 w-24 rounded" })] })] }), _jsx(Skeleton, { className: "h-9 w-32" }), _jsx(Skeleton, { className: "h-9 w-24" })] }), _jsxs("div", { className: "grid gap-6 md:grid-cols-2", children: [_jsx(PairListCard, { titleWidth: "w-24", rows: 7 }), _jsx(PairListCard, { titleWidth: "w-28", rows: 7 })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center gap-2", children: [_jsx(Skeleton, { className: "h-4 w-4" }), _jsx(Skeleton, { className: "h-5 w-36" })] }), _jsx(CardContent, { className: "space-y-3", children: Array.from({ length: 3 }).map((_, i) => (_jsxs("div", { className: "rounded-md border p-3 space-y-1.5", children: [_jsx(Skeleton, { className: "h-4 w-40" }), _jsx(Skeleton, { className: "h-3 w-56" }), _jsx(Skeleton, { className: "h-3 w-44" })] }, i))) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center gap-2", children: [_jsx(Skeleton, { className: "h-4 w-4" }), _jsx(Skeleton, { className: "h-5 w-44" })] }), _jsx(CardContent, { className: "space-y-2", children: Array.from({ length: 2 }).map((_, i) => (_jsxs("div", { className: "flex items-center justify-between rounded-md border px-3 py-2", children: [_jsx(Skeleton, { className: "h-4 w-48" }), _jsx(Skeleton, { className: "h-5 w-16 rounded-full" })] }, i))) })] })] }));
|
|
21
|
+
}
|
|
22
|
+
function PairListCard({ titleWidth, rows }) {
|
|
23
|
+
return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(Skeleton, { className: `h-5 ${titleWidth}` }) }), _jsx(CardContent, { className: "grid gap-3", children: Array.from({ length: rows }).map((_, i) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Skeleton, { className: "h-3.5 w-28" }), _jsx(Skeleton, { className: "h-3.5 w-32" })] }, i))) })] }));
|
|
24
|
+
}
|
|
25
|
+
function SkeletonTableRows({ rows = 6, columns = 4, columnWidths, }) {
|
|
26
|
+
return (_jsx(TableBody, { children: Array.from({ length: rows }).map((_, r) => (_jsx(TableRow
|
|
27
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: stable placeholders
|
|
28
|
+
, { children: Array.from({ length: columns }).map((__, c) => {
|
|
29
|
+
const width = columnWidths?.[c] ?? (c === 0 ? "w-2/3" : c === columns - 1 ? "w-16" : "w-1/2");
|
|
30
|
+
return (_jsx(TableCell
|
|
31
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: stable placeholders
|
|
32
|
+
, { children: _jsx(Skeleton, { className: `h-4 ${width}` }) }, c));
|
|
33
|
+
}) }, r))) }));
|
|
34
|
+
}
|