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:
parent
89c4f1058a
commit
b30d6df964
4 changed files with 94 additions and 101 deletions
|
@ -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() {
|
function close() {
|
||||||
dispatch('close');
|
dispatch('close');
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
export let type: 'adventure' | 'transportation' | 'lodging' = 'adventure';
|
export let type: 'adventure' | 'transportation' | 'lodging' = 'adventure';
|
||||||
|
|
||||||
// Initialize with browser's timezone
|
// 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;
|
let allDay: boolean = false;
|
||||||
|
|
||||||
|
@ -18,15 +19,14 @@
|
||||||
export let utcStartDate: string | null = null;
|
export let utcStartDate: string | null = null;
|
||||||
export let utcEndDate: 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;
|
export let note: string | null = null;
|
||||||
type Visit = {
|
type Visit = {
|
||||||
id: string;
|
id: string;
|
||||||
start_date: string;
|
start_date: string;
|
||||||
end_date: string;
|
end_date: string;
|
||||||
notes: string;
|
notes: string;
|
||||||
|
start_timezone?: string;
|
||||||
|
end_timezone?: string;
|
||||||
};
|
};
|
||||||
export let visits: Visit[] | null = null;
|
export let visits: Visit[] | null = null;
|
||||||
|
|
||||||
|
@ -42,17 +42,15 @@
|
||||||
let isEditing = false; // Disable reactivity when editing
|
let isEditing = false; // Disable reactivity when editing
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
console.log('Selected timezone:', selectedTimezone);
|
// Initialize UTC dates
|
||||||
console.log('UTC Start Date:', utcStartDate);
|
|
||||||
console.log('UTC End Date:', utcEndDate);
|
|
||||||
// Initialize UTC dates from transportationToEdit if available
|
|
||||||
localStartDate = updateLocalDate({
|
localStartDate = updateLocalDate({
|
||||||
utcDate: utcStartDate,
|
utcDate: utcStartDate,
|
||||||
timezone: selectedTimezone
|
timezone: selectedStartTimezone
|
||||||
}).localDate;
|
}).localDate;
|
||||||
|
|
||||||
localEndDate = updateLocalDate({
|
localEndDate = updateLocalDate({
|
||||||
utcDate: utcEndDate,
|
utcDate: utcEndDate,
|
||||||
timezone: selectedTimezone
|
timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone
|
||||||
}).localDate;
|
}).localDate;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -82,12 +80,12 @@
|
||||||
} else {
|
} else {
|
||||||
const start = updateLocalDate({
|
const start = updateLocalDate({
|
||||||
utcDate: utcStartDate,
|
utcDate: utcStartDate,
|
||||||
timezone: selectedTimezone
|
timezone: selectedStartTimezone
|
||||||
}).localDate;
|
}).localDate;
|
||||||
|
|
||||||
const end = updateLocalDate({
|
const end = updateLocalDate({
|
||||||
utcDate: utcEndDate,
|
utcDate: utcEndDate,
|
||||||
timezone: selectedTimezone
|
timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone
|
||||||
}).localDate;
|
}).localDate;
|
||||||
|
|
||||||
localStartDate = start;
|
localStartDate = start;
|
||||||
|
@ -99,16 +97,34 @@
|
||||||
function handleLocalDateChange() {
|
function handleLocalDateChange() {
|
||||||
utcStartDate = updateUTCDate({
|
utcStartDate = updateUTCDate({
|
||||||
localDate: localStartDate,
|
localDate: localStartDate,
|
||||||
timezone: selectedTimezone,
|
timezone: selectedStartTimezone,
|
||||||
allDay
|
allDay
|
||||||
}).utcDate;
|
}).utcDate;
|
||||||
|
|
||||||
utcEndDate = updateUTCDate({
|
utcEndDate = updateUTCDate({
|
||||||
localDate: localEndDate,
|
localDate: localEndDate,
|
||||||
timezone: selectedTimezone,
|
timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone,
|
||||||
allDay
|
allDay
|
||||||
}).utcDate;
|
}).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>
|
</script>
|
||||||
|
|
||||||
<div class="collapse collapse-plus bg-base-200 mb-4 rounded-lg">
|
<div class="collapse collapse-plus bg-base-200 mb-4 rounded-lg">
|
||||||
|
@ -117,14 +133,32 @@
|
||||||
{$t('adventures.date_information')}
|
{$t('adventures.date_information')}
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse-content">
|
<div class="collapse-content">
|
||||||
<!-- Timezone Selector -->
|
<!-- Timezone Selector Section -->
|
||||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"></div>
|
|
||||||
|
|
||||||
<div class="rounded-xl border border-base-300 bg-base-100 p-4 space-y-4 shadow-sm mb-4">
|
<div class="rounded-xl border border-base-300 bg-base-100 p-4 space-y-4 shadow-sm mb-4">
|
||||||
<!-- Group Header -->
|
<!-- Group Header -->
|
||||||
<h3 class="text-md font-semibold">{$t('navbar.settings')}</h3>
|
<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 -->
|
<!-- All Day Toggle -->
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
|
@ -145,21 +179,21 @@
|
||||||
}
|
}
|
||||||
utcStartDate = updateUTCDate({
|
utcStartDate = updateUTCDate({
|
||||||
localDate: localStartDate,
|
localDate: localStartDate,
|
||||||
timezone: selectedTimezone,
|
timezone: selectedStartTimezone,
|
||||||
allDay
|
allDay
|
||||||
}).utcDate;
|
}).utcDate;
|
||||||
utcEndDate = updateUTCDate({
|
utcEndDate = updateUTCDate({
|
||||||
localDate: localEndDate,
|
localDate: localEndDate,
|
||||||
timezone: selectedTimezone,
|
timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone,
|
||||||
allDay
|
allDay
|
||||||
}).utcDate;
|
}).utcDate;
|
||||||
localStartDate = updateLocalDate({
|
localStartDate = updateLocalDate({
|
||||||
utcDate: utcStartDate,
|
utcDate: utcStartDate,
|
||||||
timezone: selectedTimezone
|
timezone: selectedStartTimezone
|
||||||
}).localDate;
|
}).localDate;
|
||||||
localEndDate = updateLocalDate({
|
localEndDate = updateLocalDate({
|
||||||
utcDate: utcEndDate,
|
utcDate: utcEndDate,
|
||||||
timezone: selectedTimezone
|
timezone: type === 'transportation' ? selectedEndTimezone : selectedStartTimezone
|
||||||
}).localDate;
|
}).localDate;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -185,7 +219,9 @@
|
||||||
<!-- Start Date -->
|
<!-- Start Date -->
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label for="date" class="text-sm font-medium">
|
<label for="date" class="text-sm font-medium">
|
||||||
{$t('adventures.start_date')}
|
{type === 'transportation'
|
||||||
|
? $t('adventures.departure_date')
|
||||||
|
: $t('adventures.start_date')}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{#if allDay}
|
{#if allDay}
|
||||||
|
@ -217,7 +253,7 @@
|
||||||
{#if localStartDate}
|
{#if localStartDate}
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label for="end_date" class="text-sm font-medium">
|
<label for="end_date" class="text-sm font-medium">
|
||||||
{$t('adventures.end_date')}
|
{type === 'transportation' ? $t('adventures.arrival_date') : $t('adventures.end_date')}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{#if allDay}
|
{#if allDay}
|
||||||
|
@ -267,12 +303,7 @@
|
||||||
class="btn btn-primary mb-2"
|
class="btn btn-primary mb-2"
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
const newVisit = {
|
const newVisit = createVisitObject();
|
||||||
id: crypto.randomUUID(),
|
|
||||||
start_date: utcStartDate ?? '',
|
|
||||||
end_date: utcEndDate ?? utcStartDate ?? '',
|
|
||||||
notes: note ?? ''
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ensure reactivity by assigning a *new* array
|
// Ensure reactivity by assigning a *new* array
|
||||||
if (visits) {
|
if (visits) {
|
||||||
|
@ -345,7 +376,12 @@
|
||||||
{/if}
|
{/if}
|
||||||
</p>
|
</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}
|
{#if visit.notes}
|
||||||
<p class="text-sm text-base-content opacity-70 italic">
|
<p class="text-sm text-base-content opacity-70 italic">
|
||||||
|
@ -362,24 +398,24 @@
|
||||||
const isAllDayEvent = isAllDay(visit.start_date);
|
const isAllDayEvent = isAllDay(visit.start_date);
|
||||||
allDay = isAllDayEvent;
|
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) {
|
if (isAllDayEvent) {
|
||||||
localStartDate = visit.start_date.split('T')[0];
|
localStartDate = visit.start_date.split('T')[0];
|
||||||
localEndDate = visit.end_date.split('T')[0];
|
localEndDate = visit.end_date.split('T')[0];
|
||||||
} else {
|
} else {
|
||||||
const startDate = new Date(visit.start_date);
|
// Update with timezone awareness
|
||||||
const endDate = new Date(visit.end_date);
|
localStartDate = updateLocalDate({
|
||||||
|
utcDate: visit.start_date,
|
||||||
|
timezone: selectedStartTimezone
|
||||||
|
}).localDate;
|
||||||
|
|
||||||
localStartDate = `${startDate.getFullYear()}-${String(
|
localEndDate = updateLocalDate({
|
||||||
startDate.getMonth() + 1
|
utcDate: visit.end_date,
|
||||||
).padStart(2, '0')}-${String(startDate.getDate()).padStart(2, '0')}T${String(
|
timezone: visit.end_timezone || selectedStartTimezone
|
||||||
startDate.getHours()
|
}).localDate;
|
||||||
).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')}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove it from visits
|
// remove it from visits
|
||||||
|
@ -391,7 +427,6 @@
|
||||||
constrainDates = true;
|
constrainDates = true;
|
||||||
utcStartDate = visit.start_date;
|
utcStartDate = visit.start_date;
|
||||||
utcEndDate = visit.end_date;
|
utcEndDate = visit.end_date;
|
||||||
type = 'adventure';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isEditing = false;
|
isEditing = false;
|
||||||
|
|
|
@ -3,10 +3,12 @@
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
export let selectedTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
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 dropdownOpen = false;
|
||||||
let searchQuery = '';
|
let searchQuery = '';
|
||||||
let searchInput: HTMLInputElement;
|
let searchInput: HTMLInputElement | null = null;
|
||||||
const timezones = Intl.supportedValuesOf('timeZone');
|
const timezones = Intl.supportedValuesOf('timeZone');
|
||||||
|
|
||||||
// Filter timezones based on search query
|
// Filter timezones based on search query
|
||||||
|
@ -20,10 +22,12 @@
|
||||||
searchQuery = '';
|
searchQuery = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focus search input when dropdown opens
|
// Focus search input when dropdown opens - with proper null check
|
||||||
$: if (dropdownOpen && searchInput) {
|
$: if (dropdownOpen && searchInput) {
|
||||||
// Use setTimeout to delay focus until after the element is rendered
|
// 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) {
|
function handleKeydown(event: KeyboardEvent, tz?: string) {
|
||||||
|
@ -40,7 +44,7 @@
|
||||||
// Close dropdown if clicked outside
|
// Close dropdown if clicked outside
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const handleClickOutside = (e: MouseEvent) => {
|
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;
|
if (dropdown && !dropdown.contains(e.target as Node)) dropdownOpen = false;
|
||||||
};
|
};
|
||||||
document.addEventListener('click', handleClickOutside);
|
document.addEventListener('click', handleClickOutside);
|
||||||
|
@ -48,14 +52,14 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="form-control w-full max-w-xs relative" id="tz-selector">
|
<div class="form-control w-full max-w-xs relative" id={instanceId}>
|
||||||
<label class="label" for="timezone-display">
|
<label class="label" for={`timezone-display-${instanceId}`}>
|
||||||
<span class="label-text">{$t('adventures.timezone')}</span>
|
<span class="label-text">{$t('adventures.timezone')}</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<!-- Trigger -->
|
<!-- Trigger -->
|
||||||
<div
|
<div
|
||||||
id="timezone-display"
|
id={`timezone-display-${instanceId}`}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
role="button"
|
role="button"
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
|
@ -82,7 +86,7 @@
|
||||||
<div
|
<div
|
||||||
class="absolute mt-1 z-10 bg-base-100 shadow-lg rounded-box w-full max-h-60 overflow-y-auto"
|
class="absolute mt-1 z-10 bg-base-100 shadow-lg rounded-box w-full max-h-60 overflow-y-auto"
|
||||||
role="listbox"
|
role="listbox"
|
||||||
aria-labelledby="timezone-display"
|
aria-labelledby={`timezone-display-${instanceId}`}
|
||||||
>
|
>
|
||||||
<!-- Search -->
|
<!-- Search -->
|
||||||
<div class="sticky top-0 bg-base-100 p-2 border-b">
|
<div class="sticky top-0 bg-base-100 p-2 border-b">
|
||||||
|
|
|
@ -65,6 +65,8 @@
|
||||||
"invalid_date_range": "Invalid date range",
|
"invalid_date_range": "Invalid date range",
|
||||||
"timezone": "Timezone",
|
"timezone": "Timezone",
|
||||||
"no_visits": "No visits",
|
"no_visits": "No visits",
|
||||||
|
"departure_timezone": "Departure Timezone",
|
||||||
|
"arrival_timezone": "Arrival Timezone",
|
||||||
"no_image_found": "No image found",
|
"no_image_found": "No image found",
|
||||||
"collection_link_error": "Error linking adventure to collection",
|
"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.",
|
"adventure_delete_confirm": "Are you sure you want to delete this adventure? This action cannot be undone.",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue