@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
package/README.md CHANGED
@@ -11,3 +11,14 @@ pnpm add @voyantjs/bookings-ui @voyantjs/bookings-react @voyantjs/ui @tanstack/r
11
11
  `@voyantjs/ui` provides the design-system primitives. `@voyantjs/bookings-react` provides the data-layer hooks. Both are required peers.
12
12
 
13
13
  All components accept a `className` prop and merge it with `cn()`. Wrap or compose to extend; use the registry copy-paste path (`npx shadcn add @voyant/...`) for components you want to fork outright.
14
+
15
+ ## I18n
16
+
17
+ Components render English by default. To localize them, wrap your UI in
18
+ `BookingsUiMessagesProvider` and import only the locales your app supports.
19
+
20
+ ```tsx
21
+ import { BookingsUiMessagesProvider } from "@voyantjs/bookings-ui"
22
+ import { bookingsUiEn } from "@voyantjs/bookings-ui/i18n/en"
23
+ import { bookingsUiRo } from "@voyantjs/bookings-ui/i18n/ro"
24
+ ```
@@ -1 +1 @@
1
- {"version":3,"file":"booking-activity-timeline.d.ts","sourceRoot":"","sources":["../../src/components/booking-activity-timeline.tsx"],"names":[],"mappings":"AAmBA,MAAM,WAAW,4BAA4B;IAC3C,SAAS,EAAE,MAAM,CAAA;CAClB;AA+CD,wBAAgB,uBAAuB,CAAC,EAAE,SAAS,EAAE,EAAE,4BAA4B,2CAqFlF"}
1
+ {"version":3,"file":"booking-activity-timeline.d.ts","sourceRoot":"","sources":["../../src/components/booking-activity-timeline.tsx"],"names":[],"mappings":"AAyBA,MAAM,WAAW,4BAA4B;IAC3C,SAAS,EAAE,MAAM,CAAA;CAClB;AAyCD,wBAAgB,uBAAuB,CAAC,EAAE,SAAS,EAAE,EAAE,4BAA4B,2CAgHlF"}
@@ -5,6 +5,7 @@ import { usePublicBookingPayments } from "@voyantjs/finance-react";
5
5
  import { Badge, Button, Card, CardContent, CardHeader, CardTitle } from "@voyantjs/ui/components";
6
6
  import { Activity, Clock, CreditCard, ExternalLink, FileText, Pencil, Plus, RefreshCw, UserPlus, } from "lucide-react";
7
7
  import * as React from "react";
8
+ import { formatMessage, useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault, } from "../i18n/provider";
8
9
  const activityIcons = {
9
10
  booking_created: Plus,
10
11
  booking_reserved: Plus,
@@ -22,11 +23,6 @@ const activityIcons = {
22
23
  passenger_update: UserPlus,
23
24
  note_added: Pencil,
24
25
  };
25
- const sourceLabel = {
26
- activity: "Activity",
27
- document: "Document",
28
- payment: "Payment",
29
- };
30
26
  const sourceVariant = {
31
27
  activity: "outline",
32
28
  document: "secondary",
@@ -37,13 +33,21 @@ export function BookingActivityTimeline({ bookingId }) {
37
33
  const { data: activityData } = useBookingActivity(bookingId);
38
34
  const { data: documentsData } = useBookingTravelerDocuments(bookingId);
39
35
  const { data: paymentsData } = usePublicBookingPayments(bookingId);
36
+ const { formatNumber } = useBookingsUiI18nOrDefault();
37
+ const messages = useBookingsUiMessagesOrDefault();
38
+ const sourceLabel = {
39
+ activity: messages.bookingActivityTimeline.sourceLabels.activity,
40
+ document: messages.bookingActivityTimeline.sourceLabels.document,
41
+ payment: messages.bookingActivityTimeline.sourceLabels.payment,
42
+ };
40
43
  const events = React.useMemo(() => {
41
44
  const merged = [];
42
45
  for (const entry of activityData?.data ?? []) {
43
46
  merged.push({
44
47
  id: `activity:${entry.id}`,
45
48
  source: "activity",
46
- title: entry.description,
49
+ title: messages.bookingActivityTimeline.activityTitles[entry.activityType] ?? entry.description,
50
+ description: entry.description,
47
51
  actorId: entry.actorId,
48
52
  timestamp: entry.createdAt,
49
53
  icon: activityIcons[entry.activityType] ?? Activity,
@@ -53,31 +57,47 @@ export function BookingActivityTimeline({ bookingId }) {
53
57
  merged.push({
54
58
  id: `document:${doc.id}`,
55
59
  source: "document",
56
- title: `${doc.type.replace(/_/g, " ")} uploaded`,
60
+ title: `${doc.type.replace(/_/g, " ")} ${messages.bookingActivityTimeline.documentUploadedSuffix}`,
57
61
  description: doc.fileName,
58
62
  timestamp: doc.createdAt,
59
63
  icon: FileText,
60
- link: { href: doc.fileUrl, label: "View file" },
64
+ link: { href: doc.fileUrl, label: messages.bookingActivityTimeline.viewFile },
61
65
  });
62
66
  }
63
67
  for (const payment of paymentsData?.data?.payments ?? []) {
68
+ const status = messages.bookingPaymentsSummary.paymentStatusLabels[payment.status] ?? payment.status;
69
+ const amount = `${formatNumber(payment.amountCents / 100, {
70
+ minimumFractionDigits: 2,
71
+ maximumFractionDigits: 2,
72
+ })} ${payment.currency}`;
73
+ const method = messages.bookingPaymentsSummary.paymentMethodLabels[payment.paymentMethod] ?? payment.paymentMethod;
64
74
  merged.push({
65
75
  id: `payment:${payment.id}`,
66
76
  source: "payment",
67
- title: `Payment ${payment.status} — ${(payment.amountCents / 100).toFixed(2)} ${payment.currency}`,
68
- description: `Invoice ${payment.invoiceNumber} · ${payment.paymentMethod.replace(/_/g, " ")}`,
77
+ title: formatMessage(messages.bookingActivityTimeline.paymentTitle, { status, amount }),
78
+ description: formatMessage(messages.bookingActivityTimeline.paymentDescription, {
79
+ invoice: payment.invoiceNumber,
80
+ method,
81
+ }),
69
82
  timestamp: payment.paymentDate,
70
83
  icon: CreditCard,
71
84
  });
72
85
  }
73
86
  merged.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
74
87
  return merged;
75
- }, [activityData, documentsData, paymentsData]);
88
+ }, [activityData, documentsData, formatNumber, messages, paymentsData]);
76
89
  const visible = filter === "all" ? events : events.filter((e) => e.source === filter);
77
90
  const filterChips = ["all", "activity", "document", "payment"];
78
- return (_jsxs(Card, { "data-slot": "booking-activity-timeline", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(Activity, { className: "h-4 w-4" }), "Activity Timeline"] }), _jsx("div", { className: "flex items-center gap-1", children: filterChips.map((chip) => (_jsx(Button, { variant: filter === chip ? "default" : "ghost", size: "sm", className: "h-7 capitalize", onClick: () => setFilter(chip), children: chip === "all" ? "All" : sourceLabel[chip] }, chip))) })] }), _jsx(CardContent, { children: visible.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: "No events yet." })) : (_jsx("div", { className: "flex flex-col gap-3", children: visible.map((event) => (_jsx(TimelineEventItem, { event: event }, event.id))) })) })] }));
91
+ return (_jsxs(Card, { "data-slot": "booking-activity-timeline", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(Activity, { className: "h-4 w-4" }), messages.bookingActivityTimeline.title] }), _jsx("div", { className: "flex items-center gap-1", children: filterChips.map((chip) => (_jsx(Button, { variant: filter === chip ? "default" : "ghost", size: "sm", className: "h-7 capitalize", onClick: () => setFilter(chip), children: chip === "all" ? messages.bookingActivityTimeline.filters.all : sourceLabel[chip] }, chip))) })] }), _jsx(CardContent, { children: visible.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: messages.bookingActivityTimeline.empty })) : (_jsx("div", { className: "flex flex-col gap-3", children: visible.map((event) => (_jsx(TimelineEventItem, { event: event, sourceLabel: sourceLabel }, event.id))) })) })] }));
79
92
  }
