@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.
- package/dist/components/booking-create-dialog.d.ts +1 -1
- package/dist/components/booking-create-dialog.d.ts.map +1 -1
- package/dist/components/booking-create-dialog.js +94 -27
- package/dist/components/booking-create-page.d.ts +2 -1
- package/dist/components/booking-create-page.d.ts.map +1 -1
- package/dist/components/booking-create-page.js +2 -2
- package/dist/components/booking-create-utils.d.ts +33 -0
- package/dist/components/booking-create-utils.d.ts.map +1 -0
- package/dist/components/booking-create-utils.js +67 -0
- package/dist/components/booking-list.d.ts +2 -1
- package/dist/components/booking-list.d.ts.map +1 -1
- package/dist/components/booking-list.js +5 -1
- package/dist/components/bookings-page.d.ts +2 -1
- package/dist/components/bookings-page.d.ts.map +1 -1
- package/dist/components/bookings-page.js +2 -2
- package/dist/components/payment-schedule-section.d.ts +23 -0
- package/dist/components/payment-schedule-section.d.ts.map +1 -1
- package/dist/components/payment-schedule-section.js +48 -3
- package/dist/components/price-breakdown-section.d.ts +1 -0
- package/dist/components/price-breakdown-section.d.ts.map +1 -1
- package/dist/components/price-breakdown-section.js +2 -0
- package/dist/components/product-picker-section.d.ts.map +1 -1
- package/dist/components/product-picker-section.js +8 -4
- package/dist/components/rooms-stepper-section.d.ts +12 -7
- package/dist/components/rooms-stepper-section.d.ts.map +1 -1
- package/dist/components/rooms-stepper-section.js +24 -8
- package/dist/components/shared-room-section.d.ts +2 -0
- package/dist/components/shared-room-section.d.ts.map +1 -1
- package/dist/components/shared-room-section.js +14 -2
- package/dist/components/travelers-section.d.ts +15 -10
- package/dist/components/travelers-section.d.ts.map +1 -1
- package/dist/components/travelers-section.js +96 -14
- package/dist/i18n/en.d.ts +32 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +32 -0
- package/dist/i18n/messages.d.ts +32 -0
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/i18n/provider.d.ts +64 -0
- package/dist/i18n/provider.d.ts.map +1 -1
- package/dist/i18n/ro.d.ts +32 -0
- package/dist/i18n/ro.d.ts.map +1 -1
- package/dist/i18n/ro.js +32 -0
- 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/
|
|
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,
|
|
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 {
|
|
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/
|
|
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
|
|
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
|
-
|
|
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
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
|
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:
|
|
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
|
|
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 =
|
|
333
|
-
return (_jsxs(_Fragment, { children: [_jsxs(
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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;
|
|
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,
|
|
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":"
|
|
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"}
|