@voyantjs/bookings-ui 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +201 -109
  2. package/README.md +11 -0
  3. package/dist/components/booking-activity-timeline.d.ts.map +1 -1
  4. package/dist/components/booking-activity-timeline.js +34 -14
  5. package/dist/components/booking-cancellation-dialog.d.ts.map +1 -1
  6. package/dist/components/booking-cancellation-dialog.js +15 -16
  7. package/dist/components/booking-create-dialog.d.ts.map +1 -1
  8. package/dist/components/booking-create-dialog.js +77 -13
  9. package/dist/components/booking-dialog.d.ts.map +1 -1
  10. package/dist/components/booking-dialog.js +27 -21
  11. package/dist/components/booking-document-dialog.d.ts.map +1 -1
  12. package/dist/components/booking-document-dialog.js +27 -13
  13. package/dist/components/booking-document-list.d.ts.map +1 -1
  14. package/dist/components/booking-document-list.js +9 -4
  15. package/dist/components/booking-group-link-dialog.d.ts.map +1 -1
  16. package/dist/components/booking-group-link-dialog.js +17 -6
  17. package/dist/components/booking-group-section.d.ts.map +1 -1
  18. package/dist/components/booking-group-section.js +8 -2
  19. package/dist/components/booking-guarantee-dialog.d.ts.map +1 -1
  20. package/dist/components/booking-guarantee-dialog.js +30 -14
  21. package/dist/components/booking-guarantee-list.d.ts.map +1 -1
  22. package/dist/components/booking-guarantee-list.js +11 -8
  23. package/dist/components/booking-item-dialog.d.ts.map +1 -1
  24. package/dist/components/booking-item-dialog.js +34 -20
  25. package/dist/components/booking-item-list.d.ts.map +1 -1
  26. package/dist/components/booking-item-list.js +10 -9
  27. package/dist/components/booking-item-travelers.d.ts.map +1 -1
  28. package/dist/components/booking-item-travelers.js +9 -4
  29. package/dist/components/booking-list.d.ts.map +1 -1
  30. package/dist/components/booking-list.js +17 -8
  31. package/dist/components/booking-notes.d.ts.map +1 -1
  32. package/dist/components/booking-notes.js +5 -2
  33. package/dist/components/booking-payment-schedule-dialog.d.ts.map +1 -1
  34. package/dist/components/booking-payment-schedule-dialog.js +31 -12
  35. package/dist/components/booking-payment-schedule-list.d.ts.map +1 -1
  36. package/dist/components/booking-payment-schedule-list.js +7 -6
  37. package/dist/components/booking-payments-summary.d.ts.map +1 -1
  38. package/dist/components/booking-payments-summary.js +7 -4
  39. package/dist/components/file-dropzone.d.ts.map +1 -1
  40. package/dist/components/file-dropzone.js +25 -15
  41. package/dist/components/passengers-section.d.ts.map +1 -1
  42. package/dist/components/passengers-section.js +3 -17
  43. package/dist/components/payment-schedule-section.d.ts.map +1 -1
  44. package/dist/components/payment-schedule-section.js +3 -14
  45. package/dist/components/person-picker-section.d.ts.map +1 -1
  46. package/dist/components/person-picker-section.js +3 -19
  47. package/dist/components/price-breakdown-section.d.ts.map +1 -1
  48. package/dist/components/price-breakdown-section.js +15 -18
  49. package/dist/components/product-picker-section.d.ts.map +1 -1
  50. package/dist/components/product-picker-section.js +3 -8
  51. package/dist/components/rooms-stepper-section.d.ts.map +1 -1
  52. package/dist/components/rooms-stepper-section.js +4 -9
  53. package/dist/components/shared-room-section.d.ts.map +1 -1
  54. package/dist/components/shared-room-section.js +3 -9
  55. package/dist/components/status-change-dialog.d.ts.map +1 -1
  56. package/dist/components/status-change-dialog.js +6 -1
  57. package/dist/components/supplier-status-dialog.d.ts.map +1 -1
  58. package/dist/components/supplier-status-dialog.js +31 -15
  59. package/dist/components/supplier-status-list.d.ts.map +1 -1
  60. package/dist/components/supplier-status-list.js +10 -2
  61. package/dist/components/traveler-dialog.d.ts.map +1 -1
  62. package/dist/components/traveler-dialog.js +17 -8
  63. package/dist/components/traveler-list.d.ts.map +1 -1
  64. package/dist/components/traveler-list.js +5 -3
  65. package/dist/components/voucher-picker-section.d.ts.map +1 -1
  66. package/dist/components/voucher-picker-section.js +11 -26
  67. package/dist/i18n/en.d.ts +797 -0
  68. package/dist/i18n/en.d.ts.map +1 -0
  69. package/dist/i18n/en.js +796 -0
  70. package/dist/i18n/index.d.ts +5 -0
  71. package/dist/i18n/index.d.ts.map +1 -0
  72. package/dist/i18n/index.js +3 -0
  73. package/dist/i18n/messages.d.ts +684 -0
  74. package/dist/i18n/messages.d.ts.map +1 -0
  75. package/dist/i18n/messages.js +1 -0
  76. package/dist/i18n/provider.d.ts +1617 -0
  77. package/dist/i18n/provider.d.ts.map +1 -0
  78. package/dist/i18n/provider.js +45 -0
  79. package/dist/i18n/ro.d.ts +797 -0
  80. package/dist/i18n/ro.d.ts.map +1 -0
  81. package/dist/i18n/ro.js +796 -0
  82. package/dist/index.d.ts +1 -0
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +1 -0
  85. package/package.json +40 -20
