feat(dashboard): render real document thumbnail in resume strip

Replaces the generic parchment SVG placeholder with an <img> pointing at
the backend's thumbnail endpoint when the document has one. The 180×252
container matches DocumentThumbnail's 5:7 A4 convention so the
dashboard tile sits visually next to the list/person-sublist tiles
instead of looking squatter than they do. dark:mix-blend-multiply keeps
paper scans from glaring on a dark page background (#309).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-23 22:22:19 +02:00
parent a02f6cdcd7
commit 1d44bbb1bd
2 changed files with 30 additions and 20 deletions

View File

@@ -44,27 +44,20 @@ function safeColor(color: string): string {
</div> </div>
{:else} {:else}
<div data-testid="resume-strip" class="flex gap-4 rounded-sm border border-line bg-surface p-5"> <div data-testid="resume-strip" class="flex gap-4 rounded-sm border border-line bg-surface p-5">
<svg <div
xmlns="http://www.w3.org/2000/svg" class="relative h-[252px] w-[180px] flex-shrink-0 overflow-hidden rounded-sm border border-line bg-white"
width="180"
height="246"
viewBox="0 0 180 246"
aria-hidden="true"
class="shrink-0"
> >
<defs> {#if resumeDoc.thumbnailUrl}
<linearGradient id="parchment" x1="0" y1="0" x2="0" y2="1"> <img
<stop offset="0%" stop-color="#f5f0e8" /> data-testid="resume-thumbnail-img"
<stop offset="100%" stop-color="#ede8d5" /> src={resumeDoc.thumbnailUrl}
</linearGradient> alt=""
</defs> class="h-full w-full object-cover object-top dark:mix-blend-multiply"
<rect width="180" height="246" fill="url(#parchment)" /> loading="lazy"
<line x1="30" y1="40" x2="150" y2="40" stroke="#b0a898" stroke-width="1" /> decoding="async"
<line x1="30" y1="70" x2="150" y2="70" stroke="#b0a898" stroke-width="1" /> />
<line x1="30" y1="100" x2="150" y2="100" stroke="#b0a898" stroke-width="1" /> {/if}
<line x1="30" y1="130" x2="150" y2="130" stroke="#b0a898" stroke-width="1" /> </div>
<line x1="30" y1="160" x2="150" y2="160" stroke="#b0a898" stroke-width="1" />
</svg>
<div class="flex flex-1 flex-col gap-2"> <div class="flex flex-1 flex-col gap-2">
<p class="flex items-center gap-1.5 font-sans text-xs text-ink-3"> <p class="flex items-center gap-1.5 font-sans text-xs text-ink-3">

View File

@@ -21,6 +21,11 @@ const mockResume: DashboardResumeDTO = {
collaborators: [] collaborators: []
}; };
const mockResumeWithThumbnail: DashboardResumeDTO = {
...mockResume,
thumbnailUrl: '/api/documents/doc-123/thumbnail?v=2026-04-23T09%3A00'
};
describe('DashboardResumeStrip', () => { describe('DashboardResumeStrip', () => {
it('renders empty state heading when resumeDoc is null', async () => { it('renders empty state heading when resumeDoc is null', async () => {
render(DashboardResumeStrip, { resumeDoc: null }); render(DashboardResumeStrip, { resumeDoc: null });
@@ -52,4 +57,16 @@ describe('DashboardResumeStrip', () => {
const label = page.getByText(/4 Abschnitte/i); const label = page.getByText(/4 Abschnitte/i);
await expect.element(label).toBeInTheDocument(); await expect.element(label).toBeInTheDocument();
}); });
it('renders thumbnail img with expected attrs when thumbnailUrl is set', async () => {
render(DashboardResumeStrip, { resumeDoc: mockResumeWithThumbnail });
const img = page.getByTestId('resume-thumbnail-img');
await expect.element(img).toBeInTheDocument();
await expect
.element(img)
.toHaveAttribute('src', '/api/documents/doc-123/thumbnail?v=2026-04-23T09%3A00');
await expect.element(img).toHaveAttribute('alt', '');
await expect.element(img).toHaveAttribute('loading', 'lazy');
await expect.element(img).toHaveAttribute('decoding', 'async');
});
}); });