From 0e51b37a89edd5266c7bc6d270741aca8f474a08 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Tue, 30 Dec 2025 11:57:48 -0500 Subject: [PATCH] Enhance collection management with modal updates and item handling --- .../collections/CollectionAllItems.svelte | 481 ++++++++++-------- .../components/locations/LocationModal.svelte | 15 + .../components/lodging/LodgingModal.svelte | 15 + .../transportation/TransportationModal.svelte | 15 + frontend/src/lib/config.ts | 2 +- .../src/routes/collections/[id]/+page.svelte | 266 +++++++++- 6 files changed, 574 insertions(+), 220 deletions(-) diff --git a/frontend/src/lib/components/collections/CollectionAllItems.svelte b/frontend/src/lib/components/collections/CollectionAllItems.svelte index 3e741dc5..5e16db79 100644 --- a/frontend/src/lib/components/collections/CollectionAllItems.svelte +++ b/frontend/src/lib/components/collections/CollectionAllItems.svelte @@ -1,5 +1,6 @@ -{#if collection.locations && collection.locations.length > 0} -
-
-
-

- 📍 Locations ({sortedLocations.length}/{collection.locations.length}) -

+ +
+ {#if collection.locations && collection.locations.length > 0} +
+
+
+

+ 📍 Locations ({sortedLocations.length}/{collection.locations.length}) +

- {#if isFolderView} -
- -
- + {#if isFolderView} +
+ +
+ +
+ + +
+ {/if} +
- - +
+ {#each sortedLocations as location} + handleItemDelete('locations', e.detail)} + on:edit={(e) => handleItemEdit('locations', e.detail)} + /> + {/each} +
+ + {#if sortedLocations.length === 0} +
+

No locations match your search

{/if}
- -
- {#each sortedLocations as location} - - {/each} -
- - {#if sortedLocations.length === 0} -
-

No locations match your search

-
- {/if} - - - {#if collection.transportations && collection.transportations.length > 0} -
-
-
-

- ✈️ Transportation ({filteredTransportations.length}/{collection.transportations - .length}) -

- - {#if isFolderView} -
- - -
- {/if} -
- -
- {#each filteredTransportations as transport} - - {/each} -
- - {#if filteredTransportations.length === 0} -
-

No transportation matches your search

-
- {/if} -
-
- {/if} - - - {#if collection.lodging && collection.lodging.length > 0} -
-
-
-

- 🏨 Lodging ({filteredLodging.length}/{collection.lodging.length}) -

- - {#if isFolderView} -
- - -
- {/if} -
- -
- {#each filteredLodging as lodging} - - {/each} -
- - {#if filteredLodging.length === 0} -
-

No lodging matches your search

-
- {/if} -
-
- {/if} - - - {#if collection.notes && collection.notes.length > 0} -
-
-
-

- 📝 Notes ({filteredNotes.length}/{collection.notes.length}) -

- - {#if isFolderView} -
- - -
- {/if} -
- -
- {#each filteredNotes as note} - - {/each} -
- - {#if filteredNotes.length === 0} -
-

No notes match your search

-
- {/if} -
-
- {/if} - - - {#if collection.checklists && collection.checklists.length > 0} -
-
-
-

- - Checklists ({filteredChecklists.length}/{collection.checklists.length}) -

- - {#if isFolderView} -
- - -
- {/if} -
- -
- {#each filteredChecklists as checklist} - - {/each} -
- - {#if filteredChecklists.length === 0} -
-

No checklists match your search

-
- {/if} -
-
- {/if}
-
-{/if} + {/if} + + + {#if collection.transportations && collection.transportations.length > 0} +
+
+
+

+ ✈️ Transportation ({filteredTransportations.length}/{collection.transportations.length}) +

+ + {#if isFolderView} +
+ + +
+ {/if} +
+ +
+ {#each filteredTransportations as transport} + handleItemDelete('transportations', e.detail)} + on:edit={(e) => handleItemEdit('transportations', e.detail)} + /> + {/each} +
+ + {#if filteredTransportations.length === 0} +
+

No transportation matches your search

+
+ {/if} +
+
+ {/if} + + + {#if collection.lodging && collection.lodging.length > 0} +
+
+
+

+ 🏨 Lodging ({filteredLodging.length}/{collection.lodging.length}) +

+ + {#if isFolderView} +
+ + +
+ {/if} +
+ +
+ {#each filteredLodging as lodging} + handleItemDelete('lodging', e.detail)} + on:edit={(e) => handleItemEdit('lodging', e.detail)} + /> + {/each} +
+ + {#if filteredLodging.length === 0} +
+

No lodging matches your search

+
+ {/if} +
+
+ {/if} + + + {#if collection.notes && collection.notes.length > 0} +
+
+
+

+ 📝 Notes ({filteredNotes.length}/{collection.notes.length}) +

+ + {#if isFolderView} +
+ + +
+ {/if} +
+ +
+ {#each filteredNotes as note} + handleItemDelete('notes', e.detail)} + on:edit={(e) => handleItemEdit('notes', e.detail)} + /> + {/each} +
+ + {#if filteredNotes.length === 0} +
+

No notes match your search

+
+ {/if} +
+
+ {/if} + + + {#if collection.checklists && collection.checklists.length > 0} +
+
+
+

+ + Checklists ({filteredChecklists.length}/{collection.checklists.length}) +

+ + {#if isFolderView} +
+ + +
+ {/if} +
+ +
+ {#each filteredChecklists as checklist} + handleItemDelete('checklists', e.detail)} + on:edit={(e) => handleItemEdit('checklists', e.detail)} + /> + {/each} +
+ + {#if filteredChecklists.length === 0} +
+

No checklists match your search

+
+ {/if} +
+
+ {/if} +
diff --git a/frontend/src/lib/components/locations/LocationModal.svelte b/frontend/src/lib/components/locations/LocationModal.svelte index 08a463e5..db389212 100644 --- a/frontend/src/lib/components/locations/LocationModal.svelte +++ b/frontend/src/lib/components/locations/LocationModal.svelte @@ -20,6 +20,9 @@ let modal: HTMLDialogElement; + // Whether a save/create occurred during this modal session + let didSave = false; + let steps = [ { name: $t('adventures.quick_start'), @@ -116,6 +119,15 @@ }); function close() { + // If a save occurred, notify the parent with appropriate event + if (didSave) { + if (locationToEdit) { + dispatch('save', location); + } else { + dispatch('create', location); + } + } + dispatch('close'); } @@ -289,6 +301,9 @@ location.user = e.detail.user; location.id = e.detail.id; + // Mark that a save occurred so close() will notify parent + didSave = true; + steps[1].selected = false; steps[2].selected = true; }} diff --git a/frontend/src/lib/components/lodging/LodgingModal.svelte b/frontend/src/lib/components/lodging/LodgingModal.svelte index 7b17a857..f22ec914 100644 --- a/frontend/src/lib/components/lodging/LodgingModal.svelte +++ b/frontend/src/lib/components/lodging/LodgingModal.svelte @@ -18,6 +18,9 @@ let modal: HTMLDialogElement; + // Whether a save/create occurred during this modal session + let didSave = false; + let steps = [ { name: $t('adventures.details'), @@ -116,6 +119,15 @@ }); function close() { + // If a save occurred, notify the parent with appropriate event + if (didSave) { + if (lodgingToEdit) { + dispatch('save', lodging); + } else { + dispatch('create', lodging); + } + } + dispatch('close'); } @@ -235,6 +247,9 @@ // Update the entire lodging object with all saved data lodging = { ...lodging, ...e.detail }; + // Mark that a save occurred so close() will notify parent + didSave = true; + // Only allow moving to Media once we have a persisted id. if (!lodging?.id) { addToast('error', $t('adventures.lodging_save_error')); diff --git a/frontend/src/lib/components/transportation/TransportationModal.svelte b/frontend/src/lib/components/transportation/TransportationModal.svelte index 9880f973..fc0a9a89 100644 --- a/frontend/src/lib/components/transportation/TransportationModal.svelte +++ b/frontend/src/lib/components/transportation/TransportationModal.svelte @@ -18,6 +18,9 @@ let modal: HTMLDialogElement; + // Whether a save/create occurred during this modal session + let didSave = false; + let steps = [ { name: $t('adventures.details'), @@ -128,6 +131,15 @@ }); function close() { + // If a save occurred, notify the parent with appropriate event + if (didSave) { + if (transportationToEdit) { + dispatch('save', transportation); + } else { + dispatch('create', transportation); + } + } + dispatch('close'); } @@ -249,6 +261,9 @@ // Update the entire transportation object with all saved data transportation = { ...transportation, ...e.detail }; + // Mark that a save occurred so close() will notify parent + didSave = true; + // Only allow moving to Media once we have a persisted id. if (!transportation?.id) { addToast('error', $t('adventures.lodging_save_error')); diff --git a/frontend/src/lib/config.ts b/frontend/src/lib/config.ts index afc0b013..4d0e1781 100644 --- a/frontend/src/lib/config.ts +++ b/frontend/src/lib/config.ts @@ -1,4 +1,4 @@ -export let appVersion = 'v0.12.0-pre-dev-122825'; +export let appVersion = 'v0.12.0-pre-dev-123025'; export let versionChangelog = 'https://github.com/seanmorley15/AdventureLog/releases/tag/v0.11.0'; export let appTitle = 'AdventureLog'; export let copyrightYear = '2023-2025'; diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte index bf4116c8..82bf000e 100644 --- a/frontend/src/routes/collections/[id]/+page.svelte +++ b/frontend/src/routes/collections/[id]/+page.svelte @@ -25,6 +25,11 @@ import Lightbulb from '~icons/mdi/lightbulb'; import Plus from '~icons/mdi/plus'; import { addToast } from '$lib/toasts'; + import NoteModal from '$lib/components/NoteModal.svelte'; + import ChecklistModal from '$lib/components/ChecklistModal.svelte'; + import LodgingModal from '$lib/components/lodging/LodgingModal.svelte'; + import TransportationModal from '$lib/components/transportation/TransportationModal.svelte'; + import LocationModal from '$lib/components/locations/LocationModal.svelte'; const renderMarkdown = (markdown: string) => { return marked(markdown) as string; @@ -36,12 +41,42 @@ let collection: Collection = (data.props as any).collection || (data.props as any).adventure; let currentSlide = 0; let notFound: boolean = false; - let isEditModalOpen: boolean = false; + let isLocationModalOpen: boolean = false; + let isLodgingModalOpen: boolean = false; + let isTransportationModalOpen: boolean = false; + let isChecklistModalOpen: boolean = false; + let isNoteModalOpen: boolean = false; + // Edit placeholders used when creating new items from FAB dropdown + let adventureToEdit: any = null; + let transportationToEdit: any = null; + let noteToEdit: any = null; + let checklistToEdit: any = null; + let lodgingToEdit: any = null; let heroImages: ContentImage[] = []; let modalInitialIndex: number = 0; let isImageModalOpen: boolean = false; let isLocationLinkModalOpen: boolean = false; + // Shared helpers for keeping collection sub-items in sync after modal actions + type CollectionArrayKey = 'locations' | 'transportations' | 'lodging' | 'notes' | 'checklists'; + + function ensureCollectionArray(key: CollectionArrayKey) { + if (!collection) return [] as any[]; + if (!(collection as any)[key]) { + (collection as any)[key] = []; + } + return (collection as any)[key] as any[]; + } + + function upsertCollectionItem(key: CollectionArrayKey, item: any) { + if (!item || item.id === undefined || item.id === null) return; + const items = ensureCollectionArray(key); + const exists = items.some((entry: any) => String(entry.id) === String(item.id)); + (collection as any)[key] = exists + ? items.map((entry: any) => (String(entry.id) === String(item.id) ? item : entry)) + : [...items, item]; + } + // View state from URL params type ViewType = 'all' | 'itinerary' | 'map' | 'recommendations'; let currentView: ViewType = 'itinerary'; @@ -154,6 +189,35 @@ isLocationLinkModalOpen = false; } + function handleOpenEdit(event: CustomEvent<{ type: CollectionArrayKey; item: any }>) { + const { type, item } = event.detail; + + switch (type) { + case 'locations': + adventureToEdit = item; + isLocationModalOpen = true; + break; + case 'transportations': + transportationToEdit = item; + isTransportationModalOpen = true; + break; + case 'lodging': + lodgingToEdit = item; + isLodgingModalOpen = true; + break; + case 'notes': + noteToEdit = item; + isNoteModalOpen = true; + break; + case 'checklists': + checklistToEdit = item; + isChecklistModalOpen = true; + break; + default: + break; + } + } + async function handleLocationAdded(event: CustomEvent) { // Link the location to this collection const location = event.detail; @@ -238,6 +302,115 @@ /> {/if} +{#if isNoteModalOpen} + { + noteToEdit = null; + isNoteModalOpen = false; + }} + note={noteToEdit} + {collection} + on:save={(e) => { + upsertCollectionItem('notes', e.detail); + noteToEdit = null; + isNoteModalOpen = false; + }} + on:create={(e) => { + upsertCollectionItem('notes', e.detail); + noteToEdit = null; + isNoteModalOpen = false; + }} + /> +{/if} + +{#if isLocationModalOpen} + { + adventureToEdit = null; + isLocationModalOpen = false; + }} + user={data.user} + {collection} + locationToEdit={adventureToEdit} + on:save={(e) => { + upsertCollectionItem('locations', e.detail); + adventureToEdit = null; + isLocationModalOpen = false; + }} + on:create={(e) => { + upsertCollectionItem('locations', e.detail); + adventureToEdit = null; + isLocationModalOpen = false; + }} + /> +{/if} + +{#if isTransportationModalOpen} + { + transportationToEdit = null; + isTransportationModalOpen = false; + }} + user={data.user} + {collection} + {transportationToEdit} + on:save={(e) => { + upsertCollectionItem('transportations', e.detail); + transportationToEdit = null; + isTransportationModalOpen = false; + }} + on:create={(e) => { + upsertCollectionItem('transportations', e.detail); + transportationToEdit = null; + isTransportationModalOpen = false; + }} + /> +{/if} + +{#if isChecklistModalOpen} + { + checklistToEdit = null; + isChecklistModalOpen = false; + }} + {collection} + user={data.user} + checklist={checklistToEdit} + on:save={(e) => { + upsertCollectionItem('checklists', e.detail); + checklistToEdit = null; + isChecklistModalOpen = false; + }} + on:create={(e) => { + upsertCollectionItem('checklists', e.detail); + checklistToEdit = null; + isChecklistModalOpen = false; + }} + /> +{/if} + +{#if isLodgingModalOpen} + { + lodgingToEdit = null; + isLodgingModalOpen = false; + }} + {collection} + user={data.user} + {lodgingToEdit} + on:save={(e) => { + upsertCollectionItem('lodging', e.detail); + lodgingToEdit = null; + isLodgingModalOpen = false; + }} + on:create={(e) => { + upsertCollectionItem('lodging', e.detail); + lodgingToEdit = null; + isLodgingModalOpen = false; + }} + /> +{/if} + {#if !collection && !notFound}
@@ -434,12 +607,12 @@ {/if} - {#if currentView === 'all' && collection.locations && collection.locations.length > 0} + {#if currentView === 'all'} {/if} @@ -678,15 +851,82 @@ {/if} -{#if collection && canModifyCollection} -
- +{#if collection && canModifyCollection && !collection.is_archived} +
+
+ +
{/if}