diff --git a/backend/server/adventures/migrations/0025_alter_visit_end_date_alter_visit_start_date.py b/backend/server/adventures/migrations/0025_alter_visit_end_date_alter_visit_start_date.py
new file mode 100644
index 0000000..668e968
--- /dev/null
+++ b/backend/server/adventures/migrations/0025_alter_visit_end_date_alter_visit_start_date.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.0.8 on 2025-03-17 21:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('adventures', '0024_alter_attachment_file'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='visit',
+ name='end_date',
+ field=models.DateTimeField(blank=True, null=True),
+ ),
+ migrations.AlterField(
+ model_name='visit',
+ name='start_date',
+ field=models.DateTimeField(blank=True, null=True),
+ ),
+ ]
diff --git a/backend/server/adventures/models.py b/backend/server/adventures/models.py
index c7f78ca..0d53bc9 100644
--- a/backend/server/adventures/models.py
+++ b/backend/server/adventures/models.py
@@ -76,8 +76,8 @@ User = get_user_model()
class Visit(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
adventure = models.ForeignKey('Adventure', on_delete=models.CASCADE, related_name='visits')
- start_date = models.DateField(null=True, blank=True)
- end_date = models.DateField(null=True, blank=True)
+ start_date = models.DateTimeField(null=True, blank=True)
+ end_date = models.DateTimeField(null=True, blank=True)
notes = models.TextField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
diff --git a/backend/server/adventures/serializers.py b/backend/server/adventures/serializers.py
index 97dd633..d69466d 100644
--- a/backend/server/adventures/serializers.py
+++ b/backend/server/adventures/serializers.py
@@ -136,9 +136,11 @@ class AdventureSerializer(CustomModelSerializer):
def get_is_visited(self, obj):
current_date = timezone.now().date()
for visit in obj.visits.all():
- if visit.start_date and visit.end_date and (visit.start_date <= current_date):
+ start_date = visit.start_date.date() if isinstance(visit.start_date, timezone.datetime) else visit.start_date
+ end_date = visit.end_date.date() if isinstance(visit.end_date, timezone.datetime) else visit.end_date
+ if start_date and end_date and (start_date <= current_date):
return True
- elif visit.start_date and not visit.end_date and (visit.start_date <= current_date):
+ elif start_date and not end_date and (start_date <= current_date):
return True
return False
diff --git a/backend/server/adventures/views/collection_view.py b/backend/server/adventures/views/collection_view.py
index f0529ee..2c46dc5 100644
--- a/backend/server/adventures/views/collection_view.py
+++ b/backend/server/adventures/views/collection_view.py
@@ -22,7 +22,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
order_by = self.request.query_params.get('order_by', 'name')
order_direction = self.request.query_params.get('order_direction', 'asc')
- valid_order_by = ['name', 'upated_at']
+ valid_order_by = ['name', 'upated_at', 'start_date']
if order_by not in valid_order_by:
order_by = 'updated_at'
@@ -35,6 +35,12 @@ class CollectionViewSet(viewsets.ModelViewSet):
ordering = 'lower_name'
if order_direction == 'desc':
ordering = f'-{ordering}'
+ elif order_by == 'start_date':
+ ordering = 'start_date'
+ if order_direction == 'asc':
+ ordering = 'start_date'
+ else:
+ ordering = '-start_date'
else:
order_by == 'updated_at'
ordering = 'updated_at'
diff --git a/backend/server/users/backends.py b/backend/server/users/backends.py
index a099f11..f9291f0 100644
--- a/backend/server/users/backends.py
+++ b/backend/server/users/backends.py
@@ -3,7 +3,6 @@ from allauth.socialaccount.models import SocialAccount
class NoPasswordAuthBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
- print("NoPasswordAuthBackend")
# First, attempt normal authentication
user = super().authenticate(request, username=username, password=password, **kwargs)
if user is None:
diff --git a/documentation/.vitepress/config.mts b/documentation/.vitepress/config.mts
index ef5cd99..4ef995d 100644
--- a/documentation/.vitepress/config.mts
+++ b/documentation/.vitepress/config.mts
@@ -84,6 +84,16 @@ export default defineConfig({
},
],
},
+ {
+ text: "Usage",
+ collapsed: false,
+ items: [
+ {
+ text: "How to use AdventureLog",
+ link: "/docs/usage/usage",
+ },
+ ],
+ },
{
text: "Configuration",
collapsed: false,
@@ -134,6 +144,10 @@ export default defineConfig({
text: "No Images Displaying",
link: "/docs/troubleshooting/no_images",
},
+ {
+ text: "Login and Registration Unresponsive",
+ link: "/docs/troubleshooting/login_unresponsive",
+ },
{
text: "Failed to Start Nginx",
link: "/docs/troubleshooting/nginx_failed",
diff --git a/documentation/docs/intro/adventurelog_overview.md b/documentation/docs/intro/adventurelog_overview.md
index 310237f..cfb30f4 100644
--- a/documentation/docs/intro/adventurelog_overview.md
+++ b/documentation/docs/intro/adventurelog_overview.md
@@ -27,4 +27,6 @@ AdventureLog is open-source software, licensed under the GPL-3.0 license. This m
## About the Maintainer
-AdventureLog is created and maintained by [Sean Morley](https://seanmorley.com), a Computer Science student at the University of Connecticut. Sean is passionate about open-source software and building modern tools that help people solve real-world problems.
+Hi, I'm [Sean Morley](https://seanmorley.com), the creator of AdventureLog. I'm a Computer Science student at the University of Connecticut, and I'm passionate about open-source software and building modern tools that help people solve real-world problems. I created AdventureLog to solve a problem: the lack of a modern, open-source, user-friendly travel companion. Many existing travel apps are either too complex, too expensive, or too closed-off to be useful for the average traveler. AdventureLog aims to be the opposite: simple, beautiful, and open to everyone.
+
+I hope you enjoy using AdventureLog as much as I enjoy creating it! If you have any questions, feedback, or suggestions, feel free to reach out to me via the email address listed on my website. I'm always happy to hear from users and help in any way I can. Thank you for using AdventureLog, and happy travels! 🌍
diff --git a/documentation/docs/troubleshooting/login_unresponsive.md b/documentation/docs/troubleshooting/login_unresponsive.md
new file mode 100644
index 0000000..aecf431
--- /dev/null
+++ b/documentation/docs/troubleshooting/login_unresponsive.md
@@ -0,0 +1,19 @@
+# Troubleshooting: Login and Registration Unresponsive
+
+When you encounter issues with the login and registration pages being unresponsive in AdventureLog, it can be due to various reasons. This guide will help you troubleshoot and resolve the unresponsive login and registration pages in AdventureLog.
+
+1. Check to make sure the backend container is running and accessible.
+
+ - Check the backend container logs to see if there are any errors or issues blocking the contianer from running.
+
+2. Check the connection between the frontend and backend containers.
+
+ - Attempt login with the browser console network tab open to see if there are any errors or issues with the connection between the frontend and backend containers. If there is a connection issue, the code will show an error like `Failed to load resource: net::ERR_CONNECTION_REFUSED`. If this is the case, check the `PUBLIC_SERVER_URL` in the frontend container and refer to the installation docs to ensure the correct URL is set.
+ - If the error is `403`, continue to the next step.
+
+3. The error most likely is due to a CSRF security config issue in either the backend or frontend.
+
+ - Check that the `ORIGIN` variable in the frontend is set to the URL where the frontend is access and you are accessing the app from currently.
+ - Check that the `CSRF_TRUSTED_ORIGINS` variable in the backend is set to a comma separated list of the origins where you use your backend server and frontend. One of these values should match the `ORIGIN` variable in the frontend.
+
+4. If you are still experiencing issues, please refer to the [AdventureLog Discord Server](https://discord.gg/wRbQ9Egr8C) for further assistance, providing as much detail as possible about the issue you are experiencing!
diff --git a/documentation/docs/usage/usage.md b/documentation/docs/usage/usage.md
new file mode 100644
index 0000000..8cf0129
--- /dev/null
+++ b/documentation/docs/usage/usage.md
@@ -0,0 +1,29 @@
+# How to use AdventureLog
+
+Welcome to AdventureLog! This guide will help you get started with AdventureLog and provide you with an overview of the features available to you.
+
+## Key Terms
+
+#### Adventures
+
+- **Adventure**: think of an adventure as a point on a map, a location you want to visit, or a place you want to explore. An adventure can be anything you want it to be, from a local park to a famous landmark.
+- **Visit**: a visit is added to an adventure. It contains a date and notes about when the adventure was visited. If an adventure is visited multiple times, multiple visits can be added. If there are no visits on an adventure or the date of all visits is in the future, the adventure is considered planned. If the date of the visit is in the past, the adventure is considered completed.
+- **Category**: a category is a way to group adventures together. For example, you could have a category for parks, a category for museums, and a category for restaurants.
+- **Tag**: a tag is a way to add additional information to an adventure. For example, you could have a tag for the type of cuisine at a restaurant or the type of art at a museum. Multiple tags can be added to an adventure.
+- **Image**: an image is a photo that is added to an adventure. Images can be added to an adventure to provide a visual representation of the location or to capture a memory of the visit. These can be uploded from your device or with a service like [Immich](/docs/configuration/immich_integration) if the integration is enabled.
+- **Attachment**: an attachment is a file that is added to an adventure. Attachments can be added to an adventure to provide additional information, such as a map of the location or a brochure from the visit.
+
+#### Collections
+
+- **Collection**: a collection is a way to group adventures together. Collections are flexible and can be used in many ways. When no start or end date is added to a collection, it acts like a folder to group adventures together. When a start and end date is added to a collection, it acts like a trip to group adventures together that were visited during that time period. With start and end dates, the collection is transformed into a full itinerary with a map showing the route taken between adventures.
+- **Transportation**: a transportation is a collection exclusive feature that allows you to add transportation information to your trip. This can be used to show the route taken between locations and the mode of transportation used. It can also be used to track flight information, such as flight number and departure time.
+- **Lodging**: a lodging is a collection exclusive feature that allows you to add lodging information to your trip. This can be used to plan where you will stay during your trip and add notes about the lodging location. It can also be used to track reservation information, such as reservation number and check-in time.
+- **Note**: a note is a collection exclusive feature that allows you to add notes to your trip. This can be used to add additional information about your trip, such as a summary of the trip or a list of things to do. Notes can be assigned to a specific day of the trip to help organize the information.
+- **Checklist**: a checklist is a collection exclusive feature that allows you to add a checklist to your trip. This can be used to create a list of things to do during your trip or for planning purposes like packing lists. Checklists can be assigned to a specific day of the trip to help organize the information.
+
+#### World Travel
+
+- **World Travel**: the world travel feature of AdventureLog allows you to track the countries, regions, and cities you have visited during your lifetime. You can add visits to countries, regions, and cities, and view statistics about your travels. The world travel feature is a fun way to visualize where you have been and where you want to go next.
+ - **Country**: a country is a geographical area that is recognized as an independent nation. You can add visits to countries to track where you have been.
+ - **Region**: a region is a geographical area that is part of a country. You can add visits to regions to track where you have been within a country.
+ - **City**: a city is a geographical area that is a populated urban center. You can add visits to cities to track where you have been within a region.
diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte
index c8b0743..85db0a4 100644
--- a/frontend/src/lib/components/AdventureModal.svelte
+++ b/frontend/src/lib/components/AdventureModal.svelte
@@ -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 isn’t provided, assume it’s 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}
+ {$t('adventures.all_day')}
+
diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts
index 0e14369..d66abec 100644
--- a/frontend/src/lib/index.ts
+++ b/frontend/src/lib/index.ts
@@ -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 {
const groupedTransportations: Record = {};
- // 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 {
const groupedNotes: Record = {};
- // 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 {
const groupedChecklists: Record = {};
- // 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) {
diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json
index 56c84dc..13a02ad 100644
--- a/frontend/src/locales/de.json
+++ b/frontend/src/locales/de.json
@@ -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",
diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json
index 25a79de..72d1883 100644
--- a/frontend/src/locales/en.json
+++ b/frontend/src/locales/en.json
@@ -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",
diff --git a/frontend/src/locales/es.json b/frontend/src/locales/es.json
index 3814ec2..c82df3f 100644
--- a/frontend/src/locales/es.json
+++ b/frontend/src/locales/es.json
@@ -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",
diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json
index 3e6ec37..249243d 100644
--- a/frontend/src/locales/fr.json
+++ b/frontend/src/locales/fr.json
@@ -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é",
diff --git a/frontend/src/locales/it.json b/frontend/src/locales/it.json
index a68ce99..87c963f 100644
--- a/frontend/src/locales/it.json
+++ b/frontend/src/locales/it.json
@@ -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à",
diff --git a/frontend/src/locales/ko.json b/frontend/src/locales/ko.json
index bfe761b..dda6962 100644
--- a/frontend/src/locales/ko.json
+++ b/frontend/src/locales/ko.json
@@ -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": "두 암호 모두 필요합니다",
diff --git a/frontend/src/locales/nl.json b/frontend/src/locales/nl.json
index 4a783ec..7cb51dc 100644
--- a/frontend/src/locales/nl.json
+++ b/frontend/src/locales/nl.json
@@ -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",
diff --git a/frontend/src/locales/pl.json b/frontend/src/locales/pl.json
index 3925de1..c4cd914 100644
--- a/frontend/src/locales/pl.json
+++ b/frontend/src/locales/pl.json
@@ -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",
diff --git a/frontend/src/locales/sv.json b/frontend/src/locales/sv.json
index c6fd200..5efbf63 100644
--- a/frontend/src/locales/sv.json
+++ b/frontend/src/locales/sv.json
@@ -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",
diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json
index 864d699..84caea8 100644
--- a/frontend/src/locales/zh.json
+++ b/frontend/src/locales/zh.json
@@ -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": "忘记密码?",
diff --git a/frontend/src/routes/adventures/[id]/+page.svelte b/frontend/src/routes/adventures/[id]/+page.svelte
index 674a5f4..6d88e0e 100644
--- a/frontend/src/routes/adventures/[id]/+page.svelte
+++ b/frontend/src/routes/adventures/[id]/+page.svelte
@@ -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 @@
{#each adventure.visits as visit}
-
-
- {visit.start_date
- ? new Date(visit.start_date).toLocaleDateString(undefined, {
+