@voyantjs/bookings-ui 0.50.5 → 0.50.7

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 (43) hide show
  1. package/dist/components/booking-create-dialog.d.ts +1 -1
  2. package/dist/components/booking-create-dialog.d.ts.map +1 -1
  3. package/dist/components/booking-create-dialog.js +94 -27
  4. package/dist/components/booking-create-page.d.ts +2 -1
  5. package/dist/components/booking-create-page.d.ts.map +1 -1
  6. package/dist/components/booking-create-page.js +2 -2
  7. package/dist/components/booking-create-utils.d.ts +33 -0
  8. package/dist/components/booking-create-utils.d.ts.map +1 -0
  9. package/dist/components/booking-create-utils.js +67 -0
  10. package/dist/components/booking-list.d.ts +2 -1
  11. package/dist/components/booking-list.d.ts.map +1 -1
  12. package/dist/components/booking-list.js +5 -1
  13. package/dist/components/bookings-page.d.ts +2 -1
  14. package/dist/components/bookings-page.d.ts.map +1 -1
  15. package/dist/components/bookings-page.js +2 -2
  16. package/dist/components/payment-schedule-section.d.ts +23 -0
  17. package/dist/components/payment-schedule-section.d.ts.map +1 -1
  18. package/dist/components/payment-schedule-section.js +48 -3
  19. package/dist/components/price-breakdown-section.d.ts +1 -0
  20. package/dist/components/price-breakdown-section.d.ts.map +1 -1
  21. package/dist/components/price-breakdown-section.js +2 -0
  22. package/dist/components/product-picker-section.d.ts.map +1 -1
  23. package/dist/components/product-picker-section.js +8 -4
  24. package/dist/components/rooms-stepper-section.d.ts +12 -7
  25. package/dist/components/rooms-stepper-section.d.ts.map +1 -1
  26. package/dist/components/rooms-stepper-section.js +24 -8
  27. package/dist/components/shared-room-section.d.ts +2 -0
  28. package/dist/components/shared-room-section.d.ts.map +1 -1
  29. package/dist/components/shared-room-section.js +14 -2
  30. package/dist/components/travelers-section.d.ts +15 -10
  31. package/dist/components/travelers-section.d.ts.map +1 -1
  32. package/dist/components/travelers-section.js +96 -14
  33. package/dist/i18n/en.d.ts +32 -0
  34. package/dist/i18n/en.d.ts.map +1 -1
  35. package/dist/i18n/en.js +32 -0
  36. package/dist/i18n/messages.d.ts +32 -0
  37. package/dist/i18n/messages.d.ts.map +1 -1
  38. package/dist/i18n/provider.d.ts +64 -0
  39. package/dist/i18n/provider.d.ts.map +1 -1
  40. package/dist/i18n/ro.d.ts +32 -0
  41. package/dist/i18n/ro.d.ts.map +1 -1
  42. package/dist/i18n/ro.js +32 -0
  43. package/package.json +24 -24
@@ -18,7 +18,7 @@ export interface BookingCreateFormProps {
18
18
  * Operator booking-create dialog. Composes the booking-create picker
19
19
  * sections — product, departure, rooms, person, shared-room, travelers,
20
20
  * price breakdown, voucher, payment schedule — and submits via the atomic
21
- * `POST /v1/bookings/quick-create` endpoint so partial failures can't
21
+ * `POST /v1/bookings/create` endpoint so partial failures can't
22
22
  * leave orphan state.
23
23
  *
24
24
  * Normally consumed via `BookingDialog` which delegates here when no
@@ -1 +1 @@
1
- {"version":3,"file":"booking-create-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-create-dialog.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,aAAa,EAOnB,MAAM,0BAA0B,CAAA;AAuJjC,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,gBAAgB,GACjB,EAAE,wBAAwB,2CAqB1B;AAED,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,gBAAgB,EAChB,OAAc,EACd,QAAQ,GACT,EAAE,sBAAsB,2CAycxB"}
1
+ {"version":3,"file":"booking-create-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-create-dialog.tsx"],"names":[],"mappings":"AAGA,OAAO,EAKL,KAAK,aAAa,EAGnB,MAAM,0BAA0B,CAAA;AAsMjC,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,gBAAgB,GACjB,EAAE,wBAAwB,2CAqB1B;AAED,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,gBAAgB,EAChB,OAAc,EACd,QAAQ,GACT,EAAE,sBAAsB,2CAgiBxB"}
@@ -1,11 +1,12 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useSlots, useSlotUnitAvailability } from "@voyantjs/availability-react";
4
- import { useBookingQuickCreateMutation, useBookingStatusByIdMutation, } from "@voyantjs/bookings-react";
5
- import { Button, Checkbox, Dialog, DialogContent, DialogHeader, DialogTitle, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea, } from "@voyantjs/ui/components";
4
+ import { useBookingCreateMutation, useBookingStatusByIdMutation, } from "@voyantjs/bookings-react";
5
+ import { Button, Checkbox, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea, } from "@voyantjs/ui/components";
6
6
  import { Loader2 } from "lucide-react";
7
7
  import * as React from "react";
8
8
  import { formatMessage, useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault, } from "../i18n/provider.js";
9
+ import { getBookableDepartureSlots, getSelectedSharedRoomUnitId, itemLinesToRows, } from "./booking-create-utils.js";
9
10
  import { emptyPaymentScheduleValue, PaymentScheduleSection, } from "./payment-schedule-section.js";
10
11
  import { emptyPersonPickerValue, PersonPickerSection, } from "./person-picker-section.js";
11
12
  import { PriceBreakdownSection } from "./price-breakdown-section.js";
@@ -30,10 +31,11 @@ function paymentScheduleToRows(value, currency, totalAmountCents) {
30
31
  return [
31
32
  {
32
33
  scheduleType: "balance",
33
- status: "due",
34
+ status: value.fullAlreadyPaid ? "paid" : "due",
34
35
  dueDate: value.fullDueDate,
35
36
  currency,
36
37
  amountCents: totalAmountCents,
38
+ notes: paidScheduleNotes(value.fullAlreadyPaid, value.fullPaymentDate, value.fullPaymentMethod, value.fullPaymentReference),
37
39
  },
38
40
  ];
39
41
  }
