@voyantjs/bookings-ui 0.13.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 (101) hide show
  1. package/README.md +13 -0
  2. package/dist/components/booking-activity-timeline.d.ts +5 -0
  3. package/dist/components/booking-activity-timeline.d.ts.map +1 -0
  4. package/dist/components/booking-activity-timeline.js +83 -0
  5. package/dist/components/booking-cancellation-dialog.d.ts +18 -0
  6. package/dist/components/booking-cancellation-dialog.d.ts.map +1 -0
  7. package/dist/components/booking-cancellation-dialog.js +80 -0
  8. package/dist/components/booking-create-dialog.d.ts +21 -0
  9. package/dist/components/booking-create-dialog.d.ts.map +1 -0
  10. package/dist/components/booking-create-dialog.js +313 -0
  11. package/dist/components/booking-dialog.d.ts +23 -0
  12. package/dist/components/booking-dialog.d.ts.map +1 -0
  13. package/dist/components/booking-dialog.js +108 -0
  14. package/dist/components/booking-document-dialog.d.ts +8 -0
  15. package/dist/components/booking-document-dialog.d.ts.map +1 -0
  16. package/dist/components/booking-document-dialog.js +67 -0
  17. package/dist/components/booking-document-list.d.ts +5 -0
  18. package/dist/components/booking-document-list.d.ts.map +1 -0
  19. package/dist/components/booking-document-list.js +38 -0
  20. package/dist/components/booking-group-link-dialog.d.ts +10 -0
  21. package/dist/components/booking-group-link-dialog.d.ts.map +1 -0
  22. package/dist/components/booking-group-link-dialog.js +68 -0
  23. package/dist/components/booking-group-section.d.ts +17 -0
  24. package/dist/components/booking-group-section.d.ts.map +1 -0
  25. package/dist/components/booking-group-section.js +31 -0
  26. package/dist/components/booking-guarantee-dialog.d.ts +10 -0
  27. package/dist/components/booking-guarantee-dialog.d.ts.map +1 -0
  28. package/dist/components/booking-guarantee-dialog.js +101 -0
  29. package/dist/components/booking-guarantee-list.d.ts +5 -0
  30. package/dist/components/booking-guarantee-list.d.ts.map +1 -0
  31. package/dist/components/booking-guarantee-list.js +45 -0
  32. package/dist/components/booking-item-dialog.d.ts +10 -0
  33. package/dist/components/booking-item-dialog.d.ts.map +1 -0
  34. package/dist/components/booking-item-dialog.js +119 -0
  35. package/dist/components/booking-item-list.d.ts +5 -0
  36. package/dist/components/booking-item-list.d.ts.map +1 -0
  37. package/dist/components/booking-item-list.js +50 -0
  38. package/dist/components/booking-item-travelers.d.ts +6 -0
  39. package/dist/components/booking-item-travelers.d.ts.map +1 -0
  40. package/dist/components/booking-item-travelers.js +50 -0
  41. package/dist/components/booking-list.d.ts +7 -0
  42. package/dist/components/booking-list.d.ts.map +1 -0
  43. package/dist/components/booking-list.js +47 -0
  44. package/dist/components/booking-notes.d.ts +5 -0
  45. package/dist/components/booking-notes.d.ts.map +1 -0
  46. package/dist/components/booking-notes.js +16 -0
  47. package/dist/components/booking-payment-schedule-dialog.d.ts +10 -0
  48. package/dist/components/booking-payment-schedule-dialog.d.ts.map +1 -0
  49. package/dist/components/booking-payment-schedule-dialog.js +77 -0
  50. package/dist/components/booking-payment-schedule-list.d.ts +5 -0
  51. package/dist/components/booking-payment-schedule-list.d.ts.map +1 -0
  52. package/dist/components/booking-payment-schedule-list.js +43 -0
  53. package/dist/components/booking-payments-summary.d.ts +5 -0
  54. package/dist/components/booking-payments-summary.d.ts.map +1 -0
  55. package/dist/components/booking-payments-summary.js +19 -0
  56. package/dist/components/file-dropzone.d.ts +25 -0
  57. package/dist/components/file-dropzone.d.ts.map +1 -0
  58. package/dist/components/file-dropzone.js +92 -0
  59. package/dist/components/passengers-section.d.ts +72 -0
  60. package/dist/components/passengers-section.d.ts.map +1 -0
  61. package/dist/components/passengers-section.js +74 -0
  62. package/dist/components/payment-schedule-section.d.ts +62 -0
  63. package/dist/components/payment-schedule-section.d.ts.map +1 -0
  64. package/dist/components/payment-schedule-section.js +88 -0
  65. package/dist/components/person-picker-section.d.ts +53 -0
  66. package/dist/components/person-picker-section.d.ts.map +1 -0
  67. package/dist/components/person-picker-section.js +71 -0
  68. package/dist/components/price-breakdown-section.d.ts +48 -0
  69. package/dist/components/price-breakdown-section.d.ts.map +1 -0
  70. package/dist/components/price-breakdown-section.js +165 -0
  71. package/dist/components/product-picker-section.d.ts +27 -0
  72. package/dist/components/product-picker-section.d.ts.map +1 -0
  73. package/dist/components/product-picker-section.js +41 -0
  74. package/dist/components/rooms-stepper-section.d.ts +45 -0
  75. package/dist/components/rooms-stepper-section.d.ts.map +1 -0
  76. package/dist/components/rooms-stepper-section.js +60 -0
  77. package/dist/components/shared-room-section.d.ts +37 -0
  78. package/dist/components/shared-room-section.d.ts.map +1 -0
  79. package/dist/components/shared-room-section.js +40 -0
  80. package/dist/components/status-change-dialog.d.ts +10 -0
  81. package/dist/components/status-change-dialog.d.ts.map +1 -0
  82. package/dist/components/status-change-dialog.js +41 -0
  83. package/dist/components/supplier-status-dialog.d.ts +10 -0
  84. package/dist/components/supplier-status-dialog.d.ts.map +1 -0
  85. package/dist/components/supplier-status-dialog.js +77 -0
  86. package/dist/components/supplier-status-list.d.ts +5 -0
  87. package/dist/components/supplier-status-list.d.ts.map +1 -0
  88. package/dist/components/supplier-status-list.js +33 -0
  89. package/dist/components/traveler-dialog.d.ts +10 -0
  90. package/dist/components/traveler-dialog.d.ts.map +1 -0
  91. package/dist/components/traveler-dialog.js +64 -0
  92. package/dist/components/traveler-list.d.ts +5 -0
  93. package/dist/components/traveler-list.d.ts.map +1 -0
  94. package/dist/components/traveler-list.js +32 -0
  95. package/dist/components/voucher-picker-section.d.ts +50 -0
  96. package/dist/components/voucher-picker-section.d.ts.map +1 -0
  97. package/dist/components/voucher-picker-section.js +94 -0
  98. package/dist/index.d.ts +33 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +32 -0
  101. package/package.json +76 -0
@@ -0,0 +1,165 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { usePricingPreview } from "@voyantjs/bookings-react";
4
+ import { Label } from "@voyantjs/voyant-ui/components";
5
+ import * as React from "react";
6
+ const DEFAULT_LABELS = {
7
+ heading: "Price breakdown",
8
+ total: "Total",
9
+ onRequest: "On request",
10
+ groupRate: "group rate",
11
+ empty: "Pick units above to see the breakdown.",
12
+ noPricing: "No pricing catalog available for this product.",
13
+ };
14
+ function formatCents(cents, currency) {
15
+ if (cents === null)
16
+ return "";
17
+ const major = (cents / 100).toFixed(2);
18
+ return currency ? `${major} ${currency}` : major;
19
+ }
20
+ /**
21
+ * Picks the tier whose quantity range contains `qty`. Tiers are expected
22
+ * oldest-to-newest, `minQuantity`-ascending. Ties are broken by first-match —
23
+ * the server sorts by sort_order and then min_quantity, so the selection here
24
+ * mirrors the storefront engine.
25
+ */
26
+ function matchTier(tiers, qty) {
27
+ for (const tier of tiers) {
28
+ if (qty >= tier.minQuantity && (tier.maxQuantity === null || qty <= tier.maxQuantity)) {
29
+ return tier;
30
+ }
31
+ }
32
+ return null;
33
+ }
34
+ /**
35
+ * Live price-breakdown preview for booking-create flows. Read-only — uses
36
+ * `usePricingPreview` (#237) to fetch the catalog-resolved snapshot the
37
+ * storefront also uses, then computes lines against the operator's current
38
+ * unit quantities so the operator sees the same numbers the customer would.
39
+ *
40
+ * ### Pricing mode handling
41
+ *
42
+ * - `per_unit` — multiply the matched tier's `sellAmountCents` by quantity.
43
+ * - `free` / `included` — render 0.00 without an on-request badge.
44
+ * - `on_request` / anything else — render "On request"; total excludes it.
45
+ */
46
+ export function PriceBreakdownSection({ productId, optionId, unitQuantities, catalogId, labels, }) {
47
+ const merged = { ...DEFAULT_LABELS, ...labels };
48
+ const preview = usePricingPreview({
49
+ productId: productId ?? "",
50
+ optionId: optionId ?? null,
51
+ catalogId: catalogId ?? null,
52
+ enabled: Boolean(productId),
53
+ });
54
+ const snapshot = preview.data?.data;
55
+ const currency = snapshot?.catalog.currencyCode ?? null;
56
+ const { lines, total } = React.useMemo(() => {
57
+ const out = [];
58
+ let runningTotal = 0;
59
+ let anyOnRequest = false;
60
+ if (!snapshot)
61
+ return { lines: out, total: null };
62
+ // Pick the default price rule for the resolved option (snapshot already
63
+ // filters options by the caller's optionId; rules keep isDefault-first
64
+ // ordering from the server).
65
+ const rulesByOption = new Map();
66
+ for (const rule of snapshot.rules) {
67
+ const existing = rulesByOption.get(rule.optionId) ?? [];
68
+ existing.push(rule);
69
+ rulesByOption.set(rule.optionId, existing);
70
+ }
71
+ const unitPricesByUnit = new Map();
72
+ for (const up of snapshot.unitPrices) {
73
+ if (!unitPricesByUnit.has(up.unitId)) {
74
+ unitPricesByUnit.set(up.unitId, up);
75
+ }
76
+ }
77
+ for (const [unitId, quantity] of Object.entries(unitQuantities)) {
78
+ if (quantity <= 0)
79
+ continue;
80
+ const up = unitPricesByUnit.get(unitId);
81
+ if (!up) {
82
+ // The unit isn't priced in this catalog — show it on-request so the
83
+ // operator knows they need to quote manually.
84
+ out.push({
85
+ unitId,
86
+ label: unitId,
87
+ quantity,
88
+ unitAmountCents: null,
89
+ totalAmountCents: null,
90
+ tierLabel: null,
91
+ isGroupRate: false,
92
+ });
93
+ anyOnRequest = true;
94
+ continue;
95
+ }
96
+ const label = up.unitName || unitId;
97
+ if (up.pricingMode === "on_request") {
98
+ out.push({
99
+ unitId,
100
+ label,
101
+ quantity,
102
+ unitAmountCents: null,
103
+ totalAmountCents: null,
104
+ tierLabel: merged.onRequest,
105
+ isGroupRate: false,
106
+ });
107
+ anyOnRequest = true;
108
+ continue;
109
+ }
110
+ if (up.pricingMode === "free" || up.pricingMode === "included") {
111
+ out.push({
112
+ unitId,
113
+ label,
114
+ quantity,
115
+ unitAmountCents: 0,
116
+ totalAmountCents: 0,
117
+ tierLabel: null,
118
+ isGroupRate: false,
119
+ });
120
+ continue;
121
+ }
122
+ // per_unit (and anything else that falls through to explicit amounts).
123
+ const matchedTier = matchTier(up.tiers, quantity);
124
+ const unitAmount = matchedTier?.sellAmountCents ?? up.sellAmountCents;
125
+ if (unitAmount === null) {
126
+ out.push({
127
+ unitId,
128
+ label,
129
+ quantity,
130
+ unitAmountCents: null,
131
+ totalAmountCents: null,
132
+ tierLabel: merged.onRequest,
133
+ isGroupRate: false,
134
+ });
135
+ anyOnRequest = true;
136
+ continue;
137
+ }
138
+ const lineTotal = unitAmount * quantity;
139
+ const isGroupRate = matchedTier !== null && matchedTier.minQuantity > 1;
140
+ out.push({
141
+ unitId,
142
+ label,
143
+ quantity,
144
+ unitAmountCents: unitAmount,
145
+ totalAmountCents: lineTotal,
146
+ tierLabel: isGroupRate ? merged.groupRate : null,
147
+ isGroupRate,
148
+ });
149
+ runningTotal += lineTotal;
150
+ }
151
+ return { lines: out, total: anyOnRequest ? null : runningTotal };
152
+ }, [snapshot, unitQuantities, merged.onRequest, merged.groupRate]);
153
+ // Empty states
154
+ if (!productId)
155
+ return null;
156
+ if (preview.isError || (preview.isSuccess && !snapshot)) {
157
+ return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Label, { children: merged.heading }), _jsx("p", { className: "text-xs text-muted-foreground", children: merged.noPricing })] }));
158
+ }
159
+ if (lines.length === 0) {
160
+ return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Label, { children: merged.heading }), _jsx("p", { className: "text-xs text-muted-foreground", children: merged.empty })] }));
161
+ }
162
+ return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Label, { children: merged.heading }), _jsx("div", { className: "flex flex-col gap-1.5", children: lines.map((line) => (_jsxs("div", { className: "flex items-baseline justify-between text-sm", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsxs("span", { className: "tabular-nums", children: [line.quantity, "\u00D7"] }), _jsx("span", { children: line.label }), line.tierLabel ? (_jsxs("span", { className: "text-xs text-muted-foreground", children: ["\u00B7 ", line.tierLabel] })) : null] }), _jsx("div", { className: "tabular-nums", children: line.totalAmountCents === null
163
+ ? merged.onRequest
164
+ : formatCents(line.totalAmountCents, currency) })] }, line.unitId))) }), _jsxs("div", { className: "mt-1 flex items-baseline justify-between border-t pt-2 text-sm font-medium", children: [_jsx("span", { children: merged.total }), _jsx("span", { className: "tabular-nums", children: total === null ? merged.onRequest : formatCents(total, currency) })] })] }));
165
+ }
@@ -0,0 +1,27 @@
1
+ export interface ProductPickerValue {
2
+ productId: string;
3
+ /** `null` means "no specific option" — the product has options but none was picked. */
4
+ optionId: string | null;
5
+ }
6
+ export interface ProductPickerSectionProps {
7
+ value: ProductPickerValue;
8
+ onChange: (value: ProductPickerValue) => void;
9
+ /** When true, skip data fetches (dialog closed / parent gated). */
10
+ enabled?: boolean;
11
+ /** When true, hide the product picker and fix the productId (e.g., launched from a product page). */
12
+ lockProduct?: boolean;
13
+ labels?: {
14
+ product?: string;
15
+ productSearchPlaceholder?: string;
16
+ productSelectPlaceholder?: string;
17
+ option?: string;
18
+ optionNone?: string;
19
+ };
20
+ }
21
+ /**
22
+ * Controlled product + option picker. Splits `value` + `onChange` so apps can
23
+ * replace the whole section (e.g., with a typeahead against a custom catalog)
24
+ * without reimplementing the cascade logic, or keep this one and swap labels.
25
+ */
26
+ export declare function ProductPickerSection({ value, onChange, enabled, lockProduct, labels, }: ProductPickerSectionProps): import("react/jsx-runtime").JSX.Element;
27
+ //# sourceMappingURL=product-picker-section.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-picker-section.d.ts","sourceRoot":"","sources":["../../src/components/product-picker-section.tsx"],"names":[],"mappings":"AAgBA,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,uFAAuF;IACvF,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,kBAAkB,CAAA;IACzB,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC7C,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,qGAAqG;IACrG,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,wBAAwB,CAAC,EAAE,MAAM,CAAA;QACjC,wBAAwB,CAAC,EAAE,MAAM,CAAA;QACjC,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;CACF;AAUD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,QAAQ,EACR,OAAc,EACd,WAAmB,EACnB,MAAM,GACP,EAAE,yBAAyB,2CAiF3B"}
@@ -0,0 +1,41 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useProductOptions, useProducts } from "@voyantjs/products-react";
4
+ import { Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/voyant-ui/components";
5
+ import * as React from "react";
6
+ const OPTION_NONE = "__none__";
7
+ const DEFAULT_LABELS = {
8
+ product: "Product",
9
+ productSearchPlaceholder: "Search products...",
10
+ productSelectPlaceholder: "Select a product...",
11
+ option: "Option",
12
+ optionNone: "No specific option",
13
+ };
14
+ /**
15
+ * Controlled product + option picker. Splits `value` + `onChange` so apps can
16
+ * replace the whole section (e.g., with a typeahead against a custom catalog)
17
+ * without reimplementing the cascade logic, or keep this one and swap labels.
18
+ */
19
+ export function ProductPickerSection({ value, onChange, enabled = true, lockProduct = false, labels, }) {
20
+ const [productSearch, setProductSearch] = React.useState("");
21
+ const merged = { ...DEFAULT_LABELS, ...labels };
22
+ const { data: productsData } = useProducts({
23
+ search: productSearch || undefined,
24
+ limit: 20,
25
+ enabled: enabled && !lockProduct,
26
+ });
27
+ const products = productsData?.data ?? [];
28
+ const { data: optionsData } = useProductOptions({
29
+ productId: value.productId || undefined,
30
+ limit: 50,
31
+ enabled: enabled && Boolean(value.productId),
32
+ });
33
+ const options = optionsData?.data ?? [];
34
+ return (_jsxs(_Fragment, { children: [!lockProduct && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs(Label, { children: [merged.product, " ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(Input, { placeholder: merged.productSearchPlaceholder, value: productSearch, onChange: (e) => setProductSearch(e.target.value) }), _jsxs(Select, { items: products.map((p) => ({ label: p.name, value: p.id })), value: value.productId, onValueChange: (v) => onChange({ productId: v ?? "", optionId: null }), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: merged.productSelectPlaceholder }) }), _jsx(SelectContent, { children: products.map((p) => (_jsx(SelectItem, { value: p.id, children: p.name }, p.id))) })] })] })), value.productId && options.length > 0 && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: merged.option }), _jsxs(Select, { items: [
35
+ { label: merged.optionNone, value: OPTION_NONE },
36
+ ...options.map((o) => ({ label: o.name, value: o.id })),
37
+ ], value: value.optionId ?? OPTION_NONE, onValueChange: (v) => onChange({
38
+ productId: value.productId,
39
+ optionId: v === OPTION_NONE ? null : (v ?? null),
40
+ }), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: OPTION_NONE, children: merged.optionNone }), options.map((o) => (_jsx(SelectItem, { value: o.id, children: o.name }, o.id)))] })] })] }))] }));
41
+ }
@@ -0,0 +1,45 @@
1
+ /** Quantity per option_unit id; omitted ids are treated as 0. */
2
+ export interface RoomsStepperValue {
3
+ quantities: Record<string, number>;
4
+ }
5
+ export declare const emptyRoomsStepperValue: RoomsStepperValue;
6
+ export interface RoomsStepperSectionProps {
7
+ value: RoomsStepperValue;
8
+ onChange: (value: RoomsStepperValue) => void;
9
+ /**
10
+ * Departure the operator picked. Section renders nothing until a slot is
11
+ * chosen — per-unit availability is a property of the slot, not the
12
+ * product, so there's nothing to show before then.
13
+ */
14
+ slotId?: string;
15
+ enabled?: boolean;
16
+ labels?: {
17
+ heading?: string;
18
+ noSlot?: string;
19
+ noUnits?: string;
20
+ remaining?: string;
21
+ unlimited?: string;
22
+ };
23
+ }
24
+ /**
25
+ * Rooms / per-unit stepper for booking-create flows. Drives
26
+ * `GET /v1/availability/slots/:id/unit-availability` from #235 so the
27
+ * operator sees authoritative "3 doubles available" numbers instead of
28
+ * client-side math against a sampled booking list.
29
+ *
30
+ * The section only tracks **intent** (how many of each unit the operator
31
+ * wants to book). Actual hold/reservation happens when the parent submits
32
+ * the booking — capacity drops the moment the reservation transaction
33
+ * commits; the next refetch of `useSlotUnitAvailability` reflects it.
34
+ *
35
+ * ### Stepper bounds
36
+ *
37
+ * - Minimum is 0 (operator can deselect).
38
+ * - Maximum is the unit's `remaining` count from the server. Unlimited
39
+ * pools (`remaining === null`) have no upper bound.
40
+ * - The server is the truth: entering `3 doubles` when only 2 remain just
41
+ * disables the "+" button — we don't let the UI submit a request that
42
+ * would 409 at insert time.
43
+ */
44
+ export declare function RoomsStepperSection({ value, onChange, slotId, enabled, labels, }: RoomsStepperSectionProps): import("react/jsx-runtime").JSX.Element;
45
+ //# sourceMappingURL=rooms-stepper-section.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rooms-stepper-section.d.ts","sourceRoot":"","sources":["../../src/components/rooms-stepper-section.tsx"],"names":[],"mappings":"AAMA,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACnC;AAED,eAAO,MAAM,sBAAsB,EAAE,iBAAsC,CAAA;AAE3E,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,iBAAiB,CAAA;IACxB,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAC5C;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;CACF;AAUD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,MAAM,EACN,OAAc,EACd,MAAM,GACP,EAAE,wBAAwB,2CAmF1B"}
@@ -0,0 +1,60 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useSlotUnitAvailability } from "@voyantjs/availability-react";
4
+ import { Button, Label } from "@voyantjs/voyant-ui/components";
5
+ import { Minus, Plus } from "lucide-react";
6
+ export const emptyRoomsStepperValue = { quantities: {} };
7
+ const DEFAULT_LABELS = {
8
+ heading: "Rooms",
9
+ noSlot: "Pick a departure first to see available rooms.",
10
+ noUnits: "This departure has no per-unit availability configured.",
11
+ remaining: "left",
12
+ unlimited: "unlimited",
13
+ };
14
+ /**
15
+ * Rooms / per-unit stepper for booking-create flows. Drives
16
+ * `GET /v1/availability/slots/:id/unit-availability` from #235 so the
17
+ * operator sees authoritative "3 doubles available" numbers instead of
18
+ * client-side math against a sampled booking list.
19
+ *
20
+ * The section only tracks **intent** (how many of each unit the operator
21
+ * wants to book). Actual hold/reservation happens when the parent submits
22
+ * the booking — capacity drops the moment the reservation transaction
23
+ * commits; the next refetch of `useSlotUnitAvailability` reflects it.
24
+ *
25
+ * ### Stepper bounds
26
+ *
27
+ * - Minimum is 0 (operator can deselect).
28
+ * - Maximum is the unit's `remaining` count from the server. Unlimited
29
+ * pools (`remaining === null`) have no upper bound.
30
+ * - The server is the truth: entering `3 doubles` when only 2 remain just
31
+ * disables the "+" button — we don't let the UI submit a request that
32
+ * would 409 at insert time.
33
+ */
34
+ export function RoomsStepperSection({ value, onChange, slotId, enabled = true, labels, }) {
35
+ const merged = { ...DEFAULT_LABELS, ...labels };
36
+ const availability = useSlotUnitAvailability({ slotId, enabled: enabled && Boolean(slotId) });
37
+ const units = availability.data?.data ?? [];
38
+ if (!slotId) {
39
+ return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Label, { children: merged.heading }), _jsx("p", { className: "text-xs text-muted-foreground", children: merged.noSlot })] }));
40
+ }
41
+ if (availability.isSuccess && units.length === 0) {
42
+ return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Label, { children: merged.heading }), _jsx("p", { className: "text-xs text-muted-foreground", children: merged.noUnits })] }));
43
+ }
44
+ const setQuantity = (unitId, qty) => {
45
+ const next = { ...value.quantities };
46
+ if (qty <= 0) {
47
+ delete next[unitId];
48
+ }
49
+ else {
50
+ next[unitId] = qty;
51
+ }
52
+ onChange({ quantities: next });
53
+ };
54
+ return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Label, { children: merged.heading }), _jsx("div", { className: "flex flex-col gap-2", children: units.map((unit) => {
55
+ const qty = value.quantities[unit.optionUnitId] ?? 0;
56
+ const remainingLabel = unit.remaining === null ? merged.unlimited : `${unit.remaining} ${merged.remaining}`;
57
+ const atMax = unit.remaining !== null && qty >= unit.remaining;
58
+ return (_jsxs("div", { className: "flex items-center gap-3 rounded-md border px-3 py-2", children: [_jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-sm font-medium", children: unit.unitName }), _jsx("div", { className: "text-xs text-muted-foreground", children: remainingLabel })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-7 w-7 p-0", onClick: () => setQuantity(unit.optionUnitId, Math.max(0, qty - 1)), disabled: qty <= 0, "aria-label": `Decrease ${unit.unitName}`, children: _jsx(Minus, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "min-w-[1.5rem] text-center text-sm tabular-nums", children: qty }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-7 w-7 p-0", onClick: () => setQuantity(unit.optionUnitId, qty + 1), disabled: atMax, "aria-label": `Increase ${unit.unitName}`, children: _jsx(Plus, { className: "h-3.5 w-3.5" }) })] })] }, unit.optionUnitId));
59
+ }) })] }));
60
+ }
@@ -0,0 +1,37 @@
1
+ export type SharedRoomMode = "create" | "join";
2
+ export interface SharedRoomValue {
3
+ enabled: boolean;
4
+ mode: SharedRoomMode;
5
+ /** Only meaningful in "join" mode. */
6
+ groupId: string;
7
+ }
8
+ export declare const emptySharedRoomValue: SharedRoomValue;
9
+ export interface SharedRoomSectionProps {
10
+ value: SharedRoomValue;
11
+ onChange: (value: SharedRoomValue) => void;
12
+ /**
13
+ * The product context for fetching joinable groups. When unset, the join
14
+ * dropdown is disabled even if the user toggles into join mode.
15
+ */
16
+ productId?: string;
17
+ enabled?: boolean;
18
+ labels?: {
19
+ toggle?: string;
20
+ createMode?: string;
21
+ joinMode?: string;
22
+ selectPlaceholder?: string;
23
+ noGroups?: string;
24
+ createHint?: string;
25
+ };
26
+ }
27
+ /**
28
+ * Shared-room (partaj) attachment section. Operators use it to either create a
29
+ * new `booking_groups` row at booking-create time or join an existing group
30
+ * that already has a primary booking.
31
+ *
32
+ * The section only handles the *selection* — the parent dialog owns the
33
+ * create-group + add-member mutations because they fire *after* the booking
34
+ * insert (we need the new booking id to attach).
35
+ */
36
+ export declare function SharedRoomSection({ value, onChange, productId, enabled, labels, }: SharedRoomSectionProps): import("react/jsx-runtime").JSX.Element;
37
+ //# sourceMappingURL=shared-room-section.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-room-section.d.ts","sourceRoot":"","sources":["../../src/components/shared-room-section.tsx"],"names":[],"mappings":"AAgBA,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,MAAM,CAAA;AAE9C,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,cAAc,CAAA;IACpB,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,eAAO,MAAM,oBAAoB,EAAE,eAIlC,CAAA;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,eAAe,CAAA;IACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAA;IAC1C;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE;QACP,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,iBAAiB,CAAC,EAAE,MAAM,CAAA;QAC1B,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;CACF;AAWD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,EACR,SAAS,EACT,OAAc,EACd,MAAM,GACP,EAAE,sBAAsB,2CAmFxB"}
@@ -0,0 +1,40 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useBookingGroups } from "@voyantjs/bookings-react";
4
+ import { Button, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/voyant-ui/components";
5
+ const GROUP_NONE = "__none__";
6
+ export const emptySharedRoomValue = {
7
+ enabled: false,
8
+ mode: "create",
9
+ groupId: "",
10
+ };
11
+ const DEFAULT_LABELS = {
12
+ toggle: "Link to a shared-room group",
13
+ createMode: "Create new group",
14
+ joinMode: "Join existing",
15
+ selectPlaceholder: "Select a group...",
16
+ noGroups: "No existing groups for this product",
17
+ createHint: "A new group will be created with this booking as the primary member.",
18
+ };
19
+ /**
20
+ * Shared-room (partaj) attachment section. Operators use it to either create a
21
+ * new `booking_groups` row at booking-create time or join an existing group
22
+ * that already has a primary booking.
23
+ *
24
+ * The section only handles the *selection* — the parent dialog owns the
25
+ * create-group + add-member mutations because they fire *after* the booking
26
+ * insert (we need the new booking id to attach).
27
+ */
28
+ export function SharedRoomSection({ value, onChange, productId, enabled = true, labels, }) {
29
+ const merged = { ...DEFAULT_LABELS, ...labels };
30
+ const { data: groupsData } = useBookingGroups({
31
+ productId: productId || undefined,
32
+ limit: 50,
33
+ enabled: enabled && value.enabled && value.mode === "join" && Boolean(productId),
34
+ });
35
+ const existingGroups = groupsData?.data ?? [];
36
+ const set = (patch) => onChange({ ...value, ...patch });
37
+ return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { id: "shared-room-toggle", type: "checkbox", checked: value.enabled, onChange: (e) => set({ enabled: e.target.checked }) }), _jsx(Label, { htmlFor: "shared-room-toggle", className: "text-sm", children: merged.toggle })] }), value.enabled && (_jsxs("div", { className: "flex flex-col gap-2 pl-6", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", size: "sm", variant: value.mode === "create" ? "default" : "ghost", onClick: () => set({ mode: "create" }), children: merged.createMode }), _jsx(Button, { type: "button", size: "sm", variant: value.mode === "join" ? "default" : "ghost", onClick: () => set({ mode: "join" }), children: merged.joinMode })] }), value.mode === "join" && (_jsxs(Select, { items: existingGroups.length === 0
38
+ ? [{ label: merged.noGroups, value: GROUP_NONE }]
39
+ : existingGroups.map((g) => ({ label: g.label, value: g.id })), value: value.groupId || GROUP_NONE, onValueChange: (v) => set({ groupId: v === GROUP_NONE ? "" : (v ?? "") }), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: merged.selectPlaceholder }) }), _jsx(SelectContent, { children: existingGroups.length === 0 ? (_jsx(SelectItem, { value: GROUP_NONE, disabled: true, children: merged.noGroups })) : (existingGroups.map((g) => (_jsx(SelectItem, { value: g.id, children: g.label }, g.id)))) })] })), value.mode === "create" && (_jsx("p", { className: "text-xs text-muted-foreground", children: merged.createHint }))] }))] }));
40
+ }
@@ -0,0 +1,10 @@
1
+ import { type BookingRecord } from "@voyantjs/bookings-react";
2
+ export interface StatusChangeDialogProps {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ bookingId: string;
6
+ currentStatus: BookingRecord["status"];
7
+ onSuccess?: () => void;
8
+ }
9
+ export declare function StatusChangeDialog({ open, onOpenChange, bookingId, currentStatus, onSuccess, }: StatusChangeDialogProps): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=status-change-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-change-dialog.d.ts","sourceRoot":"","sources":["../../src/components/status-change-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,EAInB,MAAM,0BAA0B,CAAA;AA+BjC,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAA;IACtC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,aAAa,EACb,SAAS,GACV,EAAE,uBAAuB,2CAiFzB"}
@@ -0,0 +1,41 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { bookingStatusOptions, bookingStatusSchema, useBookingStatusMutation, } from "@voyantjs/bookings-react";
4
+ import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea, } from "@voyantjs/voyant-ui/components";
5
+ import { zodResolver } from "@voyantjs/voyant-ui/lib/zod-resolver";
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
+ const statusChangeFormSchema = z.object({
11
+ status: bookingStatusSchema,
12
+ note: z.string().optional().nullable(),
13
+ });
14
+ export function StatusChangeDialog({ open, onOpenChange, bookingId, currentStatus, onSuccess, }) {
15
+ const mutation = useBookingStatusMutation(bookingId);
16
+ const form = useForm({
17
+ resolver: zodResolver(statusChangeFormSchema),
18
+ defaultValues: {
19
+ status: "draft",
20
+ note: "",
21
+ },
22
+ });
23
+ useEffect(() => {
24
+ if (open) {
25
+ form.reset({
26
+ status: currentStatus,
27
+ note: "",
28
+ });
29
+ }
30
+ }, [currentStatus, form, open]);
31
+ const onSubmit = async (values) => {
32
+ await mutation.mutateAsync({
33
+ currentStatus,
34
+ status: values.status,
35
+ note: values.note || null,
36
+ });
37
+ onOpenChange(false);
38
+ onSuccess?.();
39
+ };
40
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Change Booking Status" }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "flex flex-1 flex-col overflow-hidden", children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "New Status" }), _jsxs(Select, { value: form.watch("status"), onValueChange: (value) => form.setValue("status", value), items: bookingStatusOptions, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: bookingStatusOptions.map((status) => (_jsx(SelectItem, { value: status.value, children: status.label }, status.value))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Note (optional)" }), _jsx(Textarea, { ...form.register("note"), placeholder: "Reason for status change..." })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), children: "Cancel" }), _jsxs(Button, { type: "submit", size: "sm", disabled: mutation.isPending, children: [mutation.isPending && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Update Status"] })] })] })] }) }));
41
+ }
@@ -0,0 +1,10 @@
1
+ import { type BookingSupplierStatusRecord } from "@voyantjs/bookings-react";
2
+ export interface SupplierStatusDialogProps {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ bookingId: string;
6
+ supplierStatus?: BookingSupplierStatusRecord;
7
+ onSuccess?: () => void;
8
+ }
9
+ export declare function SupplierStatusDialog({ open, onOpenChange, bookingId, supplierStatus, onSuccess, }: SupplierStatusDialogProps): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=supplier-status-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supplier-status-dialog.d.ts","sourceRoot":"","sources":["../../src/components/supplier-status-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,2BAA2B,EAEjC,MAAM,0BAA0B,CAAA;AAqCjC,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,CAAC,EAAE,2BAA2B,CAAA;IAC5C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AASD,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,cAAc,EACd,SAAS,GACV,EAAE,yBAAyB,2CAmJ3B"}
@@ -0,0 +1,77 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useSupplierStatusMutation, } from "@voyantjs/bookings-react";
4
+ import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea, } from "@voyantjs/voyant-ui/components";
5
+ import { CurrencyCombobox } from "@voyantjs/voyant-ui/components/currency-combobox";
6
+ import { zodResolver } from "@voyantjs/voyant-ui/lib/zod-resolver";
7
+ import { Loader2 } from "lucide-react";
8
+ import { useEffect } from "react";
9
+ import { useForm } from "react-hook-form";
10
+ import { z } from "zod/v4";
11
+ const supplierStatusFormSchema = z.object({
12
+ serviceName: z.string().min(1, "Service name is required"),
13
+ status: z.enum(["pending", "confirmed", "rejected", "cancelled"]),
14
+ supplierReference: z.string().optional().nullable(),
15
+ costCurrency: z.string().min(3).max(3, "Use 3-letter ISO code"),
16
+ costAmountCents: z.coerce.number().int().min(0),
17
+ notes: z.string().optional().nullable(),
18
+ });
19
+ const CONFIRMATION_STATUSES = [
20
+ { value: "pending", label: "Pending" },
21
+ { value: "confirmed", label: "Confirmed" },
22
+ { value: "rejected", label: "Rejected" },
23
+ { value: "cancelled", label: "Cancelled" },
24
+ ];
25
+ export function SupplierStatusDialog({ open, onOpenChange, bookingId, supplierStatus, onSuccess, }) {
26
+ const isEditing = Boolean(supplierStatus);
27
+ const { create, update } = useSupplierStatusMutation(bookingId);
28
+ const form = useForm({
29
+ resolver: zodResolver(supplierStatusFormSchema),
30
+ defaultValues: {
31
+ serviceName: "",
32
+ status: "pending",
33
+ supplierReference: "",
34
+ costCurrency: "EUR",
35
+ costAmountCents: 0,
36
+ notes: "",
37
+ },
38
+ });
39
+ useEffect(() => {
40
+ if (open && supplierStatus) {
41
+ form.reset({
42
+ serviceName: supplierStatus.serviceName,
43
+ status: supplierStatus.status,
44
+ supplierReference: supplierStatus.supplierReference ?? "",
45
+ costCurrency: supplierStatus.costCurrency,
46
+ costAmountCents: supplierStatus.costAmountCents,
47
+ notes: supplierStatus.notes ?? "",
48
+ });
49
+ }
50
+ else if (open) {
51
+ form.reset();
52
+ }
53
+ }, [form, open, supplierStatus]);
54
+ const onSubmit = async (values) => {
55
+ const payload = {
56
+ serviceName: values.serviceName,
57
+ status: values.status,
58
+ supplierReference: values.supplierReference || null,
59
+ costCurrency: values.costCurrency,
60
+ costAmountCents: values.costAmountCents,
61
+ notes: values.notes || null,
62
+ };
63
+ if (isEditing) {
64
+ await update.mutateAsync({ id: supplierStatus.id, input: payload });
65
+ }
66
+ else {
67
+ await create.mutateAsync(payload);
68
+ }
69
+ onOpenChange(false);
70
+ onSuccess?.();
71
+ };
72
+ const isSubmitting = create.isPending || update.isPending;
73
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? "Update Supplier Status" : "Add Supplier Status" }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "flex flex-1 flex-col overflow-hidden", children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Service Name" }), _jsx(Input, { ...form.register("serviceName"), placeholder: "Hotel Dubrovnik Palace", disabled: isEditing }), form.formState.errors.serviceName && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.serviceName.message }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Status" }), _jsxs(Select, { value: form.watch("status"), onValueChange: (value) => form.setValue("status", value), items: CONFIRMATION_STATUSES, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: CONFIRMATION_STATUSES.map((status) => (_jsx(SelectItem, { value: status.value, children: status.label }, status.value))) })] })] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Cost Currency" }), _jsx(CurrencyCombobox, { value: form.watch("costCurrency") || null, onChange: (next) => form.setValue("costCurrency", next ?? "EUR", {
74
+ shouldValidate: true,
75
+ shouldDirty: true,
76
+ }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Cost Amount (cents)" }), _jsx(Input, { ...form.register("costAmountCents", { valueAsNumber: true }), type: "number", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Supplier Reference" }), _jsx(Input, { ...form.register("supplierReference"), placeholder: "CONF-12345" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Notes" }), _jsx(Textarea, { ...form.register("notes"), placeholder: "Additional notes..." })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), children: "Cancel" }), _jsxs(Button, { type: "submit", size: "sm", disabled: isSubmitting, children: [isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), isEditing ? "Save Changes" : "Add"] })] })] })] }) }));
77
+ }
@@ -0,0 +1,5 @@
1
+ export interface SupplierStatusListProps {
2
+ bookingId: string;
3
+ }
4
+ export declare function SupplierStatusList({ bookingId }: SupplierStatusListProps): import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=supplier-status-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supplier-status-list.d.ts","sourceRoot":"","sources":["../../src/components/supplier-status-list.tsx"],"names":[],"mappings":"AAgBA,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAA;CAClB;AASD,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,EAAE,EAAE,uBAAuB,2CA+FxE"}