mautourco-components 0.2.85 → 0.2.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/molecules/LocationDropdown/LocationDropdown.d.ts +2 -0
- package/dist/components/molecules/LocationDropdown/LocationDropdown.js +30 -17
- package/dist/components/molecules/TransferDocket/TransferDocket.js +1 -1
- package/dist/components/molecules/VehicleSupplement/VehicleSupplement.js +3 -0
- package/dist/components/organisms/RoundTrip/RoundTrip.d.ts +12 -12
- package/dist/components/organisms/RoundTrip/RoundTrip.js +77 -24
- package/dist/components/organisms/SearchBarTransfer/SearchBarTransfer.js +8 -12
- package/dist/components/organisms/SearchBarTransfer/index.d.ts +1 -1
- package/dist/components/organisms/TransferLine/TransferLine.d.ts +10 -10
- package/dist/components/organisms/TransferLine/TransferLine.js +82 -33
- package/package.json +1 -1
- package/src/components/molecules/LocationDropdown/LocationDropdown.tsx +31 -13
- package/src/components/molecules/TransferDocket/TransferDocket.tsx +1 -1
- package/src/components/molecules/VehicleSupplement/VehicleSupplement.tsx +3 -0
- package/src/components/organisms/RoundTrip/RoundTrip.tsx +99 -37
- package/src/components/organisms/SearchBarTransfer/SearchBarTransfer.tsx +4 -4
- package/src/components/organisms/SearchBarTransfer/index.ts +1 -1
- package/src/components/organisms/TransferLine/TransferLine.tsx +101 -43
|
@@ -68,55 +68,61 @@ var TransferLine = function (_a) {
|
|
|
68
68
|
}
|
|
69
69
|
return null;
|
|
70
70
|
};
|
|
71
|
-
//
|
|
71
|
+
// Helper to check if value is LocationOption
|
|
72
|
+
var isLocationOption = function (value) {
|
|
73
|
+
return typeof value === 'object' && value !== null && 'id' in value && 'label' in value && 'type' in value;
|
|
74
|
+
};
|
|
75
|
+
// Initialize location data from IDs, LocationOption objects, or set defaults
|
|
72
76
|
React.useEffect(function () {
|
|
77
|
+
// If pickupPoint is already a LocationOption object, use it directly
|
|
78
|
+
if (isLocationOption(pickupPoint)) {
|
|
79
|
+
// Only update if the ID is different to prevent infinite loops
|
|
80
|
+
if ((internalPickupPoint === null || internalPickupPoint === void 0 ? void 0 : internalPickupPoint.id) !== pickupPoint.id) {
|
|
81
|
+
setInternalPickupPoint(pickupPoint);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
73
85
|
var _a = filterLocations('pickup'), options = _a.options, groups = _a.groups;
|
|
74
86
|
var allPickupLocations = getAllLocations(options, groups);
|
|
75
87
|
if (pickupPoint) {
|
|
76
88
|
var location_1 = allPickupLocations.find(function (loc) { return loc.id === pickupPoint; });
|
|
77
|
-
if (location_1) {
|
|
78
|
-
setInternalPickupPoint(
|
|
79
|
-
id: location_1.id,
|
|
80
|
-
locationName: location_1.label,
|
|
81
|
-
});
|
|
89
|
+
if (location_1 && (internalPickupPoint === null || internalPickupPoint === void 0 ? void 0 : internalPickupPoint.id) !== location_1.id) {
|
|
90
|
+
setInternalPickupPoint(location_1);
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
|
-
else {
|
|
85
|
-
// Set default location if applicable
|
|
93
|
+
else if (!internalPickupPoint) {
|
|
94
|
+
// Set default location if applicable (only if not already set)
|
|
86
95
|
var defaultLocation = getDefaultLocation('pickup');
|
|
87
96
|
if (defaultLocation) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
locationName: defaultLocation.label,
|
|
91
|
-
};
|
|
92
|
-
setInternalPickupPoint(defaultData);
|
|
93
|
-
onPickupChange === null || onPickupChange === void 0 ? void 0 : onPickupChange(defaultData);
|
|
97
|
+
setInternalPickupPoint(defaultLocation);
|
|
98
|
+
onPickupChange === null || onPickupChange === void 0 ? void 0 : onPickupChange(defaultLocation);
|
|
94
99
|
}
|
|
95
100
|
}
|
|
96
101
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
97
102
|
}, [pickupPoint, type, locations]);
|
|
98
103
|
React.useEffect(function () {
|
|
104
|
+
// If dropoffPoint is already a LocationOption object, use it directly
|
|
105
|
+
if (isLocationOption(dropoffPoint)) {
|
|
106
|
+
// Only update if the ID is different to prevent infinite loops
|
|
107
|
+
if ((internalDropoffPoint === null || internalDropoffPoint === void 0 ? void 0 : internalDropoffPoint.id) !== dropoffPoint.id) {
|
|
108
|
+
setInternalDropoffPoint(dropoffPoint);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
99
112
|
var _a = filterLocations('dropoff'), options = _a.options, groups = _a.groups;
|
|
100
113
|
var allDropoffLocations = getAllLocations(options, groups);
|
|
101
114
|
if (dropoffPoint) {
|
|
102
115
|
var location_2 = allDropoffLocations.find(function (loc) { return loc.id === dropoffPoint; });
|
|
103
|
-
if (location_2) {
|
|
104
|
-
setInternalDropoffPoint(
|
|
105
|
-
id: location_2.id,
|
|
106
|
-
locationName: location_2.label,
|
|
107
|
-
});
|
|
116
|
+
if (location_2 && (internalDropoffPoint === null || internalDropoffPoint === void 0 ? void 0 : internalDropoffPoint.id) !== location_2.id) {
|
|
117
|
+
setInternalDropoffPoint(location_2);
|
|
108
118
|
}
|
|
109
119
|
}
|
|
110
|
-
else {
|
|
111
|
-
// Set default location if applicable
|
|
120
|
+
else if (!internalDropoffPoint) {
|
|
121
|
+
// Set default location if applicable (only if not already set)
|
|
112
122
|
var defaultLocation = getDefaultLocation('dropoff');
|
|
113
123
|
if (defaultLocation) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
locationName: defaultLocation.label,
|
|
117
|
-
};
|
|
118
|
-
setInternalDropoffPoint(defaultData);
|
|
119
|
-
onDropoffChange === null || onDropoffChange === void 0 ? void 0 : onDropoffChange(defaultData);
|
|
124
|
+
setInternalDropoffPoint(defaultLocation);
|
|
125
|
+
onDropoffChange === null || onDropoffChange === void 0 ? void 0 : onDropoffChange(defaultLocation);
|
|
120
126
|
}
|
|
121
127
|
}
|
|
122
128
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -174,13 +180,41 @@ var TransferLine = function (_a) {
|
|
|
174
180
|
setInternalTransferDate(dateValue);
|
|
175
181
|
onDateChange === null || onDateChange === void 0 ? void 0 : onDateChange(dateValue);
|
|
176
182
|
};
|
|
183
|
+
// Helper to convert LocationData to LocationOption
|
|
184
|
+
var toLocationOption = function (locationData, position) {
|
|
185
|
+
if (!locationData)
|
|
186
|
+
return null;
|
|
187
|
+
// Try to find in the options to get the type
|
|
188
|
+
var _a = filterLocations(position), options = _a.options, groups = _a.groups;
|
|
189
|
+
var allLocations = getAllLocations(options, groups);
|
|
190
|
+
var found = allLocations.find(function (loc) { return loc.id === locationData.id; });
|
|
191
|
+
if (found)
|
|
192
|
+
return found;
|
|
193
|
+
// If not found, infer the type
|
|
194
|
+
var name = locationData.locationName.toLowerCase();
|
|
195
|
+
var id = String(locationData.id).toLowerCase();
|
|
196
|
+
var locType = 'accommodation';
|
|
197
|
+
if (name.includes('harbour') || name.includes('port') || id.includes('harbour') || id.includes('port')) {
|
|
198
|
+
locType = 'port';
|
|
199
|
+
}
|
|
200
|
+
else if (name.includes('airport') || id.includes('airport')) {
|
|
201
|
+
locType = 'airport';
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
id: locationData.id,
|
|
205
|
+
label: locationData.locationName,
|
|
206
|
+
type: locType,
|
|
207
|
+
};
|
|
208
|
+
};
|
|
177
209
|
var handlePickupChange = function (location) {
|
|
178
|
-
|
|
179
|
-
|
|
210
|
+
var locationOption = toLocationOption(location, 'pickup');
|
|
211
|
+
setInternalPickupPoint(locationOption);
|
|
212
|
+
onPickupChange === null || onPickupChange === void 0 ? void 0 : onPickupChange(locationOption);
|
|
180
213
|
};
|
|
181
214
|
var handleDropoffChange = function (location) {
|
|
182
|
-
|
|
183
|
-
|
|
215
|
+
var locationOption = toLocationOption(location, 'dropoff');
|
|
216
|
+
setInternalDropoffPoint(locationOption);
|
|
217
|
+
onDropoffChange === null || onDropoffChange === void 0 ? void 0 : onDropoffChange(locationOption);
|
|
184
218
|
};
|
|
185
219
|
// Check if inputs are empty (when checkEmpty is true)
|
|
186
220
|
var isPaxEmpty = checkEmpty &&
|
|
@@ -192,14 +226,29 @@ var TransferLine = function (_a) {
|
|
|
192
226
|
var isDateEmpty = checkEmpty && (!internalTransferDate || internalTransferDate === '');
|
|
193
227
|
var isPickupEmpty = checkEmpty && !internalPickupPoint;
|
|
194
228
|
var isDropoffEmpty = checkEmpty && !internalDropoffPoint;
|
|
229
|
+
// Helper function to find LocationOption from ID or LocationOption for defaultValue
|
|
230
|
+
var findLocationOption = function (value, position) {
|
|
231
|
+
if (!value)
|
|
232
|
+
return undefined;
|
|
233
|
+
// If value is already a LocationOption object, use it directly
|
|
234
|
+
if (isLocationOption(value)) {
|
|
235
|
+
return value;
|
|
236
|
+
}
|
|
237
|
+
// Otherwise, look up by ID in the locations
|
|
238
|
+
var _a = filterLocations(position), options = _a.options, groups = _a.groups;
|
|
239
|
+
var allLocations = getAllLocations(options, groups);
|
|
240
|
+
return allLocations.find(function (loc) { return loc.id === value; });
|
|
241
|
+
};
|
|
242
|
+
var defaultPickupOption = findLocationOption(pickupPoint, 'pickup');
|
|
243
|
+
var defaultDropoffOption = findLocationOption(dropoffPoint, 'dropoff');
|
|
195
244
|
return (_jsxs("div", { className: "transfer-line transfer-line--".concat(type, " ").concat(disabled ? 'transfer-line--disabled' : '', " ").concat(className), "data-transfer-id": id, children: [showTitle && (_jsxs("div", { className: "transfer-line__header", children: [_jsx(Icon, { name: getTypeIcon(), size: "sm", className: "transfer-line__header-icon" }), _jsx(Text, { size: "sm", variant: "medium", className: "transfer-line__header-label", children: getTypeLabel() })] })), _jsxs("div", { className: "transfer-line__content-container", children: [_jsxs("div", { className: "transfer-line__content", children: [_jsx("div", { className: "transfer-line__field transfer-line__field--pax ".concat(isPaxEmpty ? 'transfer-line__field--error' : ''), children: _jsx(PaxSelector, { label: "Number of pax", value: internalPaxData, onChange: handlePaxChange, placeholder: "2 pax", className: isPaxEmpty ? 'pax-selector--error' : '', disabled: disabled, scrollOnOpen: scrollOnOpen, checkEmpty: checkEmpty }) }), _jsxs("div", { className: "transfer-line__field transfer-line__field--date", children: [_jsx(Text, { size: "sm", variant: "regular", className: "transfer-line__field-label", children: "Transfer date" }), _jsx(DateTimePicker, { placeholder: "DD/MM/YYYY", mode: "calendar", iconPosition: "left", numberOfMonths: 1, iconBGFull: false, showChevron: true, onValueChange: handleDateChange, selectionMode: "single", defaultValue: internalTransferDate, inputClassName: "transfer-line__date-picker", state: isDateEmpty ? 'error' : undefined, disabled: disabled, scrollOnOpen: scrollOnOpen })] }), _jsx("div", { className: "transfer-line__field transfer-line__field--pickup", children: _jsx(LocationDropdown, { label: "Pick-up point", options: filterLocations('pickup').options, groups: filterLocations('pickup').groups, selectedValue: (internalPickupPoint === null || internalPickupPoint === void 0 ? void 0 : internalPickupPoint.id) || null, onSelectionChange: handlePickupChange, placeholder: "Select a pick-up point", direction: "pickup", type: type === 'inter-hotel'
|
|
196
245
|
? 'accommodation'
|
|
197
246
|
: type === 'arrival'
|
|
198
247
|
? 'airport-port'
|
|
199
|
-
: 'accommodation', showGroupTitles: false, error: isPickupEmpty, disabled: disabled, scrollOnOpen: scrollOnOpen }) }), _jsx("div", { className: "transfer-line__field transfer-line__field--dropoff", children: _jsx(LocationDropdown, { label: "Drop-off point", options: filterLocations('dropoff').options, groups: filterLocations('dropoff').groups, selectedValue: (internalDropoffPoint === null || internalDropoffPoint === void 0 ? void 0 : internalDropoffPoint.id) || null, onSelectionChange: handleDropoffChange, placeholder: "Select a drop-off point", direction: "dropoff", type: type === 'inter-hotel'
|
|
248
|
+
: 'accommodation', showGroupTitles: false, error: isPickupEmpty, disabled: disabled, scrollOnOpen: scrollOnOpen, defaultValue: defaultPickupOption }) }), _jsx("div", { className: "transfer-line__field transfer-line__field--dropoff", children: _jsx(LocationDropdown, { label: "Drop-off point", options: filterLocations('dropoff').options, groups: filterLocations('dropoff').groups, selectedValue: (internalDropoffPoint === null || internalDropoffPoint === void 0 ? void 0 : internalDropoffPoint.id) || null, onSelectionChange: handleDropoffChange, placeholder: "Select a drop-off point", direction: "dropoff", type: type === 'inter-hotel'
|
|
200
249
|
? 'accommodation'
|
|
201
250
|
: type === 'departure'
|
|
202
251
|
? 'airport-port'
|
|
203
|
-
: 'accommodation', showGroupTitles: false, error: isDropoffEmpty, disabled: disabled, scrollOnOpen: scrollOnOpen }) })] }), showDelete && !disabled && (_jsx("div", { className: "transfer-line__delete", children: _jsx("button", { type: "button", className: "transfer-line__delete-btn", onClick: onDelete, "aria-label": "Delete transfer line", children: _jsx(Icon, { name: "delete", size: "sm", className: "transfer-line__delete-icon" }) }) }))] })] }));
|
|
252
|
+
: 'accommodation', showGroupTitles: false, error: isDropoffEmpty, disabled: disabled, scrollOnOpen: scrollOnOpen, defaultValue: defaultDropoffOption }) })] }), showDelete && !disabled && (_jsx("div", { className: "transfer-line__delete", children: _jsx("button", { type: "button", className: "transfer-line__delete-btn", onClick: onDelete, "aria-label": "Delete transfer line", children: _jsx(Icon, { name: "delete", size: "sm", className: "transfer-line__delete-icon" }) }) }))] })] }));
|
|
204
253
|
};
|
|
205
254
|
export default TransferLine;
|
package/package.json
CHANGED
|
@@ -39,6 +39,8 @@ export interface LocationDropdownProps {
|
|
|
39
39
|
showGroupTitles?: boolean;
|
|
40
40
|
/** Whether to scroll to the input when the dropdown opens */
|
|
41
41
|
scrollOnOpen?: boolean;
|
|
42
|
+
/** Default location option to use when no value is selected */
|
|
43
|
+
defaultValue?: LocationOption;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
const LocationDropdown: React.FC<LocationDropdownProps> = ({
|
|
@@ -57,6 +59,7 @@ const LocationDropdown: React.FC<LocationDropdownProps> = ({
|
|
|
57
59
|
direction = undefined,
|
|
58
60
|
showGroupTitles = true,
|
|
59
61
|
scrollOnOpen = false,
|
|
62
|
+
defaultValue,
|
|
60
63
|
}) => {
|
|
61
64
|
const [isOpen, setIsOpen] = useState(false);
|
|
62
65
|
const [searchQuery, setSearchQuery] = useState('');
|
|
@@ -90,6 +93,18 @@ const LocationDropdown: React.FC<LocationDropdownProps> = ({
|
|
|
90
93
|
}
|
|
91
94
|
}, [isOpen]);
|
|
92
95
|
|
|
96
|
+
// Initialize default value if provided and no selected value
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (!selectedValue && defaultValue && !disabled) {
|
|
99
|
+
const defaultLocationData: LocationData = {
|
|
100
|
+
id: defaultValue.id,
|
|
101
|
+
locationName: defaultValue.label,
|
|
102
|
+
};
|
|
103
|
+
onSelectionChange(defaultLocationData);
|
|
104
|
+
}
|
|
105
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
106
|
+
}, [defaultValue]);
|
|
107
|
+
|
|
93
108
|
const handleToggleDropdown = () => {
|
|
94
109
|
if (!disabled) {
|
|
95
110
|
setIsOpen(!isOpen);
|
|
@@ -122,19 +137,20 @@ const LocationDropdown: React.FC<LocationDropdownProps> = ({
|
|
|
122
137
|
};
|
|
123
138
|
|
|
124
139
|
const getSelectedOption = (): LocationOption | null => {
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (flatOption) return flatOption;
|
|
140
|
+
if (selectedValue) {
|
|
141
|
+
// Search in flat options
|
|
142
|
+
const flatOption = options.find((opt) => opt.id === selectedValue);
|
|
143
|
+
if (flatOption) return flatOption;
|
|
130
144
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
145
|
+
// Search in groups
|
|
146
|
+
for (const group of groups) {
|
|
147
|
+
const groupOption = group.options.find((opt) => opt.id === selectedValue);
|
|
148
|
+
if (groupOption) return groupOption;
|
|
149
|
+
}
|
|
135
150
|
}
|
|
136
151
|
|
|
137
|
-
|
|
152
|
+
// Return default value if no selected value
|
|
153
|
+
return defaultValue || null;
|
|
138
154
|
};
|
|
139
155
|
|
|
140
156
|
const getIconForType = (optionType: 'airport' | 'port' | 'accommodation') => {
|
|
@@ -171,15 +187,17 @@ const LocationDropdown: React.FC<LocationDropdownProps> = ({
|
|
|
171
187
|
return getIconForType(option.type);
|
|
172
188
|
};
|
|
173
189
|
|
|
190
|
+
const selectedOption = getSelectedOption();
|
|
191
|
+
const effectiveSelectedValue = selectedValue || (defaultValue ? defaultValue.id : null);
|
|
192
|
+
|
|
174
193
|
const getDropdownState = () => {
|
|
175
194
|
if (disabled) return 'disabled';
|
|
176
195
|
if (error) return 'error';
|
|
177
196
|
if (isOpen) return 'open';
|
|
178
|
-
if (
|
|
197
|
+
if (effectiveSelectedValue) return 'selected';
|
|
179
198
|
return 'default';
|
|
180
199
|
};
|
|
181
200
|
|
|
182
|
-
const selectedOption = getSelectedOption();
|
|
183
201
|
const displayText = selectedOption ? selectedOption.label : placeholder;
|
|
184
202
|
|
|
185
203
|
// Prepare all options (flat or grouped) and filter them
|
|
@@ -293,7 +311,7 @@ const LocationDropdown: React.FC<LocationDropdownProps> = ({
|
|
|
293
311
|
|
|
294
312
|
<div className="location-dropdown__group-options">
|
|
295
313
|
{group.options.map((option) => {
|
|
296
|
-
const isSelected =
|
|
314
|
+
const isSelected = effectiveSelectedValue === option.id;
|
|
297
315
|
const isDisabled = option.disabled || disabled;
|
|
298
316
|
|
|
299
317
|
return (
|
|
@@ -170,7 +170,7 @@ export const TransferDocket: React.FC<TransferDocketProps> = ({
|
|
|
170
170
|
</TextWithIcon>
|
|
171
171
|
)}
|
|
172
172
|
|
|
173
|
-
{showBabySeat && (
|
|
173
|
+
{!!showBabySeat && (
|
|
174
174
|
<div className="transfer-docket__supplements">
|
|
175
175
|
<Text variant="medium" size="sm" color="default" leading="none">
|
|
176
176
|
Supplement :
|
|
@@ -88,6 +88,9 @@ const VehicleSupplement: React.FC<VehicleSupplementProps> = ({
|
|
|
88
88
|
|
|
89
89
|
const getTransferTypeIcon = (transferType: string) => {
|
|
90
90
|
const type = transferType.toLowerCase();
|
|
91
|
+
if (type.includes('harbour')) {
|
|
92
|
+
return 'ship';
|
|
93
|
+
}
|
|
91
94
|
if (type.includes('arrival') || type.includes('airport') || type.includes('inbound')) {
|
|
92
95
|
return 'plane-landing-outline';
|
|
93
96
|
}
|
|
@@ -9,15 +9,15 @@ import LocationDropdown, {
|
|
|
9
9
|
import DateTimePicker from "../DateTimePicker/DateTimePicker";
|
|
10
10
|
import PaxSelector, { PaxData } from "../PaxSelector/PaxSelector";
|
|
11
11
|
|
|
12
|
-
// Re-export
|
|
13
|
-
export type {
|
|
12
|
+
// Re-export LocationOption for convenience
|
|
13
|
+
export type { LocationOption };
|
|
14
14
|
|
|
15
15
|
export interface RoundTripTransfer {
|
|
16
16
|
type: "arrival" | "departure";
|
|
17
17
|
paxData?: PaxData;
|
|
18
18
|
transferDate?: string;
|
|
19
|
-
pickupPoint?:
|
|
20
|
-
dropoffPoint?:
|
|
19
|
+
pickupPoint?: LocationOption;
|
|
20
|
+
dropoffPoint?: LocationOption;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface RoundTripData {
|
|
@@ -25,8 +25,8 @@ export interface RoundTripData {
|
|
|
25
25
|
paxData?: PaxData;
|
|
26
26
|
arrivalDate?: string;
|
|
27
27
|
departureDate?: string;
|
|
28
|
-
pickupDropoffPoint?:
|
|
29
|
-
accommodation?:
|
|
28
|
+
pickupDropoffPoint?: LocationOption;
|
|
29
|
+
accommodation?: LocationOption;
|
|
30
30
|
transfers: [RoundTripTransfer, RoundTripTransfer];
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -39,10 +39,10 @@ export interface RoundTripProps {
|
|
|
39
39
|
arrivalDate?: string;
|
|
40
40
|
/** Departure date */
|
|
41
41
|
departureDate?: string;
|
|
42
|
-
/** Selected pickup/dropoff point ID
|
|
43
|
-
pickupDropoffPoint?: string;
|
|
44
|
-
/** Selected accommodation ID
|
|
45
|
-
accommodation?: string;
|
|
42
|
+
/** Selected pickup/dropoff point - can be an ID string or LocationOption object */
|
|
43
|
+
pickupDropoffPoint?: string | LocationOption;
|
|
44
|
+
/** Selected accommodation - can be an ID string or LocationOption object */
|
|
45
|
+
accommodation?: string | LocationOption;
|
|
46
46
|
/** All location options */
|
|
47
47
|
locations?: {
|
|
48
48
|
options?: LocationOption[];
|
|
@@ -53,9 +53,9 @@ export interface RoundTripProps {
|
|
|
53
53
|
/** Callback when dates change */
|
|
54
54
|
onDatesChange?: (arrivalDate: string, departureDate: string) => void;
|
|
55
55
|
/** Callback when pickup/dropoff point changes */
|
|
56
|
-
onPickupDropoffChange?: (location:
|
|
56
|
+
onPickupDropoffChange?: (location: LocationOption | null) => void;
|
|
57
57
|
/** Callback when accommodation changes */
|
|
58
|
-
onAccommodationChange?: (location:
|
|
58
|
+
onAccommodationChange?: (location: LocationOption | null) => void;
|
|
59
59
|
/** Callback when any data changes - returns array of 2 transfers */
|
|
60
60
|
onChange?: (data: RoundTripData) => void;
|
|
61
61
|
/** Additional CSS classes */
|
|
@@ -93,9 +93,9 @@ const RoundTrip: React.FC<RoundTripProps> = ({
|
|
|
93
93
|
string | undefined
|
|
94
94
|
>(departureDate);
|
|
95
95
|
const [internalPickupDropoffPoint, setInternalPickupDropoffPoint] =
|
|
96
|
-
useState<
|
|
96
|
+
useState<LocationOption | null>(null);
|
|
97
97
|
const [internalAccommodation, setInternalAccommodation] =
|
|
98
|
-
useState<
|
|
98
|
+
useState<LocationOption | null>(null);
|
|
99
99
|
|
|
100
100
|
// Helper function to get all locations (from options and groups)
|
|
101
101
|
const getAllLocations = (
|
|
@@ -165,8 +165,22 @@ const RoundTrip: React.FC<RoundTripProps> = ({
|
|
|
165
165
|
};
|
|
166
166
|
};
|
|
167
167
|
|
|
168
|
-
//
|
|
168
|
+
// Helper to check if value is LocationOption
|
|
169
|
+
const isLocationOption = (value: string | LocationOption | undefined): value is LocationOption => {
|
|
170
|
+
return typeof value === 'object' && value !== null && 'id' in value && 'label' in value && 'type' in value;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Initialize location data from IDs or LocationOption objects
|
|
169
174
|
useEffect(() => {
|
|
175
|
+
// If pickupDropoffPoint is already a LocationOption object, use it directly
|
|
176
|
+
if (isLocationOption(pickupDropoffPoint)) {
|
|
177
|
+
// Only update if the ID is different to prevent infinite loops
|
|
178
|
+
if (internalPickupDropoffPoint?.id !== pickupDropoffPoint.id) {
|
|
179
|
+
setInternalPickupDropoffPoint(pickupDropoffPoint);
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
170
184
|
const { options, groups } = getPickupDropoffOptions();
|
|
171
185
|
const allLocations = getAllLocations(options, groups);
|
|
172
186
|
|
|
@@ -174,38 +188,37 @@ const RoundTrip: React.FC<RoundTripProps> = ({
|
|
|
174
188
|
const location = allLocations.find(
|
|
175
189
|
(loc) => loc.id === pickupDropoffPoint
|
|
176
190
|
);
|
|
177
|
-
if (location) {
|
|
178
|
-
setInternalPickupDropoffPoint(
|
|
179
|
-
id: location.id,
|
|
180
|
-
locationName: location.label,
|
|
181
|
-
});
|
|
191
|
+
if (location && internalPickupDropoffPoint?.id !== location.id) {
|
|
192
|
+
setInternalPickupDropoffPoint(location);
|
|
182
193
|
}
|
|
183
|
-
} else {
|
|
184
|
-
// Set default to first airport if available
|
|
194
|
+
} else if (!internalPickupDropoffPoint) {
|
|
195
|
+
// Set default to first airport if available (only if not already set)
|
|
185
196
|
const defaultAirport = allLocations.find((loc) => loc.type === "airport");
|
|
186
197
|
if (defaultAirport) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
locationName: defaultAirport.label,
|
|
190
|
-
};
|
|
191
|
-
setInternalPickupDropoffPoint(defaultData);
|
|
192
|
-
onPickupDropoffChange?.(defaultData);
|
|
198
|
+
setInternalPickupDropoffPoint(defaultAirport);
|
|
199
|
+
onPickupDropoffChange?.(defaultAirport);
|
|
193
200
|
}
|
|
194
201
|
}
|
|
195
202
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
196
203
|
}, [pickupDropoffPoint, locations]);
|
|
197
204
|
|
|
198
205
|
useEffect(() => {
|
|
206
|
+
// If accommodation is already a LocationOption object, use it directly
|
|
207
|
+
if (isLocationOption(accommodation)) {
|
|
208
|
+
// Only update if the ID is different to prevent infinite loops
|
|
209
|
+
if (internalAccommodation?.id !== accommodation.id) {
|
|
210
|
+
setInternalAccommodation(accommodation);
|
|
211
|
+
}
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
199
215
|
const { options, groups } = getAccommodationOptions();
|
|
200
216
|
const allLocations = getAllLocations(options, groups);
|
|
201
217
|
|
|
202
218
|
if (accommodation) {
|
|
203
219
|
const location = allLocations.find((loc) => loc.id === accommodation);
|
|
204
|
-
if (location) {
|
|
205
|
-
setInternalAccommodation(
|
|
206
|
-
id: location.id,
|
|
207
|
-
locationName: location.label,
|
|
208
|
-
});
|
|
220
|
+
if (location && internalAccommodation?.id !== location.id) {
|
|
221
|
+
setInternalAccommodation(location);
|
|
209
222
|
}
|
|
210
223
|
}
|
|
211
224
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -265,14 +278,43 @@ const RoundTrip: React.FC<RoundTripProps> = ({
|
|
|
265
278
|
}
|
|
266
279
|
};
|
|
267
280
|
|
|
281
|
+
// Helper to convert LocationData to LocationOption
|
|
282
|
+
const toLocationOption = (locationData: LocationData | null, locationType: 'pickup-dropoff' | 'accommodation'): LocationOption | null => {
|
|
283
|
+
if (!locationData) return null;
|
|
284
|
+
|
|
285
|
+
// Try to find in the options to get the type
|
|
286
|
+
const { options, groups } = locationType === 'pickup-dropoff' ? getPickupDropoffOptions() : getAccommodationOptions();
|
|
287
|
+
const allLocations = getAllLocations(options, groups);
|
|
288
|
+
const found = allLocations.find((loc) => loc.id === locationData.id);
|
|
289
|
+
if (found) return found;
|
|
290
|
+
|
|
291
|
+
// If not found, infer the type
|
|
292
|
+
const name = locationData.locationName.toLowerCase();
|
|
293
|
+
const id = String(locationData.id).toLowerCase();
|
|
294
|
+
let type: 'airport' | 'port' | 'accommodation' = 'accommodation';
|
|
295
|
+
if (name.includes('harbour') || name.includes('port') || id.includes('harbour') || id.includes('port')) {
|
|
296
|
+
type = 'port';
|
|
297
|
+
} else if (name.includes('airport') || id.includes('airport')) {
|
|
298
|
+
type = 'airport';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
id: locationData.id,
|
|
303
|
+
label: locationData.locationName,
|
|
304
|
+
type,
|
|
305
|
+
};
|
|
306
|
+
};
|
|
307
|
+
|
|
268
308
|
const handlePickupDropoffChange = (location: LocationData | null) => {
|
|
269
|
-
|
|
270
|
-
|
|
309
|
+
const locationOption = toLocationOption(location, 'pickup-dropoff');
|
|
310
|
+
setInternalPickupDropoffPoint(locationOption);
|
|
311
|
+
onPickupDropoffChange?.(locationOption);
|
|
271
312
|
};
|
|
272
313
|
|
|
273
314
|
const handleAccommodationChange = (location: LocationData | null) => {
|
|
274
|
-
|
|
275
|
-
|
|
315
|
+
const locationOption = toLocationOption(location, 'accommodation');
|
|
316
|
+
setInternalAccommodation(locationOption);
|
|
317
|
+
onAccommodationChange?.(locationOption);
|
|
276
318
|
};
|
|
277
319
|
|
|
278
320
|
// Check if inputs are empty (when checkEmpty is true)
|
|
@@ -292,6 +334,24 @@ const RoundTrip: React.FC<RoundTripProps> = ({
|
|
|
292
334
|
const pickupDropoffOptions = getPickupDropoffOptions();
|
|
293
335
|
const accommodationOptions = getAccommodationOptions();
|
|
294
336
|
|
|
337
|
+
// Helper function to find LocationOption from ID or LocationOption for defaultValue
|
|
338
|
+
const findLocationOption = (value: string | LocationOption | undefined, locationType: 'pickup-dropoff' | 'accommodation'): LocationOption | undefined => {
|
|
339
|
+
if (!value) return undefined;
|
|
340
|
+
|
|
341
|
+
// If value is already a LocationOption object, use it directly
|
|
342
|
+
if (isLocationOption(value)) {
|
|
343
|
+
return value;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Otherwise, look up by ID in the locations
|
|
347
|
+
const { options, groups } = locationType === 'pickup-dropoff' ? getPickupDropoffOptions() : getAccommodationOptions();
|
|
348
|
+
const allLocations = getAllLocations(options, groups);
|
|
349
|
+
return allLocations.find((loc) => loc.id === value);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const defaultPickupDropoffOption = findLocationOption(pickupDropoffPoint, 'pickup-dropoff');
|
|
353
|
+
const defaultAccommodationOption = findLocationOption(accommodation, 'accommodation');
|
|
354
|
+
|
|
295
355
|
return (
|
|
296
356
|
<div className={`round-trip ${className}`} data-round-trip-id={id}>
|
|
297
357
|
<div className="round-trip__content">
|
|
@@ -339,6 +399,7 @@ const RoundTrip: React.FC<RoundTripProps> = ({
|
|
|
339
399
|
showGroupTitles={true}
|
|
340
400
|
error={isPickupDropoffEmpty}
|
|
341
401
|
scrollOnOpen={scrollOnOpen}
|
|
402
|
+
defaultValue={defaultPickupDropoffOption}
|
|
342
403
|
/>
|
|
343
404
|
</div>
|
|
344
405
|
|
|
@@ -355,6 +416,7 @@ const RoundTrip: React.FC<RoundTripProps> = ({
|
|
|
355
416
|
showGroupTitles={false}
|
|
356
417
|
error={isAccommodationEmpty}
|
|
357
418
|
scrollOnOpen={scrollOnOpen}
|
|
419
|
+
defaultValue={defaultAccommodationOption}
|
|
358
420
|
/>
|
|
359
421
|
</div>
|
|
360
422
|
</div>
|
|
@@ -430,8 +430,8 @@ const SearchBarTransfer: React.FC<SearchBarTransferProps> = ({
|
|
|
430
430
|
paxData={roundTripData?.paxData}
|
|
431
431
|
arrivalDate={roundTripData?.arrivalDate}
|
|
432
432
|
departureDate={roundTripData?.departureDate}
|
|
433
|
-
pickupDropoffPoint={roundTripData?.pickupDropoffPoint
|
|
434
|
-
accommodation={roundTripData?.accommodation
|
|
433
|
+
pickupDropoffPoint={roundTripData?.pickupDropoffPoint}
|
|
434
|
+
accommodation={roundTripData?.accommodation}
|
|
435
435
|
onChange={setRoundTripData}
|
|
436
436
|
checkEmpty={error !== null}
|
|
437
437
|
scrollOnOpen={scrollOnOpen}
|
|
@@ -508,8 +508,8 @@ const SearchBarTransfer: React.FC<SearchBarTransferProps> = ({
|
|
|
508
508
|
type={line.type}
|
|
509
509
|
paxData={line.paxData}
|
|
510
510
|
transferDate={line.transferDate}
|
|
511
|
-
pickupPoint={line.pickupPoint
|
|
512
|
-
dropoffPoint={line.dropoffPoint
|
|
511
|
+
pickupPoint={line.pickupPoint}
|
|
512
|
+
dropoffPoint={line.dropoffPoint}
|
|
513
513
|
locations={locations}
|
|
514
514
|
onDataChange={(data) => handleTransferLineChange(line.id, data)}
|
|
515
515
|
onDelete={() => handleDeleteTransferLine(line.id)}
|