From b30d6df964f6b8984d4e2c952180883d13bc88e9 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Sat, 10 May 2025 10:47:00 -0400 Subject: [PATCH] Enhance timezone handling in AdventureModal and DateRangeCollapse components; add support for departure and arrival timezones in the TimezoneSelector and update localization for new timezone labels. --- .../src/lib/components/AdventureModal.svelte | 48 ------- .../lib/components/DateRangeCollapse.svelte | 125 +++++++++++------- .../lib/components/TimezoneSelector.svelte | 20 +-- frontend/src/locales/en.json | 2 + 4 files changed, 94 insertions(+), 101 deletions(-) diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index 016e0f1..37af876 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -390,54 +390,6 @@ } } - let new_start_date: string = ''; - let new_end_date: string = ''; - let new_notes: string = ''; - - // Function to add a new visit. - function addNewVisit() { - // If an end date isn’t provided, assume it’s the same as start. - if (new_start_date && !new_end_date) { - new_end_date = new_start_date; - } - if (new_start_date > new_end_date) { - addToast('error', $t('adventures.start_before_end_error')); - return; - } - if (new_end_date && !new_start_date) { - addToast('error', $t('adventures.no_start_date')); - return; - } - // Convert input to UTC if not already. - if (new_start_date && !new_start_date.includes('Z')) { - new_start_date = new Date(new_start_date).toISOString(); - } - if (new_end_date && !new_end_date.includes('Z')) { - new_end_date = new Date(new_end_date).toISOString(); - } - - // If the visit is all day, force the times to midnight. - if (allDay) { - new_start_date = new_start_date.split('T')[0] + 'T00:00:00.000Z'; - new_end_date = new_end_date.split('T')[0] + 'T00:00:00.000Z'; - } - - adventure.visits = [ - ...adventure.visits, - { - start_date: new_start_date, - end_date: new_end_date, - notes: new_notes, - id: '' // or generate an id as needed - } - ]; - - // Clear the input fields. - new_start_date = ''; - new_end_date = ''; - new_notes = ''; - } - function close() { dispatch('close'); } diff --git a/frontend/src/lib/components/DateRangeCollapse.svelte b/frontend/src/lib/components/DateRangeCollapse.svelte index 87fd957..71b5834 100644 --- a/frontend/src/lib/components/DateRangeCollapse.svelte +++ b/frontend/src/lib/components/DateRangeCollapse.svelte @@ -10,7 +10,8 @@ export let type: 'adventure' | 'transportation' | 'lodging' = 'adventure'; // Initialize with browser's timezone - let selectedTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; + let selectedStartTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; + let selectedEndTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; let allDay: boolean = false; @@ -18,15 +19,14 @@ export let utcStartDate: string | null = null; export let utcEndDate: string | null = null; - console.log('UTC Start Date:', utcStartDate); - console.log('UTC End Date:', utcEndDate); - export let note: string | null = null; type Visit = { id: string; start_date: string; end_date: string; notes: string; + start_timezone?: string; + end_timezone?: string; }; export let visits: Visit[] | null = null; @@ -42,17 +42,15 @@ let isEditing = false; // Disable reactivity when editing onMount(async () => { - console.log('Selected timezone:', selectedTimezone); - console.log('UTC Start Date:', utcStartDate); - console.log('UTC End Date:', utcEndDate); - // Initialize UTC dates from transportationToEdit if available + // Initialize UTC dates localStartDate = updateLocalDate({ utcDate: utcStartDate, - timezone: selectedTimezone + timezone: selectedStartTimezone }).localDate; + localEndDate = updateLocalDate({ utcDate: utcEndDate, - timezone: selectedTimezone + timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone }).localDate; }); @@ -82,12 +80,12 @@ } else { const start = updateLocalDate({ utcDate: utcStartDate, - timezone: selectedTimezone + timezone: selectedStartTimezone }).localDate; const end = updateLocalDate({ utcDate: utcEndDate, - timezone: selectedTimezone + timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone }).localDate; localStartDate = start; @@ -99,16 +97,34 @@ function handleLocalDateChange() { utcStartDate = updateUTCDate({ localDate: localStartDate, - timezone: selectedTimezone, + timezone: selectedStartTimezone, allDay }).utcDate; utcEndDate = updateUTCDate({ localDate: localEndDate, - timezone: selectedTimezone, + timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone, allDay }).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 + if (type === 'transportation') { + newVisit.start_timezone = selectedStartTimezone; + newVisit.end_timezone = selectedEndTimezone; + } + + return newVisit; + }
@@ -117,14 +133,32 @@ {$t('adventures.date_information')}
- -
- +

{$t('navbar.settings')}

