Files
TREK/client/src/components/Planner/parsedItemToDraft.ts
T
Maurice 8fd46c4c18 feat: quality of life sweep from the feature discussions
Small requests from the discussions, each built end to end: planner and
map (cycling mode, date first headings, clear a day, stay times per day,
off-route stops, map lock, compact unplanned markers, pending stays,
cable car, transit quick picks), places (location filter, category from
OSM, open in maps, geo: link, image from an attachment, enrichment on
file import, places near a pin, place name language, Amap first in
China), lists and costs (packed counter, inherited sharing, Markdown
to-dos, payment notes, bookings CSV, my plan PDF), sharing (unplanned
places, travel only, without photos), journey (photo zoom, drafts,
photo order, trip suggestion, day jump), atlas (region counter, region
bucket list, places per country), vacay half company holidays, studio
copy and paste plus the book's own layouts, and RESTORE_FROM_BACKUP for
a first start restore.
2026-09-30 23:00:12 +02:00

68 lines
3.3 KiB
TypeScript

import type { BookingImportPreviewItem, Reservation, ReservationEndpoint } from '@trek/shared'
/**
* A pre-fill draft for the reservation/transport edit modals built from a parsed
* booking-import item. Carries the normal reservation fields the modals read for
* their form, plus the import-only `_venue`/`_accommodation` the hotel path needs
* to suggest a place and a day range. It has no `id` — the modal stays in
* "create" mode and the user reviews/edits before it is ever persisted.
*/
export interface BookingReviewDraft extends Omit<Partial<Reservation>, 'metadata' | 'endpoints'> {
/** Type-specific extras (airline, flight_number, check_in_time, price, …) as an object. */
metadata?: Record<string, unknown> | null
endpoints?: ReservationEndpoint[]
/** Parsed venue (auto-created place candidate) — hotel/restaurant/event. */
_venue?: BookingImportPreviewItem['_venue']
/** Parsed check-in/out + confirmation — hotels only. */
_accommodation?: BookingImportPreviewItem['_accommodation']
/** The uploaded source file(s) the item was parsed from — attached to the booking on save. */
_sourceFiles?: File[]
}
/**
* Map a parsed booking item onto the shape the edit modals pre-fill from. Pure
* (no I/O). Transport items keep their geocoded endpoints; venue/accommodation
* ride along untouched so the hotel modal can match a place by name (or create
* one from the reviewed address on save).
*/
export function parsedItemToDraft(item: BookingImportPreviewItem): BookingReviewDraft {
return {
type: item.type,
title: item.title,
status: 'pending',
reservation_time: item.reservation_time ?? null,
reservation_end_time: item.reservation_end_time ?? null,
location: item.location ?? item._venue?.address ?? item._venue?.name ?? null,
confirmation_number: item.confirmation_number ?? null,
notes: null,
metadata: (item.metadata as Record<string, unknown> | undefined) ?? null,
endpoints: (item.endpoints ?? []) as ReservationEndpoint[],
_venue: item._venue,
_accommodation: item._accommodation,
}
}
/** Transport types route to the TransportModal; everything else to the ReservationModal. */
const TRANSPORT_TYPES = new Set(['flight', 'train', 'bus', 'car', 'taxi', 'bicycle', 'cruise', 'ferry', 'cable_car', 'transit', 'transport_other'])
/** The types the booking form can express — its own chip list. */
const BOOKING_TYPES = new Set(['hotel', 'restaurant', 'event', 'tour', 'activity', 'parking', 'other'])
export function isTransportItem(item: BookingImportPreviewItem): boolean {
return TRANSPORT_TYPES.has(item.type)
}
/**
* Neither form is obviously right, so the tab the user started the import from
* breaks the tie (#2076).
*
* Two cases. A type neither form can express, and a type the extractor filled in
* rather than read. The second is the one that actually occurs: the server can
* only ever emit its eight known types, so the first branch never fires on a real
* document, and a ferry voucher the model could not classify arrived as a
* placeholder 'hotel' with no way to tell it from a real one.
*/
export function isUnplaceableItem(item: BookingImportPreviewItem): boolean {
if (item.type_guessed === true) return true
return !TRANSPORT_TYPES.has(item.type) && !BOOKING_TYPES.has(item.type)
}