@@ -9,6 +9,7 @@ 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 guaranteeTypes = [
13
14
  "deposit",
14
15
  "credit_card",
@@ -27,25 +28,30 @@ const guaranteeStatuses = [
27
28
  "cancelled",
28
29
  "expired",
29
30
  ];
30
- const guaranteeFormSchema = z.object({
31
- guaranteeType: z.enum(guaranteeTypes),
32
- status: z.enum(guaranteeStatuses).default("pending"),
33
- currency: z.string().min(3).max(3).optional().nullable(),
34
- amountCents: z.coerce.number().int().min(0).optional().nullable(),
35
- provider: z.string().optional().nullable(),
36
- referenceNumber: z.string().optional().nullable(),
37
- expiresAt: z.string().optional().nullable(),
38
- notes: z.string().optional().nullable(),
39
- });
31
+ const DEFAULT_CURRENCY = "EUR"; // i18n-literal-ok ISO default currency
32
+ function createGuaranteeFormSchema() {
33
+ return z.object({
34
+ guaranteeType: z.enum(guaranteeTypes),
35
+ status: z.enum(guaranteeStatuses).default("pending"),
36
+ currency: z.string().min(3).max(3).optional().nullable(),
37
+ amountCents: z.coerce.number().int().min(0).optional().nullable(),
38
+ provider: z.string().optional().nullable(),
39
+ referenceNumber: z.string().optional().nullable(),
40
+ expiresAt: z.string().optional().nullable(),
41
+ notes: z.string().optional().nullable(),
42
+ });
43
+ }
40
44
  export function BookingGuaranteeDialog({ open, onOpenChange, bookingId, guarantee, onSuccess, }) {
41
45
  const isEditing = Boolean(guarantee);
42
46
  const { create, update } = useBookingGuaranteeMutation(bookingId);
47
+ const messages = useBookingsUiMessagesOrDefault();
48
+ const guaranteeFormSchema = createGuaranteeFormSchema();
43
49
  const form = useForm({
44
50
  resolver: zodResolver(guaranteeFormSchema),
45
51
  defaultValues: {
46
52
  guaranteeType: "deposit",
47
53
  status: "pending",
48
- currency: "EUR",
54
+ currency: DEFAULT_CURRENCY,
49
55
  amountCents: null,
50
56
  provider: "",
51
57
  referenceNumber: "",
@@ -91,11 +97,21 @@ export function BookingGuaranteeDialog({ open, onOpenChange, bookingId, guarante
91
97
  onSuccess?.();
92
98
  };
93
99
  const isSubmitting = create.isPending || update.isPending;
94
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? "Edit Guarantee" : "Add Guarantee" }) }), _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: guaranteeTypes.map((t) => ({ label: t.replace(/_/g, " "), value: t })), value: form.watch("guaranteeType"), onValueChange: (v) => form.setValue("guaranteeType", (v ?? "deposit")), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: guaranteeTypes.map((t) => (_jsx(SelectItem, { value: t, children: t.replace(/_/g, " ") }, t))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Status" }), _jsxs(Select, { items: guaranteeStatuses.map((s) => ({ label: s.replace(/_/g, " "), 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: guaranteeStatuses.map((s) => (_jsx(SelectItem, { value: s, children: s.replace(/_/g, " ") }, s))) })] })] })] }), _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", {
100
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing
101
+ ? messages.bookingGuaranteeDialog.titles.edit
102
+ : messages.bookingGuaranteeDialog.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.bookingGuaranteeDialog.fields.type }), _jsxs(Select, { items: guaranteeTypes.map((t) => ({
103
+ label: messages.bookingGuaranteeDialog.guaranteeTypeLabels[t],
104
+ value: t,
105
+ })), value: form.watch("guaranteeType"), onValueChange: (v) => form.setValue("guaranteeType", (v ?? "deposit")), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: guaranteeTypes.map((t) => (_jsx(SelectItem, { value: t, children: messages.bookingGuaranteeDialog.guaranteeTypeLabels[t] }, t))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingGuaranteeDialog.fields.status }), _jsxs(Select, { items: guaranteeStatuses.map((s) => ({
106
+ label: messages.bookingGuaranteeDialog.guaranteeStatusLabels[s],
107
+ value: s,
108
+ })), value: form.watch("status"), onValueChange: (v) => form.setValue("status", (v ?? "pending")), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: guaranteeStatuses.map((s) => (_jsx(SelectItem, { value: s, children: messages.bookingGuaranteeDialog.guaranteeStatusLabels[s] }, s))) })] })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingGuaranteeDialog.fields.currency }), _jsx(CurrencyCombobox, { value: form.watch("currency") || null, onChange: (next) => form.setValue("currency", next ?? DEFAULT_CURRENCY, {
95
109
  shouldValidate: true,
96
110
  shouldDirty: true,
97
- }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Amount (cents)" }), _jsx(Input, { ...form.register("amountCents"), type: "number", min: 0 })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Provider" }), _jsx(Input, { ...form.register("provider"), placeholder: "Stripe, bank name..." })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Reference Number" }), _jsx(Input, { ...form.register("referenceNumber"), placeholder: "External reference..." })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Expires At" }), _jsx(DateTimePicker, { value: form.watch("expiresAt") || null, onChange: (next) => form.setValue("expiresAt", next ?? "", {
111
+ }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingGuaranteeDialog.fields.amountCents }), _jsx(Input, { ...form.register("amountCents"), type: "number", min: 0 })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingGuaranteeDialog.fields.provider }), _jsx(Input, { ...form.register("provider"), placeholder: messages.bookingGuaranteeDialog.placeholders.provider })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingGuaranteeDialog.fields.referenceNumber }), _jsx(Input, { ...form.register("referenceNumber"), placeholder: messages.bookingGuaranteeDialog.placeholders.referenceNumber })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingGuaranteeDialog.fields.expiresAt }), _jsx(DateTimePicker, { value: form.watch("expiresAt") || null, onChange: (next) => form.setValue("expiresAt", next ?? "", {
98
112
  shouldValidate: true,
99
113
  shouldDirty: true,
100
- }), placeholder: "Select expiry date & time", className: "w-full" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Notes" }), _jsx(Textarea, { ...form.register("notes"), placeholder: "Guarantee 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 Guarantee"] })] })] })] }) }));
114
+ }), placeholder: messages.bookingGuaranteeDialog.placeholders.expiresAt, className: "w-full" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingGuaranteeDialog.fields.notes }), _jsx(Textarea, { ...form.register("notes"), placeholder: messages.bookingGuaranteeDialog.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
115
+ ? messages.common.saveChanges
116
+ : messages.bookingGuaranteeDialog.actions.addGuarantee] })] })] })] }) }));
101
117
  }
