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:
@@ -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">
|
||||||
|
|||||||
@@ -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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user