mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-22 22:39:36 +02:00
Add timezone support for visits, transportation, and lodging
- Introduced TIMEZONES constant in models.py to store valid timezone options. - Updated Visit, Transportation, and Lodging models to include timezone fields. - Modified serializers to include timezone fields in VisitSerializer, TransportationSerializer, and LodgingSerializer. - Enhanced DateRangeCollapse component to handle timezone selection and formatting. - Implemented timezone formatting functions in LodgingCard and TransportationCard components. - Updated LodgingModal and TransportationModal to bind timezone data. - Added VALID_TIMEZONES to dateUtils for consistent timezone management across the application.
This commit is contained in:
parent
b30d6df964
commit
1323d91e32
12 changed files with 1056 additions and 45 deletions
|
@ -10,8 +10,8 @@
|
|||
export let type: 'adventure' | 'transportation' | 'lodging' = 'adventure';
|
||||
|
||||
// Initialize with browser's timezone
|
||||
let selectedStartTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
let selectedEndTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
export let selectedStartTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
export let selectedEndTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
let allDay: boolean = false;
|
||||
|
||||
|
@ -25,10 +25,19 @@
|
|||
start_date: string;
|
||||
end_date: string;
|
||||
notes: string;
|
||||
start_timezone?: string;
|
||||
end_timezone?: string;
|
||||
timezone: string | null;
|
||||
};
|
||||
export let visits: Visit[] | null = null;
|
||||
|
||||
type TransportationVisit = {
|
||||
id: string;
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
notes: string;
|
||||
start_timezone: string;
|
||||
end_timezone: string;
|
||||
};
|
||||
|
||||
export let visits: (Visit | TransportationVisit)[] | null = null;
|
||||
|
||||
// Local display values
|
||||
let localStartDate: string = '';
|
||||
|
@ -52,6 +61,13 @@
|
|||
utcDate: utcEndDate,
|
||||
timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone
|
||||
}).localDate;
|
||||
|
||||
if (!selectedStartTimezone) {
|
||||
selectedStartTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
}
|
||||
if (!selectedEndTimezone) {
|
||||
selectedEndTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
}
|
||||
});
|
||||
|
||||
// Set the full date range for constraining purposes
|
||||
|
@ -60,6 +76,22 @@
|
|||
fullEndDate = `${collection.end_date}T23:59`;
|
||||
}
|
||||
|
||||
function formatDateInTimezone(utcDate: string, timezone: string): string {
|
||||
try {
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
timeZone: timezone,
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
}).format(new Date(utcDate));
|
||||
} catch {
|
||||
return new Date(utcDate).toLocaleString(); // fallback
|
||||
}
|
||||
}
|
||||
|
||||
// Get constraint dates in the right format based on allDay setting
|
||||
$: constraintStartDate = allDay
|
||||
? fullStartDate && fullStartDate.includes('T')
|
||||
|
@ -108,22 +140,27 @@
|
|||
}).utcDate;
|
||||
}
|
||||
|
||||
// Create a visit object with appropriate timezone information
|
||||
function createVisitObject() {
|
||||
const newVisit: Visit = {
|
||||
id: crypto.randomUUID(),
|
||||
start_date: utcStartDate ?? '',
|
||||
end_date: utcEndDate ?? utcStartDate ?? '',
|
||||
notes: note ?? ''
|
||||
};
|
||||
|
||||
// For transportation, add timezone information
|
||||
function createVisitObject(): Visit | TransportationVisit {
|
||||
if (type === 'transportation') {
|
||||
newVisit.start_timezone = selectedStartTimezone;
|
||||
newVisit.end_timezone = selectedEndTimezone;
|
||||
const transportVisit: TransportationVisit = {
|
||||
id: crypto.randomUUID(),
|
||||
start_date: utcStartDate ?? '',
|
||||
end_date: utcEndDate ?? utcStartDate ?? '',
|
||||
notes: note ?? '',
|
||||
start_timezone: selectedStartTimezone,
|
||||
end_timezone: selectedEndTimezone
|
||||
};
|
||||
return transportVisit;
|
||||
} else {
|
||||
const regularVisit: Visit = {
|
||||
id: crypto.randomUUID(),
|
||||
start_date: utcStartDate ?? '',
|
||||
end_date: utcEndDate ?? utcStartDate ?? '',
|
||||
notes: note ?? '',
|
||||
timezone: selectedStartTimezone
|
||||
};
|
||||
return regularVisit;
|
||||
}
|
||||
|
||||
return newVisit;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -369,15 +406,31 @@
|
|||
{#if isAllDay(visit.start_date)}
|
||||
<span class="badge badge-outline mr-2">{$t('adventures.all_day')}</span>
|
||||
{visit.start_date.split('T')[0]} – {visit.end_date.split('T')[0]}
|
||||
{:else if 'start_timezone' in visit}
|
||||
{formatDateInTimezone(visit.start_date, visit.start_timezone)} – {formatDateInTimezone(
|
||||
visit.end_date,
|
||||
visit.end_timezone
|
||||
)}
|
||||
{:else if visit.timezone}
|
||||
{formatDateInTimezone(visit.start_date, visit.timezone)} – {formatDateInTimezone(
|
||||
visit.end_date,
|
||||
visit.timezone
|
||||
)}
|
||||
{:else}
|
||||
{new Date(visit.start_date).toLocaleString()} – {new Date(
|
||||
visit.end_date
|
||||
).toLocaleString()}
|
||||
<!-- showe timezones badge -->
|
||||
{/if}
|
||||
{#if 'timezone' in visit && visit.timezone}
|
||||
<span class="badge badge-outline ml-2">{visit.timezone}</span>
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
<!-- -->
|
||||
|
||||
<!-- Display timezone information for transportation visits -->
|
||||
{#if visit.start_timezone && visit.end_timezone && visit.start_timezone !== visit.end_timezone}
|
||||
{#if 'start_timezone' in visit && 'end_timezone' in visit && visit.start_timezone !== visit.end_timezone}
|
||||
<p class="text-xs text-base-content">
|
||||
{visit.start_timezone} → {visit.end_timezone}
|
||||
</p>
|
||||
|
@ -399,8 +452,14 @@
|
|||
allDay = isAllDayEvent;
|
||||
|
||||
// Set timezone information if available
|
||||
if (visit.start_timezone) selectedStartTimezone = visit.start_timezone;
|
||||
if (visit.end_timezone) selectedEndTimezone = visit.end_timezone;
|
||||
if ('start_timezone' in visit) {
|
||||
// TransportationVisit
|
||||
selectedStartTimezone = visit.start_timezone;
|
||||
selectedEndTimezone = visit.end_timezone;
|
||||
} else if (visit.timezone) {
|
||||
// Visit
|
||||
selectedStartTimezone = visit.timezone;
|
||||
}
|
||||
|
||||
if (isAllDayEvent) {
|
||||
localStartDate = visit.start_date.split('T')[0];
|
||||
|
@ -414,7 +473,8 @@
|
|||
|
||||
localEndDate = updateLocalDate({
|
||||
utcDate: visit.end_date,
|
||||
timezone: visit.end_timezone || selectedStartTimezone
|
||||
timezone:
|
||||
'end_timezone' in visit ? visit.end_timezone : selectedStartTimezone
|
||||
}).localDate;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,23 @@
|
|||
}
|
||||
}
|
||||
|
||||
function formatDateInTimezone(utcDate: string, timezone?: string): string {
|
||||
if (!utcDate) return '';
|
||||
try {
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
timeZone: timezone,
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
}).format(new Date(utcDate));
|
||||
} catch {
|
||||
return new Date(utcDate).toLocaleString();
|
||||
}
|
||||
}
|
||||
|
||||
export let lodging: Lodging;
|
||||
export let user: User | null = null;
|
||||
export let collection: Collection | null = null;
|
||||
|
@ -119,21 +136,9 @@
|
|||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-sm">{$t('adventures.dates')}:</span>
|
||||
<p>
|
||||
{new Date(lodging.check_in).toLocaleString(undefined, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric'
|
||||
})}
|
||||
-
|
||||
{new Date(lodging.check_out).toLocaleString(undefined, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric'
|
||||
})}
|
||||
{formatDateInTimezone(lodging.check_in ?? '', lodging.timezone ?? undefined)} –
|
||||
{formatDateInTimezone(lodging.check_out ?? '', lodging.timezone ?? undefined)}
|
||||
<span class="text-xs opacity-60 ml-1">({lodging.timezone})</span>
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -56,7 +56,8 @@
|
|||
is_public: lodgingToEdit?.is_public || false,
|
||||
collection: lodgingToEdit?.collection || collection.id,
|
||||
created_at: lodgingToEdit?.created_at || '',
|
||||
updated_at: lodgingToEdit?.updated_at || ''
|
||||
updated_at: lodgingToEdit?.updated_at || '',
|
||||
timezone: lodgingToEdit?.timezone || ''
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -303,6 +304,7 @@
|
|||
type="lodging"
|
||||
bind:utcStartDate={lodging.check_in}
|
||||
bind:utcEndDate={lodging.check_out}
|
||||
bind:selectedStartTimezone={lodging.timezone}
|
||||
/>
|
||||
|
||||
<!-- Location Information -->
|
||||
|
|
|
@ -18,6 +18,23 @@
|
|||
}
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function formatDateInTimezone(utcDate: string, timezone?: string): string {
|
||||
if (!utcDate) return '';
|
||||
try {
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
timeZone: timezone,
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
}).format(new Date(utcDate));
|
||||
} catch {
|
||||
return new Date(utcDate).toLocaleString();
|
||||
}
|
||||
}
|
||||
|
||||
export let transportation: Transportation;
|
||||
export let user: User | null = null;
|
||||
export let collection: Collection | null = null;
|
||||
|
@ -136,7 +153,12 @@
|
|||
{#if transportation.date}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-sm">{$t('adventures.start')}:</span>
|
||||
<p>{new Date(transportation.date).toLocaleString()}</p>
|
||||
<p>
|
||||
{formatDateInTimezone(transportation.date, transportation.start_timezone ?? undefined)}
|
||||
{#if transportation.start_timezone}
|
||||
<span class="text-xs opacity-60 ml-1">({transportation.start_timezone})</span>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -154,7 +176,15 @@
|
|||
{#if transportation.end_date}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-sm">{$t('adventures.end')}:</span>
|
||||
<p>{new Date(transportation.end_date).toLocaleString()}</p>
|
||||
<p>
|
||||
{formatDateInTimezone(
|
||||
transportation.end_date,
|
||||
transportation.end_timezone || undefined
|
||||
)}
|
||||
{#if transportation.end_timezone}
|
||||
<span class="text-xs opacity-60 ml-1">({transportation.end_timezone})</span>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -36,7 +36,9 @@
|
|||
origin_latitude: transportationToEdit?.origin_latitude || NaN,
|
||||
origin_longitude: transportationToEdit?.origin_longitude || NaN,
|
||||
destination_latitude: transportationToEdit?.destination_latitude || NaN,
|
||||
destination_longitude: transportationToEdit?.destination_longitude || NaN
|
||||
destination_longitude: transportationToEdit?.destination_longitude || NaN,
|
||||
start_timezone: transportationToEdit?.start_timezone || '',
|
||||
end_timezone: transportationToEdit?.end_timezone || ''
|
||||
};
|
||||
|
||||
let starting_airport: string = '';
|
||||
|
@ -343,6 +345,9 @@
|
|||
type="transportation"
|
||||
bind:utcStartDate={transportation.date}
|
||||
bind:utcEndDate={transportation.end_date}
|
||||
bind:selectedStartTimezone={transportation.start_timezone}
|
||||
bind:selectedEndTimezone={transportation.end_timezone}
|
||||
{collection}
|
||||
/>
|
||||
|
||||
<!-- Flight Information -->
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue