diff --git a/apps/mana/apps/web/src/lib/modules/cards/ListView.svelte b/apps/mana/apps/web/src/lib/modules/cards/ListView.svelte index 33549ba30..a5c5cb3e0 100644 --- a/apps/mana/apps/web/src/lib/modules/cards/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/cards/ListView.svelte @@ -3,41 +3,28 @@ Deck list with card counts and study info. --> -
-
-

{decks.length} Decks

-

{dueForReview()} fällig

-
+ d.id} emptyTitle="Keine Decks"> + {#snippet header()} + {decks.length} Decks + {dueForReview} fällig + {/snippet} -
- {#each decks as deck (deck.id)} - - {/each} - - {#if decks.length === 0} -

Keine Decks

- {/if} -
-
+ {#snippet item(deck)} + + {/snippet} + diff --git a/apps/mana/apps/web/src/lib/modules/cards/components/DeckCard.svelte b/apps/mana/apps/web/src/lib/modules/cards/components/DeckCard.svelte index 2c7f6c875..1bbf8c538 100644 --- a/apps/mana/apps/web/src/lib/modules/cards/components/DeckCard.svelte +++ b/apps/mana/apps/web/src/lib/modules/cards/components/DeckCard.svelte @@ -1,4 +1,5 @@ - + diff --git a/apps/mana/apps/web/src/lib/modules/chat/ListView.svelte b/apps/mana/apps/web/src/lib/modules/chat/ListView.svelte index 20c4d532a..747a409b5 100644 --- a/apps/mana/apps/web/src/lib/modules/chat/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/chat/ListView.svelte @@ -3,40 +3,31 @@ Recent conversations list. --> -
-

{conversations.length} Unterhaltungen

+ c.id} emptyTitle="Keine Unterhaltungen"> + {#snippet header()} + {conversations.length} Unterhaltungen + {/snippet} -
- {#each sorted as conv (conv.id)} - {@const lastMsg = lastMessages.get(conv.id)} -
-
-

- {conv.title || 'Neue Unterhaltung'} -

- {#if conv.isPinned} - 📌 - {/if} -
- {#if lastMsg} -

- {lastMsg.sender === 'user' ? 'Du: ' : ''}{truncate(lastMsg.messageText)} -

+ {#snippet item(conv)} + {@const lastMsg = lastMessages.get(conv.id)} +
+
+

+ {conv.title || 'Neue Unterhaltung'} +

+ {#if conv.isPinned} + 📌 {/if}
- {/each} - - {#if sorted.length === 0} -

Keine Unterhaltungen

- {/if} -
-
+ {#if lastMsg} +

+ {lastMsg.sender === 'user' ? 'Du: ' : ''}{truncate(lastMsg.messageText)} +

+ {/if} +
+ {/snippet} +
diff --git a/apps/mana/apps/web/src/lib/modules/citycorners/ListView.svelte b/apps/mana/apps/web/src/lib/modules/citycorners/ListView.svelte index c4a43f9af..49786e870 100644 --- a/apps/mana/apps/web/src/lib/modules/citycorners/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/citycorners/ListView.svelte @@ -3,40 +3,27 @@ Locations list grouped by category with favorites. --> -
-
+ l.id} emptyTitle="Keine Orte"> + {#snippet header()} {locations.length} Orte {favorites.length} Favoriten -
+ {/snippet} -
- {#each locations as location (location.id)} - - {/each} - - {#if locations.length === 0} -

Keine Orte

- {/if} -
-
+

+ {categoryLabels[location.category] ?? location.category} +

+
+ + {/snippet} + diff --git a/apps/mana/apps/web/src/lib/modules/context/ListView.svelte b/apps/mana/apps/web/src/lib/modules/context/ListView.svelte index a9ba4ab25..f88acb959 100644 --- a/apps/mana/apps/web/src/lib/modules/context/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/context/ListView.svelte @@ -3,36 +3,27 @@ Spaces and recent documents. --> -
-
+ d.id} emptyTitle="Keine Dokumente"> + {#snippet header()} {spaces.length} Spaces {documents.length} Dokumente -
+ {/snippet} -
- - {#if spaces.filter((s) => s.pinned).length > 0} + {#snippet listHeader()} + {#if pinnedSpaces.length > 0}

Angepinnte Spaces

- {#each spaces.filter((s) => s.pinned) as space (space.id)} + {#each pinnedSpaces as space (space.id)}

{space.name}

{#if space.description} @@ -64,25 +54,20 @@
{/each} {/if} - -

Zuletzt bearbeitet

- {#each recentDocs as doc (doc.id)} -
- {@html typeIcons[doc.type] ?? '📄'} -
-

{doc.title || 'Unbenannt'}

-
- {#if doc.pinned} - 📌 - {/if} -
- {/each} + {/snippet} - {#if recentDocs.length === 0} -

Keine Dokumente

- {/if} -
-
+ {#snippet item(doc)} +
+ {@html typeIcons[doc.type] ?? '📄'} +
+

{doc.title || 'Unbenannt'}

+
+ {#if doc.pinned} + 📌 + {/if} +
+ {/snippet} + diff --git a/apps/mana/apps/web/src/lib/modules/inventar/ListView.svelte b/apps/mana/apps/web/src/lib/modules/inventar/ListView.svelte index 8b9321ba8..5822d9024 100644 --- a/apps/mana/apps/web/src/lib/modules/inventar/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/inventar/ListView.svelte @@ -3,84 +3,62 @@ Collections and items overview. --> -
-
+ c.id} emptyTitle="Keine Sammlungen"> + {#snippet header()} {items.length} Gegenstände - {#if totalValue() > 0} - ~{totalValue().toFixed(0)} EUR + {#if totalValue > 0} + ~{totalValue.toFixed(0)} EUR {/if} -
+ {/snippet} -
- {#each collections as collection (collection.id)} - - {/each} - - {#if collections.length === 0} -

Keine Sammlungen

- {/if} -
-
+

{collection.name}

+ {itemsInCollection(collection.id)} + + {#if collection.description} +

{collection.description}

+ {/if} + + {/snippet} + diff --git a/apps/mana/apps/web/src/lib/modules/memoro/ListView.svelte b/apps/mana/apps/web/src/lib/modules/memoro/ListView.svelte index 77e4007e1..608b34f9a 100644 --- a/apps/mana/apps/web/src/lib/modules/memoro/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/memoro/ListView.svelte @@ -3,16 +3,28 @@ Recent memos with transcription status. --> -
- + m.id} emptyTitle="Keine Memos"> + {#snippet toolbar()} + + {/snippet} -
+ {#snippet header()} {memos.length} Memos {pinned.length} angepinnt -
+ {/snippet} -
- {#each sorted as memo (memo.id)} - - {/each} - - {#if sorted.length === 0} -

Keine Memos

- {/if} -
-
+ + {memo.processingStatus === 'completed' + ? formatDuration(memo.audioDurationMs) + : memo.processingStatus} + + + + {/snippet} + diff --git a/apps/mana/apps/web/src/lib/modules/moodlit/ListView.svelte b/apps/mana/apps/web/src/lib/modules/moodlit/ListView.svelte index 85e865cb8..46db9c1e6 100644 --- a/apps/mana/apps/web/src/lib/modules/moodlit/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/moodlit/ListView.svelte @@ -3,28 +3,22 @@ Ambient mood selector with color preview. --> -
- - {#if activeMood} -
-

{activeMood.name}

-
- {:else} -
-

Kein Mood aktiv

-
- {/if} - - -
-
- {#each moods as mood (mood.id)} - - {/each} -
- - {#if moods.length === 0} -

Keine Moods

+ m.id} + emptyTitle="Keine Moods" + class="gap-4" + listClass="grid grid-cols-2 sm:grid-cols-3 gap-2 content-start" +> + {#snippet toolbar()} + + {#if activeMood} +
+

{activeMood.name}

+
+ {:else} +
+

Kein Mood aktiv

+
{/if} -
+ {/snippet} - (ctxMenu = { ...ctxMenu, visible: false, mood: null })} - /> -
+ {#snippet item(mood)} + + {/snippet} + + + (ctxMenu = { ...ctxMenu, visible: false, mood: null })} +/> diff --git a/apps/mana/apps/web/src/lib/modules/music/ListView.svelte b/apps/mana/apps/web/src/lib/modules/music/ListView.svelte index cf44d5533..67b3e166f 100644 --- a/apps/mana/apps/web/src/lib/modules/music/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/music/ListView.svelte @@ -3,39 +3,28 @@ Song library with recent plays and playlists. --> -
-
+ s.id} emptyTitle="Noch nichts gehört"> + {#snippet header()} {songs.length} Songs {playlists.length} Playlists {favorites.length} Favoriten -
+ {/snippet} -
+ {#snippet listHeader()}

Zuletzt gehört

- {#each recentlyPlayed as song (song.id)} - - {/each} + {/snippet} - {#if recentlyPlayed.length === 0} -

Noch nichts gehört

- {/if} -
-
+ {#snippet item(song)} + + {/snippet} + diff --git a/apps/mana/apps/web/src/lib/modules/nutriphi/ListView.svelte b/apps/mana/apps/web/src/lib/modules/nutriphi/ListView.svelte index 93f863c7e..89bbcaf20 100644 --- a/apps/mana/apps/web/src/lib/modules/nutriphi/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/nutriphi/ListView.svelte @@ -3,39 +3,27 @@ Today's nutrition progress with meal log. --> -
- -
-

{Math.round(totalCalories)}

-

+ m.id} emptyTitle="Noch keine Mahlzeiten heute"> + {#snippet toolbar()} + +

+

{Math.round(totalCalories)}

+

+ {#if goal} + von {goal.dailyCalories} kcal + {:else} + kcal heute + {/if} +

{#if goal} - von {goal.dailyCalories} kcal - {:else} - kcal heute - {/if} -

- {#if goal} -
-
-
- {/if} -
- - -
- {Math.round(totalProtein)}g Protein - {todayMeals.length} Mahlzeiten -
- - -
- {#each todayMeals as meal (meal.id)} -
-
- {mealTypeLabels[meal.mealType] ?? meal.mealType} - {#if meal.nutrition} - {Math.round(meal.nutrition.calories)} kcal - {/if} +
+
-

{meal.description}

-
- {/each} + {/if} +
+ {/snippet} - {#if todayMeals.length === 0} -

Noch keine Mahlzeiten heute

- {/if} -
-
+ {#snippet header()} + {Math.round(totalProtein)}g Protein · {todayMeals.length} Mahlzeiten + {/snippet} + + {#snippet item(meal)} +
+
+ {mealTypeLabels[meal.mealType] ?? meal.mealType} + {#if meal.nutrition} + {Math.round(meal.nutrition.calories)} kcal + {/if} +
+

{meal.description}

+
+ {/snippet} + diff --git a/apps/mana/apps/web/src/lib/modules/picture/ListView.svelte b/apps/mana/apps/web/src/lib/modules/picture/ListView.svelte index 3a1aec400..4282d3235 100644 --- a/apps/mana/apps/web/src/lib/modules/picture/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/picture/ListView.svelte @@ -3,23 +3,19 @@ Recent images grid with favorites. --> -
-
-

{images.length} Bilder

-

{favoriteCount} Favoriten

-
+ i.id} + emptyTitle="Keine Bilder" + listClass="grid grid-cols-2 sm:grid-cols-3 gap-1.5 content-start" +> + {#snippet header()} + {images.length} Bilder + {favoriteCount} Favoriten + {/snippet} -
-
- {#each sorted as image (image.id)} -
- {#if image.publicUrl} - {image.prompt} - {:else} -
- {image.format ?? 'img'} -
- {/if} - {#if image.isFavorite} - - {/if} + {#snippet item(image)} +
+ {#if image.publicUrl} + {image.prompt} + {:else} +
+ {image.format ?? 'img'}
- {/each} + {/if} + {#if image.isFavorite} + + {/if}
- - {#if sorted.length === 0} -

Keine Bilder

- {/if} -
-
+ {/snippet} + diff --git a/apps/mana/apps/web/src/lib/modules/planta/ListView.svelte b/apps/mana/apps/web/src/lib/modules/planta/ListView.svelte index 9ff3720bf..3b46bd53f 100644 --- a/apps/mana/apps/web/src/lib/modules/planta/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/planta/ListView.svelte @@ -3,39 +3,26 @@ Plant overview with watering schedule. --> -
-
+ p.id} emptyTitle="Keine Pflanzen"> + {#snippet header()} {plants.length} Pflanzen {#if dueForWatering.length > 0} {dueForWatering.length} giessen @@ -67,45 +54,39 @@ {#if needsAttention.length > 0} {needsAttention.length} brauchen Pflege {/if} -
+ {/snippet} -
- {#each plants as plant (plant.id)} - {@const schedule = getSchedule(plant.id)} - {@const waterDue = needsWater(schedule)} - - {/each} - - {#if plants.length === 0} -

Keine Pflanzen

- {/if} -
-
+
+ {#if schedule} +

+ Alle {schedule.frequencyDays} Tage giessen +

+ {/if} + + {/snippet} +
diff --git a/apps/mana/apps/web/src/lib/modules/presi/ListView.svelte b/apps/mana/apps/web/src/lib/modules/presi/ListView.svelte index d4f801156..0668232d5 100644 --- a/apps/mana/apps/web/src/lib/modules/presi/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/presi/ListView.svelte @@ -3,74 +3,57 @@ Presentation decks list with slide count. --> -
-

{decks.length} Präsentationen

+ d.id} emptyTitle="Keine Präsentationen"> + {#snippet header()} + {decks.length} Präsentationen + {/snippet} -
- {#each decks as deck (deck.id)} - - {/each} - - {#if decks.length === 0} -

Keine Präsentationen

- {/if} -
-
+
+ {#if deck.description} +

{deck.description}

+ {/if} + + {/snippet} + diff --git a/apps/mana/apps/web/src/lib/modules/questions/ListView.svelte b/apps/mana/apps/web/src/lib/modules/questions/ListView.svelte index 8a77c5cb2..22caf2127 100644 --- a/apps/mana/apps/web/src/lib/modules/questions/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/questions/ListView.svelte @@ -3,39 +3,28 @@ Research questions list with status badges. --> -
-
+ q.id} emptyTitle="Keine offenen Fragen"> + {#snippet header()} {questions.length} Fragen {collections.length} Sammlungen -
+ {/snippet} -
- {#each sorted as question (question.id)} - - {/each} - - {#if sorted.length === 0} -

Keine offenen Fragen

- {/if} -
-
+ {/if} + + {/snippet} + diff --git a/apps/mana/apps/web/src/lib/modules/skilltree/ListView.svelte b/apps/mana/apps/web/src/lib/modules/skilltree/ListView.svelte index dbba7c137..d144d4b4b 100644 --- a/apps/mana/apps/web/src/lib/modules/skilltree/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/skilltree/ListView.svelte @@ -3,88 +3,61 @@ Skills overview with XP and levels. --> -
- -
+ s.id} emptyTitle="Keine Skills angelegt"> + {#snippet header()} {totalXp} XP Level {highestLevel} {skills.length} Skills -
+ {/snippet} - -
- {#each skills as skill (skill.id)} - {@const branch = BRANCH_INFO[skill.branch as SkillBranch]} - {@const progress = xpProgress(skill.currentXp, skill.level)} - - {/each} - - {#if skills.length === 0} -

Keine Skills angelegt

- {/if} -
-
+ {skill.currentXp} XP +
+

+ {branch?.name ?? skill.branch} — {LEVEL_NAMES[skill.level] ?? 'Unbekannt'} +

+ + {/snippet} + diff --git a/apps/mana/apps/web/src/lib/modules/storage/ListView.svelte b/apps/mana/apps/web/src/lib/modules/storage/ListView.svelte index 1aa572dfa..0ba666df7 100644 --- a/apps/mana/apps/web/src/lib/modules/storage/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/storage/ListView.svelte @@ -3,39 +3,30 @@ File browser with recent files and folders. --> -
-
+ f.id} emptyTitle="Keine Dateien"> + {#snippet header()} {folders.length} Ordner {files.length} Dateien -
+ {/snippet} -
- - {#if folders.filter((f) => !f.parentFolderId).length > 0} + {#snippet listHeader()} + {#if rootFolders.length > 0}

Ordner

- {#each folders.filter((f) => !f.parentFolderId) as folder (folder.id)} + {#each rootFolders as folder (folder.id)}
@@ -75,27 +65,22 @@
{/each} {/if} - -

Zuletzt

- {#each recentFiles as file (file.id)} - - {/each} + {/snippet} - {#if recentFiles.length === 0} -

Keine Dateien

- {/if} -
-
+ {#snippet item(file)} + + {/snippet} + diff --git a/apps/mana/apps/web/src/lib/modules/times/ListView.svelte b/apps/mana/apps/web/src/lib/modules/times/ListView.svelte index fe12fc71c..64e1c54a0 100644 --- a/apps/mana/apps/web/src/lib/modules/times/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/times/ListView.svelte @@ -3,18 +3,18 @@ Inline timer with start/stop + today's time entries. --> -
- -
- - handleDescriptionInput((e.target as HTMLInputElement).value)} - placeholder="Was trackst du?" - class="min-w-0 flex-1 rounded-md border border-white/10 bg-white/5 px-2.5 py-1.5 text-xs text-white/90 placeholder:text-white/30 focus:border-white/20 focus:outline-none" - /> - {#if timerStore.isRunning} -
-
- - {formatDuration(timerStore.elapsedSeconds)} - -
- {/if} -
- - -
- Heute: {todayEntries.length} Eintr{todayEntries.length === 1 ? 'ag' : 'age'} - {fmtCompact(totalToday)} -
- - -
- {#each todayEntries as entry (entry.id)} + e.id} emptyTitle="Noch keine Zeiteinträge heute"> + {#snippet toolbar()} + +
- {/each} + handleDescriptionInput((e.target as HTMLInputElement).value)} + placeholder="Was trackst du?" + class="min-w-0 flex-1 rounded-md border border-white/10 bg-white/5 px-2.5 py-1.5 text-xs text-white/90 placeholder:text-white/30 focus:border-white/20 focus:outline-none" + /> + {#if timerStore.isRunning} +
+
+ + {formatDuration(timerStore.elapsedSeconds)} + +
+ {/if} +
+ {/snippet} - {#if todayEntries.length === 0 && !timerStore.isRunning} -

Noch keine Zeiteinträge heute

- {/if} -
-
+ {#snippet header()} + Heute: {todayEntries.length} Eintr{todayEntries.length === 1 ? 'ag' : 'age'} + {fmtCompact(totalToday)} + {/snippet} + + {#snippet item(entry)} + + {/snippet} + diff --git a/apps/mana/apps/web/src/lib/modules/uload/ListView.svelte b/apps/mana/apps/web/src/lib/modules/uload/ListView.svelte index 3aacdcf34..eb98d141e 100644 --- a/apps/mana/apps/web/src/lib/modules/uload/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/uload/ListView.svelte @@ -3,39 +3,28 @@ Short links list with click counts. --> -
-
+ l.id} emptyTitle="Keine Links"> + {#snippet header()} {links.length} Links {totalClicks} Klicks {folders.length} Ordner -
+ {/snippet} -
- {#each sorted as link (link.id)} - - {/each} - - {#if sorted.length === 0} -

Keine Links

- {/if} -
-
+ {#snippet item(link)} + + {/snippet} + diff --git a/packages/shared-ui/src/index.ts b/packages/shared-ui/src/index.ts index 0187ec2dd..e6624c60f 100644 --- a/packages/shared-ui/src/index.ts +++ b/packages/shared-ui/src/index.ts @@ -67,7 +67,7 @@ export { ModalFooter, DataCard, PageHeader, KeyboardShortcutsPanel } from './mol export { ConfirmationPopover } from './molecules'; // Organisms -export { Modal, ConfirmationModal, FormModal, AppSlider } from './organisms'; +export { Modal, ConfirmationModal, FormModal, AppSlider, BaseListView } from './organisms'; export type { AppItem } from './organisms'; // Network Graph diff --git a/packages/shared-ui/src/organisms/BaseListView.svelte b/packages/shared-ui/src/organisms/BaseListView.svelte new file mode 100644 index 000000000..19e27f3b1 --- /dev/null +++ b/packages/shared-ui/src/organisms/BaseListView.svelte @@ -0,0 +1,101 @@ + + +
+ {#if toolbar} + {@render toolbar()} + {/if} + + {#if header} +
+ {@render header()} +
+ {/if} + +
+ {#if listHeader} + {@render listHeader()} + {/if} + + {#each items as entry, i (getKey(entry))} + {@render item(entry, i)} + {/each} + + {#if items.length === 0} + {#if empty} + {@render empty()} + {:else} + + {/if} + {/if} +
+
diff --git a/packages/shared-ui/src/organisms/index.ts b/packages/shared-ui/src/organisms/index.ts index f1fefa964..610da9bfd 100644 --- a/packages/shared-ui/src/organisms/index.ts +++ b/packages/shared-ui/src/organisms/index.ts @@ -2,6 +2,7 @@ export { default as Modal } from './Modal.svelte'; export { default as ConfirmationModal } from './ConfirmationModal.svelte'; export { default as FormModal } from './FormModal.svelte'; export { default as AppSlider } from './AppSlider.svelte'; +export { default as BaseListView } from './BaseListView.svelte'; export type { AppItem } from './AppSlider.types'; // Network Graph