@voyantjs/bookings-ui 0.81.18 → 0.81.20
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-activity-timeline.d.ts +28 -1
- package/dist/components/booking-activity-timeline.d.ts.map +1 -1
- package/dist/components/booking-activity-timeline.js +50 -6
- package/dist/components/booking-detail-page.d.ts +22 -3
- package/dist/components/booking-detail-page.d.ts.map +1 -1
- package/dist/components/booking-detail-page.js +33 -6
- package/dist/components/booking-list.d.ts +37 -2
- package/dist/components/booking-list.d.ts.map +1 -1
- package/dist/components/booking-list.js +214 -53
- package/dist/components/booking-note-dialog.d.ts +16 -0
- package/dist/components/booking-note-dialog.d.ts.map +1 -0
- package/dist/components/booking-note-dialog.js +41 -0
- package/dist/components/booking-notes.d.ts.map +1 -1
- package/dist/components/booking-notes.js +36 -10
- package/dist/components/bookings-page.d.ts +5 -1
- package/dist/components/bookings-page.d.ts.map +1 -1
- package/dist/components/bookings-page.js +2 -2
- package/dist/i18n/en.d.ts +34 -5
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +38 -9
- package/dist/i18n/messages.d.ts +35 -5
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/i18n/provider.d.ts +68 -10
- package/dist/i18n/provider.d.ts.map +1 -1
- package/dist/i18n/ro.d.ts +34 -5
- package/dist/i18n/ro.d.ts.map +1 -1
- package/dist/i18n/ro.js +37 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +32 -32
|
@@ -1,5 +1,32 @@
|
|
|
1
|
+
import { type LucideIcon } from "lucide-react";
|
|
2
|
+
import * as React from "react";
|
|
1
3
|
export interface BookingActivityTimelineProps {
|
|
2
4
|
bookingId: string;
|
|
5
|
+
/**
|
|
6
|
+
* Extra events to merge into the timeline alongside the built-in
|
|
7
|
+
* activity / document / payment sources. Operator templates pass
|
|
8
|
+
* action-ledger entries through here so the timeline stays a single
|
|
9
|
+
* chronological feed instead of getting split across tabs.
|
|
10
|
+
*/
|
|
11
|
+
additionalEvents?: readonly TimelineEvent[];
|
|
12
|
+
/** Rendered below the pagination — e.g. a "load more" pager for action-ledger entries. */
|
|
13
|
+
footer?: React.ReactNode;
|
|
14
|
+
/** Page size for the client-side pager. Defaults to 10. */
|
|
15
|
+
pageSize?: number;
|
|
3
16
|
}
|
|
4
|
-
export
|
|
17
|
+
export type TimelineSource = "activity" | "document" | "payment" | "action";
|
|
18
|
+
export type TimelineEvent = {
|
|
19
|
+
id: string;
|
|
20
|
+
source: TimelineSource;
|
|
21
|
+
title: string;
|
|
22
|
+
description?: string | null;
|
|
23
|
+
actorId?: string | null;
|
|
24
|
+
timestamp: string;
|
|
25
|
+
icon: LucideIcon;
|
|
26
|
+
link?: {
|
|
27
|
+
href: string;
|
|
28
|
+
label: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export declare function BookingActivityTimeline({ bookingId, additionalEvents, footer, pageSize, }: BookingActivityTimelineProps): import("react/jsx-runtime").JSX.Element;
|
|
5
32
|
//# sourceMappingURL=booking-activity-timeline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"booking-activity-timeline.d.ts","sourceRoot":"","sources":["../../src/components/booking-activity-timeline.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"booking-activity-timeline.d.ts","sourceRoot":"","sources":["../../src/components/booking-activity-timeline.tsx"],"names":[],"mappings":"AAaA,OAAO,EAML,KAAK,UAAU,EAKhB,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAQ9B,MAAM,WAAW,4BAA4B;IAC3C,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,SAAS,aAAa,EAAE,CAAA;IAC3C,0FAA0F;IAC1F,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACxB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAA;AAE3E,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,cAAc,CAAA;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,UAAU,CAAA;IAChB,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CACvC,CAAA;AA6BD,wBAAgB,uBAAuB,CAAC,EACtC,SAAS,EACT,gBAAgB,EAChB,MAAM,EACN,QAAa,GACd,EAAE,4BAA4B,2CAgJ9B"}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useBookingActivity, useBookingTravelerDocuments } from "@voyantjs/bookings-react";
|
|
4
4
|
import { usePublicBookingPayments } from "@voyantjs/finance-react";
|
|
5
|
-
import { Badge, Button
|
|
5
|
+
import { Badge, Button } from "@voyantjs/ui/components";
|
|
6
|
+
import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, } from "@voyantjs/ui/components/pagination";
|
|
6
7
|
import { Activity, Clock, CreditCard, ExternalLink, FileText, Pencil, Plus, RefreshCw, UserPlus, } from "lucide-react";
|
|
7
8
|
import * as React from "react";
|
|
8
9
|
import { formatMessage, useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault, } from "../i18n/provider.js";
|
|
@@ -27,9 +28,11 @@ const sourceVariant = {
|
|
|
27
28
|
activity: "outline",
|
|
28
29
|
document: "secondary",
|
|
29
30
|
payment: "default",
|
|
31
|
+
action: "secondary",
|
|
30
32
|
};
|
|
31
|
-
export function BookingActivityTimeline({ bookingId }) {
|
|
33
|
+
export function BookingActivityTimeline({ bookingId, additionalEvents, footer, pageSize = 10, }) {
|
|
32
34
|
const [filter, setFilter] = React.useState("all");
|
|
35
|
+
const [pageIndex, setPageIndex] = React.useState(0);
|
|
33
36
|
const { data: activityData } = useBookingActivity(bookingId);
|
|
34
37
|
const { data: documentsData } = useBookingTravelerDocuments(bookingId);
|
|
35
38
|
const { data: paymentsData } = usePublicBookingPayments(bookingId);
|
|
@@ -39,6 +42,7 @@ export function BookingActivityTimeline({ bookingId }) {
|
|
|
39
42
|
activity: messages.bookingActivityTimeline.sourceLabels.activity,
|
|
40
43
|
document: messages.bookingActivityTimeline.sourceLabels.document,
|
|
41
44
|
payment: messages.bookingActivityTimeline.sourceLabels.payment,
|
|
45
|
+
action: messages.bookingActivityTimeline.sourceLabels.action,
|
|
42
46
|
};
|
|
43
47
|
const events = React.useMemo(() => {
|
|
44
48
|
const merged = [];
|
|
@@ -48,7 +52,9 @@ export function BookingActivityTimeline({ bookingId }) {
|
|
|
48
52
|
source: "activity",
|
|
49
53
|
title: messages.bookingActivityTimeline.activityTitles[entry.activityType] ?? entry.description,
|
|
50
54
|
description: entry.description,
|
|
51
|
-
|
|
55
|
+
// Prefer the hydrated display name; fall back to email then raw
|
|
56
|
+
// id (system actors stay null and skip the "By …" render).
|
|
57
|
+
actorId: entry.actorName || entry.actorEmail || entry.actorId,
|
|
52
58
|
timestamp: entry.createdAt,
|
|
53
59
|
icon: activityIcons[entry.activityType] ?? Activity,
|
|
54
60
|
});
|
|
@@ -83,12 +89,50 @@ export function BookingActivityTimeline({ bookingId }) {
|
|
|
83
89
|
icon: CreditCard,
|
|
84
90
|
});
|
|
85
91
|
}
|
|
92
|
+
for (const extra of additionalEvents ?? []) {
|
|
93
|
+
merged.push(extra);
|
|
94
|
+
}
|
|
86
95
|
merged.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
87
96
|
return merged;
|
|
88
|
-
}, [activityData, documentsData, formatNumber, messages, paymentsData]);
|
|
97
|
+
}, [activityData, additionalEvents, documentsData, formatNumber, messages, paymentsData]);
|
|
89
98
|
const visible = filter === "all" ? events : events.filter((e) => e.source === filter);
|
|
90
|
-
const
|
|
91
|
-
|
|
99
|
+
const pageCount = Math.max(1, Math.ceil(visible.length / pageSize));
|
|
100
|
+
const safePageIndex = Math.min(pageIndex, pageCount - 1);
|
|
101
|
+
const pagedVisible = visible.slice(safePageIndex * pageSize, safePageIndex * pageSize + pageSize);
|
|
102
|
+
// Reset to page 1 whenever the filter changes. React's recommended
|
|
103
|
+
// pattern for derived state reset — cheaper and lint-clean compared
|
|
104
|
+
// to a useEffect.
|
|
105
|
+
const [lastFilter, setLastFilter] = React.useState(filter);
|
|
106
|
+
if (filter !== lastFilter) {
|
|
107
|
+
setLastFilter(filter);
|
|
108
|
+
setPageIndex(0);
|
|
109
|
+
}
|
|
110
|
+
const hasActionEvents = (additionalEvents?.length ?? 0) > 0;
|
|
111
|
+
const filterChips = hasActionEvents
|
|
112
|
+
? ["all", "activity", "document", "payment", "action"]
|
|
113
|
+
: ["all", "activity", "document", "payment"];
|
|
114
|
+
return (_jsxs("div", { "data-slot": "booking-activity-timeline", className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("h2", { className: "flex items-center gap-2 text-base font-semibold", children: [_jsx(Activity, { className: "h-4 w-4 text-muted-foreground" }), messages.bookingActivityTimeline.title] }), _jsx("div", { className: "flex flex-wrap items-center gap-1", children: filterChips.map((chip) => (_jsx(Button, { variant: filter === chip ? "default" : "ghost", size: "sm", className: "h-7 capitalize", onClick: () => setFilter(chip), children: chip === "all" ? messages.bookingActivityTimeline.filters.all : sourceLabel[chip] }, chip))) })] }), visible.length === 0 ? (_jsx("div", { className: "rounded-md border bg-background p-6 text-center", children: _jsx("p", { className: "text-sm text-muted-foreground", children: messages.bookingActivityTimeline.empty }) })) : (_jsx("div", { className: "flex flex-col gap-2", children: pagedVisible.map((event) => (_jsx(TimelineEventItem, { event: event, sourceLabel: sourceLabel }, event.id))) })), pageCount > 1 ? (_jsx(TimelinePagination, { pageIndex: safePageIndex, pageCount: pageCount, onPageChange: setPageIndex })) : null, footer ? _jsx("div", { className: "mt-1 flex justify-center", children: footer }) : null] }));
|
|
115
|
+
}
|
|
116
|
+
function TimelinePagination({ pageIndex, pageCount, onPageChange, }) {
|
|
117
|
+
const canPrev = pageIndex > 0;
|
|
118
|
+
const canNext = pageIndex + 1 < pageCount;
|
|
119
|
+
// Compact, ellipsis-less list — the timeline rarely paginates past a
|
|
120
|
+
// handful of pages, and the cards are dense, so a chunky pager would
|
|
121
|
+
// overwhelm. If we hit > 7 pages in practice we can revisit and add
|
|
122
|
+
// PaginationEllipsis.
|
|
123
|
+
const pages = Array.from({ length: pageCount }, (_, idx) => idx);
|
|
124
|
+
return (_jsx(Pagination, { className: "mt-1", children: _jsxs(PaginationContent, { children: [_jsx(PaginationItem, { children: _jsx(PaginationPrevious, { "aria-disabled": !canPrev, tabIndex: canPrev ? 0 : -1, className: canPrev ? undefined : "pointer-events-none opacity-50", onClick: (event) => {
|
|
125
|
+
event.preventDefault();
|
|
126
|
+
if (canPrev)
|
|
127
|
+
onPageChange(pageIndex - 1);
|
|
128
|
+
}, href: "#" }) }), pages.map((idx) => (_jsx(PaginationItem, { children: _jsx(PaginationLink, { isActive: idx === pageIndex, onClick: (event) => {
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
onPageChange(idx);
|
|
131
|
+
}, href: "#", children: idx + 1 }) }, idx))), _jsx(PaginationItem, { children: _jsx(PaginationNext, { "aria-disabled": !canNext, tabIndex: canNext ? 0 : -1, className: canNext ? undefined : "pointer-events-none opacity-50", onClick: (event) => {
|
|
132
|
+
event.preventDefault();
|
|
133
|
+
if (canNext)
|
|
134
|
+
onPageChange(pageIndex + 1);
|
|
135
|
+
}, href: "#" }) })] }) }));
|
|
92
136
|
}
|
|
93
137
|
function TimelineEventItem({ event, sourceLabel, }) {
|
|
94
138
|
const Icon = event.icon;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type BookingRecord } from "@voyantjs/bookings-react";
|
|
2
2
|
import { type ReactNode } from "react";
|
|
3
|
+
import { type TimelineEvent } from "./booking-activity-timeline.js";
|
|
3
4
|
import { type BookingItemResourceKind } from "./booking-item-list.js";
|
|
4
5
|
import { type BookingPaymentsSummaryRow } from "./booking-payments-summary.js";
|
|
5
6
|
/**
|
|
@@ -30,9 +31,17 @@ export interface BookingDetailPageSlots {
|
|
|
30
31
|
activityEnd?: (booking: BookingRecord) => ReactNode;
|
|
31
32
|
/** Mounts a dedicated `Invoices` tab between Payments and Suppliers. */
|
|
32
33
|
invoicesTab?: BookingDetailTabSlot;
|
|
33
|
-
/**
|
|
34
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Extra events merged into the Activity-tab timeline. Operator
|
|
36
|
+
* templates pass action-ledger entries here so the timeline stays a
|
|
37
|
+
* single chronological feed.
|
|
38
|
+
*/
|
|
39
|
+
activityExtraEvents?: readonly TimelineEvent[];
|
|
40
|
+
/** Rendered below the activity timeline events — typically a "load more" pager. */
|
|
41
|
+
activityTimelineFooter?: ReactNode;
|
|
35
42
|
}
|
|
43
|
+
/** Tab values used by the canonical `BookingDetailPage`. */
|
|
44
|
+
export type BookingDetailTabValue = "items" | "travelers" | "finance" | "invoices" | "documents" | "suppliers" | "activity" | "metadata";
|
|
36
45
|
export interface BookingDetailPageProps {
|
|
37
46
|
id: string;
|
|
38
47
|
className?: string;
|
|
@@ -86,9 +95,19 @@ export interface BookingDetailPageProps {
|
|
|
86
95
|
onEditPayment?: (row: BookingPaymentsSummaryRow) => void;
|
|
87
96
|
/** Forwarded to the finance-tab `BookingPaymentsSummary` row menu. */
|
|
88
97
|
onDeletePayment?: (row: BookingPaymentsSummaryRow) => Promise<void> | void;
|
|
98
|
+
/**
|
|
99
|
+
* Controlled active-tab value. Hosts wire this to their router so
|
|
100
|
+
* the active tab can be reflected in the URL (`?tab=activity` etc.)
|
|
101
|
+
* and the page reloads to the right tab when a link is shared.
|
|
102
|
+
* Leave both `activeTab` + `onTabChange` undefined for uncontrolled
|
|
103
|
+
* mode (defaults to `overview`).
|
|
104
|
+
*/
|
|
105
|
+
activeTab?: BookingDetailTabValue;
|
|
106
|
+
/** Fires when the user switches tabs. Hosts push the new value into the URL. */
|
|
107
|
+
onTabChange?: (tab: BookingDetailTabValue) => void;
|
|
89
108
|
slots?: BookingDetailPageSlots;
|
|
90
109
|
}
|
|
91
|
-
export declare function BookingDetailPage({ id, className, locale, hideBreadcrumb, onBack, onRecordPayment, recordPaymentDisabledReason, addScheduleDisabledReason, paidAmountCents, onItemResourceOpen, onPersonOpen, onOrganizationOpen, onInvoiceOpen, onViewPayment, onEditPayment, onDeletePayment, slots, }: BookingDetailPageProps): import("react/jsx-runtime").JSX.Element;
|
|
110
|
+
export declare function BookingDetailPage({ id, className, locale, hideBreadcrumb, onBack, onRecordPayment, recordPaymentDisabledReason, addScheduleDisabledReason, paidAmountCents, onItemResourceOpen, onPersonOpen, onOrganizationOpen, onInvoiceOpen, onViewPayment, onEditPayment, onDeletePayment, activeTab, onTabChange, slots, }: BookingDetailPageProps): import("react/jsx-runtime").JSX.Element;
|
|
92
111
|
/**
|
|
93
112
|
* Billing/contact card for the Travelers tab. Snapshot fields on the
|
|
94
113
|
* booking row are the source of truth (they capture contact info at
|
|
@@ -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":"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"}
|
|
@@ -6,7 +6,7 @@ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
|
|
|
6
6
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@voyantjs/ui/components/collapsible";
|
|
7
7
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@voyantjs/ui/components/tabs";
|
|
8
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";
|
|
9
|
+
import { Ban, ChevronDown, ChevronRight, CreditCard, Info, Mail, MapPin, Pencil, Phone, Plus, RefreshCw, Trash2, } from "lucide-react";
|
|
10
10
|
import { Fragment, useState } from "react";
|
|
11
11
|
import { formatMessage, useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault, } from "../i18n/index.js";
|
|
12
12
|
import { BookingActivityTimeline } from "./booking-activity-timeline.js";
|
|
@@ -23,7 +23,7 @@ import { StatusBadge } from "./status-badge.js";
|
|
|
23
23
|
import { StatusChangeDialog } from "./status-change-dialog.js";
|
|
24
24
|
import { SupplierStatusList } from "./supplier-status-list.js";
|
|
25
25
|
import { TravelerList } from "./traveler-list.js";
|
|
26
|
-
export function BookingDetailPage({ id, className, locale, hideBreadcrumb, onBack, onRecordPayment, recordPaymentDisabledReason, addScheduleDisabledReason, paidAmountCents, onItemResourceOpen, onPersonOpen, onOrganizationOpen, onInvoiceOpen, onViewPayment, onEditPayment, onDeletePayment, slots, }) {
|
|
26
|
+
export function BookingDetailPage({ id, className, locale, hideBreadcrumb, onBack, onRecordPayment, recordPaymentDisabledReason, addScheduleDisabledReason, paidAmountCents, onItemResourceOpen, onPersonOpen, onOrganizationOpen, onInvoiceOpen, onViewPayment, onEditPayment, onDeletePayment, activeTab, onTabChange, slots, }) {
|
|
27
27
|
const i18n = useBookingsUiI18nOrDefault();
|
|
28
28
|
const messages = useBookingsUiMessagesOrDefault();
|
|
29
29
|
const detailMessages = messages.bookingDetailPage;
|
|
@@ -91,7 +91,7 @@ export function BookingDetailPage({ id, className, locale, hideBreadcrumb, onBac
|
|
|
91
91
|
? formatAmount(paidAmountCents, booking.sellCurrency, resolvedLocale, detailMessages.noValue)
|
|
92
92
|
: detailMessages.noValue }), _jsx(StatCard, { label: detailMessages.summaryCostMargin, badge: booking.marginPercent != null
|
|
93
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: "
|
|
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: "items", value: activeTab, onValueChange: (value) => onTabChange?.(String(value)), children: [_jsxs(TabsList, { className: "w-full justify-start", children: [_jsx(TabsTrigger, { value: "items", 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 }), _jsx(TabsTrigger, { value: "metadata", children: detailMessages.tabMetadata })] }), _jsxs(TabsContent, { value: "items", 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(BookingNotes, { bookingId: id }), _jsx(BookingActivityTimeline, { bookingId: id, additionalEvents: slots?.activityExtraEvents, footer: slots?.activityTimelineFooter }), slots?.activityEnd?.(booking)] }), _jsx(TabsContent, { value: "metadata", className: "mt-4", children: _jsx(BookingMetadataList, { booking: booking, messages: detailMessages.metadataSection, statusLabel: getBookingStatusLabel(booking.status, messages.common.bookingStatusLabels), formatDateTime: i18n.formatDateTime, noValue: 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
95
|
if (!next && !remove.isPending)
|
|
96
96
|
setDeleteDialogOpen(false);
|
|
97
97
|
}, children: _jsxs(AlertDialogContent, { size: "sm", children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: detailMessages.deleteConfirm }), _jsx(AlertDialogDescription, { children: booking.bookingNumber
|
|
@@ -148,12 +148,39 @@ export function BookingBillingContextCard({ booking, onPersonOpen, onOrganizatio
|
|
|
148
148
|
.join(", ");
|
|
149
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 })] }));
|
|
150
150
|
}
|
|
151
|
-
function SummaryStat({ label, value, hint, icon, }) {
|
|
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] }));
|
|
153
|
-
}
|
|
154
151
|
function StatCard({ label, children, hint, badge, }) {
|
|
155
152
|
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
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Definition-list metadata panel for the Metadata tab. Surfaces booking
|
|
156
|
+
* fields that don't fit any other tab but are still useful for support
|
|
157
|
+
* / debugging: raw id, booking number, status, communication language,
|
|
158
|
+
* created/updated timestamps. Label-left, value-right rows matching the
|
|
159
|
+
* supplier-status / documents / notes tab style.
|
|
160
|
+
*/
|
|
161
|
+
function BookingMetadataList({ booking, messages, statusLabel, formatDateTime, noValue, }) {
|
|
162
|
+
const rows = [
|
|
163
|
+
{
|
|
164
|
+
label: messages.bookingId,
|
|
165
|
+
value: _jsx("span", { className: "font-mono text-xs", children: booking.id }),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
label: messages.bookingNumber,
|
|
169
|
+
value: _jsx("span", { className: "font-mono text-xs", children: booking.bookingNumber }),
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
label: messages.status,
|
|
173
|
+
value: _jsx(StatusBadge, { status: booking.status, children: statusLabel }),
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
label: messages.communicationLanguage,
|
|
177
|
+
value: booking.communicationLanguage ? (_jsx("span", { className: "uppercase", children: booking.communicationLanguage })) : (_jsx("span", { className: "text-muted-foreground", children: noValue })),
|
|
178
|
+
},
|
|
179
|
+
{ label: messages.created, value: formatDateTime(booking.createdAt) },
|
|
180
|
+
{ label: messages.updated, value: formatDateTime(booking.updatedAt) },
|
|
181
|
+
];
|
|
182
|
+
return (_jsxs("div", { "data-slot": "booking-metadata", className: "flex flex-col gap-3", children: [_jsxs("h2", { className: "flex items-center gap-2 text-base font-semibold", children: [_jsx(Info, { className: "h-4 w-4 text-muted-foreground" }), messages.title] }), _jsx("div", { className: "overflow-hidden rounded-md border bg-background", children: _jsx("dl", { className: "divide-y", children: rows.map((row) => (_jsxs("div", { className: "flex items-center justify-between gap-4 px-4 py-3 text-sm", children: [_jsx("dt", { className: "text-muted-foreground", children: row.label }), _jsx("dd", { className: "text-right", children: row.value })] }, row.label))) }) })] }));
|
|
183
|
+
}
|
|
157
184
|
/**
|
|
158
185
|
* Traffic-light badge for a percentage value. Color thresholds are
|
|
159
186
|
* passed in by the caller (Paid uses 0 → red, 0..100 → yellow, 100 →
|
|
@@ -1,5 +1,30 @@
|
|
|
1
|
-
import { type BookingRecord } from "@voyantjs/bookings-react";
|
|
1
|
+
import { type BookingRecord, type BookingsListSortDir, type BookingsListSortField } from "@voyantjs/bookings-react";
|
|
2
2
|
import * as React from "react";
|
|
3
|
+
/**
|
|
4
|
+
* Serializable snapshot of the booking-list filter / sort / paging
|
|
5
|
+
* state. Hosts that want shareable URLs (operator template) read this
|
|
6
|
+
* from the URL on mount via `initialFilters` and push changes via
|
|
7
|
+
* `onFiltersChange`.
|
|
8
|
+
*/
|
|
9
|
+
export interface BookingListFiltersState {
|
|
10
|
+
search: string;
|
|
11
|
+
/** `BOOKING_STATUS_ALL` ("all") or any booking status value. */
|
|
12
|
+
status: string;
|
|
13
|
+
productId: string | null;
|
|
14
|
+
optionId: string | null;
|
|
15
|
+
supplierId: string | null;
|
|
16
|
+
productCategoryId: string | null;
|
|
17
|
+
personId: string | null;
|
|
18
|
+
organizationId: string | null;
|
|
19
|
+
availabilitySlotId: string | null;
|
|
20
|
+
dateFrom: string | null;
|
|
21
|
+
dateTo: string | null;
|
|
22
|
+
paxMin: string;
|
|
23
|
+
paxMax: string;
|
|
24
|
+
sortBy: BookingsListSortField;
|
|
25
|
+
sortDir: BookingsListSortDir;
|
|
26
|
+
offset: number;
|
|
27
|
+
}
|
|
3
28
|
export interface BookingListProps {
|
|
4
29
|
pageSize?: number;
|
|
5
30
|
onSelectBooking?: (booking: BookingRecord) => void;
|
|
@@ -10,6 +35,16 @@ export interface BookingListProps {
|
|
|
10
35
|
* the trip composer without forking the component.
|
|
11
36
|
*/
|
|
12
37
|
headerActions?: React.ReactNode;
|
|
38
|
+
/**
|
|
39
|
+
* Initial filter / sort / paging state, typically parsed from the URL.
|
|
40
|
+
* Only specified keys override the defaults — partial input is fine.
|
|
41
|
+
*/
|
|
42
|
+
initialFilters?: Partial<BookingListFiltersState>;
|
|
43
|
+
/**
|
|
44
|
+
* Fires when any filter, sort, or paging value changes. Hosts push
|
|
45
|
+
* the snapshot into the URL so refresh / share preserves the view.
|
|
46
|
+
*/
|
|
47
|
+
onFiltersChange?: (filters: BookingListFiltersState) => void;
|
|
13
48
|
}
|
|
14
|
-
export declare function BookingList({ pageSize, onSelectBooking, onCreateBooking, headerActions, }?: BookingListProps): import("react/jsx-runtime").JSX.Element;
|
|
49
|
+
export declare function BookingList({ pageSize, onSelectBooking, onCreateBooking, headerActions, initialFilters, onFiltersChange, }?: BookingListProps): import("react/jsx-runtime").JSX.Element;
|
|
15
50
|
//# sourceMappingURL=booking-list.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"booking-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-list.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,
|
|
1
|
+
{"version":3,"file":"booking-list.d.ts","sourceRoot":"","sources":["../../src/components/booking-list.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAE3B,MAAM,0BAA0B,CAAA;AAsBjC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAU9B;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,qBAAqB,CAAA;IAC7B,OAAO,EAAE,mBAAmB,CAAA;IAC5B,MAAM,EAAE,MAAM,CAAA;CACf;AA6BD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAClD,eAAe,CAAC,EAAE,MAAM,IAAI,CAAA;IAC5B;;;;OAIG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC/B;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC,uBAAuB,CAAC,CAAA;IACjD;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAA;CAC7D;AAiBD,wBAAgB,WAAW,CAAC,EAC1B,QAAa,EACb,eAAe,EACf,eAAe,EACf,aAAa,EACb,cAAc,EACd,eAAe,GAChB,GAAE,gBAAqB,2CA+XvB"}
|