From a25216058586c156a1e5954c2c484733fbb7fef3 Mon Sep 17 00:00:00 2001 From: Till JS Date: Fri, 17 Apr 2026 13:17:22 +0200 Subject: [PATCH] =?UTF-8?q?feat(library):=20M3=20=E2=80=94=20progress=20tr?= =?UTF-8?q?acking=20(pages,=20episodes,=20issues)=20+=20restart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ProgressControls.svelte renders typ-spezifische Fortschritts-UI: - book → range slider + page input + "Fertig"-Button; auto-completes the entry (status=completed, times++) when current == total - series → collapsible season/episode grid; each episode is a toggleable pill that writes into details.watched with a watchedAt stamp; auto-completes once watched.length == totalEpisodes - comic → ±1 issue bumper; auto-completes on issueCount reach - movie → atomic, no progress widget libraryEntriesStore.restartEntry: flips a completed entry back to active, stamps startedAt=today, clears completedAt. Preserves the per-episode watched list so users keep the history of the previous run-through; they can reset individual episodes via the tracker if they want a fresh pass. DetailView embeds below the status row and renders a "↻ Nochmal lesen/sehen" button whenever status === 'completed'. docs/plans/library-module.md: M1 + M2 + M3 marked DONE with commit IDs. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/ProgressControls.svelte | 354 ++++++++++++++++++ .../modules/library/stores/entries.svelte.ts | 20 + .../modules/library/views/DetailView.svelte | 55 ++- docs/plans/library-module.md | 15 +- 4 files changed, 433 insertions(+), 11 deletions(-) create mode 100644 apps/mana/apps/web/src/lib/modules/library/components/ProgressControls.svelte diff --git a/apps/mana/apps/web/src/lib/modules/library/components/ProgressControls.svelte b/apps/mana/apps/web/src/lib/modules/library/components/ProgressControls.svelte new file mode 100644 index 000000000..7b1cca4eb --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/library/components/ProgressControls.svelte @@ -0,0 +1,354 @@ + + + +{#if entry.details.kind === 'book'} +
+
+

Lesefortschritt

+ {#if entry.details.pages} + {bookProgressPct}% + {/if} +
+ {#if entry.details.pages} + setCurrentPage(parseInt((e.target as HTMLInputElement).value))} + /> +
+ setCurrentPage(parseInt((e.target as HTMLInputElement).value) || 0)} + /> + / {entry.details.pages} + +
+ {:else} +

Trage die Seitenzahl ein, um den Fortschritt zu tracken.

+ {/if} +
+{:else if entry.details.kind === 'series'} +
+
+

Episoden-Tracker

+ {seriesTotals.watched} / {seriesTotals.total || '?'} +
+ {#if seasonList.length === 0} +

+ Trage Staffeln + Episoden in den Details ein, um die einzelnen Folgen abzuhaken. +

+ {:else} +
+ {#each seasonList as s (s.season)} + {@const watched = entry.details.kind === 'series' ? (entry.details.watched ?? []) : []} + {@const seasonWatched = countWatchedInSeason(watched, s.season)} +
{ + const target = e.currentTarget as HTMLDetailsElement; + if (target.open) openSeason = s.season; + else if (openSeason === s.season) openSeason = null; + }} + > + + Staffel {s.season} + {seasonWatched} / {s.episodeCount} + +
+ {#each Array.from({ length: s.episodeCount }, (_, i) => i + 1) as ep (ep)} + + {/each} +
+
+ {/each} +
+ {/if} +
+{:else if entry.details.kind === 'comic'} +
+
+

Ausgaben-Fortschritt

+ + {entry.details.currentIssue ?? 0} + {#if entry.details.issueCount}/ {entry.details.issueCount}{/if} + +
+
+ + #{entry.details.currentIssue ?? 0} + +
+
+{/if} + + diff --git a/apps/mana/apps/web/src/lib/modules/library/stores/entries.svelte.ts b/apps/mana/apps/web/src/lib/modules/library/stores/entries.svelte.ts index 0463cb415..d680458fb 100644 --- a/apps/mana/apps/web/src/lib/modules/library/stores/entries.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/library/stores/entries.svelte.ts @@ -145,6 +145,26 @@ export const libraryEntriesStore = { } }, + /** + * "Nochmal" — re-start a completed entry. Leaves `times` alone (it was + * already incremented when the entry was marked complete); resets + * `startedAt` to today, clears `completedAt`, flips status back to + * 'active'. For series, the per-episode watched list is preserved so + * the user has a record of the previous run-through (and can reset + * individual episodes via the tracker if they want). + */ + async restartEntry(id: string) { + const existing = await libraryEntryTable.get(id); + if (!existing) return; + const nowDate = new Date().toISOString().slice(0, 10); + await libraryEntryTable.update(id, { + status: 'active', + startedAt: nowDate, + completedAt: null, + updatedAt: new Date().toISOString(), + }); + }, + async rate(id: string, rating: number | null) { await libraryEntryTable.update(id, { rating, diff --git a/apps/mana/apps/web/src/lib/modules/library/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/library/views/DetailView.svelte index 266083201..dfa11ec0d 100644 --- a/apps/mana/apps/web/src/lib/modules/library/views/DetailView.svelte +++ b/apps/mana/apps/web/src/lib/modules/library/views/DetailView.svelte @@ -1,9 +1,9 @@
@@ -91,10 +105,20 @@ {/each}
- {#if entry.times > 0} -

- {entry.kind === 'book' || entry.kind === 'comic' ? 'Gelesen' : 'Gesehen'}: {entry.times}× -

+ {#if entry.times > 0 || entry.status === 'completed'} +
+ {#if entry.times > 0} + + {entry.kind === 'book' || entry.kind === 'comic' ? 'Gelesen' : 'Gesehen'}: + {entry.times}× + + {/if} + {#if entry.status === 'completed'} + + {/if} +
{/if} {#if entry.genres.length > 0 || entry.tags.length > 0} @@ -168,6 +192,8 @@ {/if} + + {#if entry.review}

Review

@@ -296,10 +322,29 @@ color: #a855f7; border-color: #a855f7; } + .times-row { + display: flex; + align-items: center; + gap: 0.75rem; + margin-top: 0.2rem; + } .times { font-size: 0.85rem; color: var(--color-text-muted, #64748b); } + .restart { + padding: 0.3rem 0.75rem; + border-radius: 0.4rem; + border: 1px solid #a855f7; + background: transparent; + color: #a855f7; + cursor: pointer; + font: inherit; + font-size: 0.8rem; + } + .restart:hover { + background: color-mix(in srgb, #a855f7 10%, transparent); + } .tag-row { display: flex; gap: 0.3rem; diff --git a/docs/plans/library-module.md b/docs/plans/library-module.md index 22bd684b1..f7597e296 100644 --- a/docs/plans/library-module.md +++ b/docs/plans/library-module.md @@ -2,10 +2,13 @@ ## Status (2026-04-17) -**M1 Skelett: DONE.** Modul registriert, Dexie v26 angelegt, Encryption-Registry -eingetragen, Route `/library` mountet einen minimalen Listen-View. Guest-Seed mit -je einem Eintrag pro `kind` (Dune, Arrival, Severance, Saga) vorhanden. -Nächster Schritt: M2 (volles CRUD + Form + Grid + DetailView). +**M1 Skelett: DONE** (commit 8c6502d0f) — Modul registriert, Dexie v26, Encryption-Registry, Route mountet, Guest-Seed (Dune, Arrival, Severance, Saga). + +**M2 CRUD / Grid / Detail: DONE** (commit 364178496) — KindTabs, StatusFilter, RatingStars, EntryCard, EntryForm (Create + Edit, typ-spezifische Details-Accordion), GridView, DetailView, `/library/entry/[id]`-Route. + +**M3 Fortschritt: DONE** — `ProgressControls.svelte`: Seiten-Slider für Bücher (auto-completes bei 100%), Episode-Tracker für Serien (abhakbare Folgen pro Staffel), Issue-Bumper für Comics. `libraryEntriesStore.restartEntry` + "Nochmal"-Button im DetailView für abgeschlossene Einträge. + +Nächster Schritt: M4 (Cover-Upload via `uload`) oder M6 (AI-Tools). Vor M2 entschieden: - Audiobooks: `kind='book'` mit `details.format='audio'` (nicht eigener `kind`). @@ -212,8 +215,8 @@ Missionen wie *"Trage meine letzten 5 gesehenen Filme ein"* können dann über d ## Reihenfolge (Milestones) 1. **M1 — Skelett [DONE 2026-04-17]**: types, collections, module.config, Registry-Eintrag, Dexie-Migration (v26), leere Route. *Ziel: App zeigt leeres Modul an, nichts crasht.* -2. **M2 — CRUD**: entries-Store, EntryForm, GridView, DetailView. Manuelles Anlegen/Editieren funktioniert für alle 4 `kind`s. Cover via URL. -3. **M3 — Fortschritt**: Status-Wechsel, Rating, times-Counter, Episode-Tracker für Serien, Seiten-Slider für Bücher. Guest-Seed komplett. +2. **M2 — CRUD [DONE 2026-04-17]**: entries-Store, EntryForm, GridView, DetailView. Manuelles Anlegen/Editieren funktioniert für alle 4 `kind`s. Cover via URL. +3. **M3 — Fortschritt [DONE 2026-04-17]**: Status-Wechsel, Rating, times-Counter, Episode-Tracker für Serien, Seiten-Slider für Bücher, Issue-Bumper für Comics, Nochmal-Button. 4. **M4 — Cover-Upload**: Integration mit `uload`. Encryption-Registry final. 5. **M5 — Stats + Dashboard-Widget**: useStats, kleiner Widget für Dashboard. 6. **M6 — AI-Tools**: list/create/complete/rate Tools, Shared-AI-Policy.