mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-22 22:39:36 +02:00
Refactor date handling components: Replace DateRangeDropdown with DateRangeCollapse
- Introduced DateRangeCollapse.svelte to manage date range selection with timezone support. - Removed DateRangeDropdown.svelte as it was redundant. - Updated LodgingModal and TransportationModal to utilize DateRangeCollapse for date selection. - Enhanced date conversion utilities to handle all-day events correctly. - Adjusted TimezoneSelector for improved accessibility and focus management. - Updated date handling logic in dateUtils.ts to support all-day events. - Modified test page to reflect changes in date range component usage.
This commit is contained in:
parent
827b150965
commit
2c50ca0b1a
8 changed files with 484 additions and 758 deletions
|
@ -92,6 +92,7 @@
|
|||
import Crown from '~icons/mdi/crown';
|
||||
import AttachmentCard from './AttachmentCard.svelte';
|
||||
import LocationDropdown from './LocationDropdown.svelte';
|
||||
import DateRangeCollapse from './DateRangeCollapse.svelte';
|
||||
let modal: HTMLDialogElement;
|
||||
|
||||
let wikiError: string = '';
|
||||
|
@ -684,237 +685,8 @@
|
|||
<ActivityComplete bind:activities={adventure.activity_types} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapse collapse-plus bg-base-200 mb-4">
|
||||
<input type="checkbox" />
|
||||
<div class="collapse-title text-xl font-medium">
|
||||
{$t('adventures.visits')} ({adventure.visits.length})
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<label class="label cursor-pointer flex items-start space-x-2">
|
||||
{#if adventure.collection && collection && collection.start_date && collection.end_date}
|
||||
<span class="label-text">{$t('adventures.date_constrain')}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary"
|
||||
id="constrain_dates"
|
||||
name="constrain_dates"
|
||||
on:change={() => (constrainDates = !constrainDates)}
|
||||
/>
|
||||
{/if}
|
||||
<span class="label-text">{$t('adventures.all_day')}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary"
|
||||
id="constrain_dates"
|
||||
name="constrain_dates"
|
||||
bind:checked={allDay}
|
||||
/>
|
||||
</label>
|
||||
<div class="flex gap-2 mb-1">
|
||||
{#if !allDay}
|
||||
<input
|
||||
type="datetime-local"
|
||||
class="input input-bordered w-full"
|
||||
placeholder={$t('adventures.start_date')}
|
||||
min={constrainDates ? fullStartDate : ''}
|
||||
max={constrainDates ? fullEndDate : ''}
|
||||
bind:value={new_start_date}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
addNewVisit();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
type="datetime-local"
|
||||
class="input input-bordered w-full"
|
||||
placeholder={$t('adventures.end_date')}
|
||||
bind:value={new_end_date}
|
||||
min={constrainDates ? fullStartDate : ''}
|
||||
max={constrainDates ? fullEndDate : ''}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
addNewVisit();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
type="date"
|
||||
class="input input-bordered w-full"
|
||||
placeholder={$t('adventures.start_date')}
|
||||
min={constrainDates ? fullStartDateOnly : ''}
|
||||
max={constrainDates ? fullEndDateOnly : ''}
|
||||
bind:value={new_start_date}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
addNewVisit();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
type="date"
|
||||
class="input input-bordered w-full"
|
||||
placeholder={$t('adventures.end_date')}
|
||||
bind:value={new_end_date}
|
||||
min={constrainDates ? fullStartDateOnly : ''}
|
||||
max={constrainDates ? fullEndDateOnly : ''}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
addNewVisit();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex gap-2 mb-1">
|
||||
<!-- textarea for notes -->
|
||||
<textarea
|
||||
class="textarea textarea-bordered w-full"
|
||||
placeholder={$t('adventures.add_notes')}
|
||||
bind:value={new_notes}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
addNewVisit();
|
||||
}
|
||||
}}
|
||||
></textarea>
|
||||
</div>
|
||||
{#if !allDay}
|
||||
<div role="alert" class="alert shadow-lg bg-neutral mt-2 mb-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
class="stroke-info h-6 w-6 shrink-0"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
></path>
|
||||
</svg>
|
||||
<span>
|
||||
{$t('lodging.current_timezone')}:
|
||||
{(() => {
|
||||
const tz = new Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
const [continent, city] = tz.split('/');
|
||||
return `${continent} (${city.replace('_', ' ')})`;
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button type="button" class="btn btn-neutral" on:click={addNewVisit}
|
||||
>{$t('adventures.add')}</button
|
||||
>
|
||||
</div>
|
||||
|
||||
{#if adventure.visits.length > 0}
|
||||
<h2 class="font-bold text-xl mt-2">{$t('adventures.my_visits')}</h2>
|
||||
{#each adventure.visits as visit}
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex gap-2 items-center">
|
||||
<p>
|
||||
{#if isAllDay(visit.start_date)}
|
||||
<!-- For all-day events, show just the date -->
|
||||
{new Date(visit.start_date).toLocaleDateString(undefined, {
|
||||
timeZone: 'UTC'
|
||||
})}
|
||||
{:else}
|
||||
<!-- For timed events, show date and time -->
|
||||
{new Date(visit.start_date).toLocaleDateString()} ({new Date(
|
||||
visit.start_date
|
||||
).toLocaleTimeString()})
|
||||
{/if}
|
||||
</p>
|
||||
{#if visit.end_date && visit.end_date !== visit.start_date}
|
||||
<p>
|
||||
{#if isAllDay(visit.end_date)}
|
||||
<!-- For all-day events, show just the date -->
|
||||
{new Date(visit.end_date).toLocaleDateString(undefined, {
|
||||
timeZone: 'UTC'
|
||||
})}
|
||||
{:else}
|
||||
<!-- For timed events, show date and time -->
|
||||
{new Date(visit.end_date).toLocaleDateString()} ({new Date(
|
||||
visit.end_date
|
||||
).toLocaleTimeString()})
|
||||
{/if}
|
||||
</p>
|
||||
{/if}
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-neutral"
|
||||
on:click={() => {
|
||||
// Determine if this is an all-day event
|
||||
const isAllDayEvent = isAllDay(visit.start_date);
|
||||
allDay = isAllDayEvent;
|
||||
|
||||
if (isAllDayEvent) {
|
||||
// For all-day events, use date only
|
||||
new_start_date = visit.start_date.split('T')[0];
|
||||
new_end_date = visit.end_date.split('T')[0];
|
||||
} else {
|
||||
// For timed events, format properly for datetime-local input
|
||||
const startDate = new Date(visit.start_date);
|
||||
const endDate = new Date(visit.end_date);
|
||||
|
||||
// Format as yyyy-MM-ddThh:mm
|
||||
new_start_date =
|
||||
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');
|
||||
|
||||
new_end_date =
|
||||
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');
|
||||
}
|
||||
|
||||
new_notes = visit.notes;
|
||||
adventure.visits = adventure.visits.filter((v) => v !== visit);
|
||||
}}
|
||||
>
|
||||
{$t('lodging.edit')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-error"
|
||||
on:click={() => {
|
||||
adventure.visits = adventure.visits.filter((v) => v !== visit);
|
||||
}}
|
||||
>
|
||||
{$t('adventures.remove')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="whitespace-pre-wrap -mt-2 mb-2">{visit.notes}</p>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<DateRangeCollapse type="adventure" {collection} bind:visits={adventure.visits} />
|
||||
|
||||
<div>
|
||||
<div class="mt-4">
|
||||
|
|
387
frontend/src/lib/components/DateRangeCollapse.svelte
Normal file
387
frontend/src/lib/components/DateRangeCollapse.svelte
Normal file
|
@ -0,0 +1,387 @@
|
|||
<script lang="ts">
|
||||
import type { Collection } from '$lib/types';
|
||||
import TimezoneSelector from './TimezoneSelector.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
export let collection: Collection | null = null;
|
||||
import { updateLocalDate, updateUTCDate, validateDateRange, formatUTCDate } from '$lib/dateUtils';
|
||||
import { onMount } from 'svelte';
|
||||
import { isAllDay } from '$lib';
|
||||
|
||||
export let type: 'adventure' | 'transportation' | 'lodging' = 'adventure';
|
||||
|
||||
// Initialize with browser's timezone
|
||||
let selectedTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
let allDay: boolean = false;
|
||||
|
||||
// Store the UTC dates as source of truth
|
||||
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;
|
||||
};
|
||||
export let visits: Visit[] | null = null;
|
||||
|
||||
// Local display values
|
||||
let localStartDate: string = '';
|
||||
let localEndDate: string = '';
|
||||
|
||||
let fullStartDate: string = '';
|
||||
let fullEndDate: string = '';
|
||||
|
||||
let constrainDates: boolean = false;
|
||||
|
||||
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
|
||||
localStartDate = updateLocalDate({
|
||||
utcDate: utcStartDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
localEndDate = updateLocalDate({
|
||||
utcDate: utcEndDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
});
|
||||
|
||||
if (collection && collection.start_date && collection.end_date) {
|
||||
fullStartDate = `${collection.start_date}T00:00`;
|
||||
fullEndDate = `${collection.end_date}T23:59`;
|
||||
}
|
||||
|
||||
// Update local display dates whenever timezone or UTC dates change
|
||||
$: if (!isEditing) {
|
||||
if (allDay) {
|
||||
localStartDate = utcStartDate?.substring(0, 10) ?? '';
|
||||
localEndDate = utcEndDate?.substring(0, 10) ?? '';
|
||||
} else {
|
||||
const start = updateLocalDate({
|
||||
utcDate: utcStartDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
|
||||
const end = updateLocalDate({
|
||||
utcDate: utcEndDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
|
||||
localStartDate = start;
|
||||
localEndDate = end;
|
||||
}
|
||||
}
|
||||
|
||||
// Update UTC dates when local dates change
|
||||
function handleLocalDateChange() {
|
||||
utcStartDate = updateUTCDate({
|
||||
localDate: localStartDate,
|
||||
timezone: selectedTimezone,
|
||||
allDay
|
||||
}).utcDate;
|
||||
|
||||
utcEndDate = updateUTCDate({
|
||||
localDate: localEndDate,
|
||||
timezone: selectedTimezone,
|
||||
allDay
|
||||
}).utcDate;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="collapse collapse-plus bg-base-200 mb-4 rounded-lg">
|
||||
<input type="checkbox" />
|
||||
<div class="collapse-title text-xl font-semibold">
|
||||
{$t('adventures.date_information')}
|
||||
</div>
|
||||
<div class="collapse-content space-y-6">
|
||||
<!-- Timezone Selector -->
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<TimezoneSelector bind:selectedTimezone />
|
||||
</div>
|
||||
|
||||
<span class="label-text">{$t('adventures.all_day')}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary"
|
||||
id="constrain_dates"
|
||||
name="constrain_dates"
|
||||
bind:checked={allDay}
|
||||
on:change={() => {
|
||||
// clear local dates when toggling all day
|
||||
if (allDay) {
|
||||
localStartDate = localStartDate.split('T')[0];
|
||||
localEndDate = localEndDate.split('T')[0];
|
||||
} else {
|
||||
localStartDate = localStartDate + 'T00:00';
|
||||
localEndDate = localEndDate + 'T23:59';
|
||||
}
|
||||
// Update UTC dates when toggling all day
|
||||
utcStartDate = updateUTCDate({
|
||||
localDate: localStartDate,
|
||||
timezone: selectedTimezone,
|
||||
allDay
|
||||
}).utcDate;
|
||||
utcEndDate = updateUTCDate({
|
||||
localDate: localEndDate,
|
||||
timezone: selectedTimezone,
|
||||
allDay
|
||||
}).utcDate;
|
||||
// Update local dates when toggling all day
|
||||
localStartDate = updateLocalDate({
|
||||
utcDate: utcStartDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
localEndDate = updateLocalDate({
|
||||
utcDate: utcEndDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
}}
|
||||
/>
|
||||
<!-- All Day Event Checkbox -->
|
||||
|
||||
<!-- Dates Input Section -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Start Date -->
|
||||
<div class="flex flex-col space-y-2">
|
||||
<label for="date" class="font-medium">
|
||||
{$t('adventures.start_date')}
|
||||
</label>
|
||||
|
||||
{#if allDay}
|
||||
<input
|
||||
type="date"
|
||||
id="date"
|
||||
name="date"
|
||||
bind:value={localStartDate}
|
||||
on:change={handleLocalDateChange}
|
||||
min={constrainDates ? fullStartDate : ''}
|
||||
max={constrainDates ? fullEndDate : ''}
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
type="datetime-local"
|
||||
id="date"
|
||||
name="date"
|
||||
bind:value={localStartDate}
|
||||
on:change={handleLocalDateChange}
|
||||
min={constrainDates ? fullStartDate : ''}
|
||||
max={constrainDates ? fullEndDate : ''}
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if collection && collection.start_date && collection.end_date}
|
||||
<label class="flex items-center gap-2 mt-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary"
|
||||
id="constrain_dates"
|
||||
name="constrain_dates"
|
||||
on:change={() => (constrainDates = !constrainDates)}
|
||||
/>
|
||||
<span class="label-text">{$t('adventures.date_constrain')}</span>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- End Date -->
|
||||
{#if localStartDate}
|
||||
<div class="flex flex-col space-y-2">
|
||||
<label for="end_date" class="font-medium">
|
||||
{$t('adventures.end_date')}
|
||||
</label>
|
||||
|
||||
{#if allDay}
|
||||
<input
|
||||
type="date"
|
||||
id="end_date"
|
||||
name="end_date"
|
||||
bind:value={localEndDate}
|
||||
on:change={handleLocalDateChange}
|
||||
min={constrainDates ? localStartDate : ''}
|
||||
max={constrainDates ? fullEndDate : ''}
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
type="datetime-local"
|
||||
id="end_date"
|
||||
name="end_date"
|
||||
bind:value={localEndDate}
|
||||
on:change={handleLocalDateChange}
|
||||
min={constrainDates ? localStartDate : ''}
|
||||
max={constrainDates ? fullEndDate : ''}
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Notes -->
|
||||
{#if type === 'adventure'}
|
||||
<div class="flex gap-2 mb-1">
|
||||
<!-- textarea for notes -->
|
||||
<textarea
|
||||
class="textarea textarea-bordered w-full"
|
||||
placeholder={$t('adventures.add_notes')}
|
||||
bind:value={note}
|
||||
></textarea>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Validation Message -->
|
||||
{#if !validateDateRange(localStartDate, localEndDate).valid}
|
||||
<div role="alert" class="alert alert-error">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6 shrink-0 stroke-current"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span>{$t('adventures.invalid_date_range')}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if visits && visits.length > 0}
|
||||
<div class="space-y-4">
|
||||
{#each visits as visit}
|
||||
<div
|
||||
class="p-4 border border-neutral rounded-lg bg-base-100 shadow-sm flex flex-col gap-2"
|
||||
>
|
||||
<p class="text-sm text-base-content font-medium">
|
||||
{#if isAllDay(visit.start_date)}
|
||||
<span class="badge badge-outline mr-2">All Day</span>
|
||||
{visit.start_date.split('T')[0]} – {visit.end_date.split('T')[0]}
|
||||
{:else}
|
||||
{new Date(visit.start_date).toLocaleString()} – {new Date(
|
||||
visit.end_date
|
||||
).toLocaleString()}
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
<!-- If the selected timezone is not the current one show the timezone + the time converted there -->
|
||||
|
||||
{#if visit.notes}
|
||||
<p class="text-sm text-base-content opacity-70 italic">
|
||||
"{visit.notes}"
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<div class="flex gap-2 mt-2">
|
||||
<button
|
||||
class="btn btn-error btn-sm"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
if (visits) {
|
||||
visits = visits.filter((v) => v.id !== visit.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{$t('adventures.remove')}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-primary btn-sm"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
isEditing = true;
|
||||
const isAllDayEvent = isAllDay(visit.start_date);
|
||||
allDay = isAllDayEvent;
|
||||
|
||||
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);
|
||||
|
||||
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')}`;
|
||||
}
|
||||
|
||||
// remove it from visits
|
||||
if (visits) {
|
||||
visits = visits.filter((v) => v.id !== visit.id);
|
||||
}
|
||||
|
||||
note = visit.notes;
|
||||
constrainDates = true;
|
||||
utcStartDate = visit.start_date;
|
||||
utcEndDate = visit.end_date;
|
||||
type = 'adventure';
|
||||
|
||||
setTimeout(() => {
|
||||
isEditing = false;
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
{$t('lodging.edit')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex gap-2 mb-1">
|
||||
<!-- add button -->
|
||||
{#if type === 'adventure'}
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
const newVisit = {
|
||||
id: crypto.randomUUID(),
|
||||
start_date: utcStartDate ?? '',
|
||||
end_date: utcEndDate ?? utcStartDate ?? '',
|
||||
notes: note ?? ''
|
||||
};
|
||||
|
||||
// Ensure reactivity by assigning a *new* array
|
||||
if (visits) {
|
||||
visits = [...visits, newVisit];
|
||||
} else {
|
||||
visits = [newVisit];
|
||||
}
|
||||
|
||||
// Optionally clear the form
|
||||
note = '';
|
||||
localStartDate = '';
|
||||
localEndDate = '';
|
||||
utcStartDate = null;
|
||||
utcEndDate = null;
|
||||
}}
|
||||
>
|
||||
{$t('adventures.add')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,172 +0,0 @@
|
|||
<script lang="ts">
|
||||
import type { Collection } from '$lib/types';
|
||||
import TimezoneSelector from './TimezoneSelector.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
export let collection: Collection | null = null;
|
||||
import { updateLocalDate, updateUTCDate, validateDateRange, formatUTCDate } from '$lib/dateUtils';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
// Initialize with browser's timezone
|
||||
let selectedTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
// Store the UTC dates as source of truth
|
||||
export let utcStartDate: string | null = null;
|
||||
export let utcEndDate: string | null = null;
|
||||
|
||||
// Local display values
|
||||
let localStartDate: string = '';
|
||||
let localEndDate: string = '';
|
||||
|
||||
let fullStartDate: string = '';
|
||||
let fullEndDate: string = '';
|
||||
|
||||
let constrainDates: boolean = false;
|
||||
|
||||
onMount(async () => {
|
||||
// Initialize UTC dates from transportationToEdit if available
|
||||
localStartDate = updateLocalDate({
|
||||
utcDate: utcStartDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
localEndDate = updateLocalDate({
|
||||
utcDate: utcEndDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
});
|
||||
|
||||
if (collection && collection.start_date && collection.end_date) {
|
||||
fullStartDate = `${collection.start_date}T00:00`;
|
||||
fullEndDate = `${collection.end_date}T23:59`;
|
||||
}
|
||||
|
||||
// Update local display dates whenever timezone or UTC dates change
|
||||
$: {
|
||||
localStartDate = updateLocalDate({
|
||||
utcDate: utcStartDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
localEndDate = updateLocalDate({
|
||||
utcDate: utcEndDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
}
|
||||
|
||||
// Update UTC dates when local dates change
|
||||
function handleLocalDateChange() {
|
||||
utcStartDate = updateUTCDate({
|
||||
localDate: localStartDate,
|
||||
timezone: selectedTimezone
|
||||
}).utcDate;
|
||||
utcEndDate = updateUTCDate({
|
||||
localDate: localEndDate,
|
||||
timezone: selectedTimezone
|
||||
}).utcDate;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="collapse collapse-plus bg-base-200 mb-4 rounded-lg">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-semibold">
|
||||
{$t('adventures.date_information')}
|
||||
</div>
|
||||
<div class="collapse-content space-y-6">
|
||||
<!-- Timezone Selector -->
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<TimezoneSelector bind:selectedTimezone />
|
||||
</div>
|
||||
|
||||
<!-- Dates Input Section -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Start Date -->
|
||||
<div class="flex flex-col space-y-2">
|
||||
<label for="date" class="font-medium">
|
||||
{$t('adventures.start_date')}
|
||||
</label>
|
||||
|
||||
<input
|
||||
type="datetime-local"
|
||||
id="date"
|
||||
name="date"
|
||||
bind:value={localStartDate}
|
||||
on:change={handleLocalDateChange}
|
||||
min={constrainDates ? fullStartDate : ''}
|
||||
max={constrainDates ? fullEndDate : ''}
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
|
||||
{#if collection && collection.start_date && collection.end_date}
|
||||
<label class="flex items-center gap-2 mt-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary"
|
||||
id="constrain_dates"
|
||||
name="constrain_dates"
|
||||
on:change={() => (constrainDates = !constrainDates)}
|
||||
/>
|
||||
<span class="label-text">{$t('adventures.date_constrain')}</span>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- End Date -->
|
||||
{#if localStartDate}
|
||||
<div class="flex flex-col space-y-2">
|
||||
<label for="end_date" class="font-medium">
|
||||
{$t('adventures.end_date')}
|
||||
</label>
|
||||
|
||||
<input
|
||||
type="datetime-local"
|
||||
id="end_date"
|
||||
name="end_date"
|
||||
bind:value={localEndDate}
|
||||
on:change={handleLocalDateChange}
|
||||
min={constrainDates ? localStartDate : ''}
|
||||
max={constrainDates ? fullEndDate : ''}
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Validation Message -->
|
||||
{#if !validateDateRange(localStartDate, localEndDate).valid}
|
||||
<div role="alert" class="alert alert-error">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6 shrink-0 stroke-current"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span>{$t('adventures.invalid_date_range')}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!--
|
||||
<div role="alert" class="alert shadow-lg bg-neutral text-neutral-content mt-6">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
class="stroke-info h-6 w-6 shrink-0"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="ml-2">
|
||||
{$t('lodging.current_timezone')}: {selectedTimezone}
|
||||
</span>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
|
@ -5,6 +5,7 @@
|
|||
import MarkdownEditor from './MarkdownEditor.svelte';
|
||||
import type { Collection, Lodging } from '$lib/types';
|
||||
import LocationDropdown from './LocationDropdown.svelte';
|
||||
import DateRangeCollapse from './DateRangeCollapse.svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
|
@ -12,22 +13,10 @@
|
|||
export let lodgingToEdit: Lodging | null = null;
|
||||
|
||||
let modal: HTMLDialogElement;
|
||||
let constrainDates: boolean = false;
|
||||
let lodging: Lodging = { ...initializeLodging(lodgingToEdit) };
|
||||
let fullStartDate: string = '';
|
||||
let fullEndDate: string = '';
|
||||
|
||||
// Format date as local datetime
|
||||
// Convert an ISO date to a datetime-local value in local time.
|
||||
function toLocalDatetime(value: string | null): string {
|
||||
if (!value) return '';
|
||||
const date = new Date(value);
|
||||
// Adjust the time by subtracting the timezone offset.
|
||||
date.setMinutes(date.getMinutes() - date.getTimezoneOffset());
|
||||
// Return format YYYY-MM-DDTHH:mm
|
||||
return date.toISOString().slice(0, 16);
|
||||
}
|
||||
|
||||
type LodgingType = {
|
||||
value: string;
|
||||
label: string;
|
||||
|
@ -47,27 +36,27 @@
|
|||
{ value: 'other', label: 'Other' }
|
||||
];
|
||||
|
||||
// Initialize hotel with values from hotelToEdit or default values
|
||||
function initializeLodging(hotelToEdit: Lodging | null): Lodging {
|
||||
// Initialize hotel with values from lodgingToEdit or default values
|
||||
function initializeLodging(lodgingToEdit: Lodging | null): Lodging {
|
||||
return {
|
||||
id: hotelToEdit?.id || '',
|
||||
user_id: hotelToEdit?.user_id || '',
|
||||
name: hotelToEdit?.name || '',
|
||||
type: hotelToEdit?.type || 'other',
|
||||
description: hotelToEdit?.description || '',
|
||||
rating: hotelToEdit?.rating || NaN,
|
||||
link: hotelToEdit?.link || '',
|
||||
check_in: hotelToEdit?.check_in ? toLocalDatetime(hotelToEdit.check_in) : null,
|
||||
check_out: hotelToEdit?.check_out ? toLocalDatetime(hotelToEdit.check_out) : null,
|
||||
reservation_number: hotelToEdit?.reservation_number || '',
|
||||
price: hotelToEdit?.price || null,
|
||||
latitude: hotelToEdit?.latitude || null,
|
||||
longitude: hotelToEdit?.longitude || null,
|
||||
location: hotelToEdit?.location || '',
|
||||
is_public: hotelToEdit?.is_public || false,
|
||||
collection: hotelToEdit?.collection || collection.id,
|
||||
created_at: hotelToEdit?.created_at || '',
|
||||
updated_at: hotelToEdit?.updated_at || ''
|
||||
id: lodgingToEdit?.id || '',
|
||||
user_id: lodgingToEdit?.user_id || '',
|
||||
name: lodgingToEdit?.name || '',
|
||||
type: lodgingToEdit?.type || 'other',
|
||||
description: lodgingToEdit?.description || '',
|
||||
rating: lodgingToEdit?.rating || NaN,
|
||||
link: lodgingToEdit?.link || '',
|
||||
check_in: lodgingToEdit?.check_in || null,
|
||||
check_out: lodgingToEdit?.check_out || null,
|
||||
reservation_number: lodgingToEdit?.reservation_number || '',
|
||||
price: lodgingToEdit?.price || null,
|
||||
latitude: lodgingToEdit?.latitude || null,
|
||||
longitude: lodgingToEdit?.longitude || null,
|
||||
location: lodgingToEdit?.location || '',
|
||||
is_public: lodgingToEdit?.is_public || false,
|
||||
collection: lodgingToEdit?.collection || collection.id,
|
||||
created_at: lodgingToEdit?.created_at || '',
|
||||
updated_at: lodgingToEdit?.updated_at || ''
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -104,27 +93,6 @@
|
|||
async function handleSubmit(event: Event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (lodging.check_in && !lodging.check_out) {
|
||||
const checkInDate = new Date(lodging.check_in);
|
||||
checkInDate.setDate(checkInDate.getDate() + 1);
|
||||
lodging.check_out = checkInDate.toISOString();
|
||||
}
|
||||
|
||||
if (lodging.check_in && lodging.check_out && lodging.check_in > lodging.check_out) {
|
||||
addToast('error', $t('adventures.start_before_end_error'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Only convert to UTC if the time is still in local format.
|
||||
if (lodging.check_in && !lodging.check_in.includes('Z')) {
|
||||
// new Date(lodging.check_in) interprets the input as local time.
|
||||
lodging.check_in = new Date(lodging.check_in).toISOString();
|
||||
}
|
||||
if (lodging.check_out && !lodging.check_out.includes('Z')) {
|
||||
lodging.check_out = new Date(lodging.check_out).toISOString();
|
||||
}
|
||||
console.log(lodging.check_in, lodging.check_out);
|
||||
|
||||
// Create or update lodging...
|
||||
const url = lodging.id === '' ? '/api/lodging' : `/api/lodging/${lodging.id}`;
|
||||
const method = lodging.id === '' ? 'POST' : 'PATCH';
|
||||
|
@ -331,85 +299,11 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="collapse collapse-plus bg-base-200 mb-4">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium">
|
||||
{$t('adventures.date_information')}
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<!-- Check In -->
|
||||
<div>
|
||||
<label for="date">
|
||||
{$t('lodging.check_in')}
|
||||
</label>
|
||||
|
||||
{#if collection && collection.start_date && collection.end_date}<label
|
||||
class="label cursor-pointer flex items-start space-x-2"
|
||||
>
|
||||
<span class="label-text">{$t('adventures.date_constrain')}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary"
|
||||
id="constrain_dates"
|
||||
name="constrain_dates"
|
||||
on:change={() => (constrainDates = !constrainDates)}
|
||||
/></label
|
||||
>
|
||||
{/if}
|
||||
<div>
|
||||
<input
|
||||
type="datetime-local"
|
||||
id="date"
|
||||
name="date"
|
||||
bind:value={lodging.check_in}
|
||||
min={constrainDates ? fullStartDate : ''}
|
||||
max={constrainDates ? fullEndDate : ''}
|
||||
class="input input-bordered w-full max-w-xs mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Date -->
|
||||
<div>
|
||||
<label for="end_date">
|
||||
{$t('lodging.check_out')}
|
||||
</label>
|
||||
<div>
|
||||
<input
|
||||
type="datetime-local"
|
||||
id="end_date"
|
||||
name="end_date"
|
||||
min={constrainDates ? lodging.check_in : ''}
|
||||
max={constrainDates ? fullEndDate : ''}
|
||||
bind:value={lodging.check_out}
|
||||
class="input input-bordered w-full max-w-xs mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div role="alert" class="alert shadow-lg bg-neutral mt-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
class="stroke-info h-6 w-6 shrink-0"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
></path>
|
||||
</svg>
|
||||
<span>
|
||||
{$t('lodging.current_timezone')}:
|
||||
{(() => {
|
||||
const tz = new Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
const [continent, city] = tz.split('/');
|
||||
return `${continent} (${city.replace('_', ' ')})`;
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DateRangeCollapse
|
||||
type="lodging"
|
||||
bind:utcStartDate={lodging.check_in}
|
||||
bind:utcEndDate={lodging.check_out}
|
||||
/>
|
||||
|
||||
<!-- Location Information -->
|
||||
<LocationDropdown bind:item={lodging} />
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
let dropdownOpen = false;
|
||||
let searchQuery = '';
|
||||
let searchInput: HTMLInputElement;
|
||||
const timezones = Intl.supportedValuesOf('timeZone');
|
||||
|
||||
// Filter timezones based on search query
|
||||
|
@ -19,6 +20,23 @@
|
|||
searchQuery = '';
|
||||
}
|
||||
|
||||
// Focus search input when dropdown opens
|
||||
$: if (dropdownOpen && searchInput) {
|
||||
// Use setTimeout to delay focus until after the element is rendered
|
||||
setTimeout(() => searchInput.focus(), 0);
|
||||
}
|
||||
|
||||
function handleKeydown(event: KeyboardEvent, tz?: string) {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
if (tz) selectTimezone(tz);
|
||||
else dropdownOpen = !dropdownOpen;
|
||||
} else if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
dropdownOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Close dropdown if clicked outside
|
||||
onMount(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
|
@ -31,16 +49,20 @@
|
|||
</script>
|
||||
|
||||
<div class="form-control w-full max-w-xs relative" id="tz-selector">
|
||||
<label class="label">
|
||||
<label class="label" for="timezone-display">
|
||||
<span class="label-text">Timezone</span>
|
||||
</label>
|
||||
|
||||
<!-- Trigger -->
|
||||
<div
|
||||
id="timezone-display"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={dropdownOpen}
|
||||
class="input input-bordered flex justify-between items-center cursor-pointer"
|
||||
on:click={() => (dropdownOpen = !dropdownOpen)}
|
||||
on:keydown={handleKeydown}
|
||||
>
|
||||
<span class="truncate">{selectedTimezone}</span>
|
||||
<svg
|
||||
|
@ -49,6 +71,7 @@
|
|||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
|
@ -58,6 +81,8 @@
|
|||
{#if dropdownOpen}
|
||||
<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"
|
||||
>
|
||||
<!-- Search -->
|
||||
<div class="sticky top-0 bg-base-100 p-2 border-b">
|
||||
|
@ -66,7 +91,7 @@
|
|||
placeholder="Search timezone"
|
||||
class="input input-sm input-bordered w-full"
|
||||
bind:value={searchQuery}
|
||||
autofocus
|
||||
bind:this={searchInput}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -75,12 +100,16 @@
|
|||
<ul class="menu p-2 space-y-1">
|
||||
{#each filteredTimezones as tz}
|
||||
<li>
|
||||
<a
|
||||
class={`truncate ${tz === selectedTimezone ? 'active font-bold' : ''}`}
|
||||
on:click|preventDefault={() => selectTimezone(tz)}
|
||||
<button
|
||||
type="button"
|
||||
class={`w-full text-left truncate ${tz === selectedTimezone ? 'active font-bold' : ''}`}
|
||||
on:click={() => selectTimezone(tz)}
|
||||
on:keydown={(e) => handleKeydown(e, tz)}
|
||||
role="option"
|
||||
aria-selected={tz === selectedTimezone}
|
||||
>
|
||||
{tz}
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
|
|
@ -6,37 +6,23 @@
|
|||
import { addToast } from '$lib/toasts';
|
||||
let modal: HTMLDialogElement;
|
||||
import { t } from 'svelte-i18n';
|
||||
import { updateLocalDate, updateUTCDate, validateDateRange, formatUTCDate } from '$lib/dateUtils';
|
||||
|
||||
// Initialize with browser's timezone
|
||||
let selectedTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
// Store the UTC dates as source of truth
|
||||
let utcStartDate: string | null = null;
|
||||
let utcEndDate: string | null = null;
|
||||
|
||||
// Local display values
|
||||
let localStartDate: string = '';
|
||||
let localEndDate: string = '';
|
||||
|
||||
import MarkdownEditor from './MarkdownEditor.svelte';
|
||||
import { appVersion } from '$lib/config';
|
||||
import { DefaultMarker, MapLibre } from 'svelte-maplibre';
|
||||
import TimezoneSelector from './TimezoneSelector.svelte';
|
||||
import DateRangeCollapse from './DateRangeCollapse.svelte';
|
||||
|
||||
export let collection: Collection;
|
||||
export let transportationToEdit: Transportation | null = null;
|
||||
|
||||
let constrainDates: boolean = false;
|
||||
|
||||
// Initialize transportation object
|
||||
let transportation: Transportation = {
|
||||
id: transportationToEdit?.id || '',
|
||||
type: transportationToEdit?.type || '',
|
||||
name: transportationToEdit?.name || '',
|
||||
description: transportationToEdit?.description || '',
|
||||
date: null,
|
||||
end_date: null,
|
||||
date: transportationToEdit?.date || null,
|
||||
end_date: transportationToEdit?.end_date || null,
|
||||
rating: transportationToEdit?.rating || 0,
|
||||
link: transportationToEdit?.link || '',
|
||||
flight_number: transportationToEdit?.flight_number || '',
|
||||
|
@ -53,58 +39,20 @@
|
|||
destination_longitude: transportationToEdit?.destination_longitude || NaN
|
||||
};
|
||||
|
||||
let fullStartDate: string = '';
|
||||
let fullEndDate: string = '';
|
||||
|
||||
let starting_airport: string = '';
|
||||
let ending_airport: string = '';
|
||||
|
||||
if (collection.start_date && collection.end_date) {
|
||||
fullStartDate = `${collection.start_date}T00:00`;
|
||||
fullEndDate = `${collection.end_date}T23:59`;
|
||||
}
|
||||
|
||||
$: {
|
||||
if (!transportation.rating) {
|
||||
transportation.rating = NaN;
|
||||
}
|
||||
}
|
||||
|
||||
// Update local display dates whenever timezone or UTC dates change
|
||||
$: {
|
||||
localStartDate = updateLocalDate({
|
||||
utcDate: utcStartDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
localEndDate = updateLocalDate({
|
||||
utcDate: utcEndDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
|
||||
if (modal) {
|
||||
modal.showModal();
|
||||
}
|
||||
|
||||
// Initialize UTC dates from transportationToEdit if available
|
||||
if (transportationToEdit?.date) {
|
||||
utcStartDate = transportationToEdit.date;
|
||||
}
|
||||
|
||||
if (transportationToEdit?.end_date) {
|
||||
utcEndDate = transportationToEdit.end_date;
|
||||
}
|
||||
|
||||
localStartDate = updateLocalDate({
|
||||
utcDate: utcStartDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
localEndDate = updateLocalDate({
|
||||
utcDate: utcEndDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
});
|
||||
|
||||
function close() {
|
||||
|
@ -117,18 +65,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Update UTC dates when local dates change
|
||||
function handleLocalDateChange() {
|
||||
utcStartDate = updateUTCDate({
|
||||
localDate: localStartDate,
|
||||
timezone: selectedTimezone
|
||||
}).utcDate;
|
||||
utcEndDate = updateUTCDate({
|
||||
localDate: localEndDate,
|
||||
timezone: selectedTimezone
|
||||
}).utcDate;
|
||||
}
|
||||
|
||||
async function geocode(e: Event | null) {
|
||||
// Geocoding logic unchanged
|
||||
if (e) {
|
||||
|
@ -214,30 +150,9 @@
|
|||
Math.round(transportation.destination_longitude * 1e6) / 1e6;
|
||||
}
|
||||
|
||||
// Validate dates using utility function
|
||||
if (localEndDate && !localStartDate) {
|
||||
addToast('error', $t('adventures.start_date_required'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (localStartDate && !localEndDate) {
|
||||
// If only start date is provided, set end date to the same value
|
||||
localEndDate = localStartDate;
|
||||
utcEndDate = utcStartDate;
|
||||
}
|
||||
|
||||
// Validate date range
|
||||
const validation = validateDateRange(localStartDate, localEndDate);
|
||||
if (!validation.valid) {
|
||||
addToast('error', $t('adventures.start_before_end_error'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the stored UTC dates for submission
|
||||
const submissionData = {
|
||||
...transportation,
|
||||
date: utcStartDate,
|
||||
end_date: utcEndDate
|
||||
...transportation
|
||||
};
|
||||
|
||||
if (transportation.type != 'plane') {
|
||||
|
@ -255,18 +170,6 @@
|
|||
let data = await res.json();
|
||||
if (data.id) {
|
||||
transportation = data as Transportation;
|
||||
// Update the UTC dates with the values from the server
|
||||
utcStartDate = data.date;
|
||||
utcEndDate = data.end_date;
|
||||
|
||||
localStartDate = updateLocalDate({
|
||||
utcDate: utcStartDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
localEndDate = updateLocalDate({
|
||||
utcDate: utcEndDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
|
||||
addToast('success', $t('adventures.adventure_created'));
|
||||
dispatch('save', transportation);
|
||||
|
@ -285,18 +188,6 @@
|
|||
let data = await res.json();
|
||||
if (data.id) {
|
||||
transportation = data as Transportation;
|
||||
// Update the UTC dates with the values from the server
|
||||
utcStartDate = data.date;
|
||||
utcEndDate = data.end_date;
|
||||
|
||||
localStartDate = updateLocalDate({
|
||||
utcDate: utcStartDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
localEndDate = updateLocalDate({
|
||||
utcDate: utcEndDate,
|
||||
timezone: selectedTimezone
|
||||
}).localDate;
|
||||
|
||||
addToast('success', $t('adventures.adventure_updated'));
|
||||
dispatch('save', transportation);
|
||||
|
@ -447,96 +338,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapse collapse-plus bg-base-200 mb-4">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium">
|
||||
{$t('adventures.date_information')}
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<TimezoneSelector bind:selectedTimezone />
|
||||
<!-- Start Date -->
|
||||
<div>
|
||||
<label for="date">
|
||||
{$t('adventures.start_date')}
|
||||
</label>
|
||||
|
||||
{#if collection && collection.start_date && collection.end_date}<label
|
||||
class="label cursor-pointer flex items-start space-x-2"
|
||||
>
|
||||
<span class="label-text">{$t('adventures.date_constrain')}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary"
|
||||
id="constrain_dates"
|
||||
name="constrain_dates"
|
||||
on:change={() => (constrainDates = !constrainDates)}
|
||||
/></label
|
||||
>
|
||||
{/if}
|
||||
<div>
|
||||
<input
|
||||
type="datetime-local"
|
||||
id="date"
|
||||
name="date"
|
||||
bind:value={localStartDate}
|
||||
on:change={handleLocalDateChange}
|
||||
min={constrainDates ? fullStartDate : ''}
|
||||
max={constrainDates ? fullEndDate : ''}
|
||||
class="input input-bordered w-full max-w-xs mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Date -->
|
||||
{#if localStartDate}
|
||||
<div>
|
||||
<label for="end_date">
|
||||
{$t('adventures.end_date')}
|
||||
</label>
|
||||
<div>
|
||||
<input
|
||||
type="datetime-local"
|
||||
id="end_date"
|
||||
name="end_date"
|
||||
min={constrainDates ? localStartDate : ''}
|
||||
max={constrainDates ? fullEndDate : ''}
|
||||
bind:value={localEndDate}
|
||||
on:change={handleLocalDateChange}
|
||||
class="input input-bordered w-full max-w-xs mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div role="alert" class="alert shadow-lg bg-neutral mt-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
class="stroke-info h-6 w-6 shrink-0"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
></path>
|
||||
</svg>
|
||||
<span>
|
||||
{$t('lodging.current_timezone')}:
|
||||
{selectedTimezone}
|
||||
</span>
|
||||
</div>
|
||||
{#if utcStartDate}
|
||||
<div class="text-sm mt-2">
|
||||
UTC Time: {formatUTCDate(utcStartDate)}
|
||||
{#if utcEndDate && utcEndDate !== utcStartDate}
|
||||
to {formatUTCDate(utcEndDate)}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<DateRangeCollapse
|
||||
type="transportation"
|
||||
bind:utcStartDate={transportation.date}
|
||||
bind:utcEndDate={transportation.end_date}
|
||||
/>
|
||||
|
||||
<!-- Flight Information -->
|
||||
|
||||
<div class="collapse collapse-plus bg-base-200 mb-4">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue