@voyantjs/bookings-ui 0.13.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 (101) hide show
  1. package/README.md +13 -0
  2. package/dist/components/booking-activity-timeline.d.ts +5 -0
  3. package/dist/components/booking-activity-timeline.d.ts.map +1 -0
  4. package/dist/components/booking-activity-timeline.js +83 -0
  5. package/dist/components/booking-cancellation-dialog.d.ts +18 -0
  6. package/dist/components/booking-cancellation-dialog.d.ts.map +1 -0
  7. package/dist/components/booking-cancellation-dialog.js +80 -0
  8. package/dist/components/booking-create-dialog.d.ts +21 -0
  9. package/dist/components/booking-create-dialog.d.ts.map +1 -0
  10. package/dist/components/booking-create-dialog.js +313 -0
  11. package/dist/components/booking-dialog.d.ts +23 -0
  12. package/dist/components/booking-dialog.d.ts.map +1 -0
  13. package/dist/components/booking-dialog.js +108 -0
  14. package/dist/components/booking-document-dialog.d.ts +8 -0
  15. package/dist/components/booking-document-dialog.d.ts.map +1 -0
  16. package/dist/components/booking-document-dialog.js +67 -0
  17. package/dist/components/booking-document-list.d.ts +5 -0
  18. package/dist/components/booking-document-list.d.ts.map +1 -0
  19. package/dist/components/booking-document-list.js +38 -0
  20. package/dist/components/booking-group-link-dialog.d.ts +10 -0
  21. package/dist/components/booking-group-link-dialog.d.ts.map +1 -0
  22. package/dist/components/booking-group-link-dialog.js +68 -0
  23. package/dist/components/booking-group-section.d.ts +17 -0
  24. package/dist/components/booking-group-section.d.ts.map +1 -0
  25. package/dist/components/booking-group-section.js +31 -0
  26. package/dist/components/booking-guarantee-dialog.d.ts +10 -0
  27. package/dist/components/booking-guarantee-dialog.d.ts.map +1 -0
  28. package/dist/components/booking-guarantee-dialog.js +101 -0
  29. package/dist/components/booking-guarantee-list.d.ts +5 -0
  30. package/dist/components/booking-guarantee-list.d.ts.map +1 -0
  31. package/dist/components/booking-guarantee-list.js +45 -0
  32. package/dist/components/booking-item-dialog.d.ts +10 -0
  33. package/dist/components/booking-item-dialog.d.ts.map +1 -0
  34. package/dist/components/booking-item-dialog.js +119 -0
  35. package/dist/components/booking-item-list.d.ts +5 -0
  36. package/dist/components/booking-item-list.d.ts.map +1 -0
  37. package/dist/components/booking-item-list.js +50 -0
  38. package/dist/components/booking-item-travelers.d.ts +6 -0
  39. package/dist/components/booking-item-travelers.d.ts.map +1 -0
  40. package/dist/components/booking-item-travelers.js +50 -0
  41. package/dist/components/booking-list.d.ts +7 -0
  42. package/dist/components/booking-list.d.ts.map +1 -0
  43. package/dist/components/booking-list.js +47 -0
  44. package/dist/components/booking-notes.d.ts +5 -0
  45. package/dist/components/booking-notes.d.ts.map +1 -0
  46. package/dist/components/booking-notes.js +16 -0
  47. package/dist/components/booking-payment-schedule-dialog.d.ts +10 -0
  48. package/dist/components/booking-payment-schedule-dialog.d.ts.map +1 -0
  49. package/dist/components/booking-payment-schedule-dialog.js +77 -0
  50. package/dist/components/booking-payment-schedule-list.d.ts +5 -0
  51. package/dist/components/booking-payment-schedule-list.d.ts.map +1 -0
  52. package/dist/components/booking-payment-schedule-list.js +43 -0
  53. package/dist/components/booking-payments-summary.d.ts +5 -0
  54. package/dist/components/booking-payments-summary.d.ts.map +1 -0
  55. package/dist/components/booking-payments-summary.js +19 -0
  56. package/dist/components/file-dropzone.d.ts +25 -0
  57. package/dist/components/file-dropzone.d.ts.map +1 -0
  58. package/dist/components/file-dropzone.js +92 -0
  59. package/dist/components/passengers-section.d.ts +72 -0
  60. package/dist/components/passengers-section.d.ts.map +1 -0
  61. package/dist/components/passengers-section.js +74 -0
  62. package/dist/components/payment-schedule-section.d.ts +62 -0
  63. package/dist/components/payment-schedule-section.d.ts.map +1 -0
  64. package/dist/components/payment-schedule-section.js +88 -0
  65. package/dist/components/person-picker-section.d.ts +53 -0
  66. package/dist/components/person-picker-section.d.ts.map +1 -0
  67. package/dist/components/person-picker-section.js +71 -0
  68. package/dist/components/price-breakdown-section.d.ts +48 -0
  69. package/dist/components/price-breakdown-section.d.ts.map +1 -0
  70. package/dist/components/price-breakdown-section.js +165 -0
  71. package/dist/components/product-picker-section.d.ts +27 -0
  72. package/dist/components/product-picker-section.d.ts.map +1 -0
  73. package/dist/components/product-picker-section.js +41 -0
  74. package/dist/components/rooms-stepper-section.d.ts +45 -0
  75. package/dist/components/rooms-stepper-section.d.ts.map +1 -0
  76. package/dist/components/rooms-stepper-section.js +60 -0
  77. package/dist/components/shared-room-section.d.ts +37 -0
  78. package/dist/components/shared-room-section.d.ts.map +1 -0
  79. package/dist/components/shared-room-section.js +40 -0
  80. package/dist/components/status-change-dialog.d.ts +10 -0
  81. package/dist/components/status-change-dialog.d.ts.map +1 -0
  82. package/dist/components/status-change-dialog.js +41 -0
  83. package/dist/components/supplier-status-dialog.d.ts +10 -0
  84. package/dist/components/supplier-status-dialog.d.ts.map +1 -0
  85. package/dist/components/supplier-status-dialog.js +77 -0
  86. package/dist/components/supplier-status-list.d.ts +5 -0
  87. package/dist/components/supplier-status-list.d.ts.map +1 -0
  88. package/dist/components/supplier-status-list.js +33 -0
  89. package/dist/components/traveler-dialog.d.ts +10 -0
  90. package/dist/components/traveler-dialog.d.ts.map +1 -0
  91. package/dist/components/traveler-dialog.js +64 -0
  92. package/dist/components/traveler-list.d.ts +5 -0
  93. package/dist/components/traveler-list.d.ts.map +1 -0
  94. package/dist/components/traveler-list.js +32 -0
  95. package/dist/components/voucher-picker-section.d.ts +50 -0
  96. package/dist/components/voucher-picker-section.d.ts.map +1 -0
  97. package/dist/components/voucher-picker-section.js +94 -0
  98. package/dist/index.d.ts +33 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +32 -0
  101. package/package.json +76 -0