80
- function TimelineEventItem({ event }) {
93
+ function TimelineEventItem({ event, sourceLabel, }) {
81
94
  const Icon = event.icon;
82
- return (_jsxs("div", { className: "flex items-start gap-3 rounded-md border p-3", children: [_jsx(Icon, { className: "mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("p", { className: "text-sm font-medium capitalize", children: event.title }), _jsx(Badge, { variant: sourceVariant[event.source], className: "text-xs", children: sourceLabel[event.source] })] }), event.description && (_jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: event.description })), _jsxs("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [event.actorId && event.actorId !== "system" ? `By ${event.actorId} · ` : "", new Date(event.timestamp).toLocaleString()] }), event.link && (_jsxs("a", { href: event.link.href, target: "_blank", rel: "noopener noreferrer", className: "mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground hover:underline", children: [event.link.label, _jsx(ExternalLink, { className: "h-3 w-3" })] }))] })] }));
95
+ const { formatDateTime } = useBookingsUiI18nOrDefault();
96
+ const messages = useBookingsUiMessagesOrDefault();
97
+ return (_jsxs("div", { className: "flex items-start gap-3 rounded-md border p-3", children: [_jsx(Icon, { className: "mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("p", { className: "text-sm font-medium capitalize", children: event.title }), _jsx(Badge, { variant: sourceVariant[event.source], className: "text-xs", children: sourceLabel[event.source] })] }), event.description && (_jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: event.description })), _jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: event.actorId && event.actorId !== "system"
98
+ ? formatMessage(messages.bookingActivityTimeline.byActor, {
99
+ actor: event.actorId,
100
+ timestamp: formatDateTime(event.timestamp),
101
+ })
102
+ : formatDateTime(event.timestamp) }), event.link && (_jsxs("a", { href: event.link.href, target: "_blank", rel: "noopener noreferrer", className: "mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground hover:underline", children: [event.link.label, _jsx(ExternalLink, { className: "h-3 w-3" })] }))] })] }));
83
103
  }
@@ -1 +1 @@
1
- {"version":3,"file":"booking-cancellation-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-cancellation-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,EAGnB,MAAM,0BAA0B,CAAA;AA4CjC,MAAM,WAAW,8BAA8B;IAC7C,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,OAAO,EAAE,aAAa,CAAA;IACtB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,yBAAyB,CAAC,EACxC,IAAI,EACJ,YAAY,EACZ,OAAO,EACP,SAAS,EACT,SAAS,GACV,EAAE,8BAA8B,2CAwMhC"}
1
+ {"version":3,"file":"booking-cancellation-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-cancellation-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,EAGnB,MAAM,0BAA0B,CAAA;AAkCjC,MAAM,WAAW,8BAA8B;IAC7C,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,OAAO,EAAE,aAAa,CAAA;IACtB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,yBAAyB,CAAC,EACxC,IAAI,EACJ,YAAY,EACZ,OAAO,EACP,SAAS,EACT,SAAS,GACV,EAAE,8BAA8B,2CAiPhC"}
@@ -5,22 +5,11 @@ import { useEvaluateCancellation, useResolvePolicy } from "@voyantjs/legal-react
5
5
  import { Badge, Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Label, Textarea, } from "@voyantjs/ui/components";
6
6
  import { AlertTriangle, Loader2 } from "lucide-react";
7
7
  import * as React from "react";
8
- function formatAmount(cents, currency) {
9
- return `${(cents / 100).toFixed(2)} ${currency}`;
10
- }
11
- function formatPercent(basisPoints) {
12
- return `${(basisPoints / 100).toFixed(0)}%`;
13
- }
8
+ import { formatMessage, useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault, } from "../i18n/provider";
14
9
  function daysBetween(from, to) {
15
10
  const diffMs = to.getTime() - from.getTime();
16
11
  return Math.max(0, Math.floor(diffMs / (1000 * 60 * 60 * 24)));
17
12
  }
18
- const refundTypeLabel = {
19
- cash: "Cash refund",
20
- credit: "Credit",
21
- cash_or_credit: "Cash or credit",
22
- none: "No refund",
23
- };
24
13
  const refundTypeVariant = {
25
14
  cash: "default",
26
15
  credit: "secondary",
@@ -29,6 +18,8 @@ const refundTypeVariant = {
29
18
  };
30
19
  export function BookingCancellationDialog({ open, onOpenChange, booking, productId, onSuccess, }) {
31
20
  const [reason, setReason] = React.useState("");
21
+ const { formatCurrency, formatNumber } = useBookingsUiI18nOrDefault();
22
+ const messages = useBookingsUiMessagesOrDefault();
32
23
  const daysBeforeDeparture = React.useMemo(() => {
33
24
  if (!booking.startDate)
34
25
  return 0;
@@ -71,10 +62,18 @@ export function BookingCancellationDialog({ open, onOpenChange, booking, product
71
62
  const total = booking.sellAmountCents;
72
63
  const refund = evaluation?.refundCents ?? 0;
73
64
  const penalty = total != null ? Math.max(0, total - refund) : 0;
74
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsxs(DialogTitle, { className: "flex items-center gap-2", children: [_jsx(AlertTriangle, { className: "h-4 w-4 text-destructive" }), "Cancel Booking"] }) }), _jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4 rounded-md border bg-muted/30 p-3 text-sm", children: [_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Booking" }), _jsx("div", { className: "font-mono text-xs", children: booking.bookingNumber })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Start date" }), _jsx("div", { children: booking.startDate ?? "TBD" })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Total" }), _jsx("div", { className: "font-mono", children: total != null ? formatAmount(total, booking.sellCurrency) : "—" })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Days before departure" }), _jsx("div", { children: daysBeforeDeparture })] })] }), resolveLoading ? (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), "Resolving cancellation policy..."] })) : !policy ? (_jsx("div", { className: "rounded-md border border-dashed p-3 text-sm text-muted-foreground", children: "No cancellation policy configured for this booking. Proceeding will cancel without a refund preview." })) : (_jsxs("div", { className: "space-y-2 rounded-md border p-3", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Applicable policy" }), _jsx("div", { className: "text-sm font-medium", children: policy.policy.name })] }), evaluation && (_jsx(Badge, { variant: refundTypeVariant[evaluation.refundType] ?? "secondary", children: refundTypeLabel[evaluation.refundType] ?? evaluation.refundType }))] }), evaluationLoading ? (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), "Calculating refund..."] })) : evaluation && total != null ? (_jsxs("div", { className: "grid grid-cols-3 gap-3 border-t pt-3 text-sm", children: [_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Refund" }), _jsx("div", { className: "font-mono font-medium", children: formatAmount(evaluation.refundCents, booking.sellCurrency) }), _jsxs("div", { className: "text-xs text-muted-foreground", children: ["(", formatPercent(evaluation.refundPercent), ")"] })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Penalty" }), _jsx("div", { className: "font-mono font-medium text-destructive", children: formatAmount(penalty, booking.sellCurrency) })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Rule" }), _jsx("div", { className: "text-xs", children: evaluation.appliedRule?.label ??
65
+ const formatAmount = React.useCallback((cents, currency) => formatCurrency(cents / 100, currency), [formatCurrency]);
66
+ const formatPercent = React.useCallback((basisPoints) => `${formatNumber(basisPoints / 100, {
67
+ maximumFractionDigits: 0,
68
+ })}%`, [formatNumber]);
69
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsxs(DialogTitle, { className: "flex items-center gap-2", children: [_jsx(AlertTriangle, { className: "h-4 w-4 text-destructive" }), messages.bookingCancellationDialog.title] }) }), _jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4 rounded-md border bg-muted/30 p-3 text-sm", children: [_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: messages.bookingCancellationDialog.summary.booking }), _jsx("div", { className: "font-mono text-xs", children: booking.bookingNumber })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: messages.bookingCancellationDialog.summary.startDate }), _jsx("div", { children: booking.startDate ?? messages.bookingCancellationDialog.values.startDateTbd })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: messages.bookingCancellationDialog.summary.total }), _jsx("div", { className: "font-mono", children: total != null
70
+ ? formatAmount(total, booking.sellCurrency)
71
+ : messages.bookingCancellationDialog.values.amountUnavailable })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: messages.bookingCancellationDialog.summary.daysBeforeDeparture }), _jsx("div", { children: daysBeforeDeparture })] })] }), resolveLoading ? (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), messages.bookingCancellationDialog.policy.resolving] })) : !policy ? (_jsxs("div", { className: "rounded-md border border-dashed p-3 text-sm text-muted-foreground", children: [messages.bookingCancellationDialog.policy.missing, " ", messages.bookingCancellationDialog.policy.missingHint] })) : (_jsxs("div", { className: "space-y-2 rounded-md border p-3", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: messages.bookingCancellationDialog.policy.applicablePolicy }), _jsx("div", { className: "text-sm font-medium", children: policy.policy.name })] }), evaluation && (_jsx(Badge, { variant: refundTypeVariant[evaluation.refundType] ?? "secondary", children: messages.bookingCancellationDialog.refundTypeLabels[evaluation.refundType] ?? evaluation.refundType }))] }), evaluationLoading ? (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), messages.bookingCancellationDialog.policy.calculating] })) : evaluation && total != null ? (_jsxs("div", { className: "grid grid-cols-3 gap-3 border-t pt-3 text-sm", children: [_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: messages.bookingCancellationDialog.policy.refund }), _jsx("div", { className: "font-mono font-medium", children: formatAmount(evaluation.refundCents, booking.sellCurrency) }), _jsxs("div", { className: "text-xs text-muted-foreground", children: ["(", formatPercent(evaluation.refundPercent), ")"] })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: messages.bookingCancellationDialog.policy.penalty }), _jsx("div", { className: "font-mono font-medium text-destructive", children: formatAmount(penalty, booking.sellCurrency) })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: messages.bookingCancellationDialog.policy.rule }), _jsx("div", { className: "text-xs", children: evaluation.appliedRule?.label ??
75
72
  (evaluation.appliedRule?.daysBeforeDeparture != null
76
- ? `≥ ${evaluation.appliedRule.daysBeforeDeparture} days`
77
- : "—") })] })] })) : total == null ? (_jsx("p", { className: "border-t pt-3 text-sm text-muted-foreground", children: "Booking has no total amount \u2014 refund cannot be calculated." })) : null] })), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs(Label, { children: ["Reason ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(Textarea, { value: reason, onChange: (e) => setReason(e.target.value), placeholder: "Why is this booking being cancelled?", required: true })] }), cancelMutation.error && (_jsx("p", { className: "text-xs text-destructive", children: cancelMutation.error instanceof Error
73
+ ? formatMessage(messages.bookingCancellationDialog.values.ruleDaysBeforeDeparture, {
74
+ days: evaluation.appliedRule.daysBeforeDeparture,
75
+ })
76
+ : messages.bookingCancellationDialog.values.ruleFallback) })] })] })) : total == null ? (_jsx("p", { className: "border-t pt-3 text-sm text-muted-foreground", children: messages.bookingCancellationDialog.policy.noTotalAmount })) : null] })), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs(Label, { children: [messages.bookingCancellationDialog.fields.reason, " ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(Textarea, { value: reason, onChange: (e) => setReason(e.target.value), placeholder: messages.bookingCancellationDialog.placeholders.reason, required: true })] }), cancelMutation.error && (_jsx("p", { className: "text-xs text-destructive", children: cancelMutation.error instanceof Error
78
77
  ? cancelMutation.error.message
79
- : "Cancellation failed" }))] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), disabled: cancelMutation.isPending, children: "Close" }), _jsxs(Button, { type: "button", variant: "destructive", size: "sm", onClick: handleConfirm, disabled: !reason.trim() || cancelMutation.isPending, children: [cancelMutation.isPending && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Confirm Cancellation"] })] })] }) }));
78
+ : messages.bookingCancellationDialog.validation.cancellationFailed }))] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), disabled: cancelMutation.isPending, children: messages.bookingCancellationDialog.actions.close }), _jsxs(Button, { type: "button", variant: "destructive", size: "sm", onClick: handleConfirm, disabled: !reason.trim() || cancelMutation.isPending, children: [cancelMutation.isPending && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), messages.bookingCancellationDialog.actions.confirm] })] })] }) }));
80
79
  }
@@ -1 +1 @@
1
- {"version":3,"file":"booking-create-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-create-dialog.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,aAAa,EAOnB,MAAM,0BAA0B,CAAA;AAqJjC,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,gBAAgB,GACjB,EAAE,wBAAwB,2CAoW1B"}
1
+ {"version":3,"file":"booking-create-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-create-dialog.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,aAAa,EAOnB,MAAM,0BAA0B,CAAA;AA2JjC,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,gBAAgB,GACjB,EAAE,wBAAwB,2CA6b1B"}
@@ -6,6 +6,7 @@ import { usePersonMutation } from "@voyantjs/crm-react";
6
6
  import { Button, Checkbox, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea, } from "@voyantjs/ui/components";
7
7
  import { Loader2 } from "lucide-react";
8
8
  import * as React from "react";
9
+ import { formatMessage, useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault, } from "../i18n/provider";
9
10
  import { emptyPassengerListValue, PassengersSection, } from "./passengers-section";
10
11
  import { emptyPaymentScheduleValue, PaymentScheduleSection, } from "./payment-schedule-section";
11
12
  import { emptyPersonPickerValue, PersonPickerSection, } from "./person-picker-section";
@@ -132,6 +133,8 @@ export function BookingCreateDialog({ open, onOpenChange, onCreated, defaultProd
132
133
  */
133
134
  const [confirmAfterCreate, setConfirmAfterCreate] = React.useState(false);
134
135
  const [error, setError] = React.useState(null);
136
+ const { formatDate } = useBookingsUiI18nOrDefault();
137
+ const messages = useBookingsUiMessagesOrDefault();
135
138
  React.useEffect(() => {
136
139
  if (!open) {
137
140
  setProduct({ productId: defaultProductId ?? "", optionId: null });
@@ -175,14 +178,16 @@ export function BookingCreateDialog({ open, onOpenChange, onCreated, defaultProd
175
178
  .sort((a, b) => a.startsAt.localeCompare(b.startsAt));
176
179
  }, [slotsData, product.optionId]);
177
180
  const formatSlotLabel = React.useCallback((slot) => {
178
- const date = new Date(slot.startsAt).toLocaleDateString(undefined, {
181
+ const date = formatDate(slot.startsAt, {
179
182
  year: "numeric",
180
183
  month: "short",
181
184
  day: "numeric",
182
185
  });
183
- const remaining = !slot.unlimited && typeof slot.remainingPax === "number" ? ` · ${slot.remainingPax} left` : "";
186
+ const remaining = !slot.unlimited && typeof slot.remainingPax === "number"
187
+ ? ` · ${slot.remainingPax} ${messages.bookingCreateDialog.labels.remainingCapacity}`
188
+ : "";
184
189
  return `${date}${remaining}`;
185
- }, []);
190
+ }, [formatDate, messages]);
186
191
  const slotUnitAvailability = useSlotUnitAvailability({
187
192
  slotId: slotId ?? undefined,
188
193
  enabled: open && Boolean(slotId),
@@ -208,28 +213,28 @@ export function BookingCreateDialog({ open, onOpenChange, onCreated, defaultProd
208
213
  // Currency placeholder — used for voucher + payment schedule display.
209
214
  // Consumers hooking in real product data should override this by wrapping
210
215
  // the component or swapping in their own currency-aware hook.
211
- const currency = "EUR";
216
+ const currency = messages.bookingCreateDialog.labels.currency;
212
217
  const { create: createPerson } = usePersonMutation();
213
218
  const quickCreateMutation = useBookingQuickCreateMutation();
214
219
  const statusMutation = useBookingStatusByIdMutation();
215
220
  const handleSubmit = async () => {
216
221
  setError(null);
217
222
  if (!product.productId) {
218
- setError("Select a product");
223
+ setError(messages.bookingCreateDialog.validation.selectProduct);
219
224
  return;
220
225
  }
221
226
  let resolvedPersonId = null;
222
227
  try {
223
228
  if (person.mode === "existing") {
224
229
  if (!person.personId) {
225
- setError("Select a person or switch to create mode");
230
+ setError(messages.bookingCreateDialog.validation.selectPerson);
226
231
  return;
227
232
  }
228
233
  resolvedPersonId = person.personId;
229
234
  }
230
235
  else {
231
236
  if (!person.newPerson.firstName.trim() || !person.newPerson.lastName.trim()) {
232
- setError("First and last name are required");
237
+ setError(messages.bookingCreateDialog.validation.firstAndLastNameRequired);
233
238
  return;
234
239
  }
235
240
  const created = await createPerson.mutateAsync({
@@ -241,7 +246,7 @@ export function BookingCreateDialog({ open, onOpenChange, onCreated, defaultProd
241
246
  resolvedPersonId = created.id;
242
247
  }
243
248
  if (sharedRoom.enabled && sharedRoom.mode === "join" && !sharedRoom.groupId) {
244
- setError("Select a shared-room group to join");
249
+ setError(messages.bookingCreateDialog.validation.selectSharedRoomGroup);
245
250
  return;
246
251
  }
247
252
  const bookingNumber = generateBookingNumber();
@@ -258,7 +263,7 @@ export function BookingCreateDialog({ open, onOpenChange, onCreated, defaultProd
258
263
  ? {
259
264
  action: "create",
260
265
  kind: "shared_room",
261
- label: `Shared room ${bookingNumber}`,
266
+ label: `${messages.bookingCreateDialog.labels.sharedRoomGeneratedLabelPrefix} - ${bookingNumber}`,
262
267
  optionUnitId: product.optionId,
263
268
  makeBookingPrimary: true,
264
269
  }
@@ -295,8 +300,10 @@ export function BookingCreateDialog({ open, onOpenChange, onCreated, defaultProd
295
300
  }
296
301
  catch (statusErr) {
297
302
  setError(statusErr instanceof Error
298
- ? `Booking created but confirm failed: ${statusErr.message}`
299
- : "Booking created but confirm failed");
303
+ ? formatMessage(messages.bookingCreateDialog.validation.confirmFailedPrefix, {
304
+ message: statusErr.message,
305
+ })
306
+ : messages.bookingCreateDialog.validation.confirmFailed);
300
307
  onCreated?.(booking);
301
308
  return;
302
309
  }
@@ -305,9 +312,66 @@ export function BookingCreateDialog({ open, onOpenChange, onCreated, defaultProd
305
312
  onCreated?.(finalBooking);
306
313
  }
307
314
  catch (err) {
308
- setError(err instanceof Error ? err.message : "Failed to create booking");
315
+ setError(err instanceof Error ? err.message : messages.bookingCreateDialog.validation.createFailed);
309
316
  }
310
317
  };
311
318
  const isSubmitting = quickCreateMutation.isPending || createPerson.isPending || statusMutation.isPending;
312
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Quick Book" }) }), _jsxs(DialogBody, { className: "grid gap-4", children: [_jsx(ProductPickerSection, { value: product, onChange: setProduct, enabled: open, lockProduct: Boolean(defaultProductId) }), product.productId ? (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { children: "Departure" }), _jsxs(Select, { value: slotId ?? "__none__", onValueChange: (v) => setSlotId(v === "__none__" ? null : (v ?? null)), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Select a departure..." }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__none__", children: "No specific departure" }), slots.length === 0 ? (_jsx(SelectItem, { value: "__empty__", disabled: true, children: "No open departures for this product" })) : (slots.map((slot) => (_jsx(SelectItem, { value: slot.id, children: formatSlotLabel(slot) }, slot.id))))] })] })] })) : null, slotId ? (_jsx(RoomsStepperSection, { value: rooms, onChange: setRooms, slotId: slotId, enabled: open })) : null, _jsx(PersonPickerSection, { value: person, onChange: setPerson, enabled: open }), _jsx(SharedRoomSection, { value: sharedRoom, onChange: setSharedRoom, productId: product.productId || undefined, enabled: open }), product.productId ? (_jsx(PassengersSection, { value: passengers, onChange: setPassengers, roomUnits: roomUnitOptions.length > 0 ? roomUnitOptions : undefined })) : null, product.productId ? (_jsx(PriceBreakdownSection, { productId: product.productId, optionId: product.optionId, unitQuantities: rooms.quantities })) : null, _jsx(VoucherPickerSection, { value: voucher, onChange: setVoucher, currency: currency }), _jsx(PaymentScheduleSection, { value: paymentSchedule, onChange: setPaymentSchedule, currency: currency }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Internal Notes" }), _jsx(Textarea, { value: notes, onChange: (e) => setNotes(e.target.value), placeholder: "Quick context for this booking..." })] }), _jsxs("div", { className: "flex items-start gap-2 rounded-md border p-3", children: [_jsx(Checkbox, { id: "quickbook-confirm-after-create", checked: confirmAfterCreate, onCheckedChange: (v) => setConfirmAfterCreate(v === true), className: "mt-0.5" }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { htmlFor: "quickbook-confirm-after-create", className: "cursor-pointer text-sm", children: "Confirm & notify traveler after creating" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Transitions to confirmed after create. When the notifications module's auto-dispatch is on, this fires the doc bundle + traveler email via the booking.confirmed subscriber." })] })] }), error && _jsx("p", { className: "text-xs text-destructive", children: error })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), disabled: isSubmitting, children: "Cancel" }), _jsxs(Button, { type: "button", size: "sm", onClick: handleSubmit, disabled: isSubmitting || !product.productId, children: [isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Create Draft Booking"] })] })] }) }));
319
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: messages.bookingCreateDialog.title }) }), _jsxs(DialogBody, { className: "grid gap-4", children: [_jsx(ProductPickerSection, { value: product, onChange: setProduct, enabled: open, lockProduct: Boolean(defaultProductId), labels: {
320
+ optionNone: messages.bookingCreateDialog.labels.noSpecificOption,
321
+ } }), product.productId ? (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { children: messages.bookingCreateDialog.fields.departure }), _jsxs(Select, { value: slotId ?? "__none__", onValueChange: (v) => setSlotId(v === "__none__" ? null : (v ?? null)), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: messages.bookingCreateDialog.placeholders.departure }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "__none__", children: messages.bookingCreateDialog.placeholders.departureNone }), slots.length === 0 ? (_jsx(SelectItem, { value: "__empty__", disabled: true, children: messages.bookingCreateDialog.placeholders.departureEmpty })) : (slots.map((slot) => (_jsx(SelectItem, { value: slot.id, children: formatSlotLabel(slot) }, slot.id))))] })] })] })) : null, slotId ? (_jsx(RoomsStepperSection, { value: rooms, onChange: setRooms, slotId: slotId, enabled: open, labels: {
322
+ heading: messages.bookingCreateDialog.labels.roomsHeading,
323
+ noSlot: messages.bookingCreateDialog.labels.roomsNoSlot,
324
+ noUnits: messages.bookingCreateDialog.labels.roomsNoUnits,
325
+ remaining: messages.bookingCreateDialog.labels.roomsRemaining,
326
+ unlimited: messages.bookingCreateDialog.labels.roomsUnlimited,
327
+ } })) : null, _jsx(PersonPickerSection, { value: person, onChange: setPerson, enabled: open, labels: {
328
+ createNewPerson: messages.bookingCreateDialog.labels.createNewPerson,
329
+ selectExistingPerson: messages.bookingCreateDialog.labels.selectExistingPerson,
330
+ organizationNone: messages.bookingCreateDialog.labels.organizationNone,
331
+ } }), _jsx(SharedRoomSection, { value: sharedRoom, onChange: setSharedRoom, productId: product.productId || undefined, enabled: open, labels: {
332
+ toggle: messages.bookingCreateDialog.labels.sharedRoomToggle,
333
+ createMode: messages.bookingCreateDialog.labels.sharedRoomCreateMode,
334
+ joinMode: messages.bookingCreateDialog.labels.sharedRoomJoinMode,
335
+ selectPlaceholder: messages.bookingCreateDialog.labels.sharedRoomSelectPlaceholder,
336
+ noGroups: messages.bookingCreateDialog.labels.sharedRoomNoGroups,
337
+ createHint: messages.bookingCreateDialog.labels.sharedRoomCreateHint,
338
+ } }), product.productId ? (_jsx(PassengersSection, { value: passengers, onChange: setPassengers, roomUnits: roomUnitOptions.length > 0 ? roomUnitOptions : undefined, labels: {
339
+ heading: messages.bookingCreateDialog.labels.passengerHeading,
340
+ addPassenger: messages.bookingCreateDialog.labels.addPassenger,
341
+ role: messages.bookingCreateDialog.labels.passengerRole,
342
+ roleLead: messages.bookingCreateDialog.labels.passengerLead,
343
+ roleAdult: messages.bookingCreateDialog.labels.passengerAdult,
344
+ roleChild: messages.bookingCreateDialog.labels.passengerChild,
345
+ roleInfant: messages.bookingCreateDialog.labels.passengerInfant,
346
+ room: messages.bookingCreateDialog.labels.passengerRoom,
347
+ noRoom: messages.bookingCreateDialog.labels.passengerNoRoom,
348
+ remove: messages.bookingCreateDialog.labels.passengerRemove,
349
+ empty: messages.bookingCreateDialog.labels.passengerEmpty,
350
+ } })) : null, product.productId ? (_jsx(PriceBreakdownSection, { productId: product.productId, optionId: product.optionId, unitQuantities: rooms.quantities, labels: {
351
+ heading: messages.bookingCreateDialog.labels.breakdownHeading,
352
+ total: messages.bookingCreateDialog.labels.breakdownTotal,
353
+ onRequest: messages.bookingCreateDialog.labels.breakdownOnRequest,
354
+ groupRate: messages.bookingCreateDialog.labels.breakdownGroupRate,
355
+ empty: messages.bookingCreateDialog.labels.breakdownEmpty,
356
+ noPricing: messages.bookingCreateDialog.labels.breakdownNoPricing,
357
+ } })) : null, _jsx(VoucherPickerSection, { value: voucher, onChange: setVoucher, currency: currency, labels: {
358
+ heading: messages.bookingCreateDialog.labels.voucherHeading,
359
+ codePlaceholder: messages.bookingCreateDialog.labels.voucherCodePlaceholder,
360
+ apply: messages.bookingCreateDialog.labels.voucherApply,
361
+ clear: messages.bookingCreateDialog.labels.voucherClear,
362
+ remainingLabel: messages.bookingCreateDialog.labels.voucherRemainingLabel,
363
+ invalidLabel: messages.bookingCreateDialog.labels.voucherInvalidLabel,
364
+ } }), _jsx(PaymentScheduleSection, { value: paymentSchedule, onChange: setPaymentSchedule, currency: currency, labels: {
365
+ heading: messages.bookingCreateDialog.labels.paymentHeading,
366
+ modeUnpaid: messages.bookingCreateDialog.labels.paymentModeUnpaid,
367
+ modeFull: messages.bookingCreateDialog.labels.paymentModeFull,
368
+ modeAdvance: messages.bookingCreateDialog.labels.paymentModeAdvance,
369
+ modeSplit: messages.bookingCreateDialog.labels.paymentModeSplit,
370
+ dueDate: messages.bookingCreateDialog.labels.paymentDueDate,
371
+ amount: messages.bookingCreateDialog.labels.paymentAmount,
372
+ firstInstallment: messages.bookingCreateDialog.labels.paymentFirstInstallment,
373
+ secondInstallment: messages.bookingCreateDialog.labels.paymentSecondInstallment,
374
+ preset5050: messages.bookingCreateDialog.labels.paymentPreset5050,
375
+ unpaidHint: messages.bookingCreateDialog.labels.paymentUnpaidHint,
376
+ } }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingCreateDialog.fields.internalNotes }), _jsx(Textarea, { value: notes, onChange: (e) => setNotes(e.target.value), placeholder: messages.bookingCreateDialog.placeholders.internalNotes })] }), _jsxs("div", { className: "flex items-start gap-2 rounded-md border p-3", children: [_jsx(Checkbox, { id: "quickbook-confirm-after-create", checked: confirmAfterCreate, onCheckedChange: (v) => setConfirmAfterCreate(v === true), className: "mt-0.5" }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { htmlFor: "quickbook-confirm-after-create", className: "cursor-pointer text-sm", children: messages.bookingCreateDialog.fields.confirmAfterCreate }), _jsx("p", { className: "text-xs text-muted-foreground", children: messages.bookingCreateDialog.fields.confirmAfterCreateHint })] })] }), error && _jsx("p", { className: "text-xs text-destructive", children: error })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), disabled: isSubmitting, children: messages.common.cancel }), _jsxs(Button, { type: "button", size: "sm", onClick: handleSubmit, disabled: isSubmitting || !product.productId, children: [isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), messages.bookingCreateDialog.actions.createDraftBooking] })] })] }) }));
313
377
  }
@@ -1 +1 @@
1
- {"version":3,"file":"booking-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAsB,MAAM,0BAA0B,CAAA;AA2CjF,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAUD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,YAAY,EACZ,OAAO,EACP,SAAS,EACT,gBAAgB,GACjB,EAAE,kBAAkB,2CAoBpB"}
1
+ {"version":3,"file":"booking-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAsB,MAAM,0BAA0B,CAAA;AA8CjF,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAWD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,YAAY,EACZ,OAAO,EACP,SAAS,EACT,gBAAgB,GACjB,EAAE,kBAAkB,2CAoBpB"}
@@ -9,25 +9,29 @@ import { Loader2 } from "lucide-react";
9
9
  import { useEffect } from "react";
10
10
  import { useForm } from "react-hook-form";
11
11
  import { z } from "zod/v4";
12
+ import { useBookingsUiMessagesOrDefault } from "../i18n/provider";
12
13
  import { BookingCreateDialog } from "./booking-create-dialog";
13
- const bookingFormSchema = z.object({
14
- bookingNumber: z.string().min(1, "Booking number is required"),
15
- status: z.enum(["draft", "confirmed", "in_progress", "completed", "cancelled"]),
16
- sellCurrency: z.string().min(3).max(3, "Use 3-letter ISO code"),
17
- sellAmountCents: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
18
- costAmountCents: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
19
- startDate: z.string().optional().nullable(),
20
- endDate: z.string().optional().nullable(),
21
- pax: z.coerce.number().int().positive().optional().or(z.literal("")).nullable(),
22
- internalNotes: z.string().optional().nullable(),
23
- });
24
- const BOOKING_STATUSES = [
25
- { value: "draft", label: "Draft" },
26
- { value: "confirmed", label: "Confirmed" },
27
- { value: "in_progress", label: "In Progress" },
28
- { value: "completed", label: "Completed" },
29
- { value: "cancelled", label: "Cancelled" },
14
+ function createBookingFormSchema(messages) {
15
+ return z.object({
16
+ bookingNumber: z.string().min(1, messages.bookingDialog.validation.bookingNumberRequired),
17
+ status: z.enum(["draft", "confirmed", "in_progress", "completed", "cancelled"]),
18
+ sellCurrency: z.string().min(3).max(3, messages.bookingDialog.validation.sellCurrencyInvalid),
19
+ sellAmountCents: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
20
+ costAmountCents: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
21
+ startDate: z.string().optional().nullable(),
22
+ endDate: z.string().optional().nullable(),
23
+ pax: z.coerce.number().int().positive().optional().or(z.literal("")).nullable(),
24
+ internalNotes: z.string().optional().nullable(),
25
+ });
26
+ }
27
+ const BOOKING_STATUS_VALUES = [
28
+ "draft",
29
+ "confirmed",
30
+ "in_progress",
31
+ "completed",
32
+ "cancelled",
30
33
  ];
34
+ const DEFAULT_CURRENCY = "EUR"; // i18n-literal-ok ISO default currency
31
35
  /**
32
36
  * Single booking dialog that handles both create and edit:
33
37
  * - Create (no `booking` prop): renders the rich product → option → person
@@ -45,12 +49,14 @@ export function BookingDialog({ open, onOpenChange, booking, onSuccess, defaultP
45
49
  }
46
50
  function BookingEditDialog({ open, onOpenChange, booking, onSuccess }) {
47
51
  const { update } = useBookingMutation();
52
+ const messages = useBookingsUiMessagesOrDefault();
53
+ const bookingFormSchema = createBookingFormSchema(messages);
48
54
  const form = useForm({
49
55
  resolver: zodResolver(bookingFormSchema),
50
56
  defaultValues: {
51
57
  bookingNumber: "",
52
58
  status: "draft",
53
- sellCurrency: "EUR",
59
+ sellCurrency: DEFAULT_CURRENCY,
54
60
  sellAmountCents: "",
55
61
  costAmountCents: "",
56
62
  startDate: "",
@@ -95,14 +101,14 @@ function BookingEditDialog({ open, onOpenChange, booking, onSuccess }) {
95
101
  onSuccess?.(saved);
96
102
  };
97
103
  const isSubmitting = update.isPending;
98
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Edit Booking" }) }), _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: "Booking Number" }), _jsx(Input, { ...form.register("bookingNumber"), placeholder: "BK-2501-1234" }), form.formState.errors.bookingNumber && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.bookingNumber.message }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Status" }), _jsxs(Select, { value: form.watch("status"), onValueChange: (value) => form.setValue("status", value), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: BOOKING_STATUSES.map((status) => (_jsx(SelectItem, { value: status.value, children: status.label }, status.value))) })] })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Sell Currency" }), _jsx(CurrencyCombobox, { value: form.watch("sellCurrency") || null, onChange: (next) => form.setValue("sellCurrency", next ?? "EUR", {
104
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: messages.bookingDialog.editTitle }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "flex flex-1 flex-col overflow-hidden", children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDialog.fields.bookingNumber }), _jsx(Input, { ...form.register("bookingNumber"), placeholder: messages.bookingDialog.placeholders.bookingNumber }), form.formState.errors.bookingNumber && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.bookingNumber.message }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDialog.fields.status }), _jsxs(Select, { value: form.watch("status"), onValueChange: (value) => form.setValue("status", value), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: BOOKING_STATUS_VALUES.map((status) => (_jsx(SelectItem, { value: status, children: messages.common.bookingStatusLabels[status] }, status))) })] })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDialog.fields.sellCurrency }), _jsx(CurrencyCombobox, { value: form.watch("sellCurrency") || null, onChange: (next) => form.setValue("sellCurrency", next ?? DEFAULT_CURRENCY, {
99
105
  shouldValidate: true,
100
106
  shouldDirty: true,
101
- }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Travel Dates" }), _jsx(DateRangePicker, { value: {
107
+ }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDialog.fields.travelDates }), _jsx(DateRangePicker, { value: {
102
108
  from: form.watch("startDate") || null,
103
109
  to: form.watch("endDate") || null,
104
110
  }, onChange: (nextValue) => {
105
111
  form.setValue("startDate", nextValue?.from ?? "", { shouldDirty: true });
106
112
  form.setValue("endDate", nextValue?.to ?? "", { shouldDirty: true });
107
- }, placeholder: "Pick travel dates", className: "w-full" })] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Sell Amount (cents)" }), _jsx(Input, { ...form.register("sellAmountCents"), type: "number", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Cost Amount (cents)" }), _jsx(Input, { ...form.register("costAmountCents"), type: "number", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Travelers (Pax)" }), _jsx(Input, { ...form.register("pax"), type: "number", min: "1", placeholder: "2" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Internal Notes" }), _jsx(Textarea, { ...form.register("internalNotes"), placeholder: "Private operations 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" }), "Save Changes"] })] })] })] }) }));
113
+ }, placeholder: messages.bookingDialog.placeholders.travelDates, className: "w-full" })] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDialog.fields.sellAmountCents }), _jsx(Input, { ...form.register("sellAmountCents"), type: "number", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDialog.fields.costAmountCents }), _jsx(Input, { ...form.register("costAmountCents"), type: "number", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDialog.fields.pax }), _jsx(Input, { ...form.register("pax"), type: "number", min: "1", placeholder: messages.bookingDialog.placeholders.pax })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDialog.fields.internalNotes }), _jsx(Textarea, { ...form.register("internalNotes"), placeholder: messages.bookingDialog.placeholders.internalNotes })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), children: messages.common.cancel }), _jsxs(Button, { type: "submit", size: "sm", disabled: isSubmitting, children: [isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), messages.common.saveChanges] })] })] })] }) }));
108
114
  }
@@ -1 +1 @@
1
- {"version":3,"file":"booking-document-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-document-dialog.tsx"],"names":[],"mappings":"AA4CA,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,qBAAqB,CAAC,EACpC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,SAAS,GACV,EAAE,0BAA0B,2CAmJ5B"}
1
+ {"version":3,"file":"booking-document-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-document-dialog.tsx"],"names":[],"mappings":"AAkDA,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,qBAAqB,CAAC,EACpC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,SAAS,GACV,EAAE,0BAA0B,2CAgK5B"}
@@ -8,21 +8,29 @@ import { Loader2 } from "lucide-react";
8
8
  import { useEffect } from "react";
9
9
  import { useForm } from "react-hook-form";
10
10
  import { z } from "zod/v4";
11
+ import { useBookingsUiMessagesOrDefault } from "../i18n/provider";
11
12
  import { FileDropzone } from "./file-dropzone";
12
13
  const documentTypes = ["visa", "insurance", "health", "passport_copy", "other"];
13
14
  const UNASSIGNED = "__unassigned__";
14
- const documentFormSchema = z.object({
15
- type: z.enum(documentTypes).default("other"),
16
- fileName: z.string().min(1, "File name is required").max(500),
17
- fileUrl: z.string().url("Must be a valid URL"),
18
- travelerId: z.string().optional().nullable(),
19
- expiresAt: z.string().optional().nullable(),
20
- notes: z.string().optional().nullable(),
21
- });
15
+ function createDocumentFormSchema(messages) {
16
+ return z.object({
17
+ type: z.enum(documentTypes).default("other"),
18
+ fileName: z
19
+ .string()
20
+ .min(1, messages.bookingDocumentDialog.validation.fileNameRequired)
21
+ .max(500),
22
+ fileUrl: z.string().url(messages.bookingDocumentDialog.validation.fileUrlInvalid),
23
+ travelerId: z.string().optional().nullable(),
24
+ expiresAt: z.string().optional().nullable(),
25
+ notes: z.string().optional().nullable(),
26
+ });
27
+ }
22
28
  export function BookingDocumentDialog({ open, onOpenChange, bookingId, onSuccess, }) {
23
29
  const { create } = useBookingTravelerDocumentMutation(bookingId);
24
30
  const { data: travelersData } = useTravelers(bookingId);
25
31
  const travelers = travelersData?.data ?? [];
32
+ const messages = useBookingsUiMessagesOrDefault();
33
+ const documentFormSchema = createDocumentFormSchema(messages);
26
34
  const form = useForm({
27
35
  resolver: zodResolver(documentFormSchema),
28
36
  defaultValues: {
@@ -51,17 +59,23 @@ export function BookingDocumentDialog({ open, onOpenChange, bookingId, onSuccess
51
59
  onOpenChange(false);
52
60
  onSuccess?.();
53
61
  };
54
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Add Document" }) }), _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: documentTypes.map((t) => ({ label: t.replace(/_/g, " "), value: t })), value: form.watch("type"), onValueChange: (v) => form.setValue("type", (v ?? "other")), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: documentTypes.map((t) => (_jsx(SelectItem, { value: t, children: t.replace(/_/g, " ") }, t))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Traveler (optional)" }), _jsxs(Select, { items: [
55
- { label: "Booking-wide", value: UNASSIGNED },
62
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: messages.bookingDocumentDialog.title }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "flex flex-1 flex-col overflow-hidden", children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDocumentDialog.fields.type }), _jsxs(Select, { items: documentTypes.map((t) => ({
63
+ label: messages.bookingDocumentDialog.documentTypeLabels[t],
64
+ value: t,
65
+ })), value: form.watch("type"), onValueChange: (v) => form.setValue("type", (v ?? "other")), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: documentTypes.map((t) => (_jsx(SelectItem, { value: t, children: messages.bookingDocumentDialog.documentTypeLabels[t] }, t))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDocumentDialog.fields.traveler }), _jsxs(Select, { items: [
66
+ {
67
+ label: messages.bookingDocumentDialog.placeholders.travelerUnassigned,
68
+ value: UNASSIGNED,
69
+ },
56
70
  ...travelers.map((traveler) => ({
57
71
  label: `${traveler.firstName} ${traveler.lastName}`,
58
72
  value: traveler.id,
59
73
  })),
60
- ], value: form.watch("travelerId") ?? UNASSIGNED, onValueChange: (v) => form.setValue("travelerId", v ?? UNASSIGNED), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: UNASSIGNED, children: "Booking-wide" }), travelers.map((traveler) => (_jsxs(SelectItem, { value: traveler.id, children: [traveler.firstName, " ", traveler.lastName] }, traveler.id)))] })] })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "File" }), _jsx(FileDropzone, { accept: "application/pdf,image/*", maxSize: 10 * 1024 * 1024, onUploaded: (upload) => {
74
+ ], value: form.watch("travelerId") ?? UNASSIGNED, onValueChange: (v) => form.setValue("travelerId", v ?? UNASSIGNED), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: UNASSIGNED, children: messages.bookingDocumentDialog.placeholders.travelerUnassigned }), travelers.map((traveler) => (_jsxs(SelectItem, { value: traveler.id, children: [traveler.firstName, " ", traveler.lastName] }, traveler.id)))] })] })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDocumentDialog.fields.file }), _jsx(FileDropzone, { accept: "application/pdf,image/*", maxSize: 10 * 1024 * 1024, onUploaded: (upload) => {
61
75
  form.setValue("fileUrl", upload.url, { shouldValidate: true });
62
76
  form.setValue("fileName", upload.name, { shouldValidate: true });
63
- }, helperText: "Drop passport, visa, or insurance document (PDF or image)" }), form.formState.errors.fileUrl && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.fileUrl.message }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Expires At (optional)" }), _jsx(DatePicker, { value: form.watch("expiresAt") || null, onChange: (next) => form.setValue("expiresAt", next ?? "", {
77
+ }, helperText: messages.bookingDocumentDialog.placeholders.helperText }), form.formState.errors.fileUrl && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.fileUrl.message }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDocumentDialog.fields.expiresAt }), _jsx(DatePicker, { value: form.watch("expiresAt") || null, onChange: (next) => form.setValue("expiresAt", next ?? "", {
64
78
  shouldValidate: true,
65
79
  shouldDirty: true,
66
- }), placeholder: "Select expiry date", className: "w-full" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Notes" }), _jsx(Textarea, { ...form.register("notes"), placeholder: "Additional notes..." })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), children: "Cancel" }), _jsxs(Button, { type: "submit", size: "sm", disabled: create.isPending, children: [create.isPending && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Add Document"] })] })] })] }) }));
80
+ }), placeholder: messages.bookingDocumentDialog.placeholders.expiresAt, className: "w-full" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingDocumentDialog.fields.notes }), _jsx(Textarea, { ...form.register("notes"), placeholder: messages.bookingDocumentDialog.placeholders.notes })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), children: messages.common.cancel }), _jsxs(Button, { type: "submit", size: "sm", disabled: create.isPending, children: [create.isPending && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), messages.bookingDocumentDialog.actions.addDocument] })] })] })] }) }));
67
81
  }
@@ -1 +1 @@
1
- {"version":3,"file":"booking-document-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-document-list.tsx"],"names":[],"mappings":"AAsBA,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,SAAS,EAAE,EAAE,wBAAwB,2CAqG1E"}
1
+ {"version":3,"file":"booking-document-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-document-list.tsx"],"names":[],"mappings":"AAuBA,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,SAAS,EAAE,EAAE,wBAAwB,2CAqH1E"}