@voyantjs/bookings-ui 0.16.0 → 0.17.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/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 +32 -17
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { bookingStatusBadgeVariant,
|
|
3
|
+
import { bookingStatusBadgeVariant, useBookings, } from "@voyantjs/bookings-react";
|
|
4
4
|
import { Badge } from "@voyantjs/ui/components/badge";
|
|
5
5
|
import { Button } from "@voyantjs/ui/components/button";
|
|
6
6
|
import { Input } from "@voyantjs/ui/components/input";
|
|
7
7
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyantjs/ui/components/table";
|
|
8
8
|
import { Loader2, Plus, Search } from "lucide-react";
|
|
9
9
|
import * as React from "react";
|
|
10
|
+
import { formatMessage, useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault, } from "../i18n/provider";
|
|
10
11
|
import { BookingDialog } from "./booking-dialog";
|
|
11
|
-
function formatAmount(cents, currency) {
|
|
12
|
-
if (cents == null)
|
|
13
|
-
return "—";
|
|
14
|
-
return `${(cents / 100).toFixed(2)} ${currency}`;
|
|
15
|
-
}
|
|
16
12
|
export function BookingList({ pageSize = 25, onSelectBooking } = {}) {
|
|
17
13
|
const [search, setSearch] = React.useState("");
|
|
18
14
|
const [offset, setOffset] = React.useState(0);
|
|
19
15
|
const [dialogOpen, setDialogOpen] = React.useState(false);
|
|
20
16
|
const [editing, setEditing] = React.useState(undefined);
|
|
17
|
+
const { formatNumber } = useBookingsUiI18nOrDefault();
|
|
18
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
21
19
|
const { data, isPending, isError } = useBookings({
|
|
22
20
|
search: search || undefined,
|
|
23
21
|
limit: pageSize,
|
|
@@ -35,13 +33,24 @@ export function BookingList({ pageSize = 25, onSelectBooking } = {}) {
|
|
|
35
33
|
setEditing(booking);
|
|
36
34
|
setDialogOpen(true);
|
|
37
35
|
};
|
|
38
|
-
return (_jsxs("div", { "data-slot": "booking-list", className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { className: "relative w-full max-w-sm", children: [_jsx(Search, { className: "absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder:
|
|
36
|
+
return (_jsxs("div", { "data-slot": "booking-list", className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { className: "relative w-full max-w-sm", children: [_jsx(Search, { className: "absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: messages.bookingList.searchPlaceholder, value: search, onChange: (event) => {
|
|
39
37
|
setSearch(event.target.value);
|
|
40
38
|
setOffset(0);
|
|
41
39
|
}, className: "pl-9" })] }), _jsx("div", { className: "flex items-center gap-2", children: _jsxs(Button, { onClick: () => {
|
|
42
40
|
setEditing(undefined);
|
|
43
41
|
setDialogOpen(true);
|
|
44
|
-
}, children: [_jsx(Plus, { className: "mr-2 size-4" }),
|
|
42
|
+
}, 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: messages.bookingList.columns.bookingNumber }), _jsx(TableHead, { children: messages.bookingList.columns.status }), _jsx(TableHead, { children: messages.bookingList.columns.sellAmount }), _jsx(TableHead, { children: messages.bookingList.columns.pax }), _jsx(TableHead, { children: messages.bookingList.columns.startDate })] }) }), _jsx(TableBody, { children: isPending ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, className: "h-24 text-center", children: _jsx(Loader2, { className: "mx-auto size-4 animate-spin text-muted-foreground" }) }) })) : isError ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, className: "h-24 text-center text-sm text-destructive", children: messages.bookingList.loadingError }) })) : bookings.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, 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: _jsx(Badge, { variant: bookingStatusBadgeVariant[booking.status], children: messages.common.bookingStatusLabels[booking.status] }) }), _jsx(TableCell, { children: booking.sellAmountCents == null
|
|
43
|
+
? "—"
|
|
44
|
+
: `${formatNumber(booking.sellAmountCents / 100, {
|
|
45
|
+
minimumFractionDigits: 2,
|
|
46
|
+
maximumFractionDigits: 2,
|
|
47
|
+
})} ${booking.sellCurrency}` }), _jsx(TableCell, { children: booking.pax ?? "—" }), _jsx(TableCell, { children: booking.startDate ?? "—" })] }, booking.id)))) })] }) }), _jsxs("div", { className: "flex items-center justify-between text-sm text-muted-foreground", children: [_jsx("span", { children: formatMessage(messages.bookingList.showingSummary, {
|
|
48
|
+
count: bookings.length,
|
|
49
|
+
total,
|
|
50
|
+
}) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", disabled: offset === 0, onClick: () => setOffset((prev) => Math.max(0, prev - pageSize)), children: "Previous" }), _jsx("span", { children: formatMessage(messages.bookingList.pageSummary, {
|
|
51
|
+
page,
|
|
52
|
+
pageCount,
|
|
53
|
+
}) }), _jsx(Button, { variant: "outline", size: "sm", disabled: offset + pageSize >= total, onClick: () => setOffset((prev) => prev + pageSize), children: "Next" })] })] }), _jsx(BookingDialog, { open: dialogOpen, onOpenChange: setDialogOpen, booking: editing, onSuccess: (booking) => {
|
|
45
54
|
onSelectBooking?.(booking);
|
|
46
55
|
} })] }));
|
|
47
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"booking-notes.d.ts","sourceRoot":"","sources":["../../src/components/booking-notes.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"booking-notes.d.ts","sourceRoot":"","sources":["../../src/components/booking-notes.tsx"],"names":[],"mappings":"AASA,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,EAAE,iBAAiB,2CAqD5D"}
|
|
@@ -4,13 +4,16 @@ import { useBookingNoteMutation, useBookingNotes } from "@voyantjs/bookings-reac
|
|
|
4
4
|
import { Button, Card, CardContent, CardHeader, CardTitle, Textarea } 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
|
export function BookingNotes({ bookingId }) {
|
|
8
9
|
const [content, setContent] = React.useState("");
|
|
9
10
|
const { data } = useBookingNotes(bookingId);
|
|
10
11
|
const mutation = useBookingNoteMutation(bookingId);
|
|
12
|
+
const { formatDateTime } = useBookingsUiI18nOrDefault();
|
|
13
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
11
14
|
const notes = data?.data ?? [];
|
|
12
|
-
return (_jsxs(Card, { "data-slot": "booking-notes", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children:
|
|
15
|
+
return (_jsxs(Card, { "data-slot": "booking-notes", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: messages.bookingNotes.title }) }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex gap-2", children: [_jsx(Textarea, { placeholder: messages.bookingNotes.placeholder, value: content, onChange: (event) => setContent(event.target.value), className: "min-h-[80px]" }), _jsx(Button, { className: "self-end", disabled: !content.trim() || mutation.isPending, onClick: async () => {
|
|
13
16
|
await mutation.mutateAsync({ content: content.trim() });
|
|
14
17
|
setContent("");
|
|
15
|
-
}, children: mutation.isPending ? _jsx(Loader2, { className: "h-4 w-4 animate-spin" }) :
|
|
18
|
+
}, children: mutation.isPending ? (_jsx(Loader2, { className: "h-4 w-4 animate-spin" })) : (messages.bookingNotes.add) })] }), notes.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: messages.bookingNotes.empty })) : (notes.map((note) => (_jsxs("div", { className: "rounded-md border p-3", children: [_jsx("p", { className: "whitespace-pre-wrap text-sm", children: note.content }), _jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: formatDateTime(note.createdAt) })] }, note.id))))] })] }));
|
|
16
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"booking-payment-schedule-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-payment-schedule-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,4BAA4B,EAElC,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"booking-payment-schedule-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-payment-schedule-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,4BAA4B,EAElC,MAAM,yBAAyB,CAAA;AAgDhC,MAAM,WAAW,iCAAiC;IAChD,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,4BAA4B,CAAA;IACvC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,4BAA4B,CAAC,EAC3C,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,SAAS,GACV,EAAE,iCAAiC,2CA6LnC"}
|
|
@@ -9,26 +9,35 @@ 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
|
const scheduleTypes = ["deposit", "installment", "balance", "hold", "other"];
|
|
13
14
|
const scheduleStatuses = ["pending", "due", "paid", "waived", "cancelled", "expired"];
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
const DEFAULT_CURRENCY = "EUR"; // i18n-literal-ok ISO default currency
|
|
16
|
+
function createScheduleFormSchema(messages) {
|
|
17
|
+
return z.object({
|
|
18
|
+
scheduleType: z.enum(scheduleTypes).default("balance"),
|
|
19
|
+
status: z.enum(scheduleStatuses).default("pending"),
|
|
20
|
+
dueDate: z.string().min(1, messages.paymentScheduleDialog.validation.dueDateRequired),
|
|
21
|
+
currency: z.string().min(3).max(3).default("EUR"),
|
|
22
|
+
amountCents: z.coerce
|
|
23
|
+
.number()
|
|
24
|
+
.int()
|
|
25
|
+
.min(0, messages.paymentScheduleDialog.validation.amountRequired),
|
|
26
|
+
notes: z.string().optional().nullable(),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
22
29
|
export function BookingPaymentScheduleDialog({ open, onOpenChange, bookingId, schedule, onSuccess, }) {
|
|
23
30
|
const isEditing = Boolean(schedule);
|
|
24
31
|
const { create, update } = useBookingPaymentScheduleMutation(bookingId);
|
|
32
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
33
|
+
const scheduleFormSchema = createScheduleFormSchema(messages);
|
|
25
34
|
const form = useForm({
|
|
26
35
|
resolver: zodResolver(scheduleFormSchema),
|
|
27
36
|
defaultValues: {
|
|
28
37
|
scheduleType: "balance",
|
|
29
38
|
status: "pending",
|
|
30
39
|
dueDate: "",
|
|
31
|
-
currency:
|
|
40
|
+
currency: DEFAULT_CURRENCY,
|
|
32
41
|
amountCents: 0,
|
|
33
42
|
notes: "",
|
|
34
43
|
},
|
|
@@ -67,11 +76,21 @@ export function BookingPaymentScheduleDialog({ open, onOpenChange, bookingId, sc
|
|
|
67
76
|
onSuccess?.();
|
|
68
77
|
};
|
|
69
78
|
const isSubmitting = create.isPending || update.isPending;
|
|
70
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing
|
|
79
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing
|
|
80
|
+
? messages.paymentScheduleDialog.titles.edit
|
|
81
|
+
: messages.paymentScheduleDialog.titles.create }) }), _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.paymentScheduleDialog.fields.type }), _jsxs(Select, { items: scheduleTypes.map((t) => ({
|
|
82
|
+
label: messages.paymentScheduleDialog.scheduleTypeLabels[t],
|
|
83
|
+
value: t,
|
|
84
|
+
})), value: form.watch("scheduleType"), onValueChange: (v) => form.setValue("scheduleType", (v ?? "balance")), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: scheduleTypes.map((t) => (_jsx(SelectItem, { value: t, children: messages.paymentScheduleDialog.scheduleTypeLabels[t] }, t))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.paymentScheduleDialog.fields.status }), _jsxs(Select, { items: scheduleStatuses.map((s) => ({
|
|
85
|
+
label: messages.paymentScheduleDialog.scheduleStatusLabels[s],
|
|
86
|
+
value: s,
|
|
87
|
+
})), value: form.watch("status"), onValueChange: (v) => form.setValue("status", (v ?? "pending")), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: scheduleStatuses.map((s) => (_jsx(SelectItem, { value: s, children: messages.paymentScheduleDialog.scheduleStatusLabels[s] }, s))) })] })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.paymentScheduleDialog.fields.dueDate }), _jsx(DatePicker, { value: form.watch("dueDate") || null, onChange: (next) => form.setValue("dueDate", next ?? "", {
|
|
71
88
|
shouldValidate: true,
|
|
72
89
|
shouldDirty: true,
|
|
73
|
-
}), placeholder:
|
|
90
|
+
}), placeholder: messages.paymentScheduleDialog.placeholders.dueDate, className: "w-full" }), form.formState.errors.dueDate && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.dueDate.message }))] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.paymentScheduleDialog.fields.currency }), _jsx(CurrencyCombobox, { value: form.watch("currency") || null, onChange: (next) => form.setValue("currency", next ?? DEFAULT_CURRENCY, {
|
|
74
91
|
shouldValidate: true,
|
|
75
92
|
shouldDirty: true,
|
|
76
|
-
}) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children:
|
|
93
|
+
}) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.paymentScheduleDialog.fields.amountCents }), _jsx(Input, { ...form.register("amountCents"), type: "number", min: 0 }), form.formState.errors.amountCents && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.amountCents.message }))] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.paymentScheduleDialog.fields.notes }), _jsx(Textarea, { ...form.register("notes"), placeholder: messages.paymentScheduleDialog.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: isSubmitting, children: [isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), isEditing
|
|
94
|
+
? messages.common.saveChanges
|
|
95
|
+
: messages.paymentScheduleDialog.actions.addSchedule] })] })] })] }) }));
|
|
77
96
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"booking-payment-schedule-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-payment-schedule-list.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"booking-payment-schedule-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-payment-schedule-list.tsx"],"names":[],"mappings":"AAuBA,MAAM,WAAW,+BAA+B;IAC9C,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,0BAA0B,CAAC,EAAE,SAAS,EAAE,EAAE,+BAA+B,2CA8HxF"}
|
|
@@ -4,6 +4,7 @@ import { useBookingPaymentScheduleMutation, useBookingPaymentSchedules, } from "
|
|
|
4
4
|
import { Badge, Button, Card, CardContent, CardHeader, CardTitle } from "@voyantjs/ui/components";
|
|
5
5
|
import { CalendarClock, Pencil, Plus, Trash2 } from "lucide-react";
|
|
6
6
|
import * as React from "react";
|
|
7
|
+
import { useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault } from "../i18n/provider";
|
|
7
8
|
import { BookingPaymentScheduleDialog } from "./booking-payment-schedule-dialog";
|
|
8
9
|
const statusVariant = {
|
|
9
10
|
pending: "outline",
|
|
@@ -13,23 +14,23 @@ const statusVariant = {
|
|
|
13
14
|
cancelled: "destructive",
|
|
14
15
|
expired: "secondary",
|
|
15
16
|
};
|
|
16
|
-
function formatAmount(cents, currency) {
|
|
17
|
-
return `${(cents / 100).toFixed(2)} ${currency}`;
|
|
18
|
-
}
|
|
19
17
|
export function BookingPaymentScheduleList({ bookingId }) {
|
|
20
18
|
const [dialogOpen, setDialogOpen] = React.useState(false);
|
|
21
19
|
const [editing, setEditing] = React.useState(undefined);
|
|
22
20
|
const { data } = useBookingPaymentSchedules(bookingId);
|
|
23
21
|
const { remove } = useBookingPaymentScheduleMutation(bookingId);
|
|
22
|
+
const { formatCurrency } = useBookingsUiI18nOrDefault();
|
|
23
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
24
24
|
const schedules = data?.data ?? [];
|
|
25
|
-
return (_jsxs(Card, { "data-slot": "booking-payment-schedule-list", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(CalendarClock, { className: "h-4 w-4" }),
|
|
25
|
+
return (_jsxs(Card, { "data-slot": "booking-payment-schedule-list", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(CalendarClock, { className: "h-4 w-4" }), messages.bookingPaymentScheduleList.title] }), _jsxs(Button, { size: "sm", onClick: () => {
|
|
26
26
|
setEditing(undefined);
|
|
27
27
|
setDialogOpen(true);
|
|
28
|
-
}, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }),
|
|
28
|
+
}, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), messages.bookingPaymentScheduleList.addSchedule] })] }), _jsx(CardContent, { children: schedules.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: messages.bookingPaymentScheduleList.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.bookingPaymentScheduleList.columns.type }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingPaymentScheduleList.columns.status }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingPaymentScheduleList.columns.dueDate }), _jsx("th", { className: "p-2 text-right font-medium", children: messages.bookingPaymentScheduleList.columns.amount }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingPaymentScheduleList.columns.notes }), _jsx("th", { className: "w-20 p-2" })] }) }), _jsx("tbody", { children: schedules.map((schedule) => (_jsxs("tr", { className: "border-b last:border-b-0", children: [_jsx("td", { className: "p-2", children: messages.paymentScheduleDialog.scheduleTypeLabels[schedule.scheduleType] }), _jsx("td", { className: "p-2", children: _jsx(Badge, { variant: statusVariant[schedule.status] ?? "secondary", children: messages.paymentScheduleDialog.scheduleStatusLabels[schedule.status] }) }), _jsx("td", { className: "p-2", children: schedule.dueDate }), _jsx("td", { className: "p-2 text-right font-mono", children: formatCurrency(schedule.amountCents / 100, schedule.currency) }), _jsx("td", { className: "max-w-[200px] truncate p-2 text-muted-foreground", children: schedule.notes ??
|
|
29
|
+
messages.bookingPaymentScheduleList.values.notesUnavailable }), _jsx("td", { className: "p-2", children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => {
|
|
29
30
|
setEditing(schedule);
|
|
30
31
|
setDialogOpen(true);
|
|
31
32
|
}, className: "text-muted-foreground hover:text-foreground", children: _jsx(Pencil, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: () => {
|
|
32
|
-
if (confirm(
|
|
33
|
+
if (confirm(messages.bookingPaymentScheduleList.actions.deleteConfirm)) {
|
|
33
34
|
remove.mutate(schedule.id);
|
|
34
35
|
}
|
|
35
36
|
}, className: "text-muted-foreground hover:text-destructive", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }) })] }, schedule.id))) })] }) })) }), _jsx(BookingPaymentScheduleDialog, { open: dialogOpen, onOpenChange: (nextOpen) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"booking-payments-summary.d.ts","sourceRoot":"","sources":["../../src/components/booking-payments-summary.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"booking-payments-summary.d.ts","sourceRoot":"","sources":["../../src/components/booking-payments-summary.tsx"],"names":[],"mappings":"AAeA,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,sBAAsB,CAAC,EAAE,SAAS,EAAE,EAAE,2BAA2B,2CAgFhF"}
|
|
@@ -9,11 +9,14 @@ const statusVariant = {
|
|
|
9
9
|
failed: "destructive",
|
|
10
10
|
refunded: "secondary",
|
|
11
11
|
};
|
|
12
|
-
|
|
13
|
-
return `${(cents / 100).toFixed(2)} ${currency}`;
|
|
14
|
-
}
|
|
12
|
+
import { useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault } from "../i18n/provider";
|
|
15
13
|
export function BookingPaymentsSummary({ bookingId }) {
|
|
16
14
|
const { data } = usePublicBookingPayments(bookingId);
|
|
15
|
+
const { formatDate, formatNumber } = useBookingsUiI18nOrDefault();
|
|
16
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
17
17
|
const payments = data?.data?.payments ?? [];
|
|
18
|
-
return (_jsxs(Card, { "data-slot": "booking-payments-summary", children: [_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(CreditCard, { className: "h-4 w-4" }),
|
|
18
|
+
return (_jsxs(Card, { "data-slot": "booking-payments-summary", children: [_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(CreditCard, { className: "h-4 w-4" }), messages.bookingPaymentsSummary.title] }) }), _jsx(CardContent, { children: payments.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: messages.bookingPaymentsSummary.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.bookingPaymentsSummary.columns.invoice }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingPaymentsSummary.columns.method }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingPaymentsSummary.columns.status }), _jsx("th", { className: "p-2 text-right font-medium", children: messages.bookingPaymentsSummary.columns.amount }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingPaymentsSummary.columns.date }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingPaymentsSummary.columns.reference })] }) }), _jsx("tbody", { children: payments.map((payment) => (_jsxs("tr", { className: "border-b last:border-b-0", children: [_jsx("td", { className: "p-2 font-mono text-xs", children: payment.invoiceNumber }), _jsx("td", { className: "p-2", children: messages.bookingPaymentsSummary.paymentMethodLabels[payment.paymentMethod] ?? payment.paymentMethod }), _jsx("td", { className: "p-2", children: _jsx(Badge, { variant: statusVariant[payment.status] ?? "secondary", children: messages.bookingPaymentsSummary.paymentStatusLabels[payment.status] ?? payment.status }) }), _jsx("td", { className: "p-2 text-right font-mono", children: `${formatNumber(payment.amountCents / 100, {
|
|
19
|
+
minimumFractionDigits: 2,
|
|
20
|
+
maximumFractionDigits: 2,
|
|
21
|
+
})} ${payment.currency}` }), _jsx("td", { className: "p-2", children: formatDate(payment.paymentDate) }), _jsx("td", { className: "max-w-[150px] truncate p-2 font-mono text-xs", children: payment.referenceNumber ?? "-" })] }, payment.id))) })] }) })) })] }));
|
|
19
22
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-dropzone.d.ts","sourceRoot":"","sources":["../../src/components/file-dropzone.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"file-dropzone.d.ts","sourceRoot":"","sources":["../../src/components/file-dropzone.tsx"],"names":[],"mappings":"AAUA,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,wCAAwC;IACxC,UAAU,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAA;IACxC,gEAAgE;IAChE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,wBAAgB,YAAY,CAAC,EAC3B,SAA6B,EAC7B,MAAM,EACN,OAAO,EACP,UAAU,EACV,OAAO,EACP,UAAU,EACV,QAAQ,GACT,EAAE,iBAAiB,2CA8KnB"}
|
|
@@ -2,19 +2,24 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { File as FileIcon, Loader2, Upload, X } from "lucide-react";
|
|
4
4
|
import * as React from "react";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return `${bytes} B`;
|
|
8
|
-
if (bytes < 1024 * 1024)
|
|
9
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
10
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
11
|
-
}
|
|
12
|
-
export function FileDropzone({ uploadUrl = "/api/v1/uploads", accept, maxSize, onUploaded, onError, helperText = "Drag and drop a file here, or click to select", disabled, }) {
|
|
5
|
+
import { formatMessage, useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault, } from "../i18n/provider";
|
|
6
|
+
export function FileDropzone({ uploadUrl = "/api/v1/uploads", accept, maxSize, onUploaded, onError, helperText, disabled, }) {
|
|
13
7
|
const inputRef = React.useRef(null);
|
|
14
8
|
const [isDragging, setIsDragging] = React.useState(false);
|
|
15
9
|
const [isUploading, setIsUploading] = React.useState(false);
|
|
16
10
|
const [uploaded, setUploaded] = React.useState(null);
|
|
17
11
|
const [error, setError] = React.useState(null);
|
|
12
|
+
const { formatNumber } = useBookingsUiI18nOrDefault();
|
|
13
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
14
|
+
const resolvedHelperText = helperText ?? messages.fileDropzone.helperText;
|
|
15
|
+
const formatSize = React.useCallback((bytes) => {
|
|
16
|
+
if (bytes < 1024)
|
|
17
|
+
return `${formatNumber(bytes)} B`;
|
|
18
|
+
if (bytes < 1024 * 1024) {
|
|
19
|
+
return `${formatNumber(bytes / 1024, { maximumFractionDigits: 1 })} KB`;
|
|
20
|
+
}
|
|
21
|
+
return `${formatNumber(bytes / (1024 * 1024), { maximumFractionDigits: 1 })} MB`;
|
|
22
|
+
}, [formatNumber]);
|
|
18
23
|
const reportError = (message) => {
|
|
19
24
|
setError(message);
|
|
20
25
|
onError?.(message);
|
|
@@ -22,7 +27,9 @@ export function FileDropzone({ uploadUrl = "/api/v1/uploads", accept, maxSize, o
|
|
|
22
27
|
const handleFile = async (file) => {
|
|
23
28
|
setError(null);
|
|
24
29
|
if (maxSize && file.size > maxSize) {
|
|
25
|
-
reportError(
|
|
30
|
+
reportError(formatMessage(messages.fileDropzone.validation.fileTooLarge, {
|
|
31
|
+
maxSize: formatSize(maxSize),
|
|
32
|
+
}));
|
|
26
33
|
return;
|
|
27
34
|
}
|
|
28
35
|
setIsUploading(true);
|
|
@@ -30,13 +37,16 @@ export function FileDropzone({ uploadUrl = "/api/v1/uploads", accept, maxSize, o
|
|
|
30
37
|
const formData = new FormData();
|
|
31
38
|
formData.append("file", file);
|
|
32
39
|
const res = await fetch(uploadUrl, {
|
|
33
|
-
method: "POST",
|
|
40
|
+
method: "POST", // i18n-literal-ok HTTP method
|
|
34
41
|
body: formData,
|
|
35
|
-
credentials: "include",
|
|
42
|
+
credentials: "include", // i18n-literal-ok fetch credentials mode
|
|
36
43
|
});
|
|
37
44
|
if (!res.ok) {
|
|
38
45
|
const body = await res.text();
|
|
39
|
-
reportError(body ||
|
|
46
|
+
reportError(body ||
|
|
47
|
+
formatMessage(messages.fileDropzone.validation.uploadFailedWithStatus, {
|
|
48
|
+
status: res.status,
|
|
49
|
+
}));
|
|
40
50
|
return;
|
|
41
51
|
}
|
|
42
52
|
const data = (await res.json());
|
|
@@ -45,7 +55,7 @@ export function FileDropzone({ uploadUrl = "/api/v1/uploads", accept, maxSize, o
|
|
|
45
55
|
onUploaded(result);
|
|
46
56
|
}
|
|
47
57
|
catch (err) {
|
|
48
|
-
reportError(err instanceof Error ? err.message :
|
|
58
|
+
reportError(err instanceof Error ? err.message : messages.fileDropzone.validation.uploadFailed);
|
|
49
59
|
}
|
|
50
60
|
finally {
|
|
51
61
|
setIsUploading(false);
|
|
@@ -86,7 +96,7 @@ export function FileDropzone({ uploadUrl = "/api/v1/uploads", accept, maxSize, o
|
|
|
86
96
|
setError(null);
|
|
87
97
|
};
|
|
88
98
|
if (uploaded) {
|
|
89
|
-
return (_jsxs("div", { className: "flex items-center justify-between gap-3 rounded-md border bg-muted/30 p-3", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(FileIcon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm font-medium", children: uploaded.name }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [formatSize(uploaded.size), " \u00B7 ", uploaded.mimeType] })] })] }), _jsx("button", { type: "button", onClick: reset, className: "text-muted-foreground hover:text-destructive", "aria-label":
|
|
99
|
+
return (_jsxs("div", { className: "flex items-center justify-between gap-3 rounded-md border bg-muted/30 p-3", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(FileIcon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm font-medium", children: uploaded.name }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [formatSize(uploaded.size), " \u00B7 ", uploaded.mimeType] })] })] }), _jsx("button", { type: "button", onClick: reset, className: "text-muted-foreground hover:text-destructive", "aria-label": messages.fileDropzone.removeFileAriaLabel, children: _jsx(X, { className: "h-4 w-4" }) })] }));
|
|
90
100
|
}
|
|
91
|
-
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("button", { type: "button", onClick: () => inputRef.current?.click(), onDrop: handleDrop, onDragOver: handleDragOver, onDragLeave: handleDragLeave, disabled: disabled || isUploading, "data-dragging": isDragging, className: "flex flex-col items-center justify-center gap-2 rounded-md border border-dashed px-4 py-8 text-center transition-colors hover:border-foreground/30 hover:bg-muted/30 data-[dragging=true]:border-primary data-[dragging=true]:bg-primary/5 disabled:cursor-not-allowed disabled:opacity-60", children: isUploading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground" }), _jsx("p", { className: "text-sm text-muted-foreground", children:
|
|
101
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("button", { type: "button", onClick: () => inputRef.current?.click(), onDrop: handleDrop, onDragOver: handleDragOver, onDragLeave: handleDragLeave, disabled: disabled || isUploading, "data-dragging": isDragging, className: "flex flex-col items-center justify-center gap-2 rounded-md border border-dashed px-4 py-8 text-center transition-colors hover:border-foreground/30 hover:bg-muted/30 data-[dragging=true]:border-primary data-[dragging=true]:bg-primary/5 disabled:cursor-not-allowed disabled:opacity-60", children: isUploading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground" }), _jsx("p", { className: "text-sm text-muted-foreground", children: messages.fileDropzone.uploading })] })) : (_jsxs(_Fragment, { children: [_jsx(Upload, { className: "h-6 w-6 text-muted-foreground" }), _jsx("p", { className: "text-sm text-muted-foreground", children: resolvedHelperText }), accept && (_jsxs("p", { className: "text-xs text-muted-foreground", children: [messages.fileDropzone.acceptedPrefix, " ", accept] }))] })) }), _jsx("input", { ref: inputRef, type: "file", className: "hidden", accept: accept, onChange: handleChange, disabled: disabled || isUploading }), error && _jsx("p", { className: "text-xs text-destructive", children: error })] }));
|
|
92
102
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passengers-section.d.ts","sourceRoot":"","sources":["../../src/components/passengers-section.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"passengers-section.d.ts","sourceRoot":"","sources":["../../src/components/passengers-section.tsx"],"names":[],"mappings":"AAeA,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAA;AAIjE,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,aAAa,CAAA;IACnB,gFAAgF;IAChF,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,cAAc,EAAE,CAAA;CAC7B;AAED,eAAO,MAAM,uBAAuB,EAAE,kBAAuC,CAAA;AAE7E,qFAAqF;AACrF,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,aAAuB,GAAG,cAAc,CAElF;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,kBAAkB,CAAA;IACzB,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC7C;;;OAGG;IACH,SAAS,CAAC,EAAE,cAAc,EAAE,CAAA;IAC5B,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;CACF;AAID;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,sBAAsB,2CA4I/F"}
|
|
@@ -2,28 +2,13 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Button, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
|
|
4
4
|
import { Trash2 } from "lucide-react";
|
|
5
|
+
import { useBookingsUiMessagesOrDefault } from "../i18n/provider";
|
|
5
6
|
const ALL_ROLES = ["lead", "adult", "child", "infant"];
|
|
6
7
|
export const emptyPassengerListValue = { passengers: [] };
|
|
7
8
|
/** Factory for a blank row — `role` defaults to `adult` unless the list is empty. */
|
|
8
9
|
export function createBlankPassenger(role = "adult") {
|
|
9
10
|
return { firstName: "", lastName: "", email: "", role, roomUnitId: null };
|
|
10
11
|
}
|
|
11
|
-
const DEFAULT_LABELS = {
|
|
12
|
-
heading: "Passengers",
|
|
13
|
-
addPassenger: "Add passenger",
|
|
14
|
-
firstName: "First name",
|
|
15
|
-
lastName: "Last name",
|
|
16
|
-
email: "Email",
|
|
17
|
-
role: "Role",
|
|
18
|
-
roleLead: "Lead",
|
|
19
|
-
roleAdult: "Adult",
|
|
20
|
-
roleChild: "Child",
|
|
21
|
-
roleInfant: "Infant",
|
|
22
|
-
room: "Room",
|
|
23
|
-
noRoom: "Unassigned",
|
|
24
|
-
remove: "Remove passenger",
|
|
25
|
-
empty: "No passengers yet. Add at least one.",
|
|
26
|
-
};
|
|
27
12
|
const NO_ROOM = "__unassigned__";
|
|
28
13
|
/**
|
|
29
14
|
* Passenger list for booking-create flows. Each row carries name + optional
|
|
@@ -46,7 +31,8 @@ const NO_ROOM = "__unassigned__";
|
|
|
46
31
|
* the submit handler errors if the invariant isn't met.
|
|
47
32
|
*/
|
|
48
33
|
export function PassengersSection({ value, onChange, roomUnits, labels }) {
|
|
49
|
-
const
|
|
34
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
35
|
+
const merged = { ...messages.passengersSection.labels, ...labels };
|
|
50
36
|
const roleLabels = {
|
|
51
37
|
lead: merged.roleLead,
|
|
52
38
|
adult: merged.roleAdult,
|
|
@@ -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":"AAKA,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAA;AAEzE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,mBAAmB,CAAA;IACzB,wEAAwE;IACxE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,wEAAwE;IACxE,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,qDAAqD;IACrD,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC;AAED,eAAO,MAAM,yBAAyB,EAAE,oBASvC,CAAA;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,oBAAoB,CAAA;IAC3B,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAC/C;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,gBAAgB,CAAC,EAAE,MAAM,CAAA;QACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;QAC1B,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;CACF;AAoBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,QAAQ,EACR,MAAM,GACP,EAAE,2BAA2B,2CAoI7B"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Button, Input, Label } from "@voyantjs/ui/components";
|
|
4
|
+
import { useBookingsUiMessagesOrDefault } from "../i18n/provider";
|
|
4
5
|
export const emptyPaymentScheduleValue = {
|
|
5
6
|
mode: "unpaid",
|
|
6
7
|
fullDueDate: null,
|
|
@@ -11,19 +12,6 @@ export const emptyPaymentScheduleValue = {
|
|
|
11
12
|
splitSecondAmountCents: null,
|
|
12
13
|
splitSecondDueDate: null,
|
|
13
14
|
};
|
|
14
|
-
const DEFAULT_LABELS = {
|
|
15
|
-
heading: "Payment schedule",
|
|
16
|
-
modeUnpaid: "Unpaid",
|
|
17
|
-
modeFull: "Full",
|
|
18
|
-
modeAdvance: "Advance",
|
|
19
|
-
modeSplit: "Split",
|
|
20
|
-
dueDate: "Due date",
|
|
21
|
-
amount: "Amount",
|
|
22
|
-
firstInstallment: "First installment",
|
|
23
|
-
secondInstallment: "Second installment",
|
|
24
|
-
preset5050: "50 / 50",
|
|
25
|
-
unpaidHint: "No payment schedule will be created. Operator will invoice manually.",
|
|
26
|
-
};
|
|
27
15
|
/**
|
|
28
16
|
* Converts an `<input type="number">` string value to minor units (cents).
|
|
29
17
|
* Accepts `""` / `NaN` → `null`. Multiplies by 100 and rounds to avoid
|
|
@@ -64,7 +52,8 @@ function centsToMajorString(cents) {
|
|
|
64
52
|
* - `split` → two schedules with `scheduleType: "installment"`.
|
|
65
53
|
*/
|
|
66
54
|
export function PaymentScheduleSection({ value, onChange, totalAmountCents, currency, labels, }) {
|
|
67
|
-
const
|
|
55
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
56
|
+
const merged = { ...messages.paymentScheduleSection.labels, ...labels };
|
|
68
57
|
const set = (patch) => onChange({ ...value, ...patch });
|
|
69
58
|
const currencySuffix = currency ? ` ${currency}` : "";
|
|
70
59
|
const modes = [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"person-picker-section.d.ts","sourceRoot":"","sources":["../../src/components/person-picker-section.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"person-picker-section.d.ts","sourceRoot":"","sources":["../../src/components/person-picker-section.tsx"],"names":[],"mappings":"AAmBA,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,KAAK,CAAA;AAEjD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,gBAAgB,CAAA;IACtB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAA;IAChB,gCAAgC;IAChC,SAAS,EAAE,cAAc,CAAA;IACzB,yCAAyC;IACzC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED,eAAO,MAAM,cAAc,EAAE,cAK5B,CAAA;AAED,eAAO,MAAM,sBAAsB,EAAE,iBAKpC,CAAA;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,iBAAiB,CAAA;IACxB,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAC5C,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,MAAM,CAAC,EAAE;QACP,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,eAAe,CAAC,EAAE,MAAM,CAAA;QACxB,oBAAoB,CAAC,EAAE,MAAM,CAAA;QAC7B,uBAAuB,CAAC,EAAE,MAAM,CAAA;QAChC,uBAAuB,CAAC,EAAE,MAAM,CAAA;QAChC,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,oBAAoB,CAAC,EAAE,MAAM,CAAA;QAC7B,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,mBAAmB,CAAC,EAAE,MAAM,CAAA;QAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,gBAAgB,CAAC,EAAE,MAAM,CAAA;QACzB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,gBAAgB,CAAC,EAAE,MAAM,CAAA;QACzB,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,6BAA6B,CAAC,EAAE,MAAM,CAAA;QACtC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAC1B,CAAA;CACF;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,OAAc,EACd,gBAAuB,EACvB,MAAM,GACP,EAAE,wBAAwB,2CA4J1B"}
|
|
@@ -4,6 +4,7 @@ import { useOrganizations, usePeople } from "@voyantjs/crm-react";
|
|
|
4
4
|
import { Button, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
|
|
5
5
|
import { UserPlus } from "lucide-react";
|
|
6
6
|
import * as React from "react";
|
|
7
|
+
import { useBookingsUiMessagesOrDefault } from "../i18n/provider";
|
|
7
8
|
const ORG_NONE = "__none__";
|
|
8
9
|
export const emptyNewPerson = {
|
|
9
10
|
firstName: "",
|
|
@@ -17,24 +18,6 @@ export const emptyPersonPickerValue = {
|
|
|
17
18
|
newPerson: emptyNewPerson,
|
|
18
19
|
organizationId: null,
|
|
19
20
|
};
|
|
20
|
-
const DEFAULT_LABELS = {
|
|
21
|
-
person: "Person",
|
|
22
|
-
createNewPerson: "Create new",
|
|
23
|
-
selectExistingPerson: "Select existing",
|
|
24
|
-
personSearchPlaceholder: "Search people by name or email...",
|
|
25
|
-
personSelectPlaceholder: "Select a person...",
|
|
26
|
-
firstName: "First Name",
|
|
27
|
-
firstNamePlaceholder: "John",
|
|
28
|
-
lastName: "Last Name",
|
|
29
|
-
lastNamePlaceholder: "Smith",
|
|
30
|
-
email: "Email",
|
|
31
|
-
emailPlaceholder: "john@example.com",
|
|
32
|
-
phone: "Phone",
|
|
33
|
-
phonePlaceholder: "+44 7911 123456",
|
|
34
|
-
organization: "Organization (optional)",
|
|
35
|
-
organizationSearchPlaceholder: "Search organizations...",
|
|
36
|
-
organizationNone: "No organization",
|
|
37
|
-
};
|
|
38
21
|
/**
|
|
39
22
|
* Person picker with inline-create + optional organization attachment.
|
|
40
23
|
*
|
|
@@ -47,7 +30,8 @@ const DEFAULT_LABELS = {
|
|
|
47
30
|
export function PersonPickerSection({ value, onChange, enabled = true, showOrganization = true, labels, }) {
|
|
48
31
|
const [personSearch, setPersonSearch] = React.useState("");
|
|
49
32
|
const [orgSearch, setOrgSearch] = React.useState("");
|
|
50
|
-
const
|
|
33
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
34
|
+
const merged = { ...messages.personPickerSection.labels, ...labels };
|
|
51
35
|
const { data: peopleData } = usePeople({
|
|
52
36
|
search: personSearch || undefined,
|
|
53
37
|
limit: 20,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"price-breakdown-section.d.ts","sourceRoot":"","sources":["../../src/components/price-breakdown-section.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"price-breakdown-section.d.ts","sourceRoot":"","sources":["../../src/components/price-breakdown-section.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,4EAA4E;IAC5E,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,4DAA4D;IAC5D,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;;;OAGG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,uEAAuE;IACvE,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACtC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;CACF;AA0BD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,QAAQ,EACR,cAAc,EACd,SAAS,EACT,MAAM,GACP,EAAE,0BAA0B,kDAkL5B"}
|
|
@@ -3,20 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { usePricingPreview } from "@voyantjs/bookings-react";
|
|
4
4
|
import { Label } from "@voyantjs/ui/components";
|
|
5
5
|
import * as React from "react";
|
|
6
|
-
|
|
7
|
-
heading: "Price breakdown",
|
|
8
|
-
total: "Total",
|
|
9
|
-
onRequest: "On request",
|
|
10
|
-
groupRate: "group rate",
|
|
11
|
-
empty: "Pick units above to see the breakdown.",
|
|
12
|
-
noPricing: "No pricing catalog available for this product.",
|
|
13
|
-
};
|
|
14
|
-
function formatCents(cents, currency) {
|
|
15
|
-
if (cents === null)
|
|
16
|
-
return "";
|
|
17
|
-
const major = (cents / 100).toFixed(2);
|
|
18
|
-
return currency ? `${major} ${currency}` : major;
|
|
19
|
-
}
|
|
6
|
+
import { useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault } from "../i18n/provider";
|
|
20
7
|
/**
|
|
21
8
|
* Picks the tier whose quantity range contains `qty`. Tiers are expected
|
|
22
9
|
* oldest-to-newest, `minQuantity`-ascending. Ties are broken by first-match —
|
|
@@ -25,7 +12,9 @@ function formatCents(cents, currency) {
|
|
|
25
12
|
*/
|
|
26
13
|
function matchTier(tiers, qty) {
|
|
27
14
|
for (const tier of tiers) {
|
|
28
|
-
if (qty >= tier.minQuantity &&
|
|
15
|
+
if (qty >= tier.minQuantity &&
|
|
16
|
+
(tier.maxQuantity === null || qty <= tier.maxQuantity) // i18n-literal-ok numeric bounds
|
|
17
|
+
) {
|
|
29
18
|
return tier;
|
|
30
19
|
}
|
|
31
20
|
}
|
|
@@ -44,7 +33,9 @@ function matchTier(tiers, qty) {
|
|
|
44
33
|
* - `on_request` / anything else — render "On request"; total excludes it.
|
|
45
34
|
*/
|
|
46
35
|
export function PriceBreakdownSection({ productId, optionId, unitQuantities, catalogId, labels, }) {
|
|
47
|
-
const
|
|
36
|
+
const { formatCurrency, formatNumber } = useBookingsUiI18nOrDefault();
|
|
37
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
38
|
+
const merged = { ...messages.priceBreakdownSection.labels, ...labels };
|
|
48
39
|
const preview = usePricingPreview({
|
|
49
40
|
productId: productId ?? "",
|
|
50
41
|
optionId: optionId ?? null,
|
|
@@ -53,6 +44,12 @@ export function PriceBreakdownSection({ productId, optionId, unitQuantities, cat
|
|
|
53
44
|
});
|
|
54
45
|
const snapshot = preview.data?.data;
|
|
55
46
|
const currency = snapshot?.catalog.currencyCode ?? null;
|
|
47
|
+
const formatAmount = React.useCallback((cents) => currency
|
|
48
|
+
? formatCurrency(cents / 100, currency)
|
|
49
|
+
: formatNumber(cents / 100, {
|
|
50
|
+
minimumFractionDigits: 2,
|
|
51
|
+
maximumFractionDigits: 2,
|
|
52
|
+
}), [currency, formatCurrency, formatNumber]);
|
|
56
53
|
const { lines, total } = React.useMemo(() => {
|
|
57
54
|
const out = [];
|
|
58
55
|
let runningTotal = 0;
|
|
@@ -159,7 +156,7 @@ export function PriceBreakdownSection({ productId, optionId, unitQuantities, cat
|
|
|
159
156
|
if (lines.length === 0) {
|
|
160
157
|
return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Label, { children: merged.heading }), _jsx("p", { className: "text-xs text-muted-foreground", children: merged.empty })] }));
|
|
161
158
|
}
|
|
162
|
-
return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Label, { children: merged.heading }), _jsx("div", { className: "flex flex-col gap-1.5", children: lines.map((line) => (_jsxs("div", { className: "flex items-baseline justify-between text-sm", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsxs("span", { className: "tabular-nums", children: [line.quantity, "
|
|
159
|
+
return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Label, { children: merged.heading }), _jsx("div", { className: "flex flex-col gap-1.5", children: lines.map((line) => (_jsxs("div", { className: "flex items-baseline justify-between text-sm", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsxs("span", { className: "tabular-nums", children: [formatNumber(line.quantity), "x"] }), _jsx("span", { children: line.label }), line.tierLabel ? (_jsxs("span", { className: "text-xs text-muted-foreground", children: ["\u00B7 ", line.tierLabel] })) : null] }), _jsx("div", { className: "tabular-nums", children: line.totalAmountCents === null
|
|
163
160
|
? merged.onRequest
|
|
164
|
-
:
|
|
161
|
+
: formatAmount(line.totalAmountCents) })] }, line.unitId))) }), _jsxs("div", { className: "mt-1 flex items-baseline justify-between border-t pt-2 text-sm font-medium", children: [_jsx("span", { children: merged.total }), _jsx("span", { className: "tabular-nums", children: total === null ? merged.onRequest : formatAmount(total) })] })] }));
|
|
165
162
|
}
|