- + {#if type === 'transportation'} + +
+
+ + +
+ +
+ + +
+
+ {:else} + + + {/if}
@@ -145,21 +179,21 @@ } utcStartDate = updateUTCDate({ localDate: localStartDate, - timezone: selectedTimezone, + timezone: selectedStartTimezone, allDay }).utcDate; utcEndDate = updateUTCDate({ localDate: localEndDate, - timezone: selectedTimezone, + timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone, allDay }).utcDate; localStartDate = updateLocalDate({ utcDate: utcStartDate, - timezone: selectedTimezone + timezone: selectedStartTimezone }).localDate; localEndDate = updateLocalDate({ utcDate: utcEndDate, - timezone: selectedTimezone + timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone }).localDate; }} /> @@ -185,7 +219,9 @@
{#if allDay} @@ -217,7 +253,7 @@ {#if localStartDate}
{#if allDay} @@ -267,12 +303,7 @@ class="btn btn-primary mb-2" type="button" on:click={() => { - const newVisit = { - id: crypto.randomUUID(), - start_date: utcStartDate ?? '', - end_date: utcEndDate ?? utcStartDate ?? '', - notes: note ?? '' - }; + const newVisit = createVisitObject(); // Ensure reactivity by assigning a *new* array if (visits) { @@ -345,7 +376,12 @@ {/if}

- + + {#if visit.start_timezone && visit.end_timezone && visit.start_timezone !== visit.end_timezone} +

+ {visit.start_timezone} → {visit.end_timezone} +

+ {/if} {#if visit.notes}

@@ -362,24 +398,24 @@ const isAllDayEvent = isAllDay(visit.start_date); allDay = isAllDayEvent; + // Set timezone information if available + if (visit.start_timezone) selectedStartTimezone = visit.start_timezone; + if (visit.end_timezone) selectedEndTimezone = visit.end_timezone; + if (isAllDayEvent) { localStartDate = visit.start_date.split('T')[0]; localEndDate = visit.end_date.split('T')[0]; } else { - const startDate = new Date(visit.start_date); - const endDate = new Date(visit.end_date); + // Update with timezone awareness + localStartDate = updateLocalDate({ + utcDate: visit.start_date, + timezone: selectedStartTimezone + }).localDate; - localStartDate = `${startDate.getFullYear()}-${String( - startDate.getMonth() + 1 - ).padStart(2, '0')}-${String(startDate.getDate()).padStart(2, '0')}T${String( - startDate.getHours() - ).padStart(2, '0')}:${String(startDate.getMinutes()).padStart(2, '0')}`; - - localEndDate = `${endDate.getFullYear()}-${String( - endDate.getMonth() + 1 - ).padStart(2, '0')}-${String(endDate.getDate()).padStart(2, '0')}T${String( - endDate.getHours() - ).padStart(2, '0')}:${String(endDate.getMinutes()).padStart(2, '0')}`; + localEndDate = updateLocalDate({ + utcDate: visit.end_date, + timezone: visit.end_timezone || selectedStartTimezone + }).localDate; } // remove it from visits @@ -391,7 +427,6 @@ constrainDates = true; utcStartDate = visit.start_date; utcEndDate = visit.end_date; - type = 'adventure'; setTimeout(() => { isEditing = false; diff --git a/frontend/src/lib/components/TimezoneSelector.svelte b/frontend/src/lib/components/TimezoneSelector.svelte index 4e40d61..b0e52fd 100644 --- a/frontend/src/lib/components/TimezoneSelector.svelte +++ b/frontend/src/lib/components/TimezoneSelector.svelte @@ -3,10 +3,12 @@ import { onMount } from 'svelte'; export let selectedTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; + // Generate a unique ID for this component instance + const instanceId = `tz-selector-${crypto.randomUUID().substring(0, 8)}`; let dropdownOpen = false; let searchQuery = ''; - let searchInput: HTMLInputElement; + let searchInput: HTMLInputElement | null = null; const timezones = Intl.supportedValuesOf('timeZone'); // Filter timezones based on search query @@ -20,10 +22,12 @@ searchQuery = ''; } - // Focus search input when dropdown opens + // Focus search input when dropdown opens - with proper null check $: if (dropdownOpen && searchInput) { // Use setTimeout to delay focus until after the element is rendered - setTimeout(() => searchInput.focus(), 0); + setTimeout(() => { + if (searchInput) searchInput.focus(); + }, 0); } function handleKeydown(event: KeyboardEvent, tz?: string) { @@ -40,7 +44,7 @@ // Close dropdown if clicked outside onMount(() => { const handleClickOutside = (e: MouseEvent) => { - const dropdown = document.getElementById('tz-selector'); + const dropdown = document.getElementById(instanceId); if (dropdown && !dropdown.contains(e.target as Node)) dropdownOpen = false; }; document.addEventListener('click', handleClickOutside); @@ -48,14 +52,14 @@ }); -

-