@voyant-travel/trips-react 0.110.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +30 -0
- package/dist/admin/admin-trips-page-controls.d.ts +28 -0
- package/dist/admin/admin-trips-page-controls.d.ts.map +1 -0
- package/dist/admin/admin-trips-page-controls.js +28 -0
- package/dist/admin/admin-trips-page-model.d.ts +87 -0
- package/dist/admin/admin-trips-page-model.d.ts.map +1 -0
- package/dist/admin/admin-trips-page-model.js +457 -0
- package/dist/admin/admin-trips-page.d.ts +6 -0
- package/dist/admin/admin-trips-page.d.ts.map +1 -0
- package/dist/admin/admin-trips-page.js +322 -0
- package/dist/admin/admin-trips-panels.d.ts +11 -0
- package/dist/admin/admin-trips-panels.d.ts.map +1 -0
- package/dist/admin/admin-trips-panels.js +11 -0
- package/dist/admin/index.d.ts +63 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +119 -0
- package/dist/admin/pages/trip-detail-page.d.ts +10 -0
- package/dist/admin/pages/trip-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/trip-detail-page.js +12 -0
- package/dist/admin/trip-component-display.d.ts +10 -0
- package/dist/admin/trip-component-display.d.ts.map +1 -0
- package/dist/admin/trip-component-display.js +137 -0
- package/dist/admin/trip-detail-host.d.ts +12 -0
- package/dist/admin/trip-detail-host.d.ts.map +1 -0
- package/dist/admin/trip-detail-host.js +37 -0
- package/dist/admin/trip-detail-record-model.d.ts +30 -0
- package/dist/admin/trip-detail-record-model.d.ts.map +1 -0
- package/dist/admin/trip-detail-record-model.js +56 -0
- package/dist/admin/trip-detail-record.d.ts +47 -0
- package/dist/admin/trip-detail-record.d.ts.map +1 -0
- package/dist/admin/trip-detail-record.js +170 -0
- package/dist/admin/trip-list-filters.d.ts +33 -0
- package/dist/admin/trip-list-filters.d.ts.map +1 -0
- package/dist/admin/trip-list-filters.js +94 -0
- package/dist/admin/trips-host.d.ts +15 -0
- package/dist/admin/trips-host.d.ts.map +1 -0
- package/dist/admin/trips-host.js +233 -0
- package/dist/admin/trips-panels/catalog-configurator.d.ts +34 -0
- package/dist/admin/trips-panels/catalog-configurator.d.ts.map +1 -0
- package/dist/admin/trips-panels/catalog-configurator.js +200 -0
- package/dist/admin/trips-panels/catalog-options.d.ts +58 -0
- package/dist/admin/trips-panels/catalog-options.d.ts.map +1 -0
- package/dist/admin/trips-panels/catalog-options.js +124 -0
- package/dist/admin/trips-panels/committed-component-card.d.ts +27 -0
- package/dist/admin/trips-panels/committed-component-card.d.ts.map +1 -0
- package/dist/admin/trips-panels/committed-component-card.js +44 -0
- package/dist/admin/trips-panels/display.d.ts +51 -0
- package/dist/admin/trips-panels/display.d.ts.map +1 -0
- package/dist/admin/trips-panels/display.js +336 -0
- package/dist/admin/trips-panels/flight-configurator.d.ts +34 -0
- package/dist/admin/trips-panels/flight-configurator.d.ts.map +1 -0
- package/dist/admin/trips-panels/flight-configurator.js +208 -0
- package/dist/admin/trips-panels/manual-configurators.d.ts +16 -0
- package/dist/admin/trips-panels/manual-configurators.d.ts.map +1 -0
- package/dist/admin/trips-panels/manual-configurators.js +41 -0
- package/dist/admin/trips-panels/pending-component-card.d.ts +16 -0
- package/dist/admin/trips-panels/pending-component-card.d.ts.map +1 -0
- package/dist/admin/trips-panels/pending-component-card.js +29 -0
- package/dist/admin/trips-panels/shared.d.ts +122 -0
- package/dist/admin/trips-panels/shared.d.ts.map +1 -0
- package/dist/admin/trips-panels/shared.js +152 -0
- package/dist/admin/trips-panels/travelers-section.d.ts +53 -0
- package/dist/admin/trips-panels/travelers-section.d.ts.map +1 -0
- package/dist/admin/trips-panels/travelers-section.js +183 -0
- package/dist/admin/trips-panels/trip-preview-rail.d.ts +52 -0
- package/dist/admin/trips-panels/trip-preview-rail.d.ts.map +1 -0
- package/dist/admin/trips-panels/trip-preview-rail.js +122 -0
- package/dist/cache.d.ts +9 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +21 -0
- package/dist/client.d.ts +15 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +51 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/use-price-trip.d.ts +3 -0
- package/dist/hooks/use-price-trip.d.ts.map +1 -0
- package/dist/hooks/use-price-trip.js +17 -0
- package/dist/hooks/use-reserve-trip.d.ts +3 -0
- package/dist/hooks/use-reserve-trip.d.ts.map +1 -0
- package/dist/hooks/use-reserve-trip.js +17 -0
- package/dist/hooks/use-trip-checkout.d.ts +3 -0
- package/dist/hooks/use-trip-checkout.d.ts.map +1 -0
- package/dist/hooks/use-trip-checkout.js +17 -0
- package/dist/hooks/use-trip-components.d.ts +40 -0
- package/dist/hooks/use-trip-components.d.ts.map +1 -0
- package/dist/hooks/use-trip-components.js +13 -0
- package/dist/hooks/use-trip.d.ts +5 -0
- package/dist/hooks/use-trip.d.ts.map +1 -0
- package/dist/hooks/use-trip.js +13 -0
- package/dist/hooks/use-trips.d.ts +6 -0
- package/dist/hooks/use-trips.d.ts.map +1 -0
- package/dist/hooks/use-trips.js +12 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/operations.d.ts +212 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +92 -0
- package/dist/provider.d.ts +2 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +1 -0
- package/dist/query-keys.d.ts +10 -0
- package/dist/query-keys.d.ts.map +1 -0
- package/dist/query-keys.js +9 -0
- package/dist/query-options.d.ts +167 -0
- package/dist/query-options.d.ts.map +1 -0
- package/dist/query-options.js +22 -0
- package/dist/schemas.d.ts +69 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +16 -0
- package/package.json +133 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type PendingComponent } from "./shared.js";
|
|
2
|
+
import type { TripTraveler } from "./travelers-section.js";
|
|
3
|
+
export declare function PendingComponentCard({ pending, onChange, onRemove, onCommit, committing, travelers, }: {
|
|
4
|
+
pending: PendingComponent;
|
|
5
|
+
onChange(next: PendingComponent): void;
|
|
6
|
+
onRemove(): void;
|
|
7
|
+
onCommit(): void;
|
|
8
|
+
committing: boolean;
|
|
9
|
+
travelers: TripTraveler[];
|
|
10
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export declare function PendingBody({ pending, onChange, travelers, }: {
|
|
12
|
+
pending: PendingComponent;
|
|
13
|
+
onChange(next: PendingComponent): void;
|
|
14
|
+
travelers: TripTraveler[];
|
|
15
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
//# sourceMappingURL=pending-component-card.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-component-card.d.ts","sourceRoot":"","sources":["../../../src/admin/trips-panels/pending-component-card.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAE,KAAK,gBAAgB,EAAqC,MAAM,aAAa,CAAA;AACtF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAE1D,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,SAAS,GACV,EAAE;IACD,OAAO,EAAE,gBAAgB,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACtC,QAAQ,IAAI,IAAI,CAAA;IAChB,QAAQ,IAAI,IAAI,CAAA;IAChB,UAAU,EAAE,OAAO,CAAA;IACnB,SAAS,EAAE,YAAY,EAAE,CAAA;CAC1B,2CAuCA;AAED,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,QAAQ,EACR,SAAS,GACV,EAAE;IACD,OAAO,EAAE,gBAAgB,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACtC,SAAS,EAAE,YAAY,EAAE,CAAA;CAC1B,2CAkBA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useOperatorAdminMessages as useAdminMessages } from "@voyant-travel/admin";
|
|
4
|
+
import { Button } from "@voyant-travel/ui/components/button";
|
|
5
|
+
import { Loader2, Plus, Trash2 } from "lucide-react";
|
|
6
|
+
import { CatalogConfigurator } from "./catalog-configurator.js";
|
|
7
|
+
import { FlightConfigurator, passengerCountsFromTripTravelers } from "./flight-configurator.js";
|
|
8
|
+
import { CruiseConfigurator, PlaceholderConfigurator, pendingComponentIsValid, } from "./manual-configurators.js";
|
|
9
|
+
import { verticalIconFor, verticalLabelFor } from "./shared.js";
|
|
10
|
+
export function PendingComponentCard({ pending, onChange, onRemove, onCommit, committing, travelers, }) {
|
|
11
|
+
const t = useAdminMessages().trips.adminComposer.panels;
|
|
12
|
+
const Icon = verticalIconFor(pending.kind);
|
|
13
|
+
const label = verticalLabelFor(pending.kind, t);
|
|
14
|
+
const valid = pendingComponentIsValid(pending);
|
|
15
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 rounded-md border bg-card p-5", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-md bg-muted", children: _jsx(Icon, { className: "size-4" }) }), _jsxs("div", { children: [_jsx("h3", { className: "font-medium text-base", children: label }), _jsx("p", { className: "text-muted-foreground text-xs", children: t.configureHint })] })] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: onRemove, "aria-label": t.removeComponent, children: _jsx(Trash2, { className: "size-4" }) })] }), _jsx(PendingBody, { pending: pending, onChange: onChange, travelers: travelers }), pending.commitError ? (_jsx("p", { className: "rounded-md bg-destructive/10 px-3 py-2 text-destructive text-sm", children: pending.commitError })) : null, _jsx("div", { className: "flex justify-end", children: _jsxs(Button, { onClick: onCommit, disabled: !valid || committing, children: [committing ? _jsx(Loader2, { className: "size-4 animate-spin" }) : _jsx(Plus, { className: "size-4" }), "Add to trip"] }) })] }));
|
|
16
|
+
}
|
|
17
|
+
export function PendingBody({ pending, onChange, travelers, }) {
|
|
18
|
+
const passengerCounts = passengerCountsFromTripTravelers(travelers);
|
|
19
|
+
if (pending.kind === "product" || pending.kind === "stay") {
|
|
20
|
+
return (_jsx(CatalogConfigurator, { pending: pending, onChange: onChange, paxAdult: passengerCounts.adults }));
|
|
21
|
+
}
|
|
22
|
+
if (pending.kind === "flight") {
|
|
23
|
+
return _jsx(FlightConfigurator, { pending: pending, travelers: travelers, onChange: onChange });
|
|
24
|
+
}
|
|
25
|
+
if (pending.kind === "cruise") {
|
|
26
|
+
return _jsx(CruiseConfigurator, { pending: pending, onChange: onChange });
|
|
27
|
+
}
|
|
28
|
+
return _jsx(PlaceholderConfigurator, { pending: pending, onChange: onChange });
|
|
29
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useOperatorAdminMessages as useAdminMessages } from "@voyant-travel/admin";
|
|
2
|
+
import type { Draft } from "@voyant-travel/bookings-react/journey";
|
|
3
|
+
import type { CatalogSearchHit } from "@voyant-travel/catalog-react";
|
|
4
|
+
import type { AncillaryCatalog, AncillarySelection, CabinClass, FlightOffer } from "@voyant-travel/flights/contract/types";
|
|
5
|
+
import type { ComponentType, ReactNode } from "react";
|
|
6
|
+
export type CatalogVertical = "products" | "accommodations" | "cruises" | "extras" | "flights";
|
|
7
|
+
export type PendingVerticalKind = "product" | "stay" | "flight" | "cruise" | "manual";
|
|
8
|
+
export type PanelsMessages = ReturnType<typeof useAdminMessages>["trips"]["adminComposer"]["panels"];
|
|
9
|
+
export interface AvailabilitySlot {
|
|
10
|
+
id: string;
|
|
11
|
+
dateLocal: string;
|
|
12
|
+
startsAt: string;
|
|
13
|
+
endsAt?: string | null;
|
|
14
|
+
timezone: string;
|
|
15
|
+
status: string;
|
|
16
|
+
unlimited: boolean;
|
|
17
|
+
remainingPax?: number | null;
|
|
18
|
+
initialPax?: number | null;
|
|
19
|
+
nights?: number | null;
|
|
20
|
+
days?: number | null;
|
|
21
|
+
}
|
|
22
|
+
type CatalogPendingFields = {
|
|
23
|
+
localId: string;
|
|
24
|
+
catalogEntityId: string | null;
|
|
25
|
+
catalogEntityName: string | null;
|
|
26
|
+
catalogSourceKind: string | null;
|
|
27
|
+
catalogSourceConnectionId: string | null;
|
|
28
|
+
catalogSourceRef: string | null;
|
|
29
|
+
catalogThumbnailUrl: string | null;
|
|
30
|
+
bookingDraft: Draft | null;
|
|
31
|
+
startsAt: string;
|
|
32
|
+
endsAt: string;
|
|
33
|
+
commitError: string | null;
|
|
34
|
+
};
|
|
35
|
+
export type PendingComponent = ({
|
|
36
|
+
kind: "product";
|
|
37
|
+
} & CatalogPendingFields) | ({
|
|
38
|
+
kind: "stay";
|
|
39
|
+
} & CatalogPendingFields) | {
|
|
40
|
+
kind: "flight";
|
|
41
|
+
localId: string;
|
|
42
|
+
tripType: "one_way" | "round_trip";
|
|
43
|
+
origin: string | null;
|
|
44
|
+
destination: string | null;
|
|
45
|
+
departDate: string;
|
|
46
|
+
returnDate: string;
|
|
47
|
+
cabin: CabinClass;
|
|
48
|
+
selectedOffer: FlightOffer | null;
|
|
49
|
+
ancillaryCatalog: AncillaryCatalog | null;
|
|
50
|
+
fareBundlePicks: NonNullable<AncillarySelection["fareBundle"]>;
|
|
51
|
+
baggagePicks: NonNullable<AncillarySelection["baggage"]>;
|
|
52
|
+
assistancePicks: NonNullable<AncillarySelection["assistance"]>;
|
|
53
|
+
extrasPicks: NonNullable<AncillarySelection["extras"]>;
|
|
54
|
+
sameFareForAllPassengers: boolean;
|
|
55
|
+
sameBaggageBothDirections: boolean;
|
|
56
|
+
commitError: string | null;
|
|
57
|
+
} | {
|
|
58
|
+
kind: "cruise";
|
|
59
|
+
localId: string;
|
|
60
|
+
description: string;
|
|
61
|
+
cabin: string;
|
|
62
|
+
embarkationDate: string;
|
|
63
|
+
estimatedAmount: string;
|
|
64
|
+
commitError: string | null;
|
|
65
|
+
} | {
|
|
66
|
+
kind: "manual";
|
|
67
|
+
localId: string;
|
|
68
|
+
name: string;
|
|
69
|
+
description: string;
|
|
70
|
+
currency: string;
|
|
71
|
+
subtotalCents: number | null;
|
|
72
|
+
taxRatePct: string;
|
|
73
|
+
startsAt: string;
|
|
74
|
+
endsAt: string;
|
|
75
|
+
commitError: string | null;
|
|
76
|
+
};
|
|
77
|
+
export declare function verticalsFor(messages: PanelsMessages): Array<{
|
|
78
|
+
kind: PendingVerticalKind;
|
|
79
|
+
label: string;
|
|
80
|
+
description: string;
|
|
81
|
+
}>;
|
|
82
|
+
export declare function verticalForKind(kind: "product" | "stay"): CatalogVertical;
|
|
83
|
+
export declare function genLocalId(): string;
|
|
84
|
+
export declare function catalogHitLabel(hit: CatalogSearchHit): string;
|
|
85
|
+
export declare function catalogHitSourceKind(hit: CatalogSearchHit): string | null;
|
|
86
|
+
export declare function catalogHitThumbnailUrl(hit: CatalogSearchHit): string | null;
|
|
87
|
+
export declare function catalogHitSourceConnectionId(hit: CatalogSearchHit): string | null;
|
|
88
|
+
export declare function catalogHitSourceRef(hit: CatalogSearchHit): string | null;
|
|
89
|
+
export declare function catalogHitStringField(hit: CatalogSearchHit, field: string): string | null;
|
|
90
|
+
export declare function newPendingComponent(kind: PendingVerticalKind): PendingComponent;
|
|
91
|
+
export declare function computePlaceholderTotals(subtotalCents: number | null, taxRatePct: string): {
|
|
92
|
+
subtotal: number;
|
|
93
|
+
tax: number;
|
|
94
|
+
total: number;
|
|
95
|
+
};
|
|
96
|
+
export declare function Section({ title, description, action, children, className, }: {
|
|
97
|
+
title?: string;
|
|
98
|
+
description?: string;
|
|
99
|
+
action?: ReactNode;
|
|
100
|
+
children: ReactNode;
|
|
101
|
+
className?: string;
|
|
102
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
103
|
+
export declare function Field({ label, className, children, }: {
|
|
104
|
+
label: string;
|
|
105
|
+
className?: string;
|
|
106
|
+
children: ReactNode;
|
|
107
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
108
|
+
export declare function verticalIconFor(kind: PendingVerticalKind): ComponentType<{
|
|
109
|
+
className?: string;
|
|
110
|
+
}>;
|
|
111
|
+
export declare function verticalLabelFor(kind: PendingVerticalKind, messages: PanelsMessages): string;
|
|
112
|
+
export declare function StatusAlert({ title, message, tone, }: {
|
|
113
|
+
title: string;
|
|
114
|
+
message: string;
|
|
115
|
+
tone?: "error";
|
|
116
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
117
|
+
export declare function AddComponentMenu({ onAdd, disabled, }: {
|
|
118
|
+
onAdd(kind: PendingVerticalKind): void;
|
|
119
|
+
disabled?: boolean;
|
|
120
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
121
|
+
export {};
|
|
122
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/admin/trips-panels/shared.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useOperatorAdminMessages as useAdminMessages } from "@voyant-travel/admin";
|
|
4
|
+
import { Alert, AlertDescription, AlertTitle } from "@voyant-travel/ui/components/alert";
|
|
5
|
+
import { Button } from "@voyant-travel/ui/components/button";
|
|
6
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@voyant-travel/ui/components/dropdown-menu";
|
|
7
|
+
import { Label } from "@voyant-travel/ui/components/label";
|
|
8
|
+
import { BedDouble, Check, CircleAlert, Plane, Plus, Route as RouteIcon, Sailboat, Wrench, } from "lucide-react";
|
|
9
|
+
export function verticalsFor(messages) {
|
|
10
|
+
const v = messages.verticals;
|
|
11
|
+
return [
|
|
12
|
+
{ kind: "product", label: v.productLabel, description: v.productDescription },
|
|
13
|
+
{ kind: "stay", label: v.stayLabel, description: v.stayDescription },
|
|
14
|
+
{ kind: "flight", label: v.flightLabel, description: v.flightDescription },
|
|
15
|
+
{ kind: "cruise", label: v.cruiseLabel, description: v.cruiseDescription },
|
|
16
|
+
{ kind: "manual", label: v.manualLabel, description: v.manualDescription },
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
export function verticalForKind(kind) {
|
|
20
|
+
return kind === "stay" ? "accommodations" : "products";
|
|
21
|
+
}
|
|
22
|
+
export function genLocalId() {
|
|
23
|
+
return `pc_${Math.random().toString(36).slice(2, 10)}`;
|
|
24
|
+
}
|
|
25
|
+
export function catalogHitLabel(hit) {
|
|
26
|
+
return (catalogHitStringField(hit, "name") ??
|
|
27
|
+
catalogHitStringField(hit, "title") ??
|
|
28
|
+
catalogHitStringField(hit, "hotel.name") ??
|
|
29
|
+
hit.id);
|
|
30
|
+
}
|
|
31
|
+
export function catalogHitSourceKind(hit) {
|
|
32
|
+
return catalogHitStringField(hit, "source.kind");
|
|
33
|
+
}
|
|
34
|
+
export function catalogHitThumbnailUrl(hit) {
|
|
35
|
+
return (catalogHitStringField(hit, "thumbnailUrl") ??
|
|
36
|
+
catalogHitStringField(hit, "hero_image_url") ??
|
|
37
|
+
catalogHitStringField(hit, "imageUrl"));
|
|
38
|
+
}
|
|
39
|
+
export function catalogHitSourceConnectionId(hit) {
|
|
40
|
+
return (catalogHitStringField(hit, "source.connectionId") ??
|
|
41
|
+
catalogHitStringField(hit, "source_connection_id"));
|
|
42
|
+
}
|
|
43
|
+
export function catalogHitSourceRef(hit) {
|
|
44
|
+
return catalogHitStringField(hit, "source.ref") ?? catalogHitStringField(hit, "source_ref");
|
|
45
|
+
}
|
|
46
|
+
export function catalogHitStringField(hit, field) {
|
|
47
|
+
const value = hit.document.fields[field];
|
|
48
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
49
|
+
}
|
|
50
|
+
export function newPendingComponent(kind) {
|
|
51
|
+
const localId = genLocalId();
|
|
52
|
+
switch (kind) {
|
|
53
|
+
case "product":
|
|
54
|
+
case "stay":
|
|
55
|
+
return {
|
|
56
|
+
kind,
|
|
57
|
+
localId,
|
|
58
|
+
catalogEntityId: null,
|
|
59
|
+
catalogEntityName: null,
|
|
60
|
+
catalogSourceKind: null,
|
|
61
|
+
catalogSourceConnectionId: null,
|
|
62
|
+
catalogSourceRef: null,
|
|
63
|
+
catalogThumbnailUrl: null,
|
|
64
|
+
bookingDraft: null,
|
|
65
|
+
startsAt: "",
|
|
66
|
+
endsAt: "",
|
|
67
|
+
commitError: null,
|
|
68
|
+
};
|
|
69
|
+
case "flight":
|
|
70
|
+
return {
|
|
71
|
+
kind,
|
|
72
|
+
localId,
|
|
73
|
+
tripType: "one_way",
|
|
74
|
+
origin: null,
|
|
75
|
+
destination: null,
|
|
76
|
+
departDate: "",
|
|
77
|
+
returnDate: "",
|
|
78
|
+
cabin: "economy",
|
|
79
|
+
selectedOffer: null,
|
|
80
|
+
ancillaryCatalog: null,
|
|
81
|
+
fareBundlePicks: [],
|
|
82
|
+
baggagePicks: [],
|
|
83
|
+
assistancePicks: [],
|
|
84
|
+
extrasPicks: [],
|
|
85
|
+
sameFareForAllPassengers: true,
|
|
86
|
+
sameBaggageBothDirections: true,
|
|
87
|
+
commitError: null,
|
|
88
|
+
};
|
|
89
|
+
case "cruise":
|
|
90
|
+
return {
|
|
91
|
+
kind,
|
|
92
|
+
localId,
|
|
93
|
+
description: "",
|
|
94
|
+
cabin: "",
|
|
95
|
+
embarkationDate: "",
|
|
96
|
+
estimatedAmount: "",
|
|
97
|
+
commitError: null,
|
|
98
|
+
};
|
|
99
|
+
default:
|
|
100
|
+
return {
|
|
101
|
+
kind,
|
|
102
|
+
localId,
|
|
103
|
+
name: "",
|
|
104
|
+
description: "",
|
|
105
|
+
currency: "EUR",
|
|
106
|
+
subtotalCents: null,
|
|
107
|
+
taxRatePct: "",
|
|
108
|
+
startsAt: "",
|
|
109
|
+
endsAt: "",
|
|
110
|
+
commitError: null,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export function computePlaceholderTotals(subtotalCents, taxRatePct) {
|
|
115
|
+
const subtotal = subtotalCents ?? 0;
|
|
116
|
+
const rate = Number.parseFloat(taxRatePct || "0") / 100;
|
|
117
|
+
const validRate = Number.isFinite(rate) && rate >= 0 ? rate : 0;
|
|
118
|
+
const tax = Math.round(subtotal * validRate);
|
|
119
|
+
return { subtotal, tax, total: subtotal + tax };
|
|
120
|
+
}
|
|
121
|
+
export function Section({ title, description, action, children, className, }) {
|
|
122
|
+
return (_jsxs("section", { className: `flex flex-col gap-4 rounded-md border bg-card p-5 ${className ?? ""}`, children: [title || description || action ? (_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0", children: [title ? _jsx("h2", { className: "font-medium text-base", children: title }) : null, description ? _jsx("p", { className: "text-muted-foreground text-sm", children: description }) : null] }), action] })) : null, children] }));
|
|
123
|
+
}
|
|
124
|
+
export function Field({ label, className, children, }) {
|
|
125
|
+
return (_jsxs("div", { className: className ? `flex flex-col gap-1.5 ${className}` : "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: label }), children] }));
|
|
126
|
+
}
|
|
127
|
+
export function verticalIconFor(kind) {
|
|
128
|
+
if (kind === "product")
|
|
129
|
+
return RouteIcon;
|
|
130
|
+
if (kind === "stay")
|
|
131
|
+
return BedDouble;
|
|
132
|
+
if (kind === "flight")
|
|
133
|
+
return Plane;
|
|
134
|
+
if (kind === "cruise")
|
|
135
|
+
return Sailboat;
|
|
136
|
+
return Wrench;
|
|
137
|
+
}
|
|
138
|
+
export function verticalLabelFor(kind, messages) {
|
|
139
|
+
const found = verticalsFor(messages).find((vertical) => vertical.kind === kind);
|
|
140
|
+
return found?.label ?? kind;
|
|
141
|
+
}
|
|
142
|
+
export function StatusAlert({ title, message, tone, }) {
|
|
143
|
+
return (_jsxs(Alert, { variant: tone === "error" ? "destructive" : "default", children: [tone === "error" ? _jsx(CircleAlert, { className: "size-4" }) : _jsx(Check, { className: "size-4" }), _jsx(AlertTitle, { children: title }), _jsx(AlertDescription, { children: message })] }));
|
|
144
|
+
}
|
|
145
|
+
export function AddComponentMenu({ onAdd, disabled, }) {
|
|
146
|
+
const t = useAdminMessages().trips.adminComposer.panels;
|
|
147
|
+
const verticals = verticalsFor(t);
|
|
148
|
+
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { render: _jsxs(Button, { variant: "outline", disabled: disabled, className: "w-full", children: [_jsx(Plus, { className: "size-4" }), t.addComponentMenu] }) }), _jsx(DropdownMenuContent, { align: "end", className: "w-72", children: verticals.map((vertical) => {
|
|
149
|
+
const Icon = verticalIconFor(vertical.kind);
|
|
150
|
+
return (_jsxs(DropdownMenuItem, { onClick: () => onAdd(vertical.kind), className: "flex items-start gap-3 py-2", children: [_jsx("span", { className: "flex size-7 shrink-0 items-center justify-center rounded-md bg-muted", children: _jsx(Icon, { className: "size-4" }) }), _jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "font-medium text-sm", children: vertical.label }), _jsx("span", { className: "text-muted-foreground text-xs", children: vertical.description })] })] }, vertical.kind));
|
|
151
|
+
}) })] }));
|
|
152
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type TravelerCategory = "adult" | "child" | "infant";
|
|
2
|
+
export type TripTravelerRole = "lead" | TravelerCategory;
|
|
3
|
+
export interface TripTraveler {
|
|
4
|
+
localId: string;
|
|
5
|
+
personId: string | null;
|
|
6
|
+
firstName: string;
|
|
7
|
+
lastName: string;
|
|
8
|
+
email: string;
|
|
9
|
+
dateOfBirth: string | null;
|
|
10
|
+
role: TripTravelerRole;
|
|
11
|
+
}
|
|
12
|
+
export declare function newTripTraveler(): TripTraveler;
|
|
13
|
+
export declare function TripTravelersSection({ value, onChange, billingPersonId, }: {
|
|
14
|
+
value: TripTraveler[];
|
|
15
|
+
onChange(next: TripTraveler[]): void;
|
|
16
|
+
billingPersonId?: string | null;
|
|
17
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export declare function BillingQuickAdd({ billingPersonId, existingPersonIds, onAdd, }: {
|
|
19
|
+
billingPersonId: string;
|
|
20
|
+
existingPersonIds: Set<string | null>;
|
|
21
|
+
onAdd(personId: string): void;
|
|
22
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
23
|
+
export declare function RelatedPersonChip({ personId, billingPersonId, relationships, disabled, onAdd, }: {
|
|
24
|
+
personId: string;
|
|
25
|
+
billingPersonId: string;
|
|
26
|
+
relationships: Array<{
|
|
27
|
+
fromPersonId: string;
|
|
28
|
+
toPersonId: string;
|
|
29
|
+
kind: string;
|
|
30
|
+
inverseKind: string | null;
|
|
31
|
+
}>;
|
|
32
|
+
disabled: boolean;
|
|
33
|
+
onAdd(): void;
|
|
34
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
35
|
+
export declare function formatPersonName(person: {
|
|
36
|
+
firstName?: string | null;
|
|
37
|
+
lastName?: string | null;
|
|
38
|
+
email?: string | null;
|
|
39
|
+
} | undefined | null): string | null;
|
|
40
|
+
export declare function formatRelationshipKind(kind: string): string;
|
|
41
|
+
export declare function TripTravelerRow({ traveler, isLead, onPatch, onRemove, }: {
|
|
42
|
+
traveler: TripTraveler;
|
|
43
|
+
isLead: boolean;
|
|
44
|
+
onPatch(patch: Partial<TripTraveler>): void;
|
|
45
|
+
onRemove(): void;
|
|
46
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
47
|
+
export declare function CategoryToggle({ value, onChange, disabled, }: {
|
|
48
|
+
value: TravelerCategory;
|
|
49
|
+
onChange(value: TravelerCategory): void;
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
52
|
+
export declare function formatDateOnly(iso: string | null): string;
|
|
53
|
+
//# sourceMappingURL=travelers-section.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"travelers-section.d.ts","sourceRoot":"","sources":["../../../src/admin/trips-panels/travelers-section.tsx"],"names":[],"mappings":"AAmBA,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAA;AAC3D,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,gBAAgB,CAAA;AAExD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,IAAI,EAAE,gBAAgB,CAAA;CACvB;AAED,wBAAgB,eAAe,IAAI,YAAY,CAU9C;AAED,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,QAAQ,EACR,eAAe,GAChB,EAAE;IACD,KAAK,EAAE,YAAY,EAAE,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,IAAI,CAAA;IACpC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC,2CAkEA;AAED,wBAAgB,eAAe,CAAC,EAC9B,eAAe,EACf,iBAAiB,EACjB,KAAK,GACN,EAAE;IACD,eAAe,EAAE,MAAM,CAAA;IACvB,iBAAiB,EAAE,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACrC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B,kDA8DA;AAED,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,eAAe,EACf,aAAa,EACb,QAAQ,EACR,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,MAAM,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;IACvB,aAAa,EAAE,KAAK,CAAC;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,UAAU,EAAE,MAAM,CAAA;QAClB,IAAI,EAAE,MAAM,CAAA;QACZ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAC3B,CAAC,CAAA;IACF,QAAQ,EAAE,OAAO,CAAA;IACjB,KAAK,IAAI,IAAI,CAAA;CACd,2CAgCA;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EACF;IACE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB,GACD,SAAS,GACT,IAAI,GACP,MAAM,GAAG,IAAI,CAOf;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,MAAM,EACN,OAAO,EACP,QAAQ,GACT,EAAE;IACD,QAAQ,EAAE,YAAY,CAAA;IACtB,MAAM,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI,CAAA;IAC3C,QAAQ,IAAI,IAAI,CAAA;CACjB,2CA6JA;AAED,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,QAAQ,EACR,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,gBAAgB,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,2CAsBA;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CASzD"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useOperatorAdminMessages as useAdminMessages } from "@voyant-travel/admin";
|
|
4
|
+
import { deriveTravelerRoleFromDob } from "@voyant-travel/bookings-react/components/travelers-section";
|
|
5
|
+
import { usePerson, usePersonRelationships } from "@voyant-travel/relationships-react";
|
|
6
|
+
import { PersonCombobox, PersonForm } from "@voyant-travel/relationships-react/ui";
|
|
7
|
+
import { Sheet, SheetBody, SheetContent, SheetHeader, SheetTitle, } from "@voyant-travel/ui/components";
|
|
8
|
+
import { Badge } from "@voyant-travel/ui/components/badge";
|
|
9
|
+
import { Button } from "@voyant-travel/ui/components/button";
|
|
10
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@voyant-travel/ui/components/tooltip";
|
|
11
|
+
import { Info, Pencil, Trash2, UserPlus } from "lucide-react";
|
|
12
|
+
import * as React from "react";
|
|
13
|
+
export function newTripTraveler() {
|
|
14
|
+
return {
|
|
15
|
+
localId: `tt_${Math.random().toString(36).slice(2, 10)}`,
|
|
16
|
+
personId: null,
|
|
17
|
+
firstName: "",
|
|
18
|
+
lastName: "",
|
|
19
|
+
email: "",
|
|
20
|
+
dateOfBirth: null,
|
|
21
|
+
role: "adult",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function TripTravelersSection({ value, onChange, billingPersonId, }) {
|
|
25
|
+
function patchAt(localId, patch) {
|
|
26
|
+
onChange(value.map((traveler) => traveler.localId === localId ? { ...traveler, ...patch } : traveler));
|
|
27
|
+
}
|
|
28
|
+
function removeAt(localId) {
|
|
29
|
+
onChange(value.filter((traveler) => traveler.localId !== localId));
|
|
30
|
+
}
|
|
31
|
+
function addTravelerByPersonId(personId) {
|
|
32
|
+
if (value.some((traveler) => traveler.personId === personId))
|
|
33
|
+
return;
|
|
34
|
+
onChange([...value, { ...newTripTraveler(), personId }]);
|
|
35
|
+
}
|
|
36
|
+
const existingPersonIds = new Set(value.map((traveler) => traveler.personId).filter(Boolean));
|
|
37
|
+
// Lead = billing person when they're on the roster; otherwise the first
|
|
38
|
+
// traveler. Keeps the "who is the primary traveler" intent natural without
|
|
39
|
+
// forcing operators to reorder the list.
|
|
40
|
+
const leadLocalId = (billingPersonId && value.find((t) => t.personId === billingPersonId)?.localId) ||
|
|
41
|
+
value[0]?.localId ||
|
|
42
|
+
null;
|
|
43
|
+
const t = useAdminMessages().trips.adminComposer.panels;
|
|
44
|
+
return (_jsxs("section", { className: "flex flex-col gap-3 rounded-md border bg-card p-5", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx("h2", { className: "font-medium text-base", children: t.travelersSectionTitle }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => onChange([...value, newTripTraveler()]), children: [_jsx(UserPlus, { className: "size-3.5" }), t.addTravelerLabel] })] }), billingPersonId ? (_jsx(BillingQuickAdd, { billingPersonId: billingPersonId, existingPersonIds: existingPersonIds, onAdd: addTravelerByPersonId })) : null, value.length === 0 ? (_jsxs("p", { className: "text-muted-foreground text-sm", children: [t.noTravelersPrefix, _jsx("span", { className: "font-medium", children: t.addTravelerLabel }), "."] })) : (_jsx("div", { className: "flex flex-col gap-2", children: value.map((traveler) => (_jsx(TripTravelerRow, { traveler: traveler, isLead: traveler.localId === leadLocalId, onPatch: (patch) => patchAt(traveler.localId, patch), onRemove: () => removeAt(traveler.localId) }, traveler.localId))) }))] }));
|
|
45
|
+
}
|
|
46
|
+
export function BillingQuickAdd({ billingPersonId, existingPersonIds, onAdd, }) {
|
|
47
|
+
const t = useAdminMessages().trips.adminComposer.panels;
|
|
48
|
+
const billingPersonQuery = usePerson(billingPersonId);
|
|
49
|
+
const relationshipsQuery = usePersonRelationships(billingPersonId);
|
|
50
|
+
const billingPerson = billingPersonQuery.data;
|
|
51
|
+
const billingAlreadyAdded = existingPersonIds.has(billingPersonId);
|
|
52
|
+
const relatedPersonIds = React.useMemo(() => {
|
|
53
|
+
const ids = new Set();
|
|
54
|
+
for (const relationship of relationshipsQuery.data?.data ?? []) {
|
|
55
|
+
const otherId = relationship.fromPersonId === billingPersonId
|
|
56
|
+
? relationship.toPersonId
|
|
57
|
+
: relationship.fromPersonId;
|
|
58
|
+
if (otherId && otherId !== billingPersonId)
|
|
59
|
+
ids.add(otherId);
|
|
60
|
+
}
|
|
61
|
+
return [...ids];
|
|
62
|
+
}, [relationshipsQuery.data?.data, billingPersonId]);
|
|
63
|
+
const hasRelationships = relatedPersonIds.length > 0;
|
|
64
|
+
if (billingAlreadyAdded && !hasRelationships)
|
|
65
|
+
return null;
|
|
66
|
+
const billingName = formatPersonName(billingPerson) ?? t.travelersAddRow.billingPersonFallback;
|
|
67
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [!billingAlreadyAdded ? (_jsxs(Button, { variant: "outline", size: "sm", className: "self-start", onClick: () => onAdd(billingPersonId), children: [_jsx(UserPlus, { className: "size-3.5" }), t.travelersAddRow.addBillingPersonPrefix, billingName, t.travelersAddRow.addBillingPersonSuffix] })) : null, hasRelationships ? (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsxs("span", { className: "text-muted-foreground text-xs", children: [t.travelersAddRow.fromRelationshipsPrefix, billingName, t.travelersAddRow.fromRelationshipsSuffix] }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: relatedPersonIds.map((personId) => (_jsx(RelatedPersonChip, { personId: personId, billingPersonId: billingPersonId, relationships: relationshipsQuery.data?.data ?? [], disabled: existingPersonIds.has(personId), onAdd: () => onAdd(personId) }, personId))) })] })) : null] }));
|
|
68
|
+
}
|
|
69
|
+
export function RelatedPersonChip({ personId, billingPersonId, relationships, disabled, onAdd, }) {
|
|
70
|
+
const personQuery = usePerson(personId);
|
|
71
|
+
const name = formatPersonName(personQuery.data) ?? "—";
|
|
72
|
+
const relation = relationships.find((relationship) => (relationship.fromPersonId === billingPersonId && relationship.toPersonId === personId) ||
|
|
73
|
+
(relationship.toPersonId === billingPersonId && relationship.fromPersonId === personId));
|
|
74
|
+
const kindLabel = relation
|
|
75
|
+
? formatRelationshipKind(relation.toPersonId === billingPersonId && relation.inverseKind
|
|
76
|
+
? relation.inverseKind
|
|
77
|
+
: relation.kind)
|
|
78
|
+
: null;
|
|
79
|
+
return (_jsxs(Button, { variant: "outline", size: "sm", disabled: disabled, onClick: onAdd, className: "h-auto py-1.5", children: [_jsx(UserPlus, { className: "size-3.5" }), _jsx("span", { children: name }), kindLabel ? (_jsx(Badge, { variant: "secondary", className: "ml-1 text-[10px] capitalize", children: kindLabel })) : null] }));
|
|
80
|
+
}
|
|
81
|
+
export function formatPersonName(person) {
|
|
82
|
+
if (!person)
|
|
83
|
+
return null;
|
|
84
|
+
const name = [person.firstName, person.lastName]
|
|
85
|
+
.filter((part) => (part ?? "").trim().length > 0)
|
|
86
|
+
.join(" ")
|
|
87
|
+
.trim();
|
|
88
|
+
return name || person.email || null;
|
|
89
|
+
}
|
|
90
|
+
export function formatRelationshipKind(kind) {
|
|
91
|
+
return kind.replaceAll("_", " ");
|
|
92
|
+
}
|
|
93
|
+
export function TripTravelerRow({ traveler, isLead, onPatch, onRemove, }) {
|
|
94
|
+
const t = useAdminMessages().trips.adminComposer.panels;
|
|
95
|
+
const personQuery = usePerson(traveler.personId ?? undefined, {
|
|
96
|
+
enabled: Boolean(traveler.personId),
|
|
97
|
+
});
|
|
98
|
+
React.useEffect(() => {
|
|
99
|
+
const person = personQuery.data;
|
|
100
|
+
if (!person)
|
|
101
|
+
return;
|
|
102
|
+
const nextDob = person.dateOfBirth ?? null;
|
|
103
|
+
const derivedCategory = deriveTravelerRoleFromDob(nextDob);
|
|
104
|
+
const nextRole = isLead
|
|
105
|
+
? "lead"
|
|
106
|
+
: nextDob
|
|
107
|
+
? derivedCategory
|
|
108
|
+
: traveler.role === "lead"
|
|
109
|
+
? "adult"
|
|
110
|
+
: traveler.role;
|
|
111
|
+
const patch = {};
|
|
112
|
+
if ((person.firstName ?? "") !== traveler.firstName)
|
|
113
|
+
patch.firstName = person.firstName ?? "";
|
|
114
|
+
if ((person.lastName ?? "") !== traveler.lastName)
|
|
115
|
+
patch.lastName = person.lastName ?? "";
|
|
116
|
+
if ((person.email ?? "") !== traveler.email)
|
|
117
|
+
patch.email = person.email ?? "";
|
|
118
|
+
if (nextDob !== traveler.dateOfBirth)
|
|
119
|
+
patch.dateOfBirth = nextDob;
|
|
120
|
+
if (nextRole !== traveler.role)
|
|
121
|
+
patch.role = nextRole;
|
|
122
|
+
if (Object.keys(patch).length > 0)
|
|
123
|
+
onPatch(patch);
|
|
124
|
+
}, [
|
|
125
|
+
personQuery.data,
|
|
126
|
+
isLead,
|
|
127
|
+
onPatch,
|
|
128
|
+
traveler.dateOfBirth,
|
|
129
|
+
traveler.email,
|
|
130
|
+
traveler.firstName,
|
|
131
|
+
traveler.lastName,
|
|
132
|
+
traveler.role,
|
|
133
|
+
]);
|
|
134
|
+
React.useEffect(() => {
|
|
135
|
+
if (isLead && traveler.role !== "lead")
|
|
136
|
+
onPatch({ role: "lead" });
|
|
137
|
+
if (!isLead && traveler.role === "lead") {
|
|
138
|
+
const derived = deriveTravelerRoleFromDob(traveler.dateOfBirth);
|
|
139
|
+
onPatch({ role: derived });
|
|
140
|
+
}
|
|
141
|
+
}, [isLead, onPatch, traveler.dateOfBirth, traveler.role]);
|
|
142
|
+
const [sheetOpen, setSheetOpen] = React.useState(false);
|
|
143
|
+
const [sheetMode, setSheetMode] = React.useState("create");
|
|
144
|
+
const lockedByDob = Boolean(traveler.dateOfBirth);
|
|
145
|
+
const displayCategory = lockedByDob
|
|
146
|
+
? deriveTravelerRoleFromDob(traveler.dateOfBirth)
|
|
147
|
+
: traveler.role === "lead"
|
|
148
|
+
? "adult"
|
|
149
|
+
: traveler.role;
|
|
150
|
+
return (_jsxs("div", { className: "flex flex-col gap-3 rounded-md border p-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "min-w-0 flex-1", children: _jsx(PersonCombobox, { value: traveler.personId, onChange: (personId) => onPatch({ personId }), placeholder: t.personPickerPlaceholder }) }), traveler.personId && personQuery.data ? (_jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => {
|
|
151
|
+
setSheetMode("edit");
|
|
152
|
+
setSheetOpen(true);
|
|
153
|
+
}, children: [_jsx(Pencil, { className: "size-3.5" }), t.travelerRow.editAction] })) : null, _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
|
|
154
|
+
setSheetMode("create");
|
|
155
|
+
setSheetOpen(true);
|
|
156
|
+
}, children: [_jsx(UserPlus, { className: "size-3.5" }), t.travelerRow.newAction] })] }), _jsx(Sheet, { open: sheetOpen, onOpenChange: setSheetOpen, children: _jsxs(SheetContent, { side: "right", size: "lg", children: [_jsx(SheetHeader, { children: _jsx(SheetTitle, { children: sheetMode === "edit" ? t.travelerRow.editPerson : t.travelerRow.createPerson }) }), _jsx(SheetBody, { children: _jsx(PersonForm, { mode: sheetMode === "edit" && personQuery.data
|
|
157
|
+
? { kind: "edit", person: personQuery.data }
|
|
158
|
+
: { kind: "create" }, onCancel: () => setSheetOpen(false), onSuccess: (person) => {
|
|
159
|
+
onPatch({ personId: person.id });
|
|
160
|
+
setSheetOpen(false);
|
|
161
|
+
} }) })] }) }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [isLead ? (_jsx(Badge, { children: t.leadBadge })) : (_jsxs(_Fragment, { children: [_jsx(CategoryToggle, { value: displayCategory, onChange: (role) => onPatch({ role }), disabled: lockedByDob }), lockedByDob ? (_jsxs("span", { className: "text-muted-foreground text-xs", children: ["Auto from DOB (", formatDateOnly(traveler.dateOfBirth), ")"] })) : (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx("button", { type: "button", "aria-label": t.categoryManualAria, className: "text-muted-foreground hover:text-foreground" }), children: _jsx(Info, { className: "size-3.5" }) }), _jsx(TooltipContent, { children: t.travelerRow.manualCategoryHint })] }))] })), _jsx(Button, { variant: "ghost", size: "sm", className: "ml-auto", onClick: onRemove, "aria-label": t.removeTraveler, children: _jsx(Trash2, { className: "size-3.5" }) })] })] }));
|
|
162
|
+
}
|
|
163
|
+
export function CategoryToggle({ value, onChange, disabled, }) {
|
|
164
|
+
const t = useAdminMessages().trips.adminComposer.panels;
|
|
165
|
+
const options = [
|
|
166
|
+
{ value: "adult", label: t.travelerRow.categoryAdult },
|
|
167
|
+
{ value: "child", label: t.travelerRow.categoryChild },
|
|
168
|
+
{ value: "infant", label: t.travelerRow.categoryInfant },
|
|
169
|
+
];
|
|
170
|
+
return (_jsx("div", { className: "flex gap-1", children: options.map((option) => (_jsx(Button, { size: "sm", variant: value === option.value ? "default" : "outline", onClick: () => onChange(option.value), disabled: disabled, children: option.label }, option.value))) }));
|
|
171
|
+
}
|
|
172
|
+
export function formatDateOnly(iso) {
|
|
173
|
+
if (!iso)
|
|
174
|
+
return "";
|
|
175
|
+
const parsed = new Date(iso);
|
|
176
|
+
if (Number.isNaN(parsed.getTime()))
|
|
177
|
+
return iso;
|
|
178
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
179
|
+
year: "numeric",
|
|
180
|
+
month: "short",
|
|
181
|
+
day: "numeric",
|
|
182
|
+
}).format(parsed);
|
|
183
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { PersonPickerValue, VoucherPickerValue } from "@voyant-travel/bookings-react/ui";
|
|
2
|
+
import type { Trip, TripComponent } from "@voyant-travel/trips";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { type TripTraveler } from "./travelers-section.js";
|
|
5
|
+
export declare function TripPreviewRail({ trip, pendingCount, travelers, billing, billingPersonId, voucher, onVoucherChange, paymentCurrency, }: {
|
|
6
|
+
trip: Trip | null;
|
|
7
|
+
pendingCount: number;
|
|
8
|
+
travelers: TripTraveler[];
|
|
9
|
+
billing: PersonPickerValue;
|
|
10
|
+
billingPersonId?: string | null;
|
|
11
|
+
voucher: VoucherPickerValue;
|
|
12
|
+
onVoucherChange(value: VoucherPickerValue): void;
|
|
13
|
+
paymentCurrency: string;
|
|
14
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export declare function PreviewComponentRow({ component }: {
|
|
16
|
+
component: TripComponent;
|
|
17
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export declare function BillingPreview({ billing }: {
|
|
19
|
+
billing: PersonPickerValue;
|
|
20
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
21
|
+
export declare function TravelersPreview({ travelers, billingPersonId, }: {
|
|
22
|
+
travelers: TripTraveler[];
|
|
23
|
+
billingPersonId: string | null;
|
|
24
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
25
|
+
export declare function TravelerPreviewRow({ traveler, index, isLead, }: {
|
|
26
|
+
traveler: TripTraveler;
|
|
27
|
+
index: number;
|
|
28
|
+
isLead: boolean;
|
|
29
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
30
|
+
export declare function CurrencyTotals({ components }: {
|
|
31
|
+
components: TripComponent[];
|
|
32
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
33
|
+
interface CurrencyBucket {
|
|
34
|
+
currency: string;
|
|
35
|
+
subtotal: number;
|
|
36
|
+
tax: number;
|
|
37
|
+
total: number;
|
|
38
|
+
}
|
|
39
|
+
export declare function aggregateByCurrency(components: TripComponent[]): CurrencyBucket[];
|
|
40
|
+
export declare function PrimaryAction({ status, componentCount, isBusy, pricePending, reservePending, onReserve, }: {
|
|
41
|
+
status: string | undefined;
|
|
42
|
+
componentCount: number;
|
|
43
|
+
isBusy: boolean;
|
|
44
|
+
pricePending: boolean;
|
|
45
|
+
reservePending: boolean;
|
|
46
|
+
onReserve(): void;
|
|
47
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
48
|
+
export declare function PreviewLabel({ children }: {
|
|
49
|
+
children: React.ReactNode;
|
|
50
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
51
|
+
export {};
|
|
52
|
+
//# sourceMappingURL=trip-preview-rail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trip-preview-rail.d.ts","sourceRoot":"","sources":["../../../src/admin/trips-panels/trip-preview-rail.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AAI7F,OAAO,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAK/D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAa9B,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAE5E,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,OAAO,EACP,eAAe,EACf,OAAO,EACP,eAAe,EACf,eAAe,GAChB,EAAE;IACD,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,YAAY,EAAE,CAAA;IACzB,OAAO,EAAE,iBAAiB,CAAA;IAC1B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,OAAO,EAAE,kBAAkB,CAAA;IAC3B,eAAe,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAAA;IAChD,eAAe,EAAE,MAAM,CAAA;CACxB,2CA8EA;AAED,wBAAgB,mBAAmB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,aAAa,CAAA;CAAE,2CAqC9E;AAED,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,iBAAiB,CAAA;CAAE,kDAmBzE;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,SAAS,EACT,eAAe,GAChB,EAAE;IACD,SAAS,EAAE,YAAY,EAAE,CAAA;IACzB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B,kDAyBA;AAED,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,KAAK,EACL,MAAM,GACP,EAAE;IACD,QAAQ,EAAE,YAAY,CAAA;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,OAAO,CAAA;CAChB,2CAqBA;AAED,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,aAAa,EAAE,CAAA;CAAE,kDAyB7E;AAED,UAAU,cAAc;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,cAAc,EAAE,CAYjF;AAED,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,cAAc,EACd,MAAM,EACN,YAAY,EACZ,cAAc,EACd,SAAS,GACV,EAAE;IACD,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,OAAO,CAAA;IACf,YAAY,EAAE,OAAO,CAAA;IACrB,cAAc,EAAE,OAAO,CAAA;IACvB,SAAS,IAAI,IAAI,CAAA;CAClB,kDAkDA;AAED,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,2CAMvE"}
|