@@ -1 +1 @@
1
- {"version":3,"file":"booking-guarantee-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-guarantee-list.tsx"],"names":[],"mappings":"AA2BA,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,EAAE,yBAAyB,2CAkH5E"}
1
+ {"version":3,"file":"booking-guarantee-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-guarantee-list.tsx"],"names":[],"mappings":"AAuBA,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,EAAE,yBAAyB,2CAwI5E"}
@@ -4,6 +4,7 @@ import { useBookingGuaranteeMutation, useBookingGuarantees, } from "@voyantjs/fi
4
4
  import { Badge, Button, Card, CardContent, CardHeader, CardTitle } from "@voyantjs/ui/components";
5
5
  import { Pencil, Plus, ShieldCheck, Trash2 } from "lucide-react";
6
6
  import * as React from "react";
7
+ import { useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault } from "../i18n/provider";
7
8
  import { BookingGuaranteeDialog } from "./booking-guarantee-dialog";
8
9
  const statusVariant = {
9
10
  pending: "outline",
@@ -13,25 +14,27 @@ const statusVariant = {
13
14
  cancelled: "destructive",
14
15
  expired: "secondary",
15
16
  };
16
- function formatAmount(cents, currency) {
17
- if (cents == null || !currency)
18
- return "-";
19
- return `${(cents / 100).toFixed(2)} ${currency}`;
20
- }
21
17
  export function BookingGuaranteeList({ bookingId }) {
22
18
  const [dialogOpen, setDialogOpen] = React.useState(false);
23
19
  const [editing, setEditing] = React.useState(undefined);
24
20
  const { data } = useBookingGuarantees(bookingId);
25
21
  const { remove } = useBookingGuaranteeMutation(bookingId);
22
+ const { formatCurrency, formatDate } = useBookingsUiI18nOrDefault();
23
+ const messages = useBookingsUiMessagesOrDefault();
26
24
  const guarantees = data?.data ?? [];
27
- return (_jsxs(Card, { "data-slot": "booking-guarantee-list", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(ShieldCheck, { className: "h-4 w-4" }), "Guarantees"] }), _jsxs(Button, { size: "sm", onClick: () => {
25
+ return (_jsxs(Card, { "data-slot": "booking-guarantee-list", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(ShieldCheck, { className: "h-4 w-4" }), messages.bookingGuaranteeList.title] }), _jsxs(Button, { size: "sm", onClick: () => {
28
26
  setEditing(undefined);
29
27
  setDialogOpen(true);
30
- }, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Add Guarantee"] })] }), _jsx(CardContent, { children: guarantees.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: "No guarantees 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-right font-medium", children: "Amount" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Provider" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Reference" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Expires" }), _jsx("th", { className: "w-20 p-2" })] }) }), _jsx("tbody", { children: guarantees.map((g) => (_jsxs("tr", { className: "border-b last:border-b-0", children: [_jsx("td", { className: "p-2 capitalize", children: g.guaranteeType.replace(/_/g, " ") }), _jsx("td", { className: "p-2", children: _jsx(Badge, { variant: statusVariant[g.status] ?? "secondary", className: "capitalize", children: g.status.replace(/_/g, " ") }) }), _jsx("td", { className: "p-2 text-right font-mono", children: formatAmount(g.amountCents, g.currency) }), _jsx("td", { className: "p-2", children: g.provider ?? "-" }), _jsx("td", { className: "max-w-[150px] truncate p-2 font-mono text-xs", children: g.referenceNumber ?? "-" }), _jsx("td", { className: "p-2", children: g.expiresAt ? new Date(g.expiresAt).toLocaleDateString() : "-" }), _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.bookingGuaranteeList.addGuarantee] })] }), _jsx(CardContent, { children: guarantees.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: messages.bookingGuaranteeList.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.bookingGuaranteeList.columns.type }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingGuaranteeList.columns.status }), _jsx("th", { className: "p-2 text-right font-medium", children: messages.bookingGuaranteeList.columns.amount }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingGuaranteeList.columns.provider }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingGuaranteeList.columns.reference }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingGuaranteeList.columns.expires }), _jsx("th", { className: "w-20 p-2" })] }) }), _jsx("tbody", { children: guarantees.map((g) => (_jsxs("tr", { className: "border-b last:border-b-0", children: [_jsx("td", { className: "p-2", children: messages.bookingGuaranteeDialog.guaranteeTypeLabels[g.guaranteeType] }), _jsx("td", { className: "p-2", children: _jsx(Badge, { variant: statusVariant[g.status] ?? "secondary", children: messages.bookingGuaranteeDialog.guaranteeStatusLabels[g.status] }) }), _jsx("td", { className: "p-2 text-right font-mono", children: g.amountCents == null || !g.currency
29
+ ? messages.bookingGuaranteeList.values.amountUnavailable
30
+ : formatCurrency(g.amountCents / 100, g.currency) }), _jsx("td", { className: "p-2", children: g.provider ?? messages.bookingGuaranteeList.values.providerUnavailable }), _jsx("td", { className: "max-w-[150px] truncate p-2 font-mono text-xs", children: g.referenceNumber ??
31
+ messages.bookingGuaranteeList.values.referenceUnavailable }), _jsx("td", { className: "p-2", children: g.expiresAt
32
+ ? formatDate(g.expiresAt)
33
+ : messages.bookingGuaranteeList.values.expiresUnavailable }), _jsx("td", { className: "p-2", children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => {
31
34
  setEditing(g);
32
35
  setDialogOpen(true);
33
36
  }, className: "text-muted-foreground hover:text-foreground", children: _jsx(Pencil, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: () => {
34
- if (confirm("Delete this guarantee?")) {
37
+ if (confirm(messages.bookingGuaranteeList.actions.deleteConfirm)) {
35
38
  remove.mutate(g.id);
36
39
  }
37
40
  }, className: "text-muted-foreground hover:text-destructive", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }) })] }, g.id))) })] }) })) }), _jsx(BookingGuaranteeDialog, { open: dialogOpen, onOpenChange: (nextOpen) => {
@@ -1 +1 @@
1
- {"version":3,"file":"booking-item-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-item-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,iBAAiB,EAA0B,MAAM,0BAA0B,CAAA;AA4DzF,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,iBAAiB,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,IAAI,EACJ,SAAS,GACV,EAAE,sBAAsB,2CA8NxB"}
1
+ {"version":3,"file":"booking-item-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-item-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,iBAAiB,EAA0B,MAAM,0BAA0B,CAAA;AAgEzF,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,iBAAiB,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,IAAI,EACJ,SAAS,GACV,EAAE,sBAAsB,2CAmQxB"}
@@ -9,6 +9,7 @@ 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 itemTypes = [
13
14
  "unit",
14
15
  "extra",
@@ -22,24 +23,29 @@ const itemTypes = [
22
23
  "other",
23
24
  ];
24
25
  const itemStatuses = ["draft", "on_hold", "confirmed", "cancelled", "expired", "fulfilled"];
25
- const bookingItemFormSchema = z.object({
26
- title: z.string().min(1, "Title is required"),
27
- itemType: z.enum(itemTypes).default("unit"),
28
- status: z.enum(itemStatuses).default("draft"),
29
- quantity: z.coerce.number().int().positive().default(1),
30
- sellCurrency: z.string().min(3).max(3).default("EUR"),
31
- unitSellAmountCents: z.coerce.number().int().optional().nullable(),
32
- totalSellAmountCents: z.coerce.number().int().optional().nullable(),
33
- costCurrency: z.string().min(3).max(3).optional().nullable(),
34
- unitCostAmountCents: z.coerce.number().int().optional().nullable(),
35
- totalCostAmountCents: z.coerce.number().int().optional().nullable(),
36
- serviceDate: z.string().optional().nullable(),
37
- description: z.string().optional().nullable(),
38
- notes: z.string().optional().nullable(),
39
- });
26
+ const DEFAULT_CURRENCY = "EUR"; // i18n-literal-ok ISO default currency
27
+ function createBookingItemFormSchema(messages) {
28
+ return z.object({
29
+ title: z.string().min(1, messages.bookingItemDialog.validation.titleRequired),
30
+ itemType: z.enum(itemTypes).default("unit"),
31
+ status: z.enum(itemStatuses).default("draft"),
32
+ quantity: z.coerce.number().int().positive().default(1),
33
+ sellCurrency: z.string().min(3).max(3).default("EUR"),
34
+ unitSellAmountCents: z.coerce.number().int().optional().nullable(),
35
+ totalSellAmountCents: z.coerce.number().int().optional().nullable(),
36
+ costCurrency: z.string().min(3).max(3).optional().nullable(),
37
+ unitCostAmountCents: z.coerce.number().int().optional().nullable(),
38
+ totalCostAmountCents: z.coerce.number().int().optional().nullable(),
39
+ serviceDate: z.string().optional().nullable(),
40
+ description: z.string().optional().nullable(),
41
+ notes: z.string().optional().nullable(),
42
+ });
43
+ }
40
44
  export function BookingItemDialog({ open, onOpenChange, bookingId, item, onSuccess, }) {
41
45
  const isEditing = Boolean(item);
42
46
  const { create, update } = useBookingItemMutation(bookingId);
47
+ const messages = useBookingsUiMessagesOrDefault();
48
+ const bookingItemFormSchema = createBookingItemFormSchema(messages);
43
49
  const form = useForm({
44
50
  resolver: zodResolver(bookingItemFormSchema),
45
51
  defaultValues: {
@@ -47,7 +53,7 @@ export function BookingItemDialog({ open, onOpenChange, bookingId, item, onSucce
47
53
  itemType: "unit",
48
54
  status: "draft",
49
55
  quantity: 1,
50
- sellCurrency: "EUR",
56
+ sellCurrency: DEFAULT_CURRENCY,
51
57
  unitSellAmountCents: null,
52
58
  totalSellAmountCents: null,
53
59
  costCurrency: null,
@@ -106,14 +112,22 @@ export function BookingItemDialog({ open, onOpenChange, bookingId, item, onSucce
106
112
  onSuccess?.();
107
113
  };
108
114
  const isSubmitting = create.isPending || update.isPending;
109
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? "Edit Item" : "Add Item" }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "flex flex-1 flex-col overflow-hidden", children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Title" }), _jsx(Input, { ...form.register("title"), placeholder: "Room night, transfer, tour..." }), form.formState.errors.title && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.title.message }))] }), _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Type" }), _jsxs(Select, { items: itemTypes.map((t) => ({ label: t.replace("_", " "), value: t })), value: form.watch("itemType"), onValueChange: (v) => form.setValue("itemType", v), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: itemTypes.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: itemStatuses.map((s) => ({ label: s.replace("_", " "), value: s })), value: form.watch("status"), onValueChange: (v) => form.setValue("status", v), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: itemStatuses.map((s) => (_jsx(SelectItem, { value: s, children: s.replace("_", " ") }, s))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Quantity" }), _jsx(Input, { ...form.register("quantity"), type: "number", min: 1 })] })] }), _jsxs("div", { className: "grid grid-cols-3 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", {
115
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing
116
+ ? messages.bookingItemDialog.titles.edit
117
+ : messages.bookingItemDialog.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: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingItemDialog.fields.title }), _jsx(Input, { ...form.register("title"), placeholder: messages.bookingItemDialog.placeholders.title }), form.formState.errors.title && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.title.message }))] }), _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingItemDialog.fields.type }), _jsxs(Select, { items: itemTypes.map((t) => ({
118
+ label: messages.bookingItemDialog.itemTypeLabels[t],
119
+ value: t,
120
+ })), value: form.watch("itemType"), onValueChange: (v) => form.setValue("itemType", v), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: itemTypes.map((t) => (_jsx(SelectItem, { value: t, children: messages.bookingItemDialog.itemTypeLabels[t] }, t))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingItemDialog.fields.status }), _jsxs(Select, { items: itemStatuses.map((s) => ({
121
+ label: messages.bookingItemDialog.itemStatusLabels[s],
122
+ value: s,
123
+ })), value: form.watch("status"), onValueChange: (v) => form.setValue("status", v), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: itemStatuses.map((s) => (_jsx(SelectItem, { value: s, children: messages.bookingItemDialog.itemStatusLabels[s] }, s))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingItemDialog.fields.quantity }), _jsx(Input, { ...form.register("quantity"), type: "number", min: 1 })] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingItemDialog.fields.sellCurrency }), _jsx(CurrencyCombobox, { value: form.watch("sellCurrency") || null, onChange: (next) => form.setValue("sellCurrency", next ?? DEFAULT_CURRENCY, {
110
124
  shouldValidate: true,
111
125
  shouldDirty: true,
112
- }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Unit Sell (cents)" }), _jsx(Input, { ...form.register("unitSellAmountCents"), type: "number", placeholder: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Total Sell (cents)" }), _jsx(Input, { ...form.register("totalSellAmountCents"), type: "number", placeholder: "0" })] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Cost Currency" }), _jsx(CurrencyCombobox, { value: form.watch("costCurrency") || null, onChange: (next) => form.setValue("costCurrency", next ?? "EUR", {
126
+ }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingItemDialog.fields.unitSellAmountCents }), _jsx(Input, { ...form.register("unitSellAmountCents"), type: "number", placeholder: messages.bookingItemDialog.placeholders.unitSellAmountCents })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingItemDialog.fields.totalSellAmountCents }), _jsx(Input, { ...form.register("totalSellAmountCents"), type: "number", placeholder: messages.bookingItemDialog.placeholders.totalSellAmountCents })] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingItemDialog.fields.costCurrency }), _jsx(CurrencyCombobox, { value: form.watch("costCurrency") || null, onChange: (next) => form.setValue("costCurrency", next ?? DEFAULT_CURRENCY, {
113
127
  shouldValidate: true,
114
128
  shouldDirty: true,
115
- }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Unit Cost (cents)" }), _jsx(Input, { ...form.register("unitCostAmountCents"), type: "number", placeholder: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Total Cost (cents)" }), _jsx(Input, { ...form.register("totalCostAmountCents"), type: "number", placeholder: "0" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Service Date" }), _jsx(DatePicker, { value: form.watch("serviceDate") || null, onChange: (next) => form.setValue("serviceDate", next ?? "", {
129
+ }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingItemDialog.fields.unitCostAmountCents }), _jsx(Input, { ...form.register("unitCostAmountCents"), type: "number", placeholder: messages.bookingItemDialog.placeholders.unitCostAmountCents })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingItemDialog.fields.totalCostAmountCents }), _jsx(Input, { ...form.register("totalCostAmountCents"), type: "number", placeholder: messages.bookingItemDialog.placeholders.totalCostAmountCents })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingItemDialog.fields.serviceDate }), _jsx(DatePicker, { value: form.watch("serviceDate") || null, onChange: (next) => form.setValue("serviceDate", next ?? "", {
116
130
  shouldValidate: true,
117
131
  shouldDirty: true,
118
- }), placeholder: "Select service date", className: "w-full" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Description" }), _jsx(Textarea, { ...form.register("description"), placeholder: "Item description..." })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Notes" }), _jsx(Textarea, { ...form.register("notes"), placeholder: "Internal 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 Item"] })] })] })] }) }));
132
+ }), placeholder: messages.bookingItemDialog.placeholders.serviceDate, className: "w-full" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingItemDialog.fields.description }), _jsx(Textarea, { ...form.register("description"), placeholder: messages.bookingItemDialog.placeholders.description })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.bookingItemDialog.fields.notes }), _jsx(Textarea, { ...form.register("notes"), placeholder: messages.bookingItemDialog.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 ? messages.common.saveChanges : messages.bookingItemDialog.actions.addItem] })] })] })] }) }));
119
133
  }
