feat(dashboard): add reader dashboard components
Adds 5 new components for the permission-gated reader layout: - ReaderStatsStrip: stat tiles (documents / persons / stories) linking to list pages - ReaderPersonChips: top-N persons by doc count with avatar + name - ReaderDraftsModule: blog draft list for BLOG_WRITE users - ReaderRecentDocs: 5 most-recently-updated docs with Neu/Aktualisiert badge - ReaderRecentStories: 3 latest published stories with 150-char HTML-stripped excerpt Each component ships with a vitest-browser spec covering the key assertions. Avatar color/initials logic is inlined to satisfy $lib/shared → $lib/person boundary rule. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,7 +29,7 @@ export async function load({ fetch, parent }) {
|
||||
const readerFetches: Promise<unknown>[] = [
|
||||
api.GET('/api/stats'),
|
||||
api.GET('/api/persons', { params: { query: { size: 4, sort: 'documentCount' } } }),
|
||||
api.GET('/api/documents', {
|
||||
api.GET('/api/documents/search', {
|
||||
params: { query: { sort: 'UPDATED_AT', dir: 'DESC', size: 5 } }
|
||||
}),
|
||||
api.GET('/api/geschichten', { params: { query: { status: 'PUBLISHED', limit: 3 } } })
|
||||
@@ -65,7 +65,10 @@ export async function load({ fetch, parent }) {
|
||||
recentDocsRes?.status === 'fulfilled' &&
|
||||
(recentDocsRes.value as { response: Response }).response.ok
|
||||
) {
|
||||
recentDocs = ((recentDocsRes.value as { data: unknown }).data as Document[]) ?? [];
|
||||
const searchResult = (recentDocsRes.value as { data: unknown }).data as {
|
||||
items: { document: Document }[];
|
||||
} | null;
|
||||
recentDocs = searchResult?.items.map((i) => i.document) ?? [];
|
||||
}
|
||||
if (
|
||||
recentStoriesRes?.status === 'fulfilled' &&
|
||||
|
||||
@@ -281,7 +281,7 @@ describe('home page load — reader branch (isReader = !canWrite && !canAnnotate
|
||||
expect(transcriptionCalls).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('calls /api/stats, /api/persons, /api/documents, /api/geschichten for a read-only user', async () => {
|
||||
it('calls /api/stats, /api/persons, /api/documents/search, /api/geschichten for a read-only user', async () => {
|
||||
const mockGet = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: null });
|
||||
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
|
||||
typeof createApiClient
|
||||
@@ -298,7 +298,7 @@ describe('home page load — reader branch (isReader = !canWrite && !canAnnotate
|
||||
const calledEndpoints = mockGet.mock.calls.map((c: unknown[]) => c[0] as string);
|
||||
expect(calledEndpoints).toContain('/api/stats');
|
||||
expect(calledEndpoints).toContain('/api/persons');
|
||||
expect(calledEndpoints).toContain('/api/documents');
|
||||
expect(calledEndpoints).toContain('/api/documents/search');
|
||||
expect(calledEndpoints).toContain('/api/geschichten');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user