import { compact, get, sortBy } from 'lodash'; import React, { ReactNode, useMemo, useState } from 'react'; import { FormikProps } from 'formik'; import flat from 'flat'; import { format, parse } from 'date-fns'; import { Country, RoomTraveler, Traveler, TravelersFormValues } from '../../booking-wizard/types'; import LabeledInput from '../../booking-wizard/components/labeled-input'; import LabeledSelect from '../../booking-wizard/components/labeled-select'; import PhoneInput from '../../booking-wizard/components/phone-input'; import GenderControl from '../../booking-wizard/features/travelers-form/controls/gender-control'; import TypeAheadInput from '../../booking-wizard/features/travelers-form/type-ahead-input'; import { buildClassName } from '../utils/class-util'; import { CountryItem, PackagingEntry, PackagingEntryPax } from '@qite/tide-client'; export type TravelersFormField = { type: string }; export type AgentOption = { id: number | string; name: string; postalCode?: string; location?: string }; export type TypeAheadOption = { key: string; value: string; text: string }; export interface SharedTravelersSettings { countries?: Country[]; formFields?: TravelersFormField[]; mainBookerFormFields?: TravelersFormField[]; } export interface SharedTravelersFormProps { formik: FormikProps; translations: any; travellersSettings?: SharedTravelersSettings; countries?: CountryItem[]; agents?: AgentOption[]; bookingType?: string; agentAdressId?: number; travelersFirstStep?: boolean; isUnavailable?: boolean; useCompactForm?: boolean; showAllCountries?: boolean; showAgentSelection?: boolean; initialShowAgents?: boolean; renderPreviousButton?: () => ReactNode; onBookingTypeChange?: (bookingType: string) => void; } export function createTraveler(traveler: RoomTraveler, followNumber: { number: number }, personTranslation?: string, isCompact?: boolean): Traveler { if (isCompact) { return { id: traveler.id, firstName: personTranslation, lastName: `${followNumber.number++}`, birthDate: '', gender: '', age: traveler.age || 30 } as Traveler; } return { id: traveler.id, firstName: '', lastName: '', birthDate: '', gender: '' } as Traveler; } export function createInitialValuesFromRooms( formRooms: { adults: RoomTraveler[]; children: RoomTraveler[] }[], startDate?: string, agentAdressId?: number, personTranslation?: string, isCompact?: boolean ): TravelersFormValues { const followNumber = { number: 1 }; const initialValues = { startDate, rooms: formRooms.map((room) => ({ adults: room.adults.map((traveler) => createTraveler(traveler, followNumber, personTranslation, isCompact)), children: room.children.map((traveler) => createTraveler(traveler, followNumber, personTranslation, isCompact)) })), mainBookerId: -1, street: '', houseNumber: '', box: '', zipCode: '', place: '', country: '', phone: '', email: '', emailConfirmation: '', travelAgentId: agentAdressId ?? 0, travelAgentName: '' } as TravelersFormValues; if (initialValues.rooms?.[0]?.adults?.[0]) { initialValues.mainBookerId = initialValues.rooms[0].adults[0].id; } return initialValues; } export function createInitialValuesFromEditablePackagingEntry(editablePackagingEntry: PackagingEntry, agentAdressId?: number): TravelersFormValues { const pax = editablePackagingEntry?.pax ?? []; const rooms = editablePackagingEntry.rooms.map((room) => { const roomPax = pax.filter((x: PackagingEntryPax) => room.paxIds.includes(x.id)); const adults = roomPax.filter((x: PackagingEntryPax) => x.age! >= 18); const children = roomPax.filter((x: PackagingEntryPax) => x.age! < 18); return { adults: adults.map((roomTraveler: PackagingEntryPax) => { return { id: roomTraveler.id, firstName: roomTraveler.firstName ?? '', lastName: roomTraveler.lastName ?? '', birthDate: roomTraveler.dateOfBirth ? format(new Date(roomTraveler.dateOfBirth), 'yyyy-MM-dd') : '', gender: '' } as Traveler; }), children: children.map((roomTraveler: PackagingEntryPax) => { return { id: roomTraveler.id, firstName: roomTraveler.firstName ?? '', lastName: roomTraveler.lastName ?? '', birthDate: roomTraveler.dateOfBirth ? format(new Date(roomTraveler.dateOfBirth), 'yyyy-MM-dd') : '', gender: '' } as Traveler; }) }; }); const values = createInitialValuesFromRooms( rooms.map((room) => ({ adults: room.adults as RoomTraveler[], children: room.children as RoomTraveler[] })), editablePackagingEntry?.lines?.[0]?.from, agentAdressId ); values.rooms = rooms; values.mainBookerId = pax.find((x: any) => x.isMainBooker)?.id ?? rooms[0]?.adults?.[0]?.id ?? -1; const address = editablePackagingEntry?.address; values.street = address?.street ?? ''; values.houseNumber = address?.houseNumber ?? ''; values.box = address?.box ?? ''; values.zipCode = address?.zipCode ?? ''; values.place = address?.place ?? ''; values.country = address?.country ?? ''; values.phone = address?.phone ?? ''; values.email = address?.email ?? ''; values.emailConfirmation = address?.email ?? ''; values.travelAgentId = address?.travelAgentId ?? agentAdressId ?? 0; return values; } export function applyTravelersFormValuesToEditablePackagingEntry(editablePackagingEntry: PackagingEntry, values: TravelersFormValues) { const travelers = values.rooms.flatMap((room) => [...room.adults, ...room.children]); return { ...editablePackagingEntry, pax: (editablePackagingEntry.pax ?? []).map((pax) => { const traveler = travelers.find((x) => x.id === pax.id); if (!traveler) return pax; return { ...pax, firstName: traveler.firstName ?? '', lastName: traveler.lastName ?? '', dateOfBirth: traveler.birthDate || null, isMainBooker: traveler.id === values.mainBookerId }; }), address: { ...editablePackagingEntry.address, street: values.street, houseNumber: values.houseNumber, box: values.box, zipCode: values.zipCode, place: values.place, country: values.country, travelAgentId: values.travelAgentId, phone: values.phone, email: values.email } }; } const SharedTravelersForm: React.FC = ({ formik, translations, travellersSettings, countries, agents, bookingType, agentAdressId, travelersFirstStep = false, isUnavailable = false, useCompactForm = false, showAllCountries = false, showAgentSelection = false, initialShowAgents = false, renderPreviousButton, onBookingTypeChange }) => { const [showAgents, setShowAgents] = useState(initialShowAgents); const typeaheadAgents = useMemo( () => sortBy( agents?.map((agent) => ({ key: `${agent.id}`, value: `${agent.name}${agent.postalCode || agent.location ? ` (${compact([agent.postalCode, agent.location]).join(' ')})` : ''}`, text: `${agent.name}${agent.postalCode || agent.location ? ` (${compact([agent.postalCode, agent.location]).join(' ')})` : ''}` })), 'value' ) ?? [], [agents] ); const [filteredAgents, setFilteredAgents] = useState(typeaheadAgents); const flatErrors: Record = flat(formik.errors); const errorKeys = Object.keys(flatErrors).filter((key) => get(formik.touched, key)); const hasVisibleError = (key: string) => get(formik.errors, key) && get(formik.touched, key); const mainBooker = formik.values.rooms .find((room) => room.adults.find((traveler) => traveler.id === formik.values.mainBookerId)) ?.adults.find((traveler) => traveler.id === formik.values.mainBookerId); const countryOptions = [ { key: 'empty', value: undefined, label: translations.TRAVELERS_FORM.SELECT_COUNTRY }, ...(showAllCountries ? countries?.map((country) => ({ key: country.iso2, value: country.iso2, label: country.name })) ?? [] : travellersSettings?.countries?.map((country) => ({ key: country.iso2, value: country.iso2, label: country.name })) ?? [ { key: 'be', value: 'be', label: translations.TRAVELERS_FORM.COUNTRIES.BELGIUM }, { key: 'nl', value: 'nl', label: translations.TRAVELERS_FORM.COUNTRIES.NETHERLANDS }, { key: 'fr', value: 'fr', label: translations.TRAVELERS_FORM.COUNTRIES.FRANCE } ]) ]; const handleMainBookerChange: React.FormEventHandler = (event) => { formik.setFieldValue('mainBookerId', parseInt(event.currentTarget.value, 10)); }; const handleAgentChange = (value: string) => { setFilteredAgents(typeaheadAgents.filter((x) => x.value.toLocaleLowerCase().indexOf(value.toLocaleLowerCase()) > -1)); formik.setFieldValue('travelAgentName', value); }; const handleAgentSelect = (key: string) => { const agent = typeaheadAgents.find((x) => x.key === key); formik.setValues({ ...formik.values, travelAgentId: Number(agent?.key), travelAgentName: agent?.value ?? '' }); onBookingTypeChange?.(agentAdressId && agentAdressId !== 0 ? 'b2b' : 'b2b2c'); }; const handleAgentClear = () => { formik.setValues({ ...formik.values, travelAgentId: 0, travelAgentName: '' }); onBookingTypeChange?.('b2c'); }; const toggleAgent = (value: boolean) => { setShowAgents(value); if (!value) { handleAgentClear(); setFilteredAgents([]); } }; const handleAddTraveler = (roomIndex: number) => { const rooms = [...formik.values.rooms]; const newAdult = { id: Date.now(), firstName: '', lastName: '', birthDate: '', gender: '' } as Traveler; rooms[roomIndex] = { ...rooms[roomIndex], adults: [...rooms[roomIndex].adults, newAdult] }; formik.setFieldValue('rooms', rooms); }; const handleRemoveTraveler = (roomIndex: number, travelerIndex: number) => { const rooms = [...formik.values.rooms]; const adults = [...rooms[roomIndex].adults]; if (adults.length <= 1) return; adults.splice(travelerIndex, 1); rooms[roomIndex] = { ...rooms[roomIndex], adults }; formik.setFieldValue('rooms', rooms); }; const handleAddRoom = () => { const rooms = [...formik.values.rooms]; rooms.push({ adults: [{ id: Date.now(), firstName: '', lastName: '', birthDate: '', gender: '' } as Traveler], children: [] }); formik.setFieldValue('rooms', rooms); }; const handleRemoveRoom = (roomIndex: number) => { const rooms = [...formik.values.rooms]; rooms.splice(roomIndex, 1); formik.setFieldValue('rooms', rooms); }; const renderGenderControl = (name: string, value: Traveler) => (
{[ ['m', translations.TRAVELERS_FORM.MALE_GENDER], ['f', translations.TRAVELERS_FORM.FEMALE_GENDER] ].map(([gender, label]) => (
))}
); const getControl = (type: string, value: Traveler, name: string) => { switch (type) { case 'gender': return ; case 'firstName': return ( ); case 'lastName': return ( ); case 'birthDate': return ( ); case 'country': return ( ); case 'phone': return ( ); case 'email': return ( <> ); case 'street': return ( ); case 'houseNumber': return ( ); case 'box': return ( ); case 'zipCode': return ( ); case 'place': return ( ); default: return null; } }; const renderRoomLabel = (room: TravelersFormValues['rooms'][number]) => compact([ room.adults.length, room.adults.length === 1 && ` ${translations.TRAVELERS_FORM.ADULT}`, room.adults.length > 1 && ` ${translations.TRAVELERS_FORM.ADULTS}`, room.adults?.length && room.children?.length && ', ', room.children.length, room.children.length === 1 && ` ${translations.TRAVELERS_FORM.CHILD}`, room.children.length > 1 && ` ${translations.TRAVELERS_FORM.CHILDREN}` ]).join(''); const renderTravelerFields = (travelerValues: Traveler, namePrefix: string, isAdult: boolean, roomIndex: number, travelerIndex: number) => { if (useCompactForm) { return (
); } if (travellersSettings?.formFields?.length) { return (
{travellersSettings.formFields.map((field, index) => (
{getControl(field.type, travelerValues, `${namePrefix}.${field.type}`)}
))}
); } return ( <>
{renderGenderControl(`${namePrefix}.gender`, travelerValues)}
{travelersFirstStep && isAdult && formik.values.rooms[roomIndex].adults.length > 1 && ( )} ); }; return (
{formik.values.rooms.map((room, roomIndex) => (
{formik.values.rooms.length > 1 && (
{translations.SHARED.ROOM} {roomIndex + 1}

{renderRoomLabel(room)}

{!useCompactForm && travelersFirstStep && formik.values.rooms.length > 1 && ( )}
)} {room.adults.map((travelerValues, index) => (
{translations.TRAVELERS_FORM.TRAVELER} {index + 1}

{translations.TRAVELERS_FORM.ADULT}

{renderTravelerFields(travelerValues, `rooms[${roomIndex}].adults[${index}]`, true, roomIndex, index)}
))} {room.children.map((travelerValues, index) => (
{translations.TRAVELERS_FORM.TRAVELER} {room.adults.length + index + 1}

{translations.TRAVELERS_FORM.CHILD}

{renderTravelerFields(travelerValues, `rooms[${roomIndex}].children[${index}]`, false, roomIndex, index)}
))} {!useCompactForm && travelersFirstStep && (
)}
))} {!useCompactForm && (bookingType !== 'b2b' || travellersSettings?.mainBookerFormFields?.length) ? (
{translations.TRAVELERS_FORM.MAIN_BOOKER}

{compact([ compact([mainBooker?.firstName, mainBooker?.lastName]).join(' '), mainBooker?.birthDate && format(parse(mainBooker.birthDate, 'yyyy-MM-dd', new Date()), 'dd-MM-yyyy') ]).join(', ')}

{travellersSettings?.mainBookerFormFields?.length ? (
{travellersSettings.mainBookerFormFields.map((field, index) => (
{getControl(field.type, {} as Traveler, field.type)}
))}
) : ( <>
)}
) : !useCompactForm ? (
) : null} {!useCompactForm && showAgentSelection && (
{translations.TRAVELERS_FORM.BOOK_WITH_AGENT}
{showAgents && (
)}
)}
{!useCompactForm && errorKeys.length > 0 && (

{translations.TRAVELERS_FORM.VALIDATION_MESSAGE}:

    {errorKeys.map((key) => (
  • {get(flatErrors, key)}
  • ))}
)} {travelersFirstStep && (
)}
{renderPreviousButton?.()}
); }; export default SharedTravelersForm;