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/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index c8b0743..b9daa0f 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -6,6 +6,16 @@ import { t } from 'svelte-i18n'; export let collection: Collection | null = null; + let fullStartDate: string = ''; + let fullEndDate: string = ''; + let allDay: boolean = false; + + // 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`; + } + const dispatch = createEventDispatcher(); let images: { id: string; image: string; is_primary: boolean }[] = []; @@ -72,7 +82,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 +389,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 +404,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 +698,23 @@ on:change={() => (constrainDates = !constrainDates)} /> {/if} + All Day + (allDay = !allDay)} + />
- {new Date(visit.start_date).toLocaleDateString(undefined, { - timeZone: 'UTC' - })} + {#if isAllDay(visit.start_date)} + + {new Date(visit.start_date).toLocaleDateString(undefined, { + timeZone: 'UTC' + })} + {:else} + + {new Date(visit.start_date).toLocaleDateString()} ({new Date( + visit.start_date + ).toLocaleTimeString()}) + {/if}
{#if visit.end_date && visit.end_date !== visit.start_date}{new Date(visit.end_date).toLocaleDateString(undefined, { timeZone: 'UTC' })} + {#if !isAllDay(visit.end_date)} + ({new Date(visit.end_date).toLocaleTimeString()}) + {/if}
{/if} -