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:
@@ -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 & 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>
|
||||
|
||||
Reference in New Issue
Block a user