@@ -1 +1 @@
1
- {"version":3,"file":"booking-item-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-item-list.tsx"],"names":[],"mappings":"AA4BA,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,EAAE,oBAAoB,2CAyIlE"}
1
+ {"version":3,"file":"booking-item-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-item-list.tsx"],"names":[],"mappings":"AAwBA,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,EAAE,oBAAoB,2CA6JlE"}
@@ -4,6 +4,7 @@ import { useBookingItemMutation, useBookingItems, } from "@voyantjs/bookings-rea
4
4
  import { Badge, Button, Card, CardContent, CardHeader, CardTitle } from "@voyantjs/ui/components";
5
5
  import { ChevronDown, ChevronRight, Package, Pencil, Plus, Trash2 } from "lucide-react";
6
6
  import * as React from "react";
7
+ import { useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault } from "../i18n/provider";
7
8
  import { BookingItemDialog } from "./booking-item-dialog";
8
9
  import { BookingItemTravelers } from "./booking-item-travelers";
9
10
  const statusVariant = {
@@ -14,28 +15,28 @@ const statusVariant = {
14
15
  expired: "secondary",
15
16
  fulfilled: "default",
16
17
  };
17
- function formatAmount(cents, currency) {
18
- if (cents == null)
19
- return "-";
20
- return `${(cents / 100).toFixed(2)} ${currency}`;
21
- }
22
18
  export function BookingItemList({ bookingId }) {
23
19
  const [dialogOpen, setDialogOpen] = React.useState(false);
24
20
  const [editing, setEditing] = React.useState(undefined);
25
21
  const [expandedItemId, setExpandedItemId] = React.useState(null);
26
22
  const { data } = useBookingItems(bookingId);
27
23
  const { remove } = useBookingItemMutation(bookingId);
24
+ const { formatCurrency } = useBookingsUiI18nOrDefault();
25
+ const messages = useBookingsUiMessagesOrDefault();
28
26
  const items = data?.data ?? [];
29
- return (_jsxs(Card, { "data-slot": "booking-item-list", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(Package, { className: "h-4 w-4" }), "Items"] }), _jsxs(Button, { size: "sm", onClick: () => {
27
+ return (_jsxs(Card, { "data-slot": "booking-item-list", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(Package, { className: "h-4 w-4" }), messages.bookingItemList.title] }), _jsxs(Button, { size: "sm", onClick: () => {
30
28
  setEditing(undefined);
31
29
  setDialogOpen(true);
32
- }, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Add Item"] })] }), _jsx(CardContent, { children: items.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: "No items 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: "w-8 p-2" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Title" }), _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-right font-medium", children: "Qty" }), _jsx("th", { className: "p-2 text-right font-medium", children: "Total" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Service Date" }), _jsx("th", { className: "w-20 p-2" })] }) }), _jsx("tbody", { children: items.map((item) => {
30
+ }, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), messages.bookingItemList.addItem] })] }), _jsx(CardContent, { children: items.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: messages.bookingItemList.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: "w-8 p-2" }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingItemList.columns.title }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingItemList.columns.type }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingItemList.columns.status }), _jsx("th", { className: "p-2 text-right font-medium", children: messages.bookingItemList.columns.quantity }), _jsx("th", { className: "p-2 text-right font-medium", children: messages.bookingItemList.columns.total }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.bookingItemList.columns.serviceDate }), _jsx("th", { className: "w-20 p-2" })] }) }), _jsx("tbody", { children: items.map((item) => {
33
31
  const isExpanded = expandedItemId === item.id;
34
- return (_jsxs(React.Fragment, { children: [_jsxs("tr", { className: "border-b", children: [_jsx("td", { className: "p-2", children: _jsx("button", { type: "button", onClick: () => setExpandedItemId(isExpanded ? null : item.id), className: "text-muted-foreground hover:text-foreground", children: isExpanded ? (_jsx(ChevronDown, { className: "h-3.5 w-3.5" })) : (_jsx(ChevronRight, { className: "h-3.5 w-3.5" })) }) }), _jsx("td", { className: "p-2 font-medium", children: item.title }), _jsx("td", { className: "p-2 capitalize", children: item.itemType.replace("_", " ") }), _jsx("td", { className: "p-2", children: _jsx(Badge, { variant: statusVariant[item.status] ?? "secondary", className: "capitalize", children: item.status.replace("_", " ") }) }), _jsx("td", { className: "p-2 text-right font-mono", children: item.quantity }), _jsx("td", { className: "p-2 text-right font-mono", children: formatAmount(item.totalSellAmountCents, item.sellCurrency) }), _jsx("td", { className: "p-2", children: item.serviceDate ?? "-" }), _jsx("td", { className: "p-2", children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => {
32
+ return (_jsxs(React.Fragment, { children: [_jsxs("tr", { className: "border-b", children: [_jsx("td", { className: "p-2", children: _jsx("button", { type: "button", onClick: () => setExpandedItemId(isExpanded ? null : item.id), className: "text-muted-foreground hover:text-foreground", children: isExpanded ? (_jsx(ChevronDown, { className: "h-3.5 w-3.5" })) : (_jsx(ChevronRight, { className: "h-3.5 w-3.5" })) }) }), _jsx("td", { className: "p-2 font-medium", children: item.title }), _jsx("td", { className: "p-2", children: messages.bookingItemDialog.itemTypeLabels[item.itemType] }), _jsx("td", { className: "p-2", children: _jsx(Badge, { variant: statusVariant[item.status] ?? "secondary", children: messages.bookingItemDialog.itemStatusLabels[item.status] }) }), _jsx("td", { className: "p-2 text-right font-mono", children: item.quantity }), _jsx("td", { className: "p-2 text-right font-mono", children: item.totalSellAmountCents == null
33
+ ? messages.bookingItemList.values.totalUnavailable
34
+ : formatCurrency(item.totalSellAmountCents / 100, item.sellCurrency) }), _jsx("td", { className: "p-2", children: item.serviceDate ??
35
+ messages.bookingItemList.values.serviceDateUnavailable }), _jsx("td", { className: "p-2", children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => {
35
36
  setEditing(item);
36
37
  setDialogOpen(true);
37
38
  }, className: "text-muted-foreground hover:text-foreground", children: _jsx(Pencil, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: () => {
38
- if (confirm("Delete this item?")) {
39
+ if (confirm(messages.bookingItemList.actions.deleteConfirm)) {
39
40
  remove.mutate(item.id);
40
41
  }
41
42
  }, className: "text-muted-foreground hover:text-destructive", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }) })] }), isExpanded && (_jsx("tr", { className: "border-b last:border-b-0", children: _jsx("td", { colSpan: 8, className: "p-2", children: _jsx(BookingItemTravelers, { bookingId: bookingId, itemId: item.id }) }) }))] }, item.id));
@@ -1 +1 @@
1
- {"version":3,"file":"booking-item-travelers.d.ts","sourceRoot":"","sources":["../../src/components/booking-item-travelers.tsx"],"names":[],"mappings":"AA8BA,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,yBAAyB,2CAuIpF"}
1
+ {"version":3,"file":"booking-item-travelers.d.ts","sourceRoot":"","sources":["../../src/components/booking-item-travelers.tsx"],"names":[],"mappings":"AA+BA,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,yBAAyB,2CA6IpF"}
@@ -4,6 +4,7 @@ import { useBookingItemTravelerMutation, useBookingItemTravelers, useTravelers,
4
4
  import { Badge, Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
5
5
  import { Plus, Trash2, UserCheck } from "lucide-react";
6
6
  import * as React from "react";
7
+ import { useBookingsUiMessagesOrDefault } from "../i18n/provider";
7
8
  const roles = [
8
9
  "traveler",
9
10
  "occupant",
@@ -16,6 +17,7 @@ export function BookingItemTravelers({ bookingId, itemId }) {
16
17
  const { data: travelerLinksData } = useBookingItemTravelers(bookingId, itemId);
17
18
  const { data: travelersData } = useTravelers(bookingId);
18
19
  const { add, remove } = useBookingItemTravelerMutation(bookingId, itemId);
20
+ const messages = useBookingsUiMessagesOrDefault();
19
21
  const [selectedTravelerId, setSelectedTravelerId] = React.useState("");
20
22
  const [selectedRole, setSelectedRole] = React.useState("traveler");
21
23
  const assignedTravelers = travelerLinksData?.data ?? [];
@@ -36,15 +38,18 @@ export function BookingItemTravelers({ bookingId, itemId }) {
36
38
  },
37
39
  });
38
40
  };
39
- return (_jsxs("div", { className: "space-y-3 rounded-md border bg-muted/30 p-3", children: [_jsxs("div", { className: "flex items-center gap-2 text-xs font-medium text-muted-foreground", children: [_jsx(UserCheck, { className: "h-3.5 w-3.5" }), "Assigned Travelers"] }), assignedTravelers.length === 0 ? (_jsx("p", { className: "text-xs text-muted-foreground", children: "No travelers assigned to this item." })) : (_jsx("div", { className: "space-y-1", children: assignedTravelers.map((link) => {
41
+ return (_jsxs("div", { className: "space-y-3 rounded-md border bg-muted/30 p-3", children: [_jsxs("div", { className: "flex items-center gap-2 text-xs font-medium text-muted-foreground", children: [_jsx(UserCheck, { className: "h-3.5 w-3.5" }), messages.bookingItemTravelers.title] }), assignedTravelers.length === 0 ? (_jsx("p", { className: "text-xs text-muted-foreground", children: messages.bookingItemTravelers.empty })) : (_jsx("div", { className: "space-y-1", children: assignedTravelers.map((link) => {
40
42
  const traveler = travelerMap.get(link.travelerId);
41
- return (_jsxs("div", { className: "flex items-center justify-between rounded px-2 py-1 text-sm", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { children: traveler ? `${traveler.firstName} ${traveler.lastName}` : link.travelerId }), _jsx(Badge, { variant: "outline", className: "text-xs capitalize", children: link.role.replace("_", " ") }), link.isPrimary && (_jsx(Badge, { variant: "default", className: "text-xs", children: "Primary" }))] }), _jsx("button", { type: "button", onClick: () => {
42
- if (confirm("Remove this traveler from the item?")) {
43
+ return (_jsxs("div", { className: "flex items-center justify-between rounded px-2 py-1 text-sm", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { children: traveler ? `${traveler.firstName} ${traveler.lastName}` : link.travelerId }), _jsx(Badge, { variant: "outline", className: "text-xs", children: messages.bookingItemTravelers.roleLabels[link.role] }), link.isPrimary && (_jsx(Badge, { variant: "default", className: "text-xs", children: messages.bookingItemTravelers.primaryBadge }))] }), _jsx("button", { type: "button", onClick: () => {
44
+ if (confirm(messages.bookingItemTravelers.actions.removeConfirm)) {
43
45
  remove.mutate(link.id);
44
46
  }
45
47
  }, className: "text-muted-foreground hover:text-destructive", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }, link.id));
46
48
  }) })), availableTravelers.length > 0 && (_jsxs("div", { className: "flex items-end gap-2 border-t pt-3", children: [_jsx("div", { className: "flex-1", children: _jsxs(Select, { items: availableTravelers.map((traveler) => ({
47
49
  label: `${traveler.firstName} ${traveler.lastName}`,
48
50
  value: traveler.id,
49
- })), value: selectedTravelerId, onValueChange: (v) => setSelectedTravelerId(v ?? ""), children: [_jsx(SelectTrigger, { className: "w-full h-8 text-xs", children: _jsx(SelectValue, { placeholder: "Select traveler..." }) }), _jsx(SelectContent, { children: availableTravelers.map((traveler) => (_jsxs(SelectItem, { value: traveler.id, children: [traveler.firstName, " ", traveler.lastName] }, traveler.id))) })] }) }), _jsx("div", { className: "w-36", children: _jsxs(Select, { items: roles.map((r) => ({ label: r.replace("_", " "), value: r })), value: selectedRole, onValueChange: (v) => setSelectedRole(v ?? "traveler"), children: [_jsx(SelectTrigger, { className: "w-full h-8 text-xs", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: roles.map((r) => (_jsx(SelectItem, { value: r, children: r.replace("_", " ") }, r))) })] }) }), _jsxs(Button, { size: "sm", variant: "outline", className: "h-8", onClick: handleAssign, disabled: !selectedTravelerId || add.isPending, children: [_jsx(Plus, { className: "mr-1 h-3.5 w-3.5" }), "Assign"] })] }))] }));
51
+ })), value: selectedTravelerId, onValueChange: (v) => setSelectedTravelerId(v ?? ""), children: [_jsx(SelectTrigger, { className: "w-full h-8 text-xs", children: _jsx(SelectValue, { placeholder: messages.bookingItemTravelers.selectTravelerPlaceholder }) }), _jsx(SelectContent, { children: availableTravelers.map((traveler) => (_jsxs(SelectItem, { value: traveler.id, children: [traveler.firstName, " ", traveler.lastName] }, traveler.id))) })] }) }), _jsx("div", { className: "w-36", children: _jsxs(Select, { items: roles.map((r) => ({
52
+ label: messages.bookingItemTravelers.roleLabels[r],
53
+ value: r,
54
+ })), value: selectedRole, onValueChange: (v) => setSelectedRole(v ?? "traveler"), children: [_jsx(SelectTrigger, { className: "w-full h-8 text-xs", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: roles.map((r) => (_jsx(SelectItem, { value: r, children: messages.bookingItemTravelers.roleLabels[r] }, r))) })] }) }), _jsxs(Button, { size: "sm", variant: "outline", className: "h-8", onClick: handleAssign, disabled: !selectedTravelerId || add.isPending, children: [_jsx(Plus, { className: "mr-1 h-3.5 w-3.5" }), messages.bookingItemTravelers.actions.assign] })] }))] }));
50
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"booking-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-list.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,EAInB,MAAM,0BAA0B,CAAA;AAiBjC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;CACnD;AAOD,wBAAgB,WAAW,CAAC,EAAE,QAAa,EAAE,eAAe,EAAE,GAAE,gBAAqB,2CAkJpF"}
1
+ {"version":3,"file":"booking-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-list.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,EAGnB,MAAM,0BAA0B,CAAA;AAsBjC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;CACnD;AAED,wBAAgB,WAAW,CAAC,EAAE,QAAa,EAAE,eAAe,EAAE,GAAE,gBAAqB,2CA+JpF"}
@@ -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"}