feat(bulk-edit): extend BulkDocumentEditLayout with mode="edit"
- New FieldLabelBadge component (additive / replace variants, WCAG AA contrast) - WhoWhenSection: hideDate prop, editMode prop renders badges next to sender and receivers, hides the meta_location field - DescriptionSection: editMode prop renders badges next to tags and archive fields; new bindable archiveBox / archiveFolder inputs only in editMode - PersonTypeahead: optional badge prop forwards to FieldLabelBadge - FileSwitcherStrip FileEntry: file is now optional, documentId added so edit-mode entries reference an existing document by UUID - BulkDocumentEditLayout: mode prop branches drop zone / read-only title / callout / save handler. Edit save chunks 500 IDs per PATCH, stops on chunk failure with retry, marks per-document errors as chips, clears the bulk selection store on full success. Refs #225 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,31 +1,45 @@
|
||||
<script lang="ts">
|
||||
import { untrack } from 'svelte';
|
||||
import TagInput, { type Tag } from '$lib/components/TagInput.svelte';
|
||||
import FieldLabelBadge from './FieldLabelBadge.svelte';
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
|
||||
let {
|
||||
tags = $bindable<Tag[]>([]),
|
||||
currentTitle = $bindable(''),
|
||||
documentLocation = $bindable(''),
|
||||
archiveBox = $bindable(''),
|
||||
archiveFolder = $bindable(''),
|
||||
initialTitle = '',
|
||||
initialDocumentLocation = '',
|
||||
initialSummary = '',
|
||||
titleRequired = false,
|
||||
suggestedTitle = '',
|
||||
hideTitle = false
|
||||
hideTitle = false,
|
||||
editMode = false
|
||||
}: {
|
||||
tags?: Tag[];
|
||||
currentTitle?: string;
|
||||
documentLocation?: string;
|
||||
archiveBox?: string;
|
||||
archiveFolder?: string;
|
||||
initialTitle?: string;
|
||||
initialDocumentLocation?: string;
|
||||
initialSummary?: string;
|
||||
titleRequired?: boolean;
|
||||
suggestedTitle?: string;
|
||||
hideTitle?: boolean;
|
||||
editMode?: boolean;
|
||||
} = $props();
|
||||
|
||||
let titleDirty = $state(false);
|
||||
currentTitle = untrack(() => initialTitle);
|
||||
const titleValue = $derived(titleDirty ? currentTitle : suggestedTitle || currentTitle);
|
||||
|
||||
// Initialize controlled location field once from the legacy initial-* props so
|
||||
// callers that haven't switched to the bindable form keep their existing
|
||||
// pre-fill behaviour.
|
||||
documentLocation = untrack(() => documentLocation || initialDocumentLocation);
|
||||
</script>
|
||||
|
||||
<div class="rounded-sm border border-line bg-surface p-6 shadow-sm">
|
||||
@@ -67,40 +81,78 @@ const titleValue = $derived(titleDirty ? currentTitle : suggestedTitle || curren
|
||||
|
||||
<!-- Schlagworte (optional) -->
|
||||
<div>
|
||||
<p class="mb-1 block text-sm font-medium text-ink-2">{m.form_label_tags()}</p>
|
||||
<p class="mb-1 block text-sm font-medium text-ink-2">
|
||||
{m.form_label_tags()}
|
||||
{#if editMode}<FieldLabelBadge variant="additive" />{/if}
|
||||
</p>
|
||||
<TagInput bind:tags={tags} />
|
||||
<input type="hidden" name="tags" value={tags.map((t) => t.name).join(',')} />
|
||||
</div>
|
||||
|
||||
<!-- Inhalt (optional) -->
|
||||
<div>
|
||||
<label for="summary" class="mb-1 block text-sm font-medium text-ink-2"
|
||||
>{m.form_label_content()}</label
|
||||
>
|
||||
<textarea
|
||||
id="summary"
|
||||
name="summary"
|
||||
rows="5"
|
||||
placeholder={m.form_placeholder_content()}
|
||||
class="block w-full rounded border border-line p-2 font-serif text-sm shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
|
||||
>{initialSummary}</textarea
|
||||
>
|
||||
</div>
|
||||
{#if !editMode}
|
||||
<!-- Inhalt (optional) — not bulk-editable. -->
|
||||
<div>
|
||||
<label for="summary" class="mb-1 block text-sm font-medium text-ink-2"
|
||||
>{m.form_label_content()}</label
|
||||
>
|
||||
<textarea
|
||||
id="summary"
|
||||
name="summary"
|
||||
rows="5"
|
||||
placeholder={m.form_placeholder_content()}
|
||||
class="block w-full rounded border border-line p-2 font-serif text-sm shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
|
||||
>{initialSummary}</textarea
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Aufbewahrungsort (optional) -->
|
||||
<div>
|
||||
<div data-testid="description-document-location">
|
||||
<label for="documentLocation" class="mb-1 block text-sm font-medium text-ink-2"
|
||||
>{m.form_label_archive_location()}</label
|
||||
>
|
||||
>{m.form_label_archive_location()}
|
||||
{#if editMode}<FieldLabelBadge variant="replace" />{/if}
|
||||
</label>
|
||||
<input
|
||||
id="documentLocation"
|
||||
type="text"
|
||||
name="documentLocation"
|
||||
value={initialDocumentLocation}
|
||||
bind:value={documentLocation}
|
||||
placeholder={m.form_placeholder_archive_location()}
|
||||
class="block w-full rounded border border-line p-2 text-sm shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-ink-3">{m.form_helper_archive_location()}</p>
|
||||
</div>
|
||||
|
||||
{#if editMode}
|
||||
<!-- Karton (only in editMode — bulk-editable replace) -->
|
||||
<div data-testid="description-archive-box">
|
||||
<label for="archiveBox" class="mb-1 block text-sm font-medium text-ink-2"
|
||||
>Karton
|
||||
<FieldLabelBadge variant="replace" />
|
||||
</label>
|
||||
<input
|
||||
id="archiveBox"
|
||||
type="text"
|
||||
name="archiveBox"
|
||||
bind:value={archiveBox}
|
||||
class="block w-full rounded border border-line p-2 text-sm shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Mappe (only in editMode — bulk-editable replace) -->
|
||||
<div data-testid="description-archive-folder">
|
||||
<label for="archiveFolder" class="mb-1 block text-sm font-medium text-ink-2"
|
||||
>Mappe
|
||||
<FieldLabelBadge variant="replace" />
|
||||
</label>
|
||||
<input
|
||||
id="archiveFolder"
|
||||
type="text"
|
||||
name="archiveFolder"
|
||||
bind:value={archiveFolder}
|
||||
class="block w-full rounded border border-line p-2 text-sm shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user