@voyantjs/availability-react 0.106.0 → 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.
Files changed (72) hide show
  1. package/README.md +161 -1
  2. package/dist/admin/availability-index-host.d.ts +12 -0
  3. package/dist/admin/availability-index-host.d.ts.map +1 -0
  4. package/dist/admin/availability-index-host.js +125 -0
  5. package/dist/admin/availability-page-data.d.ts +9 -0
  6. package/dist/admin/availability-page-data.d.ts.map +1 -0
  7. package/dist/admin/availability-page-data.js +25 -0
  8. package/dist/admin/index.d.ts +69 -0
  9. package/dist/admin/index.d.ts.map +1 -0
  10. package/dist/admin/index.js +73 -0
  11. package/dist/admin/option-resource-templates-panel.d.ts +22 -0
  12. package/dist/admin/option-resource-templates-panel.d.ts.map +1 -0
  13. package/dist/admin/option-resource-templates-panel.js +251 -0
  14. package/dist/admin/rule-detail-host.d.ts +14 -0
  15. package/dist/admin/rule-detail-host.d.ts.map +1 -0
  16. package/dist/admin/rule-detail-host.js +27 -0
  17. package/dist/admin/slot-detail-host.d.ts +29 -0
  18. package/dist/admin/slot-detail-host.d.ts.map +1 -0
  19. package/dist/admin/slot-detail-host.js +110 -0
  20. package/dist/admin/start-time-detail-host.d.ts +15 -0
  21. package/dist/admin/start-time-detail-host.d.ts.map +1 -0
  22. package/dist/admin/start-time-detail-host.js +37 -0
  23. package/dist/components/availability-columns.d.ts +42 -0
  24. package/dist/components/availability-columns.d.ts.map +1 -0
  25. package/dist/components/availability-columns.js +182 -0
  26. package/dist/components/availability-dialogs.d.ts +236 -0
  27. package/dist/components/availability-dialogs.d.ts.map +1 -0
  28. package/dist/components/availability-dialogs.js +369 -0
  29. package/dist/components/availability-overview.d.ts +54 -0
  30. package/dist/components/availability-overview.d.ts.map +1 -0
  31. package/dist/components/availability-overview.js +50 -0
  32. package/dist/components/availability-page.d.ts +32 -0
  33. package/dist/components/availability-page.d.ts.map +1 -0
  34. package/dist/components/availability-page.js +128 -0
  35. package/dist/components/availability-rule-detail-page.d.ts +251 -0
  36. package/dist/components/availability-rule-detail-page.d.ts.map +1 -0
  37. package/dist/components/availability-rule-detail-page.js +74 -0
  38. package/dist/components/availability-section-header.d.ts +8 -0
  39. package/dist/components/availability-section-header.d.ts.map +1 -0
  40. package/dist/components/availability-section-header.js +7 -0
  41. package/dist/components/availability-skeletons.d.ts +6 -0
  42. package/dist/components/availability-skeletons.d.ts.map +1 -0
  43. package/dist/components/availability-skeletons.js +34 -0
  44. package/dist/components/availability-slot-detail-page.d.ts +974 -0
  45. package/dist/components/availability-slot-detail-page.d.ts.map +1 -0
  46. package/dist/components/availability-slot-detail-page.js +383 -0
  47. package/dist/components/availability-start-time-detail-page.d.ts +246 -0
  48. package/dist/components/availability-start-time-detail-page.d.ts.map +1 -0
  49. package/dist/components/availability-start-time-detail-page.js +83 -0
  50. package/dist/components/availability-tabs.d.ts +152 -0
  51. package/dist/components/availability-tabs.d.ts.map +1 -0
  52. package/dist/components/availability-tabs.js +192 -0
  53. package/dist/components/slot-status-tone.d.ts +15 -0
  54. package/dist/components/slot-status-tone.d.ts.map +1 -0
  55. package/dist/components/slot-status-tone.js +18 -0
  56. package/dist/form-resolver.d.ts +4 -0
  57. package/dist/form-resolver.d.ts.map +1 -0
  58. package/dist/form-resolver.js +40 -0
  59. package/dist/i18n/index.d.ts +2 -0
  60. package/dist/i18n/index.d.ts.map +1 -0
  61. package/dist/i18n/index.js +1 -0
  62. package/dist/i18n/provider.d.ts +2003 -0
  63. package/dist/i18n/provider.d.ts.map +1 -0
  64. package/dist/i18n/provider.js +102 -0
  65. package/dist/ui.d.ts +13 -0
  66. package/dist/ui.d.ts.map +1 -0
  67. package/dist/ui.js +12 -0
  68. package/dist/utils.d.ts +1 -0
  69. package/dist/utils.d.ts.map +1 -1
  70. package/dist/utils.js +3 -0
  71. package/package.json +92 -9
  72. package/src/styles.css +11 -0
