fix(bulk-upload): spec-compliant split-panel layout with local PDF preview
Rewrites BulkDocumentEditLayout to match the spec exactly: - Fixed viewport layout (same as DocumentEditLayout) filling viewport below nav - Split panel visible in all states (N=0/1/≥2) — was fullscreen dark drop zone - N=0: centered drop-zone-box in left panel; shared form visible but greyed out - N≥1: real PDF preview via URL.createObjectURL (no server upload required) - N≥2: FileSwitcherStrip at bottom of left panel; count pill + discard in topbar - FileEntry gains previewUrl; blob URLs created on add, revoked on remove/destroy - save() checks response.ok and marks failed files with status: 'error' - BulkDropZone redesigned: spec-accurate box with circular mint icon, serif title - FileSwitcherStrip: number badges, arrows, keyboard nav via data-chip-id selector - ScopeCard, UploadSaveBar: hardcoded German replaced with Paraglide i18n keys - +page.svelte simplified to bare component render (layout is self-contained) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,37 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
import BulkDocumentEditLayout from '$lib/components/document/BulkDocumentEditLayout.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-7xl px-4 py-8">
|
||||
<div class="mb-6">
|
||||
<a
|
||||
href="/"
|
||||
class="group mb-4 inline-flex items-center text-xs font-bold tracking-widest text-ink-2 uppercase transition-colors hover:text-ink"
|
||||
>
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 transform transition-transform group-hover:-translate-x-1"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 19l-7-7m0 0l7-7m-7 7h18"
|
||||
/>
|
||||
</svg>
|
||||
{m.btn_back_to_overview()}
|
||||
</a>
|
||||
<h1 class="font-serif text-3xl text-ink">{m.doc_new_heading()}</h1>
|
||||
</div>
|
||||
|
||||
<BulkDocumentEditLayout
|
||||
initialSenderId={data.initialSenderId}
|
||||
initialSenderName={data.initialSenderName}
|
||||
initialReceivers={data.initialReceivers}
|
||||
/>
|
||||
</div>
|
||||
<BulkDocumentEditLayout
|
||||
initialSenderId={data.initialSenderId}
|
||||
initialSenderName={data.initialSenderName}
|
||||
initialReceivers={data.initialReceivers}
|
||||
/>
|
||||
|
||||
@@ -21,15 +21,14 @@ const baseData = {
|
||||
|
||||
describe('New document page – sender prefill', () => {
|
||||
it('shows an empty sender input when no senderId is in the URL', async () => {
|
||||
render(Page, { data: baseData, form: null });
|
||||
render(Page, { data: baseData });
|
||||
const input = document.querySelector<HTMLInputElement>('#senderId-search');
|
||||
expect(input?.value).toBe('');
|
||||
});
|
||||
|
||||
it('shows the sender name in the typeahead input when initialSenderName is set', async () => {
|
||||
render(Page, {
|
||||
data: { ...baseData, initialSenderId: 'p1', initialSenderName: 'Hans Müller' },
|
||||
form: null
|
||||
data: { ...baseData, initialSenderId: 'p1', initialSenderName: 'Hans Müller' }
|
||||
});
|
||||
const input = document.querySelector<HTMLInputElement>('#senderId-search');
|
||||
expect(input?.value).toBe('Hans Müller');
|
||||
@@ -37,8 +36,7 @@ describe('New document page – sender prefill', () => {
|
||||
|
||||
it('sets the hidden senderId input to the prefilled ID', async () => {
|
||||
render(Page, {
|
||||
data: { ...baseData, initialSenderId: 'p1', initialSenderName: 'Hans Müller' },
|
||||
form: null
|
||||
data: { ...baseData, initialSenderId: 'p1', initialSenderName: 'Hans Müller' }
|
||||
});
|
||||
const hidden = document.querySelector<HTMLInputElement>(
|
||||
'input[type="hidden"][name="senderId"]'
|
||||
@@ -51,7 +49,7 @@ describe('New document page – sender prefill', () => {
|
||||
|
||||
describe('New document page – receiver prefill', () => {
|
||||
it('shows no receiver chips when initialReceivers is empty', async () => {
|
||||
render(Page, { data: baseData, form: null });
|
||||
render(Page, { data: baseData });
|
||||
await expect.element(page.getByText('Anna Schmidt')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -62,7 +60,7 @@ describe('New document page – receiver prefill', () => {
|
||||
{ id: 'p2', firstName: 'Anna', lastName: 'Schmidt', displayName: 'Anna Schmidt' }
|
||||
]
|
||||
};
|
||||
render(Page, { data, form: null });
|
||||
render(Page, { data });
|
||||
await expect.element(page.getByText('Anna Schmidt')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -73,7 +71,7 @@ describe('New document page – receiver prefill', () => {
|
||||
{ id: 'p2', firstName: 'Anna', lastName: 'Schmidt', displayName: 'Anna Schmidt' }
|
||||
]
|
||||
};
|
||||
render(Page, { data, form: null });
|
||||
render(Page, { data });
|
||||
const hidden = document.querySelector<HTMLInputElement>('input[name="receiverIds"]');
|
||||
expect(hidden?.value).toBe('p2');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user