mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-12 23:31:02 +00:00
b80fdf27f2
An imported hotel was looked up once, by its name and its address together. A booking site's romanized address (Agoda for a stay in Japan) often matches nothing and sank the hotel's name with it, so the place was created without coordinates and silently never showed on the map. The review now finds the stay right away (name and address, then the name, then the address) and says under the address whether it is on the map and where, or that it is not yet, with a way to search again. Picking an address suggestion sets the spot by hand. The save uses that spot, falls back the same way when there is none, and a stay saved without a spot says so in a toast. Desktop dialog and phone sheet share the lookup through useReservationForm.
81 lines
3.2 KiB
TypeScript
81 lines
3.2 KiB
TypeScript
import { useMemo, useState } from 'react';
|
|
|
|
import type { Day, Place } from '../../types';
|
|
import { toggledTraveler } from './bookingFormModel';
|
|
import {
|
|
type CheckInEndField,
|
|
endTimeBeforeStartTime,
|
|
isEndBeforeStart,
|
|
type ReservationFields,
|
|
reservationSaveData,
|
|
type ReservationSaveOptions,
|
|
startParts,
|
|
tripDateBounds,
|
|
withHotelEnd,
|
|
withHotelPlace,
|
|
withHotelStart,
|
|
withLinkedPlace,
|
|
withLinkedStopDay,
|
|
withStartDate,
|
|
withStartTime,
|
|
} from './reservationFormModel';
|
|
import { useStayPin } from './useStayPin';
|
|
|
|
/**
|
|
* The booking form behind the desktop ReservationModal and the phone's booking
|
|
* sheet: the fields, the travellers and the files picked before saving, the date
|
|
* rules and the place and hotel picks. Each shell decides when to fill it and what
|
|
* to do around a save.
|
|
*/
|
|
export function useReservationForm<F extends ReservationFields & CheckInEndField>(
|
|
emptyForm: F,
|
|
days: Day[],
|
|
places: Pick<Place, 'id' | 'name' | 'address'>[]
|
|
) {
|
|
const [form, setForm] = useState<F>(emptyForm);
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
const [pendingFiles, setPendingFiles] = useState<File[]>([]);
|
|
// Travelers assigned to this booking (#1517), seeded on open and persisted after the save resolves.
|
|
const [travelerIds, setTravelerIds] = useState<Set<number>>(new Set());
|
|
|
|
const set = (field: keyof F, value: string | number) => setForm((prev) => ({ ...prev, [field]: value }));
|
|
const toggleTraveler = (id: number) => setTravelerIds((prev) => toggledTraveler(prev, id));
|
|
// Where an imported stay will land on the map, looked up while it is reviewed.
|
|
const stayPin = useStayPin();
|
|
|
|
// Restrict non-hotel booking dates to the trip's span (#1662). Hotels already
|
|
// constrain to trip days via their day dropdowns.
|
|
const dateBounds = useMemo(() => tripDateBounds(days || []), [days]);
|
|
const start = startParts(form.reservation_time);
|
|
|
|
return {
|
|
form,
|
|
setForm,
|
|
set,
|
|
isSaving,
|
|
setIsSaving,
|
|
pendingFiles,
|
|
setPendingFiles,
|
|
travelerIds,
|
|
setTravelerIds,
|
|
toggleTraveler,
|
|
isEndBeforeStart: isEndBeforeStart(form),
|
|
endTimeBeforeStartTime: endTimeBeforeStartTime(form),
|
|
dateBounds,
|
|
startDate: start.date,
|
|
startTime: start.time,
|
|
setStartDate: (date: string | null | undefined) =>
|
|
set('reservation_time', withStartDate(form.reservation_time, date)),
|
|
setStartTime: (time: string, fallbackDate: string) =>
|
|
set('reservation_time', withStartTime(form.reservation_time, time, fallbackDate)),
|
|
/** An undated booking takes the day of the stop it was just linked to. */
|
|
takeStopDay: (dayDate: string) => setForm((prev) => withLinkedStopDay(prev, dayDate)),
|
|
pickPlace: (value: string | number) => setForm((prev) => withLinkedPlace(prev, value, places)),
|
|
pickHotelPlace: (value: string | number) => setForm((prev) => withHotelPlace(prev, value, places)),
|
|
pickHotelStart: (value: string | number) => setForm((prev) => withHotelStart(prev, value, days)),
|
|
pickHotelEnd: (value: string | number) => setForm((prev) => withHotelEnd(prev, value, days)),
|
|
stayPin,
|
|
saveData: (opts: ReservationSaveOptions) => reservationSaveData(form, { ...opts, venuePin: stayPin.match }),
|
|
};
|
|
}
|