@@ -0,0 +1,251 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useOperatorAdminMessages } from "@voyantjs/admin";
4
+ import { SeatMapBuilder } from "@voyantjs/allocation-ui";
5
+ import { formatMessage } from "@voyantjs/i18n";
6
+ import { useOptionUnits } from "@voyantjs/products-react";
7
+ import { Badge, Button, Collapsible, CollapsibleContent, CollapsibleTrigger, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
8
+ import { Armchair, Bed, CalendarCheck, ChevronDown, ChevronRight, Loader2, Pencil, Plus, Sparkles, Trash2, } from "lucide-react";
9
+ import { useMemo, useState } from "react";
10
+ import { seatLayoutSpecSchema, useMaterializeOpenSlotsMutation, useProductResourceTemplates, useResourceTemplateMutation, } from "../index.js";
11
+ // Defaults populate the namePattern field when the operator picks a kind. They
12
+ // are starting points the operator edits before saving, not display labels —
13
+ // the localized prompt copy lives in the dialog's namePatternPlaceholder.
14
+ const COMMON_KINDS = [
15
+ // i18n-literal-ok
16
+ { value: "room", defaultPattern: "Room {sequence}" },
17
+ // i18n-literal-ok
18
+ { value: "vehicle_seat", defaultPattern: "Seat {sequence}" },
19
+ // i18n-literal-ok
20
+ { value: "cabin", defaultPattern: "Cabin {sequence}" },
21
+ // i18n-literal-ok
22
+ { value: "flight_seat", defaultPattern: "Seat {sequence}" },
23
+ ];
24
+ export function OptionResourceTemplatesPanel({ productId, optionId, }) {
25
+ const adminMessages = useOperatorAdminMessages();
26
+ const t = adminMessages.availability.details.resourceTemplates;
27
+ const { data, isPending, isError } = useProductResourceTemplates({ productId });
28
+ const { upsert, remove } = useResourceTemplateMutation(productId);
29
+ const materializeOpenSlots = useMaterializeOpenSlotsMutation(productId);
30
+ const templates = useMemo(() => {
31
+ const option = (data?.data ?? []).find((entry) => entry.id === optionId);
32
+ return option?.templates ?? [];
33
+ }, [data?.data, optionId]);
34
+ const totalPerDeparture = useMemo(() => templates.reduce((sum, template) => sum + (template.defaultCount ?? 0), 0), [templates]);
35
+ // The option's room units already carry quantity (maxQuantity) and occupancy
36
+ // — generate departure inventory straight from them instead of re-typing it.
37
+ const { data: unitsData } = useOptionUnits({ optionId, limit: 100 });
38
+ const roomUnits = useMemo(() => (unitsData?.data ?? []).filter((unit) => unit.unitType === "room"), [unitsData?.data]);
39
+ const [open, setOpen] = useState(false);
40
+ const [dialogOpen, setDialogOpen] = useState(false);
41
+ const [seatMapOpen, setSeatMapOpen] = useState(false);
42
+ const [editingKind, setEditingKind] = useState(null);
43
+ // The ref of the template being edited, so submit updates that exact
44
+ // (option, kind, refId) row rather than colliding with siblings of the same
45
+ // kind. Null for ref-less templates (the manual "Add" path).
46
+ const [editingRefType, setEditingRefType] = useState(null);
47
+ const [editingRefId, setEditingRefId] = useState(null);
48
+ const [kindValue, setKindValue] = useState("room");
49
+ const [capacityValue, setCapacityValue] = useState(2);
50
+ const [defaultCountValue, setDefaultCountValue] = useState(1);
51
+ const [namePatternValue, setNamePatternValue] = useState("Room {sequence}");
52
+ const [layoutSpec, setLayoutSpec] = useState(null);
53
+ const [error, setError] = useState(null);
54
+ const [applyResult, setApplyResult] = useState(null);
55
+ const derivedSeatCount = useMemo(() => countSeats(layoutSpec), [layoutSpec]);
56
+ const usingSeatMap = kindValue === "vehicle_seat" && layoutSpec !== null;
57
+ function openCreate() {
58
+ setEditingKind(null);
59
+ setEditingRefType(null);
60
+ setEditingRefId(null);
61
+ setKindValue("room");
62
+ setCapacityValue(2);
63
+ setDefaultCountValue(1);
64
+ setNamePatternValue("Room {sequence}");
65
+ setLayoutSpec(null);
66
+ setError(null);
67
+ setDialogOpen(true);
68
+ }
69
+ function openEdit(template) {
70
+ setEditingKind(template.kind);
71
+ setEditingRefType(template.refType);
72
+ setEditingRefId(template.refId);
73
+ setKindValue(template.kind);
74
+ setCapacityValue(template.capacity);
75
+ setDefaultCountValue(template.defaultCount ?? 0);
76
+ setNamePatternValue(template.namePattern);
77
+ setLayoutSpec(extractLayoutSpec(template.flags));
78
+ setError(null);
79
+ setDialogOpen(true);
80
+ }
81
+ async function submit(event) {
82
+ event.preventDefault();
83
+ setError(null);
84
+ const trimmedKind = kindValue.trim();
85
+ const trimmedPattern = namePatternValue.trim();
86
+ const effectiveCapacity = usingSeatMap ? derivedSeatCount : capacityValue;
87
+ if (!trimmedKind || !trimmedPattern || effectiveCapacity < 1) {
88
+ setError(t.validation);
89
+ return;
90
+ }
91
+ const flags = {};
92
+ if (trimmedKind === "vehicle_seat" && layoutSpec) {
93
+ flags.layoutSpec = layoutSpec;
94
+ }
95
+ try {
96
+ await upsert.mutateAsync({
97
+ optionId,
98
+ kind: trimmedKind,
99
+ input: {
100
+ capacity: effectiveCapacity,
101
+ defaultCount: defaultCountValue > 0 ? defaultCountValue : null,
102
+ namePattern: trimmedPattern,
103
+ // Preserve the edited template's ref so the upsert targets its exact
104
+ // (option, kind, refId) row instead of clobbering a sibling.
105
+ refType: editingRefType,
106
+ refId: editingRefId,
107
+ flags,
108
+ },
109
+ });
110
+ setDialogOpen(false);
111
+ }
112
+ catch (err) {
113
+ setError(err instanceof Error ? err.message : t.saveFailed);
114
+ }
115
+ }
116
+ async function handleRemove(kind, refId, label) {
117
+ if (!globalThis.confirm?.(formatMessage(t.deleteConfirm, { kind: label })))
118
+ return;
119
+ try {
120
+ await remove.mutateAsync({ optionId, kind, refId });
121
+ }
122
+ catch (err) {
123
+ setError(err instanceof Error ? err.message : t.deleteFailed);
124
+ }
125
+ }
126
+ async function generateFromRooms() {
127
+ setError(null);
128
+ try {
129
+ for (const unit of roomUnits) {
130
+ await upsert.mutateAsync({
131
+ optionId,
132
+ // All room types share kind "room"; they're distinguished — and
133
+ // travelers are constrained to their booked type — by the option_unit
134
+ // ref (allocator's groupUnitMatchScore: refType "option_unit" + refId
135
+ // === bookedOptionUnitId). The widened (option, kind, ref) unique
136
+ // index lets one option carry a "room" template per unit.
137
+ kind: "room",
138
+ input: {
139
+ capacity: unit.occupancyMax ?? unit.occupancyMin ?? 1,
140
+ defaultCount: unit.maxQuantity ?? null,
141
+ // {index} numbers each room type from 1 (Double 1…20), not the
142
+ // global {sequence}, so the shared "room" pool reads cleanly.
143
+ namePattern: `${unit.name} {index}`,
144
+ refType: "option_unit",
145
+ refId: unit.id,
146
+ flags: {},
147
+ },
148
+ });
149
+ }
150
+ setOpen(true);
151
+ }
152
+ catch (err) {
153
+ setError(err instanceof Error ? err.message : t.saveFailed);
154
+ }
155
+ }
156
+ async function applyToOpenDepartures() {
157
+ if (!globalThis.confirm?.(t.applyToOpenConfirm))
158
+ return;
159
+ setError(null);
160
+ setApplyResult(null);
161
+ try {
162
+ const result = await materializeOpenSlots.mutateAsync({ optionId });
163
+ setApplyResult(result.slots === 0
164
+ ? t.applyToOpenEmpty
165
+ : formatMessage(t.applyToOpenResult, {
166
+ created: result.created,
167
+ slots: result.slots,
168
+ }));
169
+ }
170
+ catch (err) {
171
+ setError(err instanceof Error ? err.message : t.applyToOpenFailed);
172
+ }
173
+ }
174
+ const matchingCommon = COMMON_KINDS.find((entry) => entry.value === kindValue);
175
+ const isExtendedKind = !matchingCommon && kindValue.trim().length > 0;
176
+ return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, children: [_jsxs("div", { className: "rounded-md border bg-background/60", children: [_jsxs(CollapsibleTrigger, { className: "flex w-full items-center gap-2 px-3 py-2.5 text-left", children: [_jsx(Bed, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": "true" }), _jsx("span", { className: "font-medium text-sm", children: t.title }), _jsx(Badge, { variant: "secondary", className: "font-normal", children: templates.length === 0
177
+ ? t.collapsedEmpty
178
+ : formatMessage(t.collapsedSummary, {
179
+ count: templates.length,
180
+ total: totalPerDeparture,
181
+ }) }), _jsx("span", { className: "flex-1" }), open ? (_jsx(ChevronDown, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": "true" })) : (_jsx(ChevronRight, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": "true" }))] }), _jsx(CollapsibleContent, { children: _jsxs("div", { className: "flex flex-col gap-3 border-t p-3", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsx("p", { className: "text-muted-foreground text-xs", children: t.description }), _jsxs("div", { className: "flex shrink-0 flex-wrap items-center justify-end gap-2", children: [templates.length > 0 ? (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => void applyToOpenDepartures(), disabled: materializeOpenSlots.isPending, children: [_jsx(CalendarCheck, { className: "mr-1 size-4", "aria-hidden": "true" }), t.applyToOpenButton] })) : null, roomUnits.length > 0 ? (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => void generateFromRooms(), disabled: upsert.isPending, children: [_jsx(Sparkles, { className: "mr-1 size-4", "aria-hidden": "true" }), t.generateFromRooms] })) : null, _jsxs(Button, { size: "sm", variant: "outline", onClick: openCreate, children: [_jsx(Plus, { className: "mr-1 size-4", "aria-hidden": "true" }), t.addButton] })] })] }), applyResult ? (_jsx("p", { className: "rounded-md bg-muted/50 px-3 py-2 text-muted-foreground text-xs", children: applyResult })) : null, error ? _jsx("p", { className: "text-destructive text-xs", children: error }) : null, isPending ? (_jsx("p", { className: "flex items-center justify-center gap-2 py-4 text-muted-foreground text-sm", children: _jsx(Loader2, { className: "size-3.5 animate-spin" }) })) : isError ? (_jsx("p", { className: "py-4 text-center text-destructive text-sm", children: t.loadFailed })) : templates.length === 0 ? (_jsx("p", { className: "rounded-md border border-dashed px-3 py-4 text-center text-muted-foreground text-xs", children: roomUnits.length > 0 ? t.generateFromRoomsHint : t.emptyMessage })) : (_jsx("ul", { className: "divide-y overflow-hidden rounded-md border", children: templates.map((template) => {
182
+ const kindLabel = t.kinds[template.kind] ?? template.kind;
183
+ // Several "room" templates share a kind but map to different
184
+ // option_units — surface the unit name so the operator can
185
+ // tell Single / Double / Triple rows apart.
186
+ const unit = template.refId
187
+ ? roomUnits.find((entry) => entry.id === template.refId)
188
+ : undefined;
189
+ const displayLabel = unit?.name ?? kindLabel;
190
+ return (_jsxs("li", { className: "flex items-center justify-between gap-3 px-3 py-2.5", children: [_jsx("div", { className: "min-w-0 flex-1", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Badge, { variant: "outline", className: "text-[10px]", children: displayLabel }), _jsx("span", { className: "text-sm", children: formatMessage(t.capacitySummary, {
191
+ capacity: template.capacity,
192
+ count: template.defaultCount ?? 0,
193
+ pattern: template.namePattern,
194
+ }) }), template.kind === "vehicle_seat" ? (_jsx(SeatMapSummaryBadge, { flags: template.flags, messages: t })) : null] }) }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { size: "sm", variant: "ghost", className: "h-7 px-2", onClick: () => openEdit({
195
+ kind: template.kind,
196
+ refType: template.refType,
197
+ refId: template.refId,
198
+ capacity: template.capacity,
199
+ defaultCount: template.defaultCount,
200
+ namePattern: template.namePattern,
201
+ flags: template.flags,
202
+ }), children: _jsx(Pencil, { className: "size-3.5", "aria-hidden": "true" }) }), _jsx(Button, { size: "sm", variant: "ghost", className: "h-7 px-2 text-destructive hover:text-destructive", onClick: () => void handleRemove(template.kind, template.refId, displayLabel), disabled: remove.isPending, children: _jsx(Trash2, { className: "size-3.5", "aria-hidden": "true" }) })] })] }, template.id));
203
+ }) }))] }) })] }), _jsx(Dialog, { open: dialogOpen, onOpenChange: setDialogOpen, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: editingKind ? formatMessage(t.editTitle, { kind: editingKind }) : t.newTitle }) }), _jsxs("form", { onSubmit: submit, children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "resource-template-kind", children: t.kindLabel }), _jsxs(Select, { value: isExtendedKind ? "__custom__" : kindValue, onValueChange: (value) => {
204
+ if (!value || value === "__custom__") {
205
+ setKindValue("");
206
+ return;
207
+ }
208
+ setKindValue(value);
209
+ const found = COMMON_KINDS.find((entry) => entry.value === value);
210
+ if (found && namePatternValue === "Room {sequence}") {
211
+ setNamePatternValue(found.defaultPattern);
212
+ }
213
+ }, disabled: editingKind !== null, children: [_jsx(SelectTrigger, { id: "resource-template-kind", className: "w-full", children: _jsx(SelectValue, { placeholder: t.kindPlaceholder }) }), _jsxs(SelectContent, { children: [COMMON_KINDS.map((entry) => (_jsx(SelectItem, { value: entry.value, children: t.kinds[entry.value] }, entry.value))), _jsx(SelectItem, { value: "__custom__", children: t.kindCustomOption })] })] }), isExtendedKind || editingKind ? (_jsx(Input, { value: kindValue, onChange: (event) => setKindValue(event.target.value), placeholder: t.kindCustomInputPlaceholder, disabled: editingKind !== null })) : null] }), kindValue === "vehicle_seat" ? (_jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { children: t.seatMapLabel }), layoutSpec ? (_jsxs("div", { className: "flex items-center gap-2 rounded-md border p-3", children: [_jsx(Armchair, { className: "size-4 text-muted-foreground", "aria-hidden": "true" }), _jsx("div", { className: "min-w-0 flex-1 text-sm", children: formatMessage(t.seatMapSummary, {
214
+ rows: layoutSpec.rows.length,
215
+ count: derivedSeatCount,
216
+ }) }), _jsxs(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => setSeatMapOpen(true), children: [_jsx(Pencil, { className: "mr-1 size-3.5", "aria-hidden": "true" }), t.seatMapEditButton] })] })) : (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border border-dashed p-3 text-sm", children: [_jsx("p", { className: "text-muted-foreground", children: t.seatMapEmpty }), _jsxs(Button, { type: "button", size: "sm", variant: "outline", onClick: () => setSeatMapOpen(true), children: [_jsx(Armchair, { className: "mr-1 size-3.5", "aria-hidden": "true" }), t.seatMapEditButton] })] }))] })) : null, _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "resource-template-capacity", children: t.capacityLabel }), _jsx(Input, { id: "resource-template-capacity", type: "number", min: 1, value: usingSeatMap ? derivedSeatCount : capacityValue, onChange: (event) => setCapacityValue(Number(event.target.value) || 1), disabled: usingSeatMap }), usingSeatMap ? (_jsx("p", { className: "text-muted-foreground text-xs", children: formatMessage(t.capacityDerivedHint, { count: derivedSeatCount }) })) : null] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "resource-template-count", children: t.defaultCountLabel }), _jsx(Input, { id: "resource-template-count", type: "number", min: 0, value: defaultCountValue, onChange: (event) => setDefaultCountValue(Number(event.target.value) || 0) }), _jsx("p", { className: "text-muted-foreground text-xs", children: t.defaultCountHint })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "resource-template-pattern", children: t.namePatternLabel }), _jsx(Input, { id: "resource-template-pattern", value: namePatternValue, onChange: (event) => setNamePatternValue(event.target.value), placeholder: t.namePatternPlaceholder }), _jsx("p", { className: "text-muted-foreground text-xs", children: (() => {
217
+ const [before, after] = t.namePatternHint.split("{placeholder}");
218
+ return (_jsxs(_Fragment, { children: [before, _jsx("code", { children: "{sequence}" }), after] }));
219
+ })() })] }), error ? _jsx("p", { className: "text-destructive text-xs", children: error }) : null] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => setDialogOpen(false), children: t.cancel }), _jsxs(Button, { type: "submit", disabled: upsert.isPending, children: [upsert.isPending ? (_jsx(Loader2, { className: "mr-1 size-3.5 animate-spin", "aria-hidden": "true" })) : null, editingKind ? t.save : t.createButton] })] })] })] }) }), _jsx(Dialog, { open: seatMapOpen, onOpenChange: setSeatMapOpen, children: _jsxs(DialogContent, { className: "max-w-3xl", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: t.seatMapDialogTitle }) }), _jsx(DialogBody, { children: _jsx(SeatMapBuilder, { value: layoutSpec, onChange: setLayoutSpec }) }), _jsx(DialogFooter, { children: _jsx(Button, { type: "button", onClick: () => setSeatMapOpen(false), children: t.seatMapDialogDone }) })] }) })] }));
220
+ }
221
+ function SeatMapSummaryBadge({ flags, messages, }) {
222
+ const spec = extractLayoutSpec(flags);
223
+ if (!spec)
224
+ return null;
225
+ return (_jsx(Badge, { variant: "outline", className: "text-[10px]", children: formatMessage(messages.seatMapSummary, {
226
+ rows: spec.rows.length,
227
+ count: countSeats(spec),
228
+ }) }));
229
+ }
230
+ function extractLayoutSpec(flags) {
231
+ const raw = flags?.layoutSpec;
232
+ if (!raw)
233
+ return null;
234
+ // Validate against the schema rather than trust the shape — the server
235
+ // stores flags as opaque JSON, so a malformed value (e.g. a row missing
236
+ // `cells`) could otherwise reach `countSeats` and throw at runtime.
237
+ const parsed = seatLayoutSpecSchema.safeParse(raw);
238
+ return parsed.success ? parsed.data : null;
239
+ }
240
+ function countSeats(spec) {
241
+ if (!spec)
242
+ return 0;
243
+ let count = 0;
244
+ for (const row of spec.rows) {
245
+ for (const cell of row.cells) {
246
+ if (cell === "seat")
247
+ count += 1;
248
+ }
249
+ }
250
+ return count;
251
+ }
@@ -0,0 +1,14 @@
1
+ export interface AvailabilityRuleDetailHostProps {
2
+ /** The availability rule id (route param, bound by the host route file). */
3
+ ruleId: string;
4
+ }
5
+ /**
6
+ * Packaged admin host for the availability rule detail page (packaged-admin
7
+ * RFC Phase 3). Data wiring runs through the shared availability provider
8
+ * context; breadcrumbs through the admin chrome; cross-route links through
9
+ * the semantic destinations `availabilitySlot.list`, `availabilitySlot.detail`
10
+ * and `product.detail` (RFC §4.7). The SSR prefetch loader stays in the host
11
+ * route file with the app's cookie-forwarding fetcher.
12
+ */
13
+ export declare function AvailabilityRuleDetailHost({ ruleId }: AvailabilityRuleDetailHostProps): import("react/jsx-runtime").JSX.Element;
14
+ //# sourceMappingURL=rule-detail-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-detail-host.d.ts","sourceRoot":"","sources":["../../src/admin/rule-detail-host.tsx"],"names":[],"mappings":"AAeA,MAAM,WAAW,+BAA+B;IAC9C,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,EAAE,MAAM,EAAE,EAAE,+BAA+B,2CAsBrF"}
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { useAdminBreadcrumbs, useAdminHref, useAdminNavigate, useOperatorAdminMessages, } from "@voyantjs/admin";
5
+ import { AvailabilityRuleDetailPage, getAvailabilityRuleDetailQueryOptions, } from "../components/availability-rule-detail-page.js";
6
+ import { useVoyantAvailabilityContext } from "../index.js";
7
+ /**
8
+ * Packaged admin host for the availability rule detail page (packaged-admin
9
+ * RFC Phase 3). Data wiring runs through the shared availability provider
10
+ * context; breadcrumbs through the admin chrome; cross-route links through
11
+ * the semantic destinations `availabilitySlot.list`, `availabilitySlot.detail`
12
+ * and `product.detail` (RFC §4.7). The SSR prefetch loader stays in the host
13
+ * route file with the app's cookie-forwarding fetcher.
14
+ */
15
+ export function AvailabilityRuleDetailHost({ ruleId }) {
16
+ const messages = useOperatorAdminMessages();
17
+ const resolveHref = useAdminHref();
18
+ const navigateTo = useAdminNavigate();
19
+ const client = useVoyantAvailabilityContext();
20
+ const ruleQuery = useQuery(getAvailabilityRuleDetailQueryOptions(client, ruleId));
21
+ const rule = ruleQuery.data?.data;
22
+ useAdminBreadcrumbs([
23
+ { label: messages.availability.title, href: resolveHref("availabilitySlot.list", {}) },
24
+ ...(rule ? [{ label: rule.productName ?? `Rule ${rule.id.slice(-6)}` }] : []),
25
+ ]);
26
+ return (_jsx(AvailabilityRuleDetailPage, { id: ruleId, onBack: () => navigateTo("availabilitySlot.list", {}), onDeleted: () => navigateTo("availabilitySlot.list", {}), onOpenProduct: (productId) => navigateTo("product.detail", { productId }), onOpenSlot: (slotId) => navigateTo("availabilitySlot.detail", { slotId }) }));
27
+ }
@@ -0,0 +1,29 @@
1
+ export interface AvailabilitySlotDetailHostProps {
2
+ /** The availability slot id (route param, bound by the host route file). */
3
+ slotId: string;
4
+ }
5
+ /**
6
+ * Packaged admin host for the availability slot detail page (packaged-admin
7
+ * RFC Phase 3). Owns everything package-clean:
8
+ *
9
+ * - Data wiring through the shared availability provider context
10
+ * (`useVoyantAvailabilityContext`) — the workspace shell mounts
11
+ * `VoyantAvailabilityProvider`, so no per-route provider or app env
12
+ * helper is needed.
13
+ * - Admin chrome breadcrumbs (`useAdminBreadcrumbs`).
14
+ * - Cross-route links resolve through semantic destinations (RFC §4.7):
15
+ * `availabilitySlot.list`, `availabilityStartTime.detail`,
16
+ * `booking.detail`, `product.detail` — no host route tree import.
17
+ * - The cross-domain composition the operator route previously assembled:
18
+ * the Allocation tab (`@voyantjs/allocation-ui`), the Extras manifest tab
19
+ * (`@voyantjs/extras-react/ui`), the booking create/quick-view sheets
20
+ * (`@voyantjs/bookings-react/ui`, lazy) and the product quick-view sheet
21
+ * (`@voyantjs/products-react/ui`).
22
+ * - The slot edit dialog, submitting through the package mutation
23
+ * (`useAvailabilitySlotMutation`) instead of an app RPC client.
24
+ *
25
+ * The SSR prefetch loader stays in the host route file (it runs outside the
26
+ * React tree with the app's cookie-forwarding fetcher).
27
+ */
28
+ export declare function AvailabilitySlotDetailHost({ slotId }: AvailabilitySlotDetailHostProps): import("react/jsx-runtime").JSX.Element;
29
+ //# sourceMappingURL=slot-detail-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot-detail-host.d.ts","sourceRoot":"","sources":["../../src/admin/slot-detail-host.tsx"],"names":[],"mappings":"AAyCA,MAAM,WAAW,+BAA+B;IAC9C,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,0BAA0B,CAAC,EAAE,MAAM,EAAE,EAAE,+BAA+B,2CAqIrF"}
@@ -0,0 +1,110 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { useAdminBreadcrumbs, useAdminHref, useAdminNavigate, useOperatorAdminMessages, } from "@voyantjs/admin";
5
+ import { SlotAllocationPage } from "@voyantjs/allocation-ui";
6
+ import { SlotExtrasManifestPanel, useExtrasUiMessagesOrDefault } from "@voyantjs/extras-react/ui";
7
+ import { ProductQuickViewSheet } from "@voyantjs/products-react/ui";
8
+ import { lazy, Suspense, useState } from "react";
9
+ import { AvailabilitySlotDialog } from "../components/availability-dialogs.js";
10
+ import { AvailabilitySlotDetailPage, getAvailabilitySlotDetailQueryOptions, getAvailabilitySlotProductQueryOptions, } from "../components/availability-slot-detail-page.js";
11
+ import { useAvailabilitySlotMutation, useRules, useStartTimes, useVoyantAvailabilityContext, } from "../index.js";
12
+ // Lazy: the booking sheets pull the bookings-ui bundle; only operators who
13
+ // actually create/preview a booking from a slot pay for it.
14
+ const BookingCreateSheet = lazy(() => import("@voyantjs/bookings-react/components/booking-create-sheet").then((module) => ({
15
+ default: module.BookingCreateSheet,
16
+ })));
17
+ const BookingQuickViewSheet = lazy(() => import("@voyantjs/bookings-react/components/booking-quick-view-sheet").then((module) => ({
18
+ default: module.BookingQuickViewSheet,
19
+ })));
20
+ /**
21
+ * Packaged admin host for the availability slot detail page (packaged-admin
22
+ * RFC Phase 3). Owns everything package-clean:
23
+ *
24
+ * - Data wiring through the shared availability provider context
25
+ * (`useVoyantAvailabilityContext`) — the workspace shell mounts
26
+ * `VoyantAvailabilityProvider`, so no per-route provider or app env
27
+ * helper is needed.
28
+ * - Admin chrome breadcrumbs (`useAdminBreadcrumbs`).
29
+ * - Cross-route links resolve through semantic destinations (RFC §4.7):
30
+ * `availabilitySlot.list`, `availabilityStartTime.detail`,
31
+ * `booking.detail`, `product.detail` — no host route tree import.
32
+ * - The cross-domain composition the operator route previously assembled:
33
+ * the Allocation tab (`@voyantjs/allocation-ui`), the Extras manifest tab
34
+ * (`@voyantjs/extras-react/ui`), the booking create/quick-view sheets
35
+ * (`@voyantjs/bookings-react/ui`, lazy) and the product quick-view sheet
36
+ * (`@voyantjs/products-react/ui`).
37
+ * - The slot edit dialog, submitting through the package mutation
38
+ * (`useAvailabilitySlotMutation`) instead of an app RPC client.
39
+ *
40
+ * The SSR prefetch loader stays in the host route file (it runs outside the
41
+ * React tree with the app's cookie-forwarding fetcher).
42
+ */
43
+ export function AvailabilitySlotDetailHost({ slotId }) {
44
+ const messages = useOperatorAdminMessages();
45
+ const extrasMessages = useExtrasUiMessagesOrDefault();
46
+ const resolveHref = useAdminHref();
47
+ const navigateTo = useAdminNavigate();
48
+ const client = useVoyantAvailabilityContext();
49
+ const slotMutation = useAvailabilitySlotMutation();
50
+ const slotQuery = useQuery(getAvailabilitySlotDetailQueryOptions(client, slotId));
51
+ const slot = slotQuery.data?.data;
52
+ const productQuery = useQuery({
53
+ ...getAvailabilitySlotProductQueryOptions(client, slot?.productId ?? null),
54
+ enabled: Boolean(slot?.productId),
55
+ });
56
+ const productName = productQuery.data?.data?.name ?? null;
57
+ const [bookingPreviewId, setBookingPreviewId] = useState(null);
58
+ const [productPreviewId, setProductPreviewId] = useState(null);
59
+ const [bookingCreateDefaults, setBookingCreateDefaults] = useState(null);
60
+ const [editDialogOpen, setEditDialogOpen] = useState(false);
61
+ // Lazy-load rules + start times only when the edit dialog opens —
62
+ // the slot detail view itself doesn't need them. Scope to the slot's
63
+ // product so the dialog only suggests recurring rules / start times
64
+ // that already belong to this product.
65
+ const rulesQuery = useRules({ productId: slot?.productId, enabled: editDialogOpen });
66
+ const startTimesQuery = useStartTimes({
67
+ productId: slot?.productId,
68
+ enabled: editDialogOpen,
69
+ });
70
+ useAdminBreadcrumbs([
71
+ { label: messages.availability.title, href: resolveHref("availabilitySlot.list", {}) },
72
+ ...(slot
73
+ ? [
74
+ {
75
+ label: productName ? `${productName} · ${slot.dateLocal}` : `Slot · ${slot.dateLocal}`,
76
+ },
77
+ ]
78
+ : []),
79
+ ]);
80
+ return (_jsxs(_Fragment, { children: [_jsx(AvailabilitySlotDetailPage, { id: slotId, onBack: () => navigateTo("availabilitySlot.list", {}), onDeleted: () => navigateTo("availabilitySlot.list", {}), onOpenProduct: (productId) => setProductPreviewId(productId), onOpenStartTime: (startTimeId) => navigateTo("availabilityStartTime.detail", { startTimeId }), onCreateBooking: (input) => setBookingCreateDefaults(input), onEdit: () => setEditDialogOpen(true), renderAllocation: ({ slotId: allocationSlotId }) => (_jsx(SlotAllocationPage, { slotId: allocationSlotId, embed: true, onBookingOpen: (bookingId) => setBookingPreviewId(bookingId) })), renderExtras: ({ slotId: extrasSlotId }) => (_jsx(SlotExtrasManifestPanel, { slotId: extrasSlotId })), extrasTabLabel: extrasMessages.slotManifest.title }), _jsx(Suspense, { fallback: null, children: _jsx(BookingCreateSheet, { open: Boolean(bookingCreateDefaults), onOpenChange: (open) => {
81
+ if (!open)
82
+ setBookingCreateDefaults(null);
83
+ }, defaultProductId: bookingCreateDefaults?.productId, defaultSlotId: bookingCreateDefaults?.slotId, onCreated: (booking) => setBookingPreviewId(booking.id) }) }), _jsx(Suspense, { fallback: null, children: _jsx(BookingQuickViewSheet, { bookingId: bookingPreviewId, open: bookingPreviewId !== null, onOpenChange: (open) => {
84
+ if (!open)
85
+ setBookingPreviewId(null);
86
+ }, onViewFull: (booking) => {
87
+ setBookingPreviewId(null);
88
+ navigateTo("booking.detail", { bookingId: booking.id });
89
+ } }) }), _jsx(ProductQuickViewSheet, { productId: productPreviewId, open: productPreviewId !== null, onOpenChange: (open) => {
90
+ if (!open)
91
+ setProductPreviewId(null);
92
+ }, onViewFull: (product) => {
93
+ setProductPreviewId(null);
94
+ navigateTo("product.detail", { productId: product.id });
95
+ } }), slot && productQuery.data?.data ? (_jsx(AvailabilitySlotDialog, { messages: messages.availability, open: editDialogOpen, onOpenChange: setEditDialogOpen, slot: slot, products: [productQuery.data.data], rules: rulesQuery.data?.data ?? [], startTimes: startTimesQuery.data?.data ?? [], onSubmit: async (payload, context) => {
96
+ if (context.isEditing) {
97
+ if (!context.id)
98
+ throw new Error("Slot edit requires an id.");
99
+ await slotMutation.update.mutateAsync({
100
+ id: context.id,
101
+ input: payload,
102
+ });
103
+ return;
104
+ }
105
+ await slotMutation.create.mutateAsync(payload);
106
+ }, onSuccess: () => {
107
+ setEditDialogOpen(false);
108
+ void slotQuery.refetch();
109
+ } })) : null] }));
110
+ }
@@ -0,0 +1,15 @@
1
+ export interface AvailabilityStartTimeDetailHostProps {
2
+ /** The availability start time id (route param, bound by the host route file). */
3
+ startTimeId: string;
4
+ }
5
+ /**
6
+ * Packaged admin host for the availability start time detail page
7
+ * (packaged-admin RFC Phase 3). Data wiring runs through the shared
8
+ * availability provider context; breadcrumbs through the admin chrome;
9
+ * cross-route links through the semantic destinations
10
+ * `availabilitySlot.list`, `availabilitySlot.detail` and `product.detail`
11
+ * (RFC §4.7). The SSR prefetch loader stays in the host route file with the
12
+ * app's cookie-forwarding fetcher.
13
+ */
14
+ export declare function AvailabilityStartTimeDetailHost({ startTimeId, }: AvailabilityStartTimeDetailHostProps): import("react/jsx-runtime").JSX.Element;
15
+ //# sourceMappingURL=start-time-detail-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start-time-detail-host.d.ts","sourceRoot":"","sources":["../../src/admin/start-time-detail-host.tsx"],"names":[],"mappings":"AAeA,MAAM,WAAW,oCAAoC;IACnD,kFAAkF;IAClF,WAAW,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,+BAA+B,CAAC,EAC9C,WAAW,GACZ,EAAE,oCAAoC,2CAgCtC"}
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { useAdminBreadcrumbs, useAdminHref, useAdminNavigate, useOperatorAdminMessages, } from "@voyantjs/admin";
5
+ import { AvailabilityStartTimeDetailPage, getAvailabilityStartTimeDetailQueryOptions, } from "../components/availability-start-time-detail-page.js";
6
+ import { useVoyantAvailabilityContext } from "../index.js";
7
+ /**
8
+ * Packaged admin host for the availability start time detail page
9
+ * (packaged-admin RFC Phase 3). Data wiring runs through the shared
10
+ * availability provider context; breadcrumbs through the admin chrome;
11
+ * cross-route links through the semantic destinations
12
+ * `availabilitySlot.list`, `availabilitySlot.detail` and `product.detail`
13
+ * (RFC §4.7). The SSR prefetch loader stays in the host route file with the
14
+ * app's cookie-forwarding fetcher.
15
+ */
16
+ export function AvailabilityStartTimeDetailHost({ startTimeId, }) {
17
+ const messages = useOperatorAdminMessages();
18
+ const resolveHref = useAdminHref();
19
+ const navigateTo = useAdminNavigate();
20
+ const client = useVoyantAvailabilityContext();
21
+ const startTimeQuery = useQuery(getAvailabilityStartTimeDetailQueryOptions(client, startTimeId));
22
+ const startTime = startTimeQuery.data?.data;
23
+ const startTimeFallback = messages.availability.details.startTime.fallbackTitle;
24
+ useAdminBreadcrumbs([
25
+ { label: messages.availability.title, href: resolveHref("availabilitySlot.list", {}) },
26
+ ...(startTime
27
+ ? [
28
+ {
29
+ label: startTime.label
30
+ ? `${startTime.productName ?? startTimeFallback} · ${startTime.label}`
31
+ : (startTime.productName ?? `${startTimeFallback} ${startTime.startTimeLocal}`),
32
+ },
33
+ ]
34
+ : []),
35
+ ]);
36
+ return (_jsx(AvailabilityStartTimeDetailPage, { id: startTimeId, onBack: () => navigateTo("availabilitySlot.list", {}), onDeleted: () => navigateTo("availabilitySlot.list", {}), onOpenProduct: (productId) => navigateTo("product.detail", { productId }), onOpenSlot: (slotId) => navigateTo("availabilitySlot.detail", { slotId }) }));
37
+ }
@@ -0,0 +1,42 @@
1
+ import type { ColumnDef } from "@tanstack/react-table";
2
+ import { type AvailabilityCloseoutRow, type AvailabilityPickupPointRow, type AvailabilityRuleRow, type AvailabilitySlotRow, type AvailabilityStartTimeRow, type ProductOption } from "../index.js";
3
+ export interface AvailabilityColumnsMessages {
4
+ activeLabel: string;
5
+ dateLabel: string;
6
+ details: {
7
+ noValue: string;
8
+ };
9
+ durationLabel: string;
10
+ labelLabel: string;
11
+ locationLabel: string;
12
+ maxPaxLabel: string;
13
+ nameLabel: string;
14
+ openLabel: string;
15
+ editLabel: string;
16
+ productLabel: string;
17
+ productLevelLabel: string;
18
+ reasonLabel: string;
19
+ recurrenceLabel: string;
20
+ remainingPaxLabel: string;
21
+ totalPaxLabel: string;
22
+ endsAtLabel: string;
23
+ slotLabel: string;
24
+ startLabel: string;
25
+ startsAtLabel: string;
26
+ statusActive: string;
27
+ statusCancelled: string;
28
+ statusClosed: string;
29
+ statusInactive: string;
30
+ statusLabel: string;
31
+ statusOpen: string;
32
+ statusSoldOut: string;
33
+ timezoneLabel: string;
34
+ viewLabel: string;
35
+ }
36
+ export declare function getSlotStatusLabel(status: AvailabilitySlotRow["status"], messages: AvailabilityColumnsMessages): string;
37
+ export declare const availabilityRuleColumns: (products: ProductOption[], onView: (ruleId: string) => void, messages: AvailabilityColumnsMessages) => ColumnDef<AvailabilityRuleRow>[];
38
+ export declare const availabilityStartTimeColumns: (products: ProductOption[], onView: (startTimeId: string) => void, messages: AvailabilityColumnsMessages) => ColumnDef<AvailabilityStartTimeRow>[];
39
+ export declare const availabilitySlotColumns: (products: ProductOption[], onView: (slotId: string) => void, messages: AvailabilityColumnsMessages, onEdit?: (slot: AvailabilitySlotRow) => void) => ColumnDef<AvailabilitySlotRow>[];
40
+ export declare const availabilityCloseoutColumns: (products: ProductOption[], messages: AvailabilityColumnsMessages) => ColumnDef<AvailabilityCloseoutRow>[];
41
+ export declare const availabilityPickupPointColumns: (products: ProductOption[], messages: AvailabilityColumnsMessages) => ColumnDef<AvailabilityPickupPointRow>[];
42
+ //# sourceMappingURL=availability-columns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"availability-columns.d.ts","sourceRoot":"","sources":["../../src/components/availability-columns.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAMtD,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,aAAa,EAKnB,MAAM,aAAa,CAAA;AAGpB,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAA;KAChB,CAAA;IACD,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,iBAAiB,EAAE,MAAM,CAAA;IACzB,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,iBAAiB,EAAE,MAAM,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,EAAE,MAAM,CAAA;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,EACrC,QAAQ,EAAE,2BAA2B,UAYtC;AAwCD,eAAO,MAAM,uBAAuB,GAClC,UAAU,aAAa,EAAE,EACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,EAChC,UAAU,2BAA2B,KACpC,SAAS,CAAC,mBAAmB,CAAC,EA2ChC,CAAA;AAED,eAAO,MAAM,4BAA4B,GACvC,UAAU,aAAa,EAAE,EACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,EACrC,UAAU,2BAA2B,KACpC,SAAS,CAAC,wBAAwB,CAAC,EAyCrC,CAAA;AAED,eAAO,MAAM,uBAAuB,GAClC,UAAU,aAAa,EAAE,EACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,EAChC,UAAU,2BAA2B,EACrC,SAAS,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,KAC3C,SAAS,CAAC,mBAAmB,CAAC,EA2DhC,CAAA;AAED,eAAO,MAAM,2BAA2B,GACtC,UAAU,aAAa,EAAE,EACzB,UAAU,2BAA2B,KACpC,SAAS,CAAC,uBAAuB,CAAC,EAoBpC,CAAA;AAED,eAAO,MAAM,8BAA8B,GACzC,UAAU,aAAa,EAAE,EACzB,UAAU,2BAA2B,KACpC,SAAS,CAAC,0BAA0B,CAAC,EA0BvC,CAAA"}