feat: enhance lodging management with date validation and update messages

This commit is contained in:
Sean Morley
2025-12-26 11:01:25 -05:00
parent e3e05b5ba3
commit c8cedcd9db
7 changed files with 63 additions and 20 deletions

View File

@@ -716,11 +716,13 @@ class UltraSlimCollectionSerializer(serializers.ModelSerializer):
location__collections=obj
).select_related('user').prefetch_related('location')
return ContentImageSerializer(
serializer = ContentImageSerializer(
images,
many=True,
context={'request': self.context.get('request')}
).data
)
# Filter out None values from the serialized data
return [image for image in serializer.data if image is not None]
def get_location_count(self, obj):
"""Get count of locations in this collection"""

View File

@@ -306,7 +306,7 @@
<figure class="aspect-square bg-base-200 overflow-hidden">
<img
src={image.image_url}
alt="Image from Immich"
alt="Immich"
class="w-full h-full object-cover transition-transform group-hover:scale-105"
loading="lazy"
/>

View File

@@ -593,6 +593,7 @@
// If we updated the item's date, update local state directly
if (updateItemDate) {
const isoDate = `${dateISO}T00:00:00`;
const nextDayISO = DateTime.fromISO(dateISO).plus({ days: 1 }).toISODate();
if (objectType === 'location') {
// For locations, create a new visit locally
@@ -620,15 +621,59 @@
}
} else if (objectType === 'transportation') {
if (collection.transportations) {
collection.transportations = collection.transportations.map((t) =>
t.id === objectId ? { ...t, date: isoDate } : t
);
collection.transportations = collection.transportations.map((t) => {
if (t.id === objectId) {
// If end_date exists and is before the new start date, set it to next day
let newEndDate = t.end_date;
if (newEndDate) {
const endDate = DateTime.fromISO(newEndDate);
const startDate = DateTime.fromISO(isoDate);
if (endDate < startDate) {
// Check if original end_date has a time component (not all-day)
const hasTime = !newEndDate.includes('T00:00:00');
if (hasTime && t.end_timezone) {
// Set to 9am in the end timezone
newEndDate = DateTime.fromISO(nextDayISO, { zone: t.end_timezone })
.set({ hour: 9, minute: 0, second: 0 })
.toISO();
} else {
// All-day event, keep at UTC 0
newEndDate = `${nextDayISO}T00:00:00`;
}
}
}
return { ...t, date: isoDate, end_date: newEndDate };
}
return t;
});
}
} else if (objectType === 'lodging') {
if (collection.lodging) {
collection.lodging = collection.lodging.map((l) =>
l.id === objectId ? { ...l, check_in: isoDate } : l
);
collection.lodging = collection.lodging.map((l) => {
if (l.id === objectId) {
// If check_out exists and is before the new check_in, set it to next day
let newCheckOut = l.check_out;
if (newCheckOut) {
const checkOut = DateTime.fromISO(newCheckOut);
const checkIn = DateTime.fromISO(isoDate);
if (checkOut < checkIn) {
// Check if original check_out has a time component (not all-day)
const hasTime = !newCheckOut.includes('T00:00:00');
if (hasTime && l.timezone) {
// Set to 9am in the lodging timezone
newCheckOut = DateTime.fromISO(nextDayISO, { zone: l.timezone })
.set({ hour: 9, minute: 0, second: 0 })
.toISO();
} else {
// All-day event, keep at UTC 0
newCheckOut = `${nextDayISO}T00:00:00`;
}
}
}
return { ...l, check_in: isoDate, check_out: newCheckOut };
}
return l;
});
}
} else if (objectType === 'note') {
if (collection.notes) {

View File

@@ -745,10 +745,6 @@
<!-- Action Buttons -->
<div class="flex gap-3 justify-end pt-4">
<button class="btn btn-neutral-200 gap-2" on:click={handleBack}>
<ArrowLeftIcon class="w-5 h-5" />
{$t('adventures.back')}
</button>
<button
class="btn btn-primary gap-2"
disabled={!lodging.name || !lodging.type || isReverseGeocoding}

View File

@@ -5,7 +5,7 @@
// Icons
import ArrowLeftIcon from '~icons/mdi/arrow-left';
import CloseIcon from '~icons/mdi/close';
import CheckIcon from '~icons/mdi/check';
import { addToast } from '$lib/toasts';
import ImageManagement from '../ImageManagement.svelte';
@@ -16,8 +16,8 @@
export let attachments: Attachment[] = [];
export let itemName: string = '';
export let itemId: string = '';
export let measurementSystem: 'metric' | 'imperial' = 'metric';
export let user: User | null = null;
// export let measurementSystem: 'metric' | 'imperial' = 'metric';
// export let user: User | null = null;
// Component state
let immichIntegration: boolean = false;
@@ -95,8 +95,8 @@
</button>
<button class="btn btn-primary gap-2" on:click={handleClose}>
<CloseIcon class="w-5 h-5" />
{$t('about.close')}
<CheckIcon class="w-5 h-5" />
{$t('adventures.done')}
</button>
</div>
</div>

View File

@@ -258,14 +258,12 @@
bind:images={lodging.images}
bind:attachments={lodging.attachments}
itemName={lodging.name}
{user}
on:back={() => {
steps[1].selected = false;
steps[0].selected = true;
}}
on:close={() => close()}
itemId={lodging.id}
measurementSystem={user?.measurement_system || 'metric'}
/>
{/if}
</div>

View File

@@ -963,6 +963,8 @@
"load_more": "Load More",
"immich_error": "Error updating Immich integration",
"immich_disabled": "Immich integration disabled successfully!",
"immich_updated": "Immich integration updated successfully!",
"immich_enabled": "Immich integration enabled successfully!",
"disable": "Disable",
"server_url": "Immich Server URL",
"api_note": "Note: this must be the URL to the Immich API server so it likely ends with /api unless you have a custom config.",