@voyantjs/bookings-ui 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -109
- package/README.md +11 -0
- package/dist/components/booking-activity-timeline.d.ts.map +1 -1
- package/dist/components/booking-activity-timeline.js +34 -14
- package/dist/components/booking-cancellation-dialog.d.ts.map +1 -1
- package/dist/components/booking-cancellation-dialog.js +15 -16
- package/dist/components/booking-create-dialog.d.ts.map +1 -1
- package/dist/components/booking-create-dialog.js +77 -13
- package/dist/components/booking-dialog.d.ts.map +1 -1
- package/dist/components/booking-dialog.js +27 -21
- package/dist/components/booking-document-dialog.d.ts.map +1 -1
- package/dist/components/booking-document-dialog.js +27 -13
- package/dist/components/booking-document-list.d.ts.map +1 -1
- package/dist/components/booking-document-list.js +9 -4
- package/dist/components/booking-group-link-dialog.d.ts.map +1 -1
- package/dist/components/booking-group-link-dialog.js +17 -6
- package/dist/components/booking-group-section.d.ts.map +1 -1
- package/dist/components/booking-group-section.js +8 -2
- package/dist/components/booking-guarantee-dialog.d.ts.map +1 -1
- package/dist/components/booking-guarantee-dialog.js +30 -14
- package/dist/components/booking-guarantee-list.d.ts.map +1 -1
- package/dist/components/booking-guarantee-list.js +11 -8
- package/dist/components/booking-item-dialog.d.ts.map +1 -1
- package/dist/components/booking-item-dialog.js +34 -20
- package/dist/components/booking-item-list.d.ts.map +1 -1
- package/dist/components/booking-item-list.js +10 -9
- package/dist/components/booking-item-travelers.d.ts.map +1 -1
- package/dist/components/booking-item-travelers.js +9 -4
- package/dist/components/booking-list.d.ts.map +1 -1
- package/dist/components/booking-list.js +17 -8
- package/dist/components/booking-notes.d.ts.map +1 -1
- package/dist/components/booking-notes.js +5 -2
- package/dist/components/booking-payment-schedule-dialog.d.ts.map +1 -1
- package/dist/components/booking-payment-schedule-dialog.js +31 -12
- package/dist/components/booking-payment-schedule-list.d.ts.map +1 -1
- package/dist/components/booking-payment-schedule-list.js +7 -6
- package/dist/components/booking-payments-summary.d.ts.map +1 -1
- package/dist/components/booking-payments-summary.js +7 -4
- package/dist/components/file-dropzone.d.ts.map +1 -1
- package/dist/components/file-dropzone.js +25 -15
- package/dist/components/passengers-section.d.ts.map +1 -1
- package/dist/components/passengers-section.js +3 -17
- package/dist/components/payment-schedule-section.d.ts.map +1 -1
- package/dist/components/payment-schedule-section.js +3 -14
- package/dist/components/person-picker-section.d.ts.map +1 -1
- package/dist/components/person-picker-section.js +3 -19
- package/dist/components/price-breakdown-section.d.ts.map +1 -1
- package/dist/components/price-breakdown-section.js +15 -18
- package/dist/components/product-picker-section.d.ts.map +1 -1
- package/dist/components/product-picker-section.js +3 -8
- package/dist/components/rooms-stepper-section.d.ts.map +1 -1
- package/dist/components/rooms-stepper-section.js +4 -9
- package/dist/components/shared-room-section.d.ts.map +1 -1
- package/dist/components/shared-room-section.js +3 -9
- package/dist/components/status-change-dialog.d.ts.map +1 -1
- package/dist/components/status-change-dialog.js +6 -1
- package/dist/components/supplier-status-dialog.d.ts.map +1 -1
- package/dist/components/supplier-status-dialog.js +31 -15
- package/dist/components/supplier-status-list.d.ts.map +1 -1
- package/dist/components/supplier-status-list.js +10 -2
- package/dist/components/traveler-dialog.d.ts.map +1 -1
- package/dist/components/traveler-dialog.js +17 -8
- package/dist/components/traveler-list.d.ts.map +1 -1
- package/dist/components/traveler-list.js +5 -3
- package/dist/components/voucher-picker-section.d.ts.map +1 -1
- package/dist/components/voucher-picker-section.js +11 -26
- package/dist/i18n/en.d.ts +797 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +796 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +3 -0
- package/dist/i18n/messages.d.ts +684 -0
- package/dist/i18n/messages.d.ts.map +1 -0
- package/dist/i18n/messages.js +1 -0
- package/dist/i18n/provider.d.ts +1617 -0
- package/dist/i18n/provider.d.ts.map +1 -0
- package/dist/i18n/provider.js +45 -0
- package/dist/i18n/ro.d.ts +797 -0
- package/dist/i18n/ro.d.ts.map +1 -0
- package/dist/i18n/ro.js +796 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +40 -20
|
@@ -6,6 +6,7 @@ import { usePersonMutation } from "@voyantjs/crm-react";
|
|
|
6
6
|
import { Button, Checkbox, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea, } from "@voyantjs/ui/components";
|
|
7
7
|
import { Loader2 } from "lucide-react";
|
|
8
8
|
import * as React from "react";
|
|
9
|
+
import { formatMessage, useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault, } from "../i18n/provider";
|
|
9
10
|
import { emptyPassengerListValue, PassengersSection, } from "./passengers-section";
|
|
10
11
|
import { emptyPaymentScheduleValue, PaymentScheduleSection, } from "./payment-schedule-section";
|
|
11
12
|
import { emptyPersonPickerValue, PersonPickerSection, } from "./person-picker-section";
|
|
@@ -132,6 +133,8 @@ export function BookingCreateDialog({ open, onOpenChange, onCreated, defaultProd
|
|
|
132
133
|
*/
|
|
133
134
|
const [confirmAfterCreate, setConfirmAfterCreate] = React.useState(false);
|
|
134
135
|
const [error, setError] = React.useState(null);
|
|
136
|
+
const { formatDate } = useBookingsUiI18nOrDefault();
|
|
137
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
135
138
|
React.useEffect(() => {
|
|
136
139
|
if (!open) {
|
|
137
140
|
setProduct({ productId: defaultProductId ?? "", optionId: null });
|
|
@@ -175,14 +178,16 @@ export function BookingCreateDialog({ open, onOpenChange, onCreated, defaultProd
|
|
|
175
178
|
.sort((a, b) => a.startsAt.localeCompare(b.startsAt));
|
|
176
179
|
}, [slotsData, product.optionId]);
|
|
177
180
|
const formatSlotLabel = React.useCallback((slot) => {
|
|
178
|
-
const date =
|
|
181
|
+
const date = formatDate(slot.startsAt, {
|
|
179
182
|
year: "numeric",
|
|
180
183
|
month: "short",
|
|
181
184
|
day: "numeric",
|
|
182
185
|
});
|
|
183
|
-
const remaining = !slot.unlimited && typeof slot.remainingPax === "number"
|
|
186
|
+
const remaining = !slot.unlimited && typeof slot.remainingPax === "number"
|
|
187
|
+
? ` · ${slot.remainingPax} ${messages.bookingCreateDialog.labels.remainingCapacity}`
|
|
188
|
+
: "";
|
|
184
189
|
return `${date}${remaining}`;
|
|
185
|
-
}, []);
|
|
190
|
+
}, [formatDate, messages]);
|
|
186
191
|
const slotUnitAvailability = useSlotUnitAvailability({
|
|
187
192
|
slotId: slotId ?? undefined,
|
|
188
193
|
enabled: open && Boolean(slotId),
|
|
@@ -208,28 +213,28 @@ export function BookingCreateDialog({ open, onOpenChange, onCreated, defaultProd
|
|
|
208
213
|
// Currency placeholder — used for voucher + payment schedule display.
|
|
209
214
|
// Consumers hooking in real product data should override this by wrapping
|
|
210
215
|
// the component or swapping in their own currency-aware hook.
|
|
211
|
-
const currency =
|
|
216
|
+
const currency = messages.bookingCreateDialog.labels.currency;
|
|
212
217
|
const { create: createPerson } = usePersonMutation();
|
|
213
218
|
const quickCreateMutation = useBookingQuickCreateMutation();
|
|
214
219
|
const statusMutation = useBookingStatusByIdMutation();
|
|
215
220
|
const handleSubmit = async () => {
|
|
216
221
|
setError(null);
|
|
217
222
|
if (!product.productId) {
|
|
218
|
-
setError(
|
|
223
|
+
setError(messages.bookingCreateDialog.validation.selectProduct);
|
|
219
224
|
return;
|
|
220
225
|
}
|
|
221
226
|
let resolvedPersonId = null;
|
|
222
227
|
try {
|
|
223
228
|
if (person.mode === "existing") {
|
|
224
229
|
if (!person.personId) {
|
|
225
|
-
setError(
|
|
230
|
+
setError(messages.bookingCreateDialog.validation.selectPerson);
|
|
226
231
|
return;
|
|
227
232
|
}
|
|
228
233
|
resolvedPersonId = person.personId;
|
|
229
234
|
}
|
|
230
235
|
else {
|
|
231
236
|
if (!person.newPerson.firstName.trim() || !person.newPerson.lastName.trim()) {
|
|
232
|
-
setError(
|
|
237
|
+
setError(messages.bookingCreateDialog.validation.firstAndLastNameRequired);
|
|
233
238
|
return;
|
|
234
239
|
}
|
|
235
240
|
const created = await createPerson.mutateAsync({
|
|
@@ -241,7 +246,7 @@ export function BookingCreateDialog({ open, onOpenChange, onCreated, defaultProd
|
|
|
241
246
|
resolvedPersonId = created.id;
|
|
242
247
|
}
|
|
243
248
|
if (sharedRoom.enabled && sharedRoom.mode === "join" && !sharedRoom.groupId) {
|
|
244
|
-
setError(
|
|
249
|
+
setError(messages.bookingCreateDialog.validation.selectSharedRoomGroup);
|
|
245
250
|
return;
|
|
246
251
|
}
|
|
247
252
|
const bookingNumber = generateBookingNumber();
|
|
@@ -258,7 +263,7 @@ export function BookingCreateDialog({ open, onOpenChange, onCreated, defaultProd
|
|
|
258
263
|
? {
|
|
259
264
|
action: "create",
|
|
260
265
|
kind: "shared_room",
|
|
261
|
-
label:
|
|
266
|
+
label: `${messages.bookingCreateDialog.labels.sharedRoomGeneratedLabelPrefix} - ${bookingNumber}`,
|
|
262
267
|
optionUnitId: product.optionId,
|
|
263
268
|
makeBookingPrimary: true,
|
|
264
269
|
}
|
|
@@ -295,8 +300,10 @@ export function BookingCreateDialog({ open, onOpenChange, onCreated, defaultProd
|
|
|
295
300
|
}
|
|
296
301
|
catch (statusErr) {
|
|
297
302
|
setError(statusErr instanceof Error
|
|
298
|
-
?
|
|
299
|
-
|
|
303
|
+
? formatMessage(messages.bookingCreateDialog.validation.confirmFailedPrefix, {
|
|
304
|
+
message: statusErr.message,
|
|
305
|
+
})
|
|
306
|
+
: messages.bookingCreateDialog.validation.confirmFailed);
|
|
300
307
|
onCreated?.(booking);
|
|
301
308
|
return;
|
|
302
309
|
}
|
|
@@ -305,9 +312,66 @@ export function BookingCreateDialog({ open, onOpenChange, onCreated, defaultProd
|
|
|
305
312
|
onCreated?.(finalBooking);
|
|
306
313
|
}
|
|
307
314
|
catch (err) {
|
|
308
|
-
setError(err instanceof Error ? err.message :
|
|
315
|
+
setError(err instanceof Error ? err.message : messages.bookingCreateDialog.validation.createFailed);
|
|
309
316
|
}
|
|
310
317
|
};
|
|
311
318
|
const isSubmitting = quickCreateMutation.isPending || createPerson.isPending || statusMutation.isPending;
|
|
312
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children:
|
|
319
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: messages.bookingCreateDialog.title }) }), _jsxs(DialogBody, { className: "grid gap-4", children: [_jsx(ProductPickerSection, { value: product, onChange: setProduct, enabled: open, lockProduct: Boolean(defaultProductId), labels: {
|
|
320
|
+
optionNone: messages.bookingCreateDialog.labels.noSpecificOption,
|
|
321
|
+
} }), 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: open, labels: {
|
|
322
|
+
heading: messages.bookingCreateDialog.labels.roomsHeading,
|
|
323
|
+
noSlot: messages.bookingCreateDialog.labels.roomsNoSlot,
|
|
324
|
+
noUnits: messages.bookingCreateDialog.labels.roomsNoUnits,
|
|
325
|
+
remaining: messages.bookingCreateDialog.labels.roomsRemaining,
|
|
326
|
+
unlimited: messages.bookingCreateDialog.labels.roomsUnlimited,
|
|
327
|
+
} })) : null, _jsx(PersonPickerSection, { value: person, onChange: setPerson, enabled: open, labels: {
|
|
328
|
+
createNewPerson: messages.bookingCreateDialog.labels.createNewPerson,
|
|
329
|
+
selectExistingPerson: messages.bookingCreateDialog.labels.selectExistingPerson,
|
|
330
|
+
organizationNone: messages.bookingCreateDialog.labels.organizationNone,
|
|
331
|
+
} }), _jsx(SharedRoomSection, { value: sharedRoom, onChange: setSharedRoom, productId: product.productId || undefined, enabled: open, labels: {
|
|
332
|
+
toggle: messages.bookingCreateDialog.labels.sharedRoomToggle,
|
|
333
|
+
createMode: messages.bookingCreateDialog.labels.sharedRoomCreateMode,
|
|
334
|
+
joinMode: messages.bookingCreateDialog.labels.sharedRoomJoinMode,
|
|
335
|
+
selectPlaceholder: messages.bookingCreateDialog.labels.sharedRoomSelectPlaceholder,
|
|
336
|
+
noGroups: messages.bookingCreateDialog.labels.sharedRoomNoGroups,
|
|
337
|
+
createHint: messages.bookingCreateDialog.labels.sharedRoomCreateHint,
|
|
338
|
+
} }), product.productId ? (_jsx(PassengersSection, { value: passengers, onChange: setPassengers, roomUnits: roomUnitOptions.length > 0 ? roomUnitOptions : undefined, labels: {
|
|
339
|
+
heading: messages.bookingCreateDialog.labels.passengerHeading,
|
|
340
|
+
addPassenger: messages.bookingCreateDialog.labels.addPassenger,
|
|
341
|
+
role: messages.bookingCreateDialog.labels.passengerRole,
|
|
342
|
+
roleLead: messages.bookingCreateDialog.labels.passengerLead,
|
|
343
|
+
roleAdult: messages.bookingCreateDialog.labels.passengerAdult,
|
|
344
|
+
roleChild: messages.bookingCreateDialog.labels.passengerChild,
|
|
345
|
+
roleInfant: messages.bookingCreateDialog.labels.passengerInfant,
|
|
346
|
+
room: messages.bookingCreateDialog.labels.passengerRoom,
|
|
347
|
+
noRoom: messages.bookingCreateDialog.labels.passengerNoRoom,
|
|
348
|
+
remove: messages.bookingCreateDialog.labels.passengerRemove,
|
|
349
|
+
empty: messages.bookingCreateDialog.labels.passengerEmpty,
|
|
350
|
+
} })) : null, product.productId ? (_jsx(PriceBreakdownSection, { productId: product.productId, optionId: product.optionId, unitQuantities: rooms.quantities, labels: {
|
|
351
|
+
heading: messages.bookingCreateDialog.labels.breakdownHeading,
|
|
352
|
+
total: messages.bookingCreateDialog.labels.breakdownTotal,
|
|
353
|
+
onRequest: messages.bookingCreateDialog.labels.breakdownOnRequest,
|
|
354
|
+
groupRate: messages.bookingCreateDialog.labels.breakdownGroupRate,
|
|
355
|
+
empty: messages.bookingCreateDialog.labels.breakdownEmpty,
|
|
356
|
+
noPricing: messages.bookingCreateDialog.labels.breakdownNoPricing,
|
|
357
|
+
} })) : null, _jsx(VoucherPickerSection, { value: voucher, onChange: setVoucher, currency: currency, labels: {
|
|
358
|
+
heading: messages.bookingCreateDialog.labels.voucherHeading,
|
|
359
|
+
codePlaceholder: messages.bookingCreateDialog.labels.voucherCodePlaceholder,
|
|
360
|
+
apply: messages.bookingCreateDialog.labels.voucherApply,
|
|
361
|
+
clear: messages.bookingCreateDialog.labels.voucherClear,
|
|
362
|
+
remainingLabel: messages.bookingCreateDialog.labels.voucherRemainingLabel,
|
|
363
|
+
invalidLabel: messages.bookingCreateDialog.labels.voucherInvalidLabel,
|
|
364
|
+
} }), _jsx(PaymentScheduleSection, { value: paymentSchedule, onChange: setPaymentSchedule, currency: currency, labels: {
|
|
365
|
+
heading: messages.bookingCreateDialog.labels.paymentHeading,
|
|
366
|
+
modeUnpaid: messages.bookingCreateDialog.labels.paymentModeUnpaid,
|
|
367
|
+
modeFull: messages.bookingCreateDialog.labels.paymentModeFull,
|
|
368
|
+
modeAdvance: messages.bookingCreateDialog.labels.paymentModeAdvance,
|
|
369
|
+
modeSplit: messages.bookingCreateDialog.labels.paymentModeSplit,
|
|
370
|
+
dueDate: messages.bookingCreateDialog.labels.paymentDueDate,
|
|
371
|
+
amount: messages.bookingCreateDialog.labels.paymentAmount,
|
|
372
|
+
firstInstallment: messages.bookingCreateDialog.labels.paymentFirstInstallment,
|
|
373
|
+
secondInstallment: messages.bookingCreateDialog.labels.paymentSecondInstallment,
|
|
374
|
+
preset5050: messages.bookingCreateDialog.labels.paymentPreset5050,
|
|
375
|
+
unpaidHint: messages.bookingCreateDialog.labels.paymentUnpaidHint,
|
|
376
|
+
} }), _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: "quickbook-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: "quickbook-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: () => onOpenChange(false), 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] })] })] }) }));
|
|
313
377
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"booking-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAsB,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"booking-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAsB,MAAM,0BAA0B,CAAA;AA8CjF,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAWD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,YAAY,EACZ,OAAO,EACP,SAAS,EACT,gBAAgB,GACjB,EAAE,kBAAkB,2CAoBpB"}
|
|
@@ -9,25 +9,29 @@ import { Loader2 } from "lucide-react";
|
|
|
9
9
|
import { useEffect } from "react";
|
|
10
10
|
import { useForm } from "react-hook-form";
|
|
11
11
|
import { z } from "zod/v4";
|
|
12
|
+
import { useBookingsUiMessagesOrDefault } from "../i18n/provider";
|
|
12
13
|
import { BookingCreateDialog } from "./booking-create-dialog";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
14
|
+
function createBookingFormSchema(messages) {
|
|
15
|
+
return z.object({
|
|
16
|
+
bookingNumber: z.string().min(1, messages.bookingDialog.validation.bookingNumberRequired),
|
|
17
|
+
status: z.enum(["draft", "confirmed", "in_progress", "completed", "cancelled"]),
|
|
18
|
+
sellCurrency: z.string().min(3).max(3, messages.bookingDialog.validation.sellCurrencyInvalid),
|
|
19
|
+
sellAmountCents: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
|
|
20
|
+
costAmountCents: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
|
|
21
|
+
startDate: z.string().optional().nullable(),
|
|
22
|
+
endDate: z.string().optional().nullable(),
|
|
23
|
+
pax: z.coerce.number().int().positive().optional().or(z.literal("")).nullable(),
|
|
24
|
+
internalNotes: z.string().optional().nullable(),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const BOOKING_STATUS_VALUES = [
|
|
28
|
+
"draft",
|
|
29
|
+
"confirmed",
|
|
30
|
+
"in_progress",
|
|
31
|
+
"completed",
|
|
32
|
+
"cancelled",
|
|
30
33
|
];
|
|
34
|
+
const DEFAULT_CURRENCY = "EUR"; // i18n-literal-ok ISO default currency
|
|
31
35
|
/**
|
|
32
36
|
* Single booking dialog that handles both create and edit:
|
|
33
37
|
* - Create (no `booking` prop): renders the rich product → option → person
|
|
@@ -45,12 +49,14 @@ export function BookingDialog({ open, onOpenChange, booking, onSuccess, defaultP
|
|
|
45
49
|
}
|
|
46
50
|
function BookingEditDialog({ open, onOpenChange, booking, onSuccess }) {
|
|
47
51
|
const { update } = useBookingMutation();
|
|
52
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
53
|
+
const bookingFormSchema = createBookingFormSchema(messages);
|
|
48
54
|
const form = useForm({
|
|
49
55
|
resolver: zodResolver(bookingFormSchema),
|
|
50
56
|
defaultValues: {
|
|
51
57
|
bookingNumber: "",
|
|
52
58
|
status: "draft",
|
|
53
|
-
sellCurrency:
|
|
59
|
+
sellCurrency: DEFAULT_CURRENCY,
|
|
54
60
|
sellAmountCents: "",
|
|
55
61
|
costAmountCents: "",
|
|
56
62
|
startDate: "",
|
|
@@ -95,14 +101,14 @@ function BookingEditDialog({ open, onOpenChange, booking, onSuccess }) {
|
|
|
95
101
|
onSuccess?.(saved);
|
|
96
102
|
};
|
|
97
103
|
const isSubmitting = update.isPending;
|
|
98
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children:
|
|
104
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: messages.bookingDialog.editTitle }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "flex flex-1 flex-col overflow-hidden", children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDialog.fields.bookingNumber }), _jsx(Input, { ...form.register("bookingNumber"), placeholder: messages.bookingDialog.placeholders.bookingNumber }), form.formState.errors.bookingNumber && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.bookingNumber.message }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDialog.fields.status }), _jsxs(Select, { value: form.watch("status"), onValueChange: (value) => form.setValue("status", value), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: BOOKING_STATUS_VALUES.map((status) => (_jsx(SelectItem, { value: status, children: messages.common.bookingStatusLabels[status] }, status))) })] })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDialog.fields.sellCurrency }), _jsx(CurrencyCombobox, { value: form.watch("sellCurrency") || null, onChange: (next) => form.setValue("sellCurrency", next ?? DEFAULT_CURRENCY, {
|
|
99
105
|
shouldValidate: true,
|
|
100
106
|
shouldDirty: true,
|
|
101
|
-
}) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children:
|
|
107
|
+
}) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDialog.fields.travelDates }), _jsx(DateRangePicker, { value: {
|
|
102
108
|
from: form.watch("startDate") || null,
|
|
103
109
|
to: form.watch("endDate") || null,
|
|
104
110
|
}, onChange: (nextValue) => {
|
|
105
111
|
form.setValue("startDate", nextValue?.from ?? "", { shouldDirty: true });
|
|
106
112
|
form.setValue("endDate", nextValue?.to ?? "", { shouldDirty: true });
|
|
107
|
-
}, placeholder:
|
|
113
|
+
}, placeholder: messages.bookingDialog.placeholders.travelDates, className: "w-full" })] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDialog.fields.sellAmountCents }), _jsx(Input, { ...form.register("sellAmountCents"), type: "number", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDialog.fields.costAmountCents }), _jsx(Input, { ...form.register("costAmountCents"), type: "number", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDialog.fields.pax }), _jsx(Input, { ...form.register("pax"), type: "number", min: "1", placeholder: messages.bookingDialog.placeholders.pax })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDialog.fields.internalNotes }), _jsx(Textarea, { ...form.register("internalNotes"), placeholder: messages.bookingDialog.placeholders.internalNotes })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), children: messages.common.cancel }), _jsxs(Button, { type: "submit", size: "sm", disabled: isSubmitting, children: [isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), messages.common.saveChanges] })] })] })] }) }));
|
|
108
114
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"booking-document-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-document-dialog.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"booking-document-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-document-dialog.tsx"],"names":[],"mappings":"AAkDA,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,qBAAqB,CAAC,EACpC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,SAAS,GACV,EAAE,0BAA0B,2CAgK5B"}
|
|
@@ -8,21 +8,29 @@ import { Loader2 } from "lucide-react";
|
|
|
8
8
|
import { useEffect } from "react";
|
|
9
9
|
import { useForm } from "react-hook-form";
|
|
10
10
|
import { z } from "zod/v4";
|
|
11
|
+
import { useBookingsUiMessagesOrDefault } from "../i18n/provider";
|
|
11
12
|
import { FileDropzone } from "./file-dropzone";
|
|
12
13
|
const documentTypes = ["visa", "insurance", "health", "passport_copy", "other"];
|
|
13
14
|
const UNASSIGNED = "__unassigned__";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
function createDocumentFormSchema(messages) {
|
|
16
|
+
return z.object({
|
|
17
|
+
type: z.enum(documentTypes).default("other"),
|
|
18
|
+
fileName: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1, messages.bookingDocumentDialog.validation.fileNameRequired)
|
|
21
|
+
.max(500),
|
|
22
|
+
fileUrl: z.string().url(messages.bookingDocumentDialog.validation.fileUrlInvalid),
|
|
23
|
+
travelerId: z.string().optional().nullable(),
|
|
24
|
+
expiresAt: z.string().optional().nullable(),
|
|
25
|
+
notes: z.string().optional().nullable(),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
22
28
|
export function BookingDocumentDialog({ open, onOpenChange, bookingId, onSuccess, }) {
|
|
23
29
|
const { create } = useBookingTravelerDocumentMutation(bookingId);
|
|
24
30
|
const { data: travelersData } = useTravelers(bookingId);
|
|
25
31
|
const travelers = travelersData?.data ?? [];
|
|
32
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
33
|
+
const documentFormSchema = createDocumentFormSchema(messages);
|
|
26
34
|
const form = useForm({
|
|
27
35
|
resolver: zodResolver(documentFormSchema),
|
|
28
36
|
defaultValues: {
|
|
@@ -51,17 +59,23 @@ export function BookingDocumentDialog({ open, onOpenChange, bookingId, onSuccess
|
|
|
51
59
|
onOpenChange(false);
|
|
52
60
|
onSuccess?.();
|
|
53
61
|
};
|
|
54
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children:
|
|
55
|
-
|
|
62
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: messages.bookingDocumentDialog.title }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "flex flex-1 flex-col overflow-hidden", children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDocumentDialog.fields.type }), _jsxs(Select, { items: documentTypes.map((t) => ({
|
|
63
|
+
label: messages.bookingDocumentDialog.documentTypeLabels[t],
|
|
64
|
+
value: t,
|
|
65
|
+
})), value: form.watch("type"), onValueChange: (v) => form.setValue("type", (v ?? "other")), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: documentTypes.map((t) => (_jsx(SelectItem, { value: t, children: messages.bookingDocumentDialog.documentTypeLabels[t] }, t))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDocumentDialog.fields.traveler }), _jsxs(Select, { items: [
|
|
66
|
+
{
|
|
67
|
+
label: messages.bookingDocumentDialog.placeholders.travelerUnassigned,
|
|
68
|
+
value: UNASSIGNED,
|
|
69
|
+
},
|
|
56
70
|
...travelers.map((traveler) => ({
|
|
57
71
|
label: `${traveler.firstName} ${traveler.lastName}`,
|
|
58
72
|
value: traveler.id,
|
|
59
73
|
})),
|
|
60
|
-
], value: form.watch("travelerId") ?? UNASSIGNED, onValueChange: (v) => form.setValue("travelerId", v ?? UNASSIGNED), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: UNASSIGNED, children:
|
|
74
|
+
], value: form.watch("travelerId") ?? UNASSIGNED, onValueChange: (v) => form.setValue("travelerId", v ?? UNASSIGNED), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: UNASSIGNED, children: messages.bookingDocumentDialog.placeholders.travelerUnassigned }), travelers.map((traveler) => (_jsxs(SelectItem, { value: traveler.id, children: [traveler.firstName, " ", traveler.lastName] }, traveler.id)))] })] })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDocumentDialog.fields.file }), _jsx(FileDropzone, { accept: "application/pdf,image/*", maxSize: 10 * 1024 * 1024, onUploaded: (upload) => {
|
|
61
75
|
form.setValue("fileUrl", upload.url, { shouldValidate: true });
|
|
62
76
|
form.setValue("fileName", upload.name, { shouldValidate: true });
|
|
63
|
-
}, helperText:
|
|
77
|
+
}, helperText: messages.bookingDocumentDialog.placeholders.helperText }), form.formState.errors.fileUrl && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.fileUrl.message }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDocumentDialog.fields.expiresAt }), _jsx(DatePicker, { value: form.watch("expiresAt") || null, onChange: (next) => form.setValue("expiresAt", next ?? "", {
|
|
64
78
|
shouldValidate: true,
|
|
65
79
|
shouldDirty: true,
|
|
66
|
-
}), placeholder:
|
|
80
|
+
}), placeholder: messages.bookingDocumentDialog.placeholders.expiresAt, className: "w-full" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDocumentDialog.fields.notes }), _jsx(Textarea, { ...form.register("notes"), placeholder: messages.bookingDocumentDialog.placeholders.notes })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), children: messages.common.cancel }), _jsxs(Button, { type: "submit", size: "sm", disabled: create.isPending, children: [create.isPending && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), messages.bookingDocumentDialog.actions.addDocument] })] })] })] }) }));
|
|
67
81
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"booking-document-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-document-list.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"booking-document-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-document-list.tsx"],"names":[],"mappings":"AAuBA,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,SAAS,EAAE,EAAE,wBAAwB,2CAqH1E"}
|
|
@@ -4,6 +4,7 @@ import { useBookingTravelerDocumentMutation, useBookingTravelerDocuments, useTra
|
|
|
4
4
|
import { Badge, Button, Card, CardContent, CardHeader, CardTitle } from "@voyantjs/ui/components";
|
|
5
5
|
import { ExternalLink, FileText, Plus, Trash2 } from "lucide-react";
|
|
6
6
|
import * as React from "react";
|
|
7
|
+
import { useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault } from "../i18n/provider";
|
|
7
8
|
import { BookingDocumentDialog } from "./booking-document-dialog";
|
|
8
9
|
const typeVariant = {
|
|
9
10
|
visa: "default",
|
|
@@ -17,20 +18,24 @@ export function BookingDocumentList({ bookingId }) {
|
|
|
17
18
|
const { data } = useBookingTravelerDocuments(bookingId);
|
|
18
19
|
const { data: travelersData } = useTravelers(bookingId);
|
|
19
20
|
const { remove } = useBookingTravelerDocumentMutation(bookingId);
|
|
21
|
+
const { formatDate } = useBookingsUiI18nOrDefault();
|
|
22
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
20
23
|
const documents = data?.data ?? [];
|
|
21
24
|
const travelers = travelersData?.data ?? [];
|
|
22
25
|
const travelerMap = new Map();
|
|
23
26
|
for (const traveler of travelers) {
|
|
24
27
|
travelerMap.set(traveler.id, traveler);
|
|
25
28
|
}
|
|
26
|
-
return (_jsxs(Card, { "data-slot": "booking-document-list", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(FileText, { className: "h-4 w-4" }),
|
|
29
|
+
return (_jsxs(Card, { "data-slot": "booking-document-list", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(FileText, { className: "h-4 w-4" }), messages.bookingDocumentList.title] }), _jsxs(Button, { size: "sm", onClick: () => setDialogOpen(true), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), messages.bookingDocumentList.addDocument] })] }), _jsx(CardContent, { children: documents.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: messages.bookingDocumentList.empty })) : (_jsx("div", { className: "rounded border bg-background", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b text-muted-foreground", children: [_jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingDocumentList.columns.type }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingDocumentList.columns.file }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingDocumentList.columns.traveler }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingDocumentList.columns.expires }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingDocumentList.columns.notes }), _jsx("th", { className: "w-20 p-2" })] }) }), _jsx("tbody", { children: documents.map((doc) => {
|
|
27
30
|
const traveler = doc.travelerId ? travelerMap.get(doc.travelerId) : undefined;
|
|
28
|
-
return (_jsxs("tr", { className: "border-b last:border-b-0", children: [_jsx("td", { className: "p-2", children: _jsx(Badge, { variant: typeVariant[doc.type] ?? "outline",
|
|
31
|
+
return (_jsxs("tr", { className: "border-b last:border-b-0", children: [_jsx("td", { className: "p-2", children: _jsx(Badge, { variant: typeVariant[doc.type] ?? "outline", children: messages.bookingDocumentDialog.documentTypeLabels[doc.type] }) }), _jsx("td", { className: "p-2", children: _jsxs("a", { href: doc.fileUrl, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1 hover:underline", children: [doc.fileName, _jsx(ExternalLink, { className: "h-3 w-3 text-muted-foreground" })] }) }), _jsx("td", { className: "p-2", children: traveler
|
|
29
32
|
? `${traveler.firstName} ${traveler.lastName}`
|
|
30
33
|
: doc.travelerId
|
|
31
34
|
? doc.travelerId
|
|
32
|
-
:
|
|
33
|
-
|
|
35
|
+
: messages.bookingDocumentList.values.travelerUnavailable }), _jsx("td", { className: "p-2", children: doc.expiresAt
|
|
36
|
+
? formatDate(doc.expiresAt)
|
|
37
|
+
: messages.bookingDocumentList.values.expiresUnavailable }), _jsx("td", { className: "max-w-[200px] truncate p-2 text-muted-foreground", children: doc.notes ?? messages.bookingDocumentList.values.notesUnavailable }), _jsx("td", { className: "p-2", children: _jsx("button", { type: "button", onClick: () => {
|
|
38
|
+
if (confirm(messages.bookingDocumentList.actions.deleteConfirm)) {
|
|
34
39
|
remove.mutate(doc.id);
|
|
35
40
|
}
|
|
36
41
|
}, className: "text-muted-foreground hover:text-destructive", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }) })] }, doc.id));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"booking-group-link-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-group-link-dialog.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"booking-group-link-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-group-link-dialog.tsx"],"names":[],"mappings":"AA2BA,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACrC;AAMD,wBAAgB,sBAAsB,CAAC,EACrC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,SAAS,EACT,YAAY,EACZ,QAAQ,GACT,EAAE,2BAA2B,2CAqL7B"}
|
|
@@ -4,12 +4,15 @@ import { useBookingGroupMemberMutation, useBookingGroupMutation, useBookingGroup
|
|
|
4
4
|
import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
|
|
5
5
|
import { Loader2 } from "lucide-react";
|
|
6
6
|
import * as React from "react";
|
|
7
|
+
import { useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault } from "../i18n/provider";
|
|
7
8
|
const JOIN_PLACEHOLDER = "__none__";
|
|
8
9
|
export function BookingGroupLinkDialog({ open, onOpenChange, bookingId, productId, optionUnitId, onLinked, }) {
|
|
9
10
|
const [mode, setMode] = React.useState("join");
|
|
10
11
|
const [selectedGroupId, setSelectedGroupId] = React.useState("");
|
|
11
12
|
const [newGroupLabel, setNewGroupLabel] = React.useState("");
|
|
12
13
|
const [error, setError] = React.useState(null);
|
|
14
|
+
const { formatDate } = useBookingsUiI18nOrDefault();
|
|
15
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
13
16
|
React.useEffect(() => {
|
|
14
17
|
if (!open) {
|
|
15
18
|
setMode("join");
|
|
@@ -33,7 +36,8 @@ export function BookingGroupLinkDialog({ open, onOpenChange, bookingId, productI
|
|
|
33
36
|
let targetGroupId = selectedGroupId;
|
|
34
37
|
let role = "shared";
|
|
35
38
|
if (mode === "create") {
|
|
36
|
-
const label = newGroupLabel.trim() ||
|
|
39
|
+
const label = newGroupLabel.trim() ||
|
|
40
|
+
`${messages.bookingGroupLinkDialog.labels.generatedLabelPrefix} - ${formatDate(new Date())}`;
|
|
37
41
|
const group = await createGroup.mutateAsync({
|
|
38
42
|
kind: "shared_room",
|
|
39
43
|
label,
|
|
@@ -45,7 +49,7 @@ export function BookingGroupLinkDialog({ open, onOpenChange, bookingId, productI
|
|
|
45
49
|
role = "primary";
|
|
46
50
|
}
|
|
47
51
|
if (!targetGroupId || targetGroupId === JOIN_PLACEHOLDER) {
|
|
48
|
-
setError(
|
|
52
|
+
setError(messages.bookingGroupLinkDialog.validation.selectGroup);
|
|
49
53
|
return;
|
|
50
54
|
}
|
|
51
55
|
await addMember.mutateAsync({
|
|
@@ -57,12 +61,19 @@ export function BookingGroupLinkDialog({ open, onOpenChange, bookingId, productI
|
|
|
57
61
|
onLinked?.(targetGroupId);
|
|
58
62
|
}
|
|
59
63
|
catch (err) {
|
|
60
|
-
const message = err instanceof Error ? err.message :
|
|
64
|
+
const message = err instanceof Error ? err.message : messages.bookingGroupLinkDialog.validation.linkFailed;
|
|
61
65
|
setError(message);
|
|
62
66
|
}
|
|
63
67
|
};
|
|
64
68
|
const isSubmitting = createGroup.isPending || addMember.isPending;
|
|
65
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children:
|
|
66
|
-
? [
|
|
67
|
-
|
|
69
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: messages.bookingGroupLinkDialog.title }) }), _jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", size: "sm", variant: mode === "join" ? "default" : "ghost", onClick: () => setMode("join"), children: messages.bookingGroupLinkDialog.modes.join }), _jsx(Button, { type: "button", size: "sm", variant: mode === "create" ? "default" : "ghost", onClick: () => setMode("create"), children: messages.bookingGroupLinkDialog.modes.create })] }), mode === "join" ? (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingGroupLinkDialog.fields.existingGroups }), _jsxs(Select, { items: groups.length === 0
|
|
70
|
+
? [
|
|
71
|
+
{
|
|
72
|
+
label: messages.bookingGroupLinkDialog.placeholders.noExistingGroups,
|
|
73
|
+
value: JOIN_PLACEHOLDER,
|
|
74
|
+
},
|
|
75
|
+
]
|
|
76
|
+
: groups.map((g) => ({ label: g.label, value: g.id })), value: selectedGroupId || JOIN_PLACEHOLDER, onValueChange: (v) => setSelectedGroupId(v === JOIN_PLACEHOLDER ? "" : (v ?? "")), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: messages.bookingGroupLinkDialog.placeholders.selectGroup }) }), _jsx(SelectContent, { children: groups.length === 0 ? (_jsx(SelectItem, { value: JOIN_PLACEHOLDER, disabled: true, children: messages.bookingGroupLinkDialog.placeholders.noExistingGroups })) : (groups.map((g) => (_jsx(SelectItem, { value: g.id, children: g.label }, g.id)))) })] }), productId && (_jsx("p", { className: "text-xs text-muted-foreground", children: messages.bookingGroupLinkDialog.hints.productFiltered }))] })) : (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingGroupLinkDialog.fields.groupLabel }), _jsx(Input, { value: newGroupLabel, onChange: (e) => setNewGroupLabel(e.target.value), placeholder: messages.bookingGroupLinkDialog.placeholders.groupLabel }), _jsx("p", { className: "text-xs text-muted-foreground", children: messages.bookingGroupLinkDialog.hints.primaryMember })] })), error && _jsx("p", { className: "text-xs text-destructive", children: error })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), disabled: isSubmitting, children: messages.common.cancel }), _jsxs(Button, { type: "button", size: "sm", onClick: handleSubmit, disabled: isSubmitting || (mode === "join" && !selectedGroupId), children: [isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), mode === "create"
|
|
77
|
+
? messages.bookingGroupLinkDialog.actions.createAndLink
|
|
78
|
+
: messages.bookingGroupLinkDialog.actions.linkToGroup] })] })] }) }));
|
|
68
79
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"booking-group-section.d.ts","sourceRoot":"","sources":["../../src/components/booking-group-section.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"booking-group-section.d.ts","sourceRoot":"","sources":["../../src/components/booking-group-section.tsx"],"names":[],"mappings":"AAeA,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED,wBAAgB,mBAAmB,CAAC,EAClC,SAAS,EACT,SAAS,EACT,YAAY,GACb,EAAE,wBAAwB,2CA4H1B"}
|
|
@@ -4,9 +4,11 @@ import { useBookingGroup, useBookingGroupForBooking, useBookingGroupMemberMutati
|
|
|
4
4
|
import { Badge, Button, Card, CardContent, CardHeader, CardTitle } from "@voyantjs/ui/components";
|
|
5
5
|
import { Link2, Unlink, Users } from "lucide-react";
|
|
6
6
|
import * as React from "react";
|
|
7
|
+
import { formatMessage, useBookingsUiMessagesOrDefault } from "../i18n/provider";
|
|
7
8
|
import { BookingGroupLinkDialog } from "./booking-group-link-dialog";
|
|
8
9
|
export function BookingGroupSection({ bookingId, productId, optionUnitId, }) {
|
|
9
10
|
const [linkDialogOpen, setLinkDialogOpen] = React.useState(false);
|
|
11
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
10
12
|
// Auto-resolve product/option-unit from items when the caller hasn't
|
|
11
13
|
// supplied them. Explicit `null` is respected as an override.
|
|
12
14
|
const shouldAutoResolve = productId === undefined || optionUnitId === undefined;
|
|
@@ -22,10 +24,14 @@ export function BookingGroupSection({ bookingId, productId, optionUnitId, }) {
|
|
|
22
24
|
const handleRemove = async () => {
|
|
23
25
|
if (!groupId)
|
|
24
26
|
return;
|
|
25
|
-
if (!confirm(
|
|
27
|
+
if (!confirm(messages.bookingGroupSection.actions.removeConfirm))
|
|
26
28
|
return;
|
|
27
29
|
await removeMember.mutateAsync({ groupId, bookingId });
|
|
28
30
|
};
|
|
29
31
|
const siblings = members.filter((m) => m.bookingId !== bookingId);
|
|
30
|
-
return (_jsxs(Card, { "data-slot": "booking-group-section", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(Users, { className: "h-4 w-4" }),
|
|
32
|
+
return (_jsxs(Card, { "data-slot": "booking-group-section", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(Users, { className: "h-4 w-4" }), messages.bookingGroupSection.title] }), group ? (_jsxs(Button, { size: "sm", variant: "outline", onClick: handleRemove, disabled: removeMember.isPending, children: [_jsx(Unlink, { className: "mr-2 h-4 w-4" }), messages.bookingGroupSection.actions.removeFromGroup] })) : (_jsxs(Button, { size: "sm", onClick: () => setLinkDialogOpen(true), children: [_jsx(Link2, { className: "mr-2 h-4 w-4" }), messages.bookingGroupSection.actions.linkToSharedRoom] }))] }), _jsx(CardContent, { children: !group ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: messages.bookingGroupSection.empty })) : (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between rounded-md border bg-muted/30 p-3 text-sm", children: [_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: messages.bookingGroupSection.group }), _jsx("div", { className: "font-medium", children: group.label })] }), _jsx(Badge, { variant: "outline", children: group.kind === "shared_room"
|
|
33
|
+
? messages.bookingGroupSection.sharedRoomKind
|
|
34
|
+
: group.kind.replace(/_/g, " ") })] }), _jsxs("div", { children: [_jsx("div", { className: "mb-2 text-xs font-medium text-muted-foreground", children: formatMessage(messages.bookingGroupSection.siblingBookings, {
|
|
35
|
+
count: siblings.length,
|
|
36
|
+
}) }), siblings.length === 0 ? (_jsx("p", { className: "text-xs text-muted-foreground", children: messages.bookingGroupSection.noSiblingBookings })) : (_jsx("ul", { className: "space-y-1", children: siblings.map((m) => (_jsxs("li", { className: "flex items-center justify-between rounded px-2 py-1 text-sm", children: [_jsx("span", { className: "font-mono text-xs", children: m.booking?.bookingNumber ?? m.bookingId }), _jsxs("div", { className: "flex items-center gap-2", children: [m.role === "primary" && (_jsx(Badge, { variant: "default", className: "text-xs", children: messages.bookingGroupSection.primaryBadge })), m.booking?.status && (_jsx("span", { className: "text-xs text-muted-foreground", children: messages.common.bookingStatusLabels[m.booking.status] }))] })] }, m.id))) }))] })] })) }), _jsx(BookingGroupLinkDialog, { open: linkDialogOpen, onOpenChange: setLinkDialogOpen, bookingId: bookingId, productId: effectiveProductId, optionUnitId: effectiveOptionUnitId })] }));
|
|
31
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"booking-guarantee-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-guarantee-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,sBAAsB,EAA+B,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"booking-guarantee-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-guarantee-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,sBAAsB,EAA+B,MAAM,yBAAyB,CAAA;AAgElG,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,sBAAsB,CAAA;IAClC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,sBAAsB,CAAC,EACrC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,SAAS,EACT,SAAS,GACV,EAAE,2BAA2B,2CA4M7B"}
|