@voyant-travel/catalog-react 0.117.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 +36 -0
- package/dist/admin/catalog-vertical-host.d.ts +45 -0
- package/dist/admin/catalog-vertical-host.d.ts.map +1 -0
- package/dist/admin/catalog-vertical-host.js +230 -0
- package/dist/admin/cruise-detail-host.d.ts +11 -0
- package/dist/admin/cruise-detail-host.d.ts.map +1 -0
- package/dist/admin/cruise-detail-host.js +33 -0
- package/dist/admin/dynamic-catalog-host.d.ts +13 -0
- package/dist/admin/dynamic-catalog-host.d.ts.map +1 -0
- package/dist/admin/dynamic-catalog-host.js +17 -0
- package/dist/admin/index.d.ts +133 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +144 -0
- package/dist/admin/open-in-new-tab.d.ts +7 -0
- package/dist/admin/open-in-new-tab.d.ts.map +1 -0
- package/dist/admin/open-in-new-tab.js +10 -0
- package/dist/admin/pages/catalog-accommodations-detail-page.d.ts +4 -0
- package/dist/admin/pages/catalog-accommodations-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-accommodations-detail-page.js +7 -0
- package/dist/admin/pages/catalog-accommodations-index-page.d.ts +8 -0
- package/dist/admin/pages/catalog-accommodations-index-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-accommodations-index-page.js +17 -0
- package/dist/admin/pages/catalog-cruises-detail-page.d.ts +4 -0
- package/dist/admin/pages/catalog-cruises-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-cruises-detail-page.js +7 -0
- package/dist/admin/pages/catalog-cruises-index-page.d.ts +8 -0
- package/dist/admin/pages/catalog-cruises-index-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-cruises-index-page.js +19 -0
- package/dist/admin/pages/catalog-excursions-detail-page.d.ts +4 -0
- package/dist/admin/pages/catalog-excursions-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-excursions-detail-page.js +7 -0
- package/dist/admin/pages/catalog-excursions-index-page.d.ts +8 -0
- package/dist/admin/pages/catalog-excursions-index-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-excursions-index-page.js +12 -0
- package/dist/admin/pages/catalog-products-detail-page.d.ts +8 -0
- package/dist/admin/pages/catalog-products-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-products-detail-page.js +12 -0
- package/dist/admin/pages/catalog-products-index-page.d.ts +8 -0
- package/dist/admin/pages/catalog-products-index-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-products-index-page.js +12 -0
- package/dist/admin/pages/catalog-tours-detail-page.d.ts +4 -0
- package/dist/admin/pages/catalog-tours-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-tours-detail-page.js +7 -0
- package/dist/admin/pages/catalog-tours-index-page.d.ts +8 -0
- package/dist/admin/pages/catalog-tours-index-page.d.ts.map +1 -0
- package/dist/admin/pages/catalog-tours-index-page.js +12 -0
- package/dist/admin/product-detail-host.d.ts +18 -0
- package/dist/admin/product-detail-host.d.ts.map +1 -0
- package/dist/admin/product-detail-host.js +40 -0
- package/dist/admin/scheduled-catalog-host.d.ts +15 -0
- package/dist/admin/scheduled-catalog-host.d.ts.map +1 -0
- package/dist/admin/scheduled-catalog-host.js +19 -0
- package/dist/admin/vertical-detail-host.d.ts +13 -0
- package/dist/admin/vertical-detail-host.d.ts.map +1 -0
- package/dist/admin/vertical-detail-host.js +62 -0
- package/dist/booking-engine/index.d.ts +26 -0
- package/dist/booking-engine/index.d.ts.map +1 -0
- package/dist/booking-engine/index.js +25 -0
- package/dist/booking-engine/use-booking-commit.d.ts +61 -0
- package/dist/booking-engine/use-booking-commit.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-commit.js +47 -0
- package/dist/booking-engine/use-booking-draft-shape.d.ts +20 -0
- package/dist/booking-engine/use-booking-draft-shape.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-draft-shape.js +9 -0
- package/dist/booking-engine/use-booking-draft.d.ts +102 -0
- package/dist/booking-engine/use-booking-draft.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-draft.js +93 -0
- package/dist/booking-engine/use-booking-hold.d.ts +30 -0
- package/dist/booking-engine/use-booking-hold.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-hold.js +44 -0
- package/dist/booking-engine/use-booking-journey-api.d.ts +23 -0
- package/dist/booking-engine/use-booking-journey-api.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-journey-api.js +24 -0
- package/dist/booking-engine/use-booking-quote.d.ts +711 -0
- package/dist/booking-engine/use-booking-quote.d.ts.map +1 -0
- package/dist/booking-engine/use-booking-quote.js +122 -0
- package/dist/catalog-enrichment-mappers.d.ts +162 -0
- package/dist/catalog-enrichment-mappers.d.ts.map +1 -0
- package/dist/catalog-enrichment-mappers.js +190 -0
- package/dist/catalog-enrichment.d.ts +203 -0
- package/dist/catalog-enrichment.d.ts.map +1 -0
- package/dist/catalog-enrichment.js +130 -0
- package/dist/catalog-offers-client.d.ts +58 -0
- package/dist/catalog-offers-client.d.ts.map +1 -0
- package/dist/catalog-offers-client.js +61 -0
- package/dist/catalog-search-params.d.ts +45 -0
- package/dist/catalog-search-params.d.ts.map +1 -0
- package/dist/catalog-search-params.js +30 -0
- package/dist/catalog-surfaces.d.ts +17 -0
- package/dist/catalog-surfaces.d.ts.map +1 -0
- package/dist/catalog-surfaces.js +26 -0
- package/dist/client.d.ts +20 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +65 -0
- package/dist/components/availability-calendar.d.ts +33 -0
- package/dist/components/availability-calendar.d.ts.map +1 -0
- package/dist/components/availability-calendar.js +65 -0
- package/dist/components/catalog-browse-page.d.ts +41 -0
- package/dist/components/catalog-browse-page.d.ts.map +1 -0
- package/dist/components/catalog-browse-page.js +47 -0
- package/dist/components/catalog-card.d.ts +68 -0
- package/dist/components/catalog-card.d.ts.map +1 -0
- package/dist/components/catalog-card.js +52 -0
- package/dist/components/catalog-detail-cruise-cards.d.ts +16 -0
- package/dist/components/catalog-detail-cruise-cards.d.ts.map +1 -0
- package/dist/components/catalog-detail-cruise-cards.js +54 -0
- package/dist/components/catalog-detail-departures.d.ts +25 -0
- package/dist/components/catalog-detail-departures.d.ts.map +1 -0
- package/dist/components/catalog-detail-departures.js +240 -0
- package/dist/components/catalog-detail-parts.d.ts +70 -0
- package/dist/components/catalog-detail-parts.d.ts.map +1 -0
- package/dist/components/catalog-detail-parts.js +282 -0
- package/dist/components/catalog-detail-sheet.d.ts +93 -0
- package/dist/components/catalog-detail-sheet.d.ts.map +1 -0
- package/dist/components/catalog-detail-sheet.js +68 -0
- package/dist/components/catalog-detail-view.d.ts +39 -0
- package/dist/components/catalog-detail-view.d.ts.map +1 -0
- package/dist/components/catalog-detail-view.js +157 -0
- package/dist/components/catalog-enrichment-fetchers.d.ts +8 -0
- package/dist/components/catalog-enrichment-fetchers.d.ts.map +1 -0
- package/dist/components/catalog-enrichment-fetchers.js +7 -0
- package/dist/components/catalog-faceted-filter.d.ts +30 -0
- package/dist/components/catalog-faceted-filter.d.ts.map +1 -0
- package/dist/components/catalog-faceted-filter.js +24 -0
- package/dist/components/catalog-filter-rail.d.ts +25 -0
- package/dist/components/catalog-filter-rail.d.ts.map +1 -0
- package/dist/components/catalog-filter-rail.js +88 -0
- package/dist/components/catalog-gallery.d.ts +27 -0
- package/dist/components/catalog-gallery.d.ts.map +1 -0
- package/dist/components/catalog-gallery.js +66 -0
- package/dist/components/catalog-hit.d.ts +27 -0
- package/dist/components/catalog-hit.d.ts.map +1 -0
- package/dist/components/catalog-hit.js +57 -0
- package/dist/components/catalog-page-cards.d.ts +21 -0
- package/dist/components/catalog-page-cards.d.ts.map +1 -0
- package/dist/components/catalog-page-cards.js +174 -0
- package/dist/components/catalog-page-config.d.ts +17 -0
- package/dist/components/catalog-page-config.d.ts.map +1 -0
- package/dist/components/catalog-page-config.js +369 -0
- package/dist/components/catalog-page.d.ts +88 -0
- package/dist/components/catalog-page.d.ts.map +1 -0
- package/dist/components/catalog-page.js +148 -0
- package/dist/components/catalog-range-filter.d.ts +34 -0
- package/dist/components/catalog-range-filter.d.ts.map +1 -0
- package/dist/components/catalog-range-filter.js +72 -0
- package/dist/components/catalog-search-page.d.ts +239 -0
- package/dist/components/catalog-search-page.d.ts.map +1 -0
- package/dist/components/catalog-search-page.js +63 -0
- package/dist/components/catalog-search-tab-panel.d.ts +42 -0
- package/dist/components/catalog-search-tab-panel.d.ts.map +1 -0
- package/dist/components/catalog-search-tab-panel.js +199 -0
- package/dist/components/catalog-vertical-detail-page.d.ts +33 -0
- package/dist/components/catalog-vertical-detail-page.d.ts.map +1 -0
- package/dist/components/catalog-vertical-detail-page.js +100 -0
- package/dist/components/cruise-detail-page-parts.d.ts +72 -0
- package/dist/components/cruise-detail-page-parts.d.ts.map +1 -0
- package/dist/components/cruise-detail-page-parts.js +146 -0
- package/dist/components/cruise-detail-page.d.ts +21 -0
- package/dist/components/cruise-detail-page.d.ts.map +1 -0
- package/dist/components/cruise-detail-page.js +201 -0
- package/dist/components/dynamic-catalog-page-parts.d.ts +40 -0
- package/dist/components/dynamic-catalog-page-parts.d.ts.map +1 -0
- package/dist/components/dynamic-catalog-page-parts.js +43 -0
- package/dist/components/dynamic-catalog-page.d.ts +23 -0
- package/dist/components/dynamic-catalog-page.d.ts.map +1 -0
- package/dist/components/dynamic-catalog-page.js +270 -0
- package/dist/components/media-gallery.d.ts +13 -0
- package/dist/components/media-gallery.d.ts.map +1 -0
- package/dist/components/media-gallery.js +42 -0
- package/dist/components/product-detail-page-parts.d.ts +106 -0
- package/dist/components/product-detail-page-parts.d.ts.map +1 -0
- package/dist/components/product-detail-page-parts.js +130 -0
- package/dist/components/product-detail-page.d.ts +57 -0
- package/dist/components/product-detail-page.d.ts.map +1 -0
- package/dist/components/product-detail-page.js +175 -0
- package/dist/components/scheduled-catalog-page.d.ts +34 -0
- package/dist/components/scheduled-catalog-page.d.ts.map +1 -0
- package/dist/components/scheduled-catalog-page.js +6 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/use-catalog-offers.d.ts +186 -0
- package/dist/hooks/use-catalog-offers.d.ts.map +1 -0
- package/dist/hooks/use-catalog-offers.js +105 -0
- package/dist/hooks/use-catalog-search.d.ts +109 -0
- package/dist/hooks/use-catalog-search.d.ts.map +1 -0
- package/dist/hooks/use-catalog-search.js +52 -0
- package/dist/i18n/en.d.ts +397 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +396 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +3 -0
- package/dist/i18n/messages.d.ts +335 -0
- package/dist/i18n/messages.d.ts.map +1 -0
- package/dist/i18n/messages.js +1 -0
- package/dist/i18n/provider.d.ts +816 -0
- package/dist/i18n/provider.d.ts.map +1 -0
- package/dist/i18n/provider.js +44 -0
- package/dist/i18n/ro.d.ts +397 -0
- package/dist/i18n/ro.d.ts.map +1 -0
- package/dist/i18n/ro.js +396 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/provider.d.ts +2 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +1 -0
- package/dist/schemas-catalog-offers.d.ts +290 -0
- package/dist/schemas-catalog-offers.d.ts.map +1 -0
- package/dist/schemas-catalog-offers.js +155 -0
- package/dist/schemas.d.ts +143 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +76 -0
- package/dist/ui.d.ts +19 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +18 -0
- package/package.json +150 -0
- package/src/styles.css +11 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Badge } from "@voyant-travel/ui/components/badge";
|
|
3
|
+
import { Image as ImageIcon } from "lucide-react";
|
|
4
|
+
import { formatHitPrice, numberField, resolveHitPriceUnit, stringField, } from "./catalog-hit.js";
|
|
5
|
+
import { formatBoard, formatCountry, formatDepartureMonth, formatStars, formatTransport, } from "./catalog-page-cards.js";
|
|
6
|
+
export function makeProductColumns(formatSupplier, messages) {
|
|
7
|
+
return [
|
|
8
|
+
nameColumn(messages.fallbacks.productName, messages),
|
|
9
|
+
statusColumn(messages),
|
|
10
|
+
sourceColumn(messages),
|
|
11
|
+
lookupColumn("supplierId", messages.columns.supplier, formatSupplier, messages),
|
|
12
|
+
daysColumn(messages),
|
|
13
|
+
nightsColumn(messages),
|
|
14
|
+
priceColumn("sellAmountCents", "sellCurrency", messages.columns.price, messages),
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
export function makeExtraColumns(formatSupplier, messages) {
|
|
18
|
+
return [
|
|
19
|
+
nameColumn(messages.fallbacks.extraName, messages),
|
|
20
|
+
activeColumn(messages),
|
|
21
|
+
sourceColumn(messages),
|
|
22
|
+
lookupColumn("supplierId", messages.columns.supplier, formatSupplier, messages),
|
|
23
|
+
textColumn("selectionType", messages.columns.selection, messages),
|
|
24
|
+
textColumn("pricingMode", messages.columns.pricing, messages),
|
|
25
|
+
textColumn("defaultQuantity", messages.columns.defaultQuantity, messages),
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
export function makeCruiseColumns(formatSupplier, messages) {
|
|
29
|
+
return [
|
|
30
|
+
nameColumn(messages.fallbacks.cruiseName, messages),
|
|
31
|
+
statusColumn(messages),
|
|
32
|
+
sourceColumn(messages),
|
|
33
|
+
textColumn("cruiseType", messages.columns.type, messages),
|
|
34
|
+
lookupColumn("lineSupplierId", messages.columns.supplier, formatSupplier, messages),
|
|
35
|
+
textColumn("nights", messages.columns.nights, messages),
|
|
36
|
+
priceColumn("lowestPriceCached", "lowestPriceCurrencyCached", messages.columns.price, messages, "major", "lowestPriceUnit"),
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
export function makeCharterColumns(formatSupplier, messages) {
|
|
40
|
+
return [
|
|
41
|
+
nameColumn(messages.fallbacks.charterName, messages, "heroImageUrl"),
|
|
42
|
+
statusColumn(messages),
|
|
43
|
+
sourceColumn(messages),
|
|
44
|
+
lookupColumn("lineSupplierId", messages.columns.supplier, formatSupplier, messages),
|
|
45
|
+
textColumn("defaultYachtId", messages.columns.yacht, messages),
|
|
46
|
+
priceColumn("lowestPriceCachedAmount", "lowestPriceCachedCurrency", messages.columns.from, messages),
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
export function makeAccommodationColumns(formatSupplier, messages) {
|
|
50
|
+
return [
|
|
51
|
+
nameColumn(messages.fallbacks.roomName, messages),
|
|
52
|
+
activeColumn(messages),
|
|
53
|
+
sourceColumn(messages),
|
|
54
|
+
lookupColumn("supplierId", messages.columns.supplier, formatSupplier, messages),
|
|
55
|
+
textColumn("roomClass", messages.columns.class, messages),
|
|
56
|
+
textColumn("maxOccupancy", messages.columns.maxPax, messages),
|
|
57
|
+
textColumn("bedroomCount", messages.columns.bedrooms, messages),
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
export function makeProductFilters(formatSupplier, messages) {
|
|
61
|
+
return [
|
|
62
|
+
{ field: "status", label: messages.filters.status },
|
|
63
|
+
{ field: "countryCodes", label: messages.filters.country, formatValue: formatCountry },
|
|
64
|
+
{ field: "destinations", label: messages.filters.destination },
|
|
65
|
+
{
|
|
66
|
+
field: "board",
|
|
67
|
+
label: messages.filters.board,
|
|
68
|
+
formatValue: (value) => formatBoard(String(value), messages) ?? String(value),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
field: "stars",
|
|
72
|
+
label: messages.filters.stars,
|
|
73
|
+
formatValue: (value) => formatStars(value) ?? String(value),
|
|
74
|
+
// Show 5★ → 0, not by item count.
|
|
75
|
+
sortValues: "value-desc",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
field: "transport",
|
|
79
|
+
label: messages.filters.transport,
|
|
80
|
+
formatValue: (value) => formatTransport(String(value), messages) ?? String(value),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
field: "source.kind",
|
|
84
|
+
label: messages.filters.source,
|
|
85
|
+
formatValue: (value) => formatSourceKind(value, messages),
|
|
86
|
+
},
|
|
87
|
+
{ field: "supplierId", label: messages.filters.supplier, formatValue: formatSupplier },
|
|
88
|
+
{ field: "bookingMode", label: messages.filters.bookingMode },
|
|
89
|
+
{ field: "productTypeId", label: messages.filters.type },
|
|
90
|
+
{ field: "capacityMode", label: messages.filters.capacity },
|
|
91
|
+
{ field: "visibility", label: messages.filters.visibility },
|
|
92
|
+
{ field: "facilityId", label: messages.filters.facility },
|
|
93
|
+
{
|
|
94
|
+
kind: "range",
|
|
95
|
+
field: "sellAmountCents",
|
|
96
|
+
label: messages.filters.price,
|
|
97
|
+
format: "currency",
|
|
98
|
+
currency: "EUR",
|
|
99
|
+
step: 100,
|
|
100
|
+
minPlaceholder: "0",
|
|
101
|
+
maxPlaceholder: messages.filters.any,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
kind: "range",
|
|
105
|
+
field: "pax",
|
|
106
|
+
label: messages.filters.pax,
|
|
107
|
+
minPlaceholder: "0",
|
|
108
|
+
maxPlaceholder: messages.filters.any,
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
export function makeExtraFilters(formatSupplier, messages) {
|
|
113
|
+
return [
|
|
114
|
+
{ field: "active", label: messages.filters.active },
|
|
115
|
+
{
|
|
116
|
+
field: "source.kind",
|
|
117
|
+
label: messages.filters.source,
|
|
118
|
+
formatValue: (value) => formatSourceKind(value, messages),
|
|
119
|
+
},
|
|
120
|
+
{ field: "supplierId", label: messages.filters.supplier, formatValue: formatSupplier },
|
|
121
|
+
{ field: "selectionType", label: messages.filters.selection },
|
|
122
|
+
{ field: "pricingMode", label: messages.filters.pricingMode },
|
|
123
|
+
{ field: "pricedPerPerson", label: messages.filters.perPerson },
|
|
124
|
+
{
|
|
125
|
+
kind: "range",
|
|
126
|
+
field: "minQuantity",
|
|
127
|
+
label: messages.filters.minQuantity,
|
|
128
|
+
minPlaceholder: "0",
|
|
129
|
+
maxPlaceholder: messages.filters.any,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
kind: "range",
|
|
133
|
+
field: "maxQuantity",
|
|
134
|
+
label: messages.filters.maxQuantity,
|
|
135
|
+
minPlaceholder: "0",
|
|
136
|
+
maxPlaceholder: messages.filters.any,
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
export function makeCruiseFilters(formatSupplier, messages) {
|
|
141
|
+
return [
|
|
142
|
+
{ field: "status", label: messages.filters.status },
|
|
143
|
+
{
|
|
144
|
+
field: "source.kind",
|
|
145
|
+
label: messages.filters.source,
|
|
146
|
+
formatValue: (value) => formatSourceKind(value, messages),
|
|
147
|
+
},
|
|
148
|
+
{ field: "cruiseType", label: messages.filters.type },
|
|
149
|
+
{
|
|
150
|
+
// Departure month/year facet — `YYYY-MM` values sorted chronologically,
|
|
151
|
+
// shown as "Mar 2027".
|
|
152
|
+
field: "departureMonths",
|
|
153
|
+
label: messages.filters.departureMonth,
|
|
154
|
+
formatValue: formatDepartureMonth,
|
|
155
|
+
sortValues: "value-asc",
|
|
156
|
+
},
|
|
157
|
+
{ field: "lineSupplierId", label: messages.filters.supplier, formatValue: formatSupplier },
|
|
158
|
+
{ field: "defaultShipId", label: messages.filters.ship },
|
|
159
|
+
{ field: "embarkPortFacilityId", label: messages.filters.embark },
|
|
160
|
+
{ field: "disembarkPortFacilityId", label: messages.filters.disembark },
|
|
161
|
+
{ field: "regions", label: messages.filters.region },
|
|
162
|
+
{ field: "themes", label: messages.filters.theme },
|
|
163
|
+
{
|
|
164
|
+
kind: "range",
|
|
165
|
+
field: "nights",
|
|
166
|
+
label: messages.filters.nights,
|
|
167
|
+
minPlaceholder: "0",
|
|
168
|
+
maxPlaceholder: messages.filters.any,
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
}
|
|
172
|
+
export function makeCharterFilters(formatSupplier, messages) {
|
|
173
|
+
return [
|
|
174
|
+
{ field: "status", label: messages.filters.status },
|
|
175
|
+
{
|
|
176
|
+
field: "source.kind",
|
|
177
|
+
label: messages.filters.source,
|
|
178
|
+
formatValue: (value) => formatSourceKind(value, messages),
|
|
179
|
+
},
|
|
180
|
+
{ field: "lineSupplierId", label: messages.filters.supplier, formatValue: formatSupplier },
|
|
181
|
+
{ field: "defaultYachtId", label: messages.filters.yacht },
|
|
182
|
+
{ field: "defaultBookingModes", label: messages.filters.bookingMode },
|
|
183
|
+
{ field: "regions", label: messages.filters.region },
|
|
184
|
+
{ field: "themes", label: messages.filters.theme },
|
|
185
|
+
{
|
|
186
|
+
kind: "range",
|
|
187
|
+
field: "lowestPriceCachedAmount",
|
|
188
|
+
label: messages.filters.price,
|
|
189
|
+
format: "currency",
|
|
190
|
+
currency: "EUR",
|
|
191
|
+
step: 100,
|
|
192
|
+
minPlaceholder: "0",
|
|
193
|
+
maxPlaceholder: messages.filters.any,
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
kind: "range",
|
|
197
|
+
field: "defaultApaPercent",
|
|
198
|
+
label: messages.filters.apaPercent,
|
|
199
|
+
step: 1,
|
|
200
|
+
minPlaceholder: "0",
|
|
201
|
+
maxPlaceholder: "100",
|
|
202
|
+
},
|
|
203
|
+
];
|
|
204
|
+
}
|
|
205
|
+
export function makeAccommodationFilters(formatSupplier, messages) {
|
|
206
|
+
return [
|
|
207
|
+
{ field: "active", label: messages.filters.active },
|
|
208
|
+
{
|
|
209
|
+
field: "source.kind",
|
|
210
|
+
label: messages.filters.source,
|
|
211
|
+
formatValue: (value) => formatSourceKind(value, messages),
|
|
212
|
+
},
|
|
213
|
+
{ field: "supplierId", label: messages.filters.supplier, formatValue: formatSupplier },
|
|
214
|
+
{ field: "inventoryMode", label: messages.filters.inventory },
|
|
215
|
+
{ field: "roomClass", label: messages.filters.class },
|
|
216
|
+
{ field: "smokingAllowed", label: messages.filters.smoking },
|
|
217
|
+
{ field: "propertyId", label: messages.filters.property },
|
|
218
|
+
{
|
|
219
|
+
kind: "range",
|
|
220
|
+
field: "maxOccupancy",
|
|
221
|
+
label: messages.filters.maxPax,
|
|
222
|
+
minPlaceholder: "0",
|
|
223
|
+
maxPlaceholder: messages.filters.any,
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
kind: "range",
|
|
227
|
+
field: "bedroomCount",
|
|
228
|
+
label: messages.filters.bedrooms,
|
|
229
|
+
minPlaceholder: "0",
|
|
230
|
+
maxPlaceholder: messages.filters.any,
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
kind: "range",
|
|
234
|
+
field: "bathroomCount",
|
|
235
|
+
label: messages.filters.bathrooms,
|
|
236
|
+
minPlaceholder: "0",
|
|
237
|
+
maxPlaceholder: messages.filters.any,
|
|
238
|
+
},
|
|
239
|
+
];
|
|
240
|
+
}
|
|
241
|
+
function nameColumn(fallback, messages, urlField = "thumbnailUrl") {
|
|
242
|
+
return {
|
|
243
|
+
id: "name",
|
|
244
|
+
header: messages.columns.name,
|
|
245
|
+
cell: ({ row }) => (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(CatalogThumbnail, { hit: row.original, urlField: urlField }), _jsx("span", { className: "font-medium", children: stringField(row.original, "name", fallback) })] })),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function statusColumn(messages) {
|
|
249
|
+
return {
|
|
250
|
+
id: "status",
|
|
251
|
+
header: messages.columns.status,
|
|
252
|
+
cell: ({ row }) => {
|
|
253
|
+
const status = stringField(row.original, "status", null);
|
|
254
|
+
if (!status)
|
|
255
|
+
return null;
|
|
256
|
+
return (_jsx(Badge, { variant: status === "active" ? "default" : "secondary", className: "capitalize", children: status }));
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function activeColumn(messages) {
|
|
261
|
+
return {
|
|
262
|
+
id: "active",
|
|
263
|
+
header: messages.columns.active,
|
|
264
|
+
cell: ({ row }) => {
|
|
265
|
+
const v = stringField(row.original, "active", null);
|
|
266
|
+
if (v == null)
|
|
267
|
+
return null;
|
|
268
|
+
const isActive = v === "true" || v === "1";
|
|
269
|
+
return (_jsx(Badge, { variant: isActive ? "default" : "secondary", children: isActive ? messages.values.active : messages.values.inactive }));
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function textColumn(field, header, messages) {
|
|
274
|
+
return {
|
|
275
|
+
id: field,
|
|
276
|
+
header,
|
|
277
|
+
cell: ({ row }) => (_jsx("span", { className: "text-muted-foreground text-sm", children: stringField(row.original, field, messages.values.empty) })),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function daysColumn(messages) {
|
|
281
|
+
return {
|
|
282
|
+
id: "durationDays",
|
|
283
|
+
header: messages.columns.days,
|
|
284
|
+
cell: ({ row }) => {
|
|
285
|
+
const days = numberField(row.original, "durationDays");
|
|
286
|
+
if (days == null)
|
|
287
|
+
return _jsx("span", { className: "text-muted-foreground", children: messages.values.empty });
|
|
288
|
+
return _jsx("span", { className: "tabular-nums", children: days });
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function nightsColumn(messages) {
|
|
293
|
+
return {
|
|
294
|
+
id: "nights",
|
|
295
|
+
header: messages.columns.nights,
|
|
296
|
+
cell: ({ row }) => {
|
|
297
|
+
const days = numberField(row.original, "durationDays");
|
|
298
|
+
if (days == null || days < 1)
|
|
299
|
+
return _jsx("span", { className: "text-muted-foreground", children: messages.values.empty });
|
|
300
|
+
return _jsx("span", { className: "tabular-nums", children: Math.max(0, days - 1) });
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function sourceColumn(messages) {
|
|
305
|
+
return {
|
|
306
|
+
id: "source.kind",
|
|
307
|
+
header: messages.columns.source,
|
|
308
|
+
cell: ({ row }) => {
|
|
309
|
+
const kind = stringField(row.original, "source.kind", null);
|
|
310
|
+
if (!kind)
|
|
311
|
+
return _jsx("span", { className: "text-muted-foreground", children: messages.values.empty });
|
|
312
|
+
return (_jsx(Badge, { variant: kind === "owned" ? "secondary" : "outline", children: formatSourceKind(kind, messages) }));
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
export function formatSourceKind(value, messages) {
|
|
317
|
+
const raw = String(value);
|
|
318
|
+
const known = {
|
|
319
|
+
owned: messages.sourceKinds.owned,
|
|
320
|
+
"voyant-connect": messages.sourceKinds.voyantConnect,
|
|
321
|
+
manual: messages.sourceKinds.manual,
|
|
322
|
+
"gds:amadeus": messages.sourceKinds.gdsAmadeus,
|
|
323
|
+
"gds:sabre": messages.sourceKinds.gdsSabre,
|
|
324
|
+
"gds:travelport": messages.sourceKinds.gdsTravelport,
|
|
325
|
+
"bedbank:hotelbeds": messages.sourceKinds.bedbankHotelbeds,
|
|
326
|
+
"bedbank:expedia": messages.sourceKinds.bedbankExpedia,
|
|
327
|
+
};
|
|
328
|
+
if (known[raw])
|
|
329
|
+
return known[raw];
|
|
330
|
+
if (raw.startsWith("direct:")) {
|
|
331
|
+
return `${raw.slice("direct:".length).toUpperCase()} ${messages.sourceKinds.directSuffix}`;
|
|
332
|
+
}
|
|
333
|
+
return raw
|
|
334
|
+
.split(/[:\-_]/)
|
|
335
|
+
.filter(Boolean)
|
|
336
|
+
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
|
|
337
|
+
.join(" ");
|
|
338
|
+
}
|
|
339
|
+
function lookupColumn(field, header, format, messages) {
|
|
340
|
+
return {
|
|
341
|
+
id: field,
|
|
342
|
+
header,
|
|
343
|
+
cell: ({ row }) => {
|
|
344
|
+
const id = stringField(row.original, field, null);
|
|
345
|
+
if (!id)
|
|
346
|
+
return _jsx("span", { className: "text-muted-foreground", children: messages.values.empty });
|
|
347
|
+
return _jsx("span", { children: format(id) });
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function priceColumn(amountField, currencyField, header, messages, unit = "minor", unitField) {
|
|
352
|
+
return {
|
|
353
|
+
id: amountField,
|
|
354
|
+
header,
|
|
355
|
+
cell: ({ row }) => {
|
|
356
|
+
const resolvedUnit = resolveHitPriceUnit(row.original, unit, unitField);
|
|
357
|
+
const formatted = formatHitPrice(row.original, amountField, currencyField, resolvedUnit);
|
|
358
|
+
return (_jsx("span", { className: "font-medium", children: formatted ?? _jsx("span", { className: "text-muted-foreground", children: messages.values.empty }) }));
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function CatalogThumbnail({ hit, urlField }) {
|
|
363
|
+
const url = stringField(hit, urlField, null);
|
|
364
|
+
const name = stringField(hit, "name", "");
|
|
365
|
+
if (url) {
|
|
366
|
+
return (_jsx("img", { src: url, alt: name, className: "h-9 w-9 shrink-0 rounded-md object-cover", loading: "lazy" }));
|
|
367
|
+
}
|
|
368
|
+
return (_jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground", "aria-hidden": "true", children: _jsx(ImageIcon, { className: "h-4 w-4" }) }));
|
|
369
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { CatalogSearchHit } from "../index.js";
|
|
3
|
+
import type { CatalogDetailEnrichment, CatalogDetailRenderSlot, CatalogDetailSheetProps } from "./catalog-detail-sheet.js";
|
|
4
|
+
import type { CatalogEnrichmentFetchers } from "./catalog-enrichment-fetchers.js";
|
|
5
|
+
import { type CatalogFilterSelections, type CatalogSortOption } from "./catalog-search-page.js";
|
|
6
|
+
export interface CatalogPageSearchState {
|
|
7
|
+
tab?: string;
|
|
8
|
+
q?: string;
|
|
9
|
+
page?: number;
|
|
10
|
+
market?: string;
|
|
11
|
+
locale?: string;
|
|
12
|
+
view?: "grid" | "list";
|
|
13
|
+
sort?: CatalogSortOption;
|
|
14
|
+
filters?: CatalogFilterSelections;
|
|
15
|
+
}
|
|
16
|
+
export interface CatalogPageProps {
|
|
17
|
+
search?: CatalogPageSearchState;
|
|
18
|
+
onTabChange?: (tabId: string) => void;
|
|
19
|
+
onQueryChange?: (query: string) => void;
|
|
20
|
+
onPageChange?: (page: number) => void;
|
|
21
|
+
onViewChange?: (view: "grid" | "list") => void;
|
|
22
|
+
onSortChange?: (sort: CatalogSortOption) => void;
|
|
23
|
+
onFiltersChange?: (filters: CatalogFilterSelections) => void;
|
|
24
|
+
toolbarEnd?: ReactNode;
|
|
25
|
+
formatSupplier?: (id: string | number) => string;
|
|
26
|
+
onBookHit?: (hit: CatalogSearchHit, entityModule: string) => void;
|
|
27
|
+
onBookDeparture?: (hit: CatalogSearchHit, entityModule: string, departure: NonNullable<CatalogDetailEnrichment["departures"]>[number]) => void;
|
|
28
|
+
onBookOption?: (hit: CatalogSearchHit, entityModule: string, departure: NonNullable<CatalogDetailEnrichment["departures"]>[number], option: NonNullable<CatalogDetailEnrichment["options"]>[number]) => void;
|
|
29
|
+
onOpenProductEditor?: (hit: CatalogSearchHit) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Open the full, URL-addressable detail page for a result (e.g. in a new
|
|
32
|
+
* tab) instead of the in-page detail sheet. Bound per vertical and passed
|
|
33
|
+
* the clicked hit + its vertical; return nothing. Provide it only for
|
|
34
|
+
* verticals that have a dedicated detail page — others keep the sheet.
|
|
35
|
+
*/
|
|
36
|
+
onOpenProductDetail?: (hit: CatalogSearchHit, vertical: string) => void;
|
|
37
|
+
/**
|
|
38
|
+
* Explicit detail-enrichment callback. When set, takes precedence over
|
|
39
|
+
* `enrichmentFetchers` — pass this when you need full control over the
|
|
40
|
+
* request (auth headers, locale resolution, side-channel data). The
|
|
41
|
+
* default integration uses `enrichmentFetchers` for the common case.
|
|
42
|
+
*/
|
|
43
|
+
onLoadProductDetail?: (hit: CatalogSearchHit, vertical?: string) => Promise<CatalogDetailEnrichment | null>;
|
|
44
|
+
/**
|
|
45
|
+
* Declarative detail-enrichment fetchers. Build with
|
|
46
|
+
* `createCatalogEnrichmentFetchers({ baseUrl, … })`. When provided
|
|
47
|
+
* (and `onLoadProductDetail` is not), the detail sheet calls
|
|
48
|
+
* `fetchers.loadProductDetail` on open. This is the recommended way
|
|
49
|
+
* to wire up the sheet — it pins the route contract with
|
|
50
|
+
* `createProductContentRoutes` so a missing server-side mount is
|
|
51
|
+
* caught immediately instead of rendering an empty sheet.
|
|
52
|
+
*/
|
|
53
|
+
enrichmentFetchers?: CatalogEnrichmentFetchers;
|
|
54
|
+
detailSheetWidth?: CatalogDetailSheetProps["width"];
|
|
55
|
+
detailHeaderExtras?: CatalogDetailSheetProps["headerExtras"];
|
|
56
|
+
renderDetailBrochure?: CatalogDetailRenderSlot;
|
|
57
|
+
renderDetailMedia?: CatalogDetailRenderSlot;
|
|
58
|
+
renderDetailItineraryDay?: CatalogDetailSheetProps["renderItineraryDay"];
|
|
59
|
+
renderDetailExtraSections?: CatalogDetailRenderSlot;
|
|
60
|
+
/**
|
|
61
|
+
* Renders the supplier value in the detail sheet's Attributes tab as
|
|
62
|
+
* a clickable link to the supplier record. Templates wire this with
|
|
63
|
+
* their router's `Link` component. When omitted, the supplier shows
|
|
64
|
+
* as plain text (the resolved supplier name via `formatSupplier`).
|
|
65
|
+
*/
|
|
66
|
+
renderSupplierLink?: CatalogDetailSheetProps["renderSupplierLink"];
|
|
67
|
+
/**
|
|
68
|
+
* Inline tags editor for the detail sheet. When set, the Tags row in
|
|
69
|
+
* the Overview tab becomes editable; the callback persists the next
|
|
70
|
+
* tag list (e.g. a product PATCH). Owned products only on the
|
|
71
|
+
* operator side — sourced rows pass through without an editor.
|
|
72
|
+
*/
|
|
73
|
+
onTagsChange?: CatalogDetailSheetProps["onTagsChange"];
|
|
74
|
+
/**
|
|
75
|
+
* Restrict the page to one catalog vertical and hide the vertical tab
|
|
76
|
+
* switcher. Use this when routing owns the vertical selection.
|
|
77
|
+
*/
|
|
78
|
+
vertical?: string;
|
|
79
|
+
title?: ReactNode;
|
|
80
|
+
className?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Hide the built-in search input. Use when an embedding surface provides its
|
|
83
|
+
* own unified search box and drives `search.q`/`onQueryChange` externally.
|
|
84
|
+
*/
|
|
85
|
+
hideSearchInput?: boolean;
|
|
86
|
+
}
|
|
87
|
+
export declare function CatalogPage({ search, onTabChange, onQueryChange, onPageChange, onViewChange, onSortChange, onFiltersChange, toolbarEnd, hideSearchInput, formatSupplier, onBookHit, onBookDeparture, onBookOption, onOpenProductEditor, onOpenProductDetail, onLoadProductDetail, enrichmentFetchers, detailSheetWidth, detailHeaderExtras, renderDetailBrochure, renderDetailMedia, renderDetailItineraryDay, renderDetailExtraSections, renderSupplierLink, onTagsChange, vertical, title, className, }: CatalogPageProps): import("react/jsx-runtime").JSX.Element;
|
|
88
|
+
//# sourceMappingURL=catalog-page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-page.d.ts","sourceRoot":"","sources":["../../src/components/catalog-page.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "@voyant-travel/ui/lib/utils";
|
|
4
|
+
import { useMemo } from "react";
|
|
5
|
+
import { useCatalogUiMessagesOrDefault } from "../i18n/index.js";
|
|
6
|
+
import { stringField } from "./catalog-hit.js";
|
|
7
|
+
import { makeAccommodationCard, makeCharterCard, makeCruiseCard, makeProductCard, } from "./catalog-page-cards.js";
|
|
8
|
+
import { formatSourceKind, makeAccommodationColumns, makeAccommodationFilters, makeCharterColumns, makeCharterFilters, makeCruiseColumns, makeCruiseFilters, makeExtraColumns, makeExtraFilters, makeProductColumns, makeProductFilters, } from "./catalog-page-config.js";
|
|
9
|
+
import { CatalogSearchPage, } from "./catalog-search-page.js";
|
|
10
|
+
export function CatalogPage({ search = {}, onTabChange, onQueryChange, onPageChange, onViewChange, onSortChange, onFiltersChange, toolbarEnd, hideSearchInput, formatSupplier = (id) => String(id), onBookHit, onBookDeparture, onBookOption, onOpenProductEditor, onOpenProductDetail, onLoadProductDetail, enrichmentFetchers, detailSheetWidth, detailHeaderExtras, renderDetailBrochure, renderDetailMedia, renderDetailItineraryDay, renderDetailExtraSections, renderSupplierLink, onTagsChange, vertical, title, className, }) {
|
|
11
|
+
const messages = useCatalogUiMessagesOrDefault().catalogPage;
|
|
12
|
+
const resolvedLoadProductDetail = useMemo(() => onLoadProductDetail ?? enrichmentFetchers?.loadProductDetail, [onLoadProductDetail, enrichmentFetchers]);
|
|
13
|
+
// Each tab binds the loader to its vertical so the detail sheet fetches
|
|
14
|
+
// from the right content route (e.g. cruises → /v1/admin/cruises). Without
|
|
15
|
+
// this, only the products tab loaded enrichment and every other vertical's
|
|
16
|
+
// sheet rendered the bare projection.
|
|
17
|
+
const detailLoaderFor = (vertical) => resolvedLoadProductDetail
|
|
18
|
+
? (hit) => resolvedLoadProductDetail(hit, vertical)
|
|
19
|
+
: undefined;
|
|
20
|
+
// Open the dedicated detail page (e.g. new tab), bound per vertical. Only
|
|
21
|
+
// verticals with a real detail page get it; the rest fall back to the sheet.
|
|
22
|
+
const detailOpenerFor = (vertical) => onOpenProductDetail ? (hit) => onOpenProductDetail(hit, vertical) : undefined;
|
|
23
|
+
// Lazy per-cabin pricing loader, bound per vertical (cruises only today).
|
|
24
|
+
const loadDeparturePricing = enrichmentFetchers?.loadDeparturePricing;
|
|
25
|
+
const departurePricingLoaderFor = (vertical) => loadDeparturePricing
|
|
26
|
+
? (hit, sailingRef) => loadDeparturePricing(hit, sailingRef, vertical)
|
|
27
|
+
: undefined;
|
|
28
|
+
const supplierFormatter = (value) => typeof value === "string" ? formatSupplier(value) : String(value ?? "");
|
|
29
|
+
const sourceKindFormatter = (value) => typeof value === "string" || typeof value === "number"
|
|
30
|
+
? formatSourceKind(value, messages)
|
|
31
|
+
: String(value ?? "");
|
|
32
|
+
const tabs = [
|
|
33
|
+
{
|
|
34
|
+
id: "products",
|
|
35
|
+
label: messages.tabs.products,
|
|
36
|
+
vertical: "products",
|
|
37
|
+
columns: makeProductColumns(formatSupplier, messages),
|
|
38
|
+
filterFields: makeProductFilters(formatSupplier, messages),
|
|
39
|
+
card: makeProductCard(formatSupplier, messages),
|
|
40
|
+
sorts: ["price-asc", "price-desc", "departure-asc", "newest"],
|
|
41
|
+
detailFormatters: {
|
|
42
|
+
supplierId: supplierFormatter,
|
|
43
|
+
"source.kind": sourceKindFormatter,
|
|
44
|
+
},
|
|
45
|
+
detailActions: [
|
|
46
|
+
...(onBookHit
|
|
47
|
+
? [
|
|
48
|
+
{
|
|
49
|
+
label: messages.actions.bookThis,
|
|
50
|
+
onClick: (hit) => onBookHit(hit, "products"),
|
|
51
|
+
},
|
|
52
|
+
]
|
|
53
|
+
: []),
|
|
54
|
+
...(onOpenProductEditor
|
|
55
|
+
? [
|
|
56
|
+
{
|
|
57
|
+
label: messages.actions.openEditor,
|
|
58
|
+
onClick: onOpenProductEditor,
|
|
59
|
+
visible: (hit) => {
|
|
60
|
+
const kind = stringField(hit, "source.kind", null);
|
|
61
|
+
// Owned products are the only ones that have an editor —
|
|
62
|
+
// sourced rows are read-only mirrors of the upstream.
|
|
63
|
+
return kind === "owned" || kind == null;
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
]
|
|
67
|
+
: []),
|
|
68
|
+
],
|
|
69
|
+
onLoadDetail: detailLoaderFor("products"),
|
|
70
|
+
onBookDeparture: onBookDeparture
|
|
71
|
+
? (hit, departure) => onBookDeparture(hit, "products", departure)
|
|
72
|
+
: undefined,
|
|
73
|
+
onBookOption: onBookOption
|
|
74
|
+
? (hit, departure, option) => onBookOption(hit, "products", departure, option)
|
|
75
|
+
: undefined,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "extras",
|
|
79
|
+
label: messages.tabs.extras,
|
|
80
|
+
vertical: "extras",
|
|
81
|
+
columns: makeExtraColumns(formatSupplier, messages),
|
|
82
|
+
filterFields: makeExtraFilters(formatSupplier, messages),
|
|
83
|
+
detailFormatters: {
|
|
84
|
+
supplierId: supplierFormatter,
|
|
85
|
+
"source.kind": sourceKindFormatter,
|
|
86
|
+
},
|
|
87
|
+
onLoadDetail: detailLoaderFor("extras"),
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "cruises",
|
|
91
|
+
label: messages.tabs.cruises,
|
|
92
|
+
vertical: "cruises",
|
|
93
|
+
columns: makeCruiseColumns(formatSupplier, messages),
|
|
94
|
+
filterFields: makeCruiseFilters(formatSupplier, messages),
|
|
95
|
+
imageField: "thumbnailUrl",
|
|
96
|
+
card: makeCruiseCard(formatSupplier, messages),
|
|
97
|
+
detailFormatters: {
|
|
98
|
+
lineSupplierId: supplierFormatter,
|
|
99
|
+
"source.kind": sourceKindFormatter,
|
|
100
|
+
},
|
|
101
|
+
onLoadDetail: detailLoaderFor("cruises"),
|
|
102
|
+
onLoadDeparturePricing: departurePricingLoaderFor("cruises"),
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "charters",
|
|
106
|
+
label: messages.tabs.charters,
|
|
107
|
+
vertical: "charters",
|
|
108
|
+
columns: makeCharterColumns(formatSupplier, messages),
|
|
109
|
+
filterFields: makeCharterFilters(formatSupplier, messages),
|
|
110
|
+
imageField: "heroImageUrl",
|
|
111
|
+
card: makeCharterCard(formatSupplier),
|
|
112
|
+
sorts: ["price-asc", "price-desc", "newest"],
|
|
113
|
+
detailFormatters: {
|
|
114
|
+
lineSupplierId: supplierFormatter,
|
|
115
|
+
"source.kind": sourceKindFormatter,
|
|
116
|
+
},
|
|
117
|
+
onLoadDetail: detailLoaderFor("charters"),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "accommodations",
|
|
121
|
+
label: messages.tabs.accommodations,
|
|
122
|
+
vertical: "accommodations",
|
|
123
|
+
columns: makeAccommodationColumns(formatSupplier, messages),
|
|
124
|
+
filterFields: makeAccommodationFilters(formatSupplier, messages),
|
|
125
|
+
card: makeAccommodationCard(formatSupplier),
|
|
126
|
+
detailFormatters: {
|
|
127
|
+
supplierId: supplierFormatter,
|
|
128
|
+
"source.kind": sourceKindFormatter,
|
|
129
|
+
},
|
|
130
|
+
onLoadDetail: detailLoaderFor("accommodations"),
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
// Bind the new-tab detail opener per vertical on every tab — when the host
|
|
134
|
+
// provides `onOpenProductDetail`, results open the dedicated detail page
|
|
135
|
+
// (new tab) instead of the in-page sheet, for whichever vertical is shown.
|
|
136
|
+
const tabsWithDetail = tabs.map((tab) => ({
|
|
137
|
+
...tab,
|
|
138
|
+
onOpenDetail: detailOpenerFor(tab.vertical),
|
|
139
|
+
}));
|
|
140
|
+
const visibleTabs = vertical
|
|
141
|
+
? tabsWithDetail.filter((tab) => tab.id === vertical || tab.vertical === vertical)
|
|
142
|
+
: tabsWithDetail;
|
|
143
|
+
const activeTab = vertical ? visibleTabs[0]?.id : (search.tab ?? tabs[0]?.id);
|
|
144
|
+
return (_jsx("div", { className: cn("mx-auto w-full max-w-screen-2xl px-6 py-6 lg:px-8", className), children: _jsx(CatalogSearchPage, { tabs: visibleTabs, activeTab: activeTab, onActiveTabChange: (id) => {
|
|
145
|
+
if (!vertical)
|
|
146
|
+
onTabChange?.(id);
|
|
147
|
+
}, showTabs: !vertical, hideSearchInput: hideSearchInput, query: search.q ?? "", onQueryChange: (q) => onQueryChange?.(q), page: search.page ?? 1, onPageChange: (p) => onPageChange?.(p), view: search.view, onViewChange: onViewChange, sort: search.sort, onSortChange: onSortChange, filters: search.filters, onFiltersChange: onFiltersChange, market: search.market, locale: search.locale, toolbarEnd: toolbarEnd, detailSheetWidth: detailSheetWidth, detailHeaderExtras: detailHeaderExtras, renderDetailBrochure: renderDetailBrochure, renderDetailMedia: renderDetailMedia, renderDetailItineraryDay: renderDetailItineraryDay, renderDetailExtraSections: renderDetailExtraSections, renderSupplierLink: renderSupplierLink, onTagsChange: onTagsChange, title: title ?? (_jsxs("div", { children: [_jsx("h1", { className: "font-semibold text-2xl", children: messages.title }), _jsx("p", { className: "text-muted-foreground text-sm", children: messages.description })] })), searchPlaceholder: messages.searchPlaceholder }) }));
|
|
148
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface CatalogRangeFilterValue {
|
|
2
|
+
gte?: number;
|
|
3
|
+
lte?: number;
|
|
4
|
+
}
|
|
5
|
+
export interface CatalogRangeFilterProps {
|
|
6
|
+
/** Field name (matches the indexer document field). */
|
|
7
|
+
field: string;
|
|
8
|
+
/** Display label for the trigger. */
|
|
9
|
+
label: string;
|
|
10
|
+
/** Current selection. `undefined` means no filter. */
|
|
11
|
+
value: CatalogRangeFilterValue | undefined;
|
|
12
|
+
/** Apply a new range. */
|
|
13
|
+
onChange: (next: CatalogRangeFilterValue | undefined) => void;
|
|
14
|
+
/** Number-input step (default 1). */
|
|
15
|
+
step?: number;
|
|
16
|
+
/** Optional placeholder min/max — purely visual. */
|
|
17
|
+
minPlaceholder?: string;
|
|
18
|
+
maxPlaceholder?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Format the active selection in the trigger badge. When the field
|
|
21
|
+
* stores money in cents, use `format="currency"` + `currency` so the
|
|
22
|
+
* trigger displays e.g. "€100 – €500".
|
|
23
|
+
*/
|
|
24
|
+
format?: "number" | "currency";
|
|
25
|
+
/** ISO 4217 currency code when `format === "currency"`. */
|
|
26
|
+
currency?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Numeric-range filter — popover trigger styled like the faceted filter
|
|
30
|
+
* for visual consistency. When active, the trigger shows the range as a
|
|
31
|
+
* compact badge ("≥ €100", "≤ €500", or "€100 – €500").
|
|
32
|
+
*/
|
|
33
|
+
export declare function CatalogRangeFilter({ label, value, onChange, step, minPlaceholder, maxPlaceholder, format, currency, }: CatalogRangeFilterProps): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
//# sourceMappingURL=catalog-range-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog-range-filter.d.ts","sourceRoot":"","sources":["../../src/components/catalog-range-filter.tsx"],"names":[],"mappings":"AAWA,MAAM,WAAW,uBAAuB;IACtC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,uBAAuB;IACtC,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;IACb,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,sDAAsD;IACtD,KAAK,EAAE,uBAAuB,GAAG,SAAS,CAAA;IAC1C,yBAAyB;IACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,uBAAuB,GAAG,SAAS,KAAK,IAAI,CAAA;IAC7D,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAA;IAC9B,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,KAAK,EACL,QAAQ,EACR,IAAQ,EACR,cAAc,EACd,cAAc,EACd,MAAiB,EACjB,QAAQ,GACT,EAAE,uBAAuB,2CAwFzB"}
|