@voyantjs/bookings-ui 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -109
- package/README.md +11 -0
- package/dist/components/booking-activity-timeline.d.ts.map +1 -1
- package/dist/components/booking-activity-timeline.js +34 -14
- package/dist/components/booking-cancellation-dialog.d.ts.map +1 -1
- package/dist/components/booking-cancellation-dialog.js +15 -16
- package/dist/components/booking-create-dialog.d.ts.map +1 -1
- package/dist/components/booking-create-dialog.js +77 -13
- package/dist/components/booking-dialog.d.ts.map +1 -1
- package/dist/components/booking-dialog.js +27 -21
- package/dist/components/booking-document-dialog.d.ts.map +1 -1
- package/dist/components/booking-document-dialog.js +27 -13
- package/dist/components/booking-document-list.d.ts.map +1 -1
- package/dist/components/booking-document-list.js +9 -4
- package/dist/components/booking-group-link-dialog.d.ts.map +1 -1
- package/dist/components/booking-group-link-dialog.js +17 -6
- package/dist/components/booking-group-section.d.ts.map +1 -1
- package/dist/components/booking-group-section.js +8 -2
- package/dist/components/booking-guarantee-dialog.d.ts.map +1 -1
- package/dist/components/booking-guarantee-dialog.js +30 -14
- package/dist/components/booking-guarantee-list.d.ts.map +1 -1
- package/dist/components/booking-guarantee-list.js +11 -8
- package/dist/components/booking-item-dialog.d.ts.map +1 -1
- package/dist/components/booking-item-dialog.js +34 -20
- package/dist/components/booking-item-list.d.ts.map +1 -1
- package/dist/components/booking-item-list.js +10 -9
- package/dist/components/booking-item-travelers.d.ts.map +1 -1
- package/dist/components/booking-item-travelers.js +9 -4
- package/dist/components/booking-list.d.ts.map +1 -1
- package/dist/components/booking-list.js +17 -8
- package/dist/components/booking-notes.d.ts.map +1 -1
- package/dist/components/booking-notes.js +5 -2
- package/dist/components/booking-payment-schedule-dialog.d.ts.map +1 -1
- package/dist/components/booking-payment-schedule-dialog.js +31 -12
- package/dist/components/booking-payment-schedule-list.d.ts.map +1 -1
- package/dist/components/booking-payment-schedule-list.js +7 -6
- package/dist/components/booking-payments-summary.d.ts.map +1 -1
- package/dist/components/booking-payments-summary.js +7 -4
- package/dist/components/file-dropzone.d.ts.map +1 -1
- package/dist/components/file-dropzone.js +25 -15
- package/dist/components/passengers-section.d.ts.map +1 -1
- package/dist/components/passengers-section.js +3 -17
- package/dist/components/payment-schedule-section.d.ts.map +1 -1
- package/dist/components/payment-schedule-section.js +3 -14
- package/dist/components/person-picker-section.d.ts.map +1 -1
- package/dist/components/person-picker-section.js +3 -19
- package/dist/components/price-breakdown-section.d.ts.map +1 -1
- package/dist/components/price-breakdown-section.js +15 -18
- package/dist/components/product-picker-section.d.ts.map +1 -1
- package/dist/components/product-picker-section.js +3 -8
- package/dist/components/rooms-stepper-section.d.ts.map +1 -1
- package/dist/components/rooms-stepper-section.js +4 -9
- package/dist/components/shared-room-section.d.ts.map +1 -1
- package/dist/components/shared-room-section.js +3 -9
- package/dist/components/status-change-dialog.d.ts.map +1 -1
- package/dist/components/status-change-dialog.js +6 -1
- package/dist/components/supplier-status-dialog.d.ts.map +1 -1
- package/dist/components/supplier-status-dialog.js +31 -15
- package/dist/components/supplier-status-list.d.ts.map +1 -1
- package/dist/components/supplier-status-list.js +10 -2
- package/dist/components/traveler-dialog.d.ts.map +1 -1
- package/dist/components/traveler-dialog.js +17 -8
- package/dist/components/traveler-list.d.ts.map +1 -1
- package/dist/components/traveler-list.js +5 -3
- package/dist/components/voucher-picker-section.d.ts.map +1 -1
- package/dist/components/voucher-picker-section.js +11 -26
- package/dist/i18n/en.d.ts +797 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +796 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +3 -0
- package/dist/i18n/messages.d.ts +684 -0
- package/dist/i18n/messages.d.ts.map +1 -0
- package/dist/i18n/messages.js +1 -0
- package/dist/i18n/provider.d.ts +1617 -0
- package/dist/i18n/provider.d.ts.map +1 -0
- package/dist/i18n/provider.js +45 -0
- package/dist/i18n/ro.d.ts +797 -0
- package/dist/i18n/ro.d.ts.map +1 -0
- package/dist/i18n/ro.js +796 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +40 -20
|
@@ -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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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":"
|
|
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" }),
|
|
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" }),
|
|
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(
|
|
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;
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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":"
|
|
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" }),
|
|
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" }),
|
|
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
|
|
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(
|
|
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":"
|
|
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" }),
|
|
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
|
|
42
|
-
if (confirm(
|
|
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:
|
|
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,
|
|
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,
|
|
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:
|
|
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" }),
|
|
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":"
|
|
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:
|
|
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" }) :
|
|
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;
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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":"
|
|
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"}
|