@voyantjs/bookings-ui 0.81.14 → 0.81.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/components/booking-billing-dialog.d.ts.map +1 -1
  2. package/dist/components/booking-billing-dialog.js +2 -2
  3. package/dist/components/booking-create-dialog.d.ts.map +1 -1
  4. package/dist/components/booking-create-dialog.js +53 -67
  5. package/dist/components/booking-detail-page.d.ts +44 -6
  6. package/dist/components/booking-detail-page.d.ts.map +1 -1
  7. package/dist/components/booking-detail-page.js +117 -42
  8. package/dist/components/booking-guarantee-list.d.ts.map +1 -1
  9. package/dist/components/booking-guarantee-list.js +66 -28
  10. package/dist/components/booking-item-list.d.ts +8 -1
  11. package/dist/components/booking-item-list.d.ts.map +1 -1
  12. package/dist/components/booking-item-list.js +157 -96
  13. package/dist/components/booking-list-filters.d.ts +3 -1
  14. package/dist/components/booking-list-filters.d.ts.map +1 -1
  15. package/dist/components/booking-list-filters.js +5 -3
  16. package/dist/components/booking-list.d.ts.map +1 -1
  17. package/dist/components/booking-list.js +12 -8
  18. package/dist/components/booking-payment-schedule-dialog.d.ts.map +1 -1
  19. package/dist/components/booking-payment-schedule-dialog.js +23 -8
  20. package/dist/components/booking-payment-schedule-list.d.ts +6 -1
  21. package/dist/components/booking-payment-schedule-list.d.ts.map +1 -1
  22. package/dist/components/booking-payment-schedule-list.js +131 -27
  23. package/dist/components/booking-payments-summary.d.ts +15 -7
  24. package/dist/components/booking-payments-summary.d.ts.map +1 -1
  25. package/dist/components/booking-payments-summary.js +99 -74
  26. package/dist/components/icon-action-button.d.ts +18 -0
  27. package/dist/components/icon-action-button.d.ts.map +1 -0
  28. package/dist/components/icon-action-button.js +13 -0
  29. package/dist/components/payment-schedule-section.d.ts +41 -46
  30. package/dist/components/payment-schedule-section.d.ts.map +1 -1
  31. package/dist/components/payment-schedule-section.js +150 -58
  32. package/dist/components/status-badge.d.ts +24 -0
  33. package/dist/components/status-badge.d.ts.map +1 -0
  34. package/dist/components/status-badge.js +65 -0
  35. package/dist/components/supplier-status-list.d.ts.map +1 -1
  36. package/dist/components/supplier-status-list.js +64 -27
  37. package/dist/components/traveler-dialog.d.ts.map +1 -1
  38. package/dist/components/traveler-dialog.js +10 -5
  39. package/dist/components/traveler-list.d.ts.map +1 -1
  40. package/dist/components/traveler-list.js +194 -80
  41. package/dist/i18n/en.d.ts +98 -5
  42. package/dist/i18n/en.d.ts.map +1 -1
  43. package/dist/i18n/en.js +101 -8
  44. package/dist/i18n/messages.d.ts +108 -5
  45. package/dist/i18n/messages.d.ts.map +1 -1
  46. package/dist/i18n/provider.d.ts +196 -10
  47. package/dist/i18n/provider.d.ts.map +1 -1
  48. package/dist/i18n/ro.d.ts +98 -5
  49. package/dist/i18n/ro.d.ts.map +1 -1
  50. package/dist/i18n/ro.js +101 -8
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +2 -0
  54. package/package.json +34 -32
@@ -1,13 +1,14 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { bookingStatusBadgeVariant, useBooking, useBookingMutation, } from "@voyantjs/bookings-react";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useBooking, useBookingItems, useBookingMutation, } from "@voyantjs/bookings-react";
4
4
  import { useOrganization, usePerson } from "@voyantjs/crm-react";
5
- import { useInvoiceMutation } from "@voyantjs/finance-react";
6
- import { Badge, Button, Card, CardContent, CardHeader, CardTitle, cn, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@voyantjs/ui/components";
5
+ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, Card, CardContent, cn, } from "@voyantjs/ui/components";
6
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@voyantjs/ui/components/collapsible";
7
7
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "@voyantjs/ui/components/tabs";
8
- import { Ban, Calendar, ChevronRight, CreditCard, Mail, MapPin, MoreHorizontal, Pencil, Phone, RefreshCw, Trash2, Users, } from "lucide-react";
9
- import { useState } from "react";
10
- import { useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault } from "../i18n/index.js";
8
+ import { Tooltip, TooltipContent, TooltipTrigger } from "@voyantjs/ui/components/tooltip";
9
+ import { Ban, ChevronDown, ChevronRight, CreditCard, Mail, MapPin, Pencil, Phone, Plus, RefreshCw, Trash2, } from "lucide-react";
10
+ import { Fragment, useState } from "react";
11
+ import { formatMessage, useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault, } from "../i18n/index.js";
11
12
  import { BookingActivityTimeline } from "./booking-activity-timeline.js";