@@ -43,10 +45,11 @@ function paymentScheduleToRows(value, currency, totalAmountCents) {
43
45
  const rows = [
44
46
  {
45
47
  scheduleType: "deposit",
46
- status: "due",
48
+ status: value.advanceAlreadyPaid ? "paid" : "due",
47
49
  dueDate: value.advanceDueDate,
48
50
  currency,
49
51
  amountCents: value.advanceAmountCents,
52
+ notes: paidScheduleNotes(value.advanceAlreadyPaid, value.advancePaymentDate, value.advancePaymentMethod, value.advancePaymentReference),
50
53
  },
51
54
  ];
52
55
  if (totalAmountCents !== null && totalAmountCents > value.advanceAmountCents) {
@@ -65,25 +68,38 @@ function paymentScheduleToRows(value, currency, totalAmountCents) {
65
68
  if (value.splitFirstDueDate && value.splitFirstAmountCents != null) {
66
69
  rows.push({
67
70
  scheduleType: "installment",
68
- status: "due",
71
+ status: value.splitFirstAlreadyPaid ? "paid" : "due",
69
72
  dueDate: value.splitFirstDueDate,
70
73
  currency,
71
74
  amountCents: value.splitFirstAmountCents,
75
+ notes: paidScheduleNotes(value.splitFirstAlreadyPaid, value.splitFirstPaymentDate, value.splitFirstPaymentMethod, value.splitFirstPaymentReference),
72
76
  });
73
77
  }
74
78
  if (value.splitSecondDueDate && value.splitSecondAmountCents != null) {
75
79
  rows.push({
76
80
  scheduleType: "installment",
77
- status: "pending",
81
+ status: value.splitSecondAlreadyPaid ? "paid" : "pending",
78
82
  dueDate: value.splitSecondDueDate,
79
83
  currency,
80
84
  amountCents: value.splitSecondAmountCents,
85
+ notes: paidScheduleNotes(value.splitSecondAlreadyPaid, value.splitSecondPaymentDate, value.splitSecondPaymentMethod, value.splitSecondPaymentReference),
81
86
  });
82
87
  }
83
88
  return rows;
84
89
  }
90
+ function paidScheduleNotes(alreadyPaid, paymentDate, paymentMethod, paymentReference) {
91
+ if (!alreadyPaid)
92
+ return null;
93
+ return JSON.stringify({
94
+ alreadyPaid: true,
95
+ paymentDate,
96
+ paymentMethod,
97
+ paymentReference: paymentReference.trim() || null,
98
+ });
99
+ }
85
100
  function travelersToRows(value) {
86
101
  return value.travelers.map((traveler) => ({
102
+ personId: traveler.personId,
87
103
  firstName: traveler.firstName.trim(),
88
104
  lastName: traveler.lastName.trim(),
89
105
  email: traveler.email.trim() || null,
@@ -103,7 +119,7 @@ function travelersToRows(value) {
103
119
  * Operator booking-create dialog. Composes the booking-create picker
104
120
  * sections — product, departure, rooms, person, shared-room, travelers,
105
121
  * price breakdown, voucher, payment schedule — and submits via the atomic
106
- * `POST /v1/bookings/quick-create` endpoint so partial failures can't
122
+ * `POST /v1/bookings/create` endpoint so partial failures can't
107
123
  * leave orphan state.
108
124
  *
109
125
  * Normally consumed via `BookingDialog` which delegates here when no
@@ -130,6 +146,8 @@ export function BookingCreateForm({ onCreated, defaultProductId, enabled = true,
130
146
  const [voucher, setVoucher] = React.useState(emptyVoucherPickerValue);
131
147
  const [pricing, setPricing] = React.useState(null);
132
148
  const [paymentSchedule, setPaymentSchedule] = React.useState(emptyPaymentScheduleValue);
149
+ const [generateContractDocument, setGenerateContractDocument] = React.useState(false);
150
+ const [generateInvoiceDocument, setGenerateInvoiceDocument] = React.useState(false);
133
151
  const [notes, setNotes] = React.useState("");
134
152
  /**
135
153
  * Optional post-create transition: set status to `confirmed` right after
@@ -153,6 +171,8 @@ export function BookingCreateForm({ onCreated, defaultProductId, enabled = true,
153
171
  setVoucher(emptyVoucherPickerValue);
154
172
  setPricing(null);
155
173
  setPaymentSchedule(emptyPaymentScheduleValue);
174
+ setGenerateContractDocument(false);
175
+ setGenerateInvoiceDocument(false);
156
176
  setNotes("");
157
177
  setConfirmAfterCreate(false);
158
178
  setError(null);
@@ -163,28 +183,53 @@ export function BookingCreateForm({ onCreated, defaultProductId, enabled = true,
163
183
  : { productId: defaultProductId, optionId: null });
164
184
  }
165
185
  }, [enabled, defaultProductId]);
166
- // biome-ignore lint/correctness/useExhaustiveDependencies: intentionally only resets when product/option changes
186
+ // biome-ignore lint/correctness/useExhaustiveDependencies: booking-create intentionally resets transient departure state only when product id changes; option changes are reconciled against the selected departure below.
167
187
  React.useEffect(() => {
168
188
  setSlotId(null);
169
189
  setRooms(emptyRoomsStepperValue);
170
- }, [product.productId, product.optionId]);
190
+ setSharedRoom(emptySharedRoomValue);
191
+ }, [product.productId]);
192
+ const [slotsFromIso, setSlotsFromIso] = React.useState(() => new Date().toISOString());
193
+ React.useEffect(() => {
194
+ if (enabled && product.productId)
195
+ setSlotsFromIso(new Date().toISOString());
196
+ }, [enabled, product.productId]);
171
197
  const { data: slotsData } = useSlots({
172
198
  productId: product.productId || undefined,
173
199
  status: "open",
200
+ startsAtFrom: slotsFromIso,
174
201
  limit: 100,
175
202
  enabled: enabled && Boolean(product.productId),
176
203
  });
204
+ const allOpenSlots = React.useMemo(() => {
205
+ return getBookableDepartureSlots(slotsData?.data ?? [], {
206
+ nowIso: slotsFromIso,
207
+ optionId: null,
208
+ });
209
+ }, [slotsData?.data, slotsFromIso]);
177
210
  const slots = React.useMemo(() => {
178
- const nowIso = new Date().toISOString();
179
- return (slotsData?.data ?? [])
180
- .filter((slot) => slot.startsAt >= nowIso)
181
- .filter((slot) => {
182
- if (!product.optionId)
183
- return true;
184
- return slot.optionId === null || slot.optionId === product.optionId;
185
- })
186
- .sort((a, b) => a.startsAt.localeCompare(b.startsAt));
187
- }, [slotsData, product.optionId]);
211
+ const optionSlots = getBookableDepartureSlots(slotsData?.data ?? [], {
212
+ nowIso: slotsFromIso,
213
+ optionId: product.optionId,
214
+ });
215
+ return optionSlots.length > 0 ? optionSlots : allOpenSlots;
216
+ }, [slotsData?.data, slotsFromIso, product.optionId, allOpenSlots]);
217
+ const setSelectedSlot = React.useCallback((nextSlotId) => {
218
+ const selectedSlot = nextSlotId ? allOpenSlots.find((slot) => slot.id === nextSlotId) : null;
219
+ if (selectedSlot?.optionId && selectedSlot.optionId !== product.optionId) {
220
+ setProduct((prev) => ({ ...prev, optionId: selectedSlot.optionId }));
221
+ }
222
+ setSlotId(nextSlotId);
223
+ }, [allOpenSlots, product.optionId]);
224
+ React.useEffect(() => {
225
+ setRooms(emptyRoomsStepperValue);
226
+ if (!slotId || !product.optionId)
227
+ return;
228
+ const selectedSlot = allOpenSlots.find((slot) => slot.id === slotId);
229
+ if (selectedSlot?.optionId && selectedSlot.optionId !== product.optionId) {
230
+ setSlotId(null);
231
+ }
232
+ }, [allOpenSlots, product.optionId, slotId]);
188
233
  const formatSlotLabel = React.useCallback((slot) => {
189
234
  const date = formatDate(slot.startsAt, {
190
235
  year: "numeric",
@@ -224,7 +269,7 @@ export function BookingCreateForm({ onCreated, defaultProductId, enabled = true,
224
269
  const currency = messages.bookingCreateDialog.labels.currency;
225
270
  const pricingCurrency = pricing?.currency ?? currency;
226
271
  const pricingTotalAmountCents = pricing?.confirmedAmountCents ?? undefined;
227
- const quickCreateMutation = useBookingQuickCreateMutation();
272
+ const createBookingMutation = useBookingCreateMutation();
228
273
  const statusMutation = useBookingStatusByIdMutation();
229
274
  const handleSubmit = async () => {
230
275
  setError(null);
@@ -262,6 +307,7 @@ export function BookingCreateForm({ onCreated, defaultProductId, enabled = true,
262
307
  return;
263
308
  }
264
309
  const paymentSchedules = paymentScheduleToRows(paymentSchedule, pricingCurrency, confirmedSellAmountCents);
310
+ const itemLines = itemLinesToRows(rooms.quantities, slotUnitAvailability.data?.data ?? [], pricing);
265
311
  const travelerRows = travelersToRows(travelers);
266
312
  const voucherRedemption = voucher.picked && voucher.picked.remainingAmountCents != null
267
313
  ? {
@@ -269,6 +315,7 @@ export function BookingCreateForm({ onCreated, defaultProductId, enabled = true,
269
315
  amountCents: voucher.picked.remainingAmountCents,
270
316
  }
271
317
  : undefined;
318
+ const selectedSharedRoomUnitId = getSelectedSharedRoomUnitId(rooms.quantities);
272
319
  const groupMembership = sharedRoom.enabled
273
320
  ? sharedRoom.mode === "create"
274
321
  ? {
@@ -276,14 +323,14 @@ export function BookingCreateForm({ onCreated, defaultProductId, enabled = true,
276
323
  kind: "shared_room",
277
324
  label: sharedRoom.groupLabel?.trim() ||
278
325
  `${messages.bookingCreateDialog.labels.sharedRoomGeneratedLabelPrefix} - ${bookingNumber}`,
279
- optionUnitId: product.optionId,
326
+ optionUnitId: selectedSharedRoomUnitId,
280
327
  makeBookingPrimary: true,
281
328
  }
282
329
  : sharedRoom.groupId
283
330
  ? { action: "join", groupId: sharedRoom.groupId, role: "shared" }
284
331
  : undefined
285
332
  : undefined;
286
- const { booking } = await quickCreateMutation.mutateAsync({
333
+ const { booking } = await createBookingMutation.mutateAsync({
287
334
  productId: product.productId,
288
335
  bookingNumber,
289
336
  optionId: product.optionId,
@@ -294,10 +341,15 @@ export function BookingCreateForm({ onCreated, defaultProductId, enabled = true,
294
341
  catalogSellAmountCents,
295
342
  confirmedSellAmountCents,
296
343
  priceOverrideReason: priceOverrideReason || null,
344
+ itemLines: itemLines.length > 0 ? itemLines : undefined,
297
345
  travelers: travelerRows.length > 0 ? travelerRows : undefined,
298
346
  paymentSchedules: paymentSchedules.length > 0 ? paymentSchedules : undefined,
299
347
  voucherRedemption,
300
348
  groupMembership,
349
+ documentGeneration: {
350
+ contractDocument: generateContractDocument,
351
+ invoiceDocument: generateInvoiceDocument,
352
+ },
301
353
  });
302
354
  // Optional post-create confirm. If the app has autoConfirmAndDispatch
303
355
  // wired on the notifications module, the status transition triggers
@@ -329,11 +381,12 @@ export function BookingCreateForm({ onCreated, defaultProductId, enabled = true,
329
381
  setError(err instanceof Error ? err.message : messages.bookingCreateDialog.validation.createFailed);
330
382
  }
331
383
  };
332
- const isSubmitting = quickCreateMutation.isPending || statusMutation.isPending;
333
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid gap-4", children: [_jsx(ProductPickerSection, { value: product, onChange: setProduct, enabled: enabled, lockProduct: Boolean(defaultProductId), labels: {
384
+ const isSubmitting = createBookingMutation.isPending || statusMutation.isPending;
385
+ return (_jsxs(_Fragment, { children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsx(ProductPickerSection, { value: product, onChange: setProduct, enabled: enabled, lockProduct: Boolean(defaultProductId), labels: {
334
386
  optionNone: messages.bookingCreateDialog.labels.noSpecificOption,
335
- } }), product.productId ? (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { children: messages.bookingCreateDialog.fields.departure }), _jsxs(Select, { value: slotId ?? "__none__", onValueChange: (v) => setSlotId(v === "__none__" ? null : (v ?? null)), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: messages.bookingCreateDialog.placeholders.departure }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__none__", children: messages.bookingCreateDialog.placeholders.departureNone }), slots.length === 0 ? (_jsx(SelectItem, { value: "__empty__", disabled: true, children: messages.bookingCreateDialog.placeholders.departureEmpty })) : (slots.map((slot) => (_jsx(SelectItem, { value: slot.id, children: formatSlotLabel(slot) }, slot.id))))] })] })] })) : null, slotId ? (_jsx(RoomsStepperSection, { value: rooms, onChange: setRooms, slotId: slotId, enabled: enabled, labels: {
387
+ } }), product.productId ? (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { children: messages.bookingCreateDialog.fields.departure }), _jsxs(Select, { value: slotId ?? "__none__", onValueChange: (v) => setSelectedSlot(v === "__none__" ? null : (v ?? null)), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: messages.bookingCreateDialog.placeholders.departure }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__none__", children: messages.bookingCreateDialog.placeholders.departureNone }), slots.length === 0 ? (_jsx(SelectItem, { value: "__empty__", disabled: true, children: messages.bookingCreateDialog.placeholders.departureEmpty })) : (slots.map((slot) => (_jsx(SelectItem, { value: slot.id, children: formatSlotLabel(slot) }, slot.id))))] })] })] })) : null, product.optionId ? (_jsx(RoomsStepperSection, { value: rooms, onChange: setRooms, slotId: slotId ?? undefined, optionId: product.optionId, enabled: enabled, labels: {
336
388
  heading: messages.bookingCreateDialog.labels.roomsHeading,
389
+ noOption: messages.bookingCreateDialog.labels.roomsNoOption,
337
390
  noSlot: messages.bookingCreateDialog.labels.roomsNoSlot,
338
391
  noUnits: messages.bookingCreateDialog.labels.roomsNoUnits,
339
392
  remaining: messages.bookingCreateDialog.labels.roomsRemaining,
@@ -349,9 +402,16 @@ export function BookingCreateForm({ onCreated, defaultProductId, enabled = true,
349
402
  selectPlaceholder: messages.bookingCreateDialog.labels.sharedRoomSelectPlaceholder,
350
403
  noGroups: messages.bookingCreateDialog.labels.sharedRoomNoGroups,
351
404
  createHint: messages.bookingCreateDialog.labels.sharedRoomCreateHint,
352
- } }), product.productId ? (_jsx(TravelersSection, { value: travelers, onChange: setTravelers, roomUnits: roomUnitOptions.length > 0 ? roomUnitOptions : undefined, labels: {
405
+ remove: messages.bookingCreateDialog.labels.sharedRoomRemove,
406
+ } }), product.productId ? (_jsx(TravelersSection, { value: travelers, onChange: setTravelers, roomUnits: roomUnitOptions.length > 0 ? roomUnitOptions : undefined, billingPersonId: (person.billTo ?? "person") === "person" ? person.personId : null, labels: {
353
407
  heading: messages.bookingCreateDialog.labels.travelerHeading,
354
408
  addTraveler: messages.bookingCreateDialog.labels.addTraveler,
409
+ person: messages.bookingCreateDialog.labels.travelerPerson,
410
+ personSearchPlaceholder: messages.bookingCreateDialog.labels.travelerPersonSearchPlaceholder,
411
+ personEmpty: messages.bookingCreateDialog.labels.travelerPersonEmpty,
412
+ createNewPerson: messages.bookingCreateDialog.labels.createNewPerson,
413
+ createPersonSheetTitle: messages.bookingCreateDialog.labels.createPersonSheetTitle,
414
+ addBillingPerson: messages.bookingCreateDialog.labels.addBillingPersonAsTraveler,
355
415
  role: messages.bookingCreateDialog.labels.travelerRole,
356
416
  roleLead: messages.bookingCreateDialog.labels.travelerLead,
357
417
  roleAdult: messages.bookingCreateDialog.labels.travelerAdult,
@@ -393,5 +453,12 @@ export function BookingCreateForm({ onCreated, defaultProductId, enabled = true,
393
453
  secondInstallment: messages.bookingCreateDialog.labels.paymentSecondInstallment,
394
454
  preset5050: messages.bookingCreateDialog.labels.paymentPreset5050,
395
455
  unpaidHint: messages.bookingCreateDialog.labels.paymentUnpaidHint,
396
- } }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingCreateDialog.fields.internalNotes }), _jsx(Textarea, { value: notes, onChange: (e) => setNotes(e.target.value), placeholder: messages.bookingCreateDialog.placeholders.internalNotes })] }), _jsxs("div", { className: "flex items-start gap-2 rounded-md border p-3", children: [_jsx(Checkbox, { id: "new-booking-confirm-after-create", checked: confirmAfterCreate, onCheckedChange: (v) => setConfirmAfterCreate(v === true), className: "mt-0.5" }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { htmlFor: "new-booking-confirm-after-create", className: "cursor-pointer text-sm", children: messages.bookingCreateDialog.fields.confirmAfterCreate }), _jsx("p", { className: "text-xs text-muted-foreground", children: messages.bookingCreateDialog.fields.confirmAfterCreateHint })] })] }), error && _jsx("p", { className: "text-xs text-destructive", children: error })] }), _jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: onCancel, disabled: isSubmitting, children: messages.common.cancel }), _jsxs(Button, { type: "button", size: "sm", onClick: handleSubmit, disabled: isSubmitting || !product.productId, children: [isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), messages.bookingCreateDialog.actions.createDraftBooking] })] })] }));
456
+ totalDue: messages.bookingCreateDialog.labels.paymentTotalDue,
457
+ scheduledTotal: messages.bookingCreateDialog.labels.paymentScheduledTotal,
458
+ remaining: messages.bookingCreateDialog.labels.paymentRemaining,
459
+ alreadyPaid: messages.bookingCreateDialog.labels.paymentAlreadyPaid,
460
+ paymentDate: messages.bookingCreateDialog.labels.paymentDate,
461
+ paymentMethod: messages.bookingCreateDialog.labels.paymentMethod,
462
+ paymentReference: messages.bookingCreateDialog.labels.paymentReference,
463
+ } }), _jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Label, { children: messages.bookingCreateDialog.labels.documentGenerationHeading }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Checkbox, { id: "new-booking-generate-contract-document", checked: generateContractDocument, onCheckedChange: (value) => setGenerateContractDocument(value === true) }), _jsx(Label, { htmlFor: "new-booking-generate-contract-document", className: "cursor-pointer", children: messages.bookingCreateDialog.labels.generateContractDocument })] }), _jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Checkbox, { id: "new-booking-generate-invoice-document", checked: generateInvoiceDocument, onCheckedChange: (value) => setGenerateInvoiceDocument(value === true) }), _jsx(Label, { htmlFor: "new-booking-generate-invoice-document", className: "cursor-pointer", children: messages.bookingCreateDialog.labels.generateInvoiceDocument })] })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingCreateDialog.fields.internalNotes }), _jsx(Textarea, { value: notes, onChange: (e) => setNotes(e.target.value), placeholder: messages.bookingCreateDialog.placeholders.internalNotes })] }), _jsxs("div", { className: "flex items-start gap-2 rounded-md border p-3", children: [_jsx(Checkbox, { id: "new-booking-confirm-after-create", checked: confirmAfterCreate, onCheckedChange: (v) => setConfirmAfterCreate(v === true), className: "mt-0.5" }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { htmlFor: "new-booking-confirm-after-create", className: "cursor-pointer text-sm", children: messages.bookingCreateDialog.fields.confirmAfterCreate }), _jsx("p", { className: "text-xs text-muted-foreground", children: messages.bookingCreateDialog.fields.confirmAfterCreateHint })] })] }), error && _jsx("p", { className: "text-xs text-destructive", children: error })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: onCancel, disabled: isSubmitting, children: messages.common.cancel }), _jsxs(Button, { type: "button", size: "sm", onClick: handleSubmit, disabled: isSubmitting || !product.productId, children: [isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), messages.bookingCreateDialog.actions.createDraftBooking] })] })] }));
397
464
  }
@@ -1,11 +1,12 @@
1
1
  import type { BookingRecord } from "@voyantjs/bookings-react";
2
2
  export interface BookingCreatePageProps {
3
3
  onCreated?: (booking: BookingRecord) => void;
4
+ onCancel?: () => void;
4
5
  /** When provided, pre-selects this product and hides the product picker. */
5
6
  defaultProductId?: string;
6
7
  }
7
8
  /**
8
9
  * Full-page booking create surface for route-based booking creation.
9
10
  */
10
- export declare function BookingCreatePage({ onCreated, defaultProductId }: BookingCreatePageProps): import("react/jsx-runtime").JSX.Element;
11
+ export declare function BookingCreatePage({ onCreated, onCancel, defaultProductId, }: BookingCreatePageProps): import("react/jsx-runtime").JSX.Element;
11
12
  //# sourceMappingURL=booking-create-page.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"booking-create-page.d.ts","sourceRoot":"","sources":["../../src/components/booking-create-page.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAI7D,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE,EAAE,sBAAsB,2CAgBxF"}
1
+ {"version":3,"file":"booking-create-page.d.ts","sourceRoot":"","sources":["../../src/components/booking-create-page.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAI7D,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,QAAQ,EACR,gBAAgB,GACjB,EAAE,sBAAsB,2CAoBxB"}
@@ -5,7 +5,7 @@ import { BookingCreateForm } from "./booking-create-dialog.js";
5
5
  /**
6
6
  * Full-page booking create surface for route-based booking creation.
7
7
  */
8
- export function BookingCreatePage({ onCreated, defaultProductId }) {
8
+ export function BookingCreatePage({ onCreated, onCancel, defaultProductId, }) {
9
9
  const messages = useBookingsUiMessagesOrDefault();
10
- return (_jsxs("main", { className: "mx-auto flex w-full max-w-4xl flex-col gap-6 px-4 py-6 sm:px-6 lg:px-8", children: [_jsxs("header", { className: "flex flex-col gap-1", children: [_jsx("h1", { className: "text-2xl font-semibold tracking-normal", children: messages.bookingCreatePage.title }), _jsx("p", { className: "text-sm text-muted-foreground", children: messages.bookingCreatePage.description })] }), _jsx("section", { className: "flex flex-col gap-4", children: _jsx(BookingCreateForm, { onCreated: onCreated, defaultProductId: defaultProductId }) })] }));
10
+ return (_jsxs("main", { className: "mx-auto flex w-full max-w-4xl flex-col gap-6 px-4 py-6 sm:px-6 lg:px-8", children: [_jsxs("header", { className: "flex flex-col gap-1", children: [_jsx("h1", { className: "text-2xl font-semibold tracking-normal", children: messages.bookingCreatePage.title }), _jsx("p", { className: "text-sm text-muted-foreground", children: messages.bookingCreatePage.description })] }), _jsx("section", { className: "flex flex-col gap-4", children: _jsx(BookingCreateForm, { onCreated: onCreated, onCancel: onCancel, defaultProductId: defaultProductId }) })] }));
11
11
  }
@@ -0,0 +1,33 @@
1
+ import type { BookingCreateItemLineInput } from "@voyantjs/bookings-react";
2
+ import type { ProductRecord } from "@voyantjs/products-react";
3
+ export type ProductPickerSearchRecord = Pick<ProductRecord, "description" | "name" | "sellCurrency">;
4
+ export interface DepartureSlotSearchRecord {
5
+ id: string;
6
+ optionId: string | null;
7
+ startsAt: string;
8
+ status?: string;
9
+ }
10
+ export interface BookingCreateUnitLineRecord {
11
+ optionUnitId: string;
12
+ unitName: string;
13
+ }
14
+ export interface BookingCreatePricingLineRecord {
15
+ unitId: string;
16
+ label: string;
17
+ unitAmountCents: number | null;
18
+ totalAmountCents: number | null;
19
+ }
20
+ export interface BookingCreatePricingRecord {
21
+ confirmedAmountCents: number | null;
22
+ lines: BookingCreatePricingLineRecord[];
23
+ }
24
+ export declare function normalizeBookingSearchText(value: string): string;
25
+ export declare function matchesBookingSearchText(value: string | null | undefined, query: string): boolean;
26
+ export declare function productMatchesPickerSearch(product: ProductPickerSearchRecord | null | undefined, query: string): boolean;
27
+ export declare function getBookableDepartureSlots<TSlot extends DepartureSlotSearchRecord>(slots: readonly TSlot[], options: {
28
+ nowIso: string;
29
+ optionId: string | null;
30
+ }): TSlot[];
31
+ export declare function itemLinesToRows(quantities: Record<string, number>, units: BookingCreateUnitLineRecord[], pricing: BookingCreatePricingRecord | null): BookingCreateItemLineInput[];
32
+ export declare function getSelectedSharedRoomUnitId(quantities: Record<string, number>): string | null;
33
+ //# sourceMappingURL=booking-create-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"booking-create-utils.d.ts","sourceRoot":"","sources":["../../src/components/booking-create-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAA;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAE7D,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,aAAa,EAAE,aAAa,GAAG,MAAM,GAAG,cAAc,CAAC,CAAA;AAEpG,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,2BAA2B;IAC1C,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,8BAA8B;IAC7C,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC;AAED,MAAM,WAAW,0BAA0B;IACzC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,KAAK,EAAE,8BAA8B,EAAE,CAAA;CACxC;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAIjG;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACrD,KAAK,EAAE,MAAM,GACZ,OAAO,CAKT;AAED,wBAAgB,yBAAyB,CAAC,KAAK,SAAS,yBAAyB,EAC/E,KAAK,EAAE,SAAS,KAAK,EAAE,EACvB,OAAO,EAAE;IACP,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,GACA,KAAK,EAAE,CAST;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,KAAK,EAAE,2BAA2B,EAAE,EACpC,OAAO,EAAE,0BAA0B,GAAG,IAAI,GACzC,0BAA0B,EAAE,CAuC9B;AAED,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAE7F"}
@@ -0,0 +1,67 @@
1
+ export function normalizeBookingSearchText(value) {
2
+ return value
3
+ .normalize("NFKD")
4
+ .replace(/[\u0300-\u036f]/g, "")
5
+ .trim()
6
+ .toLowerCase();
7
+ }
8
+ export function matchesBookingSearchText(value, query) {
9
+ const normalizedQuery = normalizeBookingSearchText(query);
10
+ if (!normalizedQuery)
11
+ return true;
12
+ return normalizeBookingSearchText(value ?? "").includes(normalizedQuery);
13
+ }
14
+ export function productMatchesPickerSearch(product, query) {
15
+ if (!product)
16
+ return false;
17
+ return [product.name, product.description, product.sellCurrency].some((value) => matchesBookingSearchText(value, query));
18
+ }
19
+ export function getBookableDepartureSlots(slots, options) {
20
+ return slots
21
+ .filter((slot) => !slot.status || slot.status === "open")
22
+ .filter((slot) => slot.startsAt >= options.nowIso)
23
+ .filter((slot) => {
24
+ if (!options.optionId)
25
+ return true;
26
+ return slot.optionId === null || slot.optionId === options.optionId;
27
+ })
28
+ .sort((left, right) => left.startsAt.localeCompare(right.startsAt));
29
+ }
30
+ export function itemLinesToRows(quantities, units, pricing) {
31
+ const unitNames = new Map(units.map((unit) => [unit.optionUnitId, unit.unitName]));
32
+ const pricedLines = new Map((pricing?.lines ?? []).map((line) => [line.unitId, line]));
33
+ const selectedLines = Object.entries(quantities).filter(([, quantity]) => quantity > 0);
34
+ const pricedTotal = selectedLines.reduce((sum, [optionUnitId]) => {
35
+ const total = pricedLines.get(optionUnitId)?.totalAmountCents;
36
+ return total == null ? sum : sum + total;
37
+ }, 0);
38
+ const unpricedLines = selectedLines.filter(([optionUnitId]) => pricedLines.get(optionUnitId)?.totalAmountCents == null);
39
+ const unpricedQuantity = unpricedLines.reduce((sum, [, quantity]) => sum + quantity, 0);
40
+ const manualRemainder = pricing?.confirmedAmountCents != null && unpricedLines.length > 0
41
+ ? Math.max(0, pricing.confirmedAmountCents - pricedTotal)
42
+ : null;
43
+ let allocatedManualTotal = 0;
44
+ return selectedLines.map(([optionUnitId, quantity]) => {
45
+ const pricedLine = pricedLines.get(optionUnitId);
46
+ let totalSellAmountCents = pricedLine?.totalAmountCents ?? null;
47
+ if (totalSellAmountCents == null && manualRemainder != null && unpricedQuantity > 0) {
48
+ const isLastUnpriced = unpricedLines[unpricedLines.length - 1]?.[0] === optionUnitId;
49
+ totalSellAmountCents = isLastUnpriced
50
+ ? manualRemainder - allocatedManualTotal
51
+ : Math.floor((manualRemainder * quantity) / unpricedQuantity);
52
+ allocatedManualTotal += totalSellAmountCents;
53
+ }
54
+ const unitSellAmountCents = pricedLine?.unitAmountCents ??
55
+ (totalSellAmountCents != null ? Math.floor(totalSellAmountCents / quantity) : null);
56
+ return {
57
+ optionUnitId,
58
+ quantity,
59
+ title: pricedLine?.label ?? unitNames.get(optionUnitId) ?? null,
60
+ unitSellAmountCents,
61
+ totalSellAmountCents,
62
+ };
63
+ });
64
+ }
65
+ export function getSelectedSharedRoomUnitId(quantities) {
66
+ return Object.entries(quantities).find(([, quantity]) => quantity > 0)?.[0] ?? null;
67
+ }
@@ -2,6 +2,7 @@ import { type BookingRecord } from "@voyantjs/bookings-react";
2
2
  export interface BookingListProps {
3
3
  pageSize?: number;
4
4
  onSelectBooking?: (booking: BookingRecord) => void;
5
+ onCreateBooking?: () => void;
5
6
  }
6
- export declare function BookingList({ pageSize, onSelectBooking }?: BookingListProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function BookingList({ pageSize, onSelectBooking, onCreateBooking, }?: BookingListProps): import("react/jsx-runtime").JSX.Element;
7
8
  //# sourceMappingURL=booking-list.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"booking-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-list.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,EAKnB,MAAM,0BAA0B,CAAA;AAyBjC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;CACnD;AAgBD,wBAAgB,WAAW,CAAC,EAAE,QAAa,EAAE,eAAe,EAAE,GAAE,gBAAqB,2CAmVpF"}
1
+ {"version":3,"file":"booking-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-list.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,EAKnB,MAAM,0BAA0B,CAAA;AAyBjC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAClD,eAAe,CAAC,EAAE,MAAM,IAAI,CAAA;CAC7B;AAgBD,wBAAgB,WAAW,CAAC,EAC1B,QAAa,EACb,eAAe,EACf,eAAe,GAChB,GAAE,gBAAqB,2CAuVvB"}
@@ -22,7 +22,7 @@ const SORTABLE_COLUMNS = {
22
22
  };
23
23
  const SKELETON_ROW_COUNT = 6;
24
24
  const TABLE_COLUMN_COUNT = 7;
25
- export function BookingList({ pageSize = 25, onSelectBooking } = {}) {
25
+ export function BookingList({ pageSize = 25, onSelectBooking, onCreateBooking, } = {}) {
26
26
  const [search, setSearch] = React.useState("");
27
27
  const [status, setStatus] = React.useState(BOOKING_STATUS_ALL);
28
28
  const [productId, setProductId] = React.useState(null);
@@ -121,6 +121,10 @@ export function BookingList({ pageSize = 25, onSelectBooking } = {}) {
121
121
  setSearch(event.target.value);
122
122
  resetOffset();
123
123
  }, className: "pl-9" })] }), _jsx(BookingListFiltersPopover, { open: filterPopoverOpen, onOpenChange: setFilterPopoverOpen, activeFilterCount: activeFilterCount, status: status, onStatusChange: setStatus, productId: productId, onProductIdChange: setProductId, optionId: optionId, onOptionIdChange: setOptionId, supplierId: supplierId, onSupplierIdChange: setSupplierId, productCategoryId: productCategoryId, onProductCategoryIdChange: setProductCategoryId, personId: personId, onPersonIdChange: setPersonId, organizationId: organizationId, onOrganizationIdChange: setOrganizationId, dateRange: dateRange, onDateRangeChange: setDateRange, paxMin: paxMin, onPaxMinChange: setPaxMin, paxMax: paxMax, onPaxMaxChange: setPaxMax, onFiltersChanged: resetOffset }), hasActiveFilters && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: clearFilters, children: [_jsx(X, { className: "mr-1 size-4" }), filterMessages.clear] })), _jsx("div", { className: "ml-auto", children: _jsxs(Button, { onClick: () => {
124
+ if (onCreateBooking) {
125
+ onCreateBooking();
126
+ return;
127
+ }
124
128
  setEditing(undefined);
125
129
  setDialogOpen(true);
126
130
  }, children: [_jsx(Plus, { className: "mr-2 size-4" }), messages.bookingList.newBooking] }) })] }), _jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: _jsx(SortHeader, { label: columnMessages.bookingNumber, field: SORTABLE_COLUMNS.bookingNumber, sortBy: sortBy, sortDir: sortDir, onSort: handleSort }) }), _jsx(TableHead, { children: columnMessages.whatBooked }), _jsx(TableHead, { children: _jsx(SortHeader, { label: columnMessages.status, field: SORTABLE_COLUMNS.status, sortBy: sortBy, sortDir: sortDir, onSort: handleSort }) }), _jsx(TableHead, { children: _jsx(SortHeader, { label: columnMessages.sellAmount, field: SORTABLE_COLUMNS.sellAmount, sortBy: sortBy, sortDir: sortDir, onSort: handleSort }) }), _jsx(TableHead, { children: _jsx(SortHeader, { label: columnMessages.pax, field: SORTABLE_COLUMNS.pax, sortBy: sortBy, sortDir: sortDir, onSort: handleSort }) }), _jsx(TableHead, { children: _jsx(SortHeader, { label: columnMessages.startDate, field: SORTABLE_COLUMNS.startDate, sortBy: sortBy, sortDir: sortDir, onSort: handleSort }) }), _jsx(TableHead, { children: _jsx(SortHeader, { label: columnMessages.endDate, field: SORTABLE_COLUMNS.endDate, sortBy: sortBy, sortDir: sortDir, onSort: handleSort }) })] }) }), _jsx(TableBody, { children: showSkeleton ? (_jsx(BookingTableSkeleton, { rows: SKELETON_ROW_COUNT })) : isError ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: TABLE_COLUMN_COUNT, className: "h-24 text-center text-sm text-destructive", children: messages.bookingList.loadingError }) })) : bookings.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: TABLE_COLUMN_COUNT, className: "h-24 text-center text-sm text-muted-foreground", children: messages.bookingList.empty }) })) : (bookings.map((booking) => (_jsxs(TableRow, { onClick: () => handleSelect(booking), className: "cursor-pointer", children: [_jsx(TableCell, { className: "font-medium", children: booking.bookingNumber }), _jsx(TableCell, { children: formatBookingItems(booking, messages.bookingList.itemsMore) }), _jsx(TableCell, { children: _jsx(Badge, { variant: bookingStatusBadgeVariant[booking.status], children: statusLabels[booking.status] }) }), _jsx(TableCell, { children: booking.sellAmountCents == null
@@ -2,7 +2,8 @@ import type { BookingRecord } from "@voyantjs/bookings-react";
2
2
  export interface BookingsPageProps {
3
3
  pageSize?: number;
4
4
  onBookingOpen?: (booking: BookingRecord) => void;
5
+ onCreateBooking?: () => void;
5
6
  className?: string;
6
7
  }
7
- export declare function BookingsPage({ pageSize, onBookingOpen, className }?: BookingsPageProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function BookingsPage({ pageSize, onBookingOpen, onCreateBooking, className, }?: BookingsPageProps): import("react/jsx-runtime").JSX.Element;
8
9
  //# sourceMappingURL=bookings-page.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bookings-page.d.ts","sourceRoot":"","sources":["../../src/components/bookings-page.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAK7D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAChD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,GAAE,iBAAsB,2CAa1F"}
1
+ {"version":3,"file":"bookings-page.d.ts","sourceRoot":"","sources":["../../src/components/bookings-page.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAK7D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAChD,eAAe,CAAC,EAAE,MAAM,IAAI,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,aAAa,EACb,eAAe,EACf,SAAS,GACV,GAAE,iBAAsB,2CAiBxB"}
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { cn } from "@voyantjs/ui/lib/utils";
4
4
  import { useBookingsUiMessagesOrDefault } from "../i18n/index.js";
5
5
  import { BookingList } from "./booking-list.js";
6
- export function BookingsPage({ pageSize, onBookingOpen, className } = {}) {
6
+ export function BookingsPage({ pageSize, onBookingOpen, onCreateBooking, className, } = {}) {
7
7
  const messages = useBookingsUiMessagesOrDefault().bookingsPage;
8
- return (_jsxs("div", { "data-slot": "bookings-page", className: cn("flex flex-col gap-6 p-6", className), children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: messages.title }), _jsx("p", { className: "text-sm text-muted-foreground", children: messages.description })] }), _jsx(BookingList, { pageSize: pageSize, onSelectBooking: onBookingOpen })] }));
8
+ return (_jsxs("div", { "data-slot": "bookings-page", className: cn("flex flex-col gap-6 p-6", className), children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: messages.title }), _jsx("p", { className: "text-sm text-muted-foreground", children: messages.description })] }), _jsx(BookingList, { pageSize: pageSize, onSelectBooking: onBookingOpen, onCreateBooking: onCreateBooking })] }));
9
9
  }
