mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-18 20:39:36 +02:00
feat(lodging): improve lodging date handling with all-day event support and timezone adjustments
This commit is contained in:
parent
63e8e96d52
commit
df24316837
5 changed files with 170 additions and 78 deletions
|
@ -120,23 +120,39 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{#if lodging.check_in && lodging.check_out}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-medium">{$t('adventures.dates')}:</span>
|
||||
<p class="text-sm">
|
||||
{#if isAllDay(lodging.check_in)}
|
||||
{formatAllDayDate(lodging.check_in)} –
|
||||
{formatAllDayDate(lodging.check_out)}
|
||||
{:else}
|
||||
{formatDateInTimezone(lodging.check_in, lodging.timezone)} –
|
||||
{formatDateInTimezone(lodging.check_out, lodging.timezone)}
|
||||
{#if lodging.timezone}
|
||||
<span class="ml-1 text-xs opacity-60">({lodging.timezone})</span>
|
||||
<div class="space-y-3">
|
||||
{#if lodging.check_in}
|
||||
<div class="flex gap-2 text-sm">
|
||||
<span class="font-medium whitespace-nowrap">{$t('adventures.check_in')}:</span>
|
||||
<span>
|
||||
{#if isAllDay(lodging.check_in)}
|
||||
{formatAllDayDate(lodging.check_in)}
|
||||
{:else}
|
||||
{formatDateInTimezone(lodging.check_in, lodging.timezone)}
|
||||
{#if lodging.timezone}
|
||||
<span class="ml-1 text-xs opacity-60">({lodging.timezone})</span>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if lodging.check_out}
|
||||
<div class="flex gap-2 text-sm">
|
||||
<span class="font-medium whitespace-nowrap">{$t('adventures.check_out')}:</span>
|
||||
<span>
|
||||
{#if isAllDay(lodging.check_out)}
|
||||
{formatAllDayDate(lodging.check_out)}
|
||||
{:else}
|
||||
{formatDateInTimezone(lodging.check_out, lodging.timezone)}
|
||||
{#if lodging.timezone}
|
||||
<span class="ml-1 text-xs opacity-60">({lodging.timezone})</span>
|
||||
{/if}
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reservation Info -->
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import type { Collection, Lodging } from '$lib/types';
|
||||
import LocationDropdown from './LocationDropdown.svelte';
|
||||
import DateRangeCollapse from './DateRangeCollapse.svelte';
|
||||
import { isAllDay } from '$lib';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
|
@ -82,6 +83,24 @@
|
|||
async function handleSubmit(event: Event) {
|
||||
event.preventDefault();
|
||||
|
||||
lodging.timezone = lodgingTimezone || null;
|
||||
|
||||
// Auto-set end date if missing but start date exists
|
||||
if (lodging.check_in && !lodging.check_out) {
|
||||
const startDate = new Date(lodging.check_in);
|
||||
const nextDay = new Date(startDate);
|
||||
nextDay.setDate(nextDay.getDate() + 1);
|
||||
|
||||
if (isAllDay(lodging.check_in)) {
|
||||
// For all-day, set to next day at 00:00:00
|
||||
lodging.check_out = nextDay.toISOString().split('T')[0] + 'T00:00:00';
|
||||
} else {
|
||||
// For timed events, set to next day at 9:00 AM
|
||||
nextDay.setHours(9, 0, 0, 0);
|
||||
lodging.check_out = nextDay.toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
// Create or update lodging...
|
||||
const url = lodging.id === '' ? '/api/lodging' : `/api/lodging/${lodging.id}`;
|
||||
const method = lodging.id === '' ? 'POST' : 'PATCH';
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
import DeleteWarning from './DeleteWarning.svelte';
|
||||
// import ArrowDownThick from '~icons/mdi/arrow-down-thick';
|
||||
import { TRANSPORTATION_TYPES_ICONS } from '$lib';
|
||||
import { formatDateInTimezone } from '$lib/dateUtils';
|
||||
import { formatAllDayDate, formatDateInTimezone } from '$lib/dateUtils';
|
||||
import { isAllDay } from '$lib';
|
||||
|
||||
function getTransportationIcon(type: string) {
|
||||
if (type in TRANSPORTATION_TYPES_ICONS) {
|
||||
|
@ -161,9 +162,13 @@
|
|||
<div class="flex gap-2 text-sm">
|
||||
<span class="font-medium whitespace-nowrap">{$t('adventures.start')}:</span>
|
||||
<span>
|
||||
{formatDateInTimezone(transportation.date, transportation.start_timezone)}
|
||||
{#if transportation.start_timezone}
|
||||
<span class="ml-1 text-xs opacity-60">({transportation.start_timezone})</span>
|
||||
{#if isAllDay(transportation.date) && (!transportation.end_date || isAllDay(transportation.end_date))}
|
||||
{formatAllDayDate(transportation.date)}
|
||||
{:else}
|
||||
{formatDateInTimezone(transportation.date, transportation.start_timezone)}
|
||||
{#if transportation.start_timezone}
|
||||
<span class="ml-1 text-xs opacity-60">({transportation.start_timezone})</span>
|
||||
{/if}
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
|
@ -173,9 +178,13 @@
|
|||
<div class="flex gap-2 text-sm">
|
||||
<span class="font-medium whitespace-nowrap">{$t('adventures.end')}:</span>
|
||||
<span>
|
||||
{formatDateInTimezone(transportation.end_date, transportation.end_timezone)}
|
||||
{#if transportation.end_timezone}
|
||||
<span class="ml-1 text-xs opacity-60">({transportation.end_timezone})</span>
|
||||
{#if isAllDay(transportation.end_date) && (!transportation.date || isAllDay(transportation.date))}
|
||||
{formatAllDayDate(transportation.end_date)}
|
||||
{:else}
|
||||
{formatDateInTimezone(transportation.end_date, transportation.end_timezone)}
|
||||
{#if transportation.end_timezone}
|
||||
<span class="ml-1 text-xs opacity-60">({transportation.end_timezone})</span>
|
||||
{/if}
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import inspirationalQuotes from './json/quotes.json';
|
||||
import randomBackgrounds from './json/backgrounds.json';
|
||||
|
||||
// @ts-ignore
|
||||
import { DateTime } from 'luxon';
|
||||
import type {
|
||||
Adventure,
|
||||
Background,
|
||||
|
@ -144,13 +147,6 @@ function getLocalDateString(date: Date): string {
|
|||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
// Helper to check if a given date string represents midnight (all-day)
|
||||
// Improved isAllDay function to handle different ISO date formats
|
||||
export function isAllDay(dateStr: string): boolean {
|
||||
// Check for various midnight formats in UTC
|
||||
return dateStr.endsWith('T00:00:00Z') || dateStr.endsWith('T00:00:00.000Z');
|
||||
}
|
||||
|
||||
export function groupTransportationsByDate(
|
||||
transportations: Transportation[],
|
||||
startDate: Date,
|
||||
|
@ -158,82 +154,127 @@ export function groupTransportationsByDate(
|
|||
): Record<string, Transportation[]> {
|
||||
const groupedTransportations: Record<string, Transportation[]> = {};
|
||||
|
||||
// Initialize all days in the range
|
||||
// Initialize days
|
||||
for (let i = 0; i < numberOfDays; i++) {
|
||||
const currentDate = new Date(startDate);
|
||||
currentDate.setDate(startDate.getDate() + i);
|
||||
const dateString = getLocalDateString(currentDate);
|
||||
const currentDate = DateTime.fromJSDate(startDate).plus({ days: i });
|
||||
const dateString = currentDate.toISODate(); // 'YYYY-MM-DD'
|
||||
groupedTransportations[dateString] = [];
|
||||
}
|
||||
|
||||
transportations.forEach((transportation) => {
|
||||
if (transportation.date) {
|
||||
const transportationDate = getLocalDateString(new Date(transportation.date));
|
||||
if (transportation.end_date) {
|
||||
const endDate = new Date(transportation.end_date).toISOString().split('T')[0];
|
||||
// Check if it's all-day: start has 00:00:00 AND (no end OR end also has 00:00:00)
|
||||
const startHasZeros = transportation.date.includes('T00:00:00');
|
||||
const endHasZeros = transportation.end_date
|
||||
? transportation.end_date.includes('T00:00:00')
|
||||
: true;
|
||||
const isTranspoAllDay = startHasZeros && endHasZeros;
|
||||
|
||||
// Loop through all days and include transportation if it falls within the range
|
||||
for (let i = 0; i < numberOfDays; i++) {
|
||||
const currentDate = new Date(startDate);
|
||||
currentDate.setDate(startDate.getDate() + i);
|
||||
const dateString = getLocalDateString(currentDate);
|
||||
let startDT: DateTime;
|
||||
let endDT: DateTime;
|
||||
|
||||
// Include the current day if it falls within the transportation date range
|
||||
if (dateString >= transportationDate && dateString <= endDate) {
|
||||
if (groupedTransportations[dateString]) {
|
||||
groupedTransportations[dateString].push(transportation);
|
||||
}
|
||||
}
|
||||
if (isTranspoAllDay) {
|
||||
// For all-day events, extract just the date part and ignore timezone
|
||||
const dateOnly = transportation.date.split('T')[0]; // Get 'YYYY-MM-DD'
|
||||
startDT = DateTime.fromISO(dateOnly); // This creates a date without time/timezone
|
||||
|
||||
endDT = transportation.end_date
|
||||
? DateTime.fromISO(transportation.end_date.split('T')[0])
|
||||
: startDT;
|
||||
} else {
|
||||
// For timed events, use timezone conversion
|
||||
startDT = DateTime.fromISO(transportation.date, {
|
||||
zone: transportation.start_timezone ?? 'UTC'
|
||||
});
|
||||
|
||||
endDT = transportation.end_date
|
||||
? DateTime.fromISO(transportation.end_date, {
|
||||
zone: transportation.end_timezone ?? transportation.start_timezone ?? 'UTC'
|
||||
})
|
||||
: startDT;
|
||||
}
|
||||
|
||||
const startDateStr = startDT.toISODate();
|
||||
const endDateStr = endDT.toISODate();
|
||||
|
||||
// Loop through all days in range
|
||||
for (let i = 0; i < numberOfDays; i++) {
|
||||
const currentDate = DateTime.fromJSDate(startDate).plus({ days: i });
|
||||
const currentDateStr = currentDate.toISODate();
|
||||
|
||||
if (currentDateStr >= startDateStr && currentDateStr <= endDateStr) {
|
||||
groupedTransportations[currentDateStr]?.push(transportation);
|
||||
}
|
||||
} else if (groupedTransportations[transportationDate]) {
|
||||
// If there's no end date, add transportation to the start date only
|
||||
groupedTransportations[transportationDate].push(transportation);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return groupedTransportations;
|
||||
}
|
||||
|
||||
export function groupLodgingByDate(
|
||||
transportations: Lodging[],
|
||||
lodging: Lodging[],
|
||||
startDate: Date,
|
||||
numberOfDays: number
|
||||
): Record<string, Lodging[]> {
|
||||
const groupedTransportations: Record<string, Lodging[]> = {};
|
||||
const groupedLodging: Record<string, Lodging[]> = {};
|
||||
|
||||
// Initialize all days in the range using local dates
|
||||
for (let i = 0; i < numberOfDays; i++) {
|
||||
const currentDate = new Date(startDate);
|
||||
currentDate.setDate(startDate.getDate() + i);
|
||||
const dateString = getLocalDateString(currentDate);
|
||||
groupedTransportations[dateString] = [];
|
||||
// Initialize days (excluding last day for lodging)
|
||||
// If trip is 7/1 to 7/4 (4 days), show lodging only on 7/1, 7/2, 7/3
|
||||
const lodgingDays = numberOfDays - 1;
|
||||
|
||||
for (let i = 0; i < lodgingDays; i++) {
|
||||
const currentDate = DateTime.fromJSDate(startDate).plus({ days: i });
|
||||
const dateString = currentDate.toISODate(); // 'YYYY-MM-DD'
|
||||
groupedLodging[dateString] = [];
|
||||
}
|
||||
|
||||
transportations.forEach((transportation) => {
|
||||
if (transportation.check_in) {
|
||||
// Use local date string conversion
|
||||
const transportationDate = getLocalDateString(new Date(transportation.check_in));
|
||||
if (transportation.check_out) {
|
||||
const endDate = getLocalDateString(new Date(transportation.check_out));
|
||||
lodging.forEach((hotel) => {
|
||||
if (hotel.check_in) {
|
||||
// Check if it's all-day: start has 00:00:00 AND (no end OR end also has 00:00:00)
|
||||
const startHasZeros = hotel.check_in.includes('T00:00:00');
|
||||
const endHasZeros = hotel.check_out ? hotel.check_out.includes('T00:00:00') : true;
|
||||
const isAllDay = startHasZeros && endHasZeros;
|
||||
|
||||
// Loop through all days and include transportation if it falls within the transportation date range
|
||||
for (let i = 0; i < numberOfDays; i++) {
|
||||
const currentDate = new Date(startDate);
|
||||
currentDate.setDate(startDate.getDate() + i);
|
||||
const dateString = getLocalDateString(currentDate);
|
||||
let startDT: DateTime;
|
||||
let endDT: DateTime;
|
||||
|
||||
if (dateString >= transportationDate && dateString <= endDate) {
|
||||
groupedTransportations[dateString].push(transportation);
|
||||
}
|
||||
if (isAllDay) {
|
||||
// For all-day events, extract just the date part and ignore timezone
|
||||
const dateOnly = hotel.check_in.split('T')[0]; // Get 'YYYY-MM-DD'
|
||||
startDT = DateTime.fromISO(dateOnly); // This creates a date without time/timezone
|
||||
|
||||
endDT = hotel.check_out ? DateTime.fromISO(hotel.check_out.split('T')[0]) : startDT;
|
||||
} else {
|
||||
// For timed events, use timezone conversion
|
||||
startDT = DateTime.fromISO(hotel.check_in, {
|
||||
zone: hotel.timezone ?? 'UTC'
|
||||
});
|
||||
|
||||
endDT = hotel.check_out
|
||||
? DateTime.fromISO(hotel.check_out, {
|
||||
zone: hotel.timezone ?? 'UTC'
|
||||
})
|
||||
: startDT;
|
||||
}
|
||||
|
||||
const startDateStr = startDT.toISODate();
|
||||
const endDateStr = endDT.toISODate();
|
||||
|
||||
// Loop through lodging days only (excluding last day)
|
||||
for (let i = 0; i < lodgingDays; i++) {
|
||||
const currentDate = DateTime.fromJSDate(startDate).plus({ days: i });
|
||||
const currentDateStr = currentDate.toISODate();
|
||||
|
||||
// Show lodging on days where check-in occurs through the day before check-out
|
||||
// For lodging, we typically want to show it on the nights you're staying
|
||||
if (currentDateStr >= startDateStr && currentDateStr < endDateStr) {
|
||||
groupedLodging[currentDateStr]?.push(hotel);
|
||||
}
|
||||
} else if (groupedTransportations[transportationDate]) {
|
||||
groupedTransportations[transportationDate].push(transportation);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return groupedTransportations;
|
||||
return groupedLodging;
|
||||
}
|
||||
|
||||
export function groupNotesByDate(
|
||||
|
@ -292,6 +333,13 @@ export function groupChecklistsByDate(
|
|||
return groupedChecklists;
|
||||
}
|
||||
|
||||
// Helper to check if a given date string represents midnight (all-day)
|
||||
// Improved isAllDay function to handle different ISO date formats
|
||||
export function isAllDay(dateStr: string): boolean {
|
||||
// Check for various midnight formats in UTC
|
||||
return dateStr.endsWith('T00:00:00Z') || dateStr.endsWith('T00:00:00.000Z');
|
||||
}
|
||||
|
||||
export function continentCodeToString(code: string) {
|
||||
switch (code) {
|
||||
case 'AF':
|
||||
|
|
|
@ -1042,7 +1042,7 @@
|
|||
numberOfDays + 1
|
||||
)[dateString] || []}
|
||||
{@const dayLodging =
|
||||
groupLodgingByDate(lodging, new Date(collection.start_date), numberOfDays)[
|
||||
groupLodgingByDate(lodging, new Date(collection.start_date), numberOfDays + 1)[
|
||||
dateString
|
||||
] || []}
|
||||
{@const dayNotes =
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue