1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-23 14:59:36 +02:00

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.

This commit is contained in:
Sean Morley 2025-05-10 10:47:00 -04:00
parent 89c4f1058a
commit b30d6df964
4 changed files with 94 additions and 101 deletions

View file

@ -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 isnt provided, assume its 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');
}

View file

@ -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;
}
</script>
<div class="collapse collapse-plus bg-base-200 mb-4 rounded-lg">
@ -117,14 +133,32 @@
{$t('adventures.date_information')}
</div>
<div class="collapse-content">
<!-- Timezone Selector -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"></div>
<!-- Timezone Selector Section -->
<div class="rounded-xl border border-base-300 bg-base-100 p-4 space-y-4 shadow-sm mb-4">
<!-- Group Header -->
<h3 class="text-md font-semibold">{$t('navbar.settings')}</h3>
<TimezoneSelector bind:selectedTimezone />
{#if type === 'transportation'}
<!-- Dual timezone selectors for transportation -->
<div class="space-y-4">
<div>
<label class="text-sm font-medium block mb-1">
{$t('adventures.departure_timezone')}
</label>
<TimezoneSelector bind:selectedTimezone={selectedStartTimezone} />
</div>
<div>
<label class="text-sm font-medium block mb-1">
{$t('adventures.arrival_timezone')}
</label>
<TimezoneSelector bind:selectedTimezone={selectedEndTimezone} />
</div>
</div>
{:else}
<!-- Single timezone selector for other types -->
<TimezoneSelector bind:selectedTimezone={selectedStartTimezone} />
{/if}
<!-- All Day Toggle -->
<div class="flex justify-between items-center">
@ -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 @@
<!-- Start Date -->
<div class="space-y-2">
<label for="date" class="text-sm font-medium">
{$t('adventures.start_date')}
{type === 'transportation'
? $t('adventures.departure_date')
: $t('adventures.start_date')}
</label>
{#if allDay}
@ -217,7 +253,7 @@
{#if localStartDate}
<div class="space-y-2">
<label for="end_date" class="text-sm font-medium">
{$t('adventures.end_date')}
{type === 'transportation' ? $t('adventures.arrival_date') : $t('adventures.end_date')}
</label>
{#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}
</p>
<!-- If the selected timezone is not the current one show the timezone + the time converted there -->
<!-- Display timezone information for transportation visits -->
{#if visit.start_timezone && visit.end_timezone && visit.start_timezone !== visit.end_timezone}
<p class="text-xs text-base-content">
{visit.start_timezone}{visit.end_timezone}
</p>
{/if}
{#if visit.notes}
<p class="text-sm text-base-content opacity-70 italic">
@ -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;

View file

@ -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 @@
});
</script>
<div class="form-control w-full max-w-xs relative" id="tz-selector">
<label class="label" for="timezone-display">
<div class="form-control w-full max-w-xs relative" id={instanceId}>
<label class="label" for={`timezone-display-${instanceId}`}>
<span class="label-text">{$t('adventures.timezone')}</span>
</label>
<!-- Trigger -->
<div
id="timezone-display"
id={`timezone-display-${instanceId}`}
tabindex="0"
role="button"
aria-haspopup="listbox"
@ -82,7 +86,7 @@
<div
class="absolute mt-1 z-10 bg-base-100 shadow-lg rounded-box w-full max-h-60 overflow-y-auto"
role="listbox"
aria-labelledby="timezone-display"
aria-labelledby={`timezone-display-${instanceId}`}
>
<!-- Search -->
<div class="sticky top-0 bg-base-100 p-2 border-b">

View file

@ -65,6 +65,8 @@
"invalid_date_range": "Invalid date range",
"timezone": "Timezone",
"no_visits": "No visits",
"departure_timezone": "Departure Timezone",
"arrival_timezone": "Arrival Timezone",
"no_image_found": "No image found",
"collection_link_error": "Error linking adventure to collection",
"adventure_delete_confirm": "Are you sure you want to delete this adventure? This action cannot be undone.",