12
13
  import { BookingBillingDialog } from "./booking-billing-dialog.js";
13
14
  import { BookingCancellationDialog } from "./booking-cancellation-dialog.js";
@@ -16,13 +17,13 @@ import { BookingGroupSection } from "./booking-group-section.js";
16
17
  import { BookingGuaranteeList } from "./booking-guarantee-list.js";
17
18
  import { BookingItemList } from "./booking-item-list.js";
18
19
  import { BookingNotes } from "./booking-notes.js";
19
- import { BookingPaymentReconciliationBanner } from "./booking-payment-reconciliation-banner.js";
20
20
  import { BookingPaymentScheduleList } from "./booking-payment-schedule-list.js";
21
21
  import { BookingPaymentsSummary, } from "./booking-payments-summary.js";
22
+ import { StatusBadge } from "./status-badge.js";
22
23
  import { StatusChangeDialog } from "./status-change-dialog.js";
23
24
  import { SupplierStatusList } from "./supplier-status-list.js";
24
25
  import { TravelerList } from "./traveler-list.js";
25
- export function BookingDetailPage({ id, className, locale, hideBreadcrumb, onBack, onPersonOpen, onOrganizationOpen, onCollectPayment, onRecordPayment, onViewPayment, onEditPayment, onDeletePayment, slots, }) {
26
+ export function BookingDetailPage({ id, className, locale, hideBreadcrumb, onBack, onRecordPayment, recordPaymentDisabledReason, addScheduleDisabledReason, paidAmountCents, onItemResourceOpen, onPersonOpen, onOrganizationOpen, onInvoiceOpen, onViewPayment, onEditPayment, onDeletePayment, slots, }) {
26
27
  const i18n = useBookingsUiI18nOrDefault();
27
28
  const messages = useBookingsUiMessagesOrDefault();
28
29
  const detailMessages = messages.bookingDetailPage;
@@ -30,9 +31,24 @@ export function BookingDetailPage({ id, className, locale, hideBreadcrumb, onBac
30
31
  const [editOpen, setEditOpen] = useState(false);
31
32
  const [statusDialogOpen, setStatusDialogOpen] = useState(false);
32
33
  const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
34
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
33
35
  const { data: bookingData, isPending } = useBooking(id);
34
36
  const { remove } = useBookingMutation();
35
- const { convertToInvoice } = useInvoiceMutation();
37
+ const headerPersonId = bookingData?.data?.personId ?? null;
38
+ const headerOrganizationId = bookingData?.data?.organizationId ?? null;
39
+ const headerPerson = usePerson(headerPersonId ?? undefined, {
40
+ enabled: Boolean(headerPersonId),
41
+ }).data;
42
+ const headerOrganization = useOrganization(headerOrganizationId ?? undefined, {
43
+ enabled: Boolean(headerOrganizationId) && !headerPersonId,
44
+ }).data;
45
+ // Pull booking items so the subtitle can link to the primary product
46
+ // + availability slot. We pick the first item (or the one flagged
47
+ // `isPrimary` when present) — most bookings only have one product.
48
+ const headerItems = useBookingItems(id).data?.data ?? [];
49
+ const primaryItem = headerItems.find((item) => item.isPrimary) ??
50
+ headerItems[0] ??
51
+ null;
36
52
  if (isPending) {
37
53
  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 }) }));
38
54
  }
@@ -43,15 +59,56 @@ export function BookingDetailPage({ id, className, locale, hideBreadcrumb, onBac
43
59
  const canCancel = ["draft", "on_hold", "confirmed", "in_progress"].includes(booking.status);
44
60
  const sellHint = booking.priceOverride?.isManual
45
61
  ? `${detailMessages.summaryPriceOverride}: ${booking.priceOverride.reason}`
46
- : booking.sellCurrency;
47
- return (_jsxs("div", { "data-slot": "booking-detail-page", className: cn("flex flex-col gap-6 p-6", className), children: [hideBreadcrumb ? null : (_jsxs("div", { className: "flex items-center gap-1.5 text-sm text-muted-foreground", children: [onBack ? (_jsx("button", { type: "button", onClick: onBack, className: "transition-colors hover:text-foreground", children: detailMessages.breadcrumbBookings })) : (_jsx("span", { children: detailMessages.breadcrumbBookings })), _jsx(ChevronRight, { className: "h-3.5 w-3.5", "aria-hidden": "true" }), _jsx("span", { className: "font-normal text-foreground", children: booking.bookingNumber })] })), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: booking.bookingNumber }), _jsx(Badge, { variant: bookingStatusBadgeVariant[booking.status], children: getBookingStatusLabel(booking.status, messages.common.bookingStatusLabels) })] }), _jsxs(ActionMenu, { children: [_jsxs(DropdownMenuItem, { onClick: () => setEditOpen(true), children: [_jsx(Pencil, { className: "h-4 w-4", "aria-hidden": "true" }), detailMessages.editAction] }), _jsxs(DropdownMenuItem, { onClick: () => setStatusDialogOpen(true), children: [_jsx(RefreshCw, { className: "h-4 w-4", "aria-hidden": "true" }), detailMessages.changeStatusAction] }), canCancel ? (_jsxs(DropdownMenuItem, { onClick: () => setCancelDialogOpen(true), children: [_jsx(Ban, { className: "h-4 w-4", "aria-hidden": "true" }), detailMessages.cancelBookingAction] })) : null, _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { variant: "destructive", disabled: remove.isPending, onClick: async () => {
48
- if (confirm(detailMessages.deleteConfirm)) {
62
+ : undefined;
63
+ const billingPersonName = [booking.contactFirstName, booking.contactLastName].filter(Boolean).join(" ") ||
64
+ (headerPerson
65
+ ? [headerPerson.firstName, headerPerson.lastName].filter(Boolean).join(" ")
66
+ : "") ||
67
+ headerOrganization?.name ||
68
+ "";
69
+ const billingHref = headerPersonId
70
+ ? () => onPersonOpen?.(headerPersonId)
71
+ : headerOrganizationId
72
+ ? () => onOrganizationOpen?.(headerOrganizationId)
73
+ : null;
74
+ const billingClickable = billingHref && (headerPersonId ? Boolean(onPersonOpen) : Boolean(onOrganizationOpen));
75
+ const headerDateRange = booking.startDate
76
+ ? `${formatDate(booking.startDate, resolvedLocale, detailMessages.noValue)} - ${formatDate(booking.endDate, resolvedLocale, detailMessages.noValue)}`
77
+ : null;
78
+ const headerPax = booking.pax != null ? `${booking.pax} PAX` : null;
79
+ const headerProductTitle = primaryItem?.productNameSnapshot ?? primaryItem?.title ?? null;
80
+ const headerProductId = primaryItem?.productId ?? null;
81
+ const headerSlotId = primaryItem?.availabilitySlotId ?? null;
82
+ const headerSubtitleParts = [
83
+ billingPersonName ? (billingClickable ? (_jsx("button", { type: "button", onClick: billingHref, className: "hover:text-foreground hover:underline", children: billingPersonName }, "billing")) : (_jsx("span", { children: billingPersonName }, "billing"))) : null,
84
+ headerProductTitle ? (headerProductId && onItemResourceOpen ? (_jsx("button", { type: "button", onClick: () => onItemResourceOpen("product", headerProductId), className: "inline-block max-w-[18rem] truncate align-bottom hover:text-foreground hover:underline", title: headerProductTitle, children: headerProductTitle }, "product")) : (_jsx("span", { className: "inline-block max-w-[18rem] truncate align-bottom", title: headerProductTitle, children: headerProductTitle }, "product"))) : null,
85
+ headerDateRange ? (headerSlotId && onItemResourceOpen ? (_jsx("button", { type: "button", onClick: () => onItemResourceOpen("availabilitySlot", headerSlotId), className: "hover:text-foreground hover:underline", children: headerDateRange }, "dates")) : (_jsx("span", { children: headerDateRange }, "dates"))) : null,
86
+ headerPax ? _jsx("span", { children: headerPax }, "pax") : null,
87
+ ].filter(Boolean);
88
+ return (_jsxs("div", { "data-slot": "booking-detail-page", className: cn("flex flex-col gap-6 p-6", className), children: [hideBreadcrumb ? null : (_jsxs("div", { className: "flex items-center gap-1.5 text-sm text-muted-foreground", children: [onBack ? (_jsx("button", { type: "button", onClick: onBack, className: "transition-colors hover:text-foreground", children: detailMessages.breadcrumbBookings })) : (_jsx("span", { children: detailMessages.breadcrumbBookings })), _jsx(ChevronRight, { className: "h-3.5 w-3.5", "aria-hidden": "true" }), _jsx("span", { className: "font-normal text-foreground", children: booking.bookingNumber })] })), _jsxs("div", { className: "flex items-start justify-between", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: booking.bookingNumber }), _jsx(StatusBadge, { status: booking.status, children: getBookingStatusLabel(booking.status, messages.common.bookingStatusLabels) })] }), headerSubtitleParts.length > 0 ? (_jsx("div", { className: "flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground", children: headerSubtitleParts.map((part, idx) => (_jsxs(Fragment, { children: [idx > 0 ? _jsx("span", { className: "text-muted-foreground/60", children: "/" }) : null, part] }, idx))) })) : null] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => setEditOpen(true), children: [_jsx(Pencil, { className: "mr-1.5 h-3.5 w-3.5", "aria-hidden": "true" }), detailMessages.editAction] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setStatusDialogOpen(true), children: [_jsx(RefreshCw, { className: "mr-1.5 h-3.5 w-3.5", "aria-hidden": "true" }), detailMessages.changeStatusAction] }), canCancel ? (_jsxs(Button, { variant: "outline", size: "sm", onClick: () => setCancelDialogOpen(true), children: [_jsx(Ban, { className: "mr-1.5 h-3.5 w-3.5", "aria-hidden": "true" }), detailMessages.cancelBookingAction] })) : null, _jsxs(Button, { variant: "outline", size: "sm", className: "text-destructive hover:bg-destructive/10 hover:text-destructive", disabled: remove.isPending, onClick: () => setDeleteDialogOpen(true), children: [_jsx(Trash2, { className: "mr-1.5 h-3.5 w-3.5", "aria-hidden": "true" }), detailMessages.deleteAction] })] })] }), slots?.header?.(booking), _jsxs("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-4", children: [_jsx(StatCard, { label: detailMessages.summaryTotal, hint: sellHint, children: formatAmount(booking.sellAmountCents, booking.sellCurrency, resolvedLocale, detailMessages.noValue) }), _jsx(StatCard, { label: detailMessages.summaryPaid, badge: paidAmountCents != null && booking.sellAmountCents
89
+ ? renderPercentBadge(Math.round((paidAmountCents / booking.sellAmountCents) * 100), paidBadgeClass)
90
+ : null, children: paidAmountCents != null
91
+ ? formatAmount(paidAmountCents, booking.sellCurrency, resolvedLocale, detailMessages.noValue)
92
+ : detailMessages.noValue }), _jsx(StatCard, { label: detailMessages.summaryCostMargin, badge: booking.marginPercent != null
93
+ ? renderPercentBadge(booking.marginPercent, marginBadgeClass)
94
+ : null, children: formatAmount(booking.costAmountCents, booking.sellCurrency, resolvedLocale, detailMessages.noValue) }), _jsx(StatCard, { label: detailMessages.summaryCreated, children: formatDate(booking.createdAt, 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: "documents", children: detailMessages.tabDocuments }), _jsx(TabsTrigger, { value: "suppliers", children: detailMessages.tabSuppliers }), _jsx(TabsTrigger, { value: "activity", children: detailMessages.tabActivity }), slots?.ledgerTab ? (_jsx(TabsTrigger, { value: "ledger", children: slots.ledgerTab.label ?? detailMessages.tabLedger })) : null, _jsx(TabsTrigger, { value: "meta", children: detailMessages.tabMeta })] }), _jsxs(TabsContent, { value: "overview", className: "mt-4 flex flex-col gap-6", children: [slots?.overviewStart?.(booking), _jsx(BookingItemList, { bookingId: id, onResourceOpen: onItemResourceOpen }), _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, onPersonOpen: onPersonOpen, onOrganizationOpen: onOrganizationOpen }), _jsx(TravelerList, { bookingId: id, autoReveal: true })] }), _jsxs(TabsContent, { value: "finance", className: "mt-4 flex flex-col gap-6", children: [_jsx(BookingPaymentScheduleList, { bookingId: id, addScheduleDisabledReason: addScheduleDisabledReason ?? null }), slots?.paymentsContent ? (renderDetailSlot(slots.paymentsContent, booking)) : (_jsx(BookingPaymentsSummary, { bookingId: id, variant: "admin", onViewPayment: onViewPayment, onInvoiceOpen: onInvoiceOpen, onEditPayment: onEditPayment, onDeletePayment: onDeletePayment, headerAction: onRecordPayment ? (_jsx(RecordPaymentHeaderButton, { label: detailMessages.recordPaymentAction, disabledReason: recordPaymentDisabledReason ?? null, onClick: () => onRecordPayment(booking) })) : null })), slots?.financeStart?.(booking), _jsxs(Collapsible, { children: [_jsxs(CollapsibleTrigger, { className: "group flex w-full items-center justify-between rounded-md border bg-background px-4 py-3 text-sm font-semibold hover:bg-muted/30", children: [messages.bookingGuaranteeList.title, _jsx(ChevronDown, { className: "h-4 w-4 transition-transform group-data-panel-open:rotate-180" })] }), _jsx(CollapsibleContent, { className: "mt-3", children: _jsx(BookingGuaranteeList, { bookingId: id }) })] }), slots?.financeEnd?.(booking)] }), slots?.invoicesTab ? (_jsx(TabsContent, { value: "invoices", className: "mt-4 flex flex-col gap-6", children: renderDetailSlot(slots.invoicesTab.content, booking) })) : null, _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 })) }), _jsx(TabsContent, { value: "suppliers", className: "mt-4", children: _jsx(SupplierStatusList, { bookingId: id }) }), _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: renderDetailSlot(slots.ledgerTab.content, booking) })) : null, _jsx(TabsContent, { value: "meta", className: "mt-4", children: _jsx(Card, { children: _jsx(CardContent, { className: "grid grid-cols-2 gap-6 py-6 sm:grid-cols-4", children: _jsx(SummaryStat, { label: detailMessages.summaryUpdated, value: formatDate(booking.updatedAt, resolvedLocale, detailMessages.noValue) }) }) }) })] }), _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 }), _jsx(AlertDialog, { open: deleteDialogOpen, onOpenChange: (next) => {
95
+ if (!next && !remove.isPending)
96
+ setDeleteDialogOpen(false);
97
+ }, children: _jsxs(AlertDialogContent, { size: "sm", children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: detailMessages.deleteConfirm }), _jsx(AlertDialogDescription, { children: booking.bookingNumber
98
+ ? formatMessage(detailMessages.deleteConfirmDescription, {
99
+ number: booking.bookingNumber,
100
+ })
101
+ : detailMessages.deleteConfirmDescriptionFallback })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: remove.isPending, children: detailMessages.deleteCancel }), _jsx(AlertDialogAction, { variant: "destructive", disabled: remove.isPending, onClick: async () => {
49
102
  await remove.mutateAsync(id);
103
+ setDeleteDialogOpen(false);
50
104
  onBack?.();
51
- }
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
53
- ? `${formatDate(booking.startDate, resolvedLocale, detailMessages.noValue)} - ${formatDate(booking.endDate, resolvedLocale, detailMessages.noValue)}`
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 }), slots?.paymentsContent ? (renderDetailSlot(slots.paymentsContent, booking)) : (_jsx(BookingPaymentsSummary, { bookingId: id, variant: "admin", onViewPayment: onViewPayment, onConvertProforma: (row) => convertToInvoice.mutateAsync({ id: row.invoiceId }), onEditPayment: onEditPayment, onDeletePayment: onDeletePayment })), _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: renderDetailSlot(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: renderDetailSlot(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 })] }));
105
+ }, children: detailMessages.deleteConfirmAction })] })] }) })] }));
106
+ }
107
+ function RecordPaymentHeaderButton({ label, disabledReason, onClick, }) {
108
+ if (disabledReason) {
109
+ return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx("span", { tabIndex: 0, className: "inline-block" }), children: _jsxs(Button, { variant: "outline", size: "sm", disabled: true, className: "pointer-events-none", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), label] }) }), _jsx(TooltipContent, { children: disabledReason })] }));
110
+ }
111
+ return (_jsxs(Button, { variant: "outline", size: "sm", onClick: onClick, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), label] }));
55
112
  }
56
113
  function renderDetailSlot(content, booking) {
57
114
  return typeof content === "function" ? content(booking) : content;
@@ -64,7 +121,7 @@ function renderDetailSlot(content, booking) {
64
121
  * person / organization so the operator still sees who they're
65
122
  * billing.
66
123
  */
67
- export function BookingBillingContextCard({ booking }) {
124
+ export function BookingBillingContextCard({ booking, onPersonOpen, onOrganizationOpen, }) {
68
125
  const messages = useBookingsUiMessagesOrDefault().bookingDetailPage;
69
126
  const [editOpen, setEditOpen] = useState(false);
70
127
  const person = usePerson(booking.personId ?? undefined, {
@@ -89,30 +146,38 @@ export function BookingBillingContextCard({ booking }) {
89
146
  ]
90
147
  .filter(Boolean)
91
148
  .join(", ");
92
- return (_jsxs(_Fragment, { children: [_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between pb-3", children: [_jsxs(CardTitle, { className: "flex items-center gap-2 text-base", children: [_jsx(CreditCard, { className: "h-4 w-4", "aria-hidden": "true" }), messages.billingPayer] }), _jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setEditOpen(true), children: [_jsx(Pencil, { className: "mr-1 h-3.5 w-3.5", "aria-hidden": "true" }), messages.editAction] })] }), _jsxs(CardContent, { className: "grid gap-4 md:grid-cols-4", children: [_jsx(BillingField, { label: messages.billingPayer, value: payerName || messages.noValue }), _jsx(BillingField, { label: messages.billingEmail, value: email ?? messages.noValue, icon: _jsx(Mail, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }), _jsx(BillingField, { label: messages.billingPhone, value: phone ?? messages.noValue, icon: _jsx(Phone, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }), _jsx(BillingField, { label: messages.billingAddress, value: address || messages.noValue, icon: _jsx(MapPin, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) })] })] }), _jsx(BookingBillingDialog, { open: editOpen, onOpenChange: setEditOpen, booking: booking })] }));
149
+ return (_jsxs("div", { "data-slot": "booking-billing-context", className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("h2", { className: "flex items-center gap-2 text-base font-semibold", children: [_jsx(CreditCard, { className: "h-4 w-4", "aria-hidden": "true" }), messages.billingPayer] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setEditOpen(true), children: [_jsx(Pencil, { className: "mr-1 h-3.5 w-3.5", "aria-hidden": "true" }), messages.editAction] })] }), _jsxs("div", { className: "flex flex-col gap-4 rounded-md border p-4", children: [_jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [_jsx(BillingField, { label: messages.billingPayer, value: payerName ? (booking.personId && onPersonOpen ? (_jsx("button", { type: "button", onClick: () => onPersonOpen(booking.personId), className: "text-left text-primary hover:underline", children: payerName })) : booking.organizationId && !booking.personId && onOrganizationOpen ? (_jsx("button", { type: "button", onClick: () => onOrganizationOpen(booking.organizationId), className: "text-left text-primary hover:underline", children: payerName })) : (payerName)) : (messages.noValue) }), _jsx(BillingField, { label: messages.billingEmail, value: email ?? messages.noValue, icon: _jsx(Mail, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }), _jsx(BillingField, { label: messages.billingPhone, value: phone ?? messages.noValue, icon: _jsx(Phone, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) })] }), _jsx(BillingField, { label: messages.billingAddress, value: address || messages.noValue, icon: _jsx(MapPin, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) })] }), _jsx(BookingBillingDialog, { open: editOpen, onOpenChange: setEditOpen, booking: booking })] }));
93
150
  }
94
151
  function SummaryStat({ label, value, hint, icon, }) {
95
152
  return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-xs font-medium text-muted-foreground", children: [icon, label] }), _jsx("div", { className: "text-base font-semibold tabular-nums", children: value }), hint ? _jsx("div", { className: "text-xs text-muted-foreground", children: hint }) : null] }));
96
153
  }
97
- function BillingField({ label, value, icon }) {
98
- return (_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "mb-1 flex items-center gap-1.5 text-xs font-medium text-muted-foreground", children: [icon, label] }), _jsx("div", { className: "truncate text-sm font-medium", children: value })] }));
154
+ function StatCard({ label, children, hint, badge, }) {
155
+ return (_jsx(Card, { children: _jsxs(CardContent, { className: "flex flex-col gap-1", children: [_jsx("div", { className: "text-xs font-medium text-muted-foreground", children: label }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("div", { className: "text-xl font-semibold tabular-nums leading-none", children: children }), badge] }), hint ? _jsx("div", { className: "text-xs text-muted-foreground", children: hint }) : null] }) }));
156
+ }
157
+ /**
158
+ * Traffic-light badge for a percentage value. Color thresholds are
159
+ * passed in by the caller (Paid uses 0 → red, 0..100 → yellow, 100 →
160
+ * green; Margin uses <0 → red, 0..10 → yellow, >10 → green).
161
+ */
162
+ function renderPercentBadge(percent, classFor) {
163
+ return (_jsxs(Badge, { variant: "outline", className: cn("border-transparent", classFor(percent)), children: [percent, "%"] }));
99
164
  }
100
- function ActionMenu({ children }) {
101
- return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { render: _jsx(Button, { variant: "ghost", size: "icon", className: "h-8 w-8 text-muted-foreground", children: _jsx(MoreHorizontal, { className: "h-4 w-4", "aria-hidden": "true" }) }) }), _jsx(DropdownMenuContent, { align: "end", children: children })] }));
165
+ function paidBadgeClass(percent) {
166
+ if (percent <= 0)
167
+ return "bg-red-500/10 text-red-600 dark:text-red-400";
168
+ if (percent >= 100)
169
+ return "bg-green-500/10 text-green-600 dark:text-green-400";
170
+ return "bg-yellow-500/10 text-yellow-700 dark:text-yellow-400";
102
171
  }
103
- function SummaryPersonLink({ label, personId, onOpen, }) {
104
- // Hydrate the CRM person so the header shows a human name with a
105
- // link to the detail page, falling back to the raw id while the
106
- // record is in flight (or when the person was hard-deleted).
107
- const person = usePerson(personId).data;
108
- const name = person ? [person.firstName, person.lastName].filter(Boolean).join(" ").trim() : "";
109
- const display = name || personId;
110
- return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsx("div", { className: "text-xs font-medium text-muted-foreground", children: label }), onOpen ? (_jsx("button", { type: "button", onClick: () => onOpen(personId), className: "truncate text-left text-sm font-medium text-primary hover:underline", children: display })) : (_jsx("span", { className: "truncate text-sm font-medium", children: display }))] }));
172
+ function marginBadgeClass(percent) {
173
+ if (percent < 0)
174
+ return "bg-red-500/10 text-red-600 dark:text-red-400";
175
+ if (percent > 10)
176
+ return "bg-green-500/10 text-green-600 dark:text-green-400";
177
+ return "bg-yellow-500/10 text-yellow-700 dark:text-yellow-400";
111
178
  }
