mautourco-components 0.2.64 → 0.2.66
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/AgeSelector/AgeSelector.d.ts +2 -0
- package/dist/components/molecules/AgeSelector/AgeSelector.js +4 -4
- package/dist/components/molecules/TooltipDisplay/TooltipDisplay.js +1 -1
- package/dist/components/organisms/PaxSelector/PaxSelector.d.ts +2 -0
- package/dist/components/organisms/PaxSelector/PaxSelector.js +84 -16
- package/dist/components/organisms/RoundTrip/RoundTrip.js +1 -1
- package/dist/components/organisms/SearchBarTransfer/SearchBarTransfer.js +44 -0
- package/dist/components/organisms/TransferLine/TransferLine.js +1 -1
- package/dist/styles/components/molecule/age-selector.css +21 -0
- package/package.json +1 -1
- package/src/components/molecules/AgeSelector/AgeSelector.tsx +4 -1
- package/src/components/molecules/TooltipDisplay/TooltipDisplay.tsx +1 -1
- package/src/components/organisms/PaxSelector/PaxSelector.tsx +78 -4
- package/src/components/organisms/RoundTrip/RoundTrip.tsx +1 -0
- package/src/components/organisms/SearchBarTransfer/SearchBarTransfer.tsx +46 -0
- package/src/components/organisms/TransferLine/TransferLine.tsx +1 -0
- package/src/styles/components/molecule/age-selector.css +20 -0
|
@@ -15,6 +15,8 @@ export interface AgeSelectorProps {
|
|
|
15
15
|
className?: string;
|
|
16
16
|
/** Placeholder text */
|
|
17
17
|
placeholder?: string;
|
|
18
|
+
/** Whether to show error state */
|
|
19
|
+
error?: boolean;
|
|
18
20
|
}
|
|
19
21
|
declare const AgeSelector: React.FC<AgeSelectorProps>;
|
|
20
22
|
export default AgeSelector;
|
|
@@ -4,9 +4,9 @@ import '../../../styles/components/molecule/age-selector.css';
|
|
|
4
4
|
import Icon from '../../atoms/Icon/Icon';
|
|
5
5
|
import { Text } from '../../atoms/Typography/Typography';
|
|
6
6
|
var AgeSelector = function (_a) {
|
|
7
|
-
var label = _a.label, value = _a.value, onChange = _a.onChange, ageRange = _a.ageRange, _b = _a.required, required = _b === void 0 ? false : _b, _c = _a.className, className = _c === void 0 ? '' : _c, _d = _a.placeholder, placeholder = _d === void 0 ? '--' : _d;
|
|
8
|
-
var
|
|
9
|
-
var
|
|
7
|
+
var label = _a.label, value = _a.value, onChange = _a.onChange, ageRange = _a.ageRange, _b = _a.required, required = _b === void 0 ? false : _b, _c = _a.className, className = _c === void 0 ? '' : _c, _d = _a.placeholder, placeholder = _d === void 0 ? '--' : _d, _e = _a.error, error = _e === void 0 ? false : _e;
|
|
8
|
+
var _f = useState(false), isOpen = _f[0], setIsOpen = _f[1];
|
|
9
|
+
var _g = useState(value !== undefined ? value.toString() : ''), inputValue = _g[0], setInputValue = _g[1];
|
|
10
10
|
var containerRef = useRef(null);
|
|
11
11
|
var inputRef = useRef(null);
|
|
12
12
|
// Sync input value when prop value changes
|
|
@@ -75,7 +75,7 @@ var AgeSelector = function (_a) {
|
|
|
75
75
|
setIsOpen(!isOpen);
|
|
76
76
|
};
|
|
77
77
|
var ageOptions = ageRange.map(function (age) { return age.toString(); });
|
|
78
|
-
return (_jsxs("div", { className: "age-selector ".concat(className), ref: containerRef, children: [_jsxs(Text, { size: "sm", variant: "regular", className: "age-selector__label", children: [label, required && _jsx("span", { className: "age-selector__required", children: " *" })] }), _jsxs("div", { className: "age-selector__container", children: [_jsxs("div", { className: "age-selector__input ".concat(isOpen ? 'age-selector__input--open' : '', " ").concat(value !== undefined ? 'age-selector__input--selected' : 'age-selector__input--default'), children: [_jsx("input", { ref: inputRef, type: "text", inputMode: "numeric", className: "age-selector__input-field", value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleInputKeyDown, onFocus: function () { return setIsOpen(false); }, placeholder: placeholder, "aria-label": "".concat(label, " age") }), _jsx("button", { type: "button", className: "age-selector__dropdown-btn", onClick: function (e) {
|
|
78
|
+
return (_jsxs("div", { className: "age-selector ".concat(className), ref: containerRef, children: [_jsxs(Text, { size: "sm", variant: "regular", className: "age-selector__label", children: [label, required && _jsx("span", { className: "age-selector__required", children: " *" })] }), _jsxs("div", { className: "age-selector__container", children: [_jsxs("div", { className: "age-selector__input ".concat(isOpen ? 'age-selector__input--open' : '', " ").concat(value !== undefined ? 'age-selector__input--selected' : 'age-selector__input--default', " ").concat(error && value === undefined ? 'age-selector__input--error' : ''), children: [_jsx("input", { ref: inputRef, type: "text", inputMode: "numeric", className: "age-selector__input-field", value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleInputKeyDown, onFocus: function () { return setIsOpen(false); }, placeholder: placeholder, "aria-label": "".concat(label, " age") }), _jsx("button", { type: "button", className: "age-selector__dropdown-btn", onClick: function (e) {
|
|
79
79
|
e.preventDefault();
|
|
80
80
|
e.stopPropagation();
|
|
81
81
|
handleDropdownToggle();
|
|
@@ -3,5 +3,5 @@ import { Text } from '../../atoms/Typography/Typography';
|
|
|
3
3
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/tooltip';
|
|
4
4
|
export function TooltipDisplay(props) {
|
|
5
5
|
var children = props.children, content = props.content, _a = props.side, side = _a === void 0 ? 'right' : _a, _b = props.maxWidth, maxWidth = _b === void 0 ? '300px' : _b;
|
|
6
|
-
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { className: "text-[var(--color-icon-branded)]",
|
|
6
|
+
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { className: "text-[var(--color-icon-branded)]", children: children }), _jsx(TooltipContent, { className: "max-w-[".concat(maxWidth, "]"), side: side, style: { maxWidth: maxWidth }, children: _jsx(Text, { variant: "medium", size: "sm", className: "!text-white", children: content }) })] }));
|
|
7
7
|
}
|
|
@@ -64,6 +64,8 @@ export interface PaxSelectorProps {
|
|
|
64
64
|
scrollOnOpen?: boolean;
|
|
65
65
|
/** Age range for child categories */
|
|
66
66
|
ageRange?: number[];
|
|
67
|
+
/** Whether to check if age inputs are empty and show error state */
|
|
68
|
+
checkEmpty?: boolean;
|
|
67
69
|
}
|
|
68
70
|
export declare const DEFAULT_PAX_DATA_WITH_ADULTS: PaxData;
|
|
69
71
|
export declare const CHILD_CATEGORY_AGES: number[];
|
|
@@ -85,7 +85,7 @@ var ClientTypeSelector = function (_a) {
|
|
|
85
85
|
return (_jsxs("div", { className: "pax-selector__client-type", children: [_jsx(Text, { size: "sm", variant: "regular", className: "pax-selector__client-type-label", children: "Client type" }), _jsxs("div", { className: "pax-selector__client-type-select", ref: containerRef, children: [_jsxs("button", { type: "button", className: "pax-selector__client-type-trigger", onClick: function () { return setIsOpen(!isOpen); }, "aria-expanded": isOpen, "aria-haspopup": "listbox", children: [_jsxs("div", { className: "pax-selector__client-type-content", children: [_jsx(Icon, { name: "user-icon", size: "sm", className: "pax-selector__client-type-icon" }), _jsx("span", { className: "pax-selector__client-type-text", children: value })] }), _jsx(Icon, { name: "chevron-down", size: "sm", className: "pax-selector__client-type-chevron ".concat(isOpen ? 'pax-selector__client-type-chevron--open' : '') })] }), isOpen && (_jsx("div", { className: "pax-selector__client-type-dropdown", role: "listbox", children: CLIENT_TYPES.map(function (type) { return (_jsxs("button", { type: "button", className: "pax-selector__client-type-option ".concat(value === type ? 'pax-selector__client-type-option--selected' : ''), onClick: function () { return handleSelect(type); }, role: "option", "aria-selected": value === type, children: [_jsx(Icon, { name: "user-icon", size: "sm" }), type] }, type)); }) }))] })] }));
|
|
86
86
|
};
|
|
87
87
|
var RoomEditor = function (_a) {
|
|
88
|
-
var room = _a.room, roomNumber = _a.roomNumber, showRemove = _a.showRemove, maxAdults = _a.maxAdults, maxTeens = _a.maxTeens, maxChildren = _a.maxChildren, maxInfants = _a.maxInfants, onChange = _a.onChange, onRemove = _a.onRemove, scrollToRef = _a.scrollToRef, ageRange = _a.ageRange;
|
|
88
|
+
var room = _a.room, roomNumber = _a.roomNumber, showRemove = _a.showRemove, maxAdults = _a.maxAdults, maxTeens = _a.maxTeens, maxChildren = _a.maxChildren, maxInfants = _a.maxInfants, onChange = _a.onChange, onRemove = _a.onRemove, scrollToRef = _a.scrollToRef, ageRange = _a.ageRange, _b = _a.checkEmpty, checkEmpty = _b === void 0 ? false : _b;
|
|
89
89
|
var roomAgesSectionRef = useRef(null);
|
|
90
90
|
var previousRoomCounts = useRef({ teens: 0, children: 0, infants: 0 });
|
|
91
91
|
var handleFieldChange = function (field, value) {
|
|
@@ -167,26 +167,26 @@ var RoomEditor = function (_a) {
|
|
|
167
167
|
var actualIndex = chunkIndex * 2 + ageIndex;
|
|
168
168
|
return (_jsx(AgeSelector, { label: "Teen", value: age, onChange: function (selectedAge) {
|
|
169
169
|
return handleAgeChange('teenAges', actualIndex, selectedAge);
|
|
170
|
-
}, ageRange: ageRange, required: true }, "teen-".concat(actualIndex)));
|
|
170
|
+
}, ageRange: ageRange, required: true, error: checkEmpty }, "teen-".concat(actualIndex)));
|
|
171
171
|
}) }, "teen-chunk-".concat(chunkIndex))); }), room.children > 0 &&
|
|
172
172
|
childAgeChunks.map(function (chunk, chunkIndex) { return (_jsx(Fragment, { children: chunk.map(function (age, ageIndex) {
|
|
173
173
|
var actualIndex = chunkIndex * 2 + ageIndex;
|
|
174
174
|
return (_jsx(AgeSelector, { label: "Child", value: age, onChange: function (selectedAge) {
|
|
175
175
|
return handleAgeChange('childAges', actualIndex, selectedAge);
|
|
176
|
-
}, ageRange: ageRange, required: true }, "child-".concat(actualIndex)));
|
|
176
|
+
}, ageRange: ageRange, required: true, error: checkEmpty }, "child-".concat(actualIndex)));
|
|
177
177
|
}) }, "child-chunk-".concat(chunkIndex))); }), room.infants > 0 &&
|
|
178
178
|
infantAgeChunks.map(function (chunk, chunkIndex) { return (_jsx(Fragment, { children: chunk.map(function (age, ageIndex) {
|
|
179
179
|
var actualIndex = chunkIndex * 2 + ageIndex;
|
|
180
180
|
return (_jsx(AgeSelector, { label: "Infant", value: age, onChange: function (selectedAge) {
|
|
181
181
|
return handleAgeChange('infantAges', actualIndex, selectedAge);
|
|
182
|
-
}, ageRange: ageRange, required: true }, "infant-".concat(actualIndex)));
|
|
182
|
+
}, ageRange: ageRange, required: true, error: checkEmpty }, "infant-".concat(actualIndex)));
|
|
183
183
|
}) }, "infant-chunk-".concat(chunkIndex))); })] })] })), _jsx(ClientTypeSelector, { value: room.clientType, onChange: function (val) { return handleFieldChange('clientType', val); } })] })] }));
|
|
184
184
|
};
|
|
185
185
|
var PaxSelector = function (_a) {
|
|
186
|
-
var _b = _a.label, label = _b === void 0 ? 'Number of pax' : _b, value = _a.value, onChange = _a.onChange, onAddRoom = _a.onAddRoom, onDone = _a.onDone, _c = _a.placeholder, placeholder = _c === void 0 ? 'Select pax' : _c, _d = _a.className, className = _d === void 0 ? '' : _d, _e = _a.maxAdults, maxAdults = _e === void 0 ? 10 : _e, _f = _a.maxTeens, maxTeens = _f === void 0 ? 10 : _f, _g = _a.maxChildren, maxChildren = _g === void 0 ? 10 : _g, _h = _a.maxInfants, maxInfants = _h === void 0 ? 10 : _h, _j = _a.showAddRoom, showAddRoom = _j === void 0 ? true : _j, _k = _a.multipleRooms, multipleRooms = _k === void 0 ? false : _k, defaultRooms = _a.defaultRooms, onRoomsChange = _a.onRoomsChange, onRemoveRoom = _a.onRemoveRoom, _l = _a.defaultPaxData, defaultPaxData = _l === void 0 ? DEFAULT_PAX_DATA_WITH_ADULTS : _l, _m = _a.ageRange, ageRange = _m === void 0 ? CHILD_CATEGORY_AGES : _m, _o = _a.scrollOnOpen, scrollOnOpen = _o === void 0 ? false : _o, _p = _a.disabled, disabled = _p === void 0 ? false : _p;
|
|
187
|
-
var
|
|
188
|
-
var
|
|
189
|
-
var
|
|
186
|
+
var _b = _a.label, label = _b === void 0 ? 'Number of pax' : _b, value = _a.value, onChange = _a.onChange, onAddRoom = _a.onAddRoom, onDone = _a.onDone, _c = _a.placeholder, placeholder = _c === void 0 ? 'Select pax' : _c, _d = _a.className, className = _d === void 0 ? '' : _d, _e = _a.maxAdults, maxAdults = _e === void 0 ? 10 : _e, _f = _a.maxTeens, maxTeens = _f === void 0 ? 10 : _f, _g = _a.maxChildren, maxChildren = _g === void 0 ? 10 : _g, _h = _a.maxInfants, maxInfants = _h === void 0 ? 10 : _h, _j = _a.showAddRoom, showAddRoom = _j === void 0 ? true : _j, _k = _a.multipleRooms, multipleRooms = _k === void 0 ? false : _k, defaultRooms = _a.defaultRooms, onRoomsChange = _a.onRoomsChange, onRemoveRoom = _a.onRemoveRoom, _l = _a.defaultPaxData, defaultPaxData = _l === void 0 ? DEFAULT_PAX_DATA_WITH_ADULTS : _l, _m = _a.ageRange, ageRange = _m === void 0 ? CHILD_CATEGORY_AGES : _m, _o = _a.scrollOnOpen, scrollOnOpen = _o === void 0 ? false : _o, _p = _a.disabled, disabled = _p === void 0 ? false : _p, _q = _a.checkEmpty, checkEmpty = _q === void 0 ? false : _q;
|
|
187
|
+
var _r = useState(false), isOpen = _r[0], setIsOpen = _r[1];
|
|
188
|
+
var _s = useState(value || defaultPaxData || DEFAULT_PAX_DATA), internalData = _s[0], setInternalData = _s[1];
|
|
189
|
+
var _t = useState(defaultRooms || [__assign(__assign({}, DEFAULT_PAX_DATA), { roomId: '1' })]), rooms = _t[0], setRooms = _t[1];
|
|
190
190
|
var containerRef = useRef(null);
|
|
191
191
|
var triggerRef = useRef(null);
|
|
192
192
|
var hasInitialized = useRef(false);
|
|
@@ -252,11 +252,76 @@ var PaxSelector = function (_a) {
|
|
|
252
252
|
}
|
|
253
253
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
254
254
|
}, [internalData.teens, internalData.children, internalData.infants, multipleRooms]);
|
|
255
|
+
// Helper function to check if all ages are filled
|
|
256
|
+
var areAllAgesFilled = function () {
|
|
257
|
+
if (multipleRooms) {
|
|
258
|
+
// Check all rooms
|
|
259
|
+
for (var _i = 0, rooms_1 = rooms; _i < rooms_1.length; _i++) {
|
|
260
|
+
var room = rooms_1[_i];
|
|
261
|
+
if (room.teens > 0) {
|
|
262
|
+
var teenAges = room.teenAges || [];
|
|
263
|
+
for (var i = 0; i < room.teens; i++) {
|
|
264
|
+
if (teenAges[i] === undefined || teenAges[i] === null)
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (room.children > 0) {
|
|
269
|
+
var childAges = room.childAges || [];
|
|
270
|
+
for (var i = 0; i < room.children; i++) {
|
|
271
|
+
if (childAges[i] === undefined || childAges[i] === null)
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (room.infants > 0) {
|
|
276
|
+
var infantAges = room.infantAges || [];
|
|
277
|
+
for (var i = 0; i < room.infants; i++) {
|
|
278
|
+
if (infantAges[i] === undefined || infantAges[i] === null)
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
// Check single room mode
|
|
286
|
+
if (internalData.teens > 0) {
|
|
287
|
+
var teenAges = internalData.teenAges || [];
|
|
288
|
+
for (var i = 0; i < internalData.teens; i++) {
|
|
289
|
+
if (teenAges[i] === undefined || teenAges[i] === null)
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (internalData.children > 0) {
|
|
294
|
+
var childAges = internalData.childAges || [];
|
|
295
|
+
for (var i = 0; i < internalData.children; i++) {
|
|
296
|
+
if (childAges[i] === undefined || childAges[i] === null)
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (internalData.infants > 0) {
|
|
301
|
+
var infantAges = internalData.infantAges || [];
|
|
302
|
+
for (var i = 0; i < internalData.infants; i++) {
|
|
303
|
+
if (infantAges[i] === undefined || infantAges[i] === null)
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return true;
|
|
309
|
+
};
|
|
310
|
+
// Check if there are any minors that need age specification
|
|
311
|
+
var hasMinorsWithoutAges = function () {
|
|
312
|
+
if (multipleRooms) {
|
|
313
|
+
return rooms.some(function (room) { return room.teens > 0 || room.children > 0 || room.infants > 0; });
|
|
314
|
+
}
|
|
315
|
+
return internalData.teens > 0 || internalData.children > 0 || internalData.infants > 0;
|
|
316
|
+
};
|
|
255
317
|
// Handle clicks outside the dropdown
|
|
256
318
|
useEffect(function () {
|
|
257
319
|
var handleClickOutside = function (event) {
|
|
258
320
|
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
|
259
|
-
|
|
321
|
+
// Only close if all ages are filled (when there are minors)
|
|
322
|
+
if (!hasMinorsWithoutAges() || areAllAgesFilled()) {
|
|
323
|
+
setIsOpen(false);
|
|
324
|
+
}
|
|
260
325
|
}
|
|
261
326
|
};
|
|
262
327
|
if (isOpen) {
|
|
@@ -265,7 +330,7 @@ var PaxSelector = function (_a) {
|
|
|
265
330
|
return function () {
|
|
266
331
|
document.removeEventListener('mousedown', handleClickOutside);
|
|
267
332
|
};
|
|
268
|
-
}, [isOpen]);
|
|
333
|
+
}, [isOpen, internalData, rooms, multipleRooms]);
|
|
269
334
|
// Scroll to input when dropdown opens (if scrollOnOpen is true)
|
|
270
335
|
useEffect(function () {
|
|
271
336
|
scrollIntoViewOnOpen(triggerRef, isOpen, scrollOnOpen);
|
|
@@ -324,8 +389,11 @@ var PaxSelector = function (_a) {
|
|
|
324
389
|
}
|
|
325
390
|
};
|
|
326
391
|
var handleDone = function () {
|
|
327
|
-
|
|
328
|
-
|
|
392
|
+
// Only close if all ages are filled (when there are minors)
|
|
393
|
+
if (!hasMinorsWithoutAges() || areAllAgesFilled()) {
|
|
394
|
+
setIsOpen(false);
|
|
395
|
+
onDone === null || onDone === void 0 ? void 0 : onDone(multipleRooms ? rooms : internalData);
|
|
396
|
+
}
|
|
329
397
|
};
|
|
330
398
|
var handleAddRoom = function () {
|
|
331
399
|
var newRoom = __assign(__assign({}, DEFAULT_PAX_DATA), { roomId: "".concat(rooms.length + 1) });
|
|
@@ -372,7 +440,7 @@ var PaxSelector = function (_a) {
|
|
|
372
440
|
return "".concat(total, " pax");
|
|
373
441
|
};
|
|
374
442
|
var hasPax = getTotalPax() > 0;
|
|
375
|
-
return (_jsxs("div", { className: "pax-selector ".concat(disabled ? 'pax-selector--disabled' : '', " ").concat(className), ref: containerRef, children: [_jsxs("button", { ref: triggerRef, type: "button", className: "pax-selector__trigger", onClick: function () { return !disabled && setIsOpen(!isOpen); }, "aria-expanded": isOpen, "aria-haspopup": "true", disabled: disabled, children: [_jsx(Text, { size: "sm", variant: "regular", className: "pax-selector__label", children: label }), _jsxs("div", { className: "pax-selector__input ".concat(isOpen ? 'pax-selector__input--active' : '', " ").concat(disabled ? 'pax-selector__input--disabled' : ''), children: [_jsxs("div", { className: "pax-selector__input-content", children: [_jsx(Icon, { name: "user-icon", size: "sm", className: "pax-selector__input-icon" }), _jsx("span", { className: "pax-selector__input-text ".concat(!hasPax ? 'pax-selector__input-placeholder' : ''), children: getDisplayText() })] }), _jsx(Icon, { name: "chevron-down", size: "sm", className: "pax-selector__chevron ".concat(isOpen ? 'pax-selector__chevron--open' : '') })] })] }), isOpen && (_jsxs("div", { className: "pax-selector__dropdown", children: [multipleRooms ? (_jsxs(_Fragment, { children: [showAddRoom && (_jsxs("button", { type: "button", className: "pax-selector__add-room", onClick: handleAddRoom, children: [_jsx(Icon, { name: "home", size: "sm", className: "pax-selector__add-room-icon" }), "Add a room", _jsx(Icon, { name: "plus", size: "sm", className: "pax-selector__add-room-icon" })] })), _jsx("div", { className: "pax-selector__rooms", children: rooms.map(function (room, index) { return (_jsx(RoomEditor, { room: room, roomNumber: index + 1, showRemove: rooms.length > 1, maxAdults: maxAdults, maxTeens: maxTeens, maxChildren: maxChildren, maxInfants: maxInfants, onChange: function (updatedRoom) { return handleRoomChange(room.roomId, updatedRoom); }, onRemove: function () { return handleRemoveRoom(room.roomId); }, scrollToRef: index === rooms.length - 1 ? lastRoomRef : undefined, ageRange: ageRange }, room.roomId)); }) })] })) : (
|
|
443
|
+
return (_jsxs("div", { className: "pax-selector ".concat(disabled ? 'pax-selector--disabled' : '', " ").concat(className), ref: containerRef, children: [_jsxs("button", { ref: triggerRef, type: "button", className: "pax-selector__trigger", onClick: function () { return !disabled && setIsOpen(!isOpen); }, "aria-expanded": isOpen, "aria-haspopup": "true", disabled: disabled, children: [_jsx(Text, { size: "sm", variant: "regular", className: "pax-selector__label", children: label }), _jsxs("div", { className: "pax-selector__input ".concat(isOpen ? 'pax-selector__input--active' : '', " ").concat(disabled ? 'pax-selector__input--disabled' : ''), children: [_jsxs("div", { className: "pax-selector__input-content", children: [_jsx(Icon, { name: "user-icon", size: "sm", className: "pax-selector__input-icon" }), _jsx("span", { className: "pax-selector__input-text ".concat(!hasPax ? 'pax-selector__input-placeholder' : ''), children: getDisplayText() })] }), _jsx(Icon, { name: "chevron-down", size: "sm", className: "pax-selector__chevron ".concat(isOpen ? 'pax-selector__chevron--open' : '') })] })] }), isOpen && (_jsxs("div", { className: "pax-selector__dropdown", children: [multipleRooms ? (_jsxs(_Fragment, { children: [showAddRoom && (_jsxs("button", { type: "button", className: "pax-selector__add-room", onClick: handleAddRoom, children: [_jsx(Icon, { name: "home", size: "sm", className: "pax-selector__add-room-icon" }), "Add a room", _jsx(Icon, { name: "plus", size: "sm", className: "pax-selector__add-room-icon" })] })), _jsx("div", { className: "pax-selector__rooms", children: rooms.map(function (room, index) { return (_jsx(RoomEditor, { room: room, roomNumber: index + 1, showRemove: rooms.length > 1, maxAdults: maxAdults, maxTeens: maxTeens, maxChildren: maxChildren, maxInfants: maxInfants, onChange: function (updatedRoom) { return handleRoomChange(room.roomId, updatedRoom); }, onRemove: function () { return handleRemoveRoom(room.roomId); }, scrollToRef: index === rooms.length - 1 ? lastRoomRef : undefined, ageRange: ageRange, checkEmpty: checkEmpty }, room.roomId)); }) })] })) : (
|
|
376
444
|
/* Single Room Mode */
|
|
377
445
|
_jsxs(_Fragment, { children: [_jsxs("div", { className: "pax-selector__steppers", children: [_jsx(StepperRow, { label: "Adult", value: internalData.adults, max: maxAdults, onChange: function (val) { return handleDataChange('adults', val); } }), _jsx(StepperRow, { label: "Teen", value: internalData.teens, max: maxTeens, onChange: function (val) { return handleDataChange('teens', val); } }), _jsx(StepperRow, { label: "Child", value: internalData.children, max: maxChildren, onChange: function (val) { return handleDataChange('children', val); } }), _jsx(StepperRow, { label: "Infant", value: internalData.infants, max: maxInfants, onChange: function (val) { return handleDataChange('infants', val); } })] }), (internalData.teens > 0 ||
|
|
378
446
|
internalData.children > 0 ||
|
|
@@ -397,19 +465,19 @@ var PaxSelector = function (_a) {
|
|
|
397
465
|
var actualIndex = chunkIndex * 2 + ageIndex;
|
|
398
466
|
return (_jsx(AgeSelector, { label: "Teen", value: age, onChange: function (selectedAge) {
|
|
399
467
|
return handleAgeChange('teenAges', actualIndex, selectedAge);
|
|
400
|
-
}, ageRange: ageRange, required: true }, "teen-".concat(actualIndex)));
|
|
468
|
+
}, ageRange: ageRange, required: true, error: checkEmpty }, "teen-".concat(actualIndex)));
|
|
401
469
|
}) }, "teen-chunk-".concat(chunkIndex))); }), internalData.children > 0 &&
|
|
402
470
|
childAgeChunks.map(function (chunk, chunkIndex) { return (_jsx(Fragment, { children: chunk.map(function (age, ageIndex) {
|
|
403
471
|
var actualIndex = chunkIndex * 2 + ageIndex;
|
|
404
472
|
return (_jsx(AgeSelector, { label: "Child", value: age, onChange: function (selectedAge) {
|
|
405
473
|
return handleAgeChange('childAges', actualIndex, selectedAge);
|
|
406
|
-
}, ageRange: ageRange, required: true }, "child-".concat(actualIndex)));
|
|
474
|
+
}, ageRange: ageRange, required: true, error: checkEmpty }, "child-".concat(actualIndex)));
|
|
407
475
|
}) }, "child-chunk-".concat(chunkIndex))); }), internalData.infants > 0 &&
|
|
408
476
|
infantAgeChunks.map(function (chunk, chunkIndex) { return (_jsx(Fragment, { children: chunk.map(function (age, ageIndex) {
|
|
409
477
|
var actualIndex = chunkIndex * 2 + ageIndex;
|
|
410
478
|
return (_jsx(AgeSelector, { label: "Infant", value: age, onChange: function (selectedAge) {
|
|
411
479
|
return handleAgeChange('infantAges', actualIndex, selectedAge);
|
|
412
|
-
}, ageRange: ageRange, required: true }, "infant-".concat(actualIndex)));
|
|
480
|
+
}, ageRange: ageRange, required: true, error: checkEmpty }, "infant-".concat(actualIndex)));
|
|
413
481
|
}) }, "infant-chunk-".concat(chunkIndex))); })] }));
|
|
414
482
|
})() })] })), _jsx(ClientTypeSelector, { value: internalData.clientType, onChange: function (val) { return handleDataChange('clientType', val); } })] })), _jsxs("div", { className: "pax-selector__actions", children: [_jsx("button", { type: "button", className: "pax-selector__clear-btn", onClick: handleClear, children: "Clear Pax" }), _jsx("button", { type: "button", className: "pax-selector__done-btn", onClick: handleDone, children: "Done" })] })] }))] }));
|
|
415
483
|
};
|
|
@@ -184,6 +184,6 @@ var RoundTrip = function (_a) {
|
|
|
184
184
|
var isAccommodationEmpty = checkEmpty && !internalAccommodation;
|
|
185
185
|
var pickupDropoffOptions = getPickupDropoffOptions();
|
|
186
186
|
var accommodationOptions = getAccommodationOptions();
|
|
187
|
-
return (_jsx("div", { className: "round-trip ".concat(className), "data-round-trip-id": id, children: _jsxs("div", { className: "round-trip__content", children: [_jsx("div", { className: "round-trip__field round-trip__field--pax ".concat(isPaxEmpty ? 'round-trip__field--error' : ''), children: _jsx(PaxSelector, { label: "Number of pax", value: internalPaxData, onChange: handlePaxChange, placeholder: "2 pax", className: isPaxEmpty ? 'pax-selector--error' : '', scrollOnOpen: scrollOnOpen }) }), _jsxs("div", { className: "round-trip__field round-trip__field--dates", children: [_jsx(Text, { size: "sm", variant: "regular", className: "round-trip__field-label", children: "Arrival date - Departure date" }), _jsx(DateTimePicker, { placeholder: "DD/MM/YYYY - DD/MM/YYYY", mode: "calendar", iconPosition: "left", numberOfMonths: 2, iconBGFull: false, showChevron: true, onValueChange: handleDateRangeChange, selectionMode: "range", defaultValue: internalArrivalDate && internalDepartureDate ? [internalArrivalDate, internalDepartureDate] : undefined, inputClassName: "round-trip__date-picker--input", state: isDateEmpty ? 'error' : undefined, scrollOnOpen: scrollOnOpen })] }), _jsx("div", { className: "round-trip__field round-trip__field--pickup-dropoff", children: _jsx(LocationDropdown, { label: "Pick up / Drop-off point", options: pickupDropoffOptions.options, groups: pickupDropoffOptions.groups, selectedValue: (internalPickupDropoffPoint === null || internalPickupDropoffPoint === void 0 ? void 0 : internalPickupDropoffPoint.id) || null, onSelectionChange: handlePickupDropoffChange, placeholder: "Select pick-up / drop-off point", direction: undefined, type: "airport-port", showGroupTitles: true, error: isPickupDropoffEmpty, scrollOnOpen: scrollOnOpen }) }), _jsx("div", { className: "round-trip__field round-trip__field--accommodation", children: _jsx(LocationDropdown, { label: "Accommodation", options: accommodationOptions.options, groups: accommodationOptions.groups, selectedValue: (internalAccommodation === null || internalAccommodation === void 0 ? void 0 : internalAccommodation.id) || null, onSelectionChange: handleAccommodationChange, placeholder: "Select accommodation", direction: "dropoff", type: "accommodation", showGroupTitles: false, error: isAccommodationEmpty, scrollOnOpen: scrollOnOpen }) })] }) }));
|
|
187
|
+
return (_jsx("div", { className: "round-trip ".concat(className), "data-round-trip-id": id, children: _jsxs("div", { className: "round-trip__content", children: [_jsx("div", { className: "round-trip__field round-trip__field--pax ".concat(isPaxEmpty ? 'round-trip__field--error' : ''), children: _jsx(PaxSelector, { label: "Number of pax", value: internalPaxData, onChange: handlePaxChange, placeholder: "2 pax", className: isPaxEmpty ? 'pax-selector--error' : '', scrollOnOpen: scrollOnOpen, checkEmpty: checkEmpty }) }), _jsxs("div", { className: "round-trip__field round-trip__field--dates", children: [_jsx(Text, { size: "sm", variant: "regular", className: "round-trip__field-label", children: "Arrival date - Departure date" }), _jsx(DateTimePicker, { placeholder: "DD/MM/YYYY - DD/MM/YYYY", mode: "calendar", iconPosition: "left", numberOfMonths: 2, iconBGFull: false, showChevron: true, onValueChange: handleDateRangeChange, selectionMode: "range", defaultValue: internalArrivalDate && internalDepartureDate ? [internalArrivalDate, internalDepartureDate] : undefined, inputClassName: "round-trip__date-picker--input", state: isDateEmpty ? 'error' : undefined, scrollOnOpen: scrollOnOpen })] }), _jsx("div", { className: "round-trip__field round-trip__field--pickup-dropoff", children: _jsx(LocationDropdown, { label: "Pick up / Drop-off point", options: pickupDropoffOptions.options, groups: pickupDropoffOptions.groups, selectedValue: (internalPickupDropoffPoint === null || internalPickupDropoffPoint === void 0 ? void 0 : internalPickupDropoffPoint.id) || null, onSelectionChange: handlePickupDropoffChange, placeholder: "Select pick-up / drop-off point", direction: undefined, type: "airport-port", showGroupTitles: true, error: isPickupDropoffEmpty, scrollOnOpen: scrollOnOpen }) }), _jsx("div", { className: "round-trip__field round-trip__field--accommodation", children: _jsx(LocationDropdown, { label: "Accommodation", options: accommodationOptions.options, groups: accommodationOptions.groups, selectedValue: (internalAccommodation === null || internalAccommodation === void 0 ? void 0 : internalAccommodation.id) || null, onSelectionChange: handleAccommodationChange, placeholder: "Select accommodation", direction: "dropoff", type: "accommodation", showGroupTitles: false, error: isAccommodationEmpty, scrollOnOpen: scrollOnOpen }) })] }) }));
|
|
188
188
|
};
|
|
189
189
|
export default RoundTrip;
|
|
@@ -120,6 +120,40 @@ var SearchBarTransfer = function (_a) {
|
|
|
120
120
|
}
|
|
121
121
|
setTransferLines(function (prevLines) { return prevLines.filter(function (line) { return line.id !== id; }); });
|
|
122
122
|
};
|
|
123
|
+
// Helper function to check if all ages are filled for pax data
|
|
124
|
+
var areAllAgesFilled = function (paxData) {
|
|
125
|
+
// Check teen ages
|
|
126
|
+
if (paxData.teens > 0) {
|
|
127
|
+
var teenAges = paxData.teenAges || [];
|
|
128
|
+
if (teenAges.length < paxData.teens)
|
|
129
|
+
return false;
|
|
130
|
+
for (var i = 0; i < paxData.teens; i++) {
|
|
131
|
+
if (teenAges[i] === undefined || teenAges[i] === null)
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Check child ages
|
|
136
|
+
if (paxData.children > 0) {
|
|
137
|
+
var childAges = paxData.childAges || [];
|
|
138
|
+
if (childAges.length < paxData.children)
|
|
139
|
+
return false;
|
|
140
|
+
for (var i = 0; i < paxData.children; i++) {
|
|
141
|
+
if (childAges[i] === undefined || childAges[i] === null)
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Check infant ages
|
|
146
|
+
if (paxData.infants > 0) {
|
|
147
|
+
var infantAges = paxData.infantAges || [];
|
|
148
|
+
if (infantAges.length < paxData.infants)
|
|
149
|
+
return false;
|
|
150
|
+
for (var i = 0; i < paxData.infants; i++) {
|
|
151
|
+
if (infantAges[i] === undefined || infantAges[i] === null)
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
};
|
|
123
157
|
// Validate data
|
|
124
158
|
var validateData = function () {
|
|
125
159
|
if (mode === "roundtrip") {
|
|
@@ -136,6 +170,11 @@ var SearchBarTransfer = function (_a) {
|
|
|
136
170
|
setError("Please fill in all the transfer details.");
|
|
137
171
|
return false;
|
|
138
172
|
}
|
|
173
|
+
// Check if all ages are filled
|
|
174
|
+
if (!areAllAgesFilled(roundTripData.paxData)) {
|
|
175
|
+
setError("Please fill in all the transfer details.");
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
139
178
|
}
|
|
140
179
|
else {
|
|
141
180
|
if (transferLines.length === 0) {
|
|
@@ -152,6 +191,11 @@ var SearchBarTransfer = function (_a) {
|
|
|
152
191
|
setError("Please fill in all the transfer details.");
|
|
153
192
|
return false;
|
|
154
193
|
}
|
|
194
|
+
// Check if all ages are filled for this line
|
|
195
|
+
if (!areAllAgesFilled(line.paxData)) {
|
|
196
|
+
setError("Please fill in all the transfer details.");
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
155
199
|
}
|
|
156
200
|
}
|
|
157
201
|
setError(null);
|
|
@@ -192,7 +192,7 @@ var TransferLine = function (_a) {
|
|
|
192
192
|
var isDateEmpty = checkEmpty && (!internalTransferDate || internalTransferDate === '');
|
|
193
193
|
var isPickupEmpty = checkEmpty && !internalPickupPoint;
|
|
194
194
|
var isDropoffEmpty = checkEmpty && !internalDropoffPoint;
|
|
195
|
-
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 }) }), _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'
|
|
195
|
+
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
196
|
? 'accommodation'
|
|
197
197
|
: type === 'arrival'
|
|
198
198
|
? 'airport-port'
|
|
@@ -64,6 +64,27 @@
|
|
|
64
64
|
border-color: var(--dropdown-color-border-hover,#0f7173);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
.age-selector__input--error {
|
|
68
|
+
border-color: var(--color-red-600,#dc2626);
|
|
69
|
+
background-color: var(--color-coral-red-50,#fff8f8);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.age-selector__input--error:hover {
|
|
73
|
+
border-color: var(--color-red-600,#dc2626);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.age-selector__input--error .age-selector__input-field {
|
|
77
|
+
color: var(--color-coral-red-600,#f83b3b);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.age-selector__input--error .age-selector__input-field::placeholder {
|
|
81
|
+
color: var(--color-coral-red-600,#f83b3b);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.age-selector__input--error .age-selector__icon {
|
|
85
|
+
color: var(--color-coral-red-600,#f83b3b);
|
|
86
|
+
}
|
|
87
|
+
|
|
67
88
|
.age-selector__input--default .age-selector__input-field::placeholder {
|
|
68
89
|
color: var(--dropdown-selector-color-filled-foreground-default,#737373);
|
|
69
90
|
}
|
package/package.json
CHANGED
|
@@ -18,6 +18,8 @@ export interface AgeSelectorProps {
|
|
|
18
18
|
className?: string;
|
|
19
19
|
/** Placeholder text */
|
|
20
20
|
placeholder?: string;
|
|
21
|
+
/** Whether to show error state */
|
|
22
|
+
error?: boolean;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
const AgeSelector: React.FC<AgeSelectorProps> = ({
|
|
@@ -28,6 +30,7 @@ const AgeSelector: React.FC<AgeSelectorProps> = ({
|
|
|
28
30
|
required = false,
|
|
29
31
|
className = '',
|
|
30
32
|
placeholder = '--',
|
|
33
|
+
error = false,
|
|
31
34
|
}) => {
|
|
32
35
|
const [isOpen, setIsOpen] = useState(false);
|
|
33
36
|
const [inputValue, setInputValue] = useState<string>(
|
|
@@ -118,7 +121,7 @@ const AgeSelector: React.FC<AgeSelectorProps> = ({
|
|
|
118
121
|
</Text>
|
|
119
122
|
<div className="age-selector__container">
|
|
120
123
|
<div
|
|
121
|
-
className={`age-selector__input ${isOpen ? 'age-selector__input--open' : ''} ${value !== undefined ? 'age-selector__input--selected' : 'age-selector__input--default'}`}>
|
|
124
|
+
className={`age-selector__input ${isOpen ? 'age-selector__input--open' : ''} ${value !== undefined ? 'age-selector__input--selected' : 'age-selector__input--default'} ${error && value === undefined ? 'age-selector__input--error' : ''}`}>
|
|
122
125
|
<input
|
|
123
126
|
ref={inputRef}
|
|
124
127
|
type="text"
|
|
@@ -21,7 +21,7 @@ export function TooltipDisplay(props: TooltipDisplayProps) {
|
|
|
21
21
|
const { children, content, side = 'right', maxWidth = '300px' } = props;
|
|
22
22
|
return (
|
|
23
23
|
<Tooltip>
|
|
24
|
-
<TooltipTrigger className="text-[var(--color-icon-branded)]"
|
|
24
|
+
<TooltipTrigger className="text-[var(--color-icon-branded)]">
|
|
25
25
|
{children}
|
|
26
26
|
</TooltipTrigger>
|
|
27
27
|
<TooltipContent className={`max-w-[${maxWidth}]`} side={side} style={{ maxWidth }}>
|
|
@@ -72,6 +72,8 @@ export interface PaxSelectorProps {
|
|
|
72
72
|
scrollOnOpen?: boolean;
|
|
73
73
|
/** Age range for child categories */
|
|
74
74
|
ageRange?: number[];
|
|
75
|
+
/** Whether to check if age inputs are empty and show error state */
|
|
76
|
+
checkEmpty?: boolean;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
const DEFAULT_PAX_DATA: PaxData = {
|
|
@@ -246,6 +248,7 @@ interface RoomEditorProps {
|
|
|
246
248
|
onRemove: () => void;
|
|
247
249
|
scrollToRef?: React.RefObject<HTMLDivElement | null>;
|
|
248
250
|
ageRange: number[];
|
|
251
|
+
checkEmpty?: boolean;
|
|
249
252
|
}
|
|
250
253
|
|
|
251
254
|
const RoomEditor: React.FC<RoomEditorProps> = ({
|
|
@@ -260,6 +263,7 @@ const RoomEditor: React.FC<RoomEditorProps> = ({
|
|
|
260
263
|
onRemove,
|
|
261
264
|
scrollToRef,
|
|
262
265
|
ageRange,
|
|
266
|
+
checkEmpty = false,
|
|
263
267
|
}) => {
|
|
264
268
|
const roomAgesSectionRef = useRef<HTMLDivElement>(null);
|
|
265
269
|
const previousRoomCounts = useRef({ teens: 0, children: 0, infants: 0 });
|
|
@@ -444,6 +448,7 @@ const RoomEditor: React.FC<RoomEditorProps> = ({
|
|
|
444
448
|
}
|
|
445
449
|
ageRange={ageRange}
|
|
446
450
|
required
|
|
451
|
+
error={checkEmpty}
|
|
447
452
|
/>
|
|
448
453
|
);
|
|
449
454
|
})}
|
|
@@ -466,6 +471,7 @@ const RoomEditor: React.FC<RoomEditorProps> = ({
|
|
|
466
471
|
}
|
|
467
472
|
ageRange={ageRange}
|
|
468
473
|
required
|
|
474
|
+
error={checkEmpty}
|
|
469
475
|
/>
|
|
470
476
|
);
|
|
471
477
|
})}
|
|
@@ -488,6 +494,7 @@ const RoomEditor: React.FC<RoomEditorProps> = ({
|
|
|
488
494
|
}
|
|
489
495
|
ageRange={ageRange}
|
|
490
496
|
required
|
|
497
|
+
error={checkEmpty}
|
|
491
498
|
/>
|
|
492
499
|
);
|
|
493
500
|
})}
|
|
@@ -527,6 +534,7 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
527
534
|
ageRange = CHILD_CATEGORY_AGES,
|
|
528
535
|
scrollOnOpen = false,
|
|
529
536
|
disabled = false,
|
|
537
|
+
checkEmpty = false,
|
|
530
538
|
}) => {
|
|
531
539
|
const [isOpen, setIsOpen] = useState(false);
|
|
532
540
|
const [internalData, setInternalData] = useState<PaxData>(
|
|
@@ -617,11 +625,70 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
617
625
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
618
626
|
}, [internalData.teens, internalData.children, internalData.infants, multipleRooms]);
|
|
619
627
|
|
|
628
|
+
// Helper function to check if all ages are filled
|
|
629
|
+
const areAllAgesFilled = (): boolean => {
|
|
630
|
+
if (multipleRooms) {
|
|
631
|
+
// Check all rooms
|
|
632
|
+
for (const room of rooms) {
|
|
633
|
+
if (room.teens > 0) {
|
|
634
|
+
const teenAges = room.teenAges || [];
|
|
635
|
+
for (let i = 0; i < room.teens; i++) {
|
|
636
|
+
if (teenAges[i] === undefined || teenAges[i] === null) return false;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (room.children > 0) {
|
|
640
|
+
const childAges = room.childAges || [];
|
|
641
|
+
for (let i = 0; i < room.children; i++) {
|
|
642
|
+
if (childAges[i] === undefined || childAges[i] === null) return false;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (room.infants > 0) {
|
|
646
|
+
const infantAges = room.infantAges || [];
|
|
647
|
+
for (let i = 0; i < room.infants; i++) {
|
|
648
|
+
if (infantAges[i] === undefined || infantAges[i] === null) return false;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
} else {
|
|
653
|
+
// Check single room mode
|
|
654
|
+
if (internalData.teens > 0) {
|
|
655
|
+
const teenAges = internalData.teenAges || [];
|
|
656
|
+
for (let i = 0; i < internalData.teens; i++) {
|
|
657
|
+
if (teenAges[i] === undefined || teenAges[i] === null) return false;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
if (internalData.children > 0) {
|
|
661
|
+
const childAges = internalData.childAges || [];
|
|
662
|
+
for (let i = 0; i < internalData.children; i++) {
|
|
663
|
+
if (childAges[i] === undefined || childAges[i] === null) return false;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (internalData.infants > 0) {
|
|
667
|
+
const infantAges = internalData.infantAges || [];
|
|
668
|
+
for (let i = 0; i < internalData.infants; i++) {
|
|
669
|
+
if (infantAges[i] === undefined || infantAges[i] === null) return false;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return true;
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
// Check if there are any minors that need age specification
|
|
677
|
+
const hasMinorsWithoutAges = (): boolean => {
|
|
678
|
+
if (multipleRooms) {
|
|
679
|
+
return rooms.some(room => room.teens > 0 || room.children > 0 || room.infants > 0);
|
|
680
|
+
}
|
|
681
|
+
return internalData.teens > 0 || internalData.children > 0 || internalData.infants > 0;
|
|
682
|
+
};
|
|
683
|
+
|
|
620
684
|
// Handle clicks outside the dropdown
|
|
621
685
|
useEffect(() => {
|
|
622
686
|
const handleClickOutside = (event: MouseEvent) => {
|
|
623
687
|
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
|
624
|
-
|
|
688
|
+
// Only close if all ages are filled (when there are minors)
|
|
689
|
+
if (!hasMinorsWithoutAges() || areAllAgesFilled()) {
|
|
690
|
+
setIsOpen(false);
|
|
691
|
+
}
|
|
625
692
|
}
|
|
626
693
|
};
|
|
627
694
|
|
|
@@ -632,7 +699,7 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
632
699
|
return () => {
|
|
633
700
|
document.removeEventListener('mousedown', handleClickOutside);
|
|
634
701
|
};
|
|
635
|
-
}, [isOpen]);
|
|
702
|
+
}, [isOpen, internalData, rooms, multipleRooms]);
|
|
636
703
|
|
|
637
704
|
// Scroll to input when dropdown opens (if scrollOnOpen is true)
|
|
638
705
|
useEffect(() => {
|
|
@@ -704,8 +771,11 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
704
771
|
};
|
|
705
772
|
|
|
706
773
|
const handleDone = () => {
|
|
707
|
-
|
|
708
|
-
|
|
774
|
+
// Only close if all ages are filled (when there are minors)
|
|
775
|
+
if (!hasMinorsWithoutAges() || areAllAgesFilled()) {
|
|
776
|
+
setIsOpen(false);
|
|
777
|
+
onDone?.(multipleRooms ? rooms : internalData);
|
|
778
|
+
}
|
|
709
779
|
};
|
|
710
780
|
|
|
711
781
|
const handleAddRoom = () => {
|
|
@@ -829,6 +899,7 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
829
899
|
onRemove={() => handleRemoveRoom(room.roomId)}
|
|
830
900
|
scrollToRef={index === rooms.length - 1 ? lastRoomRef : undefined}
|
|
831
901
|
ageRange={ageRange}
|
|
902
|
+
checkEmpty={checkEmpty}
|
|
832
903
|
/>
|
|
833
904
|
))}
|
|
834
905
|
</div>
|
|
@@ -935,6 +1006,7 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
935
1006
|
}
|
|
936
1007
|
ageRange={ageRange}
|
|
937
1008
|
required
|
|
1009
|
+
error={checkEmpty}
|
|
938
1010
|
/>
|
|
939
1011
|
);
|
|
940
1012
|
})}
|
|
@@ -961,6 +1033,7 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
961
1033
|
}
|
|
962
1034
|
ageRange={ageRange}
|
|
963
1035
|
required
|
|
1036
|
+
error={checkEmpty}
|
|
964
1037
|
/>
|
|
965
1038
|
);
|
|
966
1039
|
})}
|
|
@@ -987,6 +1060,7 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
987
1060
|
}
|
|
988
1061
|
ageRange={ageRange}
|
|
989
1062
|
required
|
|
1063
|
+
error={checkEmpty}
|
|
990
1064
|
/>
|
|
991
1065
|
);
|
|
992
1066
|
})}
|
|
@@ -194,6 +194,42 @@ const SearchBarTransfer: React.FC<SearchBarTransferProps> = ({
|
|
|
194
194
|
setTransferLines((prevLines) => prevLines.filter((line) => line.id !== id));
|
|
195
195
|
};
|
|
196
196
|
|
|
197
|
+
// Helper function to check if all ages are filled for pax data
|
|
198
|
+
const areAllAgesFilled = (paxData: {
|
|
199
|
+
teens: number;
|
|
200
|
+
children: number;
|
|
201
|
+
infants: number;
|
|
202
|
+
teenAges?: number[];
|
|
203
|
+
childAges?: number[];
|
|
204
|
+
infantAges?: number[]
|
|
205
|
+
}): boolean => {
|
|
206
|
+
// Check teen ages
|
|
207
|
+
if (paxData.teens > 0) {
|
|
208
|
+
const teenAges = paxData.teenAges || [];
|
|
209
|
+
if (teenAges.length < paxData.teens) return false;
|
|
210
|
+
for (let i = 0; i < paxData.teens; i++) {
|
|
211
|
+
if (teenAges[i] === undefined || teenAges[i] === null) return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Check child ages
|
|
215
|
+
if (paxData.children > 0) {
|
|
216
|
+
const childAges = paxData.childAges || [];
|
|
217
|
+
if (childAges.length < paxData.children) return false;
|
|
218
|
+
for (let i = 0; i < paxData.children; i++) {
|
|
219
|
+
if (childAges[i] === undefined || childAges[i] === null) return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Check infant ages
|
|
223
|
+
if (paxData.infants > 0) {
|
|
224
|
+
const infantAges = paxData.infantAges || [];
|
|
225
|
+
if (infantAges.length < paxData.infants) return false;
|
|
226
|
+
for (let i = 0; i < paxData.infants; i++) {
|
|
227
|
+
if (infantAges[i] === undefined || infantAges[i] === null) return false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return true;
|
|
231
|
+
};
|
|
232
|
+
|
|
197
233
|
// Validate data
|
|
198
234
|
const validateData = (): boolean => {
|
|
199
235
|
if (mode === "roundtrip") {
|
|
@@ -212,6 +248,11 @@ const SearchBarTransfer: React.FC<SearchBarTransferProps> = ({
|
|
|
212
248
|
setError("Please fill in all the transfer details.");
|
|
213
249
|
return false;
|
|
214
250
|
}
|
|
251
|
+
// Check if all ages are filled
|
|
252
|
+
if (!areAllAgesFilled(roundTripData.paxData)) {
|
|
253
|
+
setError("Please fill in all the transfer details.");
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
215
256
|
} else {
|
|
216
257
|
if (transferLines.length === 0) {
|
|
217
258
|
setError("Please add at least one transfer.");
|
|
@@ -228,6 +269,11 @@ const SearchBarTransfer: React.FC<SearchBarTransferProps> = ({
|
|
|
228
269
|
setError("Please fill in all the transfer details.");
|
|
229
270
|
return false;
|
|
230
271
|
}
|
|
272
|
+
// Check if all ages are filled for this line
|
|
273
|
+
if (!areAllAgesFilled(line.paxData)) {
|
|
274
|
+
setError("Please fill in all the transfer details.");
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
231
277
|
}
|
|
232
278
|
}
|
|
233
279
|
setError(null);
|
|
@@ -35,6 +35,26 @@
|
|
|
35
35
|
@apply border-[var(--dropdown-color-border-hover,#0f7173)];
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
.age-selector__input--error {
|
|
39
|
+
@apply border-[var(--color-red-600,#dc2626)] bg-[var(--color-coral-red-50,#fff8f8)];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.age-selector__input--error:hover {
|
|
43
|
+
@apply border-[var(--color-red-600,#dc2626)];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.age-selector__input--error .age-selector__input-field {
|
|
47
|
+
@apply text-[var(--color-coral-red-600,#f83b3b)];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.age-selector__input--error .age-selector__input-field::placeholder {
|
|
51
|
+
@apply text-[var(--color-coral-red-600,#f83b3b)];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.age-selector__input--error .age-selector__icon {
|
|
55
|
+
@apply text-[var(--color-coral-red-600,#f83b3b)];
|
|
56
|
+
}
|
|
57
|
+
|
|
38
58
|
.age-selector__input--default .age-selector__input-field::placeholder {
|
|
39
59
|
@apply text-[var(--dropdown-selector-color-filled-foreground-default,#737373)];
|
|
40
60
|
}
|