@voyantjs/bookings-ui 0.80.18 → 0.81.5
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/dist/components/booking-create-dialog.d.ts +0 -18
- package/dist/components/booking-create-dialog.d.ts.map +1 -1
- package/dist/components/booking-create-dialog.js +126 -221
- package/dist/components/booking-create-utils.d.ts +1 -1
- package/dist/components/booking-create-utils.d.ts.map +1 -1
- package/dist/components/booking-create-utils.js +26 -8
- package/dist/components/booking-detail-page.d.ts.map +1 -1
- package/dist/components/booking-detail-page.js +3 -1
- package/dist/components/booking-payments-summary.d.ts +4 -1
- package/dist/components/booking-payments-summary.d.ts.map +1 -1
- package/dist/components/booking-payments-summary.js +21 -4
- package/dist/components/option-units-stepper-section.d.ts +4 -1
- package/dist/components/option-units-stepper-section.d.ts.map +1 -1
- package/dist/components/option-units-stepper-section.js +7 -2
- package/dist/components/traveler-category-buttons.d.ts +1 -1
- package/dist/components/traveler-category-buttons.d.ts.map +1 -1
- package/dist/components/traveler-category-buttons.js +3 -3
- package/dist/components/travelers-section.d.ts +12 -7
- package/dist/components/travelers-section.d.ts.map +1 -1
- package/dist/components/travelers-section.js +148 -139
- package/dist/i18n/en.d.ts +5 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +5 -0
- package/dist/i18n/messages.d.ts +5 -0
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/i18n/provider.d.ts +10 -0
- package/dist/i18n/provider.d.ts.map +1 -1
- package/dist/i18n/ro.d.ts +5 -0
- package/dist/i18n/ro.d.ts.map +1 -1
- package/dist/i18n/ro.js +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +32 -30
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"booking-detail-page.d.ts","sourceRoot":"","sources":["../../src/components/booking-detail-page.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,EAInB,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"booking-detail-page.d.ts","sourceRoot":"","sources":["../../src/components/booking-detail-page.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,EAInB,MAAM,0BAA0B,CAAA;AAgCjC,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAA;AAkBhD;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,6EAA6E;IAC7E,OAAO,EAAE,SAAS,GAAG,CAAC,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAC,CAAA;CAC7D;AAED,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IAC9C,sDAAsD;IACtD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACpD,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACrD,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACnD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACtD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACpD,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IAClD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACjD,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACnD,wEAAwE;IACxE,WAAW,CAAC,EAAE,oBAAoB,CAAA;IAClC,wDAAwD;IACxD,SAAS,CAAC,EAAE,oBAAoB,CAAA;CACjC;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;gEAC4D;IAC5D,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,kBAAkB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAA;IACrD,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IACnD,wEAAwE;IACxE,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAClD,KAAK,CAAC,EAAE,sBAAsB,CAAA;CAC/B;AAED,wBAAgB,iBAAiB,CAAC,EAChC,EAAE,EACF,SAAS,EACT,MAAM,EACN,cAAc,EACd,MAAM,EACN,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,KAAK,GACN,EAAE,sBAAsB,2CAiSxB;AASD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,aAAa,CAAA;CAAE,2CA8DhF"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { bookingStatusBadgeVariant, useBooking, useBookingMutation, } from "@voyantjs/bookings-react";
|
|
4
4
|
import { useOrganization, usePerson } from "@voyantjs/crm-react";
|
|
5
|
+
import { useInvoiceMutation } from "@voyantjs/finance-react";
|
|
5
6
|
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, cn, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@voyantjs/ui/components";
|
|
6
7
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@voyantjs/ui/components/tabs";
|
|
7
8
|
import { Ban, Calendar, ChevronRight, CreditCard, Mail, MapPin, MoreHorizontal, Pencil, Phone, RefreshCw, Trash2, Users, } from "lucide-react";
|
|
@@ -31,6 +32,7 @@ export function BookingDetailPage({ id, className, locale, hideBreadcrumb, onBac
|
|
|
31
32
|
const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
|
|
32
33
|
const { data: bookingData, isPending } = useBooking(id);
|
|
33
34
|
const { remove } = useBookingMutation();
|
|
35
|
+
const { convertToInvoice } = useInvoiceMutation();
|
|
34
36
|
if (isPending) {
|
|
35
37
|
return (_jsx("div", { className: cn("flex items-center justify-center py-12", className), children: _jsx("p", { className: "text-sm text-muted-foreground", children: messages.common.loading }) }));
|
|
36
38
|
}
|
|
@@ -49,7 +51,7 @@ export function BookingDetailPage({ id, className, locale, hideBreadcrumb, onBac
|
|
|
49
51
|
}
|
|
50
52
|
}, children: [_jsx(Trash2, { className: "h-4 w-4", "aria-hidden": "true" }), detailMessages.deleteAction] })] })] }), slots?.header?.(booking), _jsx(Card, { children: _jsxs(CardContent, { className: "grid grid-cols-2 gap-6 py-6 sm:grid-cols-4", children: [_jsx(SummaryStat, { label: detailMessages.summarySell, value: formatAmount(booking.sellAmountCents, booking.sellCurrency, resolvedLocale, detailMessages.noValue), hint: sellHint }), _jsx(SummaryStat, { label: detailMessages.summaryCostMargin, value: formatAmount(booking.costAmountCents, booking.sellCurrency, resolvedLocale, detailMessages.noValue), hint: formatMargin(booking.marginPercent, detailMessages.noValue) }), _jsx(SummaryStat, { label: detailMessages.summaryDates, value: booking.startDate
|
|
51
53
|
? `${formatDate(booking.startDate, resolvedLocale, detailMessages.noValue)} - ${formatDate(booking.endDate, resolvedLocale, detailMessages.noValue)}`
|
|
52
|
-
: detailMessages.tbd, icon: _jsx(Calendar, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }), _jsx(SummaryStat, { label: detailMessages.summaryTravelers, value: booking.pax != null ? String(booking.pax) : detailMessages.noValue, icon: _jsx(Users, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }), booking.personId ? (_jsx(SummaryPersonLink, { label: detailMessages.summaryPerson, personId: booking.personId, onOpen: onPersonOpen })) : null, booking.organizationId ? (_jsx(SummaryOrganizationLink, { label: detailMessages.summaryOrganization, organizationId: booking.organizationId, onOpen: onOrganizationOpen })) : null, _jsx(SummaryStat, { label: detailMessages.summaryCreated, value: formatDate(booking.createdAt, resolvedLocale, detailMessages.noValue) }), _jsx(SummaryStat, { label: detailMessages.summaryUpdated, value: formatDate(booking.updatedAt, resolvedLocale, detailMessages.noValue) })] }) }), slots?.afterSummary?.(booking), _jsxs(Tabs, { defaultValue: "overview", children: [_jsxs(TabsList, { className: "w-full justify-start", children: [_jsx(TabsTrigger, { value: "overview", children: detailMessages.tabOverview }), _jsx(TabsTrigger, { value: "travelers", children: detailMessages.tabTravelers }), _jsx(TabsTrigger, { value: "finance", children: detailMessages.tabFinance }), slots?.invoicesTab ? (_jsx(TabsTrigger, { value: "invoices", children: slots.invoicesTab.label ?? detailMessages.tabInvoices })) : null, _jsx(TabsTrigger, { value: "suppliers", children: detailMessages.tabSuppliers }), _jsx(TabsTrigger, { value: "documents", children: detailMessages.tabDocuments }), _jsx(TabsTrigger, { value: "activity", children: detailMessages.tabActivity }), slots?.ledgerTab ? (_jsx(TabsTrigger, { value: "ledger", children: slots.ledgerTab.label ?? detailMessages.tabLedger })) : null] }), _jsxs(TabsContent, { value: "overview", className: "mt-4 flex flex-col gap-6", children: [slots?.overviewStart?.(booking), _jsx(BookingItemList, { bookingId: id }), _jsx(BookingGroupSection, { bookingId: id }), visibleInternalNotes(booking.internalNotes) ? (_jsx(Card, { children: _jsxs(CardContent, { className: "py-5", children: [_jsx("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: detailMessages.internalNotesLabel }), _jsx("p", { className: "whitespace-pre-wrap text-sm", children: visibleInternalNotes(booking.internalNotes) })] }) })) : null, slots?.overviewEnd?.(booking)] }), _jsxs(TabsContent, { value: "travelers", className: "mt-4 flex flex-col gap-6", children: [slots?.travelersStart?.(booking), _jsx(BookingBillingContextCard, { booking: booking }), _jsx(TravelerList, { bookingId: id, autoReveal: true })] }), _jsxs(TabsContent, { value: "finance", className: "mt-4 flex flex-col gap-6", children: [onCollectPayment || onRecordPayment ? (_jsxs("div", { className: "flex items-center justify-end gap-2", children: [onRecordPayment ? (_jsx(Button, { variant: "outline", onClick: () => onRecordPayment(booking), children: detailMessages.recordPaymentAction })) : null, onCollectPayment ? (_jsx(Button, { onClick: () => onCollectPayment(booking), children: detailMessages.collectPaymentAction })) : null] })) : null, slots?.financeStart?.(booking), _jsx(BookingPaymentReconciliationBanner, { bookingId: id }), _jsx(BookingPaymentsSummary, { bookingId: id, variant: "admin" }), _jsx(BookingPaymentScheduleList, { bookingId: id }), _jsx(BookingGuaranteeList, { bookingId: id }), slots?.financeEnd?.(booking)] }), slots?.invoicesTab ? (_jsx(TabsContent, { value: "invoices", className: "mt-4 flex flex-col gap-6", children: renderTabSlot(slots.invoicesTab.content, booking) })) : null, _jsx(TabsContent, { value: "suppliers", className: "mt-4", children: _jsx(SupplierStatusList, { bookingId: id }) }), _jsx(TabsContent, { value: "documents", className: "mt-4 flex flex-col gap-4", children: slots?.documents ? (slots.documents(booking)) : (_jsx("p", { className: "rounded-md border border-dashed p-4 text-sm text-muted-foreground", children: detailMessages.documentsSlotEmpty })) }), _jsxs(TabsContent, { value: "activity", className: "mt-4 flex flex-col gap-6", children: [_jsx(BookingActivityTimeline, { bookingId: id }), _jsx(BookingNotes, { bookingId: id }), slots?.activityEnd?.(booking)] }), slots?.ledgerTab ? (_jsx(TabsContent, { value: "ledger", className: "mt-4 flex flex-col gap-6", children: renderTabSlot(slots.ledgerTab.content, booking) })) : null] }), _jsx(BookingDialog, { open: editOpen, onOpenChange: setEditOpen, booking: booking }), _jsx(StatusChangeDialog, { open: statusDialogOpen, onOpenChange: setStatusDialogOpen, bookingId: id, currentStatus: booking.status }), _jsx(BookingCancellationDialog, { open: cancelDialogOpen, onOpenChange: setCancelDialogOpen, booking: booking })] }));
|
|
54
|
+
: detailMessages.tbd, icon: _jsx(Calendar, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }), _jsx(SummaryStat, { label: detailMessages.summaryTravelers, value: booking.pax != null ? String(booking.pax) : detailMessages.noValue, icon: _jsx(Users, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }), booking.personId ? (_jsx(SummaryPersonLink, { label: detailMessages.summaryPerson, personId: booking.personId, onOpen: onPersonOpen })) : null, booking.organizationId ? (_jsx(SummaryOrganizationLink, { label: detailMessages.summaryOrganization, organizationId: booking.organizationId, onOpen: onOrganizationOpen })) : null, _jsx(SummaryStat, { label: detailMessages.summaryCreated, value: formatDate(booking.createdAt, resolvedLocale, detailMessages.noValue) }), _jsx(SummaryStat, { label: detailMessages.summaryUpdated, value: formatDate(booking.updatedAt, resolvedLocale, detailMessages.noValue) })] }) }), slots?.afterSummary?.(booking), _jsxs(Tabs, { defaultValue: "overview", children: [_jsxs(TabsList, { className: "w-full justify-start", children: [_jsx(TabsTrigger, { value: "overview", children: detailMessages.tabOverview }), _jsx(TabsTrigger, { value: "travelers", children: detailMessages.tabTravelers }), _jsx(TabsTrigger, { value: "finance", children: detailMessages.tabFinance }), slots?.invoicesTab ? (_jsx(TabsTrigger, { value: "invoices", children: slots.invoicesTab.label ?? detailMessages.tabInvoices })) : null, _jsx(TabsTrigger, { value: "suppliers", children: detailMessages.tabSuppliers }), _jsx(TabsTrigger, { value: "documents", children: detailMessages.tabDocuments }), _jsx(TabsTrigger, { value: "activity", children: detailMessages.tabActivity }), slots?.ledgerTab ? (_jsx(TabsTrigger, { value: "ledger", children: slots.ledgerTab.label ?? detailMessages.tabLedger })) : null] }), _jsxs(TabsContent, { value: "overview", className: "mt-4 flex flex-col gap-6", children: [slots?.overviewStart?.(booking), _jsx(BookingItemList, { bookingId: id }), _jsx(BookingGroupSection, { bookingId: id }), visibleInternalNotes(booking.internalNotes) ? (_jsx(Card, { children: _jsxs(CardContent, { className: "py-5", children: [_jsx("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: detailMessages.internalNotesLabel }), _jsx("p", { className: "whitespace-pre-wrap text-sm", children: visibleInternalNotes(booking.internalNotes) })] }) })) : null, slots?.overviewEnd?.(booking)] }), _jsxs(TabsContent, { value: "travelers", className: "mt-4 flex flex-col gap-6", children: [slots?.travelersStart?.(booking), _jsx(BookingBillingContextCard, { booking: booking }), _jsx(TravelerList, { bookingId: id, autoReveal: true })] }), _jsxs(TabsContent, { value: "finance", className: "mt-4 flex flex-col gap-6", children: [onCollectPayment || onRecordPayment ? (_jsxs("div", { className: "flex items-center justify-end gap-2", children: [onRecordPayment ? (_jsx(Button, { variant: "outline", onClick: () => onRecordPayment(booking), children: detailMessages.recordPaymentAction })) : null, onCollectPayment ? (_jsx(Button, { onClick: () => onCollectPayment(booking), children: detailMessages.collectPaymentAction })) : null] })) : null, slots?.financeStart?.(booking), _jsx(BookingPaymentReconciliationBanner, { bookingId: id }), _jsx(BookingPaymentsSummary, { bookingId: id, variant: "admin", onConvertProforma: (row) => convertToInvoice.mutateAsync({ id: row.invoiceId }) }), _jsx(BookingPaymentScheduleList, { bookingId: id }), _jsx(BookingGuaranteeList, { bookingId: id }), slots?.financeEnd?.(booking)] }), slots?.invoicesTab ? (_jsx(TabsContent, { value: "invoices", className: "mt-4 flex flex-col gap-6", children: renderTabSlot(slots.invoicesTab.content, booking) })) : null, _jsx(TabsContent, { value: "suppliers", className: "mt-4", children: _jsx(SupplierStatusList, { bookingId: id }) }), _jsx(TabsContent, { value: "documents", className: "mt-4 flex flex-col gap-4", children: slots?.documents ? (slots.documents(booking)) : (_jsx("p", { className: "rounded-md border border-dashed p-4 text-sm text-muted-foreground", children: detailMessages.documentsSlotEmpty })) }), _jsxs(TabsContent, { value: "activity", className: "mt-4 flex flex-col gap-6", children: [_jsx(BookingActivityTimeline, { bookingId: id }), _jsx(BookingNotes, { bookingId: id }), slots?.activityEnd?.(booking)] }), slots?.ledgerTab ? (_jsx(TabsContent, { value: "ledger", className: "mt-4 flex flex-col gap-6", children: renderTabSlot(slots.ledgerTab.content, booking) })) : null] }), _jsx(BookingDialog, { open: editOpen, onOpenChange: setEditOpen, booking: booking }), _jsx(StatusChangeDialog, { open: statusDialogOpen, onOpenChange: setStatusDialogOpen, bookingId: id, currentStatus: booking.status }), _jsx(BookingCancellationDialog, { open: cancelDialogOpen, onOpenChange: setCancelDialogOpen, booking: booking })] }));
|
|
53
55
|
}
|
|
54
56
|
function renderTabSlot(content, booking) {
|
|
55
57
|
return typeof content === "function" ? content(booking) : content;
|
|
@@ -2,6 +2,7 @@ export interface BookingPaymentsSummaryRow {
|
|
|
2
2
|
id: string;
|
|
3
3
|
invoiceId: string;
|
|
4
4
|
invoiceNumber: string;
|
|
5
|
+
invoiceType?: "invoice" | "proforma" | "credit_note";
|
|
5
6
|
amountCents: number;
|
|
6
7
|
currency: string;
|
|
7
8
|
status: string;
|
|
@@ -32,6 +33,8 @@ export interface BookingPaymentsSummaryProps {
|
|
|
32
33
|
* on menu items, so this is a click handler rather than an href.
|
|
33
34
|
*/
|
|
34
35
|
onViewPayment?: (row: BookingPaymentsSummaryRow) => void;
|
|
36
|
+
/** Convert the row's proforma invoice into a final invoice. */
|
|
37
|
+
onConvertProforma?: (row: BookingPaymentsSummaryRow) => Promise<unknown> | unknown;
|
|
35
38
|
/** Edit handler — typically opens a dialog pre-filled with the row. */
|
|
36
39
|
onEditPayment?: (row: BookingPaymentsSummaryRow) => void;
|
|
37
40
|
/**
|
|
@@ -60,5 +63,5 @@ export interface BookingPaymentsSummaryProps {
|
|
|
60
63
|
* first as the primary identifier — that's the difference between
|
|
61
64
|
* "list of payments" and "list of invoice line-items".
|
|
62
65
|
*/
|
|
63
|
-
export declare function BookingPaymentsSummary({ bookingId, variant, getInvoiceHref, onViewPayment, onEditPayment, onDeletePayment, }: BookingPaymentsSummaryProps): import("react/jsx-runtime").JSX.Element;
|
|
66
|
+
export declare function BookingPaymentsSummary({ bookingId, variant, getInvoiceHref, onViewPayment, onConvertProforma, onEditPayment, onDeletePayment, }: BookingPaymentsSummaryProps): import("react/jsx-runtime").JSX.Element;
|
|
64
67
|
//# sourceMappingURL=booking-payments-summary.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"booking-payments-summary.d.ts","sourceRoot":"","sources":["../../src/components/booking-payments-summary.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"booking-payments-summary.d.ts","sourceRoot":"","sources":["../../src/components/booking-payments-summary.tsx"],"names":[],"mappings":"AAkEA,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,aAAa,CAAA;IACpD,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;IAC5B;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,yBAAyB,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAC9E;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,yBAAyB,KAAK,IAAI,CAAA;IACxD,+DAA+D;IAC/D,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,yBAAyB,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;IAClF,uEAAuE;IACvE,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,yBAAyB,KAAK,IAAI,CAAA;IACxD;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,yBAAyB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAC3E;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,SAAS,EACT,OAAkB,EAClB,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,eAAe,GAChB,EAAE,2BAA2B,2CAiR7B"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useAdminBookingPayments, usePublicBookingPayments } from "@voyantjs/finance-react";
|
|
4
4
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, Card, CardContent, CardHeader, CardTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@voyantjs/ui/components";
|
|
5
|
-
import { Banknote, CreditCard, Eye, MoreHorizontal, Pencil, Receipt, Ticket, Trash2, Wallet, } from "lucide-react";
|
|
5
|
+
import { ArrowRightLeft, Banknote, CreditCard, Eye, MoreHorizontal, Pencil, Receipt, Ticket, Trash2, Wallet, } from "lucide-react";
|
|
6
6
|
import * as React from "react";
|
|
7
7
|
import { useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault } from "../i18n/provider.js";
|
|
8
8
|
/**
|
|
@@ -48,7 +48,7 @@ const methodIcon = {
|
|
|
48
48
|
* first as the primary identifier — that's the difference between
|
|
49
49
|
* "list of payments" and "list of invoice line-items".
|
|
50
50
|
*/
|
|
51
|
-
export function BookingPaymentsSummary({ bookingId, variant = "public", getInvoiceHref, onViewPayment, onEditPayment, onDeletePayment, }) {
|
|
51
|
+
export function BookingPaymentsSummary({ bookingId, variant = "public", getInvoiceHref, onViewPayment, onConvertProforma, onEditPayment, onDeletePayment, }) {
|
|
52
52
|
const publicQuery = usePublicBookingPayments(bookingId, { enabled: variant === "public" });
|
|
53
53
|
const adminQuery = useAdminBookingPayments(bookingId, { enabled: variant === "admin" });
|
|
54
54
|
const data = variant === "admin" ? adminQuery.data : publicQuery.data;
|
|
@@ -56,9 +56,14 @@ export function BookingPaymentsSummary({ bookingId, variant = "public", getInvoi
|
|
|
56
56
|
const messages = useBookingsUiMessagesOrDefault();
|
|
57
57
|
const card = messages.bookingPaymentsSummary;
|
|
58
58
|
const payments = data?.data?.payments ?? [];
|
|
59
|
-
const
|
|
59
|
+
const hasConvertibleProformas = payments.some((payment) => payment.invoiceType === "proforma");
|
|
60
|
+
const showActionsColumn = Boolean(onViewPayment ||
|
|
61
|
+
(onConvertProforma && hasConvertibleProformas) ||
|
|
62
|
+
onEditPayment ||
|
|
63
|
+
onDeletePayment);
|
|
60
64
|
const [deleteTarget, setDeleteTarget] = React.useState(null);
|
|
61
65
|
const [deletePending, setDeletePending] = React.useState(false);
|
|
66
|
+
const [convertingInvoiceId, setConvertingInvoiceId] = React.useState(null);
|
|
62
67
|
const handleDeleteConfirm = async () => {
|
|
63
68
|
if (!deleteTarget || !onDeletePayment)
|
|
64
69
|
return;
|
|
@@ -71,6 +76,17 @@ export function BookingPaymentsSummary({ bookingId, variant = "public", getInvoi
|
|
|
71
76
|
setDeletePending(false);
|
|
72
77
|
}
|
|
73
78
|
};
|
|
79
|
+
const handleConvertProforma = async (row) => {
|
|
80
|
+
if (!onConvertProforma || row.invoiceType !== "proforma")
|
|
81
|
+
return;
|
|
82
|
+
setConvertingInvoiceId(row.invoiceId);
|
|
83
|
+
try {
|
|
84
|
+
await onConvertProforma(row);
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
setConvertingInvoiceId(null);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
74
90
|
// Empty-state polish: completed totals across all visible rows so
|
|
75
91
|
// the card carries useful summary information even when there are
|
|
76
92
|
// many small partial payments to scan.
|
|
@@ -88,6 +104,7 @@ export function BookingPaymentsSummary({ bookingId, variant = "public", getInvoi
|
|
|
88
104
|
id: payment.id,
|
|
89
105
|
invoiceId: payment.invoiceId,
|
|
90
106
|
invoiceNumber: payment.invoiceNumber,
|
|
107
|
+
invoiceType: payment.invoiceType,
|
|
91
108
|
amountCents: payment.amountCents,
|
|
92
109
|
currency: payment.currency,
|
|
93
110
|
status: payment.status,
|
|
@@ -97,7 +114,7 @@ export function BookingPaymentsSummary({ bookingId, variant = "public", getInvoi
|
|
|
97
114
|
notes: payment.notes,
|
|
98
115
|
};
|
|
99
116
|
const invoiceHref = getInvoiceHref?.(row) ?? null;
|
|
100
|
-
return (_jsxs("tr", { className: "border-b last:border-b-0", children: [_jsx("td", { className: "px-4 py-2.5 text-right font-mono font-medium", children: formatMoney(payment.amountCents, payment.currency) }), _jsx("td", { className: "px-4 py-2.5", children: _jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(MethodIcon, { className: "h-3.5 w-3.5 text-muted-foreground" }), methodLabel] }) }), _jsx("td", { className: "px-4 py-2.5", children: _jsx(Badge, { variant: statusVariant[payment.status] ?? "secondary", children: messages.bookingPaymentsSummary.paymentStatusLabels[payment.status] ?? payment.status }) }), _jsx("td", { className: "px-4 py-2.5 text-muted-foreground text-xs", children: formatDate(payment.paymentDate) }), _jsx("td", { className: "px-4 py-2.5", children: _jsx("span", { title: payment.referenceNumber ?? undefined, className: "inline-block max-w-[180px] truncate font-mono text-muted-foreground text-xs", children: payment.referenceNumber ?? "—" }) }), _jsx("td", { className: "px-4 py-2.5 font-mono text-xs", children: invoiceHref ? (_jsx("a", { href: invoiceHref, className: "text-foreground underline-offset-2 hover:underline", children: payment.invoiceNumber })) : (payment.invoiceNumber) }), showActionsColumn ? (_jsx("td", { className: "px-2 py-2.5 text-right", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { render: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": card.actions.open }), children: _jsx(MoreHorizontal, { className: "h-4 w-4" }) }), _jsxs(DropdownMenuContent, { align: "end", children: [onViewPayment ? (_jsxs(DropdownMenuItem, { onClick: () => onViewPayment(row), children: [_jsx(Eye, { className: "mr-2 h-4 w-4" }), card.actions.view] })) : null, onEditPayment ? (_jsxs(DropdownMenuItem, { onClick: () => onEditPayment(row), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), card.actions.edit] })) : null, onDeletePayment ? (_jsxs(_Fragment, { children: [onViewPayment || onEditPayment ? (_jsx(DropdownMenuSeparator, {})) : null, _jsxs(DropdownMenuItem, { variant: "destructive", onClick: () => setDeleteTarget(row), children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), card.actions.delete] })] })) : null] })] }) })) : null] }, payment.id));
|
|
117
|
+
return (_jsxs("tr", { className: "border-b last:border-b-0", children: [_jsx("td", { className: "px-4 py-2.5 text-right font-mono font-medium", children: formatMoney(payment.amountCents, payment.currency) }), _jsx("td", { className: "px-4 py-2.5", children: _jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(MethodIcon, { className: "h-3.5 w-3.5 text-muted-foreground" }), methodLabel] }) }), _jsx("td", { className: "px-4 py-2.5", children: _jsx(Badge, { variant: statusVariant[payment.status] ?? "secondary", children: messages.bookingPaymentsSummary.paymentStatusLabels[payment.status] ?? payment.status }) }), _jsx("td", { className: "px-4 py-2.5 text-muted-foreground text-xs", children: formatDate(payment.paymentDate) }), _jsx("td", { className: "px-4 py-2.5", children: _jsx("span", { title: payment.referenceNumber ?? undefined, className: "inline-block max-w-[180px] truncate font-mono text-muted-foreground text-xs", children: payment.referenceNumber ?? "—" }) }), _jsx("td", { className: "px-4 py-2.5 font-mono text-xs", children: invoiceHref ? (_jsx("a", { href: invoiceHref, className: "text-foreground underline-offset-2 hover:underline", children: payment.invoiceNumber })) : (payment.invoiceNumber) }), showActionsColumn ? (_jsx("td", { className: "px-2 py-2.5 text-right", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { render: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": card.actions.open }), children: _jsx(MoreHorizontal, { className: "h-4 w-4" }) }), _jsxs(DropdownMenuContent, { align: "end", children: [onViewPayment ? (_jsxs(DropdownMenuItem, { onClick: () => onViewPayment(row), children: [_jsx(Eye, { className: "mr-2 h-4 w-4" }), card.actions.view] })) : null, onConvertProforma && row.invoiceType === "proforma" ? (_jsxs(DropdownMenuItem, { disabled: convertingInvoiceId === row.invoiceId, onClick: () => void handleConvertProforma(row), children: [_jsx(ArrowRightLeft, { className: "mr-2 h-4 w-4" }), card.actions.convertToInvoice] })) : null, onEditPayment ? (_jsxs(DropdownMenuItem, { onClick: () => onEditPayment(row), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), card.actions.edit] })) : null, onDeletePayment ? (_jsxs(_Fragment, { children: [onViewPayment || onEditPayment ? (_jsx(DropdownMenuSeparator, {})) : null, _jsxs(DropdownMenuItem, { variant: "destructive", onClick: () => setDeleteTarget(row), children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), card.actions.delete] })] })) : null] })] }) })) : null] }, payment.id));
|
|
101
118
|
}) })] }) })) }), onDeletePayment ? (_jsx(AlertDialog, { open: Boolean(deleteTarget), onOpenChange: (next) => {
|
|
102
119
|
if (!next && !deletePending)
|
|
103
120
|
setDeleteTarget(null);
|
|
@@ -45,8 +45,10 @@ export interface OptionUnitsStepperSectionProps {
|
|
|
45
45
|
remaining?: string;
|
|
46
46
|
unlimited?: string;
|
|
47
47
|
fillsSlotCapacity?: string;
|
|
48
|
+
reviewLine?: string;
|
|
48
49
|
};
|
|
49
50
|
slotHasFiniteCapacity?: boolean;
|
|
51
|
+
invalidOptionUnitIds?: readonly string[];
|
|
50
52
|
}
|
|
51
53
|
/**
|
|
52
54
|
* Rooms / per-unit stepper for booking-create flows. Drives
|
|
@@ -68,7 +70,7 @@ export interface OptionUnitsStepperSectionProps {
|
|
|
68
70
|
* disables the "+" button — we don't let the UI submit a request that
|
|
69
71
|
* would 409 at insert time.
|
|
70
72
|
*/
|
|
71
|
-
export declare function OptionUnitsStepperSection({ value, onChange, productId, slotId, optionId, enabled, onUnitsChange, labels, slotHasFiniteCapacity, }: OptionUnitsStepperSectionProps): import("react/jsx-runtime").JSX.Element;
|
|
73
|
+
export declare function OptionUnitsStepperSection({ value, onChange, productId, slotId, optionId, enabled, onUnitsChange, labels, slotHasFiniteCapacity, invalidOptionUnitIds, }: OptionUnitsStepperSectionProps): import("react/jsx-runtime").JSX.Element;
|
|
72
74
|
export declare function resolveOptionRemainingLabel({ totalRemaining, units, slotHasFiniteCapacity, remaining, unlimited, fillsSlotCapacity, }: {
|
|
73
75
|
totalRemaining: number | null;
|
|
74
76
|
units: ReadonlyArray<Pick<OptionUnitsStepperUnit, "unitType">>;
|
|
@@ -77,6 +79,7 @@ export declare function resolveOptionRemainingLabel({ totalRemaining, units, slo
|
|
|
77
79
|
unlimited: string;
|
|
78
80
|
fillsSlotCapacity?: string;
|
|
79
81
|
}): string;
|
|
82
|
+
export declare function optionRowHasInvalidUnit(units: ReadonlyArray<Pick<OptionUnitsStepperUnit, "optionUnitId">>, invalidOptionUnitIds: ReadonlySet<string>): boolean;
|
|
80
83
|
/**
|
|
81
84
|
* Returns the `optionId` the slot is bound to, derived from the first
|
|
82
85
|
* slot-availability row whose `optionUnitId` we can map to a known
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"option-units-stepper-section.d.ts","sourceRoot":"","sources":["../../src/components/option-units-stepper-section.tsx"],"names":[],"mappings":"AAgBA,iEAAiE;AACjE,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACnC;AAED,eAAO,MAAM,4BAA4B,EAAE,uBAA4C,CAAA;AAEvF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,yFAAyF;IACzF,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,IAAI,CAAA;IAC/E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,8BAA8B;IAC7C,KAAK,EAAE,uBAAuB,CAAA;IAC9B,QAAQ,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAClD,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,EAAE,KAAK,IAAI,CAAA;IACzD,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,iBAAiB,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"option-units-stepper-section.d.ts","sourceRoot":"","sources":["../../src/components/option-units-stepper-section.tsx"],"names":[],"mappings":"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"}
|
|
@@ -28,7 +28,7 @@ export const emptyOptionUnitsStepperValue = { quantities: {} };
|
|
|
28
28
|
* disables the "+" button — we don't let the UI submit a request that
|
|
29
29
|
* would 409 at insert time.
|
|
30
30
|
*/
|
|
31
|
-
export function OptionUnitsStepperSection({ value, onChange, productId, slotId, optionId, enabled = true, onUnitsChange, labels, slotHasFiniteCapacity = false, }) {
|
|
31
|
+
export function OptionUnitsStepperSection({ value, onChange, productId, slotId, optionId, enabled = true, onUnitsChange, labels, slotHasFiniteCapacity = false, invalidOptionUnitIds = [], }) {
|
|
32
32
|
const productsClient = useVoyantProductsContext();
|
|
33
33
|
const messages = useBookingsUiMessagesOrDefault();
|
|
34
34
|
const merged = { ...messages.roomsStepperSection.labels, ...labels };
|
|
@@ -106,6 +106,7 @@ export function OptionUnitsStepperSection({ value, onChange, productId, slotId,
|
|
|
106
106
|
// the no-slot-rows branch and use the product fallback for everything.
|
|
107
107
|
// See issue #960.
|
|
108
108
|
const units = React.useMemo(() => mergeStepperUnits(availabilityUnitRows, optionUnitRows, slotOptionId, Boolean(slotId)), [availabilityUnitRows, optionUnitRows, slotOptionId, slotId]);
|
|
109
|
+
const invalidOptionUnitIdSet = React.useMemo(() => new Set(invalidOptionUnitIds), [invalidOptionUnitIds]);
|
|
109
110
|
React.useEffect(() => {
|
|
110
111
|
onUnitsChange?.(units);
|
|
111
112
|
}, [onUnitsChange, units]);
|
|
@@ -173,6 +174,7 @@ export function OptionUnitsStepperSection({ value, onChange, productId, slotId,
|
|
|
173
174
|
};
|
|
174
175
|
return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Label, { children: merged.heading }), _jsx("div", { className: "flex flex-col gap-2", children: optionRows.map(({ optionKey, optionName, primary, allUnits, totalRemaining }) => {
|
|
175
176
|
const qty = value.quantities[primary.optionUnitId] ?? 0;
|
|
177
|
+
const isInvalid = optionRowHasInvalidUnit(allUnits, invalidOptionUnitIdSet);
|
|
176
178
|
const remainingLabel = resolveOptionRemainingLabel({
|
|
177
179
|
totalRemaining,
|
|
178
180
|
units: allUnits,
|
|
@@ -182,7 +184,7 @@ export function OptionUnitsStepperSection({ value, onChange, productId, slotId,
|
|
|
182
184
|
fillsSlotCapacity: merged.fillsSlotCapacity,
|
|
183
185
|
});
|
|
184
186
|
const atMax = totalRemaining !== null && qty >= totalRemaining;
|
|
185
|
-
return (_jsxs("div", { className:
|
|
187
|
+
return (_jsxs("div", { className: `flex items-center gap-3 rounded-md border px-3 py-2 ${isInvalid ? "border-destructive/70 bg-destructive/5 ring-1 ring-destructive/20" : ""}`, "aria-invalid": isInvalid ? true : undefined, children: [_jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 text-sm font-medium", children: [_jsx("span", { children: optionName }), isInvalid ? (_jsx("span", { className: "rounded-sm bg-destructive/10 px-1.5 py-0.5 text-[10px] font-medium text-destructive", children: merged.reviewLine })) : null] }), _jsx("div", { className: "text-xs text-muted-foreground", children: remainingLabel })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-7 w-7 p-0", onClick: () => setQuantity(primary.optionUnitId, Math.max(0, qty - 1)), disabled: qty <= 0, "aria-label": `${merged.decreaseUnitPrefix} ${optionName}`, children: _jsx(Minus, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "min-w-[1.5rem] text-center text-sm tabular-nums", children: qty }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-7 w-7 p-0", onClick: () => setQuantity(primary.optionUnitId, qty + 1), disabled: atMax, "aria-label": `${merged.increaseUnitPrefix} ${optionName}`, children: _jsx(Plus, { className: "h-3.5 w-3.5" }) })] })] }, optionKey));
|
|
186
188
|
}) })] }));
|
|
187
189
|
}
|
|
188
190
|
export function resolveOptionRemainingLabel({ totalRemaining, units, slotHasFiniteCapacity, remaining, unlimited, fillsSlotCapacity, }) {
|
|
@@ -193,6 +195,9 @@ export function resolveOptionRemainingLabel({ totalRemaining, units, slotHasFini
|
|
|
193
195
|
}
|
|
194
196
|
return unlimited;
|
|
195
197
|
}
|
|
198
|
+
export function optionRowHasInvalidUnit(units, invalidOptionUnitIds) {
|
|
199
|
+
return units.some((unit) => invalidOptionUnitIds.has(unit.optionUnitId));
|
|
200
|
+
}
|
|
196
201
|
/**
|
|
197
202
|
* Returns the `optionId` the slot is bound to, derived from the first
|
|
198
203
|
* slot-availability row whose `optionUnitId` we can map to a known
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type TravelerCategoryRole = "lead" | "adult" | "child" | "infant";
|
|
2
2
|
export interface TravelerCategoryState {
|
|
3
3
|
role: TravelerCategoryRole;
|
|
4
|
-
|
|
4
|
+
pricingUnitId: string | null;
|
|
5
5
|
}
|
|
6
6
|
export interface TravelerCategoryUnitState {
|
|
7
7
|
unitId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"traveler-category-buttons.d.ts","sourceRoot":"","sources":["../../src/components/traveler-category-buttons.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAA;AAExE,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,oBAAoB,CAAA;IAC1B,
|
|
1
|
+
{"version":3,"file":"traveler-category-buttons.d.ts","sourceRoot":"","sources":["../../src/components/traveler-category-buttons.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAA;AAExE,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,oBAAoB,CAAA;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAKvC;AAED,wBAAgB,oCAAoC,CAClD,QAAQ,EAAE,qBAAqB,EAC/B,QAAQ,EAAE,OAAO,CAAC,oBAAoB,EAAE,MAAM,CAAC,GAC9C;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,oBAAoB,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAS5E;AAED,wBAAgB,qCAAqC,CACnD,QAAQ,EAAE,qBAAqB,EAC/B,IAAI,EAAE,yBAAyB,GAC9B;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,oBAAoB,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAY5E"}
|
|
@@ -17,12 +17,12 @@ export function getStaticTravelerCategoryButtonState(traveler, category) {
|
|
|
17
17
|
}
|
|
18
18
|
export function getDynamicTravelerCategoryButtonState(traveler, unit) {
|
|
19
19
|
const inferredRole = inferTravelerRoleFromUnit(unit);
|
|
20
|
-
const active = traveler.
|
|
21
|
-
(traveler.role === "lead" && inferredRole === "adult" && traveler.
|
|
20
|
+
const active = traveler.pricingUnitId === unit.unitId ||
|
|
21
|
+
(traveler.role === "lead" && inferredRole === "adult" && traveler.pricingUnitId == null);
|
|
22
22
|
const nextRole = traveler.role === "lead" && inferredRole === "adult" ? "lead" : inferredRole;
|
|
23
23
|
return {
|
|
24
24
|
active,
|
|
25
25
|
nextRole,
|
|
26
|
-
shouldUpdate: traveler.
|
|
26
|
+
shouldUpdate: traveler.pricingUnitId !== unit.unitId || traveler.role !== nextRole,
|
|
27
27
|
};
|
|
28
28
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export type TravelerRole = "lead" | "adult" | "child" | "infant";
|
|
2
|
+
export type TravelerUnitAssignmentSource = "auto" | "manual" | "none";
|
|
2
3
|
export interface TravelerEntry {
|
|
4
|
+
/** Stable client-side identity used by item/extra travelerKeys links. */
|
|
5
|
+
clientTravelerKey?: string;
|
|
3
6
|
personId: string | null;
|
|
4
7
|
firstName: string;
|
|
5
8
|
lastName: string;
|
|
@@ -11,8 +14,14 @@ export interface TravelerEntry {
|
|
|
11
14
|
role: TravelerRole;
|
|
12
15
|
/** ISO `YYYY-MM-DD` date of birth. Drives age-derived unit assignment. */
|
|
13
16
|
dateOfBirth: string | null;
|
|
14
|
-
/** option_unit_id the
|
|
15
|
-
|
|
17
|
+
/** option_unit_id of the person pricing tier this traveler is billed as. */
|
|
18
|
+
pricingUnitId: string | null;
|
|
19
|
+
/** option_unit_id of the room/vehicle this traveler occupies, when applicable. */
|
|
20
|
+
inventoryUnitId: string | null;
|
|
21
|
+
/** Operator intent for `pricingUnitId`; defaults to `auto` when omitted. */
|
|
22
|
+
pricingUnitSource?: TravelerUnitAssignmentSource;
|
|
23
|
+
/** Operator intent for `inventoryUnitId`; defaults to `auto` when omitted. */
|
|
24
|
+
inventoryUnitSource?: TravelerUnitAssignmentSource;
|
|
16
25
|
}
|
|
17
26
|
export interface TravelerListValue {
|
|
18
27
|
travelers: TravelerEntry[];
|
|
@@ -20,11 +29,7 @@ export interface TravelerListValue {
|
|
|
20
29
|
export declare const emptyTravelerListValue: TravelerListValue;
|
|
21
30
|
/** Factory for a blank row — `role` defaults to `adult` unless the list is empty. */
|
|
22
31
|
export declare function createBlankTraveler(role?: TravelerRole): TravelerEntry;
|
|
23
|
-
|
|
24
|
-
* Compute integer age in full years from an ISO date-of-birth string.
|
|
25
|
-
* Returns null when the DOB is missing or unparseable.
|
|
26
|
-
*/
|
|
27
|
-
export declare function computeAgeYears(dob: string | null, now?: Date): number | null;
|
|
32
|
+
export { computeAgeYears } from "@voyantjs/bookings/pricing-assignment";
|
|
28
33
|
/**
|
|
29
34
|
* Derive the age-banded traveler role from DOB. Falls back to `adult`
|
|
30
35
|
* when DOB is missing so partial entries still typecheck downstream.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"travelers-section.d.ts","sourceRoot":"","sources":["../../src/components/travelers-section.tsx"],"names":[],"mappings":"AAyCA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"travelers-section.d.ts","sourceRoot":"","sources":["../../src/components/travelers-section.tsx"],"names":[],"mappings":"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"}
|