Rework entry types: merge hotel into stay, transport with modes, auto-transport

- Type set is now activity/stay/transport/flight/rental/note; hotel, travel
  and immigration are removed with idempotent startup data migrations
  (hotel->stay, travel->transport, immigration->activity with flag prefix)
- Transport entries carry an optional mode (train/bus/ferry/taxi/drive/other)
  that drives the chip/map icon; route stops expose transport_mode
- Creating a stay auto-creates a bridging transport to its neighbouring
  stays unless a transport/flight already covers the gap (one-shot)
- Summary: transports count replaces hotels/travelLegs
This commit is contained in:
2026-07-19 17:47:21 +07:00
parent b066e7b885
commit d393e16ae1
17 changed files with 469 additions and 62 deletions
+22 -3
View File
@@ -6,12 +6,10 @@
// is roughly most-common-first with Activity as the default for new entries.
export const ENTRY_TYPES = {
activity: { label: 'Activity', icon: '📍', color: '#059669' },
hotel: { label: 'Hotel', icon: '🏨', color: '#db2777' },
stay: { label: 'Stay', icon: '🏙️', color: '#f59e0b' },
travel: { label: 'Travel', icon: '🚗', color: '#d97706' },
transport: { label: 'Transport', icon: '🚆', color: '#d97706' },
flight: { label: 'Flight', icon: '✈️', color: '#2563eb' },
rental: { label: 'Rental car', icon: '🚙', color: '#0891b2' },
immigration: { label: 'Immigration', icon: '🛂', color: '#7c3aed' },
note: { label: 'Note', icon: '📝', color: '#64748b' },
};
@@ -20,10 +18,31 @@ export const ENTRY_TYPE_LIST = Object.entries(ENTRY_TYPES).map(([value, meta]) =
...meta,
}));
// Transport entries may carry an optional transport_mode; this is its own
// select (shown only for type === 'transport'), separate from ENTRY_TYPES.
export const TRANSPORT_MODES = [
{ value: 'train', label: 'Train', icon: '🚆' },
{ value: 'bus', label: 'Bus', icon: '🚌' },
{ value: 'ferry', label: 'Ferry', icon: '⛴️' },
{ value: 'taxi', label: 'Taxi', icon: '🚕' },
{ value: 'drive', label: 'Drive', icon: '🚗' },
{ value: 'other', label: 'Other', icon: '➡️' },
];
export function typeInfo(type) {
return ENTRY_TYPES[type] || { label: type || 'Entry', icon: '•', color: '#64748b' };
}
// Icon for an entry: a transport entry with a mode shows the mode's icon,
// otherwise falls back to the type's icon.
export function entryIcon(entry) {
if (entry && entry.type === 'transport' && entry.transport_mode) {
const mode = TRANSPORT_MODES.find((m) => m.value === entry.transport_mode);
if (mode) return mode.icon;
}
return typeInfo(entry && entry.type).icon;
}
// Split modes with the human labels the day-editor select shows.
export const SPLIT_MODES = [
{ value: 'equal', label: 'Split equally' },