@@ -0,0 +1,119 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useBookingItemMutation } from "@voyantjs/bookings-react";
4
+ import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea, } from "@voyantjs/voyant-ui/components";
5
+ import { CurrencyCombobox } from "@voyantjs/voyant-ui/components/currency-combobox";
6
+ import { DatePicker } from "@voyantjs/voyant-ui/components/date-picker";
7
+ import { zodResolver } from "@voyantjs/voyant-ui/lib/zod-resolver";
8
+ import { Loader2 } from "lucide-react";
9
+ import { useEffect } from "react";
10
+ import { useForm } from "react-hook-form";
11
+ import { z } from "zod/v4";
12
+ const itemTypes = [
13
+ "unit",
14
+ "extra",
15
+ "service",
16
+ "fee",
17
+ "tax",
18
+ "discount",
19
+ "adjustment",
20
+ "accommodation",
21
+ "transport",
22
+ "other",
23
+ ];
24
+ 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
+ });
40
+ export function BookingItemDialog({ open, onOpenChange, bookingId, item, onSuccess, }) {
41
+ const isEditing = Boolean(item);
42
+ const { create, update } = useBookingItemMutation(bookingId);
43
+ const form = useForm({
44
+ resolver: zodResolver(bookingItemFormSchema),
45
+ defaultValues: {
46
+ title: "",
47
+ itemType: "unit",
48
+ status: "draft",
49
+ quantity: 1,
50
+ sellCurrency: "EUR",
51
+ unitSellAmountCents: null,
52
+ totalSellAmountCents: null,
53
+ costCurrency: null,
54
+ unitCostAmountCents: null,
55
+ totalCostAmountCents: null,
56
+ serviceDate: "",
57
+ description: "",
58
+ notes: "",
59
+ },
60
+ });
61
+ useEffect(() => {
62
+ if (open && item) {
63
+ form.reset({
64
+ title: item.title,
65
+ itemType: item.itemType,
66
+ status: item.status,
67
+ quantity: item.quantity,
68
+ sellCurrency: item.sellCurrency,
69
+ unitSellAmountCents: item.unitSellAmountCents,
70
+ totalSellAmountCents: item.totalSellAmountCents,
71
+ costCurrency: item.costCurrency,
72
+ unitCostAmountCents: item.unitCostAmountCents,
73
+ totalCostAmountCents: item.totalCostAmountCents,
74
+ serviceDate: item.serviceDate ?? "",
75
+ description: item.description ?? "",
76
+ notes: item.notes ?? "",
77
+ });
78
+ }
79
+ else if (open) {
80
+ form.reset();
81
+ }
82
+ }, [form, open, item]);
83
+ const onSubmit = async (values) => {
84
+ const payload = {
85
+ title: values.title,
86
+ itemType: values.itemType,
87
+ status: values.status,
88
+ quantity: values.quantity,
89
+ sellCurrency: values.sellCurrency,
90
+ unitSellAmountCents: values.unitSellAmountCents || null,
91
+ totalSellAmountCents: values.totalSellAmountCents || null,
92
+ costCurrency: values.costCurrency || null,
93
+ unitCostAmountCents: values.unitCostAmountCents || null,
94
+ totalCostAmountCents: values.totalCostAmountCents || null,
95
+ serviceDate: values.serviceDate || null,
96
+ description: values.description || null,
97
+ notes: values.notes || null,
98
+ };
99
+ if (isEditing) {
100
+ await update.mutateAsync({ id: item.id, input: payload });
101
+ }
102
+ else {
103
+ await create.mutateAsync(payload);
104
+ }
105
+ onOpenChange(false);
106
+ onSuccess?.();
107
+ };
108
+ 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", {
110
+ shouldValidate: true,
111
+ 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", {
113
+ shouldValidate: true,
114
+ 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 ?? "", {
116
+ shouldValidate: true,
117
+ 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"] })] })] })] }) }));
119
+ }
@@ -0,0 +1,5 @@
1
+ export interface BookingItemListProps {
2
+ bookingId: string;
3
+ }
4
+ export declare function BookingItemList({ bookingId }: BookingItemListProps): import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=booking-item-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"booking-item-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-item-list.tsx"],"names":[],"mappings":"AAmCA,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,EAAE,oBAAoB,2CAyIlE"}
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useBookingItemMutation, useBookingItems, } from "@voyantjs/bookings-react";
4
+ import { Badge, Button, Card, CardContent, CardHeader, CardTitle, } from "@voyantjs/voyant-ui/components";
5
+ import { ChevronDown, ChevronRight, Package, Pencil, Plus, Trash2 } from "lucide-react";
6
+ import * as React from "react";
7
+ import { BookingItemDialog } from "./booking-item-dialog";
8
+ import { BookingItemTravelers } from "./booking-item-travelers";
9
+ const statusVariant = {
10
+ draft: "outline",
11
+ on_hold: "secondary",
12
+ confirmed: "default",
13
+ cancelled: "destructive",
14
+ expired: "secondary",
15
+ fulfilled: "default",
16
+ };
17
+ function formatAmount(cents, currency) {
18
+ if (cents == null)
19
+ return "-";
20
+ return `${(cents / 100).toFixed(2)} ${currency}`;
21
+ }
22
+ export function BookingItemList({ bookingId }) {
23
+ const [dialogOpen, setDialogOpen] = React.useState(false);
24
+ const [editing, setEditing] = React.useState(undefined);
25
+ const [expandedItemId, setExpandedItemId] = React.useState(null);
26
+ const { data } = useBookingItems(bookingId);
27
+ const { remove } = useBookingItemMutation(bookingId);
28
+ 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: () => {
30
+ setEditing(undefined);
31
+ 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) => {
33
+ 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: () => {
35
+ setEditing(item);
36
+ setDialogOpen(true);
37
+ }, 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
+ remove.mutate(item.id);
40
+ }
41
+ }, 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));
42
+ }) })] }) })) }), _jsx(BookingItemDialog, { open: dialogOpen, onOpenChange: (nextOpen) => {
43
+ setDialogOpen(nextOpen);
44
+ if (!nextOpen) {
45
+ setEditing(undefined);
46
+ }
47
+ }, bookingId: bookingId, item: editing, onSuccess: () => {
48
+ setEditing(undefined);
49
+ } })] }));
50
+ }
@@ -0,0 +1,6 @@
1
+ export interface BookingItemTravelersProps {
2
+ bookingId: string;
3
+ itemId: string;
4
+ }
5
+ export declare function BookingItemTravelers({ bookingId, itemId }: BookingItemTravelersProps): import("react/jsx-runtime").JSX.Element;
6
+ //# sourceMappingURL=booking-item-travelers.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useBookingItemTravelerMutation, useBookingItemTravelers, useTravelers, } from "@voyantjs/bookings-react";
4
+ import { Badge, Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/voyant-ui/components";
5
+ import { Plus, Trash2, UserCheck } from "lucide-react";
6
+ import * as React from "react";
7
+ const roles = [
8
+ "traveler",
9
+ "occupant",
10
+ "primary_contact",
11
+ "service_assignee",
12
+ "beneficiary",
13
+ "other",
14
+ ];
15
+ export function BookingItemTravelers({ bookingId, itemId }) {
16
+ const { data: travelerLinksData } = useBookingItemTravelers(bookingId, itemId);
17
+ const { data: travelersData } = useTravelers(bookingId);
18
+ const { add, remove } = useBookingItemTravelerMutation(bookingId, itemId);
19
+ const [selectedTravelerId, setSelectedTravelerId] = React.useState("");
20
+ const [selectedRole, setSelectedRole] = React.useState("traveler");
21
+ const assignedTravelers = travelerLinksData?.data ?? [];
22
+ const travelers = travelersData?.data ?? [];
23
+ const assignedIds = new Set(assignedTravelers.map((link) => link.travelerId));
24
+ const availableTravelers = travelers.filter((traveler) => !assignedIds.has(traveler.id));
25
+ const travelerMap = new Map();
26
+ for (const traveler of travelers) {
27
+ travelerMap.set(traveler.id, traveler);
28
+ }
29
+ const handleAssign = () => {
30
+ if (!selectedTravelerId)
31
+ return;
32
+ add.mutate({ travelerId: selectedTravelerId, role: selectedRole }, {
33
+ onSuccess: () => {
34
+ setSelectedTravelerId("");
35
+ setSelectedRole("traveler");
36
+ },
37
+ });
38
+ };
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) => {
40
+ 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
+ remove.mutate(link.id);
44
+ }
45
+ }, className: "text-muted-foreground hover:text-destructive", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }, link.id));
46
+ }) })), 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
+ label: `${traveler.firstName} ${traveler.lastName}`,
48
+ 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"] })] }))] }));
50
+ }
@@ -0,0 +1,7 @@
1
+ import { type BookingRecord } from "@voyantjs/bookings-react";
2
+ export interface BookingListProps {
3
+ pageSize?: number;
4
+ onSelectBooking?: (booking: BookingRecord) => void;
5
+ }
6
+ export declare function BookingList({ pageSize, onSelectBooking }?: BookingListProps): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=booking-list.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,47 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { bookingStatusBadgeVariant, formatBookingStatus, useBookings, } from "@voyantjs/bookings-react";
4
+ import { Badge } from "@voyantjs/voyant-ui/components/badge";
5
+ import { Button } from "@voyantjs/voyant-ui/components/button";
6
+ import { Input } from "@voyantjs/voyant-ui/components/input";
7
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyantjs/voyant-ui/components/table";
8
+ import { Loader2, Plus, Search } from "lucide-react";
9
+ import * as React from "react";
10
+ 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
+ export function BookingList({ pageSize = 25, onSelectBooking } = {}) {
17
+ const [search, setSearch] = React.useState("");
18
+ const [offset, setOffset] = React.useState(0);
19
+ const [dialogOpen, setDialogOpen] = React.useState(false);
20
+ const [editing, setEditing] = React.useState(undefined);
21
+ const { data, isPending, isError } = useBookings({
22
+ search: search || undefined,
23
+ limit: pageSize,
24
+ offset,
25
+ });
26
+ const bookings = data?.data ?? [];
27
+ const total = data?.total ?? 0;
28
+ const page = Math.floor(offset / pageSize) + 1;
29
+ const pageCount = Math.max(1, Math.ceil(total / pageSize));
30
+ const handleSelect = (booking) => {
31
+ if (onSelectBooking) {
32
+ onSelectBooking(booking);
33
+ return;
34
+ }
35
+ setEditing(booking);
36
+ setDialogOpen(true);
37
+ };
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) => {
39
+ setSearch(event.target.value);
40
+ setOffset(0);
41
+ }, className: "pl-9" })] }), _jsx("div", { className: "flex items-center gap-2", children: _jsxs(Button, { onClick: () => {
42
+ setEditing(undefined);
43
+ 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) => {
45
+ onSelectBooking?.(booking);
46
+ } })] }));
47
+ }
@@ -0,0 +1,5 @@
1
+ export interface BookingNotesProps {
2
+ bookingId: string;
3
+ }
4
+ export declare function BookingNotes({ bookingId }: BookingNotesProps): import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=booking-notes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"booking-notes.d.ts","sourceRoot":"","sources":["../../src/components/booking-notes.tsx"],"names":[],"mappings":"AAcA,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,EAAE,iBAAiB,2CA+C5D"}
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useBookingNoteMutation, useBookingNotes } from "@voyantjs/bookings-react";
4
+ import { Button, Card, CardContent, CardHeader, CardTitle, Textarea, } from "@voyantjs/voyant-ui/components";
5
+ import { Loader2 } from "lucide-react";
6
+ import * as React from "react";
7
+ export function BookingNotes({ bookingId }) {
8
+ const [content, setContent] = React.useState("");
9
+ const { data } = useBookingNotes(bookingId);
10
+ const mutation = useBookingNoteMutation(bookingId);
11
+ 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 () => {
13
+ await mutation.mutateAsync({ content: content.trim() });
14
+ 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))))] })] }));
16
+ }
@@ -0,0 +1,10 @@
1
+ import { type BookingPaymentScheduleRecord } from "@voyantjs/finance-react";
2
+ export interface BookingPaymentScheduleDialogProps {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ bookingId: string;
6
+ schedule?: BookingPaymentScheduleRecord;
7
+ onSuccess?: () => void;
8
+ }
9
+ export declare function BookingPaymentScheduleDialog({ open, onOpenChange, bookingId, schedule, onSuccess, }: BookingPaymentScheduleDialogProps): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=booking-payment-schedule-dialog.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,77 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useBookingPaymentScheduleMutation, } from "@voyantjs/finance-react";
4
+ import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea, } from "@voyantjs/voyant-ui/components";
5
+ import { CurrencyCombobox } from "@voyantjs/voyant-ui/components/currency-combobox";
6
+ import { DatePicker } from "@voyantjs/voyant-ui/components/date-picker";
7
+ import { zodResolver } from "@voyantjs/voyant-ui/lib/zod-resolver";
8
+ import { Loader2 } from "lucide-react";
9
+ import { useEffect } from "react";
10
+ import { useForm } from "react-hook-form";
11
+ import { z } from "zod/v4";
12
+ const scheduleTypes = ["deposit", "installment", "balance", "hold", "other"];
13
+ 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
+ });
22
+ export function BookingPaymentScheduleDialog({ open, onOpenChange, bookingId, schedule, onSuccess, }) {
23
+ const isEditing = Boolean(schedule);
24
+ const { create, update } = useBookingPaymentScheduleMutation(bookingId);
25
+ const form = useForm({
26
+ resolver: zodResolver(scheduleFormSchema),
27
+ defaultValues: {
28
+ scheduleType: "balance",
29
+ status: "pending",
30
+ dueDate: "",
31
+ currency: "EUR",
32
+ amountCents: 0,
33
+ notes: "",
34
+ },
35
+ });
36
+ useEffect(() => {
37
+ if (open && schedule) {
38
+ form.reset({
39
+ scheduleType: schedule.scheduleType,
40
+ status: schedule.status,
41
+ dueDate: schedule.dueDate,
42
+ currency: schedule.currency,
43
+ amountCents: schedule.amountCents,
44
+ notes: schedule.notes ?? "",
45
+ });
46
+ }
47
+ else if (open) {
48
+ form.reset();
49
+ }
50
+ }, [form, open, schedule]);
51
+ const onSubmit = async (values) => {
52
+ const payload = {
53
+ scheduleType: values.scheduleType,
54
+ status: values.status,
55
+ dueDate: values.dueDate,
56
+ currency: values.currency,
57
+ amountCents: values.amountCents,
58
+ notes: values.notes || null,
59
+ };
60
+ if (isEditing) {
61
+ await update.mutateAsync({ id: schedule.id, input: payload });
62
+ }
63
+ else {
64
+ await create.mutateAsync(payload);
65
+ }
66
+ onOpenChange(false);
67
+ onSuccess?.();
68
+ };
69
+ 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 ?? "", {
71
+ shouldValidate: true,
72
+ 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", {
74
+ shouldValidate: true,
75
+ 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"] })] })] })] }) }));
77
+ }
@@ -0,0 +1,5 @@
1
+ export interface BookingPaymentScheduleListProps {
2
+ bookingId: string;
3
+ }
4
+ export declare function BookingPaymentScheduleList({ bookingId }: BookingPaymentScheduleListProps): import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=booking-payment-schedule-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"booking-payment-schedule-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-payment-schedule-list.tsx"],"names":[],"mappings":"AAiCA,MAAM,WAAW,+BAA+B;IAC9C,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,0BAA0B,CAAC,EAAE,SAAS,EAAE,EAAE,+BAA+B,2CAgHxF"}
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useBookingPaymentScheduleMutation, useBookingPaymentSchedules, } from "@voyantjs/finance-react";
4
+ import { Badge, Button, Card, CardContent, CardHeader, CardTitle, } from "@voyantjs/voyant-ui/components";
5
+ import { CalendarClock, Pencil, Plus, Trash2 } from "lucide-react";
6
+ import * as React from "react";
7
+ import { BookingPaymentScheduleDialog } from "./booking-payment-schedule-dialog";
8
+ const statusVariant = {
9
+ pending: "outline",
10
+ due: "secondary",
11
+ paid: "default",
12
+ waived: "secondary",
13
+ cancelled: "destructive",
14
+ expired: "secondary",
15
+ };
16
+ function formatAmount(cents, currency) {
17
+ return `${(cents / 100).toFixed(2)} ${currency}`;
18
+ }
19
+ export function BookingPaymentScheduleList({ bookingId }) {
20
+ const [dialogOpen, setDialogOpen] = React.useState(false);
21
+ const [editing, setEditing] = React.useState(undefined);
22
+ const { data } = useBookingPaymentSchedules(bookingId);
23
+ const { remove } = useBookingPaymentScheduleMutation(bookingId);
24
+ const schedules = data?.data ?? [];
25
+ return (_jsxs(Card, { "data-slot": "booking-payment-schedule-list", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(CalendarClock, { className: "h-4 w-4" }), "Payment Schedule"] }), _jsxs(Button, { size: "sm", onClick: () => {
26
+ setEditing(undefined);
27
+ setDialogOpen(true);
28
+ }, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Add Schedule"] })] }), _jsx(CardContent, { children: schedules.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: "No payment schedules yet." })) : (_jsx("div", { className: "rounded border bg-background", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b text-muted-foreground", children: [_jsx("th", { className: "p-2 text-left font-medium", children: "Type" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Status" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Due Date" }), _jsx("th", { className: "p-2 text-right font-medium", children: "Amount" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Notes" }), _jsx("th", { className: "w-20 p-2" })] }) }), _jsx("tbody", { children: schedules.map((schedule) => (_jsxs("tr", { className: "border-b last:border-b-0", children: [_jsx("td", { className: "p-2 capitalize", children: schedule.scheduleType.replace("_", " ") }), _jsx("td", { className: "p-2", children: _jsx(Badge, { variant: statusVariant[schedule.status] ?? "secondary", className: "capitalize", children: schedule.status.replace("_", " ") }) }), _jsx("td", { className: "p-2", children: schedule.dueDate }), _jsx("td", { className: "p-2 text-right font-mono", children: formatAmount(schedule.amountCents, schedule.currency) }), _jsx("td", { className: "max-w-[200px] truncate p-2 text-muted-foreground", children: schedule.notes ?? "-" }), _jsx("td", { className: "p-2", children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => {
29
+ setEditing(schedule);
30
+ setDialogOpen(true);
31
+ }, className: "text-muted-foreground hover:text-foreground", children: _jsx(Pencil, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: () => {
32
+ if (confirm("Delete this payment schedule?")) {
33
+ remove.mutate(schedule.id);
34
+ }
35
+ }, className: "text-muted-foreground hover:text-destructive", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }) })] }, schedule.id))) })] }) })) }), _jsx(BookingPaymentScheduleDialog, { open: dialogOpen, onOpenChange: (nextOpen) => {
36
+ setDialogOpen(nextOpen);
37
+ if (!nextOpen) {
38
+ setEditing(undefined);
39
+ }
40
+ }, bookingId: bookingId, schedule: editing, onSuccess: () => {
41
+ setEditing(undefined);
42
+ } })] }));
43
+ }
@@ -0,0 +1,5 @@
1
+ export interface BookingPaymentsSummaryProps {
2
+ bookingId: string;
3
+ }
4
+ export declare function BookingPaymentsSummary({ bookingId }: BookingPaymentsSummaryProps): import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=booking-payments-summary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"booking-payments-summary.d.ts","sourceRoot":"","sources":["../../src/components/booking-payments-summary.tsx"],"names":[],"mappings":"AAiBA,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,sBAAsB,CAAC,EAAE,SAAS,EAAE,EAAE,2BAA2B,2CA0DhF"}
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { usePublicBookingPayments } from "@voyantjs/finance-react";
4
+ import { Badge, Card, CardContent, CardHeader, CardTitle } from "@voyantjs/voyant-ui/components";
5
+ import { CreditCard } from "lucide-react";
6
+ const statusVariant = {
7
+ pending: "outline",
8
+ completed: "default",
9
+ failed: "destructive",
10
+ refunded: "secondary",
11
+ };
12
+ function formatAmount(cents, currency) {
13
+ return `${(cents / 100).toFixed(2)} ${currency}`;
14
+ }
15
+ export function BookingPaymentsSummary({ bookingId }) {
16
+ const { data } = usePublicBookingPayments(bookingId);
17
+ const payments = data?.data?.payments ?? [];
18
+ return (_jsxs(Card, { "data-slot": "booking-payments-summary", children: [_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(CreditCard, { className: "h-4 w-4" }), "Payments"] }) }), _jsx(CardContent, { children: payments.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: "No payments recorded." })) : (_jsx("div", { className: "rounded border bg-background", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b text-muted-foreground", children: [_jsx("th", { className: "p-2 text-left font-medium", children: "Invoice" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Method" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Status" }), _jsx("th", { className: "p-2 text-right font-medium", children: "Amount" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Date" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Reference" })] }) }), _jsx("tbody", { children: payments.map((payment) => (_jsxs("tr", { className: "border-b last:border-b-0", children: [_jsx("td", { className: "p-2 font-mono text-xs", children: payment.invoiceNumber }), _jsx("td", { className: "p-2 capitalize", children: payment.paymentMethod.replace(/_/g, " ") }), _jsx("td", { className: "p-2", children: _jsx(Badge, { variant: statusVariant[payment.status] ?? "secondary", className: "capitalize", children: payment.status }) }), _jsx("td", { className: "p-2 text-right font-mono", children: formatAmount(payment.amountCents, payment.currency) }), _jsx("td", { className: "p-2", children: new Date(payment.paymentDate).toLocaleDateString() }), _jsx("td", { className: "max-w-[150px] truncate p-2 font-mono text-xs", children: payment.referenceNumber ?? "-" })] }, payment.id))) })] }) })) })] }));
19
+ }
@@ -0,0 +1,25 @@
1
+ export interface UploadedFile {
2
+ key: string;
3
+ url: string;
4
+ mimeType: string;
5
+ size: number;
6
+ name: string;
7
+ }
8
+ export interface FileDropzoneProps {
9
+ /** URL of the upload endpoint (defaults to /api/v1/uploads). */
10
+ uploadUrl?: string;
11
+ /** MIME types or extensions to accept (same format as <input accept>). */
12
+ accept?: string;
13
+ /** Maximum file size in bytes. */
14
+ maxSize?: number;
15
+ /** Called after a successful upload. */
16
+ onUploaded: (file: UploadedFile) => void;
17
+ /** Called when an error occurs (validation, upload failure). */
18
+ onError?: (message: string) => void;
19
+ /** Helper text shown in the idle state. */
20
+ helperText?: string;
21
+ /** Disable interaction. */
22
+ disabled?: boolean;
23
+ }
24
+ export declare function FileDropzone({ uploadUrl, accept, maxSize, onUploaded, onError, helperText, disabled, }: FileDropzoneProps): import("react/jsx-runtime").JSX.Element;
25
+ //# sourceMappingURL=file-dropzone.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-dropzone.d.ts","sourceRoot":"","sources":["../../src/components/file-dropzone.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,wCAAwC;IACxC,UAAU,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAA;IACxC,gEAAgE;IAChE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAQD,wBAAgB,YAAY,CAAC,EAC3B,SAA6B,EAC7B,MAAM,EACN,OAAO,EACP,UAAU,EACV,OAAO,EACP,UAA4D,EAC5D,QAAQ,GACT,EAAE,iBAAiB,2CAgJnB"}