1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-08-04 20:55:19 +02:00

Merge branch 'development' into main

This commit is contained in:
Sean Morley 2025-03-21 17:34:03 -04:00 committed by GitHub
commit 44ede92b92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 939 additions and 270 deletions

View file

@ -6,6 +6,20 @@
import { t } from 'svelte-i18n';
export let collection: Collection | null = null;
let fullStartDate: string = '';
let fullEndDate: string = '';
let fullStartDateOnly: string = '';
let fullEndDateOnly: string = '';
let allDay: boolean = true;
// Set full start and end dates from collection
if (collection && collection.start_date && collection.end_date) {
fullStartDate = `${collection.start_date}T00:00`;
fullEndDate = `${collection.end_date}T23:59`;
fullStartDateOnly = collection.start_date;
fullEndDateOnly = collection.end_date;
}
const dispatch = createEventDispatcher();
let images: { id: string; image: string; is_primary: boolean }[] = [];
@ -72,7 +86,7 @@
import ActivityComplete from './ActivityComplete.svelte';
import CategoryDropdown from './CategoryDropdown.svelte';
import { findFirstValue } from '$lib';
import { findFirstValue, isAllDay } from '$lib';
import MarkdownEditor from './MarkdownEditor.svelte';
import ImmichSelect from './ImmichSelect.svelte';
import Star from '~icons/mdi/star';
@ -379,7 +393,10 @@
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;
}
@ -391,15 +408,31 @@
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: ''
id: '' // or generate an id as needed
}
];
// Clear the input fields.
new_start_date = '';
new_end_date = '';
new_notes = '';
@ -669,13 +702,23 @@
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 !constrainDates}
{#if !allDay}
<input
type="date"
type="datetime-local"
class="input input-bordered w-full"
placeholder="Start Date"
placeholder={$t('adventures.start_date')}
min={constrainDates ? fullStartDate : ''}
max={constrainDates ? fullEndDate : ''}
bind:value={new_start_date}
on:keydown={(e) => {
if (e.key === 'Enter') {
@ -685,10 +728,12 @@
}}
/>
<input
type="date"
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();
@ -701,8 +746,8 @@
type="date"
class="input input-bordered w-full"
placeholder={$t('adventures.start_date')}
min={collection?.start_date}
max={collection?.end_date}
min={constrainDates ? fullStartDateOnly : ''}
max={constrainDates ? fullEndDateOnly : ''}
bind:value={new_start_date}
on:keydown={(e) => {
if (e.key === 'Enter') {
@ -716,8 +761,8 @@
class="input input-bordered w-full"
placeholder={$t('adventures.end_date')}
bind:value={new_end_date}
min={collection?.start_date}
max={collection?.end_date}
min={constrainDates ? fullStartDateOnly : ''}
max={constrainDates ? fullEndDateOnly : ''}
on:keydown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
@ -741,6 +786,31 @@
}}
></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}
@ -749,24 +819,86 @@
</div>
{#if adventure.visits.length > 0}
<h2 class=" font-bold text-xl mt-2">{$t('adventures.my_visits')}</h2>
<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">
<div class="flex gap-2 items-center">
<p>
{new Date(visit.start_date).toLocaleDateString(undefined, {
timeZone: 'UTC'
})}
{#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>
{new Date(visit.end_date).toLocaleDateString(undefined, {
timeZone: 'UTC'
})}
{#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"

View file

@ -189,10 +189,31 @@
</div>
</div>
</div>
<!-- Form Actions -->
{#if !collection.start_date && !collection.end_date}
<div class="mt-4">
<div role="alert" class="alert alert-neutral">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="h-6 w-6 shrink-0 stroke-current"
>
<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('adventures.collection_no_start_end_date')}</span>
</div>
</div>
{/if}
<div class="mt-4">
<button type="submit" class="btn btn-primary">
{$t('adventures.save_next')}
{$t('notes.save')}
</button>
<button type="button" class="btn" on:click={close}>
{$t('about.close')}

View file

@ -124,7 +124,7 @@
<div class="flex items-center gap-2">
<span class="font-medium text-sm">{$t('adventures.dates')}:</span>
<p>
{new Date(lodging.check_in).toLocaleString('en-US', {
{new Date(lodging.check_in).toLocaleString(undefined, {
month: 'short',
day: 'numeric',
year: 'numeric',
@ -132,7 +132,7 @@
minute: 'numeric'
})}
-
{new Date(lodging.check_out).toLocaleString('en-US', {
{new Date(lodging.check_out).toLocaleString(undefined, {
month: 'short',
day: 'numeric',
year: 'numeric',

View file

@ -7,7 +7,15 @@
import { t } from 'svelte-i18n';
import DeleteWarning from './DeleteWarning.svelte';
// import ArrowDownThick from '~icons/mdi/arrow-down-thick';
import { TRANSPORTATION_TYPES_ICONS } from '$lib';
function getTransportationIcon(type: string) {
if (type in TRANSPORTATION_TYPES_ICONS) {
return TRANSPORTATION_TYPES_ICONS[type as keyof typeof TRANSPORTATION_TYPES_ICONS];
} else {
return '🚗';
}
}
const dispatch = createEventDispatcher();
export let transportation: Transportation;
@ -106,7 +114,9 @@
<h2 class="card-title text-lg font-semibold truncate">{transportation.name}</h2>
<div class="flex items-center gap-2">
<div class="badge badge-secondary">
{$t(`transportation.modes.${transportation.type}`)}
{$t(`transportation.modes.${transportation.type}`) +
' ' +
getTransportationIcon(transportation.type)}
</div>
{#if transportation.type == 'plane' && transportation.flight_number}
<div class="badge badge-neutral-200">{transportation.flight_number}</div>
@ -128,7 +138,7 @@
{#if transportation.date}
<div class="flex items-center gap-2">
<span class="font-medium text-sm">{$t('adventures.start')}:</span>
<p>{new Date(transportation.date).toLocaleString(undefined, { timeZone: 'UTC' })}</p>
<p>{new Date(transportation.date).toLocaleString()}</p>
</div>
{/if}
</div>
@ -146,7 +156,7 @@
{#if transportation.end_date}
<div class="flex items-center gap-2">
<span class="font-medium text-sm">{$t('adventures.end')}:</span>
<p>{new Date(transportation.end_date).toLocaleString(undefined, { timeZone: 'UTC' })}</p>
<p>{new Date(transportation.end_date).toLocaleString()}</p>
</div>
{/if}
</div>

View file

@ -16,10 +16,15 @@
let constrainDates: boolean = false;
// 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);
return date.toISOString().slice(0, 16); // Format: YYYY-MM-DDTHH:mm
// 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);
}
let transportation: Transportation = {
@ -185,6 +190,14 @@
return;
}
// Convert local dates to UTC
if (transportation.date && !transportation.date.includes('Z')) {
transportation.date = new Date(transportation.date).toISOString();
}
if (transportation.end_date && !transportation.end_date.includes('Z')) {
transportation.end_date = new Date(transportation.end_date).toISOString();
}
if (transportation.type != 'plane') {
transportation.flight_number = '';
}
@ -422,6 +435,29 @@
</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')}:
{(() => {
const tz = new Intl.DateTimeFormat().resolvedOptions().timeZone;
const [continent, city] = tz.split('/');
return `${continent} (${city.replace('_', ' ')})`;
})()}
</span>
</div>
</div>
</div>

View file

@ -70,34 +70,65 @@ export function groupAdventuresByDate(
// Initialize all days in the range
for (let i = 0; i < numberOfDays; i++) {
const currentDate = new Date(startDate);
currentDate.setUTCDate(startDate.getUTCDate() + i);
const dateString = currentDate.toISOString().split('T')[0];
currentDate.setDate(startDate.getDate() + i);
const dateString = getLocalDateString(currentDate);
groupedAdventures[dateString] = [];
}
adventures.forEach((adventure) => {
adventure.visits.forEach((visit) => {
if (visit.start_date) {
const adventureDate = new Date(visit.start_date).toISOString().split('T')[0];
if (visit.end_date) {
const endDate = new Date(visit.end_date).toISOString().split('T')[0];
// Check if this is an all-day event (both start and end at midnight)
const isAllDayEvent =
isAllDay(visit.start_date) && (visit.end_date ? isAllDay(visit.end_date) : false);
// Loop through all days and include adventure if it falls within the range
// For all-day events, we need to handle dates differently
if (isAllDayEvent && visit.end_date) {
// Extract just the date parts without time
const startDateStr = visit.start_date.split('T')[0];
const endDateStr = visit.end_date.split('T')[0];
// Loop through all days in the range
for (let i = 0; i < numberOfDays; i++) {
const currentDate = new Date(startDate);
currentDate.setUTCDate(startDate.getUTCDate() + i);
const dateString = currentDate.toISOString().split('T')[0];
currentDate.setDate(startDate.getDate() + i);
const currentDateStr = getLocalDateString(currentDate);
// Include the current day if it falls within the adventure date range
if (dateString >= adventureDate && dateString <= endDate) {
if (groupedAdventures[dateString]) {
groupedAdventures[dateString].push(adventure);
if (currentDateStr >= startDateStr && currentDateStr <= endDateStr) {
if (groupedAdventures[currentDateStr]) {
groupedAdventures[currentDateStr].push(adventure);
}
}
}
} else if (groupedAdventures[adventureDate]) {
// If there's no end date, add adventure to the start date only
groupedAdventures[adventureDate].push(adventure);
} else {
// Handle regular events with time components
const adventureStartDate = new Date(visit.start_date);
const adventureDateStr = getLocalDateString(adventureStartDate);
if (visit.end_date) {
const adventureEndDate = new Date(visit.end_date);
const endDateStr = getLocalDateString(adventureEndDate);
// Loop through all days and include adventure if it falls within the range
for (let i = 0; i < numberOfDays; i++) {
const currentDate = new Date(startDate);
currentDate.setDate(startDate.getDate() + i);
const dateString = getLocalDateString(currentDate);
// Include the current day if it falls within the adventure date range
if (dateString >= adventureDateStr && dateString <= endDateStr) {
if (groupedAdventures[dateString]) {
groupedAdventures[dateString].push(adventure);
}
}
}
} else {
// If there's no end date, add adventure to the start date only
if (groupedAdventures[adventureDateStr]) {
groupedAdventures[adventureDateStr].push(adventure);
}
}
}
}
});
@ -106,6 +137,20 @@ export function groupAdventuresByDate(
return groupedAdventures;
}
function getLocalDateString(date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are 0-indexed
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// Helper to check if a given date string represents midnight (all-day)
// Improved isAllDay function to handle different ISO date formats
export function isAllDay(dateStr: string): boolean {
// Check for various midnight formats in UTC
return dateStr.endsWith('T00:00:00Z') || dateStr.endsWith('T00:00:00.000Z');
}
export function groupTransportationsByDate(
transportations: Transportation[],
startDate: Date,
@ -116,22 +161,22 @@ export function groupTransportationsByDate(
// Initialize all days in the range
for (let i = 0; i < numberOfDays; i++) {
const currentDate = new Date(startDate);
currentDate.setUTCDate(startDate.getUTCDate() + i);
const dateString = currentDate.toISOString().split('T')[0];
currentDate.setDate(startDate.getDate() + i);
const dateString = getLocalDateString(currentDate);
groupedTransportations[dateString] = [];
}
transportations.forEach((transportation) => {
if (transportation.date) {
const transportationDate = new Date(transportation.date).toISOString().split('T')[0];
const transportationDate = getLocalDateString(new Date(transportation.date));
if (transportation.end_date) {
const endDate = new Date(transportation.end_date).toISOString().split('T')[0];
// Loop through all days and include transportation if it falls within the range
for (let i = 0; i < numberOfDays; i++) {
const currentDate = new Date(startDate);
currentDate.setUTCDate(startDate.getUTCDate() + i);
const dateString = currentDate.toISOString().split('T')[0];
currentDate.setDate(startDate.getDate() + i);
const dateString = getLocalDateString(currentDate);
// Include the current day if it falls within the transportation date range
if (dateString >= transportationDate && dateString <= endDate) {
@ -157,35 +202,32 @@ export function groupLodgingByDate(
): Record<string, Lodging[]> {
const groupedTransportations: Record<string, Lodging[]> = {};
// Initialize all days in the range
// Initialize all days in the range using local dates
for (let i = 0; i < numberOfDays; i++) {
const currentDate = new Date(startDate);
currentDate.setUTCDate(startDate.getUTCDate() + i);
const dateString = currentDate.toISOString().split('T')[0];
currentDate.setDate(startDate.getDate() + i);
const dateString = getLocalDateString(currentDate);
groupedTransportations[dateString] = [];
}
transportations.forEach((transportation) => {
if (transportation.check_in) {
const transportationDate = new Date(transportation.check_in).toISOString().split('T')[0];
// Use local date string conversion
const transportationDate = getLocalDateString(new Date(transportation.check_in));
if (transportation.check_out) {
const endDate = new Date(transportation.check_out).toISOString().split('T')[0];
const endDate = getLocalDateString(new Date(transportation.check_out));
// Loop through all days and include transportation if it falls within the range
// Loop through all days and include transportation if it falls within the transportation date range
for (let i = 0; i < numberOfDays; i++) {
const currentDate = new Date(startDate);
currentDate.setUTCDate(startDate.getUTCDate() + i);
const dateString = currentDate.toISOString().split('T')[0];
currentDate.setDate(startDate.getDate() + i);
const dateString = getLocalDateString(currentDate);
// Include the current day if it falls within the transportation date range
if (dateString >= transportationDate && dateString <= endDate) {
if (groupedTransportations[dateString]) {
groupedTransportations[dateString].push(transportation);
}
groupedTransportations[dateString].push(transportation);
}
}
} else if (groupedTransportations[transportationDate]) {
// If there's no end date, add transportation to the start date only
groupedTransportations[transportationDate].push(transportation);
}
}
@ -201,19 +243,18 @@ export function groupNotesByDate(
): Record<string, Note[]> {
const groupedNotes: Record<string, Note[]> = {};
// Initialize all days in the range
// Initialize all days in the range using local dates
for (let i = 0; i < numberOfDays; i++) {
const currentDate = new Date(startDate);
currentDate.setUTCDate(startDate.getUTCDate() + i);
const dateString = currentDate.toISOString().split('T')[0];
currentDate.setDate(startDate.getDate() + i);
const dateString = getLocalDateString(currentDate);
groupedNotes[dateString] = [];
}
notes.forEach((note) => {
if (note.date) {
const noteDate = new Date(note.date).toISOString().split('T')[0];
// Add note to the appropriate date group if it exists
// Use the date string as is since it's already in "YYYY-MM-DD" format.
const noteDate = note.date;
if (groupedNotes[noteDate]) {
groupedNotes[noteDate].push(note);
}
@ -230,19 +271,18 @@ export function groupChecklistsByDate(
): Record<string, Checklist[]> {
const groupedChecklists: Record<string, Checklist[]> = {};
// Initialize all days in the range
// Initialize all days in the range using local dates
for (let i = 0; i < numberOfDays; i++) {
const currentDate = new Date(startDate);
currentDate.setUTCDate(startDate.getUTCDate() + i);
const dateString = currentDate.toISOString().split('T')[0];
currentDate.setDate(startDate.getDate() + i);
const dateString = getLocalDateString(currentDate);
groupedChecklists[dateString] = [];
}
checklists.forEach((checklist) => {
if (checklist.date) {
const checklistDate = new Date(checklist.date).toISOString().split('T')[0];
// Add checklist to the appropriate date group if it exists
// Use the date string as is since it's already in "YYYY-MM-DD" format.
const checklistDate = checklist.date;
if (groupedChecklists[checklistDate]) {
groupedChecklists[checklistDate].push(checklist);
}
@ -338,6 +378,17 @@ export let LODGING_TYPES_ICONS = {
other: '❓'
};
export let TRANSPORTATION_TYPES_ICONS = {
car: '🚗',
plane: '✈️',
train: '🚆',
bus: '🚌',
boat: '⛵',
bike: '🚲',
walking: '🚶',
other: '❓'
};
export function getAdventureTypeLabel(type: string) {
// return the emoji ADVENTURE_TYPE_ICONS label for the given type if not found return ? emoji
if (type in ADVENTURE_TYPE_ICONS) {

View file

@ -247,7 +247,12 @@
"price": "Preis",
"reservation_number": "Reservierungsnummer",
"welcome_map_info": "Frei zugängliche Abenteuer auf diesem Server",
"open_in_maps": "In Karten öffnen"
"open_in_maps": "In Karten öffnen",
"all_day": "Den ganzen Tag",
"collection_no_start_end_date": "Durch das Hinzufügen eines Start- und Enddatums zur Sammlung werden Reiseroutenplanungsfunktionen auf der Sammlungsseite freigegeben.",
"date_itinerary": "Datumstrecke",
"no_ordered_items": "Fügen Sie der Sammlung Elemente mit Daten hinzu, um sie hier zu sehen.",
"ordered_itinerary": "Reiseroute bestellt"
},
"home": {
"desc_1": "Entdecken, planen und erkunden Sie mühelos",

View file

@ -131,6 +131,7 @@
"search_for_location": "Search for a location",
"clear_map": "Clear map",
"search_results": "Searh results",
"collection_no_start_end_date": "Adding a start and end date to the collection will unlock itinerary planning features in the collection page.",
"no_results": "No results found",
"wiki_desc": "Pulls excerpt from Wikipedia article matching the name of the adventure.",
"attachments": "Attachments",
@ -250,6 +251,10 @@
"show_map": "Show Map",
"emoji_picker": "Emoji Picker",
"download_calendar": "Download Calendar",
"all_day": "All Day",
"ordered_itinerary": "Ordered Itinerary",
"date_itinerary": "Date Itinerary",
"no_ordered_items": "Add items with dates to the collection to see them here.",
"date_information": "Date Information",
"flight_information": "Flight Information",
"out_of_range": "Not in itinerary date range",

View file

@ -295,7 +295,12 @@
"region": "Región",
"reservation_number": "Número de reserva",
"welcome_map_info": "Aventuras públicas en este servidor",
"open_in_maps": "Abrir en mapas"
"open_in_maps": "Abrir en mapas",
"all_day": "Todo el día",
"collection_no_start_end_date": "Agregar una fecha de inicio y finalización a la colección desbloqueará las funciones de planificación del itinerario en la página de colección.",
"date_itinerary": "Itinerario de fecha",
"no_ordered_items": "Agregue elementos con fechas a la colección para verlos aquí.",
"ordered_itinerary": "Itinerario ordenado"
},
"worldtravel": {
"all": "Todo",

View file

@ -247,7 +247,12 @@
"region": "Région",
"reservation_number": "Numéro de réservation",
"welcome_map_info": "Aventures publiques sur ce serveur",
"open_in_maps": "Ouvert dans les cartes"
"open_in_maps": "Ouvert dans les cartes",
"all_day": "Toute la journée",
"collection_no_start_end_date": "L'ajout d'une date de début et de fin à la collection débloquera les fonctionnalités de planification de l'itinéraire dans la page de collection.",
"date_itinerary": "Itinéraire de date",
"no_ordered_items": "Ajoutez des articles avec des dates à la collection pour les voir ici.",
"ordered_itinerary": "Itinéraire ordonné"
},
"home": {
"desc_1": "Découvrez, planifiez et explorez en toute simplicité",

View file

@ -247,7 +247,12 @@
"region": "Regione",
"welcome_map_info": "Avventure pubbliche su questo server",
"reservation_number": "Numero di prenotazione",
"open_in_maps": "Aperto in mappe"
"open_in_maps": "Aperto in mappe",
"all_day": "Tutto il giorno",
"collection_no_start_end_date": "L'aggiunta di una data di inizio e fine alla raccolta sbloccherà le funzionalità di pianificazione dell'itinerario nella pagina di raccolta.",
"date_itinerary": "Itinerario della data",
"no_ordered_items": "Aggiungi articoli con date alla collezione per vederli qui.",
"ordered_itinerary": "Itinerario ordinato"
},
"home": {
"desc_1": "Scopri, pianifica ed esplora con facilità",

View file

@ -247,7 +247,12 @@
"region": "지역",
"reservation_number": "예약 번호",
"welcome_map_info": "이 서버의 공개 모험",
"open_in_maps": "지도에서 열립니다"
"open_in_maps": "지도에서 열립니다",
"all_day": "하루 종일",
"collection_no_start_end_date": "컬렉션에 시작 및 종료 날짜를 추가하면 컬렉션 페이지에서 여정 계획 기능이 잠금 해제됩니다.",
"date_itinerary": "날짜 일정",
"no_ordered_items": "컬렉션에 날짜가있는 항목을 추가하여 여기에서 확인하십시오.",
"ordered_itinerary": "주문한 여정"
},
"auth": {
"both_passwords_required": "두 암호 모두 필요합니다",

View file

@ -247,7 +247,12 @@
"lodging_information": "Informatie overliggen",
"price": "Prijs",
"region": "Regio",
"open_in_maps": "Open in kaarten"
"open_in_maps": "Open in kaarten",
"all_day": "De hele dag",
"collection_no_start_end_date": "Als u een start- en einddatum aan de collectie toevoegt, ontgrendelt u de functies van de planning van de route ontgrendelen in de verzamelpagina.",
"date_itinerary": "Datumroute",
"no_ordered_items": "Voeg items toe met datums aan de collectie om ze hier te zien.",
"ordered_itinerary": "Besteld reisschema"
},
"home": {
"desc_1": "Ontdek, plan en verken met gemak",

View file

@ -295,7 +295,12 @@
"region": "Region",
"reservation_number": "Numer rezerwacji",
"welcome_map_info": "Publiczne przygody na tym serwerze",
"open_in_maps": "Otwarte w mapach"
"open_in_maps": "Otwarte w mapach",
"all_day": "Cały dzień",
"collection_no_start_end_date": "Dodanie daty rozpoczęcia i końca do kolekcji odblokuje funkcje planowania planu podróży na stronie kolekcji.",
"date_itinerary": "Trasa daty",
"no_ordered_items": "Dodaj przedmioty z datami do kolekcji, aby je zobaczyć tutaj.",
"ordered_itinerary": "Zamówiono trasę"
},
"worldtravel": {
"country_list": "Lista krajów",

View file

@ -247,7 +247,12 @@
"price": "Pris",
"region": "Område",
"reservation_number": "Bokningsnummer",
"open_in_maps": "Kappas in"
"open_in_maps": "Kappas in",
"all_day": "Hela dagen",
"collection_no_start_end_date": "Att lägga till ett start- och slutdatum till samlingen kommer att låsa upp planeringsfunktioner för resplan på insamlingssidan.",
"date_itinerary": "Datum resplan",
"no_ordered_items": "Lägg till objekt med datum i samlingen för att se dem här.",
"ordered_itinerary": "Beställd resplan"
},
"home": {
"desc_1": "Upptäck, planera och utforska med lätthet",

View file

@ -295,7 +295,12 @@
"lodging_information": "住宿信息",
"price": "价格",
"reservation_number": "预订号",
"open_in_maps": "在地图上打开"
"open_in_maps": "在地图上打开",
"all_day": "整天",
"collection_no_start_end_date": "在集合页面中添加开始日期和结束日期将在“收集”页面中解锁行程计划功能。",
"date_itinerary": "日期行程",
"no_ordered_items": "将带有日期的项目添加到集合中,以便在此处查看它们。",
"ordered_itinerary": "订购了行程"
},
"auth": {
"forgot_password": "忘记密码?",

View file

@ -91,6 +91,7 @@
import AdventureModal from '$lib/components/AdventureModal.svelte';
import ImageDisplayModal from '$lib/components/ImageDisplayModal.svelte';
import AttachmentCard from '$lib/components/AttachmentCard.svelte';
import { isAllDay } from '$lib';
onMount(async () => {
if (data.props.adventure) {
@ -410,23 +411,33 @@
</p>
<!-- show each visit start and end date as well as notes -->
{#each adventure.visits as visit}
<div class="grid gap-2">
<p class="text-sm text-muted-foreground">
{visit.start_date
? new Date(visit.start_date).toLocaleDateString(undefined, {
<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'
})
: ''}
{visit.end_date &&
visit.end_date !== '' &&
visit.end_date !== visit.start_date
? ' - ' +
new Date(visit.end_date).toLocaleDateString(undefined, {
})}
{: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>
- {new Date(visit.end_date).toLocaleDateString(undefined, {
timeZone: 'UTC'
})
: ''}
</p>
<p class="text-sm text-muted-foreground -mt-2 mb-2">{visit.notes}</p>
})}
{#if !isAllDay(visit.end_date)}
({new Date(visit.end_date).toLocaleTimeString()})
{/if}
</p>
{/if}
</div>
<p class="whitespace-pre-wrap -mt-2 mb-2">{visit.notes}</p>
</div>
{/each}
</div>
@ -445,12 +456,14 @@
</div>
</div>
{/if}
<a
class="btn btn-neutral btn-sm max-w-32"
href={`https://maps.apple.com/?q=${adventure.latitude},${adventure.longitude}`}
target="_blank"
rel="noopener noreferrer">{$t('adventures.open_in_maps')}</a
>
{#if adventure.longitude && adventure.latitude}
<a
class="btn btn-neutral btn-sm max-w-32"
href={`https://maps.apple.com/?q=${adventure.latitude},${adventure.longitude}`}
target="_blank"
rel="noopener noreferrer">{$t('adventures.open_in_maps')}</a
>
{/if}
<MapLibre
style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
class="flex items-center self-center justify-center aspect-[9/16] max-h-[70vh] sm:aspect-video sm:max-h-full w-10/12 rounded-lg"

View file

@ -208,7 +208,7 @@ export const actions: Actions = {
const order_direction = formData.get('order_direction') as string;
const order_by = formData.get('order_by') as string;
console.log(order_direction, order_by);
// console.log(order_direction, order_by);
let adventures: Adventure[] = [];
@ -242,7 +242,7 @@ export const actions: Actions = {
previous = res.previous;
count = res.count;
adventures = [...adventures, ...visited];
console.log(next, previous, count);
// console.log(next, previous, count);
}
return {

View file

@ -15,8 +15,6 @@
let collections: Collection[] = data.props.adventures || [];
let currentSort = { attribute: 'name', order: 'asc' };
let newType: string = '';
let resultsPerPage: number = 25;
@ -235,17 +233,36 @@
aria-label={$t(`adventures.descending`)}
/>
</div>
<p class="text-lg font-semibold mt-2 mb-2">{$t('adventures.order_by')}</p>
<div class="join">
<input
class="join-item btn btn-neutral"
type="radio"
name="order_by"
id="upated_at"
value="upated_at"
aria-label={$t('adventures.updated')}
checked
/>
<input
class="join-item btn btn-neutral"
type="radio"
name="order_by"
id="start_date"
value="start_date"
aria-label={$t('adventures.start_date')}
/>
<input
class="join-item btn btn-neutral"
type="radio"
name="order_by"
id="name"
value="name"
aria-label={$t('adventures.name')}
/>
</div>
<br />
<input
type="radio"
name="order_by"
id="name"
class="radio radio-primary"
checked
value="name"
hidden
/>
<button type="submit" class="btn btn-success btn-primary mt-4"
>{$t(`adventures.sort`)}</button
>

View file

@ -133,9 +133,70 @@
}
let currentView: string = 'itinerary';
let currentItineraryView: string = 'date';
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
function createLineData(
items: Array<{
item: Adventure | Transportation | Lodging | Note | Checklist;
start: string;
end: string;
}>
) {
if (items.length < 2) return null;
const coordinates: [number, number][] = [];
// Extract coordinates from each item
for (const orderItem of items) {
const item = orderItem.item;
if (
'origin_longitude' in item &&
'origin_latitude' in item &&
'destination_longitude' in item &&
'destination_latitude' in item &&
item.origin_longitude &&
item.origin_latitude &&
item.destination_longitude &&
item.destination_latitude
) {
// For Transportation, add both origin and destination points
coordinates.push([item.origin_longitude, item.origin_latitude]);
coordinates.push([item.destination_longitude, item.destination_latitude]);
} else if ('longitude' in item && 'latitude' in item && item.longitude && item.latitude) {
// Handle Adventure and Lodging types
coordinates.push([item.longitude, item.latitude]);
}
}
// Only create line data if we have at least 2 coordinates
if (coordinates.length >= 2) {
return {
type: 'Feature' as const,
properties: {
name: 'Itinerary Path',
description: 'Path connecting chronological items'
},
geometry: {
type: 'LineString' as const,
coordinates: coordinates
}
};
}
return null;
}
let numVisited: number = 0;
let numAdventures: number = 0;
@ -169,6 +230,63 @@
}
}
let orderedItems: Array<{
item: Adventure | Transportation | Lodging;
type: 'adventure' | 'transportation' | 'lodging';
start: string; // ISO date string
end: string; // ISO date string
}> = [];
$: {
// Reset ordered items
orderedItems = [];
// Add Adventures (using visit dates)
adventures.forEach((adventure) => {
adventure.visits.forEach((visit) => {
orderedItems.push({
item: adventure,
start: visit.start_date,
end: visit.end_date,
type: 'adventure'
});
});
});
// Add Transportation
transportations.forEach((transport) => {
if (transport.date) {
// Only add if date exists
orderedItems.push({
item: transport,
start: transport.date,
end: transport.end_date || transport.date, // Use end_date if available, otherwise use date,
type: 'transportation'
});
}
});
// Add Lodging
lodging.forEach((lodging) => {
if (lodging.check_in) {
// Only add if check_in exists
orderedItems.push({
item: lodging,
start: lodging.check_in,
end: lodging.check_out || lodging.check_in, // Use check_out if available, otherwise use check_in,
type: 'lodging'
});
}
});
// Sort all items chronologically by start date
orderedItems.sort((a, b) => {
const dateA = new Date(a.start).getTime();
const dateB = new Date(b.start).getTime();
return dateA - dateB;
});
}
$: {
numAdventures = adventures.length;
numVisited = adventures.filter((adventure) => adventure.is_visited).length;
@ -186,6 +304,7 @@
} else {
notFound = true;
}
if (collection.start_date && collection.end_date) {
numberOfDays =
Math.floor(
@ -806,133 +925,265 @@
})}</span
>
</p>
<div class="join mt-2">
<input
class="join-item btn btn-neutral"
type="radio"
name="options"
aria-label={$t('adventures.date_itinerary')}
checked={currentItineraryView == 'date'}
on:change={() => (currentItineraryView = 'date')}
/>
<input
class="join-item btn btn-neutral"
type="radio"
name="options"
aria-label={$t('adventures.ordered_itinerary')}
checked={currentItineraryView == 'ordered'}
on:change={() => (currentItineraryView = 'ordered')}
/>
</div>
</div>
</div>
</div>
<div class="container mx-auto px-4">
{#each Array(numberOfDays) as _, i}
{@const startDate = new Date(collection.start_date)}
{@const tempDate = new Date(startDate.getTime())}
{@const adjustedDate = new Date(tempDate.setUTCDate(tempDate.getUTCDate() + i))}
{@const dateString = adjustedDate.toISOString().split('T')[0]}
{#if currentItineraryView == 'date'}
<div class="container mx-auto px-4">
{#each Array(numberOfDays) as _, i}
{@const startDate = new Date(collection.start_date)}
{@const tempDate = new Date(startDate.getTime())}
{@const adjustedDate = new Date(tempDate.setUTCDate(tempDate.getUTCDate() + i))}
{@const dateString = adjustedDate.toISOString().split('T')[0]}
{@const dayAdventures =
groupAdventuresByDate(adventures, new Date(collection.start_date), numberOfDays)[
dateString
] || []}
{@const dayTransportations =
groupTransportationsByDate(
transportations,
new Date(collection.start_date),
numberOfDays
)[dateString] || []}
{@const dayLodging =
groupLodgingByDate(lodging, new Date(collection.start_date), numberOfDays)[
dateString
] || []}
{@const dayNotes =
groupNotesByDate(notes, new Date(collection.start_date), numberOfDays)[dateString] ||
[]}
{@const dayChecklists =
groupChecklistsByDate(checklists, new Date(collection.start_date), numberOfDays)[
dateString
] || []}
{@const dayAdventures =
groupAdventuresByDate(adventures, new Date(collection.start_date), numberOfDays + 1)[
dateString
] || []}
{@const dayTransportations =
groupTransportationsByDate(
transportations,
new Date(collection.start_date),
numberOfDays + 1
)[dateString] || []}
{@const dayLodging =
groupLodgingByDate(lodging, new Date(collection.start_date), numberOfDays + 1)[
dateString
] || []}
{@const dayNotes =
groupNotesByDate(notes, new Date(collection.start_date), numberOfDays + 1)[
dateString
] || []}
{@const dayChecklists =
groupChecklistsByDate(checklists, new Date(collection.start_date), numberOfDays + 1)[
dateString
] || []}
<div class="card bg-base-100 shadow-xl my-8">
<div class="card-body bg-base-200">
<h2 class="card-title text-3xl justify-center g">
{$t('adventures.day')}
{i + 1}
<div class="badge badge-lg">
{adjustedDate.toLocaleDateString(undefined, { timeZone: 'UTC' })}
<div class="card bg-base-100 shadow-xl my-8">
<div class="card-body bg-base-200">
<h2 class="card-title text-3xl justify-center g">
{$t('adventures.day')}
{i + 1}
<div class="badge badge-lg">
{adjustedDate.toLocaleDateString(undefined, { timeZone: 'UTC' })}
</div>
</h2>
<div class="divider"></div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{#if dayAdventures.length > 0}
{#each dayAdventures as adventure}
<AdventureCard
user={data.user}
on:edit={editAdventure}
on:delete={deleteAdventure}
{adventure}
/>
{/each}
{/if}
{#if dayTransportations.length > 0}
{#each dayTransportations as transportation}
<TransportationCard
{transportation}
user={data?.user}
on:delete={(event) => {
transportations = transportations.filter((t) => t.id != event.detail);
}}
on:edit={(event) => {
transportationToEdit = event.detail;
isShowingTransportationModal = true;
}}
/>
{/each}
{/if}
{#if dayNotes.length > 0}
{#each dayNotes as note}
<NoteCard
{note}
user={data.user || null}
on:edit={(event) => {
noteToEdit = event.detail;
isNoteModalOpen = true;
}}
on:delete={(event) => {
notes = notes.filter((n) => n.id != event.detail);
}}
/>
{/each}
{/if}
{#if dayLodging.length > 0}
{#each dayLodging as hotel}
<LodgingCard
lodging={hotel}
user={data?.user}
on:delete={(event) => {
lodging = lodging.filter((t) => t.id != event.detail);
}}
on:edit={editLodging}
/>
{/each}
{/if}
{#if dayChecklists.length > 0}
{#each dayChecklists as checklist}
<ChecklistCard
{checklist}
user={data.user || null}
on:delete={(event) => {
notes = notes.filter((n) => n.id != event.detail);
}}
on:edit={(event) => {
checklistToEdit = event.detail;
isShowingChecklistModal = true;
}}
/>
{/each}
{/if}
</div>
</h2>
<div class="divider"></div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{#if dayAdventures.length > 0}
{#each dayAdventures as adventure}
<AdventureCard
user={data.user}
on:edit={editAdventure}
on:delete={deleteAdventure}
{adventure}
{collection}
/>
{/each}
{/if}
{#if dayTransportations.length > 0}
{#each dayTransportations as transportation}
<TransportationCard
{transportation}
{collection}
user={data?.user}
on:delete={(event) => {
transportations = transportations.filter((t) => t.id != event.detail);
}}
on:edit={(event) => {
transportationToEdit = event.detail;
isShowingTransportationModal = true;
}}
/>
{/each}
{/if}
{#if dayNotes.length > 0}
{#each dayNotes as note}
<NoteCard
{note}
{collection}
user={data.user || null}
on:edit={(event) => {
noteToEdit = event.detail;
isNoteModalOpen = true;
}}
on:delete={(event) => {
notes = notes.filter((n) => n.id != event.detail);
}}
/>
{/each}
{/if}
{#if dayLodging.length > 0}
{#each dayLodging as hotel}
<LodgingCard
lodging={hotel}
{collection}
user={data?.user}
on:delete={(event) => {
lodging = lodging.filter((t) => t.id != event.detail);
}}
on:edit={editLodging}
/>
{/each}
{/if}
{#if dayChecklists.length > 0}
{#each dayChecklists as checklist}
<ChecklistCard
{checklist}
{collection}
user={data.user || null}
on:delete={(event) => {
notes = notes.filter((n) => n.id != event.detail);
}}
on:edit={(event) => {
checklistToEdit = event.detail;
isShowingChecklistModal = true;
}}
/>
{/each}
{#if dayAdventures.length == 0 && dayTransportations.length == 0 && dayNotes.length == 0 && dayChecklists.length == 0 && dayLodging.length == 0}
<p class="text-center text-lg mt-2 italic">{$t('adventures.nothing_planned')}</p>
{/if}
</div>
{#if dayAdventures.length == 0 && dayTransportations.length == 0 && dayNotes.length == 0 && dayChecklists.length == 0 && dayLodging.length == 0}
<p class="text-center text-lg mt-2 italic">{$t('adventures.nothing_planned')}</p>
</div>
{/each}
</div>
{:else}
<div class="container mx-auto px-4 py-8">
<div class="flex flex-col items-center">
<div class="w-full max-w-4xl relative">
<!-- Vertical timeline line that spans the entire height -->
{#if orderedItems.length > 0}
<div class="absolute left-8 top-0 bottom-0 w-1 bg-primary"></div>
{/if}
<ul class="relative">
{#each orderedItems as orderedItem, index}
<li class="relative pl-20 mb-8">
<!-- Timeline Icon -->
<div
class="absolute left-0 top-0 flex items-center justify-center w-16 h-16 bg-base-200 rounded-full border-2 border-primary"
>
{#if orderedItem.type === 'adventure' && orderedItem.item && 'category' in orderedItem.item && orderedItem.item.category && 'icon' in orderedItem.item.category}
<span class="text-2xl">{orderedItem.item.category.icon}</span>
{:else if orderedItem.type === 'transportation' && orderedItem.item && 'origin_latitude' in orderedItem.item}
<span class="text-2xl">{getTransportationEmoji(orderedItem.item.type)}</span
>
{:else if orderedItem.type === 'lodging' && orderedItem.item && 'reservation_number' in orderedItem.item}
<span class="text-2xl">{getLodgingIcon(orderedItem.item.type)}</span>
{/if}
</div>
<!-- Card Content -->
<div class="bg-base-200 p-6 rounded-lg shadow-lg">
<div class="flex justify-between items-center mb-4">
<span class="badge badge-lg">{$t(`adventures.${orderedItem.type}`)}</span>
<div class="text-sm opacity-80 text-right">
{new Date(orderedItem.start).toLocaleDateString(undefined, {
month: 'short',
day: 'numeric'
})}
{#if orderedItem.start !== orderedItem.end}
<div>
{new Date(orderedItem.start).toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit'
})}
-
{new Date(orderedItem.end).toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit'
})}
</div>
<div>
<!-- Duration -->
{Math.round(
(new Date(orderedItem.end).getTime() -
new Date(orderedItem.start).getTime()) /
1000 /
60 /
60
)}h
{Math.round(
((new Date(orderedItem.end).getTime() -
new Date(orderedItem.start).getTime()) /
1000 /
60 /
60 -
Math.floor(
(new Date(orderedItem.end).getTime() -
new Date(orderedItem.start).getTime()) /
1000 /
60 /
60
)) *
60
)}m
</div>
{:else}
<p>{$t('adventures.all_day')} ⏱️</p>
{/if}
</div>
</div>
{#if orderedItem.type === 'adventure' && orderedItem.item && 'images' in orderedItem.item}
<AdventureCard
user={data.user}
on:edit={editAdventure}
on:delete={deleteAdventure}
adventure={orderedItem.item}
{collection}
/>
{:else if orderedItem.type === 'transportation' && orderedItem.item && 'origin_latitude' in orderedItem.item}
<TransportationCard
transportation={orderedItem.item}
user={data?.user}
on:delete={(event) => {
transportations = transportations.filter((t) => t.id != event.detail);
}}
on:edit={editTransportation}
{collection}
/>
{:else if orderedItem.type === 'lodging' && orderedItem.item && 'reservation_number' in orderedItem.item}
<LodgingCard
lodging={orderedItem.item}
user={data?.user}
on:delete={(event) => {
lodging = lodging.filter((t) => t.id != event.detail);
}}
on:edit={editLodging}
{collection}
/>
{/if}
</div>
</li>
{/each}
</ul>
{#if orderedItems.length === 0}
<div class="alert alert-info">
<p class="text-center text-lg">{$t('adventures.no_ordered_items')}</p>
</div>
{/if}
</div>
</div>
{/each}
</div>
</div>
{/if}
{/if}
{/if}
@ -999,6 +1250,19 @@
</Marker>
{/if}
{/each}
{#if lineData}
<GeoJSON data={lineData}>
<LineLayer
layout={{ 'line-cap': 'round', 'line-join': 'round' }}
paint={{
'line-width': 4,
'line-color': '#0088CC', // Blue line to distinguish from transportation lines
'line-opacity': 0.8,
'line-dasharray': [2, 1] // Dashed line to differentiate from direct transportation lines
}}
/>
</GeoJSON>
{/if}
{#each transportations as transportation}
{#if transportation.origin_latitude && transportation.origin_longitude && transportation.destination_latitude && transportation.destination_longitude}
<!-- Origin Marker -->
@ -1040,34 +1304,6 @@
</p>
</Popup>
</Marker>
<!-- Line connecting origin and destination -->
<GeoJSON
data={{
type: 'Feature',
properties: {
name: transportation.name,
type: transportation.type
},
geometry: {
type: 'LineString',
coordinates: [
[transportation.origin_longitude, transportation.origin_latitude],
[transportation.destination_longitude, transportation.destination_latitude]
]
}
}}
>
<LineLayer
layout={{ 'line-cap': 'round', 'line-join': 'round' }}
paint={{
'line-width': 3,
'line-color': '#898989', // customize your line color here
'line-opacity': 0.8
// 'line-dasharray': [5, 2]
}}
/>
</GeoJSON>
{/if}
{/each}
@ -1233,13 +1469,16 @@
{recomendation.name || $t('recomendations.recommendation')}
</h2>
<div class="badge badge-primary">{recomendation.tag}</div>
{#if recomendation.address}
{#if recomendation.address && (recomendation.address.housenumber || recomendation.address.street || recomendation.address.city || recomendation.address.state || recomendation.address.postcode)}
<p class="text-md">
<strong>{$t('recomendations.address')}:</strong>
{recomendation.address.housenumber}
{recomendation.address.street}, {recomendation.address.city}, {recomendation
.address.state}
{recomendation.address.postcode}
{#if recomendation.address.housenumber}{recomendation.address
.housenumber}{/if}
{#if recomendation.address.street}
{recomendation.address.street}{/if}
{#if recomendation.address.city}, {recomendation.address.city}{/if}
{#if recomendation.address.state}, {recomendation.address.state}{/if}
{#if recomendation.address.postcode}, {recomendation.address.postcode}{/if}
</p>
{/if}
{#if recomendation.contact}

View file

@ -74,8 +74,6 @@ export const actions: Actions = {
} else {
const setCookieHeader = loginFetch.headers.get('Set-Cookie');
console.log('setCookieHeader:', setCookieHeader);
if (setCookieHeader) {
// Regular expression to match sessionid cookie and its expiry
const sessionIdRegex = /sessionid=([^;]+).*?expires=([^;]+)/;

View file

@ -164,6 +164,14 @@
{#if filteredCountries.length === 0}
<p class="text-center font-bold text-2xl mt-12">{$t('worldtravel.no_countries_found')}</p>
<div class="text-center mt-4">
<a
class="link link-primary"
href="https://adventurelog.app/docs/configuration/updating.html#updating-the-region-data"
target="_blank">{$t('settings.documentation_link')}</a
>
</div>
{/if}
<svelte:head>