@voyantjs/bookings-ui 0.34.0 → 0.37.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 (55) hide show
  1. package/dist/components/booking-combobox.d.ts +13 -0
  2. package/dist/components/booking-combobox.d.ts.map +1 -0
  3. package/dist/components/booking-combobox.js +44 -0
  4. package/dist/components/booking-create-dialog.d.ts +9 -0
  5. package/dist/components/booking-create-dialog.d.ts.map +1 -1
  6. package/dist/components/booking-create-dialog.js +110 -90
  7. package/dist/components/booking-create-page.d.ts +11 -0
  8. package/dist/components/booking-create-page.d.ts.map +1 -0
  9. package/dist/components/booking-create-page.js +11 -0
  10. package/dist/components/booking-detail-page.d.ts.map +1 -1
  11. package/dist/components/booking-detail-page.js +4 -1
  12. package/dist/components/booking-item-list.d.ts.map +1 -1
  13. package/dist/components/booking-item-list.js +5 -3
  14. package/dist/components/booking-list-filters.d.ts +35 -0
  15. package/dist/components/booking-list-filters.d.ts.map +1 -0
  16. package/dist/components/booking-list-filters.js +148 -0
  17. package/dist/components/booking-list.d.ts.map +1 -1
  18. package/dist/components/booking-list.js +30 -46
  19. package/dist/components/person-picker-section.d.ts +15 -7
  20. package/dist/components/person-picker-section.d.ts.map +1 -1
  21. package/dist/components/person-picker-section.js +100 -21
  22. package/dist/components/price-breakdown-section.d.ts +16 -1
  23. package/dist/components/price-breakdown-section.d.ts.map +1 -1
  24. package/dist/components/price-breakdown-section.js +36 -5
  25. package/dist/components/product-picker-section.d.ts.map +1 -1
  26. package/dist/components/product-picker-section.js +38 -4
  27. package/dist/components/shared-room-section.d.ts +9 -8
  28. package/dist/components/shared-room-section.d.ts.map +1 -1
  29. package/dist/components/shared-room-section.js +67 -14
  30. package/dist/components/traveler-list.d.ts.map +1 -1
  31. package/dist/components/traveler-list.js +27 -16
  32. package/dist/i18n/en.d.ts +284 -1
  33. package/dist/i18n/en.d.ts.map +1 -1
  34. package/dist/i18n/en.js +300 -17
  35. package/dist/i18n/messages.d.ts +255 -1
  36. package/dist/i18n/messages.d.ts.map +1 -1
  37. package/dist/i18n/provider.d.ts +568 -2
  38. package/dist/i18n/provider.d.ts.map +1 -1
  39. package/dist/i18n/ro.d.ts +284 -1
  40. package/dist/i18n/ro.d.ts.map +1 -1
  41. package/dist/i18n/ro.js +301 -18
  42. package/dist/index.d.ts +3 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +3 -1
  45. package/dist/journey/components/booking-journey.d.ts.map +1 -1
  46. package/dist/journey/components/booking-journey.js +22 -13
  47. package/dist/journey/components/contract-preview-dialog.d.ts.map +1 -1
  48. package/dist/journey/components/contract-preview-dialog.js +9 -4
  49. package/dist/journey/components/journey-steps.d.ts.map +1 -1
  50. package/dist/journey/components/journey-steps.js +94 -72
  51. package/dist/journey/components/side-panel.d.ts.map +1 -1
  52. package/dist/journey/components/side-panel.js +58 -35
  53. package/dist/journey/components/step-header.d.ts.map +1 -1
  54. package/dist/journey/components/step-header.js +3 -19
  55. package/package.json +24 -20
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { usePricingPreview } from "@voyantjs/bookings-react";
4
- import { Label } from "@voyantjs/ui/components";
4
+ import { Button, Label, Textarea } from "@voyantjs/ui/components";
5
+ import { CurrencyInput } from "@voyantjs/ui/components/currency-input";
5
6
  import * as React from "react";
6
7
  import { useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault } from "../i18n/provider.js";
7
8
  /**
@@ -32,7 +33,7 @@ function matchTier(tiers, qty) {
32
33
  * - `free` / `included` — render 0.00 without an on-request badge.
33
34
  * - `on_request` / anything else — render "On request"; total excludes it.
34
35
  */
35
- export function PriceBreakdownSection({ productId, optionId, unitQuantities, catalogId, labels, }) {
36
+ export function PriceBreakdownSection({ productId, optionId, unitQuantities, catalogId, labels, onChange, }) {
36
37
  const { formatCurrency, formatNumber } = useBookingsUiI18nOrDefault();
37
38
  const messages = useBookingsUiMessagesOrDefault();
38
39
  const merged = { ...messages.priceBreakdownSection.labels, ...labels };
@@ -42,6 +43,14 @@ export function PriceBreakdownSection({ productId, optionId, unitQuantities, cat
42
43
  catalogId: catalogId ?? null,
43
44
  enabled: Boolean(productId),
44
45
  });
46
+ const quantitiesKey = React.useMemo(() => JSON.stringify(unitQuantities), [unitQuantities]);
47
+ const [manualAmountCents, setManualAmountCents] = React.useState(null);
48
+ const [overrideReason, setOverrideReason] = React.useState("");
49
+ // biome-ignore lint/correctness/useExhaustiveDependencies: reset manual confirmation when the priced selection changes
50
+ React.useEffect(() => {
51
+ setManualAmountCents(null);
52
+ setOverrideReason("");
53
+ }, [productId, optionId, catalogId, quantitiesKey]);
45
54
  const snapshot = preview.data?.data;
46
55
  const currency = snapshot?.catalog.currencyCode ?? null;
47
56
  const formatAmount = React.useCallback((cents) => currency
@@ -147,16 +156,38 @@ export function PriceBreakdownSection({ productId, optionId, unitQuantities, cat
147
156
  }
148
157
  return { lines: out, total: anyOnRequest ? null : runningTotal };
149
158
  }, [snapshot, unitQuantities, merged.onRequest, merged.groupRate]);
159
+ const confirmedAmountCents = manualAmountCents ?? total;
160
+ const isManualOverride = manualAmountCents != null && (total === null || manualAmountCents !== total);
161
+ const requiresReason = isManualOverride && overrideReason.trim().length === 0;
162
+ React.useEffect(() => {
163
+ onChange?.({
164
+ catalogAmountCents: total,
165
+ confirmedAmountCents,
166
+ currency,
167
+ priceOverrideReason: overrideReason,
168
+ isManualOverride,
169
+ requiresReason,
170
+ });
171
+ }, [
172
+ confirmedAmountCents,
173
+ currency,
174
+ isManualOverride,
175
+ onChange,
176
+ overrideReason,
177
+ requiresReason,
178
+ total,
179
+ ]);
180
+ const manualTotalControls = (_jsxs("div", { className: "flex flex-col gap-2 border-t pt-2", children: [_jsxs("div", { className: "grid gap-2 sm:grid-cols-[minmax(0,1fr)_auto] sm:items-end", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { className: "text-xs", children: merged.manualTotal }), _jsx(CurrencyInput, { value: manualAmountCents, onChange: setManualAmountCents, currency: currency ?? undefined, placeholder: total === null ? merged.onRequest : formatAmount(total) })] }), manualAmountCents != null ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setManualAmountCents(null), children: merged.useCatalogTotal })) : null] }), isManualOverride ? (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { className: "text-xs", children: merged.overrideReason }), _jsx(Textarea, { value: overrideReason, onChange: (event) => setOverrideReason(event.target.value), placeholder: merged.overrideReasonPlaceholder, rows: 2 }), requiresReason ? (_jsx("p", { className: "text-xs text-destructive", children: merged.overrideReasonRequired })) : null] })) : null] }));
150
181
  // Empty states
151
182
  if (!productId)
152
183
  return null;
153
184
  if (preview.isError || (preview.isSuccess && !snapshot)) {
154
- 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 })] }));
185
+ 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 }), manualTotalControls] }));
155
186
  }
156
187
  if (lines.length === 0) {
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.empty })] }));
188
+ 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 }), manualTotalControls] }));
158
189
  }
159
190
  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: [formatNumber(line.quantity), "x"] }), _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
160
191
  ? merged.onRequest
161
- : formatAmount(line.totalAmountCents) })] }, 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 : formatAmount(total) })] })] }));
192
+ : formatAmount(line.totalAmountCents) })] }, 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 : formatAmount(total) })] }), _jsxs("div", { className: "flex items-baseline justify-between text-sm font-medium", children: [_jsx("span", { children: merged.confirmedTotal }), _jsx("span", { className: "tabular-nums", children: confirmedAmountCents === null ? merged.onRequest : formatAmount(confirmedAmountCents) })] }), manualTotalControls] }));
162
193
  }
@@ -1 +1 @@
1
- {"version":3,"file":"product-picker-section.d.ts","sourceRoot":"","sources":["../../src/components/product-picker-section.tsx"],"names":[],"mappings":"AAiBA,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;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,QAAQ,EACR,OAAc,EACd,WAAmB,EACnB,MAAM,GACP,EAAE,yBAAyB,2CAkF3B"}
1
+ {"version":3,"file":"product-picker-section.d.ts","sourceRoot":"","sources":["../../src/components/product-picker-section.tsx"],"names":[],"mappings":"AA8BA,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;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,QAAQ,EACR,OAAc,EACd,WAAmB,EACnB,MAAM,GACP,EAAE,yBAAyB,2CAgI3B"}
@@ -1,7 +1,8 @@
1
1
  "use client";
2
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/ui/components";
3
+ import { useProduct, useProductOptions, useProducts, } from "@voyantjs/products-react";
4
+ import { Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
5
+ import { Combobox, ComboboxCollection, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, } from "@voyantjs/ui/components/combobox";
5
6
  import * as React from "react";
6
7
  import { useBookingsUiMessagesOrDefault } from "../i18n/provider.js";
7
8
  const OPTION_NONE = "__none__";
@@ -19,14 +20,47 @@ export function ProductPickerSection({ value, onChange, enabled = true, lockProd
19
20
  limit: 20,
20
21
  enabled: enabled && !lockProduct,
21
22
  });
22
- const products = productsData?.data ?? [];
23
+ const selectedProductQuery = useProduct(value.productId || undefined, {
24
+ enabled: enabled && Boolean(value.productId),
25
+ });
26
+ const products = React.useMemo(() => {
27
+ const map = new Map();
28
+ for (const product of productsData?.data ?? [])
29
+ map.set(product.id, product);
30
+ if (selectedProductQuery.data)
31
+ map.set(selectedProductQuery.data.id, selectedProductQuery.data);
32
+ return Array.from(map.values());
33
+ }, [productsData?.data, selectedProductQuery.data]);
34
+ const productMap = React.useMemo(() => new Map(products.map((product) => [product.id, product])), [products]);
35
+ const selectedProductLabel = value.productId ? (productMap.get(value.productId)?.name ?? "") : "";
36
+ const [productInputValue, setProductInputValue] = React.useState(selectedProductLabel);
37
+ React.useEffect(() => {
38
+ if (selectedProductLabel)
39
+ setProductInputValue(selectedProductLabel);
40
+ }, [selectedProductLabel]);
23
41
  const { data: optionsData } = useProductOptions({
24
42
  productId: value.productId || undefined,
25
43
  limit: 50,
26
44
  enabled: enabled && Boolean(value.productId),
27
45
  });
28
46
  const options = optionsData?.data ?? [];
29
- 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: [
47
+ 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: "*" })] }), _jsxs(Combobox, { items: products.map((product) => product.id), value: value.productId || null, inputValue: productInputValue, autoHighlight: true, disabled: !enabled, itemToStringValue: (id) => productMap.get(id)?.name ?? "", onInputValueChange: (next) => {
48
+ setProductInputValue(next);
49
+ setProductSearch(next);
50
+ if (!next)
51
+ onChange({ productId: "", optionId: null });
52
+ }, onValueChange: (next) => {
53
+ const productId = next ?? "";
54
+ onChange({ productId, optionId: null });
55
+ setProductInputValue(productId ? (productMap.get(productId)?.name ?? "") : "");
56
+ }, children: [_jsx(ComboboxInput, { placeholder: merged.productSearchPlaceholder, showClear: !!value.productId }), _jsxs(ComboboxContent, { children: [_jsx(ComboboxEmpty, { children: merged.productEmpty }), _jsx(ComboboxList, { children: _jsx(ComboboxCollection, { children: (id) => {
57
+ const product = productMap.get(id);
58
+ if (!product)
59
+ return null;
60
+ return (_jsx(ComboboxItem, { value: product.id, children: _jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate font-medium", children: product.name }), _jsxs("span", { className: "truncate text-xs text-muted-foreground", children: [product.sellCurrency, product.sellAmountCents != null
61
+ ? ` · ${product.sellAmountCents / 100}`
62
+ : ""] })] }) }, product.id));
63
+ } }) })] })] })] })), value.productId && options.length > 0 && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: merged.option }), _jsxs(Select, { items: [
30
64
  { label: merged.optionNone, value: OPTION_NONE },
31
65
  ...options.map((o) => ({ label: o.name, value: o.id })),
32
66
  ], value: value.optionId ?? OPTION_NONE, onValueChange: (v) => onChange({
@@ -4,6 +4,8 @@ export interface SharedRoomValue {
4
4
  mode: SharedRoomMode;
5
5
  /** Only meaningful in "join" mode. */
6
6
  groupId: string;
7
+ /** Optional label used when creating a new shared-room group. */
8
+ groupLabel?: string;
7
9
  }
8
10
  export declare const emptySharedRoomValue: SharedRoomValue;
9
11
  export interface SharedRoomSectionProps {
@@ -11,7 +13,7 @@ export interface SharedRoomSectionProps {
11
13
  onChange: (value: SharedRoomValue) => void;
12
14
  /**
13
15
  * The product context for fetching joinable groups. When unset, the join
14
- * dropdown is disabled even if the user toggles into join mode.
16
+ * combobox is disabled even if the user toggles into join mode.
15
17
  */
16
18
  productId?: string;
17
19
  enabled?: boolean;
@@ -22,16 +24,15 @@ export interface SharedRoomSectionProps {
22
24
  selectPlaceholder?: string;
23
25
  noGroups?: string;
24
26
  createHint?: string;
27
+ createSheetTitle?: string;
28
+ groupLabel?: string;
29
+ groupLabelPlaceholder?: string;
30
+ createAction?: string;
25
31
  };
26
32
  }
27
33
  /**
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).
34
+ * Shared-room (partaj) attachment section. Operators can create a new group in
35
+ * a sheet or join an existing product-scoped group with an async combobox.
35
36
  */
36
37
  export declare function SharedRoomSection({ value, onChange, productId, enabled, labels, }: SharedRoomSectionProps): import("react/jsx-runtime").JSX.Element;
37
38
  //# sourceMappingURL=shared-room-section.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-room-section.d.ts","sourceRoot":"","sources":["../../src/components/shared-room-section.tsx"],"names":[],"mappings":"AAiBA,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;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,EACR,SAAS,EACT,OAAc,EACd,MAAM,GACP,EAAE,sBAAsB,2CAoFxB"}
1
+ {"version":3,"file":"shared-room-section.d.ts","sourceRoot":"","sources":["../../src/components/shared-room-section.tsx"],"names":[],"mappings":"AA2BA,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;IACf,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,eAAO,MAAM,oBAAoB,EAAE,eAKlC,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;QACnB,gBAAgB,CAAC,EAAE,MAAM,CAAA;QACzB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,qBAAqB,CAAC,EAAE,MAAM,CAAA;QAC9B,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,CAAA;CACF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,EACR,SAAS,EACT,OAAc,EACd,MAAM,GACP,EAAE,sBAAsB,2CAyJxB"}
@@ -1,34 +1,87 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useBookingGroups } from "@voyantjs/bookings-react";
4
- import { Button, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
4
+ import { Button, Input, Label, Sheet, SheetBody, SheetContent, SheetFooter, SheetHeader, SheetTitle, } from "@voyantjs/ui/components";
5
+ import { Combobox, ComboboxCollection, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, } from "@voyantjs/ui/components/combobox";
6
+ import { Link2, Plus } from "lucide-react";
7
+ import * as React from "react";
5
8
  import { useBookingsUiMessagesOrDefault } from "../i18n/provider.js";
6
- const GROUP_NONE = "__none__";
7
9
  export const emptySharedRoomValue = {
8
10
  enabled: false,
9
11
  mode: "create",
10
12
  groupId: "",
13
+ groupLabel: "",
11
14
  };
12
15
  /**
13
- * Shared-room (partaj) attachment section. Operators use it to either create a
14
- * new `booking_groups` row at booking-create time or join an existing group
15
- * that already has a primary booking.
16
- *
17
- * The section only handles the *selection* — the parent dialog owns the
18
- * create-group + add-member mutations because they fire *after* the booking
19
- * insert (we need the new booking id to attach).
16
+ * Shared-room (partaj) attachment section. Operators can create a new group in
17
+ * a sheet or join an existing product-scoped group with an async combobox.
20
18
  */
21
19
  export function SharedRoomSection({ value, onChange, productId, enabled = true, labels, }) {
20
+ const [groupSearch, setGroupSearch] = React.useState("");
21
+ const [groupInputValue, setGroupInputValue] = React.useState("");
22
+ const [createSheetOpen, setCreateSheetOpen] = React.useState(false);
23
+ const [draftGroupLabel, setDraftGroupLabel] = React.useState(value.groupLabel ?? "");
22
24
  const messages = useBookingsUiMessagesOrDefault();
23
25
  const merged = { ...messages.sharedRoomSection.labels, ...labels };
24
26
  const { data: groupsData } = useBookingGroups({
27
+ kind: "shared_room",
25
28
  productId: productId || undefined,
26
29
  limit: 50,
27
30
  enabled: enabled && value.enabled && value.mode === "join" && Boolean(productId),
28
31
  });
29
- const existingGroups = groupsData?.data ?? [];
32
+ const existingGroups = React.useMemo(() => {
33
+ const normalizedSearch = groupSearch.trim().toLowerCase();
34
+ const groups = groupsData?.data ?? [];
35
+ if (!normalizedSearch)
36
+ return groups;
37
+ return groups.filter((group) => group.label.toLowerCase().includes(normalizedSearch));
38
+ }, [groupsData?.data, groupSearch]);
39
+ const groupsMap = React.useMemo(() => new Map((groupsData?.data ?? []).map((group) => [group.id, group])), [groupsData?.data]);
40
+ const selectedGroupLabel = value.groupId ? (groupsMap.get(value.groupId)?.label ?? "") : "";
41
+ React.useEffect(() => {
42
+ if (selectedGroupLabel)
43
+ setGroupInputValue(selectedGroupLabel);
44
+ }, [selectedGroupLabel]);
45
+ React.useEffect(() => {
46
+ if (createSheetOpen)
47
+ setDraftGroupLabel(value.groupLabel ?? "");
48
+ }, [createSheetOpen, value.groupLabel]);
30
49
  const set = (patch) => onChange({ ...value, ...patch });
31
- 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
32
- ? [{ label: merged.noGroups, value: GROUP_NONE }]
33
- : 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 }))] }))] }));
50
+ const selectCreateMode = () => {
51
+ if (!enabled)
52
+ return;
53
+ setCreateSheetOpen(true);
54
+ };
55
+ const selectJoinMode = () => {
56
+ if (!enabled)
57
+ return;
58
+ set({ enabled: true, mode: "join", groupLabel: "" });
59
+ };
60
+ const saveCreateLabel = () => {
61
+ set({
62
+ enabled: true,
63
+ mode: "create",
64
+ groupId: "",
65
+ groupLabel: draftGroupLabel.trim(),
66
+ });
67
+ setCreateSheetOpen(false);
68
+ };
69
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-col gap-3 rounded-md border p-3", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { children: merged.toggle }), value.enabled && value.mode === "create" && value.groupLabel ? (_jsx("p", { className: "text-xs text-muted-foreground", children: value.groupLabel })) : value.enabled && value.mode === "create" ? (_jsx("p", { className: "text-xs text-muted-foreground", children: merged.createHint })) : null] }), _jsxs("div", { className: "grid gap-2 sm:grid-cols-2", children: [_jsxs(Button, { type: "button", size: "sm", variant: value.enabled && value.mode === "create" ? "default" : "outline", onClick: selectCreateMode, disabled: !enabled, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), merged.createMode] }), _jsxs(Button, { type: "button", size: "sm", variant: value.enabled && value.mode === "join" ? "default" : "outline", onClick: selectJoinMode, disabled: !enabled || !productId, children: [_jsx(Link2, { className: "mr-2 h-4 w-4" }), merged.joinMode] })] }), value.enabled && value.mode === "join" ? (_jsxs(Combobox, { items: existingGroups.map((group) => group.id), value: value.groupId || null, inputValue: groupInputValue, autoHighlight: true, disabled: !enabled || !productId, itemToStringValue: (id) => groupsMap.get(id)?.label ?? "", onInputValueChange: (next) => {
70
+ setGroupInputValue(next);
71
+ setGroupSearch(next);
72
+ if (!next)
73
+ set({ groupId: "" });
74
+ }, onValueChange: (next) => {
75
+ const groupId = next ?? "";
76
+ set({ groupId });
77
+ setGroupInputValue(groupId ? (groupsMap.get(groupId)?.label ?? "") : "");
78
+ }, children: [_jsx(ComboboxInput, { placeholder: merged.selectPlaceholder, showClear: !!value.groupId }), _jsxs(ComboboxContent, { children: [_jsx(ComboboxEmpty, { children: merged.noGroups }), _jsx(ComboboxList, { children: _jsx(ComboboxCollection, { children: (id) => {
79
+ const group = groupsMap.get(id);
80
+ if (!group)
81
+ return null;
82
+ return _jsx(SharedRoomGroupItem, { group: group }, group.id);
83
+ } }) })] })] })) : null] }), _jsx(Sheet, { open: createSheetOpen, onOpenChange: setCreateSheetOpen, children: _jsxs(SheetContent, { side: "right", size: "default", children: [_jsx(SheetHeader, { children: _jsx(SheetTitle, { children: merged.createSheetTitle }) }), _jsx(SheetBody, { children: _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "shared-room-group-label", children: merged.groupLabel }), _jsx(Input, { id: "shared-room-group-label", value: draftGroupLabel, onChange: (event) => setDraftGroupLabel(event.target.value), placeholder: merged.groupLabelPlaceholder }), _jsx("p", { className: "text-xs text-muted-foreground", children: merged.createHint })] }) }), _jsxs(SheetFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => setCreateSheetOpen(false), children: messages.common.cancel }), _jsx(Button, { type: "button", onClick: saveCreateLabel, children: merged.createAction })] })] }) })] }));
84
+ }
85
+ function SharedRoomGroupItem({ group }) {
86
+ return (_jsx(ComboboxItem, { value: group.id, children: _jsx("span", { className: "truncate font-medium", children: group.label }) }));
34
87
  }
@@ -1 +1 @@
1
- {"version":3,"file":"traveler-list.d.ts","sourceRoot":"","sources":["../../src/components/traveler-list.tsx"],"names":[],"mappings":"AAkBA,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,UAAkB,EAAE,EAAE,iBAAiB,2CA+HhF"}
1
+ {"version":3,"file":"traveler-list.d.ts","sourceRoot":"","sources":["../../src/components/traveler-list.tsx"],"names":[],"mappings":"AAkBA,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,UAAkB,EAAE,EAAE,iBAAiB,2CAqIhF"}
@@ -4,7 +4,7 @@ import { useBookingTravelerDocuments, useRevealTraveler, useTravelerMutation, us
4
4
  import { Button, Card, CardContent, CardHeader, CardTitle } from "@voyantjs/ui/components";
5
5
  import { Eye, EyeOff, Loader2, Pencil, Plus, Trash2, Users } from "lucide-react";
6
6
  import * as React from "react";
7
- import { useBookingsUiMessagesOrDefault } from "../i18n/provider.js";
7
+ import { formatMessage, useBookingsUiMessagesOrDefault } from "../i18n/provider.js";
8
8
  import { TravelerDialog } from "./traveler-dialog.js";
9
9
  export function TravelerList({ bookingId, autoReveal = false }) {
10
10
  const [dialogOpen, setDialogOpen] = React.useState(false);
@@ -47,7 +47,7 @@ export function TravelerList({ bookingId, autoReveal = false }) {
47
47
  return (_jsxs(Card, { "data-slot": "traveler-list", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(Users, { className: "h-4 w-4" }), messages.travelerList.title] }), _jsxs(Button, { size: "sm", onClick: () => {
48
48
  setEditing(undefined);
49
49
  setDialogOpen(true);
50
- }, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), messages.travelerList.addTraveler] })] }), _jsx(CardContent, { children: travelers.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: messages.travelerList.empty })) : (_jsx("div", { className: "overflow-x-auto rounded border bg-background", children: _jsxs("table", { className: "w-full min-w-[980px] text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b text-muted-foreground", children: [_jsx("th", { className: "p-2 text-left font-medium", children: messages.travelerList.columns.name }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.travelerList.columns.email }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.travelerList.columns.phone }), _jsx("th", { className: "p-2 text-left font-medium", children: "Role" }), _jsx("th", { className: "p-2 text-left font-medium", children: "DOB / age" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Documents" }), _jsx("th", { className: "w-20 p-2" })] }) }), _jsx("tbody", { children: travelers.map((traveler) => (_jsx(TravelerRow, { bookingId: bookingId, traveler: traveler, documents: documentsByTraveler.get(traveler.id) ?? [], revealed: autoReveal || revealedIds.has(traveler.id), onToggleReveal: autoReveal || allAlreadyRevealed ? undefined : () => toggleReveal(traveler.id), emailUnavailable: messages.travelerList.values.emailUnavailable, phoneUnavailable: messages.travelerList.values.phoneUnavailable, onEdit: () => {
50
+ }, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), messages.travelerList.addTraveler] })] }), _jsx(CardContent, { children: travelers.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: messages.travelerList.empty })) : (_jsx("div", { className: "overflow-x-auto rounded border bg-background", children: _jsxs("table", { className: "w-full min-w-[980px] text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b text-muted-foreground", children: [_jsx("th", { className: "p-2 text-left font-medium", children: messages.travelerList.columns.name }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.travelerList.columns.email }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.travelerList.columns.phone }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.travelerList.columns.role }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.travelerList.columns.dobAge }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.travelerList.columns.documents }), _jsx("th", { className: "w-20 p-2" })] }) }), _jsx("tbody", { children: travelers.map((traveler) => (_jsx(TravelerRow, { bookingId: bookingId, traveler: traveler, documents: documentsByTraveler.get(traveler.id) ?? [], revealed: autoReveal || revealedIds.has(traveler.id), onToggleReveal: autoReveal || allAlreadyRevealed ? undefined : () => toggleReveal(traveler.id), emailUnavailable: messages.travelerList.values.emailUnavailable, phoneUnavailable: messages.travelerList.values.phoneUnavailable, onEdit: () => {
51
51
  setEditing(traveler);
52
52
  setDialogOpen(true);
53
53
  }, onDelete: () => {
@@ -69,6 +69,7 @@ export function TravelerList({ bookingId, autoReveal = false }) {
69
69
  * the server, so toggling the eye button creates a permanent record.
70
70
  */
71
71
  function TravelerRow({ bookingId, traveler, documents, revealed, onToggleReveal, emailUnavailable, phoneUnavailable, onEdit, onDelete, }) {
72
+ const messages = useBookingsUiMessagesOrDefault();
72
73
  const reveal = useRevealTraveler(bookingId, traveler.id, { enabled: revealed });
73
74
  // Use the revealed copy when available; otherwise fall back to
74
75
  // the masked row from the list endpoint. This keeps the UI snappy
@@ -79,29 +80,39 @@ function TravelerRow({ bookingId, traveler, documents, revealed, onToggleReveal,
79
80
  const travelDetails = revealed && revealedTraveler ? revealedTraveler.travelDetails : null;
80
81
  const showLoading = revealed && reveal.isLoading;
81
82
  const revealError = revealed && reveal.error;
82
- return (_jsxs(_Fragment, { children: [_jsxs("tr", { className: "border-b", children: [_jsx("td", { className: "p-2", children: showLoading ? (_jsx(RowLoading, {})) : (`${display.firstName ?? ""} ${display.lastName ?? ""}`.trim()) }), _jsx("td", { className: "p-2", children: showLoading ? _jsx(RowLoading, {}) : (display.email ?? emailUnavailable) }), _jsx("td", { className: "p-2", children: showLoading ? _jsx(RowLoading, {}) : (display.phone ?? phoneUnavailable) }), _jsx("td", { className: "p-2", children: _jsxs("div", { className: "flex flex-wrap gap-1.5", children: [display.isPrimary ? _jsx(MiniPill, { children: "Primary" }) : null, travelDetails?.isLeadTraveler ? _jsx(MiniPill, { children: "Lead" }) : null, display.travelerCategory ? _jsx(MiniPill, { children: display.travelerCategory }) : null] }) }), _jsx("td", { className: "p-2", children: showLoading ? _jsx(RowLoading, {}) : formatDobAge(travelDetails?.dateOfBirth) }), _jsx("td", { className: "p-2", children: documents.length > 0 ? (_jsxs("div", { className: "flex flex-wrap gap-1.5", children: [documents.slice(0, 2).map((document) => (_jsx(MiniPill, { children: document.type.replaceAll("_", " ") }, document.id))), documents.length > 2 ? _jsxs(MiniPill, { children: ["+", documents.length - 2] }) : null] })) : (_jsx("span", { className: "text-muted-foreground", children: "-" })) }), _jsxs("td", { className: "p-2", children: [_jsxs("div", { className: "flex items-center gap-1", children: [onToggleReveal ? (_jsx("button", { type: "button", onClick: onToggleReveal, className: "text-muted-foreground hover:text-foreground", title: revealed ? "Hide details" : "Reveal contact details", "aria-label": revealed ? "Hide traveler contact details" : "Reveal traveler contact details", children: revealed ? _jsx(EyeOff, { className: "h-3.5 w-3.5" }) : _jsx(Eye, { className: "h-3.5 w-3.5" }) })) : null, _jsx("button", { type: "button", onClick: onEdit, className: "text-muted-foreground hover:text-foreground", "aria-label": "Edit traveler", children: _jsx(Pencil, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: onDelete, className: "text-muted-foreground hover:text-destructive", "aria-label": "Delete traveler", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }), revealError ? (_jsx("div", { className: "mt-1 text-[10px] text-destructive", children: revealError instanceof Error ? revealError.message : "Reveal failed" })) : null] })] }), _jsx("tr", { className: "border-b last:border-b-0", children: _jsx("td", { colSpan: 7, className: "bg-muted/20 px-2 py-3", children: _jsx(TravelerContextGrid, { traveler: display, travelDetails: travelDetails, documents: documents, loading: showLoading }) }) })] }));
83
+ return (_jsxs(_Fragment, { children: [_jsxs("tr", { className: "border-b", children: [_jsx("td", { className: "p-2", children: showLoading ? (_jsx(RowLoading, {})) : (`${display.firstName ?? ""} ${display.lastName ?? ""}`.trim()) }), _jsx("td", { className: "p-2", children: showLoading ? _jsx(RowLoading, {}) : (display.email ?? emailUnavailable) }), _jsx("td", { className: "p-2", children: showLoading ? _jsx(RowLoading, {}) : (display.phone ?? phoneUnavailable) }), _jsx("td", { className: "p-2", children: _jsxs("div", { className: "flex flex-wrap gap-1.5", children: [display.isPrimary ? _jsx(MiniPill, { children: messages.travelerList.roles.primary }) : null, travelDetails?.isLeadTraveler ? (_jsx(MiniPill, { children: messages.travelerList.roles.lead })) : null, display.travelerCategory ? _jsx(MiniPill, { children: display.travelerCategory }) : null] }) }), _jsx("td", { className: "p-2", children: showLoading ? (_jsx(RowLoading, {})) : (formatDobAge(travelDetails?.dateOfBirth, messages.travelerList.values.fieldUnavailable)) }), _jsx("td", { className: "p-2", children: documents.length > 0 ? (_jsxs("div", { className: "flex flex-wrap gap-1.5", children: [documents.slice(0, 2).map((document) => (_jsx(MiniPill, { children: document.type.replaceAll("_", " ") }, document.id))), documents.length > 2 ? _jsxs(MiniPill, { children: ["+", documents.length - 2] }) : null] })) : (_jsx("span", { className: "text-muted-foreground", children: messages.travelerList.values.documentsUnavailable })) }), _jsxs("td", { className: "p-2", children: [_jsxs("div", { className: "flex items-center gap-1", children: [onToggleReveal ? (_jsx("button", { type: "button", onClick: onToggleReveal, className: "text-muted-foreground hover:text-foreground", title: revealed
84
+ ? messages.travelerList.actions.hideContactDetails
85
+ : messages.travelerList.actions.revealContactDetails, "aria-label": revealed
86
+ ? messages.travelerList.actions.hideTravelerContactDetails
87
+ : messages.travelerList.actions.revealTravelerContactDetails, children: revealed ? _jsx(EyeOff, { className: "h-3.5 w-3.5" }) : _jsx(Eye, { className: "h-3.5 w-3.5" }) })) : null, _jsx("button", { type: "button", onClick: onEdit, className: "text-muted-foreground hover:text-foreground", "aria-label": messages.travelerList.actions.editTraveler, children: _jsx(Pencil, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: onDelete, className: "text-muted-foreground hover:text-destructive", "aria-label": messages.travelerList.actions.deleteTraveler, children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }), revealError ? (_jsx("div", { className: "mt-1 text-[10px] text-destructive", children: revealError instanceof Error
88
+ ? revealError.message
89
+ : messages.travelerList.validation.revealFailed })) : null] })] }), _jsx("tr", { className: "border-b last:border-b-0", children: _jsx("td", { colSpan: 7, className: "bg-muted/20 px-2 py-3", children: _jsx(TravelerContextGrid, { traveler: display, travelDetails: travelDetails, documents: documents, loading: showLoading }) }) })] }));
83
90
  }
84
91
  function RowLoading() {
85
- return (_jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), _jsx("span", { className: "text-xs", children: "Decrypting\u2026" })] }));
92
+ const messages = useBookingsUiMessagesOrDefault();
93
+ return (_jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), _jsx("span", { className: "text-xs", children: messages.travelerList.loading.decrypting })] }));
86
94
  }
87
95
  function TravelerContextGrid({ traveler, travelDetails, documents, loading, }) {
96
+ const messages = useBookingsUiMessagesOrDefault();
88
97
  if (loading)
89
98
  return _jsx(RowLoading, {});
90
99
  const fields = [
91
- ["Nationality", travelDetails?.nationality],
92
- ["Passport", travelDetails?.passportNumber],
93
- ["Passport expiry", formatDateValue(travelDetails?.passportExpiry)],
94
- ["Language", traveler.preferredLanguage],
95
- ["Dietary", travelDetails?.dietaryRequirements],
96
- ["Accessibility", travelDetails?.accessibilityNeeds],
97
- ["Special requests", traveler.specialRequests],
98
- ["Notes", traveler.notes],
100
+ [messages.travelerList.context.nationality, travelDetails?.nationality],
101
+ [messages.travelerList.context.passport, travelDetails?.passportNumber],
102
+ [messages.travelerList.context.passportExpiry, formatDateValue(travelDetails?.passportExpiry)],
103
+ [messages.travelerList.context.language, traveler.preferredLanguage],
104
+ [messages.travelerList.context.dietary, travelDetails?.dietaryRequirements],
105
+ [messages.travelerList.context.accessibility, travelDetails?.accessibilityNeeds],
106
+ [messages.travelerList.context.specialRequests, traveler.specialRequests],
107
+ [messages.travelerList.context.notes, traveler.notes],
99
108
  ];
100
109
  const visibleFields = fields.filter(([, value]) => Boolean(value));
101
110
  if (visibleFields.length === 0 && documents.length === 0) {
102
- return _jsx("span", { className: "text-xs text-muted-foreground", children: "No additional traveler context" });
111
+ return (_jsx("span", { className: "text-xs text-muted-foreground", children: messages.travelerList.values.noAdditionalContext }));
103
112
  }
104
- return (_jsxs("div", { className: "grid gap-3 md:grid-cols-4", children: [visibleFields.map(([label, value]) => (_jsx(DetailField, { label: label, value: value ?? "-" }, label))), documents.map((document) => (_jsx(DetailField, { label: `Document · ${document.type.replaceAll("_", " ")}`, value: document.fileName }, document.id)))] }));
113
+ return (_jsxs("div", { className: "grid gap-3 md:grid-cols-4", children: [visibleFields.map(([label, value]) => (_jsx(DetailField, { label: label, value: value ?? messages.travelerList.values.fieldUnavailable }, label))), documents.map((document) => (_jsx(DetailField, { label: formatMessage(messages.travelerList.context.documentLabel, {
114
+ type: document.type.replaceAll("_", " "),
115
+ }), value: document.fileName }, document.id)))] }));
105
116
  }
106
117
  function DetailField({ label, value }) {
107
118
  return (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-[10px] font-medium uppercase text-muted-foreground", children: label }), _jsx("div", { className: "truncate text-xs text-foreground", children: value })] }));
@@ -109,9 +120,9 @@ function DetailField({ label, value }) {
109
120
  function MiniPill({ children }) {
110
121
  return (_jsx("span", { className: "inline-flex h-5 items-center rounded-full border px-2 text-[11px] capitalize text-muted-foreground", children: children }));
111
122
  }
112
- function formatDobAge(value) {
123
+ function formatDobAge(value, unavailable) {
113
124
  if (!value)
114
- return "-";
125
+ return unavailable;
115
126
  const date = new Date(value);
116
127
  if (Number.isNaN(date.getTime()))
117
128
  return value;