@voyantjs/availability-ui 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,341 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { booleanOptions, formatDateTime, NONE_VALUE, nullableNumber, nullableString, slotStatusOptions, toIsoDateTime, toLocalDateTimeInput, } from "@voyantjs/availability-react";
4
+ import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Textarea, } from "@voyantjs/ui/components";
5
+ import { DatePicker } from "@voyantjs/ui/components/date-picker";
6
+ import { Loader2 } from "lucide-react";
7
+ import { useEffect } from "react";
8
+ import { useForm } from "react-hook-form";
9
+ import { z } from "zod/v4";
10
+ import { zodResolver } from "../form-resolver.js";
11
+ function getRuleFormSchema(messages) {
12
+ return z.object({
13
+ productId: z.string().min(1, messages.dialogs.rule.validationProductRequired),
14
+ timezone: z.string().min(1, messages.dialogs.rule.validationTimezoneRequired),
15
+ recurrenceRule: z.string().min(1, messages.dialogs.rule.validationRecurrenceRequired),
16
+ maxCapacity: z.coerce.number().int().min(0),
17
+ maxPickupCapacity: z.string().optional(),
18
+ minTotalPax: z.string().optional(),
19
+ cutoffMinutes: z.string().optional(),
20
+ earlyBookingLimitMinutes: z.string().optional(),
21
+ active: z.boolean(),
22
+ });
23
+ }
24
+ export function AvailabilityRuleDialog(props) {
25
+ const ruleMessages = props.messages.dialogs.rule;
26
+ const ruleFormSchema = getRuleFormSchema(props.messages);
27
+ const form = useForm({
28
+ resolver: zodResolver(ruleFormSchema),
29
+ defaultValues: {
30
+ productId: "",
31
+ timezone: "Europe/Bucharest",
32
+ recurrenceRule: "FREQ=DAILY;INTERVAL=1",
33
+ maxCapacity: 0,
34
+ maxPickupCapacity: "",
35
+ minTotalPax: "",
36
+ cutoffMinutes: "",
37
+ earlyBookingLimitMinutes: "",
38
+ active: true,
39
+ },
40
+ });
41
+ useEffect(() => {
42
+ if (props.open && props.rule) {
43
+ form.reset({
44
+ productId: props.rule.productId,
45
+ timezone: props.rule.timezone,
46
+ recurrenceRule: props.rule.recurrenceRule,
47
+ maxCapacity: props.rule.maxCapacity,
48
+ maxPickupCapacity: props.rule.maxPickupCapacity?.toString() ?? "",
49
+ minTotalPax: "",
50
+ cutoffMinutes: props.rule.cutoffMinutes?.toString() ?? "",
51
+ earlyBookingLimitMinutes: "",
52
+ active: props.rule.active,
53
+ });
54
+ }
55
+ else if (props.open) {
56
+ form.reset();
57
+ }
58
+ }, [form, props.open, props.rule]);
59
+ const isEditing = Boolean(props.rule);
60
+ const onSubmit = async (values) => {
61
+ await props.onSubmit({
62
+ productId: values.productId,
63
+ timezone: values.timezone,
64
+ recurrenceRule: values.recurrenceRule,
65
+ maxCapacity: values.maxCapacity,
66
+ maxPickupCapacity: nullableNumber(values.maxPickupCapacity),
67
+ minTotalPax: nullableNumber(values.minTotalPax),
68
+ cutoffMinutes: nullableNumber(values.cutoffMinutes),
69
+ earlyBookingLimitMinutes: nullableNumber(values.earlyBookingLimitMinutes),
70
+ active: values.active,
71
+ }, { isEditing, id: props.rule?.id });
72
+ props.onSuccess();
73
+ };
74
+ return (_jsx(Dialog, { open: props.open, onOpenChange: props.onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? ruleMessages.editTitle : ruleMessages.newTitle }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsx(ProductSelect, { label: ruleMessages.productLabel, placeholder: ruleMessages.selectProductPlaceholder, products: props.products, value: form.watch("productId"), onValueChange: (value) => form.setValue("productId", value ?? "") }), form.formState.errors.productId ? (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.productId.message })) : null, _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: ruleMessages.timezoneLabel }), _jsx(Input, { ...form.register("timezone"), placeholder: ruleMessages.timezonePlaceholder })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: ruleMessages.maxCapacityLabel }), _jsx(Input, { ...form.register("maxCapacity"), type: "number", min: 0 })] })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: ruleMessages.recurrenceRuleLabel }), _jsx(Textarea, { ...form.register("recurrenceRule"), placeholder: ruleMessages.recurrenceRulePlaceholder, className: "font-mono text-xs" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: ruleMessages.maxPickupCapacityLabel }), _jsx(Input, { ...form.register("maxPickupCapacity"), type: "number", min: 0 })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: ruleMessages.minimumTotalPaxLabel }), _jsx(Input, { ...form.register("minTotalPax"), type: "number", min: 0 })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: ruleMessages.cutoffMinutesLabel }), _jsx(Input, { ...form.register("cutoffMinutes"), type: "number", min: 0 })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: ruleMessages.earlyBookingLimitMinutesLabel }), _jsx(Input, { ...form.register("earlyBookingLimitMinutes"), type: "number", min: 0 })] })] }), _jsx(SwitchField, { title: ruleMessages.activeTitle, description: ruleMessages.activeDescription, checked: form.watch("active"), onCheckedChange: (checked) => form.setValue("active", checked) })] }), _jsx(DialogActions, { cancel: ruleMessages.cancel, save: ruleMessages.save, create: ruleMessages.create, isEditing: isEditing, isSubmitting: form.formState.isSubmitting, onCancel: () => props.onOpenChange(false) })] })] }) }));
75
+ }
76
+ function getStartTimeFormSchema(messages) {
77
+ return z.object({
78
+ productId: z.string().min(1, messages.dialogs.startTime.validationProductRequired),
79
+ label: z.string().optional(),
80
+ startTimeLocal: z.string().min(1, messages.dialogs.startTime.validationStartTimeRequired),
81
+ durationMinutes: z.string().optional(),
82
+ sortOrder: z.coerce.number().int(),
83
+ active: z.boolean(),
84
+ });
85
+ }
86
+ export function AvailabilityStartTimeDialog(props) {
87
+ const startTimeMessages = props.messages.dialogs.startTime;
88
+ const startTimeFormSchema = getStartTimeFormSchema(props.messages);
89
+ const form = useForm({
90
+ resolver: zodResolver(startTimeFormSchema),
91
+ defaultValues: {
92
+ productId: "",
93
+ label: "",
94
+ startTimeLocal: "09:00",
95
+ durationMinutes: "",
96
+ sortOrder: 0,
97
+ active: true,
98
+ },
99
+ });
100
+ useEffect(() => {
101
+ if (props.open && props.startTime) {
102
+ form.reset({
103
+ productId: props.startTime.productId,
104
+ label: props.startTime.label ?? "",
105
+ startTimeLocal: props.startTime.startTimeLocal,
106
+ durationMinutes: props.startTime.durationMinutes?.toString() ?? "",
107
+ sortOrder: props.startTime.sortOrder,
108
+ active: props.startTime.active,
109
+ });
110
+ }
111
+ else if (props.open) {
112
+ form.reset();
113
+ }
114
+ }, [form, props.open, props.startTime]);
115
+ const isEditing = Boolean(props.startTime);
116
+ const onSubmit = async (values) => {
117
+ await props.onSubmit({
118
+ productId: values.productId,
119
+ label: nullableString(values.label),
120
+ startTimeLocal: values.startTimeLocal,
121
+ durationMinutes: nullableNumber(values.durationMinutes),
122
+ sortOrder: values.sortOrder,
123
+ active: values.active,
124
+ }, { isEditing, id: props.startTime?.id });
125
+ props.onSuccess();
126
+ };
127
+ return (_jsx(Dialog, { open: props.open, onOpenChange: props.onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? startTimeMessages.editTitle : startTimeMessages.newTitle }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsx(ProductSelect, { label: startTimeMessages.productLabel, placeholder: startTimeMessages.selectProductPlaceholder, products: props.products, value: form.watch("productId"), onValueChange: (value) => form.setValue("productId", value ?? "") }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: startTimeMessages.labelLabel }), _jsx(Input, { ...form.register("label"), placeholder: startTimeMessages.labelPlaceholder })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: startTimeMessages.startTimeLabel }), _jsx(Input, { ...form.register("startTimeLocal"), type: "time" })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: startTimeMessages.durationMinutesLabel }), _jsx(Input, { ...form.register("durationMinutes"), type: "number", min: 0 })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: startTimeMessages.sortOrderLabel }), _jsx(Input, { ...form.register("sortOrder"), type: "number" })] })] }), _jsx(SwitchField, { title: startTimeMessages.activeTitle, description: startTimeMessages.activeDescription, checked: form.watch("active"), onCheckedChange: (checked) => form.setValue("active", checked) })] }), _jsx(DialogActions, { cancel: startTimeMessages.cancel, save: startTimeMessages.save, create: startTimeMessages.create, isEditing: isEditing, isSubmitting: form.formState.isSubmitting, onCancel: () => props.onOpenChange(false) })] })] }) }));
128
+ }
129
+ function getSlotFormSchema(messages) {
130
+ return z.object({
131
+ productId: z.string().min(1, messages.dialogs.slot.validationProductRequired),
132
+ availabilityRuleId: z.string().optional(),
133
+ startTimeId: z.string().optional(),
134
+ dateLocal: z.string().min(1, messages.dialogs.slot.validationDateRequired),
135
+ startsAt: z.string().min(1, messages.dialogs.slot.validationStartsAtRequired),
136
+ endsAt: z.string().optional(),
137
+ timezone: z.string().min(1, messages.dialogs.slot.validationTimezoneRequired),
138
+ status: z.enum(["open", "closed", "sold_out", "cancelled"]),
139
+ unlimited: z.boolean(),
140
+ initialPax: z.string().optional(),
141
+ remainingPax: z.string().optional(),
142
+ initialPickups: z.string().optional(),
143
+ remainingPickups: z.string().optional(),
144
+ remainingResources: z.string().optional(),
145
+ pastCutoff: z.boolean(),
146
+ tooEarly: z.boolean(),
147
+ notes: z.string().optional(),
148
+ });
149
+ }
150
+ export function AvailabilitySlotDialog(props) {
151
+ const slotMessages = props.messages.dialogs.slot;
152
+ const slotFormSchema = getSlotFormSchema(props.messages);
153
+ const form = useForm({
154
+ resolver: zodResolver(slotFormSchema),
155
+ defaultValues: {
156
+ productId: "",
157
+ availabilityRuleId: NONE_VALUE,
158
+ startTimeId: NONE_VALUE,
159
+ dateLocal: "",
160
+ startsAt: "",
161
+ endsAt: "",
162
+ timezone: "Europe/Bucharest",
163
+ status: "open",
164
+ unlimited: false,
165
+ initialPax: "",
166
+ remainingPax: "",
167
+ initialPickups: "",
168
+ remainingPickups: "",
169
+ remainingResources: "",
170
+ pastCutoff: false,
171
+ tooEarly: false,
172
+ notes: "",
173
+ },
174
+ });
175
+ useEffect(() => {
176
+ if (props.open && props.slot) {
177
+ form.reset({
178
+ productId: props.slot.productId,
179
+ availabilityRuleId: props.slot.availabilityRuleId ?? NONE_VALUE,
180
+ startTimeId: props.slot.startTimeId ?? NONE_VALUE,
181
+ dateLocal: props.slot.dateLocal,
182
+ startsAt: toLocalDateTimeInput(props.slot.startsAt),
183
+ endsAt: toLocalDateTimeInput(props.slot.endsAt),
184
+ timezone: props.slot.timezone,
185
+ status: props.slot.status,
186
+ unlimited: props.slot.unlimited,
187
+ initialPax: props.slot.initialPax?.toString() ?? "",
188
+ remainingPax: props.slot.remainingPax?.toString() ?? "",
189
+ initialPickups: "",
190
+ remainingPickups: "",
191
+ remainingResources: "",
192
+ pastCutoff: false,
193
+ tooEarly: false,
194
+ notes: props.slot.notes ?? "",
195
+ });
196
+ }
197
+ else if (props.open) {
198
+ form.reset();
199
+ }
200
+ }, [form, props.open, props.slot]);
201
+ const selectedProductId = form.watch("productId");
202
+ const filteredRules = props.rules.filter((rule) => rule.productId === selectedProductId);
203
+ const filteredStartTimes = props.startTimes.filter((startTime) => startTime.productId === selectedProductId);
204
+ const isEditing = Boolean(props.slot);
205
+ const onSubmit = async (values) => {
206
+ await props.onSubmit({
207
+ productId: values.productId,
208
+ availabilityRuleId: values.availabilityRuleId === NONE_VALUE ? null : (values.availabilityRuleId ?? null),
209
+ startTimeId: values.startTimeId === NONE_VALUE ? null : (values.startTimeId ?? null),
210
+ dateLocal: values.dateLocal,
211
+ startsAt: new Date(values.startsAt).toISOString(),
212
+ endsAt: toIsoDateTime(values.endsAt),
213
+ timezone: values.timezone,
214
+ status: values.status,
215
+ unlimited: values.unlimited,
216
+ initialPax: nullableNumber(values.initialPax),
217
+ remainingPax: nullableNumber(values.remainingPax),
218
+ initialPickups: nullableNumber(values.initialPickups),
219
+ remainingPickups: nullableNumber(values.remainingPickups),
220
+ remainingResources: nullableNumber(values.remainingResources),
221
+ pastCutoff: values.pastCutoff,
222
+ tooEarly: values.tooEarly,
223
+ notes: nullableString(values.notes),
224
+ }, { isEditing, id: props.slot?.id });
225
+ props.onSuccess();
226
+ };
227
+ return (_jsx(Dialog, { open: props.open, onOpenChange: props.onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? slotMessages.editTitle : slotMessages.newTitle }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsx(ProductSelect, { label: slotMessages.productLabel, placeholder: slotMessages.selectProductPlaceholder, products: props.products, value: form.watch("productId"), onValueChange: (value) => form.setValue("productId", value ?? "") }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: slotMessages.ruleLabel }), _jsxs(Select, { value: form.watch("availabilityRuleId") ?? NONE_VALUE, onValueChange: (value) => form.setValue("availabilityRuleId", value ?? NONE_VALUE), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: slotMessages.optionalRulePlaceholder }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: NONE_VALUE, children: slotMessages.noRule }), filteredRules.map((rule) => (_jsxs(SelectItem, { value: rule.id, children: [rule.timezone, " \u00B7 ", rule.recurrenceRule] }, rule.id)))] })] })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: slotMessages.startTimeLabel }), _jsxs(Select, { value: form.watch("startTimeId") ?? NONE_VALUE, onValueChange: (value) => form.setValue("startTimeId", value ?? NONE_VALUE), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: slotMessages.optionalStartTimePlaceholder }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: NONE_VALUE, children: slotMessages.noStartTime }), filteredStartTimes.map((startTime) => (_jsx(SelectItem, { value: startTime.id, children: startTime.label ?? startTime.startTimeLocal }, startTime.id)))] })] })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: slotMessages.dateLabel }), _jsx(Input, { ...form.register("dateLocal"), type: "date" })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: slotMessages.timezoneLabel }), _jsx(Input, { ...form.register("timezone"), placeholder: slotMessages.timezonePlaceholder })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: slotMessages.startsAtLabel }), _jsx(Input, { ...form.register("startsAt"), type: "datetime-local" })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: slotMessages.endsAtLabel }), _jsx(Input, { ...form.register("endsAt"), type: "datetime-local" })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: slotMessages.statusLabel }), _jsxs(Select, { items: slotStatusOptions, value: form.watch("status"), onValueChange: (value) => form.setValue("status", value), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: slotStatusOptions.map((option) => (_jsx(SelectItem, { value: option.value, children: {
228
+ open: props.messages.statusOpen,
229
+ closed: props.messages.statusClosed,
230
+ sold_out: props.messages.statusSoldOut,
231
+ cancelled: props.messages.statusCancelled,
232
+ }[option.value] }, option.value))) })] })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: slotMessages.unlimitedLabel }), _jsxs(Select, { items: booleanOptions, value: String(form.watch("unlimited")), onValueChange: (value) => form.setValue("unlimited", value === "true"), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: booleanOptions.map((option) => (_jsx(SelectItem, { value: option.value, children: option.value === "true" ? slotMessages.yes : slotMessages.no }, option.value))) })] })] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: slotMessages.initialPaxLabel }), _jsx(Input, { ...form.register("initialPax"), type: "number", min: 0 })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: slotMessages.remainingPaxLabel }), _jsx(Input, { ...form.register("remainingPax"), type: "number", min: 0 })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: slotMessages.remainingResourcesLabel }), _jsx(Input, { ...form.register("remainingResources"), type: "number", min: 0 })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: slotMessages.initialPickupsLabel }), _jsx(Input, { ...form.register("initialPickups"), type: "number", min: 0 })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: slotMessages.remainingPickupsLabel }), _jsx(Input, { ...form.register("remainingPickups"), type: "number", min: 0 })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(SwitchField, { title: slotMessages.pastCutoffTitle, description: slotMessages.pastCutoffDescription, checked: form.watch("pastCutoff"), onCheckedChange: (checked) => form.setValue("pastCutoff", checked) }), _jsx(SwitchField, { title: slotMessages.tooEarlyTitle, description: slotMessages.tooEarlyDescription, checked: form.watch("tooEarly"), onCheckedChange: (checked) => form.setValue("tooEarly", checked) })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: slotMessages.notesLabel }), _jsx(Textarea, { ...form.register("notes"), placeholder: slotMessages.notesPlaceholder })] })] }), _jsx(DialogActions, { cancel: slotMessages.cancel, save: slotMessages.save, create: slotMessages.create, isEditing: isEditing, isSubmitting: form.formState.isSubmitting, onCancel: () => props.onOpenChange(false) })] })] }) }));
233
+ }
234
+ function getCloseoutFormSchema(messages) {
235
+ return z.object({
236
+ productId: z.string().min(1, messages.dialogs.closeout.validationProductRequired),
237
+ slotId: z.string().optional(),
238
+ dateLocal: z.string().min(1, messages.dialogs.closeout.validationDateRequired),
239
+ reason: z.string().optional(),
240
+ });
241
+ }
242
+ export function AvailabilityCloseoutDialog(props) {
243
+ const closeoutMessages = props.messages.dialogs.closeout;
244
+ const closeoutFormSchema = getCloseoutFormSchema(props.messages);
245
+ const form = useForm({
246
+ resolver: zodResolver(closeoutFormSchema),
247
+ defaultValues: {
248
+ productId: "",
249
+ slotId: NONE_VALUE,
250
+ dateLocal: "",
251
+ reason: "",
252
+ },
253
+ });
254
+ useEffect(() => {
255
+ if (props.open && props.closeout) {
256
+ form.reset({
257
+ productId: props.closeout.productId,
258
+ slotId: props.closeout.slotId ?? NONE_VALUE,
259
+ dateLocal: props.closeout.dateLocal,
260
+ reason: props.closeout.reason ?? "",
261
+ });
262
+ }
263
+ else if (props.open) {
264
+ form.reset();
265
+ }
266
+ }, [form, props.closeout, props.open]);
267
+ const selectedProductId = form.watch("productId");
268
+ const filteredSlots = props.slots.filter((slot) => slot.productId === selectedProductId);
269
+ const isEditing = Boolean(props.closeout);
270
+ const onSubmit = async (values) => {
271
+ await props.onSubmit({
272
+ productId: values.productId,
273
+ slotId: values.slotId === NONE_VALUE ? null : (values.slotId ?? null),
274
+ dateLocal: values.dateLocal,
275
+ reason: nullableString(values.reason),
276
+ }, { isEditing, id: props.closeout?.id });
277
+ props.onSuccess();
278
+ };
279
+ return (_jsx(Dialog, { open: props.open, onOpenChange: props.onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? closeoutMessages.editTitle : closeoutMessages.newTitle }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsx(ProductSelect, { label: closeoutMessages.productLabel, placeholder: closeoutMessages.selectProductPlaceholder, products: props.products, value: form.watch("productId"), onValueChange: (value) => form.setValue("productId", value ?? "") }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: closeoutMessages.slotLabel }), _jsxs(Select, { value: form.watch("slotId") ?? NONE_VALUE, onValueChange: (value) => form.setValue("slotId", value ?? NONE_VALUE), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: closeoutMessages.optionalSlotPlaceholder }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: NONE_VALUE, children: closeoutMessages.productLevelOption }), filteredSlots.map((slot) => (_jsxs(SelectItem, { value: slot.id, children: [slot.dateLocal, " \u00B7 ", formatDateTime(slot.startsAt)] }, slot.id)))] })] })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: closeoutMessages.dateLabel }), _jsx(DatePicker, { value: form.watch("dateLocal") || null, onChange: (nextValue) => form.setValue("dateLocal", nextValue ?? "", {
280
+ shouldDirty: true,
281
+ shouldValidate: true,
282
+ }), placeholder: closeoutMessages.datePlaceholder, className: "w-full" })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: closeoutMessages.reasonLabel }), _jsx(Textarea, { ...form.register("reason"), placeholder: closeoutMessages.reasonPlaceholder })] })] }), _jsx(DialogActions, { cancel: closeoutMessages.cancel, save: closeoutMessages.save, create: closeoutMessages.create, isEditing: isEditing, isSubmitting: form.formState.isSubmitting, onCancel: () => props.onOpenChange(false) })] })] }) }));
283
+ }
284
+ function getPickupPointFormSchema(messages) {
285
+ return z.object({
286
+ productId: z.string().min(1, messages.dialogs.pickupPoint.validationProductRequired),
287
+ name: z.string().min(1, messages.dialogs.pickupPoint.validationNameRequired),
288
+ description: z.string().optional(),
289
+ locationText: z.string().optional(),
290
+ active: z.boolean(),
291
+ });
292
+ }
293
+ export function AvailabilityPickupPointDialog(props) {
294
+ const pickupPointMessages = props.messages.dialogs.pickupPoint;
295
+ const pickupPointFormSchema = getPickupPointFormSchema(props.messages);
296
+ const form = useForm({
297
+ resolver: zodResolver(pickupPointFormSchema),
298
+ defaultValues: {
299
+ productId: "",
300
+ name: "",
301
+ description: "",
302
+ locationText: "",
303
+ active: true,
304
+ },
305
+ });
306
+ useEffect(() => {
307
+ if (props.open && props.pickupPoint) {
308
+ form.reset({
309
+ productId: props.pickupPoint.productId,
310
+ name: props.pickupPoint.name,
311
+ description: props.pickupPoint.description ?? "",
312
+ locationText: props.pickupPoint.locationText ?? "",
313
+ active: props.pickupPoint.active,
314
+ });
315
+ }
316
+ else if (props.open) {
317
+ form.reset();
318
+ }
319
+ }, [form, props.open, props.pickupPoint]);
320
+ const isEditing = Boolean(props.pickupPoint);
321
+ const onSubmit = async (values) => {
322
+ await props.onSubmit({
323
+ productId: values.productId,
324
+ name: values.name,
325
+ description: nullableString(values.description),
326
+ locationText: nullableString(values.locationText),
327
+ active: values.active,
328
+ }, { isEditing, id: props.pickupPoint?.id });
329
+ props.onSuccess();
330
+ };
331
+ return (_jsx(Dialog, { open: props.open, onOpenChange: props.onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? pickupPointMessages.editTitle : pickupPointMessages.newTitle }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsx(ProductSelect, { label: pickupPointMessages.productLabel, placeholder: pickupPointMessages.selectProductPlaceholder, products: props.products, value: form.watch("productId"), onValueChange: (value) => form.setValue("productId", value ?? "") }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: pickupPointMessages.nameLabel }), _jsx(Input, { ...form.register("name"), placeholder: pickupPointMessages.namePlaceholder })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: pickupPointMessages.locationTextLabel }), _jsx(Input, { ...form.register("locationText"), placeholder: pickupPointMessages.locationTextPlaceholder })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: pickupPointMessages.descriptionLabel }), _jsx(Textarea, { ...form.register("description"), placeholder: pickupPointMessages.descriptionPlaceholder })] }), _jsx(SwitchField, { title: pickupPointMessages.activeTitle, description: pickupPointMessages.activeDescription, checked: form.watch("active"), onCheckedChange: (checked) => form.setValue("active", checked) })] }), _jsx(DialogActions, { cancel: pickupPointMessages.cancel, save: pickupPointMessages.save, create: pickupPointMessages.create, isEditing: isEditing, isSubmitting: form.formState.isSubmitting, onCancel: () => props.onOpenChange(false) })] })] }) }));
332
+ }
333
+ function ProductSelect({ label, placeholder, products, value, onValueChange, }) {
334
+ return (_jsxs("div", { className: "grid gap-2", children: [_jsx(Label, { children: label }), _jsxs(Select, { items: products.map((product) => ({ label: product.name, value: product.id })), value: value, onValueChange: onValueChange, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: placeholder }) }), _jsx(SelectContent, { children: products.map((product) => (_jsx(SelectItem, { value: product.id, children: product.name }, product.id))) })] })] }));
335
+ }
336
+ function SwitchField({ title, description, checked, onCheckedChange, }) {
337
+ return (_jsxs("div", { className: "flex items-center justify-between rounded-md border px-3 py-2", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium", children: title }), _jsx("p", { className: "text-xs text-muted-foreground", children: description })] }), _jsx(Switch, { checked: checked, onCheckedChange: onCheckedChange })] }));
338
+ }
339
+ function DialogActions({ cancel, save, create, isEditing, isSubmitting, onCancel, }) {
340
+ return (_jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: onCancel, children: cancel }), _jsxs(Button, { type: "submit", disabled: isSubmitting, children: [isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), isEditing ? save : create] })] }));
341
+ }
@@ -0,0 +1,39 @@
1
+ import type { AvailabilityPickupPointRow, AvailabilityRuleRow, AvailabilitySlotRow, ProductOption } from "@voyantjs/availability-react";
2
+ import { type AvailabilityColumnsMessages } from "./availability-columns.js";
3
+ export interface AvailabilityOverviewMessages extends AvailabilityColumnsMessages {
4
+ allProducts: string;
5
+ clearFilters: string;
6
+ searchPlaceholder: string;
7
+ overview: {
8
+ openSlotsTitle: string;
9
+ openSlotsDescription: string;
10
+ constrainedSlotsTitle: string;
11
+ constrainedSlotsDescription: string;
12
+ activeRulesTitle: string;
13
+ activeRulesDescription: string;
14
+ pickupPointsTitle: string;
15
+ pickupPointsDescription: string;
16
+ capacityWatchlistTitle: string;
17
+ capacityWatchlistEmpty: string;
18
+ coverageGapsTitle: string;
19
+ coverageGapsEmpty: string;
20
+ coverageGapDescription: string;
21
+ };
22
+ }
23
+ export declare function AvailabilityOverview({ messages, products, constrainedSlots, filteredRules, filteredPickupPoints, productsWithoutActiveRules, search, setSearch, productFilter, setProductFilter, hasFilters, onClearFilters, onOpenSlot, onOpenProduct, }: {
24
+ messages: AvailabilityOverviewMessages;
25
+ products: ProductOption[];
26
+ constrainedSlots: AvailabilitySlotRow[];
27
+ filteredRules: AvailabilityRuleRow[];
28
+ filteredPickupPoints: AvailabilityPickupPointRow[];
29
+ productsWithoutActiveRules: ProductOption[];
30
+ search: string;
31
+ setSearch: (value: string) => void;
32
+ productFilter: string;
33
+ setProductFilter: (value: string) => void;
34
+ hasFilters: boolean;
35
+ onClearFilters: () => void;
36
+ onOpenSlot: (slotId: string) => void;
37
+ onOpenProduct: (productId: string) => void;
38
+ }): import("react/jsx-runtime").JSX.Element;
39
+ //# sourceMappingURL=availability-overview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"availability-overview.d.ts","sourceRoot":"","sources":["../../src/components/availability-overview.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,0BAA0B,EAC1B,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACd,MAAM,8BAA8B,CAAA;AAiBrC,OAAO,EAAE,KAAK,2BAA2B,EAAsB,MAAM,2BAA2B,CAAA;AAEhG,MAAM,WAAW,4BAA6B,SAAQ,2BAA2B;IAC/E,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,iBAAiB,EAAE,MAAM,CAAA;IACzB,QAAQ,EAAE;QACR,cAAc,EAAE,MAAM,CAAA;QACtB,oBAAoB,EAAE,MAAM,CAAA;QAC5B,qBAAqB,EAAE,MAAM,CAAA;QAC7B,2BAA2B,EAAE,MAAM,CAAA;QACnC,gBAAgB,EAAE,MAAM,CAAA;QACxB,sBAAsB,EAAE,MAAM,CAAA;QAC9B,iBAAiB,EAAE,MAAM,CAAA;QACzB,uBAAuB,EAAE,MAAM,CAAA;QAC/B,sBAAsB,EAAE,MAAM,CAAA;QAC9B,sBAAsB,EAAE,MAAM,CAAA;QAC9B,iBAAiB,EAAE,MAAM,CAAA;QACzB,iBAAiB,EAAE,MAAM,CAAA;QACzB,sBAAsB,EAAE,MAAM,CAAA;KAC/B,CAAA;CACF;AAED,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,QAAQ,EACR,gBAAgB,EAChB,aAAa,EACb,oBAAoB,EACpB,0BAA0B,EAC1B,MAAM,EACN,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,UAAU,EACV,aAAa,GACd,EAAE;IACD,QAAQ,EAAE,4BAA4B,CAAA;IACtC,QAAQ,EAAE,aAAa,EAAE,CAAA;IACzB,gBAAgB,EAAE,mBAAmB,EAAE,CAAA;IACvC,aAAa,EAAE,mBAAmB,EAAE,CAAA;IACpC,oBAAoB,EAAE,0BAA0B,EAAE,CAAA;IAClD,0BAA0B,EAAE,aAAa,EAAE,CAAA;IAC3C,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,aAAa,EAAE,MAAM,CAAA;IACrB,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,UAAU,EAAE,OAAO,CAAA;IACnB,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;CAC3C,2CA4HA"}
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { formatDateTime, productNameById } from "@voyantjs/availability-react";
4
+ import { Button, Card, CardContent, CardHeader, CardTitle, Input, OverviewMetric, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
5
+ import { CalendarDays, Clock3, Package, Search, Truck } from "lucide-react";
6
+ import { getSlotStatusLabel } from "./availability-columns.js";
7
+ export function AvailabilityOverview({ messages, products, constrainedSlots, filteredRules, filteredPickupPoints, productsWithoutActiveRules, search, setSearch, productFilter, setProductFilter, hasFilters, onClearFilters, onOpenSlot, onOpenProduct, }) {
8
+ const openSlotsCount = constrainedSlots.filter((slot) => slot.status === "open").length;
9
+ const activeRulesCount = filteredRules.filter((rule) => rule.active).length;
10
+ const activePickupPointsCount = filteredPickupPoints.filter((pickupPoint) => pickupPoint.active).length;
11
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-4", children: [_jsx(OverviewMetric, { title: messages.overview.openSlotsTitle, value: openSlotsCount, description: messages.overview.openSlotsDescription, icon: CalendarDays }), _jsx(OverviewMetric, { title: messages.overview.constrainedSlotsTitle, value: constrainedSlots.length, description: messages.overview.constrainedSlotsDescription, icon: Clock3 }), _jsx(OverviewMetric, { title: messages.overview.activeRulesTitle, value: activeRulesCount, description: messages.overview.activeRulesDescription, icon: Package }), _jsx(OverviewMetric, { title: messages.overview.pickupPointsTitle, value: activePickupPointsCount, description: messages.overview.pickupPointsDescription, icon: Truck })] }), _jsxs("div", { className: "grid gap-4 xl:grid-cols-2", children: [_jsxs(Card, { size: "sm", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: messages.overview.capacityWatchlistTitle }) }), _jsx(CardContent, { className: "space-y-3 text-sm", children: constrainedSlots.length === 0 ? (_jsx("p", { className: "text-muted-foreground", children: messages.overview.capacityWatchlistEmpty })) : (constrainedSlots.slice(0, 4).map((slot) => (_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: [productNameById(products, slot.productId, slot.productName), " \u00B7 ", slot.dateLocal] }), _jsxs("div", { className: "text-muted-foreground", children: [formatDateTime(slot.startsAt), " \u00B7 ", getSlotStatusLabel(slot.status, messages), " \u00B7", " ", messages.remainingPaxLabel, ": ", slot.remainingPax ?? messages.details.noValue] })] }, slot.id)))) })] }), _jsxs(Card, { size: "sm", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: messages.overview.coverageGapsTitle }) }), _jsx(CardContent, { className: "space-y-3 text-sm", children: productsWithoutActiveRules.length === 0 ? (_jsx("p", { className: "text-muted-foreground", children: messages.overview.coverageGapsEmpty })) : (productsWithoutActiveRules.slice(0, 4).map((product) => (_jsxs("button", { type: "button", className: "block w-full rounded-md border p-3 text-left hover:bg-muted/40", onClick: () => onOpenProduct(product.id), children: [_jsx("div", { className: "font-medium", children: product.name }), _jsx("div", { className: "text-muted-foreground", children: messages.overview.coverageGapDescription })] }, product.id)))) })] })] }), _jsxs("div", { className: "flex flex-col gap-3 md:flex-row md:items-center md:justify-between", children: [_jsxs("div", { className: "flex flex-1 flex-col gap-3 md:flex-row md:items-center", children: [_jsxs("div", { className: "relative w-full max-w-sm", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: messages.searchPlaceholder, value: search, onChange: (event) => setSearch(event.target.value), className: "pl-9" })] }), _jsxs(Select, { value: productFilter, onValueChange: (value) => setProductFilter(value ?? "all"), children: [_jsx(SelectTrigger, { className: "w-full md:w-56", children: _jsx(SelectValue, { placeholder: messages.allProducts }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: messages.allProducts }), products.map((product) => (_jsx(SelectItem, { value: product.id, children: product.name }, product.id)))] })] })] }), hasFilters ? (_jsx(Button, { variant: "outline", onClick: onClearFilters, children: messages.clearFilters })) : null] })] }));
12
+ }
@@ -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":"AAWA,wBAAgB,wBAAwB,4CAqEvC;AAED,wBAAgB,wBAAwB,4CAUvC;AAED,wBAAgB,8BAA8B,4CAmE7C;AAED,wBAAgB,mCAAmC,4CAqDlD;AAED,wBAAgB,8BAA8B,4CA6D7C"}
@@ -0,0 +1,32 @@
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
+ export function AvailabilityBodySkeleton() {
6
+ 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: "Date" }), _jsx(TableHead, { children: "Product" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { children: "Remaining" }), _jsx(TableHead, { children: "Capacity" })] }) }), _jsx(SkeletonTableRows, { rows: 6, columns: 5, columnWidths: ["w-24", "w-40", "w-16", "w-16", "w-16"] })] }) })] }));
7
+ }
8
+ export function AvailabilityPageSkeleton() {
9
+ 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, {})] }));
10
+ }
11
+ export function AvailabilityRuleDetailSkeleton() {
12
+ 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))) })] })] }));
13
+ }
14
+ export function AvailabilityStartTimeDetailSkeleton() {
15
+ 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))) })] })] })] }));
16
+ }
17
+ export function AvailabilitySlotDetailSkeleton() {
18
+ 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))) })] })] }));
19
+ }
20
+ function PairListCard({ titleWidth, rows }) {
21
+ 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))) })] }));
22
+ }
23
+ function SkeletonTableRows({ rows = 6, columns = 4, columnWidths, }) {
24
+ return (_jsx(TableBody, { children: Array.from({ length: rows }).map((_, r) => (_jsx(TableRow
25
+ // biome-ignore lint/suspicious/noArrayIndexKey: stable placeholders
26
+ , { children: Array.from({ length: columns }).map((__, c) => {
27
+ const width = columnWidths?.[c] ?? (c === 0 ? "w-2/3" : c === columns - 1 ? "w-16" : "w-1/2");
28
+ return (_jsx(TableCell
29
+ // biome-ignore lint/suspicious/noArrayIndexKey: stable placeholders
30
+ , { children: _jsx(Skeleton, { className: `h-4 ${width}` }) }, c));
31
+ }) }, r))) }));
32
+ }