Add multi-day entries and area stay blocks

Entries gain an optional inclusive end_date (backfilled via migration): flights and travel longer than 24h show a continuation marker on following days, and a new stay entry type marks a time frame in one area (e.g. 3 days Venice) rendered as continuous bands across the calendar weeks. Stays feed the map route as stops; the route summary gains stays count and a chronological areas list with day spans. 49 API tests.
This commit is contained in:
2026-07-19 00:30:14 +07:00
parent 1d86c68665
commit 154f56a0a0
14 changed files with 416 additions and 26 deletions
+18
View File
@@ -7,6 +7,7 @@
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' },
flight: { label: 'Flight', icon: '✈️', color: '#2563eb' },
rental: { label: 'Rental car', icon: '🚙', color: '#0891b2' },
@@ -146,3 +147,20 @@ export function hasSegments(entry) {
export function hasRental(entry) {
return entry && entry.rental && typeof entry.rental === 'object';
}
// True when an entry spans more than its start day (valid end_date after date).
export function isMultiDay(entry) {
return !!(entry && entry.end_date && entry.end_date > entry.date);
}
// Inclusive span in days (end_date null → 1).
export function entrySpanDays(entry) {
const end = entry.end_date && entry.end_date >= entry.date ? entry.end_date : entry.date;
return daysBetweenInclusive(entry.date, end);
}
// A stay's short label: first comma-segment of its location name (fallback title).
export function stayShortName(entry) {
const base = entry.location_name || entry.title || 'Stay';
return base.split(',')[0].trim();
}