diff --git a/frontend/src/lib/components/document/BulkDropZone.svelte b/frontend/src/lib/components/document/BulkDropZone.svelte
index 065159e3..a279ffc5 100644
--- a/frontend/src/lib/components/document/BulkDropZone.svelte
+++ b/frontend/src/lib/components/document/BulkDropZone.svelte
@@ -1,4 +1,6 @@
-
+
{
+ e.preventDefault();
+ isDragging = true;
+ }}
+ ondragleave={() => (isDragging = false)}
+ ondrop={(e) => {
+ e.preventDefault();
+ isDragging = false;
+ if (e.dataTransfer && e.dataTransfer.files.length > 0) {
+ onFilesAdded(Array.from(e.dataTransfer.files));
+ }
+ }}
+>
{
- e.preventDefault();
- isDragging = true;
- }}
- ondragleave={() => (isDragging = false)}
- ondrop={(e) => {
- e.preventDefault();
- isDragging = false;
- if (e.dataTransfer && e.dataTransfer.files.length > 0) {
- onFilesAdded(Array.from(e.dataTransfer.files));
- }
- }}
+ class={[
+ 'flex w-full max-w-sm flex-col items-center gap-3 rounded-md border-2 border-dashed px-6 py-9 text-center transition-colors',
+ isDragging ? 'border-accent bg-accent/10' : 'border-accent/50 bg-white/[0.04]'
+ ].join(' ')}
>
-
+
+
-
PDF-Dateien hier ablegen
-
oder
+
+
+
{m.bulk_drop_hint()}
+
+
+
+ Für jede Datei wird ein eigenes Dokument erstellt.
+ Der Titel wird aus dem Dateinamen vorausgefüllt —
+ alle anderen Felder gelten für alle gemeinsam.
+
+
+
Dateien auswählen
+
+
+
{m.bulk_drop_sub()}
diff --git a/frontend/src/lib/components/document/FileSwitcherStrip.svelte b/frontend/src/lib/components/document/FileSwitcherStrip.svelte
index e7badb4d..eef08b87 100644
--- a/frontend/src/lib/components/document/FileSwitcherStrip.svelte
+++ b/frontend/src/lib/components/document/FileSwitcherStrip.svelte
@@ -1,9 +1,12 @@
-
- {#each files as entry (entry.id)}
-
- onSelect(entry.id)}
- class={[
- 'inline-flex cursor-pointer items-center gap-1 rounded-sm border px-3 py-1.5 text-xs font-medium transition-colors',
- entry.id === activeId
- ? 'border-brand-mint bg-brand-mint/10 font-bold text-brand-navy'
- : 'border-brand-sand bg-white text-brand-navy',
- entry.status === 'error'
- ? 'border-dashed border-red-300 bg-red-50 text-red-700'
- : ''
- ].join(' ')}
- >{entry.title}
- onRemove(entry.id)}
- class="ml-1 text-xs text-gray-400 hover:text-brand-navy"
- >
- ×
-
-
- {/each}
-
+
‹
+
+
+
+ {#each files as entry, i (entry.id)}
+
+ onSelect(entry.id)}
+ class={[
+ 'inline-flex cursor-pointer items-center gap-1 rounded-[2px] px-1.5 py-0.5 text-[11px] font-bold transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent',
+ entry.id === activeId
+ ? 'bg-accent text-primary'
+ : 'bg-black/[0.06] text-ink-2 hover:bg-black/10',
+ entry.status === 'error'
+ ? '!border !border-dashed !border-red-400 !bg-red-50/80 !text-red-700'
+ : ''
+ ].join(' ')}
+ >
+ {i + 1}
+ {entry.title}
+
+ onRemove(entry.id)}
+ class="ml-0.5 flex h-[44px] w-[44px] items-center justify-center text-base text-ink-3 hover:text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-accent"
+ >
+ ×
+
+
+ {/each}
+
+
+
+
›
+
diff --git a/frontend/src/lib/components/document/FileSwitcherStrip.svelte.spec.ts b/frontend/src/lib/components/document/FileSwitcherStrip.svelte.spec.ts
index 86968e24..56fe281a 100644
--- a/frontend/src/lib/components/document/FileSwitcherStrip.svelte.spec.ts
+++ b/frontend/src/lib/components/document/FileSwitcherStrip.svelte.spec.ts
@@ -10,6 +10,7 @@ export interface FileEntry {
file: File;
title: string;
status: 'idle' | 'error';
+ previewUrl: string;
}
function makeFiles(n: number): FileEntry[] {
@@ -17,7 +18,8 @@ function makeFiles(n: number): FileEntry[] {
id: `id-${i}`,
file: new File([''], `file${i}.pdf`),
title: `File ${i}`,
- status: 'idle' as const
+ status: 'idle' as const,
+ previewUrl: ''
}));
}
@@ -65,7 +67,13 @@ describe('FileSwitcherStrip', () => {
it('error chip has aria-label containing warning indicator', async () => {
const files: FileEntry[] = [
- { id: 'e1', file: new File([''], 'bad.pdf'), title: 'Bad file', status: 'error' }
+ {
+ id: 'e1',
+ file: new File([''], 'bad.pdf'),
+ title: 'Bad file',
+ status: 'error',
+ previewUrl: ''
+ }
];
const { container } = render(FileSwitcherStrip, {
files,
@@ -85,7 +93,7 @@ describe('FileSwitcherStrip', () => {
onSelect: vi.fn(),
onRemove: vi.fn()
});
- const firstBtn = container.querySelectorAll('[role="button"]')[0] as HTMLElement;
+ const firstBtn = container.querySelectorAll('[data-chip-id]')[0] as HTMLElement;
firstBtn.focus();
await userEvent.keyboard('{ArrowRight}');
const focused = document.activeElement;
diff --git a/frontend/src/lib/components/document/ScopeCard.svelte b/frontend/src/lib/components/document/ScopeCard.svelte
index ebdbe82a..7eb6002d 100644
--- a/frontend/src/lib/components/document/ScopeCard.svelte
+++ b/frontend/src/lib/components/document/ScopeCard.svelte
@@ -1,4 +1,6 @@
-
+
{#if chunkProgress}
{/if}
-
+
{m.bulk_discard_all()}
- {fileCount === 1 ? 'Speichern →' : `${fileCount} speichern →`}
+ {fileCount === 1 ? m.bulk_save_cta_one() : m.bulk_save_cta({ count: fileCount })}
diff --git a/frontend/src/routes/documents/new/+page.svelte b/frontend/src/routes/documents/new/+page.svelte
index 753233b1..c4cb69f8 100644
--- a/frontend/src/routes/documents/new/+page.svelte
+++ b/frontend/src/routes/documents/new/+page.svelte
@@ -1,37 +1,11 @@
-
+
diff --git a/frontend/src/routes/documents/new/page.svelte.spec.ts b/frontend/src/routes/documents/new/page.svelte.spec.ts
index dcdfbdef..b22028bf 100644
--- a/frontend/src/routes/documents/new/page.svelte.spec.ts
+++ b/frontend/src/routes/documents/new/page.svelte.spec.ts
@@ -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
('#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('#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(
'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('input[name="receiverIds"]');
expect(hidden?.value).toBe('p2');
});