@qite/tide-booking-component 1.4.97 → 1.4.99
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/index.js +1423 -975
- package/build/build-cjs/src/search-results/components/itinerary/index.d.ts +2 -0
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +9 -10
- package/build/build-cjs/src/search-results/types.d.ts +24 -1
- package/build/build-cjs/src/search-results/utils/packaging-utils.d.ts +7 -0
- package/build/build-cjs/src/search-results/utils/query-utils.d.ts +11 -0
- package/build/build-cjs/src/shared/components/flyin/accommodation-flyin.d.ts +1 -2
- package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +4 -0
- package/build/build-cjs/src/shared/utils/localization-util.d.ts +1 -0
- package/build/build-esm/index.js +1419 -965
- package/build/build-esm/src/search-results/components/itinerary/index.d.ts +2 -0
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +9 -10
- package/build/build-esm/src/search-results/types.d.ts +24 -1
- package/build/build-esm/src/search-results/utils/packaging-utils.d.ts +7 -0
- package/build/build-esm/src/search-results/utils/query-utils.d.ts +11 -0
- package/build/build-esm/src/shared/components/flyin/accommodation-flyin.d.ts +1 -2
- package/build/build-esm/src/shared/components/flyin/flyin.d.ts +4 -0
- package/build/build-esm/src/shared/utils/localization-util.d.ts +1 -0
- package/package.json +2 -2
- package/src/qsm/components/search-input-group/index.tsx +0 -1
- package/src/search-results/components/itinerary/index.tsx +331 -234
- package/src/search-results/components/search-results-container/search-results-container.tsx +444 -383
- package/src/search-results/store/search-results-slice.ts +22 -10
- package/src/search-results/types.ts +26 -1
- package/src/search-results/utils/packaging-utils.ts +75 -0
- package/src/search-results/utils/query-utils.ts +152 -0
- package/src/shared/components/flyin/accommodation-flyin.tsx +10 -11
- package/src/shared/components/flyin/flyin.tsx +52 -4
- package/styles/components/_flyin.scss +25 -0
- package/styles/components/_search.scss +28 -1
|
@@ -1,309 +1,406 @@
|
|
|
1
|
-
import React, { useContext } from 'react';
|
|
2
|
-
import Icon from '../icon';
|
|
3
|
-
import Spinner from '../spinner/spinner';
|
|
4
|
-
import { SearchResultsRootState } from '../../store/search-results-store';
|
|
5
|
-
import { useSelector } from 'react-redux';
|
|
6
|
-
import { first, isEmpty, last } from 'lodash';
|
|
7
|
-
import { formatPrice, getTranslations } from '../../../shared/utils/localization-util';
|
|
1
|
+
import React, { useContext, useMemo } from 'react';
|
|
8
2
|
import { differenceInCalendarDays, format } from 'date-fns';
|
|
9
|
-
import {
|
|
3
|
+
import { first, groupBy, isEmpty, last } from 'lodash';
|
|
4
|
+
import { PackagingEntryLine, PackagingEntryLineFlightLine } from '@qite/tide-client';
|
|
5
|
+
import Icon from '../icon';
|
|
10
6
|
import SearchResultsConfigurationContext from '../../search-results-configuration-context';
|
|
7
|
+
import { formatPrice, getTranslations } from '../../../shared/utils/localization-util';
|
|
8
|
+
import { useSelector } from 'react-redux';
|
|
9
|
+
import { SearchResultsRootState } from '../../store/search-results-store';
|
|
10
|
+
import { ACCOMMODATION_SERVICE_TYPE, FLIGHT_SERVICE_TYPE } from '../../utils/query-utils';
|
|
11
11
|
|
|
12
12
|
interface ItineraryProps {
|
|
13
13
|
isOpen: boolean;
|
|
14
14
|
handleSetIsOpen: () => void;
|
|
15
15
|
isLoading?: boolean;
|
|
16
|
+
onEditAccommodation?: (segments: PackagingEntryLine[]) => void;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
const config = first(flight?.metaDatas)?.configuration;
|
|
23
|
-
if (!config) return '';
|
|
19
|
+
const getFlightLines = (flight?: PackagingEntryLine): PackagingEntryLineFlightLine[] => {
|
|
20
|
+
return flight?.flightInformation?.flightLines ?? [];
|
|
21
|
+
};
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const getDepartureTime = (flight?: PackagingEntryLine): string => {
|
|
24
|
+
const firstLine = first(getFlightLines(flight));
|
|
25
|
+
if (!firstLine?.departureTime) return '';
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
} catch {
|
|
30
|
-
return '';
|
|
31
|
-
}
|
|
27
|
+
return firstLine.departureTime.slice(0, 5);
|
|
32
28
|
};
|
|
33
29
|
|
|
34
|
-
const getArrivalTime = (flight?:
|
|
35
|
-
|
|
30
|
+
const getArrivalTime = (flight?: PackagingEntryLine): string => {
|
|
31
|
+
const lastLine = last(getFlightLines(flight));
|
|
32
|
+
if (!lastLine?.arrivalTime) return '';
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (!config) return '';
|
|
34
|
+
return lastLine.arrivalTime.slice(0, 5);
|
|
35
|
+
};
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
const getDuration = (flight?: PackagingEntryLine): string => {
|
|
38
|
+
const lines = getFlightLines(flight);
|
|
39
|
+
if (!lines.length) return '';
|
|
43
40
|
|
|
44
|
-
|
|
45
|
-
} catch {
|
|
46
|
-
return '';
|
|
47
|
-
}
|
|
48
|
-
};
|
|
41
|
+
const totalTicks = lines.reduce((sum, line) => sum + (line.durationInTicks ?? 0), 0) || 0;
|
|
49
42
|
|
|
50
|
-
|
|
51
|
-
if (isEmpty(flight?.metaDatas)) return '';
|
|
43
|
+
if (!totalTicks) return '';
|
|
52
44
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
const seconds = totalTicks / 10_000_000;
|
|
46
|
+
const hours = Math.floor(seconds / 3600);
|
|
47
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
56
48
|
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
return `${hours}h ${minutes.toString().padStart(2, '0')}m`;
|
|
50
|
+
};
|
|
59
51
|
|
|
60
|
-
|
|
52
|
+
const numberOfNights = (segment: PackagingEntryLine) => {
|
|
53
|
+
return differenceInCalendarDays(new Date(segment.to), new Date(segment.from));
|
|
54
|
+
};
|
|
61
55
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
const getSegmentIcon = (segment: PackagingEntryLine) => {
|
|
57
|
+
switch (segment.serviceType) {
|
|
58
|
+
case 3:
|
|
59
|
+
return (
|
|
60
|
+
<div className="search__filter__itinerary__segment-badge search__filter__itinerary__segment-badge--secondary">
|
|
61
|
+
<Icon name="ui-bed" width={15} height={15} />
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
case 4:
|
|
65
|
+
return (
|
|
66
|
+
<div className="search__filter__itinerary__segment-badge search__filter__itinerary__segment-badge--secondary">
|
|
67
|
+
<Icon name="ui-ticket" width={15} height={15} />
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
case 11:
|
|
71
|
+
return (
|
|
72
|
+
<div className="search__filter__itinerary__segment-badge search__filter__itinerary__segment-badge--secondary">
|
|
73
|
+
<Icon name="ui-ship" width={15} height={15} />
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
case 13:
|
|
77
|
+
case 17:
|
|
78
|
+
case 22:
|
|
79
|
+
return (
|
|
80
|
+
<div className="search__filter__itinerary__transport-badge">
|
|
81
|
+
<Icon name="ui-car" width={15} height={15} />
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
default:
|
|
85
|
+
return (
|
|
86
|
+
<div className="search__filter__itinerary__segment-badge">
|
|
87
|
+
<Icon name="ui-location" width={15} height={15} />
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
65
92
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return
|
|
93
|
+
const canEdit = (segment: PackagingEntryLine) => {
|
|
94
|
+
if (segment.serviceType === ACCOMMODATION_SERVICE_TYPE) {
|
|
95
|
+
return true;
|
|
69
96
|
}
|
|
97
|
+
return false;
|
|
70
98
|
};
|
|
71
99
|
|
|
72
|
-
const
|
|
73
|
-
return
|
|
100
|
+
const getSegmentTitle = (segment: PackagingEntryLine) => {
|
|
101
|
+
return segment.productName ?? segment.accommodationName;
|
|
74
102
|
};
|
|
75
103
|
|
|
76
|
-
const Itinerary: React.FC<ItineraryProps> = ({ isOpen, handleSetIsOpen, isLoading }) => {
|
|
104
|
+
const Itinerary: React.FC<ItineraryProps> = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation }) => {
|
|
77
105
|
const context = useContext(SearchResultsConfigurationContext);
|
|
78
106
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
107
|
+
const { editablePackagingEntry } = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
108
|
+
|
|
109
|
+
const packagingEntry = editablePackagingEntry ?? context?.packagingEntry;
|
|
110
|
+
|
|
111
|
+
const sortedLines = useMemo(() => {
|
|
112
|
+
return [...(packagingEntry?.lines ?? [])].sort((a, b) => {
|
|
113
|
+
const orderA = a.order ?? Infinity;
|
|
114
|
+
const orderB = b.order ?? Infinity;
|
|
79
115
|
|
|
80
|
-
|
|
116
|
+
// First sort by order
|
|
117
|
+
if (orderA !== orderB) {
|
|
118
|
+
return orderA - orderB;
|
|
119
|
+
}
|
|
81
120
|
|
|
82
|
-
|
|
121
|
+
// Fallback to date
|
|
122
|
+
return new Date(a.from).getTime() - new Date(b.from).getTime();
|
|
123
|
+
});
|
|
124
|
+
}, [packagingEntry]);
|
|
125
|
+
|
|
126
|
+
if (!packagingEntry) {
|
|
83
127
|
return null;
|
|
84
128
|
}
|
|
85
129
|
|
|
86
|
-
const firstEntryLine = first(
|
|
87
|
-
const lastEntryLine = last(
|
|
130
|
+
const firstEntryLine = first(sortedLines);
|
|
131
|
+
const lastEntryLine = last(sortedLines);
|
|
132
|
+
|
|
133
|
+
const accommodationLine = sortedLines.find((line) => line.serviceType === ACCOMMODATION_SERVICE_TYPE) ?? firstEntryLine;
|
|
134
|
+
|
|
135
|
+
const country = accommodationLine?.country?.name ?? firstEntryLine?.country?.name;
|
|
136
|
+
const location = accommodationLine?.location?.name ?? accommodationLine?.oord?.name ?? accommodationLine?.region?.name ?? firstEntryLine?.location?.name;
|
|
88
137
|
|
|
89
|
-
const
|
|
90
|
-
const
|
|
138
|
+
const flightSegments = sortedLines.filter((item) => item.serviceType === FLIGHT_SERVICE_TYPE);
|
|
139
|
+
const outboundFlight = first(flightSegments);
|
|
140
|
+
const returnFlight = flightSegments.length > 1 ? last(flightSegments) : undefined;
|
|
91
141
|
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
|
|
142
|
+
const otherSegments = sortedLines.filter((item) => item.serviceType !== FLIGHT_SERVICE_TYPE);
|
|
143
|
+
const grouped = groupBy(otherSegments, (segment) => {
|
|
144
|
+
return `${segment.productCode}-${segment.from}-${segment.to}`;
|
|
145
|
+
});
|
|
146
|
+
const groupedOtherSegments = Object.entries(grouped).map(([key, segments]) => ({
|
|
147
|
+
key,
|
|
148
|
+
segments
|
|
149
|
+
}));
|
|
150
|
+
console.log('otherSegments', otherSegments);
|
|
151
|
+
console.log('groupedOtherSegments', groupedOtherSegments);
|
|
152
|
+
|
|
153
|
+
const numberOfPax = packagingEntry.pax?.length || 1;
|
|
154
|
+
const totalPrice = packagingEntry.price || 0;
|
|
155
|
+
const pricePerPerson = totalPrice / numberOfPax;
|
|
95
156
|
|
|
96
157
|
return (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
<div className="search__filter-row-flex-title">
|
|
109
|
-
<p className="search__filter-small-title">{translations.SRP.ITINERARY_TITLE}</p>
|
|
110
|
-
</div>
|
|
158
|
+
<div className={`search__filters--modal ${isOpen ? 'is-open' : ''}`}>
|
|
159
|
+
<div className="search__filters--background" onClick={handleSetIsOpen}></div>
|
|
160
|
+
|
|
161
|
+
<button className="search__filters--close" onClick={handleSetIsOpen}>
|
|
162
|
+
<Icon name="ui-close" height={24} />
|
|
163
|
+
</button>
|
|
164
|
+
|
|
165
|
+
<div className="search__filters">
|
|
166
|
+
<div className="search__filter-row search__filter__header">
|
|
167
|
+
<div className="search__filter-row-flex-title">
|
|
168
|
+
<p className="search__filter-small-title">{translations.SRP.ITINERARY_TITLE}</p>
|
|
111
169
|
</div>
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
)}
|
|
125
|
-
<h4 className="search__filter__image__title">{(location || '') + (location && country ? ' - ' : '') + (country || '')}</h4>
|
|
126
|
-
</div>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<>
|
|
173
|
+
<div className="search__filter-group">
|
|
174
|
+
{context?.destinationImage && (
|
|
175
|
+
<div className="search__filter__image__wrapper">
|
|
176
|
+
<img src={context.destinationImage.url} alt={context.destinationImage.alt} className="search__filter__image" />
|
|
177
|
+
|
|
178
|
+
{packagingEntry?.dossierNumber && (
|
|
179
|
+
<span className="search__filter__image__text">
|
|
180
|
+
{translations.SRP.DOSSIER_NUMBER}: {packagingEntry.dossierNumber}
|
|
181
|
+
</span>
|
|
127
182
|
)}
|
|
183
|
+
|
|
184
|
+
<h4 className="search__filter__image__title">{(location || '') + (location && country ? ' - ' : '') + (country || '')}</h4>
|
|
128
185
|
</div>
|
|
186
|
+
)}
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div className="search__filter__prices">
|
|
190
|
+
<div className="search__filter__prices__wrapper">
|
|
191
|
+
<h3 className="search__filter__prices--amount">{formatPrice(pricePerPerson, 'EUR')}</h3>
|
|
192
|
+
<p>{translations.SRP.PACKAGE_PRICE_PER_PERSON}</p>
|
|
193
|
+
<p>
|
|
194
|
+
<strong>
|
|
195
|
+
({formatPrice(totalPrice, 'EUR')} {translations.SRP.TOTAL})
|
|
196
|
+
</strong>
|
|
197
|
+
</p>
|
|
198
|
+
</div>
|
|
199
|
+
<button className="cta">{translations.QSM.CONFIRM}</button>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<div className="search__filter__itinerary">
|
|
203
|
+
<p>{translations.SRP.DAY_BY_DAY}</p>
|
|
129
204
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
205
|
+
{firstEntryLine && (
|
|
206
|
+
<div className="search__filter__itinerary__country">
|
|
207
|
+
<div className="search__filter__itinerary__country-icon">
|
|
208
|
+
<Icon name="ui-flag" width={17.5} height={20} />
|
|
209
|
+
</div>
|
|
210
|
+
<div className="search__filter__itinerary__country-content">
|
|
136
211
|
<p>
|
|
137
|
-
<strong>
|
|
138
|
-
({formatPrice(entry?.sellingPrice || 0, entry?.currencyCode || 'EUR')} {translations.SRP.TOTAL})
|
|
139
|
-
</strong>
|
|
212
|
+
{format(new Date(firstEntryLine.from), 'EEE. d MMM yyyy')} - <strong>{translations.SRP.START}</strong>
|
|
140
213
|
</p>
|
|
141
214
|
</div>
|
|
142
|
-
<button className="cta">{translations.QSM.CONFIRM}</button>
|
|
143
215
|
</div>
|
|
216
|
+
)}
|
|
217
|
+
|
|
218
|
+
{outboundFlight && (
|
|
219
|
+
<div className="search__filter__itinerary__transport">
|
|
220
|
+
<div className="search__filter__itinerary__transport-timeline">
|
|
221
|
+
<div className="search__filter__itinerary__transport-timeline-line"></div>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div className="search__filter__itinerary__transport-item">
|
|
225
|
+
<div className="search__filter__itinerary__transport-date">
|
|
226
|
+
<p className="search__filter__itinerary__transport-date-date">
|
|
227
|
+
<strong>{format(new Date(outboundFlight.from), 'd')}</strong>
|
|
228
|
+
</p>
|
|
229
|
+
<p>{format(new Date(outboundFlight.from), 'MMM')}</p>
|
|
230
|
+
</div>
|
|
144
231
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
<div className="search__filter__itinerary__country">
|
|
148
|
-
<div className="search__filter__itinerary__country-icon">
|
|
149
|
-
<Icon name="ui-flag" width={17.5} height={20} />
|
|
232
|
+
<div className="search__filter__itinerary__transport-badge">
|
|
233
|
+
<Icon name="ui-plane" height={15} />
|
|
150
234
|
</div>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
235
|
+
|
|
236
|
+
<div className="search__filter__itinerary__transport-details">
|
|
237
|
+
<h6>{outboundFlight.productName}</h6>
|
|
238
|
+
<p className="search__filter__itinerary__transport-details-plane">
|
|
239
|
+
<span>
|
|
240
|
+
<Icon name="ui-plane-depart" height={14} /> <strong>{getDepartureTime(outboundFlight)}</strong>
|
|
241
|
+
</span>{' '}
|
|
242
|
+
-{' '}
|
|
243
|
+
<span>
|
|
244
|
+
<Icon name="ui-plane-arrive" height={14} /> <strong>{getArrivalTime(outboundFlight)}</strong>
|
|
245
|
+
</span>
|
|
246
|
+
</p>
|
|
247
|
+
<p className="search__filter__itinerary__transport-details-time">
|
|
248
|
+
<span>
|
|
249
|
+
<Icon name="ui-clock" height={20} /> {getDuration(outboundFlight)}
|
|
250
|
+
</span>
|
|
154
251
|
</p>
|
|
155
252
|
</div>
|
|
156
253
|
</div>
|
|
254
|
+
</div>
|
|
255
|
+
)}
|
|
157
256
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
<div className="search__filter__itinerary__transport-date">
|
|
165
|
-
<p className="search__filter__itinerary__transport-date-date">
|
|
166
|
-
<strong>{format(new Date(outboundFlight?.startDate || ''), 'd')}</strong>
|
|
167
|
-
</p>
|
|
168
|
-
<p>{format(new Date(outboundFlight?.startDate || ''), 'MMM')}</p>
|
|
169
|
-
</div>
|
|
170
|
-
<div className="search__filter__itinerary__transport-badge">
|
|
171
|
-
<Icon name="ui-plane" height={15} />
|
|
172
|
-
</div>
|
|
173
|
-
<div className="search__filter__itinerary__transport-details">
|
|
174
|
-
<h6>{outboundFlight.productName}</h6>
|
|
175
|
-
<p className="search__filter__itinerary__transport-details-plane">
|
|
176
|
-
<span>
|
|
177
|
-
<Icon name="ui-plane-depart" height={14} /> <strong>{getDepartureTime(outboundFlight)}</strong>
|
|
178
|
-
</span>{' '}
|
|
179
|
-
-{' '}
|
|
180
|
-
<span>
|
|
181
|
-
<Icon name="ui-plane-arrive" height={14} /> <strong>{getArrivalTime(outboundFlight)}</strong>
|
|
182
|
-
</span>
|
|
183
|
-
</p>
|
|
184
|
-
<p className="search__filter__itinerary__transport-details-time">
|
|
185
|
-
<span>
|
|
186
|
-
<Icon name="ui-clock" height={20} /> {getDuration(outboundFlight)}
|
|
187
|
-
</span>
|
|
188
|
-
</p>
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
</div>
|
|
192
|
-
)}
|
|
193
|
-
{!isEmpty(otherSegments) && (
|
|
257
|
+
{!isEmpty(groupedOtherSegments) &&
|
|
258
|
+
groupedOtherSegments.map((group) => {
|
|
259
|
+
const firstSegment = first(group.segments);
|
|
260
|
+
if (!firstSegment) return null;
|
|
261
|
+
|
|
262
|
+
return (
|
|
194
263
|
<div className="search__filter__itinerary__segments">
|
|
195
|
-
|
|
196
|
-
<div className="
|
|
197
|
-
<div className="search__filter__itinerary__segments-timeline">
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
264
|
+
<div className="search__filter__itinerary__segments__wrapper" key={group.key}>
|
|
265
|
+
<div className="search__filter__itinerary__segments-timeline">
|
|
266
|
+
<div className="search__filter__itinerary__segments-timeline-line"></div>
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
<div className="search__filter__itinerary__segment">
|
|
270
|
+
<div className="search__filter__itinerary__segment-item search__filter__itinerary__segment-item--start">
|
|
271
|
+
<div className="search__filter__itinerary__transport-date">
|
|
272
|
+
<p className="search__filter__itinerary__transport-date-date">
|
|
273
|
+
<strong>{format(new Date(firstSegment.from), 'd')}</strong>
|
|
274
|
+
</p>
|
|
275
|
+
<p>{format(new Date(firstSegment.from), 'MMM')}</p>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<div className="search__filter__itinerary__segment-badge">
|
|
279
|
+
<Icon name="ui-location" width={15} height={15} />
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
<div className="search__filter__itinerary__segment-details">
|
|
283
|
+
<h6>
|
|
284
|
+
{firstSegment.location?.name || firstSegment.oord?.name || firstSegment.region?.name}
|
|
285
|
+
{(firstSegment.location?.name || firstSegment.oord?.name || firstSegment.region?.name) && firstSegment.country?.name ? ', ' : ''}
|
|
286
|
+
{firstSegment.country?.name}
|
|
287
|
+
</h6>
|
|
288
|
+
<p className="search__filter__itinerary__segment-details-text">
|
|
289
|
+
{format(new Date(firstSegment.from), 'EEE. d MMM yyyy')}
|
|
290
|
+
> {format(new Date(firstSegment.to), 'EEE. d MMM yyyy')}
|
|
291
|
+
</p>
|
|
220
292
|
</div>
|
|
221
293
|
</div>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
<div className="search__filter__itinerary__segment">
|
|
297
|
+
<div
|
|
298
|
+
className={`search__filter__itinerary__segment-item ${
|
|
299
|
+
canEdit(firstSegment) ? 'search__filter__itinerary__segment-item--editable' : ''
|
|
300
|
+
}`}
|
|
301
|
+
onClick={() => {
|
|
302
|
+
if (canEdit(firstSegment) && onEditAccommodation) {
|
|
303
|
+
onEditAccommodation(group.segments);
|
|
304
|
+
}
|
|
305
|
+
}}>
|
|
306
|
+
<div className="search__filter__itinerary__segment-date search__filter__itinerary__segment-nights">
|
|
307
|
+
{firstSegment.serviceType === ACCOMMODATION_SERVICE_TYPE && (
|
|
308
|
+
<>
|
|
226
309
|
<p className="search__filter__itinerary__segment-date-date">
|
|
227
|
-
<strong>{numberOfNights(
|
|
310
|
+
<strong>{numberOfNights(firstSegment)}</strong>
|
|
228
311
|
</p>
|
|
229
312
|
<Icon name="ui-moon" width={16} height={16} />
|
|
230
|
-
|
|
313
|
+
</>
|
|
231
314
|
)}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
</
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
{getSegmentIcon(firstSegment)}
|
|
318
|
+
|
|
319
|
+
<div className="search__filter__itinerary__segment-details">
|
|
320
|
+
<h6>{getSegmentTitle(firstSegment)}</h6>
|
|
321
|
+
{firstSegment.serviceType === ACCOMMODATION_SERVICE_TYPE &&
|
|
322
|
+
group.segments.map((segment, index) => (
|
|
323
|
+
<React.Fragment key={segment.guid}>
|
|
324
|
+
<strong>
|
|
325
|
+
{translations.SHARED.ROOM} {index + 1}
|
|
326
|
+
</strong>
|
|
327
|
+
{segment.productName && segment.accommodationName && (
|
|
328
|
+
<div className="search__filter__itinerary__segment-details__room">
|
|
329
|
+
<Icon name="ui-bed" width={12} height={12} />
|
|
330
|
+
{segment.accommodationName}
|
|
331
|
+
</div>
|
|
332
|
+
)}
|
|
333
|
+
{segment.regimeName && (
|
|
334
|
+
<div className="search__filter__itinerary__segment-details__room">
|
|
335
|
+
<Icon name="ui-utensils" width={12} height={12} />
|
|
336
|
+
{segment.regimeName}
|
|
337
|
+
</div>
|
|
338
|
+
)}
|
|
339
|
+
</React.Fragment>
|
|
340
|
+
))}
|
|
248
341
|
</div>
|
|
249
342
|
</div>
|
|
250
343
|
</div>
|
|
251
|
-
|
|
344
|
+
</div>
|
|
252
345
|
</div>
|
|
253
|
-
)
|
|
346
|
+
);
|
|
347
|
+
})}
|
|
254
348
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
<div className="search__filter__itinerary__transport-badge">
|
|
268
|
-
<Icon name="ui-plane" height={15} />
|
|
269
|
-
</div>
|
|
270
|
-
<div className="search__filter__itinerary__transport-details">
|
|
271
|
-
<h6>{returnFlight?.productName}</h6>
|
|
272
|
-
<p className="search__filter__itinerary__transport-details-plane">
|
|
273
|
-
<span>
|
|
274
|
-
<Icon name="ui-plane-depart" height={14} /> <strong>{getDepartureTime(returnFlight)}</strong>
|
|
275
|
-
</span>{' '}
|
|
276
|
-
-{' '}
|
|
277
|
-
<span>
|
|
278
|
-
<Icon name="ui-plane-arrive" height={14} /> <strong>{getArrivalTime(returnFlight)}</strong>
|
|
279
|
-
</span>
|
|
280
|
-
</p>
|
|
281
|
-
<p className="search__filter__itinerary__transport-details-time">
|
|
282
|
-
<span>
|
|
283
|
-
<Icon name="ui-clock" height={20} /> {getDuration(returnFlight)}
|
|
284
|
-
</span>
|
|
285
|
-
</p>
|
|
286
|
-
</div>
|
|
287
|
-
</div>
|
|
349
|
+
{returnFlight && returnFlight !== outboundFlight && (
|
|
350
|
+
<div className="search__filter__itinerary__transport">
|
|
351
|
+
<div className="search__filter__itinerary__transport-timeline">
|
|
352
|
+
<div className="search__filter__itinerary__transport-timeline-line"></div>
|
|
353
|
+
</div>
|
|
354
|
+
|
|
355
|
+
<div className="search__filter__itinerary__transport-item">
|
|
356
|
+
<div className="search__filter__itinerary__transport-date">
|
|
357
|
+
<p className="search__filter__itinerary__transport-date-date">
|
|
358
|
+
<strong>{format(new Date(returnFlight.from), 'd')}</strong>
|
|
359
|
+
</p>
|
|
360
|
+
<p>{format(new Date(returnFlight.from), 'MMM')}</p>
|
|
288
361
|
</div>
|
|
289
|
-
)}
|
|
290
362
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
<Icon name="ui-flag" width={17.5} height={20} />
|
|
363
|
+
<div className="search__filter__itinerary__transport-badge">
|
|
364
|
+
<Icon name="ui-plane" height={15} />
|
|
294
365
|
</div>
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
366
|
+
|
|
367
|
+
<div className="search__filter__itinerary__transport-details">
|
|
368
|
+
<h6>{returnFlight.productName}</h6>
|
|
369
|
+
<p className="search__filter__itinerary__transport-details-plane">
|
|
370
|
+
<span>
|
|
371
|
+
<Icon name="ui-plane-depart" height={14} /> <strong>{getDepartureTime(returnFlight)}</strong>
|
|
372
|
+
</span>{' '}
|
|
373
|
+
-{' '}
|
|
374
|
+
<span>
|
|
375
|
+
<Icon name="ui-plane-arrive" height={14} /> <strong>{getArrivalTime(returnFlight)}</strong>
|
|
376
|
+
</span>
|
|
377
|
+
</p>
|
|
378
|
+
<p className="search__filter__itinerary__transport-details-time">
|
|
379
|
+
<span>
|
|
380
|
+
<Icon name="ui-clock" height={20} /> {getDuration(returnFlight)}
|
|
381
|
+
</span>
|
|
298
382
|
</p>
|
|
299
383
|
</div>
|
|
300
384
|
</div>
|
|
301
385
|
</div>
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
386
|
+
)}
|
|
387
|
+
|
|
388
|
+
{lastEntryLine && (
|
|
389
|
+
<div className="search__filter__itinerary__country">
|
|
390
|
+
<div className="search__filter__itinerary__country-icon">
|
|
391
|
+
<Icon name="ui-flag" width={17.5} height={20} />
|
|
392
|
+
</div>
|
|
393
|
+
<div className="search__filter__itinerary__country-content">
|
|
394
|
+
<p>
|
|
395
|
+
{format(new Date(lastEntryLine.to), 'EEE. d MMM yyyy')} - <strong>{translations.SRP.END}</strong>
|
|
396
|
+
</p>
|
|
397
|
+
</div>
|
|
398
|
+
</div>
|
|
399
|
+
)}
|
|
400
|
+
</div>
|
|
401
|
+
</>
|
|
305
402
|
</div>
|
|
306
|
-
|
|
403
|
+
</div>
|
|
307
404
|
);
|
|
308
405
|
};
|
|
309
406
|
|