feat: redesign document edit form for accessibility and usability

Full UX overhaul of the edit page targeting non-technical users:
- Grouped fields into four labelled sections (Wer & Wann, Beschreibung, Transkription, Datei)
- Replaced date text field with native <input type="date">
- Replaced sender <select> with PersonTypeahead component
- Replaced receiver multi-select with new PersonMultiSelect (chip-based)
- Sticky save bar at bottom with cancel/save actions
- Aligned all colours to brand palette (no more blue-*)
- Fixed a11y: replaced invalid <label> on compound components with <p>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-16 10:50:03 +01:00
parent 8d66a50652
commit 9c4a7acde8

View File

@@ -1,190 +1,198 @@
<script lang="ts">
import { enhance } from '$app/forms';
import TagInput from '$lib/components/TagInput.svelte';
export let data;
export let form; // Rückgabe der Action (Fehler etc.)
import TagInput from '$lib/components/TagInput.svelte';
import PersonTypeahead from '$lib/components/PersonTypeahead.svelte';
import PersonMultiSelect from '$lib/components/PersonMultiSelect.svelte';
let { document: doc, persons } = data;
let tags = doc.tags ? doc.tags.map(t => t.name) : [];
export let data;
export let form;
let { document: doc } = data;
let tags = doc.tags ? doc.tags.map((t: any) => t.name) : [];
let senderId = doc.sender?.id ?? '';
let selectedReceivers = doc.receivers ?? [];
</script>
<div class="max-w-4xl mx-auto p-6 bg-white shadow mt-10 rounded-lg">
<h1 class="text-2xl font-bold mb-6">Dokument bearbeiten</h1>
<div class="max-w-4xl mx-auto py-8 px-4">
{#if form?.message}
<div class="bg-red-100 text-red-700 p-3 rounded mb-4">{form.message}</div>
{/if}
<!-- Heading -->
<div class="mb-6">
<a href="/documents/{doc.id}" class="inline-flex items-center text-xs font-bold uppercase tracking-widest text-gray-500 hover:text-brand-navy transition-colors group mb-4">
<svg class="w-4 h-4 mr-2 transform group-hover:-translate-x-1 transition-transform" 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>
Zurück zum Dokument
</a>
<h1 class="text-3xl font-serif text-brand-navy">
Bearbeiten — <span class="text-brand-navy/70">{doc.title || doc.originalFilename}</span>
</h1>
</div>
<form method="POST" enctype="multipart/form-data" use:enhance class="space-y-6">
<!-- Datei Austausch -->
<div class="bg-blue-50 p-4 rounded border border-blue-100">
<label for="file-upload" class="block text-sm font-medium text-gray-700 mb-2"
>Datei ersetzen (optional)</label
>
<div class="flex items-center gap-4">
<span class="text-xs text-gray-500">Aktuell: {doc.originalFilename}</span>
<!-- ID hinzugefügt -->
<input
id="file-upload"
type="file"
name="file"
class="block w-full text-sm text-gray-500
file:mr-4 file:py-2 file:px-4
file:rounded-full file:border-0
file:text-sm file:font-semibold
file:bg-blue-50 file:text-blue-700
hover:file:bg-blue-100"
/>
</div>
</div>
{#if form?.error}
<div class="bg-red-50 text-red-700 border border-red-200 p-4 rounded mb-6">{form.error}</div>
{/if}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Titel -->
<div>
<label for="title" class="block text-sm font-medium text-gray-700">Titel</label>
<!-- ID hinzugefügt -->
<input
id="title"
type="text"
name="title"
value={doc.title || ''}
required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
/>
</div>
<form method="POST" enctype="multipart/form-data" use:enhance class="space-y-6 pb-20">
<div>
<label for="documentLocation" class="block text-sm font-medium text-gray-700"
>Dokumentenort</label
>
<!-- ID hinzugefügt -->
<input
id="documentLocation"
type="text"
name="documentLocation"
value={doc.documentLocation || ''}
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
/>
</div>
<!-- ── Section 1: Wer & Wann ── -->
<div class="bg-white shadow-sm border border-brand-sand rounded-sm p-6">
<h2 class="text-xs font-bold uppercase tracking-widest text-gray-400 mb-5">Wer &amp; Wann</h2>
<!-- Datum -->
<div>
<label for="documentDate" class="block text-sm font-medium text-gray-700">Datum</label>
<!-- ID hinzugefügt -->
<input
id="documentDate"
type="text"
name="documentDate"
value={doc.documentDate || ''}
placeholder="YYYY-MM-DD"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
/>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
<!-- Ort -->
<div>
<label for="location" class="block text-sm font-medium text-gray-700">Ort</label>
<!-- ID hinzugefügt -->
<input
id="location"
type="text"
name="location"
value={doc.location || ''}
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
/>
</div>
</div>
<!-- Datum -->
<div>
<label for="documentDate" class="block text-sm font-medium text-gray-700 mb-1">Datum</label>
<input
id="documentDate"
type="date"
name="documentDate"
value={doc.documentDate || ''}
class="block w-full rounded border-gray-300 shadow-sm p-2 border focus:border-brand-navy focus:ring-brand-navy text-sm"
/>
</div>
<!-- Sender -->
<div>
<label for="senderId" class="block text-sm font-medium text-gray-700">Absender</label>
<!-- ID hinzugefügt -->
<select
id="senderId"
name="senderId"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
>
<option value="">-- Unbekannt --</option>
{#each persons as person}
<option value={person.id} selected={doc.sender?.id === person.id}>
{person.firstName}
{person.lastName}
</option>
{/each}
</select>
</div>
<!-- Ort -->
<div>
<label for="location" class="block text-sm font-medium text-gray-700 mb-1">Ort</label>
<input
id="location"
type="text"
name="location"
value={doc.location || ''}
placeholder="z.B. Berlin, Wien…"
class="block w-full rounded border-gray-300 shadow-sm p-2 border focus:border-brand-navy focus:ring-brand-navy text-sm"
/>
</div>
<!-- Empfänger (Multi-Select) -->
<div>
<label for="receiverIds" class="block text-sm font-medium text-gray-700"
>Empfänger (Strg+Klick für mehrere)</label
>
<!-- ID hinzugefügt -->
<select
id="receiverIds"
name="receiverIds"
multiple
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm h-32"
>
{#each persons as person}
<option value={person.id} selected={doc.receivers?.some((r) => r.id === person.id)}>
{person.firstName}
{person.lastName}
</option>
{/each}
</select>
</div>
<!-- Absender -->
<div>
<PersonTypeahead
name="senderId"
label="Absender"
bind:value={senderId}
initialName={doc.sender ? `${doc.sender.firstName} ${doc.sender.lastName}` : ''}
/>
</div>
<div>
<label class="block text-sm font-bold text-brand-navy uppercase tracking-widest mb-2">
Schlagworte
</label>
<TagInput bind:tags />
<input type="hidden" name="tags" value={tags.join(',')} />
</div>
<!-- Empfänger -->
<div>
<p class="block text-sm font-medium text-gray-700 mb-1">Empfänger</p>
<PersonMultiSelect bind:selectedPersons={selectedReceivers} />
</div>
<!-- Inhalt -->
<div>
<label for="summary" class="block text-sm font-medium text-gray-700">Inhalt</label>
<!-- ID hinzugefügt -->
<textarea
id="summary"
name="summary"
rows="2"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm font-serif"
>{doc.summary || ''}</textarea
>
</div>
</div>
</div>
<!-- Transkription -->
<div>
<label for="transcription" class="block text-sm font-medium text-gray-700"
>Transkription</label
>
<!-- ID hinzugefügt -->
<textarea
id="transcription"
name="transcription"
rows="10"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm font-serif"
>{doc.transcription || ''}</textarea
>
</div>
<!-- ── Section 2: Beschreibung ── -->
<div class="bg-white shadow-sm border border-brand-sand rounded-sm p-6">
<h2 class="text-xs font-bold uppercase tracking-widest text-gray-400 mb-5">Beschreibung</h2>
<!-- Buttons -->
<div class="flex justify-end gap-4">
<a
href="/documents/{doc.id}"
class="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50"
>
Abbrechen
</a>
<button
type="submit"
class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700"
>
Speichern
</button>
</div>
</form>
<div class="space-y-5">
<!-- Titel -->
<div>
<label for="title" class="block text-sm font-medium text-gray-700 mb-1">Titel *</label>
<input
id="title"
type="text"
name="title"
value={doc.title || ''}
required
class="block w-full rounded border-gray-300 shadow-sm p-2 border focus:border-brand-navy focus:ring-brand-navy text-sm"
/>
</div>
<!-- Aufbewahrungsort -->
<div>
<label for="documentLocation" class="block text-sm font-medium text-gray-700 mb-1">Aufbewahrungsort</label>
<input
id="documentLocation"
type="text"
name="documentLocation"
value={doc.documentLocation || ''}
placeholder="z.B. Schrank 3, Mappe B"
class="block w-full rounded border-gray-300 shadow-sm p-2 border focus:border-brand-navy focus:ring-brand-navy text-sm"
/>
<p class="mt-1 text-xs text-gray-400">Wo befindet sich das Originaldokument?</p>
</div>
<!-- Schlagworte -->
<div>
<p class="block text-sm font-medium text-gray-700 mb-1">Schlagworte</p>
<TagInput bind:tags />
<input type="hidden" name="tags" value={tags.join(',')} />
</div>
<!-- Inhalt -->
<div>
<label for="summary" class="block text-sm font-medium text-gray-700 mb-1">Inhalt</label>
<textarea
id="summary"
name="summary"
rows="5"
placeholder="Kurze Beschreibung des Inhalts…"
class="block w-full rounded border-gray-300 shadow-sm p-2 border focus:border-brand-navy focus:ring-brand-navy text-sm font-serif"
>{doc.summary || ''}</textarea>
</div>
</div>
</div>
<!-- ── Section 3: Transkription ── -->
<div class="bg-white shadow-sm border border-brand-sand rounded-sm p-6">
<h2 class="text-xs font-bold uppercase tracking-widest text-gray-400 mb-5">Transkription</h2>
<textarea
id="transcription"
name="transcription"
rows="12"
placeholder="Vollständiger Text des Dokuments…"
class="block w-full rounded border-gray-300 shadow-sm p-2 border focus:border-brand-navy focus:ring-brand-navy text-sm font-serif"
>{doc.transcription || ''}</textarea>
</div>
<!-- ── Section 4: Datei ── -->
<div class="bg-white shadow-sm border border-brand-sand rounded-sm p-6">
<h2 class="text-xs font-bold uppercase tracking-widest text-gray-400 mb-5">Datei</h2>
<div class="flex items-center gap-3 mb-4 text-sm text-gray-600 bg-brand-sand/20 rounded px-3 py-2">
<svg class="w-4 h-4 text-brand-navy flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
<span>Aktuelle Datei: <strong class="text-brand-navy font-medium">{doc.originalFilename}</strong></span>
</div>
<label for="file-upload" class="block text-sm font-medium text-gray-700 mb-1">
Neue Datei hochladen <span class="font-normal text-gray-400">(ersetzt die aktuelle Datei)</span>
</label>
<input
id="file-upload"
type="file"
name="file"
class="block w-full text-sm text-gray-500
file:mr-4 file:py-2 file:px-4
file:rounded file:border-0
file:text-sm file:font-semibold
file:bg-brand-sand/40 file:text-brand-navy
hover:file:bg-brand-sand/60 cursor-pointer"
/>
</div>
<!-- ── Sticky Save Bar ── -->
<div class="sticky bottom-0 z-10 bg-white border-t border-brand-sand shadow-[0_-2px_8px_rgba(0,0,0,0.06)] -mx-4 px-6 py-4 flex items-center justify-between">
<a
href="/documents/{doc.id}"
class="text-sm text-gray-500 hover:text-brand-navy transition-colors font-medium"
>
Abbrechen
</a>
<button
type="submit"
class="px-6 py-2 bg-brand-navy text-white text-sm font-bold uppercase tracking-widest rounded hover:bg-brand-navy/80 transition-colors"
>
Speichern
</button>
</div>
</form>
</div>