@@ -11,6 +11,22 @@ export interface PaymentScheduleValue {
11
11
  splitFirstDueDate: string | null;
12
12
  splitSecondAmountCents: number | null;
13
13
  splitSecondDueDate: string | null;
14
+ fullAlreadyPaid: boolean;
15
+ fullPaymentDate: string | null;
16
+ fullPaymentMethod: string;
17
+ fullPaymentReference: string;
18
+ advanceAlreadyPaid: boolean;
19
+ advancePaymentDate: string | null;
20
+ advancePaymentMethod: string;
21
+ advancePaymentReference: string;
22
+ splitFirstAlreadyPaid: boolean;
23
+ splitFirstPaymentDate: string | null;
24
+ splitFirstPaymentMethod: string;
25
+ splitFirstPaymentReference: string;
26
+ splitSecondAlreadyPaid: boolean;
27
+ splitSecondPaymentDate: string | null;
28
+ splitSecondPaymentMethod: string;
29
+ splitSecondPaymentReference: string;
14
30
  }
15
31
  export declare const emptyPaymentScheduleValue: PaymentScheduleValue;
16
32
  export interface PaymentScheduleSectionProps {
@@ -36,6 +52,13 @@ export interface PaymentScheduleSectionProps {
36
52
  secondInstallment?: string;
37
53
  preset5050?: string;
38
54
  unpaidHint?: string;
55
+ totalDue?: string;
56
+ scheduledTotal?: string;
57
+ remaining?: string;
58
+ alreadyPaid?: string;
59
+ paymentDate?: string;
60
+ paymentMethod?: string;
61
+ paymentReference?: string;
39
62
  };
40
63
  }
41
64
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"payment-schedule-section.d.ts","sourceRoot":"","sources":["../../src/components/payment-schedule-section.tsx"],"names":[],"mappings":"AAOA,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAA;AAEzE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,mBAAmB,CAAA;IACzB,wEAAwE;IACxE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,wEAAwE;IACxE,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,qDAAqD;IACrD,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC;AAED,eAAO,MAAM,yBAAyB,EAAE,oBASvC,CAAA;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,oBAAoB,CAAA;IAC3B,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAC/C;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,gBAAgB,CAAC,EAAE,MAAM,CAAA;QACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;QAC1B,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;CACF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,QAAQ,EACR,MAAM,GACP,EAAE,2BAA2B,2CAmH7B"}
1
+ {"version":3,"file":"payment-schedule-section.d.ts","sourceRoot":"","sources":["../../src/components/payment-schedule-section.tsx"],"names":[],"mappings":"AAiBA,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAA;AAEzE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,mBAAmB,CAAA;IACzB,wEAAwE;IACxE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,wEAAwE;IACxE,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,qDAAqD;IACrD,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,eAAe,EAAE,OAAO,CAAA;IACxB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,iBAAiB,EAAE,MAAM,CAAA;IACzB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,kBAAkB,EAAE,OAAO,CAAA;IAC3B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,oBAAoB,EAAE,MAAM,CAAA;IAC5B,uBAAuB,EAAE,MAAM,CAAA;IAC/B,qBAAqB,EAAE,OAAO,CAAA;IAC9B,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,uBAAuB,EAAE,MAAM,CAAA;IAC/B,0BAA0B,EAAE,MAAM,CAAA;IAClC,sBAAsB,EAAE,OAAO,CAAA;IAC/B,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,wBAAwB,EAAE,MAAM,CAAA;IAChC,2BAA2B,EAAE,MAAM,CAAA;CACpC;AAED,eAAO,MAAM,yBAAyB,EAAE,oBAyBvC,CAAA;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,oBAAoB,CAAA;IAC3B,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAC/C;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,gBAAgB,CAAC,EAAE,MAAM,CAAA;QACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;QAC1B,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAC1B,CAAA;CACF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,QAAQ,EACR,MAAM,GACP,EAAE,2BAA2B,2CAkO7B"}