@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
|
@@ -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
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"product-picker-section.d.ts","sourceRoot":"","sources":["../../src/components/product-picker-section.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"product-picker-section.d.ts","sourceRoot":"","sources":["../../src/components/product-picker-section.tsx"],"names":[],"mappings":"AAiBA,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,uFAAuF;IACvF,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,kBAAkB,CAAA;IACzB,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC7C,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,qGAAqG;IACrG,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,wBAAwB,CAAC,EAAE,MAAM,CAAA;QACjC,wBAAwB,CAAC,EAAE,MAAM,CAAA;QACjC,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;CACF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,QAAQ,EACR,OAAc,EACd,WAAmB,EACnB,MAAM,GACP,EAAE,yBAAyB,2CAkF3B"}
|
|
@@ -3,14 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import { useProductOptions, useProducts } from "@voyantjs/products-react";
|
|
4
4
|
import { Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
|
|
5
5
|
import * as React from "react";
|
|
6
|
+
import { useBookingsUiMessagesOrDefault } from "../i18n/provider";
|
|
6
7
|
const OPTION_NONE = "__none__";
|
|
7
|
-
const DEFAULT_LABELS = {
|
|
8
|
-
product: "Product",
|
|
9
|
-
productSearchPlaceholder: "Search products...",
|
|
10
|
-
productSelectPlaceholder: "Select a product...",
|
|
11
|
-
option: "Option",
|
|
12
|
-
optionNone: "No specific option",
|
|
13
|
-
};
|
|
14
8
|
/**
|
|
15
9
|
* Controlled product + option picker. Splits `value` + `onChange` so apps can
|
|
16
10
|
* replace the whole section (e.g., with a typeahead against a custom catalog)
|
|
@@ -18,7 +12,8 @@ const DEFAULT_LABELS = {
|
|
|
18
12
|
*/
|
|
19
13
|
export function ProductPickerSection({ value, onChange, enabled = true, lockProduct = false, labels, }) {
|
|
20
14
|
const [productSearch, setProductSearch] = React.useState("");
|
|
21
|
-
const
|
|
15
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
16
|
+
const merged = { ...messages.productPickerSection.labels, ...labels };
|
|
22
17
|
const { data: productsData } = useProducts({
|
|
23
18
|
search: productSearch || undefined,
|
|
24
19
|
limit: 20,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rooms-stepper-section.d.ts","sourceRoot":"","sources":["../../src/components/rooms-stepper-section.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rooms-stepper-section.d.ts","sourceRoot":"","sources":["../../src/components/rooms-stepper-section.tsx"],"names":[],"mappings":"AAOA,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACnC;AAED,eAAO,MAAM,sBAAsB,EAAE,iBAAsC,CAAA;AAE3E,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,iBAAiB,CAAA;IACxB,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAC5C;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;CACF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,MAAM,EACN,OAAc,EACd,MAAM,GACP,EAAE,wBAAwB,2CAoF1B"}
|
|
@@ -3,14 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useSlotUnitAvailability } from "@voyantjs/availability-react";
|
|
4
4
|
import { Button, Label } from "@voyantjs/ui/components";
|
|
5
5
|
import { Minus, Plus } from "lucide-react";
|
|
6
|
+
import { useBookingsUiMessagesOrDefault } from "../i18n/provider";
|
|
6
7
|
export const emptyRoomsStepperValue = { quantities: {} };
|
|
7
|
-
const DEFAULT_LABELS = {
|
|
8
|
-
heading: "Rooms",
|
|
9
|
-
noSlot: "Pick a departure first to see available rooms.",
|
|
10
|
-
noUnits: "This departure has no per-unit availability configured.",
|
|
11
|
-
remaining: "left",
|
|
12
|
-
unlimited: "unlimited",
|
|
13
|
-
};
|
|
14
8
|
/**
|
|
15
9
|
* Rooms / per-unit stepper for booking-create flows. Drives
|
|
16
10
|
* `GET /v1/availability/slots/:id/unit-availability` from #235 so the
|
|
@@ -32,7 +26,8 @@ const DEFAULT_LABELS = {
|
|
|
32
26
|
* would 409 at insert time.
|
|
33
27
|
*/
|
|
34
28
|
export function RoomsStepperSection({ value, onChange, slotId, enabled = true, labels, }) {
|
|
35
|
-
const
|
|
29
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
30
|
+
const merged = { ...messages.roomsStepperSection.labels, ...labels };
|
|
36
31
|
const availability = useSlotUnitAvailability({ slotId, enabled: enabled && Boolean(slotId) });
|
|
37
32
|
const units = availability.data?.data ?? [];
|
|
38
33
|
if (!slotId) {
|
|
@@ -55,6 +50,6 @@ export function RoomsStepperSection({ value, onChange, slotId, enabled = true, l
|
|
|
55
50
|
const qty = value.quantities[unit.optionUnitId] ?? 0;
|
|
56
51
|
const remainingLabel = unit.remaining === null ? merged.unlimited : `${unit.remaining} ${merged.remaining}`;
|
|
57
52
|
const atMax = unit.remaining !== null && qty >= unit.remaining;
|
|
58
|
-
return (_jsxs("div", { className: "flex items-center gap-3 rounded-md border px-3 py-2", children: [_jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-sm font-medium", children: unit.unitName }), _jsx("div", { className: "text-xs text-muted-foreground", children: remainingLabel })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-7 w-7 p-0", onClick: () => setQuantity(unit.optionUnitId, Math.max(0, qty - 1)), disabled: qty <= 0, "aria-label":
|
|
53
|
+
return (_jsxs("div", { className: "flex items-center gap-3 rounded-md border px-3 py-2", children: [_jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-sm font-medium", children: unit.unitName }), _jsx("div", { className: "text-xs text-muted-foreground", children: remainingLabel })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-7 w-7 p-0", onClick: () => setQuantity(unit.optionUnitId, Math.max(0, qty - 1)), disabled: qty <= 0, "aria-label": `${merged.decreaseUnitPrefix} ${unit.unitName}`, children: _jsx(Minus, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "min-w-[1.5rem] text-center text-sm tabular-nums", children: qty }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-7 w-7 p-0", onClick: () => setQuantity(unit.optionUnitId, qty + 1), disabled: atMax, "aria-label": `${merged.increaseUnitPrefix} ${unit.unitName}`, children: _jsx(Plus, { className: "h-3.5 w-3.5" }) })] })] }, unit.optionUnitId));
|
|
59
54
|
}) })] }));
|
|
60
55
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared-room-section.d.ts","sourceRoot":"","sources":["../../src/components/shared-room-section.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shared-room-section.d.ts","sourceRoot":"","sources":["../../src/components/shared-room-section.tsx"],"names":[],"mappings":"AAiBA,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,MAAM,CAAA;AAE9C,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,cAAc,CAAA;IACpB,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,eAAO,MAAM,oBAAoB,EAAE,eAIlC,CAAA;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,eAAe,CAAA;IACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAA;IAC1C;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE;QACP,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,iBAAiB,CAAC,EAAE,MAAM,CAAA;QAC1B,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;CACF;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,EACR,SAAS,EACT,OAAc,EACd,MAAM,GACP,EAAE,sBAAsB,2CAoFxB"}
|
|
@@ -2,20 +2,13 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useBookingGroups } from "@voyantjs/bookings-react";
|
|
4
4
|
import { Button, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
|
|
5
|
+
import { useBookingsUiMessagesOrDefault } from "../i18n/provider";
|
|
5
6
|
const GROUP_NONE = "__none__";
|
|
6
7
|
export const emptySharedRoomValue = {
|
|
7
8
|
enabled: false,
|
|
8
9
|
mode: "create",
|
|
9
10
|
groupId: "",
|
|
10
11
|
};
|
|
11
|
-
const DEFAULT_LABELS = {
|
|
12
|
-
toggle: "Link to a shared-room group",
|
|
13
|
-
createMode: "Create new group",
|
|
14
|
-
joinMode: "Join existing",
|
|
15
|
-
selectPlaceholder: "Select a group...",
|
|
16
|
-
noGroups: "No existing groups for this product",
|
|
17
|
-
createHint: "A new group will be created with this booking as the primary member.",
|
|
18
|
-
};
|
|
19
12
|
/**
|
|
20
13
|
* Shared-room (partaj) attachment section. Operators use it to either create a
|
|
21
14
|
* new `booking_groups` row at booking-create time or join an existing group
|
|
@@ -26,7 +19,8 @@ const DEFAULT_LABELS = {
|
|
|
26
19
|
* insert (we need the new booking id to attach).
|
|
27
20
|
*/
|
|
28
21
|
export function SharedRoomSection({ value, onChange, productId, enabled = true, labels, }) {
|
|
29
|
-
const
|
|
22
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
23
|
+
const merged = { ...messages.sharedRoomSection.labels, ...labels };
|
|
30
24
|
const { data: groupsData } = useBookingGroups({
|
|
31
25
|
productId: productId || undefined,
|
|
32
26
|
limit: 50,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status-change-dialog.d.ts","sourceRoot":"","sources":["../../src/components/status-change-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,EAInB,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"status-change-dialog.d.ts","sourceRoot":"","sources":["../../src/components/status-change-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,EAInB,MAAM,0BAA0B,CAAA;AAgCjC,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAA;IACtC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,aAAa,EACb,SAAS,GACV,EAAE,uBAAuB,2CA+FzB"}
|
|
@@ -7,12 +7,14 @@ import { Loader2 } from "lucide-react";
|
|
|
7
7
|
import { useEffect } from "react";
|
|
8
8
|
import { useForm } from "react-hook-form";
|
|
9
9
|
import { z } from "zod/v4";
|
|
10
|
+
import { useBookingsUiMessagesOrDefault } from "../i18n/provider";
|
|
10
11
|
const statusChangeFormSchema = z.object({
|
|
11
12
|
status: bookingStatusSchema,
|
|
12
13
|
note: z.string().optional().nullable(),
|
|
13
14
|
});
|
|
14
15
|
export function StatusChangeDialog({ open, onOpenChange, bookingId, currentStatus, onSuccess, }) {
|
|
15
16
|
const mutation = useBookingStatusMutation(bookingId);
|
|
17
|
+
const messages = useBookingsUiMessagesOrDefault();
|
|
16
18
|
const form = useForm({
|
|
17
19
|
resolver: zodResolver(statusChangeFormSchema),
|
|
18
20
|
defaultValues: {
|
|
@@ -37,5 +39,8 @@ export function StatusChangeDialog({ open, onOpenChange, bookingId, currentStatu
|
|
|
37
39
|
onOpenChange(false);
|
|
38
40
|
onSuccess?.();
|
|
39
41
|
};
|
|
40
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children:
|
|
42
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: messages.statusChangeDialog.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: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.statusChangeDialog.fields.status }), _jsxs(Select, { value: form.watch("status"), onValueChange: (value) => form.setValue("status", value), items: bookingStatusOptions.map((status) => ({
|
|
43
|
+
...status,
|
|
44
|
+
label: messages.common.bookingStatusLabels[status.value],
|
|
45
|
+
})), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: bookingStatusOptions.map((status) => (_jsx(SelectItem, { value: status.value, children: messages.common.bookingStatusLabels[status.value] }, status.value))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.statusChangeDialog.fields.note }), _jsx(Textarea, { ...form.register("note"), placeholder: messages.statusChangeDialog.placeholders.note })] })] }), _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: mutation.isPending, children: [mutation.isPending && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), messages.statusChangeDialog.actions.updateStatus] })] })] })] }) }));
|
|
41
46
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supplier-status-dialog.d.ts","sourceRoot":"","sources":["../../src/components/supplier-status-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,2BAA2B,EAEjC,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"supplier-status-dialog.d.ts","sourceRoot":"","sources":["../../src/components/supplier-status-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,2BAA2B,EAEjC,MAAM,0BAA0B,CAAA;AA6CjC,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,CAAC,EAAE,2BAA2B,CAAA;IAC5C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAUD,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,cAAc,EACd,SAAS,GACV,EAAE,yBAAyB,2CAoK3B"}
|