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 && (
{context.destinationImage.alt} {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;