import React, { useContext, useMemo } from 'react';
import { differenceInCalendarDays, format } from 'date-fns';
import { first, groupBy, isEmpty, last } from 'lodash';
import { PackagingEntryLine, PackagingEntryLineFlightLine } from '@qite/tide-client';
import Icon from '../../../shared/components/icon';
import SearchResultsConfigurationContext from '../../search-results-configuration-context';
import { formatPrice, getDateOnlyTime, getTranslations } from '../../../shared/utils/localization-util';
import { useDispatch, useSelector } from 'react-redux';
import { SearchResultsRootState } from '../../store/search-results-store';
import { ACCOMMODATION_SERVICE_TYPE, FLIGHT_SERVICE_TYPE } from '../../utils/query-utils';
import Spinner from '../spinner/spinner';
import { setBookPackagingEntry } from '../../store/search-results-slice';
interface ItineraryProps {
isOpen: boolean;
handleSetIsOpen: () => void;
isLoading?: boolean;
onEditAccommodation?: (segments: PackagingEntryLine[]) => void;
}
const getFlightLines = (flight?: PackagingEntryLine): PackagingEntryLineFlightLine[] => {
return flight?.flightInformation?.flightLines ?? [];
};
const getDepartureTime = (flight?: PackagingEntryLine): string => {
const firstLine = first(getFlightLines(flight));
if (!firstLine?.departureTime) return '';
return firstLine.departureTime.slice(0, 5);
};
const getArrivalTime = (flight?: PackagingEntryLine): string => {
const lastLine = last(getFlightLines(flight));
if (!lastLine?.arrivalTime) return '';
return lastLine.arrivalTime.slice(0, 5);
};
const getDuration = (flight?: PackagingEntryLine): string => {
const lines = getFlightLines(flight);
if (!lines.length) return '';
const totalTicks = lines.reduce((sum, line) => sum + (line.durationInTicks ?? 0), 0) || 0;
if (!totalTicks) return '';
const seconds = totalTicks / 10_000_000;
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
return `${hours}h ${minutes.toString().padStart(2, '0')}m`;
};
const numberOfNights = (segment: PackagingEntryLine) => {
return differenceInCalendarDays(new Date(segment.to), new Date(segment.from));
};
const getSegmentIcon = (segment: PackagingEntryLine) => {
switch (segment.serviceType) {
case 3:
return (
);
case 4:
return (
);
case 11:
return (
);
case 13:
case 17:
case 22:
return (
);
default:
return (
);
}
};
const canEdit = (segment: PackagingEntryLine) => {
if (segment.serviceType === ACCOMMODATION_SERVICE_TYPE) {
return true;
}
return false;
};
const getSegmentTitle = (segment: PackagingEntryLine) => {
return segment.productName ?? segment.accommodationName;
};
const SERVICE_TYPE_PRIORITY: Record = {
7: 0, // Flight
13: 1, // Transfer
3: 2, // Hotel
4: 3 // Excursion
};
const getServiceTypePriority = (serviceType?: number) => {
return SERVICE_TYPE_PRIORITY[serviceType ?? -1] ?? 2;
};
const Itinerary: React.FC = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation }) => {
const context = useContext(SearchResultsConfigurationContext);
const translations = getTranslations(context?.languageCode ?? 'en-GB');
const { editablePackagingEntry, priceDetails } = useSelector((state: SearchResultsRootState) => state.searchResults);
const dispatch = useDispatch();
const packagingEntry = editablePackagingEntry ?? context?.packagingEntry;
const sortedLines = useMemo(() => {
return [...(packagingEntry?.lines ?? [])].sort((a, b) => {
const dateA = getDateOnlyTime(a.from);
const dateB = getDateOnlyTime(b.from);
if (dateA !== dateB) {
return dateA - dateB;
}
const priorityA = getServiceTypePriority(a.serviceType);
const priorityB = getServiceTypePriority(b.serviceType);
if (priorityA !== priorityB) {
return priorityA - priorityB;
}
return (a.order ?? Infinity) - (b.order ?? Infinity);
});
}, [packagingEntry]);
if (!packagingEntry) {
return null;
}
const firstEntryLine = first(sortedLines);
const lastEntryLine = last(sortedLines);
const accommodationLine = sortedLines.find((line) => line.serviceType === ACCOMMODATION_SERVICE_TYPE) ?? firstEntryLine;
const country = accommodationLine?.country?.name ?? firstEntryLine?.country?.name;
const location = accommodationLine?.location?.name ?? accommodationLine?.oord?.name ?? accommodationLine?.region?.name ?? firstEntryLine?.location?.name;
const flightSegments = sortedLines.filter((item) => item.serviceType === FLIGHT_SERVICE_TYPE);
const outboundFlight = first(flightSegments);
const returnFlight = flightSegments.length > 1 ? last(flightSegments) : undefined;
const otherSegments = sortedLines.filter((item) => item.serviceType !== FLIGHT_SERVICE_TYPE);
const grouped = groupBy(otherSegments, (segment) => {
return `${segment.productCode}-${segment.from}-${segment.to}`;
});
const groupedOtherSegments = Object.entries(grouped).map(([key, segments]) => ({
key,
segments
}));
const numberOfPax = packagingEntry.pax?.length || 1;
const totalPrice = priceDetails?.total || packagingEntry.price || 0;
const pricePerPerson = totalPrice / numberOfPax;
const handleConfirm = () => {
dispatch(setBookPackagingEntry(true));
};
return (
{translations.SRP.ITINERARY_TITLE}
<>
{context?.destinationImage && (

{packagingEntry?.dossierNumber && (
{translations.SRP.DOSSIER_NUMBER}: {packagingEntry.dossierNumber}
)}
{(location || '') + (location && country ? ' - ' : '') + (country || '')}
)}
{isLoading ? (
) : (
<>
{formatPrice(pricePerPerson, 'EUR')}
{translations.SRP.PACKAGE_PRICE_PER_PERSON}
({formatPrice(totalPrice, 'EUR')} {translations.SRP.TOTAL})
>
)}
{translations.SRP.DAY_BY_DAY}
{firstEntryLine && (
{format(new Date(firstEntryLine.from), 'EEE. d MMM yyyy')} - {translations.SRP.START}
)}
{outboundFlight && (
{format(new Date(outboundFlight.from), 'd')}
{format(new Date(outboundFlight.from), 'MMM')}
{outboundFlight.productName}
{getDepartureTime(outboundFlight)}
{' '}
-{' '}
{getArrivalTime(outboundFlight)}
{getDuration(outboundFlight)}
)}
{!isEmpty(groupedOtherSegments) &&
groupedOtherSegments.map((group) => {
const firstSegment = first(group.segments);
if (!firstSegment) return null;
return (
{format(new Date(firstSegment.from), 'd')}
{format(new Date(firstSegment.from), 'MMM')}
{firstSegment.location?.name || firstSegment.oord?.name || firstSegment.region?.name}
{(firstSegment.location?.name || firstSegment.oord?.name || firstSegment.region?.name) && firstSegment.country?.name ? ', ' : ''}
{firstSegment.country?.name}
{format(new Date(firstSegment.from), 'EEE. d MMM yyyy')}
> {format(new Date(firstSegment.to), 'EEE. d MMM yyyy')}
{
if (canEdit(firstSegment) && onEditAccommodation) {
onEditAccommodation(group.segments);
}
}}>
{firstSegment.serviceType === ACCOMMODATION_SERVICE_TYPE && (
<>
{numberOfNights(firstSegment)}
>
)}
{getSegmentIcon(firstSegment)}
{getSegmentTitle(firstSegment)}
{firstSegment.serviceType === ACCOMMODATION_SERVICE_TYPE &&
group.segments.map((segment, index) => (
{translations.SHARED.ROOM} {index + 1}
{segment.productName && segment.accommodationName && (
{segment.accommodationName}
)}
{segment.regimeName && (
{segment.regimeName}
)}
))}
);
})}
{returnFlight && returnFlight !== outboundFlight && (
{format(new Date(returnFlight.from), 'd')}
{format(new Date(returnFlight.from), 'MMM')}
{returnFlight.productName}
{getDepartureTime(returnFlight)}
{' '}
-{' '}
{getArrivalTime(returnFlight)}
{getDuration(returnFlight)}
)}
{lastEntryLine && (
{format(new Date(lastEntryLine.to), 'EEE. d MMM yyyy')} - {translations.SRP.END}
)}
>
);
};
export default Itinerary;