@qite/tide-booking-component 1.4.38 → 1.4.40
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/build/build-cjs/booking-wizard/types.d.ts +1 -0
- package/build/build-cjs/content/components/login.d.ts +3 -0
- package/build/build-cjs/index.js +7618 -1952
- package/build/build-cjs/qsm/components/double-search-input-group/index.d.ts +2 -1
- package/build/build-cjs/qsm/components/search-input/index.d.ts +1 -0
- package/build/build-cjs/qsm/components/search-input-group/index.d.ts +3 -1
- package/build/build-cjs/qsm/store/qsm-slice.d.ts +6 -2
- package/build/build-cjs/qsm/types.d.ts +17 -32
- package/build/build-cjs/search-results/components/filters/flight-filters.d.ts +8 -0
- package/build/build-cjs/search-results/components/flight/flight-accommodation-results.d.ts +4 -1
- package/build/build-cjs/search-results/components/flight/flight-option.d.ts +7 -0
- package/build/build-cjs/search-results/components/flight/flight-search-context/index.d.ts +36 -0
- package/build/build-cjs/search-results/components/icon.d.ts +1 -0
- package/build/build-cjs/search-results/components/item-picker/index.d.ts +5 -3
- package/build/build-cjs/search-results/components/search-results-container/flight-search-results.d.ts +6 -0
- package/build/build-cjs/search-results/store/search-results-slice.d.ts +2 -0
- package/build/build-cjs/search-results/types.d.ts +31 -1
- package/build/build-cjs/search-results/utils/flight-utils.d.ts +16 -0
- package/build/build-cjs/shared/components/flyin.d.ts +9 -0
- package/build/build-cjs/shared/types.d.ts +6 -0
- package/build/build-cjs/shared/utils/localization-util.d.ts +91 -0
- package/build/build-esm/booking-wizard/types.d.ts +1 -0
- package/build/build-esm/content/components/login.d.ts +3 -0
- package/build/build-esm/index.js +8053 -2356
- package/build/build-esm/qsm/components/double-search-input-group/index.d.ts +2 -1
- package/build/build-esm/qsm/components/search-input/index.d.ts +1 -0
- package/build/build-esm/qsm/components/search-input-group/index.d.ts +3 -1
- package/build/build-esm/qsm/store/qsm-slice.d.ts +6 -2
- package/build/build-esm/qsm/types.d.ts +17 -32
- package/build/build-esm/search-results/components/filters/flight-filters.d.ts +8 -0
- package/build/build-esm/search-results/components/flight/flight-accommodation-results.d.ts +4 -1
- package/build/build-esm/search-results/components/flight/flight-option.d.ts +7 -0
- package/build/build-esm/search-results/components/flight/flight-search-context/index.d.ts +36 -0
- package/build/build-esm/search-results/components/icon.d.ts +1 -0
- package/build/build-esm/search-results/components/item-picker/index.d.ts +5 -3
- package/build/build-esm/search-results/components/search-results-container/flight-search-results.d.ts +6 -0
- package/build/build-esm/search-results/store/search-results-slice.d.ts +2 -0
- package/build/build-esm/search-results/types.d.ts +31 -1
- package/build/build-esm/search-results/utils/flight-utils.d.ts +16 -0
- package/build/build-esm/shared/components/flyin.d.ts +9 -0
- package/build/build-esm/shared/types.d.ts +6 -0
- package/build/build-esm/shared/utils/localization-util.d.ts +91 -0
- package/package.json +4 -3
- package/rollup.config.js +2 -2
- package/src/booking-product/components/dates.tsx +1 -1
- package/src/booking-wizard/features/booking/booking-slice.ts +4 -2
- package/src/booking-wizard/types.ts +1 -0
- package/src/content/components/login.tsx +162 -0
- package/src/content/components/slider.tsx +1 -1
- package/src/content/features/content-page/content-page-self-contained.tsx +56 -75
- package/src/qsm/components/QSMContainer/qsm-container.tsx +197 -75
- package/src/qsm/components/double-search-input-group/index.tsx +14 -75
- package/src/qsm/components/mobile-filter-modal/index.tsx +18 -11
- package/src/qsm/components/search-input/index.tsx +9 -2
- package/src/qsm/components/search-input-group/index.tsx +19 -31
- package/src/qsm/components/travel-class-picker/index.tsx +1 -0
- package/src/qsm/components/travel-input/index.tsx +4 -4
- package/src/qsm/components/travel-input-group/index.tsx +4 -3
- package/src/qsm/components/travel-nationality-picker/index.tsx +1 -0
- package/src/qsm/components/travel-type-picker/index.tsx +1 -0
- package/src/qsm/qsm-configuration-context.ts +6 -17
- package/src/qsm/store/qsm-slice.ts +13 -1
- package/src/qsm/types.ts +19 -39
- package/src/search-results/components/filters/flight-filters.tsx +671 -0
- package/src/search-results/components/flight/flight-accommodation-results.tsx +20 -562
- package/src/search-results/components/flight/flight-banner.tsx +1 -1
- package/src/search-results/components/flight/flight-option.tsx +243 -0
- package/src/search-results/components/flight/flight-search-context/index.tsx +508 -0
- package/src/search-results/components/hotel/hotel-card.tsx +0 -1
- package/src/search-results/components/icon.tsx +84 -44
- package/src/search-results/components/item-picker/index.tsx +16 -11
- package/src/search-results/components/search-results-container/flight-search-results.tsx +120 -0
- package/src/search-results/components/search-results-container/search-results-container.tsx +85 -70
- package/src/search-results/store/search-results-slice.ts +6 -0
- package/src/search-results/types.ts +37 -1
- package/src/search-results/utils/flight-utils.ts +106 -0
- package/src/shared/components/flyin.tsx +334 -0
- package/src/shared/translations/ar-SA.json +13 -1
- package/src/shared/translations/da-DK.json +13 -1
- package/src/shared/translations/de-DE.json +13 -1
- package/src/shared/translations/en-GB.json +13 -1
- package/src/shared/translations/es-ES.json +13 -1
- package/src/shared/translations/fr-BE.json +13 -1
- package/src/shared/translations/fr-FR.json +13 -1
- package/src/shared/translations/is-IS.json +13 -1
- package/src/shared/translations/it-IT.json +13 -1
- package/src/shared/translations/ja-JP.json +13 -1
- package/src/shared/translations/nl-BE.json +13 -1
- package/src/shared/translations/nl-NL.json +13 -1
- package/src/shared/translations/no-NO.json +13 -1
- package/src/shared/translations/pl-PL.json +13 -1
- package/src/shared/translations/pt-PT.json +13 -1
- package/src/shared/translations/sv-SE.json +13 -1
- package/src/shared/types.ts +7 -0
- package/src/shared/utils/localization-util.ts +71 -0
- package/styles/booking-search-results.scss +1 -0
- package/styles/components/_flyin.scss +550 -0
- package/styles/components/_login.scss +133 -0
- package/styles/content-blocks.scss +1 -0
|
@@ -14,16 +14,33 @@ import Icon from '../icon';
|
|
|
14
14
|
import TravelNationalityPicker from '../travel-nationality-picker';
|
|
15
15
|
import { addDays, addMonths } from 'date-fns';
|
|
16
16
|
import { DateRange } from '../../../booking-product/types';
|
|
17
|
-
import { QSMState, setFromDate, setToDate } from '../../store/qsm-slice';
|
|
18
|
-
import {
|
|
17
|
+
import { QSMState, setFromDate, setSelectedQsmType, setToDate, setTripType } from '../../store/qsm-slice';
|
|
18
|
+
import { BaseFieldConfig, DoubleFieldConfig, QsmType } from '../../types';
|
|
19
19
|
import { getTranslations } from '../../../shared/utils/localization-util';
|
|
20
20
|
|
|
21
21
|
const QSMContainer: React.FC = () => {
|
|
22
22
|
const dispatch = useDispatch();
|
|
23
23
|
const isMobile = useMediaQuery('(max-width: 768px)');
|
|
24
24
|
const qsmState = useSelector((state: QSMRootState) => state.qsm);
|
|
25
|
-
const { mobileFilterType, fromDate, toDate } = qsmState;
|
|
26
|
-
const {
|
|
25
|
+
const { qsmType, mobileFilterType, fromDate, toDate, tripType } = qsmState;
|
|
26
|
+
const {
|
|
27
|
+
askTravelers,
|
|
28
|
+
askRooms,
|
|
29
|
+
askNationality,
|
|
30
|
+
askTravelClass,
|
|
31
|
+
askTravelType,
|
|
32
|
+
submitIcon,
|
|
33
|
+
onSubmit,
|
|
34
|
+
travelTypes,
|
|
35
|
+
languageCode,
|
|
36
|
+
departureAirport,
|
|
37
|
+
destinationAirport,
|
|
38
|
+
returnAirport,
|
|
39
|
+
destination,
|
|
40
|
+
allowOneWay,
|
|
41
|
+
allowRoundtrip,
|
|
42
|
+
allowOpenJaw
|
|
43
|
+
} = useContext(QSMConfigurationContext);
|
|
27
44
|
const translations = getTranslations(languageCode ?? 'en-GB');
|
|
28
45
|
|
|
29
46
|
useEffect(() => {
|
|
@@ -50,63 +67,112 @@ const QSMContainer: React.FC = () => {
|
|
|
50
67
|
dispatch(setToDate(value.toDate?.toISOString()));
|
|
51
68
|
};
|
|
52
69
|
|
|
70
|
+
const handleTripTypeChange = (value: 'oneway' | 'roundtrip' | 'openjaw') => {
|
|
71
|
+
dispatch(setTripType(value));
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const handleQsmTypeChange = (value: QsmType) => {
|
|
75
|
+
dispatch(setSelectedQsmType(value));
|
|
76
|
+
};
|
|
77
|
+
|
|
53
78
|
const handleSubmit = () => {
|
|
54
79
|
if (!onSubmit) return;
|
|
55
80
|
|
|
56
|
-
const { fromDate, toDate,
|
|
57
|
-
qsmState;
|
|
58
|
-
|
|
59
|
-
const selectedTravelTypeValue = travelTypes.find((t) => t.label === selectedTravelType);
|
|
81
|
+
const { fromDate, toDate, selectedTravelClass, selectedTravelType, selectedNationality, rooms, tripType, adults, kids, babies } = qsmState;
|
|
60
82
|
|
|
83
|
+
const selectedTravelTypeValue = travelTypes?.find((t) => t.label === selectedTravelType);
|
|
61
84
|
const payload = {
|
|
62
85
|
fromDate,
|
|
63
86
|
toDate,
|
|
64
87
|
travelClass: selectedTravelClass,
|
|
65
88
|
travelType: selectedTravelTypeValue,
|
|
66
89
|
nationality: selectedNationality,
|
|
67
|
-
|
|
68
|
-
};
|
|
90
|
+
tripType
|
|
91
|
+
} as any;
|
|
92
|
+
|
|
93
|
+
if (askRooms) {
|
|
94
|
+
payload.rooms = rooms;
|
|
95
|
+
} else {
|
|
96
|
+
payload.travelers = { adults, kids, babies };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Filter out undefined fields before passing to addSearchFieldsToPayload
|
|
100
|
+
const searchFields = [departureAirport, destinationAirport, returnAirport, destination].filter((field): field is BaseFieldConfig => field !== undefined);
|
|
69
101
|
|
|
70
102
|
addSearchFieldsToPayload(payload, searchFields, qsmState);
|
|
71
103
|
onSubmit(payload);
|
|
104
|
+
console.log('Submitted QSM data:', payload);
|
|
72
105
|
};
|
|
73
106
|
|
|
74
|
-
const addSearchFieldsToPayload = (payload: any, fields:
|
|
107
|
+
const addSearchFieldsToPayload = (payload: any, fields: BaseFieldConfig[], state: QSMState) => {
|
|
75
108
|
fields.forEach((field) => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
});
|
|
89
|
-
}
|
|
109
|
+
const fieldKey = field.fieldKey;
|
|
110
|
+
const option = field.options.find((opt) => opt.value === state[fieldKey]);
|
|
111
|
+
payload[fieldKey] = option?.key ?? state[fieldKey];
|
|
112
|
+
|
|
113
|
+
// if (field.type === 'double' && field.fields) {
|
|
114
|
+
// // recursively add each nested field
|
|
115
|
+
// field.fields.forEach((nestedField) => {
|
|
116
|
+
// const key = nestedField.fieldKey;
|
|
117
|
+
// const option = nestedField.options.find((opt) => opt.value === state[key]);
|
|
118
|
+
// payload[key] = option ?? state[key];
|
|
119
|
+
// });
|
|
120
|
+
// }
|
|
90
121
|
});
|
|
91
122
|
};
|
|
92
123
|
|
|
124
|
+
const originDestinationField = useMemo<DoubleFieldConfig | undefined>(() => {
|
|
125
|
+
if (!fromDate || !toDate) return undefined;
|
|
126
|
+
const searchFields = [departureAirport, destinationAirport].filter((field): field is BaseFieldConfig => field !== undefined);
|
|
127
|
+
return {
|
|
128
|
+
type: 'double',
|
|
129
|
+
fieldKey: 'locationGroup',
|
|
130
|
+
showReverse: true,
|
|
131
|
+
fields: searchFields
|
|
132
|
+
};
|
|
133
|
+
}, [fromDate, toDate, departureAirport, destinationAirport]);
|
|
134
|
+
|
|
135
|
+
const openJawReturnDestinationField = useMemo<DoubleFieldConfig | undefined>(() => {
|
|
136
|
+
if (!fromDate || !toDate || !allowOpenJaw || !departureAirport || !returnAirport) return undefined;
|
|
137
|
+
|
|
138
|
+
const mirroredDepartureField: BaseFieldConfig = {
|
|
139
|
+
...departureAirport,
|
|
140
|
+
label: 'Bestemming',
|
|
141
|
+
placeholder: 'Kies bestemming'
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
type: 'double',
|
|
146
|
+
fieldKey: 'openjawLocationGroup',
|
|
147
|
+
showReverse: false,
|
|
148
|
+
disableReturnField: true,
|
|
149
|
+
fields: [returnAirport, mirroredDepartureField]
|
|
150
|
+
};
|
|
151
|
+
}, [fromDate, toDate, departureAirport, returnAirport, allowOpenJaw]);
|
|
152
|
+
|
|
93
153
|
return (
|
|
94
154
|
<div className="qsm">
|
|
95
155
|
<div className="qsm__content">
|
|
96
156
|
<div className="qsm__tabs">
|
|
97
|
-
<button
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
className={`qsm__tab ${qsmType == 'multidestination' ? 'qsm__tab--active' : ''}`}
|
|
160
|
+
onClick={() => handleQsmTypeChange('multidestination')}>
|
|
98
161
|
<span className="qsm__tab__icons">
|
|
99
162
|
<Icon name="ui-location" height={16} />
|
|
100
163
|
</span>
|
|
101
164
|
{translations.QSM.MULTIDESTINATION}
|
|
102
165
|
</button>
|
|
103
|
-
<button type="button" className=
|
|
166
|
+
<button type="button" className={`qsm__tab ${qsmType == 'package' ? 'qsm__tab--active' : ''}`} onClick={() => handleQsmTypeChange('package')}>
|
|
104
167
|
<span className="qsm__tab__icons">
|
|
105
168
|
<Icon name="ui-suitcase" height={16} />
|
|
106
169
|
</span>
|
|
107
170
|
{translations.QSM.PACKAGES}
|
|
108
171
|
</button>
|
|
109
|
-
<button
|
|
172
|
+
<button
|
|
173
|
+
type="button"
|
|
174
|
+
className={`qsm__tab ${qsmType == 'hotel-flight' ? 'qsm__tab--active' : ''}`}
|
|
175
|
+
onClick={() => handleQsmTypeChange('hotel-flight')}>
|
|
110
176
|
<span className="qsm__tab__icons">
|
|
111
177
|
<Icon name="ui-backforward" height={14} />
|
|
112
178
|
+
|
|
@@ -114,37 +180,37 @@ const QSMContainer: React.FC = () => {
|
|
|
114
180
|
</span>
|
|
115
181
|
{translations.QSM.TRANSPORT_HOTEL}
|
|
116
182
|
</button>
|
|
117
|
-
<button type="button" className=
|
|
183
|
+
<button type="button" className={`qsm__tab ${qsmType == 'hotel' ? 'qsm__tab--active' : ''}`} onClick={() => handleQsmTypeChange('hotel')}>
|
|
118
184
|
<span className="qsm__tab__icons">
|
|
119
185
|
<Icon name="ui-bed" height={16} />
|
|
120
186
|
</span>
|
|
121
187
|
{translations.QSM.ACCOMMODATION}
|
|
122
188
|
</button>
|
|
123
|
-
<button type="button" className=
|
|
189
|
+
<button type="button" className={`qsm__tab ${qsmType == 'flight' ? 'qsm__tab--active' : ''}`} onClick={() => handleQsmTypeChange('flight')}>
|
|
124
190
|
<span className="qsm__tab__icons">
|
|
125
191
|
<Icon name="ui-flight" height={16} />
|
|
126
192
|
</span>
|
|
127
193
|
{translations.QSM.TRANSPORTS}
|
|
128
194
|
</button>
|
|
129
|
-
<button type="button" className=
|
|
195
|
+
<button type="button" className={`qsm__tab ${qsmType == 'ticket' ? 'qsm__tab--active' : ''}`} onClick={() => handleQsmTypeChange('ticket')}>
|
|
130
196
|
<span className="qsm__tab__icons">
|
|
131
197
|
<Icon name="ui-ticket" height={16} />
|
|
132
198
|
</span>
|
|
133
199
|
{translations.QSM.TICKET_ONLY}
|
|
134
200
|
</button>
|
|
135
|
-
<button type="button" className=
|
|
201
|
+
<button type="button" className={`qsm__tab ${qsmType == 'car' ? 'qsm__tab--active' : ''}`} onClick={() => handleQsmTypeChange('car')}>
|
|
136
202
|
<span className="qsm__tab__icons">
|
|
137
203
|
<Icon name="ui-car" height={16} />
|
|
138
204
|
</span>
|
|
139
205
|
{translations.QSM.RENT_A_CAR}
|
|
140
206
|
</button>
|
|
141
|
-
<button type="button" className=
|
|
207
|
+
<button type="button" className={`qsm__tab ${qsmType == 'transfers' ? 'qsm__tab--active' : ''}`} onClick={() => handleQsmTypeChange('transfers')}>
|
|
142
208
|
<span className="qsm__tab__icons">
|
|
143
209
|
<Icon name="ui-backforward" height={16} />
|
|
144
210
|
</span>
|
|
145
211
|
{translations.QSM.TRANSFERS}
|
|
146
212
|
</button>
|
|
147
|
-
<button type="button" className=
|
|
213
|
+
<button type="button" className={`qsm__tab ${qsmType == 'cruises' ? 'qsm__tab--active' : ''}`} onClick={() => handleQsmTypeChange('cruises')}>
|
|
148
214
|
<span className="qsm__tab__icons">
|
|
149
215
|
<Icon name="ui-ship" height={16} />
|
|
150
216
|
</span>
|
|
@@ -152,52 +218,108 @@ const QSMContainer: React.FC = () => {
|
|
|
152
218
|
</button>
|
|
153
219
|
</div>
|
|
154
220
|
<div className="qsm__filter">
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
<
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
221
|
+
{qsmType === 'hotel' ||
|
|
222
|
+
(qsmType == 'hotel-flight' && (
|
|
223
|
+
<div className="radiobutton-group qsm__filter__inputgroup">
|
|
224
|
+
<div className="radiobutton">
|
|
225
|
+
<label className="radiobutton__label">
|
|
226
|
+
<input
|
|
227
|
+
type="radio"
|
|
228
|
+
name="numberOfAccommodations"
|
|
229
|
+
// onChange={handleMainBookerChange}
|
|
230
|
+
// onBlur={formik.handleBlur}
|
|
231
|
+
value=""
|
|
232
|
+
checked={true}
|
|
233
|
+
readOnly
|
|
234
|
+
className="radiobutton__input"
|
|
235
|
+
/>
|
|
236
|
+
<span>{translations.QSM.ONE_ACCOMMODATION}</span>
|
|
237
|
+
</label>
|
|
238
|
+
</div>
|
|
239
|
+
<div className="radiobutton">
|
|
240
|
+
<label className="radiobutton__label">
|
|
241
|
+
<input
|
|
242
|
+
type="radio"
|
|
243
|
+
name="numberOfAccommodations"
|
|
244
|
+
// onChange={handleMainBookerChange}
|
|
245
|
+
// onBlur={formik.handleBlur}
|
|
246
|
+
value=""
|
|
247
|
+
className="radiobutton__input"
|
|
248
|
+
disabled={true}
|
|
249
|
+
/>
|
|
250
|
+
<span>{translations.QSM.MULTIPLE_ACCOMMODATIONS}</span>
|
|
251
|
+
</label>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
))}
|
|
255
|
+
{qsmType === 'flight' && (
|
|
256
|
+
<div className="radiobutton-group qsm__filter__inputgroup">
|
|
257
|
+
{allowOneWay && (
|
|
258
|
+
<div className="radiobutton">
|
|
259
|
+
<label className="radiobutton__label">
|
|
260
|
+
<input
|
|
261
|
+
type="radio"
|
|
262
|
+
name="tripType"
|
|
263
|
+
value="oneway"
|
|
264
|
+
checked={tripType === 'oneway'}
|
|
265
|
+
onChange={() => handleTripTypeChange('oneway')}
|
|
266
|
+
className="radiobutton__input"
|
|
267
|
+
/>
|
|
268
|
+
<span>One way</span>
|
|
269
|
+
</label>
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
|
|
273
|
+
{allowRoundtrip && (
|
|
274
|
+
<div className="radiobutton">
|
|
275
|
+
<label className="radiobutton__label">
|
|
276
|
+
<input
|
|
277
|
+
type="radio"
|
|
278
|
+
name="tripType"
|
|
279
|
+
value="roundtrip"
|
|
280
|
+
checked={tripType === 'roundtrip'}
|
|
281
|
+
onChange={() => handleTripTypeChange('roundtrip')}
|
|
282
|
+
className="radiobutton__input"
|
|
283
|
+
/>
|
|
284
|
+
<span>Roundtrip</span>
|
|
285
|
+
</label>
|
|
286
|
+
</div>
|
|
287
|
+
)}
|
|
288
|
+
|
|
289
|
+
{allowOpenJaw && (
|
|
290
|
+
<div className="radiobutton">
|
|
291
|
+
<label className="radiobutton__label">
|
|
292
|
+
<input
|
|
293
|
+
type="radio"
|
|
294
|
+
name="tripType"
|
|
295
|
+
value="openjaw"
|
|
296
|
+
checked={tripType === 'openjaw'}
|
|
297
|
+
onChange={() => handleTripTypeChange('openjaw')}
|
|
298
|
+
className="radiobutton__input"
|
|
299
|
+
/>
|
|
300
|
+
<span>Open Jaw</span>
|
|
301
|
+
</label>
|
|
302
|
+
</div>
|
|
303
|
+
)}
|
|
186
304
|
</div>
|
|
187
|
-
|
|
305
|
+
)}
|
|
188
306
|
<div className="qsm__filter__classgroup">
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
307
|
+
{qsmType !== 'hotel' && qsmType !== 'car' && qsmType !== 'ticket' && qsmType !== 'cruises' && qsmType !== 'transfers' && askTravelClass && (
|
|
308
|
+
<TravelClassPicker />
|
|
309
|
+
)}
|
|
310
|
+
{qsmType !== 'multidestination' && qsmType !== 'car' && qsmType !== 'flight' && qsmType !== 'transfers' && askTravelType && <TravelTypePicker />}
|
|
311
|
+
{askNationality && <TravelNationalityPicker />}
|
|
192
312
|
</div>
|
|
193
313
|
</div>
|
|
194
314
|
<div className="qsm__input-group">
|
|
195
|
-
{
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
315
|
+
{/* TODO, determine which fields to show for what type of QSM */}
|
|
316
|
+
{(qsmType == 'flight' || qsmType == 'hotel-flight') && originDestinationField && (
|
|
317
|
+
<DoubleSearchInputGroup fieldConfig={originDestinationField} showReverse={originDestinationField.showReverse} />
|
|
318
|
+
)}
|
|
319
|
+
{qsmType == 'flight' && tripType == 'openjaw' && openJawReturnDestinationField && (
|
|
320
|
+
<DoubleSearchInputGroup fieldConfig={openJawReturnDestinationField} showReverse={openJawReturnDestinationField.showReverse} />
|
|
321
|
+
)}
|
|
322
|
+
{qsmType == 'hotel' && destination && <SearchInputGroup fieldConfig={destination} />}
|
|
201
323
|
|
|
202
324
|
<Dates value={dateRange} onChange={handleDateChange} />
|
|
203
325
|
|
|
@@ -1,92 +1,25 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { useDispatch, useSelector } from 'react-redux';
|
|
3
3
|
import { QSMRootState } from '../../store/qsm-store';
|
|
4
4
|
import { setFieldValue } from '../../store/qsm-slice';
|
|
5
|
-
import QSMConfigurationContext from '../../qsm-configuration-context';
|
|
6
5
|
import SearchInputGroup from '../search-input-group';
|
|
7
6
|
import { FieldConfig, DoubleFieldConfig } from '../../types';
|
|
8
7
|
|
|
9
8
|
const isDouble = (f: FieldConfig): f is DoubleFieldConfig => f.type === 'double';
|
|
10
9
|
|
|
11
10
|
interface Props {
|
|
12
|
-
|
|
11
|
+
fieldConfig: DoubleFieldConfig;
|
|
13
12
|
showReverse?: boolean;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
const DoubleSearchInputGroup: React.FC<Props> = ({
|
|
17
|
-
const { searchFields, allowOpenJaw = false } = useContext(QSMConfigurationContext);
|
|
15
|
+
const DoubleSearchInputGroup: React.FC<Props> = ({ fieldConfig, showReverse = false }) => {
|
|
18
16
|
const dispatch = useDispatch();
|
|
19
|
-
const cfg = searchFields.find((f) => f.fieldKey === fieldKey);
|
|
20
|
-
if (!cfg || !isDouble(cfg)) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const maybeFields = cfg.fields as unknown as any[];
|
|
25
|
-
const isOpenJaw = allowOpenJaw && maybeFields.length === 4;
|
|
26
|
-
|
|
27
|
-
if (isOpenJaw) {
|
|
28
|
-
const [from1Cfg, to1Cfg, from2Cfg, to2Cfg] = maybeFields;
|
|
29
|
-
|
|
30
|
-
const from1Val = useSelector((s: QSMRootState) => (s.qsm as any)[from1Cfg.fieldKey] ?? '');
|
|
31
|
-
const to2Val = useSelector((s: QSMRootState) => (s.qsm as any)[to2Cfg.fieldKey] ?? '');
|
|
32
|
-
|
|
33
|
-
// keep last "to" in sync with first "from"
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
if (from1Val && from1Val !== to2Val) {
|
|
36
|
-
dispatch(
|
|
37
|
-
setFieldValue({
|
|
38
|
-
fieldKey: to2Cfg.fieldKey,
|
|
39
|
-
value: from1Val
|
|
40
|
-
})
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
if (!from1Val && to2Val) {
|
|
44
|
-
dispatch(
|
|
45
|
-
setFieldValue({
|
|
46
|
-
fieldKey: to2Cfg.fieldKey,
|
|
47
|
-
value: ''
|
|
48
|
-
})
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
}, [from1Val, to2Val, dispatch, to2Cfg.fieldKey]);
|
|
52
17
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
{/* Row 1 : A ➜ B */}
|
|
56
|
-
<div className="qsm__double-input qsm__double-input--splittable">
|
|
57
|
-
<div className="qsm__input-wrapper qsm__input-wrapper--splittable">
|
|
58
|
-
<SearchInputGroup fieldKey={from1Cfg.fieldKey} isDoubleInput />
|
|
59
|
-
</div>
|
|
60
|
-
<div className="qsm__reverse-wrapper">
|
|
61
|
-
<div className="qsm__input-line qsm__input-line--splittable" />
|
|
62
|
-
</div>
|
|
63
|
-
<div className="qsm__input-wrapper qsm__input-wrapper--splittable">
|
|
64
|
-
<SearchInputGroup fieldKey={to1Cfg.fieldKey} isSecondInput isDoubleInput />
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
|
|
68
|
-
{/* Row 2 : C ➜ A (A is readonly) */}
|
|
69
|
-
<div className="qsm__double-input qsm__double-input--splittable u-mt-2">
|
|
70
|
-
<div className="qsm__input-wrapper qsm__input-wrapper--splittable">
|
|
71
|
-
<SearchInputGroup fieldKey={from2Cfg.fieldKey} isDoubleInput />
|
|
72
|
-
</div>
|
|
73
|
-
<div className="qsm__reverse-wrapper">
|
|
74
|
-
<div className="qsm__input-line qsm__input-line--splittable" />
|
|
75
|
-
</div>
|
|
76
|
-
<div className="qsm__input-wrapper qsm__input-wrapper--splittable">
|
|
77
|
-
<SearchInputGroup fieldKey={to2Cfg.fieldKey} isSecondInput isDoubleInput readOnlyForced={Boolean(from1Val)} />
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
</>
|
|
81
|
-
);
|
|
18
|
+
if (!fieldConfig || !isDouble(fieldConfig)) {
|
|
19
|
+
return null;
|
|
82
20
|
}
|
|
83
21
|
|
|
84
|
-
|
|
85
|
-
* ──────────────────────────────────────────────────────────────
|
|
86
|
-
* CLASSIC PATH (2 sub‑fields, original behaviour)
|
|
87
|
-
* ──────────────────────────────────────────────────────────────
|
|
88
|
-
*/
|
|
89
|
-
const [firstCfg, secondCfg] = cfg.fields;
|
|
22
|
+
const [firstCfg, secondCfg] = fieldConfig.fields;
|
|
90
23
|
|
|
91
24
|
const firstSelector = useMemo(() => (s: QSMRootState) => (s.qsm as any)[firstCfg.fieldKey] ?? '', [firstCfg.fieldKey]);
|
|
92
25
|
const secondSelector = useMemo(() => (s: QSMRootState) => (s.qsm as any)[secondCfg.fieldKey] ?? '', [secondCfg.fieldKey]);
|
|
@@ -111,7 +44,7 @@ const DoubleSearchInputGroup: React.FC<Props> = ({ fieldKey, showReverse = false
|
|
|
111
44
|
return (
|
|
112
45
|
<div className="qsm__double-input qsm__double-input--splittable">
|
|
113
46
|
<div className="qsm__input-wrapper qsm__input-wrapper--splittable">
|
|
114
|
-
<SearchInputGroup
|
|
47
|
+
<SearchInputGroup fieldConfig={firstCfg} isDoubleInput />
|
|
115
48
|
</div>
|
|
116
49
|
|
|
117
50
|
{showReverse ? (
|
|
@@ -130,7 +63,13 @@ const DoubleSearchInputGroup: React.FC<Props> = ({ fieldKey, showReverse = false
|
|
|
130
63
|
)}
|
|
131
64
|
|
|
132
65
|
<div className="qsm__input-wrapper qsm__input-wrapper--splittable">
|
|
133
|
-
<SearchInputGroup
|
|
66
|
+
<SearchInputGroup
|
|
67
|
+
fieldConfig={secondCfg}
|
|
68
|
+
isSecondInput
|
|
69
|
+
isDoubleInput
|
|
70
|
+
isDisabled={fieldConfig.disableReturnField}
|
|
71
|
+
readOnlyForced={fieldConfig.disableReturnField}
|
|
72
|
+
/>
|
|
134
73
|
</div>
|
|
135
74
|
</div>
|
|
136
75
|
);
|
|
@@ -15,14 +15,25 @@ import DateRangePicker from '../date-range-picker';
|
|
|
15
15
|
import TravelInput from '../travel-input';
|
|
16
16
|
import { format } from 'date-fns';
|
|
17
17
|
import QSMConfigurationContext from '../../qsm-configuration-context';
|
|
18
|
-
import { BaseFieldConfig,
|
|
18
|
+
import { BaseFieldConfig, TypeaheadOption } from '../../types';
|
|
19
19
|
import { getTranslations } from '../../../shared/utils/localization-util';
|
|
20
20
|
|
|
21
21
|
const MobileFilterModal: React.FC = () => {
|
|
22
|
-
const { datesIcon, languageCode
|
|
22
|
+
const { datesIcon, languageCode } = useContext(QSMConfigurationContext);
|
|
23
23
|
const translations = getTranslations(languageCode ?? 'en-GB');
|
|
24
24
|
const dispatch = useDispatch();
|
|
25
|
-
const {
|
|
25
|
+
const {
|
|
26
|
+
mobileFilterType,
|
|
27
|
+
mobileDatePickerMode,
|
|
28
|
+
activeSearchFieldProps,
|
|
29
|
+
fromDate,
|
|
30
|
+
toDate,
|
|
31
|
+
searchResults,
|
|
32
|
+
departureAirport,
|
|
33
|
+
destinationAirport,
|
|
34
|
+
returnAirport,
|
|
35
|
+
destination
|
|
36
|
+
} = useSelector((state: QSMRootState) => state.qsm);
|
|
26
37
|
|
|
27
38
|
const [inputValue, setInputValue] = useState('');
|
|
28
39
|
|
|
@@ -45,24 +56,20 @@ const MobileFilterModal: React.FC = () => {
|
|
|
45
56
|
dispatch(closeMobileFilter());
|
|
46
57
|
};
|
|
47
58
|
|
|
48
|
-
const findConfig = (all:
|
|
59
|
+
const findConfig = (all: BaseFieldConfig[], key: string): BaseFieldConfig | undefined => {
|
|
49
60
|
for (const config of all) {
|
|
50
|
-
if (config.
|
|
61
|
+
if (config.fieldKey === key) {
|
|
51
62
|
return config;
|
|
52
63
|
}
|
|
53
|
-
if (config.type === 'double') {
|
|
54
|
-
const field = config.fields.find((x) => x.fieldKey === key);
|
|
55
|
-
if (field) return field;
|
|
56
|
-
}
|
|
57
64
|
}
|
|
58
65
|
return undefined;
|
|
59
66
|
};
|
|
60
67
|
|
|
61
68
|
const config = useMemo<BaseFieldConfig | undefined>(() => {
|
|
62
69
|
if (!activeSearchFieldProps) return undefined;
|
|
63
|
-
|
|
70
|
+
const searchFields = [departureAirport, destinationAirport, returnAirport, destination].filter((field): field is BaseFieldConfig => field !== undefined);
|
|
64
71
|
return findConfig(searchFields, activeSearchFieldProps.fieldKey);
|
|
65
|
-
}, [
|
|
72
|
+
}, [departureAirport, destinationAirport, returnAirport, destination, activeSearchFieldProps]);
|
|
66
73
|
|
|
67
74
|
const match = useCallback(
|
|
68
75
|
(input: string) => {
|
|
@@ -10,9 +10,13 @@ interface SearchInputProps {
|
|
|
10
10
|
label: string;
|
|
11
11
|
isSecondInput?: boolean;
|
|
12
12
|
isDoubleInput?: boolean;
|
|
13
|
+
isDisabled?: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
const SearchInput: React.FC<SearchInputProps> = ({ searchResults, onOptionSelect, highlightTarget, label, isSecondInput, isDoubleInput }) => {
|
|
16
|
+
const SearchInput: React.FC<SearchInputProps> = ({ searchResults, onOptionSelect, highlightTarget, label, isSecondInput, isDoubleInput, isDisabled }) => {
|
|
17
|
+
if (isDisabled) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
16
20
|
const highlightMatch = (option: TypeaheadOption, highlight: string) => {
|
|
17
21
|
if (!highlight) {
|
|
18
22
|
return option.value;
|
|
@@ -47,7 +51,10 @@ const SearchInput: React.FC<SearchInputProps> = ({ searchResults, onOptionSelect
|
|
|
47
51
|
<div
|
|
48
52
|
key={index}
|
|
49
53
|
className="qsm__double-input-option"
|
|
50
|
-
onMouseDown={(e) =>
|
|
54
|
+
onMouseDown={(e) => {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
e.stopPropagation();
|
|
57
|
+
}}
|
|
51
58
|
onClick={(e) => {
|
|
52
59
|
e.stopPropagation();
|
|
53
60
|
onOptionSelect(option);
|