+
+
-
-
-
{$t('adventures.download_calendar')}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {$t('adventures.adventure_calendar')}
+
+
+ {filteredDates.length}
+ {$t('calendar.events_scheduled')}
+
+
+
+
+
+
+
+
+
+
{$t('calendar.total_events')}
+
{allDates.length}
+
+
+
{$t('navbar.adventures')}
+
{adventures.length}
+
+
+
+
+
+
+
+
+
+
+ {#if searchFilter.length > 0}
+
+ {/if}
+
+
+
+
+
+ {$t('worldtravel.filter_by')}:
+ {#if searchFilter}
+
+ {/if}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{$t('adventures.filters_and_stats')}
+
+
+
+
+
+
+ {$t('calendar.calendar_overview')}
+
+
+
+
+
{$t('calendar.total_events')}
+
{allDates.length}
+
+
+
+
+
{$t('navbar.adventures')}
+
{adventures.length}
+
+
+
+ {#if filteredDates.length !== allDates.length}
+
+
+ {$t('calendar.filtered_results')}
+ {filteredDates.length} {$t('worldtravel.of')} {allDates.length}
+
+
+
+ {/if}
+
+
+
+
+
+
+
+
+
+
+
+{#if showEventModal && selectedEvent}
+
+
+
+
+
+
+ {selectedEvent.extendedProps.icon}
+
+
+
{selectedEvent.extendedProps.adventureName}
+
+ {selectedEvent.extendedProps.category}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {#if selectedEvent.extendedProps.isAllDay}
+ {$t('calendar.all_day_event')}
+ {:else}
+ {selectedEvent.extendedProps.formattedStart}
+ {#if selectedEvent.extendedProps.formattedEnd !== selectedEvent.extendedProps.formattedStart}
+ → {selectedEvent.extendedProps.formattedEnd}
+ {/if}
+ {/if}
+
+ {#if !selectedEvent.extendedProps.isAllDay && selectedEvent.extendedProps.timezone}
+
+ {selectedEvent.extendedProps.timezone}
+
+ {/if}
+
+
+
+
+
+
+ {#if selectedEvent.extendedProps.location}
+
+
+
+
+
{selectedEvent.extendedProps.location}
+
+
+
+ {/if}
+
+
+ {#if selectedEvent.extendedProps.description}
+
+
+
{$t('adventures.description')}
+
+ {@html renderMarkdown(selectedEvent.extendedProps.description || '')}
+
+
+
+ {/if}
+
+ {#if selectedEvent.extendedProps.adventureId}
+
+ {$t('map.view_details')}
+
+ {/if}
+
+
+
+
+
+
+
+
+
+
+{/if}
diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte
index 5f64513..cd75934 100644
--- a/frontend/src/routes/collections/[id]/+page.svelte
+++ b/frontend/src/routes/collections/[id]/+page.svelte
@@ -17,12 +17,13 @@
import Plus from '~icons/mdi/plus';
import AdventureCard from '$lib/components/AdventureCard.svelte';
import AdventureLink from '$lib/components/AdventureLink.svelte';
- import NotFound from '$lib/components/NotFound.svelte';
- import { DefaultMarker, MapLibre, Marker, Popup, LineLayer, GeoJSON } from 'svelte-maplibre';
+ import { MapLibre, Marker, Popup, LineLayer, GeoJSON } from 'svelte-maplibre';
import TransportationCard from '$lib/components/TransportationCard.svelte';
import NoteCard from '$lib/components/NoteCard.svelte';
import NoteModal from '$lib/components/NoteModal.svelte';
+ const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
+
import {
groupAdventuresByDate,
groupNotesByDate,
@@ -31,8 +32,11 @@
osmTagToEmoji,
groupLodgingByDate,
LODGING_TYPES_ICONS,
- getBasemapUrl
+ getBasemapUrl,
+ isAllDay
} from '$lib';
+ import { formatDateInTimezone, formatAllDayDate } from '$lib/dateUtils';
+
import ChecklistCard from '$lib/components/ChecklistCard.svelte';
import ChecklistModal from '$lib/components/ChecklistModal.svelte';
import AdventureModal from '$lib/components/AdventureModal.svelte';
@@ -60,19 +64,6 @@
let collection: Collection;
- // add christmas and new years
- // dates = Array.from({ length: 25 }, (_, i) => {
- // const date = new Date();
- // date.setMonth(11);
- // date.setDate(i + 1);
- // return {
- // id: i.toString(),
- // start: date.toISOString(),
- // end: date.toISOString(),
- // title: '🎄'
- // };
- // });
-
let dates: Array<{
id: string;
start: string;
@@ -93,16 +84,79 @@
dates = [];
if (adventures) {
- dates = dates.concat(
- adventures.flatMap((adventure) =>
- adventure.visits.map((visit) => ({
- id: adventure.id,
- start: visit.start_date || '', // Ensure it's a string
- end: visit.end_date || visit.start_date || '', // Ensure it's a string
- title: adventure.name + (adventure.category?.icon ? ' ' + adventure.category.icon : '')
- }))
- )
- );
+ adventures.forEach((adventure) => {
+ adventure.visits.forEach((visit) => {
+ if (visit.start_date) {
+ let startDate = visit.start_date;
+ let endDate = visit.end_date || visit.start_date;
+ const targetTimezone = visit.timezone || userTimezone;
+ const allDay = isAllDay(visit.start_date);
+
+ // Handle timezone conversion for non-all-day events
+ if (!allDay) {
+ // Convert UTC dates to target timezone
+ const startDateTime = new Date(visit.start_date);
+ const endDateTime = new Date(visit.end_date || visit.start_date);
+
+ // Format for calendar (ISO string in target timezone)
+ startDate = new Intl.DateTimeFormat('sv-SE', {
+ timeZone: targetTimezone,
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ hourCycle: 'h23'
+ })
+ .format(startDateTime)
+ .replace(' ', 'T');
+
+ endDate = new Intl.DateTimeFormat('sv-SE', {
+ timeZone: targetTimezone,
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ hourCycle: 'h23'
+ })
+ .format(endDateTime)
+ .replace(' ', 'T');
+ } else {
+ // For all-day events, use just the date part
+ startDate = visit.start_date.split('T')[0];
+
+ // For all-day events, add one day to end date to make it inclusive
+ const endDateObj = new Date(visit.end_date || visit.start_date);
+ endDateObj.setDate(endDateObj.getDate() + 1);
+ endDate = endDateObj.toISOString().split('T')[0];
+ }
+
+ // Create detailed title with timezone info
+ let detailedTitle = adventure.name;
+ if (adventure.category?.icon) {
+ detailedTitle = `${adventure.category.icon} ${detailedTitle}`;
+ }
+
+ // Add time info to title for non-all-day events
+ if (!allDay) {
+ const startTime = formatDateInTimezone(visit.start_date, targetTimezone);
+ detailedTitle += ` (${startTime.split(' ').slice(-2).join(' ')})`;
+ if (targetTimezone !== userTimezone) {
+ detailedTitle += ` ${targetTimezone}`;
+ }
+ }
+
+ dates.push({
+ id: adventure.id,
+ start: startDate,
+ end: endDate,
+ title: detailedTitle,
+ backgroundColor: '#3b82f6'
+ });
+ }
+ });
+ });
}
if (transportations) {
@@ -113,7 +167,8 @@
id: transportation.id,
start: transportation.date || '', // Ensure it's a string
end: transportation.end_date || transportation.date || '', // Ensure it's a string
- title: transportation.name + (transportation.type ? ` (${transportation.type})` : '')
+ title: transportation.name + (transportation.type ? ` (${transportation.type})` : ''),
+ backgroundColor: '#10b981'
}))
);
}
@@ -122,12 +177,61 @@
dates = dates.concat(
lodging
.filter((i) => i.check_in)
- .map((lodging) => ({
- id: lodging.id,
- start: lodging.check_in || '', // Ensure it's a string
- end: lodging.check_out || lodging.check_in || '', // Ensure it's a string
- title: lodging.name
- }))
+ .map((lodging) => {
+ const checkIn = lodging.check_in;
+ const checkOut = lodging.check_out || lodging.check_in;
+ if (!checkIn) return null;
+
+ const isAlldayLodging: boolean = isAllDay(checkIn as string);
+
+ let startDate: string;
+ let endDate: string;
+
+ if (isAlldayLodging) {
+ // For all-day, use date part only, no timezone conversion
+ startDate = (checkIn as string).split('T')[0];
+
+ const endDateObj = new Date(checkOut as string);
+ endDateObj.setDate(endDateObj.getDate());
+ endDate = endDateObj.toISOString().split('T')[0];
+
+ return {
+ id: lodging.id,
+ start: startDate,
+ end: endDate,
+ title: `${getLodgingIcon(lodging.type)} ${lodging.name}`,
+ backgroundColor: '#f59e0b'
+ };
+ } else {
+ // Only use timezone if not all-day
+ const lodgingTimezone = lodging.timezone || userTimezone;
+ const checkInDateTime = new Date(checkIn as string);
+ const checkOutDateTime = new Date(checkOut as string);
+
+ startDate = new Intl.DateTimeFormat('sv-SE', {
+ timeZone: lodgingTimezone,
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit'
+ }).format(checkInDateTime);
+
+ endDate = new Intl.DateTimeFormat('sv-SE', {
+ timeZone: lodgingTimezone,
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit'
+ }).format(checkOutDateTime);
+
+ return {
+ id: lodging.id,
+ start: startDate,
+ end: endDate,
+ title: lodging.name,
+ backgroundColor: '#f59e0b'
+ };
+ }
+ })
+ .filter((item) => item !== null)
);
}
@@ -140,11 +244,6 @@
let adventures: Adventure[] = [];
- // Add this after your existing MapLibre markers
-
- // Add this after your existing MapLibre markers
-
- // Create line data from orderedItems
$: lineData = createLineData(orderedItems);
// Function to create GeoJSON line data from ordered items