From 15114c2d927009a00ffda6948460115aeaa9924a Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 25 May 2026 17:45:55 +0200 Subject: [PATCH] feat(dashboard): load tag tree for both reader and editor dashboard Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/routes/+page.server.ts | 20 ++++++++++++++++---- frontend/src/routes/page.server.spec.ts | 9 ++++++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/frontend/src/routes/+page.server.ts b/frontend/src/routes/+page.server.ts index ed229042..11d74af1 100644 --- a/frontend/src/routes/+page.server.ts +++ b/frontend/src/routes/+page.server.ts @@ -12,6 +12,7 @@ type IncompleteDocumentDTO = components['schemas']['IncompleteDocumentDTO']; type PersonSummaryDTO = components['schemas']['PersonSummaryDTO']; type DocumentListItem = components['schemas']['DocumentListItem']; type Geschichte = components['schemas']['Geschichte']; +type TagTreeNodeDTO = components['schemas']['TagTreeNodeDTO']; function settled(res: PromiseSettledResult | undefined): T | null { if (res?.status !== 'fulfilled') return null; @@ -40,7 +41,8 @@ export async function load({ fetch, parent }) { api.GET('/api/documents/search', { params: { query: { sort: 'UPDATED_AT', dir: 'DESC', size: 5 } } }), - api.GET('/api/geschichten', { params: { query: { status: 'PUBLISHED', limit: 3 } } }) + api.GET('/api/geschichten', { params: { query: { status: 'PUBLISHED', limit: 3 } } }), + api.GET('/api/tags/tree') ]; if (canBlogWrite) { readerFetches.push( @@ -48,7 +50,7 @@ export async function load({ fetch, parent }) { ); } - const [statsRes, topPersonsRes, recentDocsRes, recentStoriesRes, draftsRes] = + const [statsRes, topPersonsRes, recentDocsRes, recentStoriesRes, tagTreeRes, draftsRes] = await Promise.allSettled(readerFetches); const readerStats = settled(statsRes); @@ -56,6 +58,7 @@ export async function load({ fetch, parent }) { const searchData = settled<{ items: DocumentListItem[] }>(recentDocsRes); const recentDocs = searchData?.items ?? []; const recentStories = settled(recentStoriesRes) ?? []; + const tagTree = settled(tagTreeRes) ?? []; const drafts = settled(draftsRes) ?? []; return { @@ -65,6 +68,7 @@ export async function load({ fetch, parent }) { topPersons, recentDocs, recentStories, + tagTree, drafts, error: null as string | null }; @@ -80,7 +84,8 @@ export async function load({ fetch, parent }) { readyResult, weeklyStatsResult, incompleteResult, - incompleteCountResult + incompleteCountResult, + tagTreeResult ] = await Promise.allSettled([ api.GET('/api/stats'), api.GET('/api/dashboard/resume'), @@ -91,7 +96,8 @@ export async function load({ fetch, parent }) { api.GET('/api/transcription/ready-to-read'), api.GET('/api/transcription/weekly-stats'), api.GET('/api/documents/incomplete', { params: { query: { size: 5 } } }), - api.GET('/api/documents/incomplete-count') + api.GET('/api/documents/incomplete-count'), + api.GET('/api/tags/tree') ]); let stats: StatsDTO | null = null; @@ -104,6 +110,7 @@ export async function load({ fetch, parent }) { let weeklyStats: TranscriptionWeeklyStatsDTO | null = null; let incompleteDocs: IncompleteDocumentDTO[] = []; let incompleteTotal = 0; + let tagTree: TagTreeNodeDTO[] = []; if (statsResult.status === 'fulfilled' && statsResult.value.response.ok) { stats = statsResult.value.data ?? null; @@ -135,6 +142,9 @@ export async function load({ fetch, parent }) { if (incompleteCountResult.status === 'fulfilled' && incompleteCountResult.value.response.ok) { incompleteTotal = (incompleteCountResult.value.data?.count as number | undefined) ?? 0; } + if (tagTreeResult.status === 'fulfilled' && tagTreeResult.value.response.ok) { + tagTree = (tagTreeResult.value.data as TagTreeNodeDTO[]) ?? []; + } return { isReader: false as const, @@ -148,6 +158,7 @@ export async function load({ fetch, parent }) { weeklyStats, incompleteDocs, incompleteTotal, + tagTree, error: null as string | null }; } catch (e) { @@ -169,6 +180,7 @@ export async function load({ fetch, parent }) { topPersons: [] as PersonSummaryDTO[], recentDocs: [] as DocumentListItem[], recentStories: [] as Geschichte[], + tagTree: [] as TagTreeNodeDTO[], drafts: [] as Geschichte[], error: 'Daten konnten nicht geladen werden.' as string | null }; diff --git a/frontend/src/routes/page.server.spec.ts b/frontend/src/routes/page.server.spec.ts index a04cf1fe..66850c28 100644 --- a/frontend/src/routes/page.server.spec.ts +++ b/frontend/src/routes/page.server.spec.ts @@ -108,7 +108,8 @@ describe('home page load — dashboard', () => { data: { segmentationCount: 0, transcriptionCount: 0, readyCount: 0 } }) // weekly-stats .mockResolvedValueOnce({ response: { ok: true }, data: [] }) // incomplete - .mockResolvedValueOnce({ response: { ok: true }, data: { count: 0 } }); // incomplete-count + .mockResolvedValueOnce({ response: { ok: true }, data: { count: 0 } }) // incomplete-count + .mockResolvedValueOnce({ response: { ok: true }, data: [] }); // tags/tree vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); @@ -146,7 +147,8 @@ describe('home page load — dashboard', () => { data: { segmentationCount: 0, transcriptionCount: 0, readyCount: 0 } }) // weekly-stats .mockResolvedValueOnce({ response: { ok: true }, data: [] }) // incomplete - .mockResolvedValueOnce({ response: { ok: true }, data: { count: 0 } }); // incomplete-count + .mockResolvedValueOnce({ response: { ok: true }, data: { count: 0 } }) // incomplete-count + .mockResolvedValueOnce({ response: { ok: true }, data: [] }); // tags/tree vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >); @@ -458,7 +460,8 @@ describe('home page load — reader branch (isReader = !canWrite && !canAnnotate .mockResolvedValueOnce(okStats) .mockReturnValueOnce(failPersons) .mockResolvedValueOnce(okSearch) - .mockResolvedValueOnce(okStories); + .mockResolvedValueOnce(okStories) + .mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }); // tags/tree vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< typeof createApiClient >);