112
- function SummaryOrganizationLink({ label, organizationId, onOpen, }) {
113
- const organization = useOrganization(organizationId).data;
114
- const display = organization?.name || organizationId;
115
- return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsx("div", { className: "text-xs font-medium text-muted-foreground", children: label }), onOpen ? (_jsx("button", { type: "button", onClick: () => onOpen(organizationId), className: "truncate text-left text-sm font-medium text-primary hover:underline", children: display })) : (_jsx("span", { className: "truncate text-sm font-medium", children: display }))] }));
179
+ function BillingField({ label, value, icon, }) {
180
+ return (_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "mb-1 flex items-center gap-1.5 text-xs font-medium text-muted-foreground", children: [icon, label] }), _jsx("div", { className: "truncate text-sm font-medium", children: value })] }));
116
181
  }
117
182
  function getBookingStatusLabel(status, labels) {
118
183
  return labels[status] ?? status;
@@ -120,16 +185,26 @@ function getBookingStatusLabel(status, labels) {
120
185
  function formatAmount(cents, currency, locale, empty) {
121
186
  if (cents == null)
122
187
  return empty;
123
- return new Intl.NumberFormat(locale, {
188
+ const amount = cents / 100;
189
+ const amountText = new Intl.NumberFormat(locale, {
190
+ maximumFractionDigits: 0,
191
+ }).format(amount);
192
+ // RON's "symbol" is just the ISO code itself, so a `<symbol> <amount> <code>`
193
+ // layout would print "RON 1.185 RON" — collapse it back to "1.185 RON".
194
+ if (currency.toUpperCase() === "RON") {
195
+ return `${amountText} ${currency}`;
196
+ }
197
+ // Extract the locale's native symbol so we can always render
198
+ // `<symbol> <amount> <code>` regardless of where Intl would otherwise
199
+ // place the symbol for this locale (e.g. Romanian puts it after).
200
+ const parts = new Intl.NumberFormat(locale, {
124
201
  style: "currency",
125
202
  currency,
203
+ currencyDisplay: "narrowSymbol",
126
204
  maximumFractionDigits: 0,
127
- }).format(cents / 100);
128
- }
129
- function formatMargin(percent, empty) {
130
- if (percent == null)
131
- return empty;
132
- return `${percent.toFixed(0)}%`;
205
+ }).formatToParts(amount);
206
+ const symbol = parts.find((p) => p.type === "currency")?.value ?? currency;
207
+ return `${symbol} ${amountText} ${currency}`;
133
208
  }
134
209
  function formatDate(iso, locale, empty) {
135
210
  if (!iso)
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"booking-guarantee-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-guarantee-list.tsx"],"names":[],"mappings":"AA4BA,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,EAAE,yBAAyB,2CAkK5E"}
@@ -1,48 +1,86 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useBookingGuaranteeMutation, useBookingGuarantees, } from "@voyantjs/finance-react";
4
- import { Badge, Button, Card, CardContent, CardHeader, CardTitle } from "@voyantjs/ui/components";
4
+ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Button, } from "@voyantjs/ui/components";
5
+ import { DataTable } from "@voyantjs/ui/components/data-table";
5
6
  import { Pencil, Plus, ShieldCheck, Trash2 } from "lucide-react";
6
7
  import * as React from "react";
7
8
  import { useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault } from "../i18n/provider.js";
8
9
  import { BookingGuaranteeDialog } from "./booking-guarantee-dialog.js";
9
- const statusVariant = {
10
- pending: "outline",
11
- active: "default",
12
- released: "secondary",
13
- failed: "destructive",
14
- cancelled: "destructive",
15
- expired: "secondary",
16
- };
10
+ import { IconActionButton } from "./icon-action-button.js";
11
+ import { StatusBadge } from "./status-badge.js";
17
12
  export function BookingGuaranteeList({ bookingId }) {
18
13
  const [dialogOpen, setDialogOpen] = React.useState(false);
19
14
  const [editing, setEditing] = React.useState(undefined);
15
+ const [deleteTarget, setDeleteTarget] = React.useState(null);
20
16
  const { data } = useBookingGuarantees(bookingId);
21
17
  const { remove } = useBookingGuaranteeMutation(bookingId);
22
18
  const { formatCurrency, formatDate } = useBookingsUiI18nOrDefault();
23
19
  const messages = useBookingsUiMessagesOrDefault();
20
+ const t = messages.bookingGuaranteeList;
21
+ const deleteMessages = t.actions.deleteConfirm;
24
22
  const guarantees = data?.data ?? [];
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: () => {
23
+ const handleConfirmDelete = async () => {
24
+ if (!deleteTarget)
25
+ return;
26
+ await remove.mutateAsync(deleteTarget.id);
27
+ setDeleteTarget(null);
28
+ };
29
+ const columns = React.useMemo(() => [
30
+ {
31
+ accessorKey: "guaranteeType",
32
+ header: t.columns.type,
33
+ cell: ({ row }) => messages.bookingGuaranteeDialog.guaranteeTypeLabels[row.original.guaranteeType],
34
+ },
35
+ {
36
+ accessorKey: "amountCents",
37
+ header: t.columns.amount,
38
+ cell: ({ row }) => (_jsx("span", { className: "font-mono", children: row.original.amountCents == null || !row.original.currency
39
+ ? t.values.amountUnavailable
40
+ : formatCurrency(row.original.amountCents / 100, row.original.currency) })),
41
+ },
42
+ {
43
+ accessorKey: "status",
44
+ header: t.columns.status,
45
+ cell: ({ row }) => (_jsx(StatusBadge, { status: row.original.status, children: messages.bookingGuaranteeDialog.guaranteeStatusLabels[row.original.status] })),
46
+ },
47
+ {
48
+ accessorKey: "provider",
49
+ header: t.columns.provider,
50
+ cell: ({ row }) => row.original.provider ?? t.values.providerUnavailable,
51
+ },
52
+ {
53
+ accessorKey: "referenceNumber",
54
+ header: t.columns.reference,
55
+ cell: ({ row }) => (_jsx("span", { className: "block max-w-[180px] truncate font-mono text-xs", children: row.original.referenceNumber ?? t.values.referenceUnavailable })),
56
+ },
57
+ {
58
+ accessorKey: "expiresAt",
59
+ header: t.columns.expires,
60
+ cell: ({ row }) => row.original.expiresAt ? formatDate(row.original.expiresAt) : t.values.expiresUnavailable,
61
+ },
62
+ {
63
+ id: "actions",
64
+ header: "",
65
+ cell: ({ row }) => (_jsxs("div", { className: "flex items-center justify-end gap-1", children: [_jsx(IconActionButton, { label: t.actions.editGuarantee, icon: _jsx(Pencil, { className: "h-3.5 w-3.5" }), onClick: (e) => {
66
+ e.stopPropagation();
67
+ setEditing(row.original);
68
+ setDialogOpen(true);
69
+ } }), _jsx(IconActionButton, { label: t.actions.deleteGuarantee, icon: _jsx(Trash2, { className: "h-3.5 w-3.5" }), className: "text-muted-foreground hover:bg-destructive/10 hover:text-destructive", onClick: (e) => {
70
+ e.stopPropagation();
71
+ setDeleteTarget(row.original);
72
+ } })] })),
73
+ },
74
+ ], [formatCurrency, formatDate, messages, t]);
75
+ return (_jsxs("div", { "data-slot": "booking-guarantee-list", className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("h2", { className: "flex items-center gap-2 text-base font-semibold", children: [_jsx(ShieldCheck, { className: "h-4 w-4" }), t.title] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => {
26
76
  setEditing(undefined);
27
77
  setDialogOpen(true);
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: () => {
34
- setEditing(g);
35
- setDialogOpen(true);
36
- }, className: "text-muted-foreground hover:text-foreground", children: _jsx(Pencil, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: () => {
37
- if (confirm(messages.bookingGuaranteeList.actions.deleteConfirm)) {
38
- remove.mutate(g.id);
39
- }
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) => {
78
+ }, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), t.addGuarantee] })] }), _jsx(DataTable, { columns: columns, data: guarantees, emptyMessage: t.empty, showPagination: false }), _jsx(BookingGuaranteeDialog, { open: dialogOpen, onOpenChange: (nextOpen) => {
41
79
  setDialogOpen(nextOpen);
42
- if (!nextOpen) {
80
+ if (!nextOpen)
43
81
  setEditing(undefined);
44
- }
45
- }, bookingId: bookingId, guarantee: editing, onSuccess: () => {
46
- setEditing(undefined);
47
- } })] }));
82
+ }, bookingId: bookingId, guarantee: editing, onSuccess: () => setEditing(undefined) }), _jsx(AlertDialog, { open: Boolean(deleteTarget), onOpenChange: (next) => {
83
+ if (!next && !remove.isPending)
84
+ setDeleteTarget(null);
85
+ }, children: _jsxs(AlertDialogContent, { size: "sm", children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: deleteMessages.title }), _jsx(AlertDialogDescription, { children: deleteMessages.description })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: remove.isPending, children: deleteMessages.cancel }), _jsx(AlertDialogAction, { variant: "destructive", disabled: remove.isPending, onClick: () => void handleConfirmDelete(), children: deleteMessages.confirm })] })] }) })] }));
48
86
  }
@@ -1,5 +1,12 @@
1
+ export type BookingItemResourceKind = "product" | "availabilitySlot";
1
2
  export interface BookingItemListProps {
2
3
  bookingId: string;
4
+ /**
5
+ * Open a linked resource (product / availability slot) in the host app.
6
+ * When omitted, the snapshot sheet renders the names as plain text
7
+ * instead of clickable links.
8
+ */
9
+ onResourceOpen?: (kind: BookingItemResourceKind, id: string) => void;
3
10
  }
4
- export declare function BookingItemList({ bookingId }: BookingItemListProps): import("react/jsx-runtime").JSX.Element;
11
+ export declare function BookingItemList({ bookingId, onResourceOpen }: BookingItemListProps): import("react/jsx-runtime").JSX.Element;
5
12
  //# sourceMappingURL=booking-item-list.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"booking-item-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-item-list.tsx"],"names":[],"mappings":"AAuBA,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,EAAE,oBAAoB,2CAwMlE"}
1
+ {"version":3,"file":"booking-item-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-item-list.tsx"],"names":[],"mappings":"AAmCA,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,kBAAkB,CAAA;AAEpE,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,uBAAuB,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CACrE;AAED,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,EAAE,oBAAoB,2CA+NlF"}