From fcc4c4665c23673ad003e8c54a4e34cb1c2e76b8 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 18 Apr 2026 15:50:21 +0200 Subject: [PATCH] feat(edit): unify edit page with enrich split-panel layout Extract DocumentEditLayout shared component for the PDF+form split-panel UI, replacing the old scrolling layout on /documents/[id]/edit with the same fixed-panel structure used by /enrich/[id]. Removes TranscriptionSection and FileSectionEdit from the edit page; file upload/replace is now handled by the shared layout. Delete SaveBar and FileSectionEdit as dead code. Co-Authored-By: Claude Sonnet 4.6 --- .../document/DocumentEditLayout.svelte | 221 ++++++++++++++++ .../components/document/WhoWhenSection.svelte | 7 +- .../documents/[id]/edit/+page.server.ts | 5 +- .../routes/documents/[id]/edit/+page.svelte | 131 ++++++---- .../[id]/edit/FileSectionEdit.svelte | 62 ----- .../routes/documents/[id]/edit/SaveBar.svelte | 76 ------ .../[id]/edit/SaveBar.svelte.spec.ts | 92 ------- frontend/src/routes/enrich/[id]/+page.svelte | 240 +++--------------- 8 files changed, 338 insertions(+), 496 deletions(-) create mode 100644 frontend/src/lib/components/document/DocumentEditLayout.svelte delete mode 100644 frontend/src/routes/documents/[id]/edit/FileSectionEdit.svelte delete mode 100644 frontend/src/routes/documents/[id]/edit/SaveBar.svelte delete mode 100644 frontend/src/routes/documents/[id]/edit/SaveBar.svelte.spec.ts diff --git a/frontend/src/lib/components/document/DocumentEditLayout.svelte b/frontend/src/lib/components/document/DocumentEditLayout.svelte new file mode 100644 index 00000000..8fe0664f --- /dev/null +++ b/frontend/src/lib/components/document/DocumentEditLayout.svelte @@ -0,0 +1,221 @@ + + +
+ +
+ {@render topbar()} +
+ + +
+ Pflichtfelder +
+
+
+ {requiredFilled} / 3 +
+ + +
+ +
+ {#if !doc.filePath} + + {:else} + +
+ +
+
+ {}} + /> +
+ {/if} +
+ + +
+ {#if formError} +
+ {formError} +
+ {/if} + +
+ + + + + +
+ {@render actionbar()} +
+
+
+
diff --git a/frontend/src/lib/components/document/WhoWhenSection.svelte b/frontend/src/lib/components/document/WhoWhenSection.svelte index cddd9514..6a1dfe2a 100644 --- a/frontend/src/lib/components/document/WhoWhenSection.svelte +++ b/frontend/src/lib/components/document/WhoWhenSection.svelte @@ -4,12 +4,9 @@ import PersonTypeahead from '$lib/components/PersonTypeahead.svelte'; import PersonMultiSelect from '$lib/components/PersonMultiSelect.svelte'; import { isoToGerman, handleGermanDateInput } from '$lib/utils/date'; import { m } from '$lib/paraglide/messages.js'; +import type { components } from '$lib/generated/api'; -interface Person { - id: string; - firstName: string; - lastName: string; -} +type Person = components['schemas']['Person']; let { senderId = $bindable(''), diff --git a/frontend/src/routes/documents/[id]/edit/+page.server.ts b/frontend/src/routes/documents/[id]/edit/+page.server.ts index de11c47a..f8e28fe5 100644 --- a/frontend/src/routes/documents/[id]/edit/+page.server.ts +++ b/frontend/src/routes/documents/[id]/edit/+page.server.ts @@ -6,12 +6,15 @@ import { parseBackendError, getErrorMessage } from '$lib/errors'; export async function load({ params, fetch, - locals + locals, + depends }: { params: { id: string }; fetch: typeof globalThis.fetch; locals: App.Locals; + depends: (dep: string) => void; }) { + depends('app:document'); const canWrite = locals.user?.groups?.some((g: { permissions: string[] }) => g.permissions.includes('WRITE_ALL') diff --git a/frontend/src/routes/documents/[id]/edit/+page.svelte b/frontend/src/routes/documents/[id]/edit/+page.svelte index 6bc970f1..936407b4 100644 --- a/frontend/src/routes/documents/[id]/edit/+page.svelte +++ b/frontend/src/routes/documents/[id]/edit/+page.svelte @@ -1,27 +1,34 @@ -
- -
+ + {doc.title || doc.originalFilename || 'Dokument'} — {m.doc_edit_heading()} + + + + {#snippet topbar()} {m.btn_back_to_document()} -

- {m.doc_edit_heading()} — - {doc.title || doc.originalFilename} -

-
- {#if form?.error} -
{form.error}
- {/if} +

+ {doc.title || doc.originalFilename} +

-
- - - - - - +
+ {/snippet} -
-
-
+ {#snippet actionbar()} + + +
+ + {m.btn_cancel()} + + + + + +
+ {/snippet} + + +
+
diff --git a/frontend/src/routes/documents/[id]/edit/FileSectionEdit.svelte b/frontend/src/routes/documents/[id]/edit/FileSectionEdit.svelte deleted file mode 100644 index 6a30be46..00000000 --- a/frontend/src/routes/documents/[id]/edit/FileSectionEdit.svelte +++ /dev/null @@ -1,62 +0,0 @@ - - -
-
-

- {m.doc_section_file()} -

-
- - -
- - {m.doc_current_file_label()} - {originalFilename} -
- - - - -
diff --git a/frontend/src/routes/documents/[id]/edit/SaveBar.svelte b/frontend/src/routes/documents/[id]/edit/SaveBar.svelte deleted file mode 100644 index 477f3582..00000000 --- a/frontend/src/routes/documents/[id]/edit/SaveBar.svelte +++ /dev/null @@ -1,76 +0,0 @@ - - -
- - -
- -
- - -
- - -
- - - {m.btn_cancel()} - -
-
-
diff --git a/frontend/src/routes/documents/[id]/edit/SaveBar.svelte.spec.ts b/frontend/src/routes/documents/[id]/edit/SaveBar.svelte.spec.ts deleted file mode 100644 index d1ad0d97..00000000 --- a/frontend/src/routes/documents/[id]/edit/SaveBar.svelte.spec.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { describe, it, expect, vi, afterEach } from 'vitest'; -import { cleanup, render } from 'vitest-browser-svelte'; -import { page } from 'vitest/browser'; -import SaveBar from './SaveBar.svelte'; -import { createConfirmService, CONFIRM_KEY } from '$lib/services/confirm.svelte.js'; - -let appendedForms: HTMLFormElement[] = []; - -afterEach(() => { - cleanup(); - appendedForms.forEach((f) => f.remove()); - appendedForms = []; -}); - -function renderSaveBar(docId = 'doc-1') { - const service = createConfirmService(); - - // Mount a dummy delete form so SaveBar can find it via document.getElementById - const deleteForm = document.createElement('form'); - deleteForm.id = 'delete-form'; - document.body.appendChild(deleteForm); - appendedForms.push(deleteForm); - - const result = render(SaveBar, { - props: { docId }, - context: new Map([[CONFIRM_KEY, service]]) - }); - - return { ...result, service, deleteForm }; -} - -// ─── Rendering ──────────────────────────────────────────────────────────────── - -describe('SaveBar — rendering', () => { - it('renders save button', async () => { - renderSaveBar(); - await expect.element(page.getByRole('button', { name: /Speichern/i })).toBeInTheDocument(); - }); - - it('renders delete button', async () => { - renderSaveBar(); - // The delete button should be type="button" (async confirm flow) - const deleteBtn = document.querySelector('button[type="button"]'); - expect(deleteBtn).not.toBeNull(); - }); - - it('renders cancel link pointing to /documents/doc-1', async () => { - renderSaveBar(); - await expect - .element(page.getByRole('link', { name: /Abbrechen/i })) - .toHaveAttribute('href', '/documents/doc-1'); - }); -}); - -// ─── Delete confirmation ────────────────────────────────────────────────────── - -describe('SaveBar — delete confirmation', () => { - it('opens confirm dialog when delete button is clicked', async () => { - const { service } = renderSaveBar(); - const deleteBtn = document.querySelectorAll('button[type="button"]')[0]; - deleteBtn.click(); - await vi.waitFor(() => expect(service.options).not.toBeNull()); - expect(service.options?.destructive).toBe(true); - service.settle(false); - }); - - it('submits delete form when user confirms', async () => { - const { service, deleteForm } = renderSaveBar(); - const requestSubmit = vi.spyOn(deleteForm, 'requestSubmit').mockImplementation(() => {}); - - const deleteBtn = document.querySelectorAll('button[type="button"]')[0]; - deleteBtn.click(); - await vi.waitFor(() => expect(service.options).not.toBeNull()); - service.settle(true); - await vi.waitFor(() => expect(service.options).toBeNull()); - - expect(requestSubmit).toHaveBeenCalledOnce(); - }); - - it('does not submit delete form when user cancels', async () => { - const { service, deleteForm } = renderSaveBar(); - const requestSubmit = vi.spyOn(deleteForm, 'requestSubmit').mockImplementation(() => {}); - - const deleteBtn = document.querySelectorAll('button[type="button"]')[0]; - deleteBtn.click(); - await vi.waitFor(() => expect(service.options).not.toBeNull()); - service.settle(false); - await vi.waitFor(() => expect(service.options).toBeNull()); - - expect(requestSubmit).not.toHaveBeenCalled(); - }); -}); diff --git a/frontend/src/routes/enrich/[id]/+page.svelte b/frontend/src/routes/enrich/[id]/+page.svelte index 2bd05b74..81d72d49 100644 --- a/frontend/src/routes/enrich/[id]/+page.svelte +++ b/frontend/src/routes/enrich/[id]/+page.svelte @@ -1,101 +1,19 @@ {doc.title || doc.originalFilename || 'Dokument'} — Anreicherung - + {/snippet} + + +