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:
@@ -7,10 +7,10 @@ import SearchFilterBar from './SearchFilterBar.svelte';
|
||||
import DropZone from './DropZone.svelte';
|
||||
import DocumentList from './DocumentList.svelte';
|
||||
import DashboardResumeStrip from '$lib/components/DashboardResumeStrip.svelte';
|
||||
import DashboardNeedsMetadata from '$lib/components/DashboardNeedsMetadata.svelte';
|
||||
import DashboardRecentDocuments from '$lib/components/DashboardRecentDocuments.svelte';
|
||||
import MissionControlStrip from '$lib/components/MissionControlStrip.svelte';
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
import DashboardFamilyPulse from '$lib/components/DashboardFamilyPulse.svelte';
|
||||
import DashboardActivityFeed from '$lib/components/DashboardActivityFeed.svelte';
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
@@ -66,7 +66,6 @@ function handleImmediateSearch() {
|
||||
triggerSearch();
|
||||
}
|
||||
|
||||
// Trigger search when tags change
|
||||
let prevTagStr = untrack(() => tagNames.map((t) => t.name).join(','));
|
||||
$effect(() => {
|
||||
const cur = tagNames.map((t) => t.name).join(',');
|
||||
@@ -76,8 +75,6 @@ $effect(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// Sync local state with server data after navigation.
|
||||
// Guard q: skip overwrite while the user is actively typing in the search field.
|
||||
$effect(() => {
|
||||
if (!qFocused) q = data.filters?.q || '';
|
||||
from = data.filters?.from || '';
|
||||
@@ -92,10 +89,13 @@ $effect(() => {
|
||||
if (hasAdvancedFilters(data.filters)) showAdvanced = true;
|
||||
});
|
||||
|
||||
// Right column is only rendered when there is something to show.
|
||||
// Omitting it prevents an empty 300px ghost column for read-only users
|
||||
// with a complete archive.
|
||||
const showRightColumn = $derived(data.canWrite || (data.incompleteDocs?.length ?? 0) > 0);
|
||||
const greetingText = $derived.by(() => {
|
||||
const name = data?.user?.firstName ?? '';
|
||||
const h = new Date().getHours();
|
||||
if (h < 12) return m.greeting_morning({ name });
|
||||
if (h < 18) return m.greeting_day({ name });
|
||||
return m.greeting_evening({ name });
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -125,35 +125,35 @@ const showRightColumn = $derived(data.canWrite || (data.incompleteDocs?.length ?
|
||||
/>
|
||||
|
||||
{#if data.isDashboard}
|
||||
<DashboardResumeStrip />
|
||||
{#if data?.user}
|
||||
<div class="mb-6">
|
||||
<h1 class="font-serif text-[2rem] text-ink">{greetingText}</h1>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Classic Split: right column first in DOM so it appears above recent docs on mobile.
|
||||
lg:order-last moves it back to the visual right on desktop. -->
|
||||
<!-- No items-start — CSS Grid stretch default makes both columns equal height -->
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 {showRightColumn ? 'lg:grid-cols-[1fr_300px]' : ''}">
|
||||
{#if showRightColumn}
|
||||
<div data-testid="dashboard-right-column" class="flex h-full flex-col gap-4 lg:order-last">
|
||||
{#if data.canWrite}
|
||||
<DropZone />
|
||||
{/if}
|
||||
<!-- flex-1 + min-h-0 fills remaining height after DropZone.
|
||||
min-h-0 overrides the default min-height:auto that prevents flex
|
||||
children from shrinking below their content size. -->
|
||||
<div class="flex min-h-0 flex-1 flex-col">
|
||||
<DashboardNeedsMetadata incompleteDocs={data.incompleteDocs ?? []} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="grid grid-cols-1 gap-5 lg:grid-cols-[1fr_320px] lg:items-start">
|
||||
<div class="flex flex-col gap-5">
|
||||
<DashboardResumeStrip resumeDoc={data.resumeDoc ?? null} />
|
||||
|
||||
<DashboardRecentDocuments recentDocs={data.recentDocs ?? []} stats={data.stats} />
|
||||
<section aria-label={m.dashboard_mission_caption()}>
|
||||
<h2 class="mb-3 font-sans text-xs font-bold tracking-widest text-ink-3 uppercase">
|
||||
{m.dashboard_mission_caption()}
|
||||
</h2>
|
||||
<MissionControlStrip
|
||||
segmentationDocs={data.segmentationDocs ?? []}
|
||||
transcriptionDocs={data.transcriptionDocs ?? []}
|
||||
readyDocs={data.readyDocs ?? []}
|
||||
weeklyStats={data.weeklyStats ?? null}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-5 lg:sticky lg:top-[80px]">
|
||||
<DashboardFamilyPulse pulse={data.pulse ?? null} />
|
||||
<DashboardActivityFeed feed={data.activityFeed ?? []} />
|
||||
<DropZone />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MissionControlStrip
|
||||
segmentationDocs={data.segmentationDocs ?? []}
|
||||
transcriptionDocs={data.transcriptionDocs ?? []}
|
||||
readyDocs={data.readyDocs ?? []}
|
||||
weeklyStats={data.weeklyStats ?? null}
|
||||
/>
|
||||
{:else}
|
||||
<DocumentList
|
||||
documents={data.documents ?? []}
|
||||
|
||||
Reference in New Issue
Block a user