@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.
Files changed (84) hide show
  1. package/README.md +11 -0
  2. package/dist/components/booking-activity-timeline.d.ts.map +1 -1
  3. package/dist/components/booking-activity-timeline.js +34 -14
  4. package/dist/components/booking-cancellation-dialog.d.ts.map +1 -1
  5. package/dist/components/booking-cancellation-dialog.js +15 -16
  6. package/dist/components/booking-create-dialog.d.ts.map +1 -1
  7. package/dist/components/booking-create-dialog.js +77 -13
  8. package/dist/components/booking-dialog.d.ts.map +1 -1
  9. package/dist/components/booking-dialog.js +27 -21
  10. package/dist/components/booking-document-dialog.d.ts.map +1 -1
  11. package/dist/components/booking-document-dialog.js +27 -13
  12. package/dist/components/booking-document-list.d.ts.map +1 -1
  13. package/dist/components/booking-document-list.js +9 -4
  14. package/dist/components/booking-group-link-dialog.d.ts.map +1 -1
  15. package/dist/components/booking-group-link-dialog.js +17 -6
  16. package/dist/components/booking-group-section.d.ts.map +1 -1
  17. package/dist/components/booking-group-section.js +8 -2
  18. package/dist/components/booking-guarantee-dialog.d.ts.map +1 -1
  19. package/dist/components/booking-guarantee-dialog.js +30 -14
  20. package/dist/components/booking-guarantee-list.d.ts.map +1 -1
  21. package/dist/components/booking-guarantee-list.js +11 -8
  22. package/dist/components/booking-item-dialog.d.ts.map +1 -1
  23. package/dist/components/booking-item-dialog.js +34 -20
  24. package/dist/components/booking-item-list.d.ts.map +1 -1
  25. package/dist/components/booking-item-list.js +10 -9
  26. package/dist/components/booking-item-travelers.d.ts.map +1 -1
  27. package/dist/components/booking-item-travelers.js +9 -4
  28. package/dist/components/booking-list.d.ts.map +1 -1
  29. package/dist/components/booking-list.js +17 -8
  30. package/dist/components/booking-notes.d.ts.map +1 -1
  31. package/dist/components/booking-notes.js +5 -2
  32. package/dist/components/booking-payment-schedule-dialog.d.ts.map +1 -1
  33. package/dist/components/booking-payment-schedule-dialog.js +31 -12
  34. package/dist/components/booking-payment-schedule-list.d.ts.map +1 -1
  35. package/dist/components/booking-payment-schedule-list.js +7 -6
  36. package/dist/components/booking-payments-summary.d.ts.map +1 -1
  37. package/dist/components/booking-payments-summary.js +7 -4
  38. package/dist/components/file-dropzone.d.ts.map +1 -1
  39. package/dist/components/file-dropzone.js +25 -15
  40. package/dist/components/passengers-section.d.ts.map +1 -1
  41. package/dist/components/passengers-section.js +3 -17
  42. package/dist/components/payment-schedule-section.d.ts.map +1 -1
  43. package/dist/components/payment-schedule-section.js +3 -14
  44. package/dist/components/person-picker-section.d.ts.map +1 -1
  45. package/dist/components/person-picker-section.js +3 -19
  46. package/dist/components/price-breakdown-section.d.ts.map +1 -1
  47. package/dist/components/price-breakdown-section.js +15 -18
  48. package/dist/components/product-picker-section.d.ts.map +1 -1
  49. package/dist/components/product-picker-section.js +3 -8
  50. package/dist/components/rooms-stepper-section.d.ts.map +1 -1
  51. package/dist/components/rooms-stepper-section.js +4 -9
  52. package/dist/components/shared-room-section.d.ts.map +1 -1
  53. package/dist/components/shared-room-section.js +3 -9
  54. package/dist/components/status-change-dialog.d.ts.map +1 -1
  55. package/dist/components/status-change-dialog.js +6 -1
  56. package/dist/components/supplier-status-dialog.d.ts.map +1 -1
  57. package/dist/components/supplier-status-dialog.js +31 -15
  58. package/dist/components/supplier-status-list.d.ts.map +1 -1
  59. package/dist/components/supplier-status-list.js +10 -2
  60. package/dist/components/traveler-dialog.d.ts.map +1 -1
  61. package/dist/components/traveler-dialog.js +17 -8
  62. package/dist/components/traveler-list.d.ts.map +1 -1
  63. package/dist/components/traveler-list.js +5 -3
  64. package/dist/components/voucher-picker-section.d.ts.map +1 -1
  65. package/dist/components/voucher-picker-section.js +11 -26
  66. package/dist/i18n/en.d.ts +797 -0
  67. package/dist/i18n/en.d.ts.map +1 -0
  68. package/dist/i18n/en.js +796 -0
  69. package/dist/i18n/index.d.ts +5 -0
  70. package/dist/i18n/index.d.ts.map +1 -0
  71. package/dist/i18n/index.js +3 -0
  72. package/dist/i18n/messages.d.ts +684 -0
  73. package/dist/i18n/messages.d.ts.map +1 -0
  74. package/dist/i18n/messages.js +1 -0
  75. package/dist/i18n/provider.d.ts +1617 -0
  76. package/dist/i18n/provider.d.ts.map +1 -0
  77. package/dist/i18n/provider.js +45 -0
  78. package/dist/i18n/ro.d.ts +797 -0
  79. package/dist/i18n/ro.d.ts.map +1 -0
  80. package/dist/i18n/ro.js +796 -0
  81. package/dist/index.d.ts +1 -0
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +1 -0
  84. 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, formatBookingStatus, useBookings, } from "@voyantjs/bookings-react";
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: "Search bookings\u2026", value: search, onChange: (event) => {
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" }), "New booking"] }) })] }), _jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Booking #" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { children: "Sell Amount" }), _jsx(TableHead, { children: "Pax" }), _jsx(TableHead, { children: "Start Date" })] }) }), _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: "Failed to load bookings." }) })) : bookings.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, className: "h-24 text-center text-sm text-muted-foreground", children: "No bookings found." }) })) : (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: formatBookingStatus(booking.status) }) }), _jsx(TableCell, { children: formatAmount(booking.sellAmountCents, 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: [_jsxs("span", { children: ["Showing ", bookings.length, " of ", total] }), _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" }), _jsxs("span", { children: ["Page ", page, " / ", pageCount] }), _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) => {
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":"AAOA,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,EAAE,iBAAiB,2CA+C5D"}
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: "Notes" }) }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex gap-2", children: [_jsx(Textarea, { placeholder: "Add a note...", value: content, onChange: (event) => setContent(event.target.value), className: "min-h-[80px]" }), _jsx(Button, { className: "self-end", disabled: !content.trim() || mutation.isPending, onClick: async () => {
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" }) : "Add" })] }), notes.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: "No notes yet." })) : (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: new Date(note.createdAt).toLocaleString() })] }, note.id))))] })] }));
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;AAyChC,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,2CA4KnC"}
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 scheduleFormSchema = z.object({
15
- scheduleType: z.enum(scheduleTypes).default("balance"),
16
- status: z.enum(scheduleStatuses).default("pending"),
17
- dueDate: z.string().min(1, "Due date is required"),
18
- currency: z.string().min(3).max(3).default("EUR"),
19
- amountCents: z.coerce.number().int().min(0, "Amount is required"),
20
- notes: z.string().optional().nullable(),
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: "EUR",
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 ? "Edit Payment Schedule" : "Add Payment Schedule" }) }), _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: "Type" }), _jsxs(Select, { items: scheduleTypes.map((t) => ({ label: t.replace("_", " "), value: t })), 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: t.replace("_", " ") }, t))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Status" }), _jsxs(Select, { items: scheduleStatuses.map((s) => ({ label: s.replace("_", " "), value: s })), 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: s.replace("_", " ") }, s))) })] })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Due Date" }), _jsx(DatePicker, { value: form.watch("dueDate") || null, onChange: (next) => form.setValue("dueDate", next ?? "", {
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: "Select due date", 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: "Currency" }), _jsx(CurrencyCombobox, { value: form.watch("currency") || null, onChange: (next) => form.setValue("currency", next ?? "EUR", {
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: "Amount (cents)" }), _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: "Notes" }), _jsx(Textarea, { ...form.register("notes"), placeholder: "Payment notes..." })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), children: "Cancel" }), _jsxs(Button, { type: "submit", size: "sm", disabled: isSubmitting, children: [isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), isEditing ? "Save Changes" : "Add Schedule"] })] })] })] }) }));
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":"AA0BA,MAAM,WAAW,+BAA+B;IAC9C,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,0BAA0B,CAAC,EAAE,SAAS,EAAE,EAAE,+BAA+B,2CAgHxF"}
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" }), "Payment Schedule"] }), _jsxs(Button, { size: "sm", onClick: () => {
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" }), "Add Schedule"] })] }), _jsx(CardContent, { children: schedules.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: "No payment schedules yet." })) : (_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: "Type" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Status" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Due Date" }), _jsx("th", { className: "p-2 text-right font-medium", children: "Amount" }), _jsx("th", { className: "p-2 text-left font-medium", children: "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 capitalize", children: schedule.scheduleType.replace("_", " ") }), _jsx("td", { className: "p-2", children: _jsx(Badge, { variant: statusVariant[schedule.status] ?? "secondary", className: "capitalize", children: schedule.status.replace("_", " ") }) }), _jsx("td", { className: "p-2", children: schedule.dueDate }), _jsx("td", { className: "p-2 text-right font-mono", children: formatAmount(schedule.amountCents, schedule.currency) }), _jsx("td", { className: "max-w-[200px] truncate p-2 text-muted-foreground", children: schedule.notes ?? "-" }), _jsx("td", { className: "p-2", children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => {
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("Delete this payment schedule?")) {
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":"AAiBA,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,sBAAsB,CAAC,EAAE,SAAS,EAAE,EAAE,2BAA2B,2CA0DhF"}
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
- function formatAmount(cents, currency) {
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" }), "Payments"] }) }), _jsx(CardContent, { children: payments.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: "No payments recorded." })) : (_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: "Invoice" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Method" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Status" }), _jsx("th", { className: "p-2 text-right font-medium", children: "Amount" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Date" }), _jsx("th", { className: "p-2 text-left font-medium", children: "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 capitalize", children: payment.paymentMethod.replace(/_/g, " ") }), _jsx("td", { className: "p-2", children: _jsx(Badge, { variant: statusVariant[payment.status] ?? "secondary", className: "capitalize", children: payment.status }) }), _jsx("td", { className: "p-2 text-right font-mono", children: formatAmount(payment.amountCents, payment.currency) }), _jsx("td", { className: "p-2", children: new Date(payment.paymentDate).toLocaleDateString() }), _jsx("td", { className: "max-w-[150px] truncate p-2 font-mono text-xs", children: payment.referenceNumber ?? "-" })] }, payment.id))) })] }) })) })] }));
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":"AAKA,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;AAQD,wBAAgB,YAAY,CAAC,EAC3B,SAA6B,EAC7B,MAAM,EACN,OAAO,EACP,UAAU,EACV,OAAO,EACP,UAA4D,EAC5D,QAAQ,GACT,EAAE,iBAAiB,2CAgJnB"}
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
- function formatSize(bytes) {
6
- if (bytes < 1024)
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(`File too large (max ${formatSize(maxSize)})`);
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 || `Upload failed (${res.status})`);
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 : "Upload failed");
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": "Remove file", children: _jsx(X, { className: "h-4 w-4" }) })] }));
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: "Uploading..." })] })) : (_jsxs(_Fragment, { children: [_jsx(Upload, { className: "h-6 w-6 text-muted-foreground" }), _jsx("p", { className: "text-sm text-muted-foreground", children: helperText }), accept && _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Accepted: ", 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 })] }));
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":"AAcA,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;AAqBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,sBAAsB,2CA2I/F"}
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 merged = { ...DEFAULT_LABELS, ...labels };
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":"AAIA,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;AAkCD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,QAAQ,EACR,MAAM,GACP,EAAE,2BAA2B,2CAmI7B"}
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 merged = { ...DEFAULT_LABELS, ...labels };
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":"AAkBA,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;AAqBD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,OAAc,EACd,gBAAuB,EACvB,MAAM,GACP,EAAE,wBAAwB,2CA2J1B"}
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 merged = { ...DEFAULT_LABELS, ...labels };
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":"AAMA,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;AAsCD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,QAAQ,EACR,cAAc,EACd,SAAS,EACT,MAAM,GACP,EAAE,0BAA0B,kDAsK5B"}
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
- const DEFAULT_LABELS = {
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 && (tier.maxQuantity === null || qty <= tier.maxQuantity)) {
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 merged = { ...DEFAULT_LABELS, ...labels };
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, "\u00D7"] }), _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
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
- : formatCents(line.totalAmountCents, currency) })] }, 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 : formatCents(total, currency) })] })] }));
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
  }