feat(dashboard): complete frontend redesign for Issue #271
Some checks failed
CI / OCR Service Tests (push) Successful in 29s
CI / Backend Unit Tests (push) Failing after 1m21s
CI / Unit & Component Tests (push) Failing after 2m37s
CI / Unit & Component Tests (pull_request) Failing after 2m27s
CI / OCR Service Tests (pull_request) Successful in 30s
CI / Backend Unit Tests (pull_request) Failing after 1m21s
Some checks failed
CI / OCR Service Tests (push) Successful in 29s
CI / Backend Unit Tests (push) Failing after 1m21s
CI / Unit & Component Tests (push) Failing after 2m37s
CI / Unit & Component Tests (pull_request) Failing after 2m27s
CI / OCR Service Tests (pull_request) Successful in 30s
CI / Backend Unit Tests (pull_request) Failing after 1m21s
- +layout.svelte: Upload button in header (authenticated users only) - +page.server.ts: call /api/dashboard/resume, /pulse, /activity; remove deprecated /api/documents/incomplete and /recent-activity - +page.svelte: 2-col grid layout (main + 320px sidebar), greeting, DashboardFamilyPulse + DashboardActivityFeed in sidebar - DashboardResumeStrip: refactored to use server data (resumeDoc prop), SVG thumbnail, progress bar with aria-*, empty state, CTA - DashboardFamilyPulse: new component — weekly stats from audit_log - DashboardActivityFeed: new component — activity feed with "für dich" badge - Update specs for new data shapes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,7 @@ function makeUrl(params: Record<string, string | string[]> = {}) {
|
||||
// ─── dashboard mode (no search filters) ──────────────────────────────────────
|
||||
|
||||
describe('home page load — dashboard mode', () => {
|
||||
it('sets isDashboard true and fetches stats, incomplete, and recent APIs', async () => {
|
||||
it('sets isDashboard true and fetches stats, resume, pulse, and activity APIs', async () => {
|
||||
const mockGet = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }) // persons
|
||||
@@ -30,8 +30,31 @@ describe('home page load — dashboard mode', () => {
|
||||
response: { ok: true },
|
||||
data: { totalDocuments: 42, totalPersons: 7 }
|
||||
}) // stats
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: [{ id: 'd1' }] }) // incomplete
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: [{ id: 'd2' }] }) // recent
|
||||
.mockResolvedValueOnce({
|
||||
response: { ok: true },
|
||||
data: {
|
||||
documentId: 'd1',
|
||||
title: 'T',
|
||||
caption: '',
|
||||
excerpt: '',
|
||||
page: 1,
|
||||
pages: 2,
|
||||
pct: 50,
|
||||
collaborators: []
|
||||
}
|
||||
}) // resume
|
||||
.mockResolvedValueOnce({
|
||||
response: { ok: true },
|
||||
data: {
|
||||
pages: 5,
|
||||
annotated: 1,
|
||||
transcribed: 2,
|
||||
uploaded: 1,
|
||||
yourPages: 3,
|
||||
contributors: []
|
||||
}
|
||||
}) // pulse
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // activity
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // segmentation-queue
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // transcription-queue
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // ready-to-read
|
||||
@@ -47,8 +70,9 @@ describe('home page load — dashboard mode', () => {
|
||||
|
||||
expect(result.isDashboard).toBe(true);
|
||||
expect(result.stats).toEqual({ totalDocuments: 42, totalPersons: 7 });
|
||||
expect(result.incompleteDocs).toHaveLength(1);
|
||||
expect(result.recentDocs).toHaveLength(1);
|
||||
expect(result.resumeDoc).not.toBeNull();
|
||||
expect(result.pulse).not.toBeNull();
|
||||
expect(result.activityFeed).toEqual([]);
|
||||
expect(result.documents).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -60,8 +84,9 @@ describe('home page load — dashboard mode', () => {
|
||||
response: { ok: true },
|
||||
data: { totalDocuments: 248, totalPersons: 34 }
|
||||
}) // stats
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // incomplete
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // recent
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: null }) // resume
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: null }) // pulse
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // activity
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // segmentation-queue
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // transcription-queue
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // ready-to-read
|
||||
@@ -95,23 +120,24 @@ describe('home page load — dashboard mode', () => {
|
||||
expect(result.stats).toBeNull();
|
||||
});
|
||||
|
||||
it('defaults incompleteDocs to [] when incomplete API rejects', async () => {
|
||||
it('defaults resumeDoc to null when resume API rejects', async () => {
|
||||
const mockGet = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }) // persons
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: { content: [] } }) // notifications
|
||||
.mockRejectedValueOnce(new Error('network')) // incomplete
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: [] }); // recent
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: { content: [] } }) // stats
|
||||
.mockRejectedValueOnce(new Error('network')) // resume
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: null }) // pulse
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: [] }); // activity
|
||||
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
|
||||
typeof createApiClient
|
||||
>);
|
||||
|
||||
const result = await load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch });
|
||||
|
||||
expect(result.incompleteDocs).toEqual([]);
|
||||
expect(result.resumeDoc).toBeNull();
|
||||
});
|
||||
|
||||
it('defaults recentDocs to [] when recent-activity API rejects', async () => {
|
||||
it('defaults activityFeed to [] when activity API rejects', async () => {
|
||||
const mockGet = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ response: { ok: true, status: 200 }, data: [] }) // persons
|
||||
@@ -119,15 +145,16 @@ describe('home page load — dashboard mode', () => {
|
||||
response: { ok: true },
|
||||
data: { totalDocuments: 0, totalPersons: 0 }
|
||||
}) // stats
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: [] }) // incomplete
|
||||
.mockRejectedValueOnce(new Error('network')); // recent
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: null }) // resume
|
||||
.mockResolvedValueOnce({ response: { ok: true }, data: null }) // pulse
|
||||
.mockRejectedValueOnce(new Error('network')); // activity
|
||||
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
|
||||
typeof createApiClient
|
||||
>);
|
||||
|
||||
const result = await load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch });
|
||||
|
||||
expect(result.recentDocs).toEqual([]);
|
||||
expect(result.activityFeed).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -154,8 +181,8 @@ describe('home page load — search mode', () => {
|
||||
expect(result.isDashboard).toBe(false);
|
||||
expect(result.documents).toHaveLength(1);
|
||||
expect(result.stats).toBeNull();
|
||||
expect(result.incompleteDocs).toEqual([]);
|
||||
expect(result.recentDocs).toEqual([]);
|
||||
expect(result.resumeDoc).toBeNull();
|
||||
expect(result.activityFeed).toEqual([]);
|
||||
// Only two API calls — no widget calls
|
||||
expect(mockGet).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user