Elicit C1+C3 — bulk-selection count uses ICU-style plural keys
(bulk_edit_n_selected_one / _other) so n=1 reads as "1 Dokument" instead
of "1 Dokumente". Save CTA in edit mode reads "Anwenden" via the existing
bulk_edit_save_button key; UploadSaveBar grew an editMode prop. Multi-
chunk progress text is now visible (not aria-only).
Felix C2 — bulk-edit page wires the backend error code through
parseBackendError + getErrorMessage instead of falling back to a generic
internal_error.
Felix C5 — editAllMatching no longer swallows fetch failures: the button
shows an inline error with the backend-mapped message (e.g. when the
filter cap is exceeded).
Leonie C8 — replace the literal "…" loading glyph on /documents/bulk-edit
with a spinner + role=status + aria-live=polite + visible "Loading
documents…" text.
Leonie C9 — partial-failure card and bulk-edit page error card now use
the design-system `text-danger` / `bg-danger/10` / `border-danger/40`
tokens (dark-mode safe) instead of raw red palette values.
Leonie C10 + C13 — German plural fixed; EN badges retensed
("+ added" → "+ will be added", "replaced" → "will replace") to match
the future-tense intent of DE/ES.
Refs #225, PR #331
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
85 lines
2.3 KiB
Svelte
85 lines
2.3 KiB
Svelte
<script lang="ts">
|
||
import { onMount } from 'svelte';
|
||
import { goto } from '$app/navigation';
|
||
import { bulkSelectionStore } from '$lib/stores/bulkSelection.svelte';
|
||
import BulkDocumentEditLayout, {
|
||
type BulkEditEntry
|
||
} from '$lib/components/document/BulkDocumentEditLayout.svelte';
|
||
import { getErrorMessage, parseBackendError } from '$lib/errors';
|
||
import { m } from '$lib/paraglide/messages.js';
|
||
|
||
let entries = $state<BulkEditEntry[]>([]);
|
||
let loading = $state(true);
|
||
let error = $state<string | null>(null);
|
||
|
||
onMount(async () => {
|
||
const ids = Array.from(bulkSelectionStore.ids);
|
||
if (ids.length === 0) {
|
||
// Skip the loading flash on the empty-store redirect path — the user
|
||
// is bouncing back to /documents in the next tick.
|
||
loading = false;
|
||
await goto('/documents');
|
||
return;
|
||
}
|
||
try {
|
||
const res = await fetch('/api/documents/batch-metadata', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ ids })
|
||
});
|
||
if (!res.ok) {
|
||
const backend = await parseBackendError(res);
|
||
error = getErrorMessage(backend?.code);
|
||
loading = false;
|
||
return;
|
||
}
|
||
const summaries = (await res.json()) as BulkEditEntry[];
|
||
entries = summaries;
|
||
} catch {
|
||
error = getErrorMessage(undefined);
|
||
} finally {
|
||
loading = false;
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<svelte:head>
|
||
<title>{m.bulk_edit_title()} – Familienarchiv</title>
|
||
</svelte:head>
|
||
|
||
{#if loading}
|
||
<div
|
||
class="flex h-full items-center justify-center gap-3 p-12 text-sm text-ink-2"
|
||
role="status"
|
||
aria-live="polite"
|
||
data-testid="bulk-edit-loading"
|
||
>
|
||
<svg
|
||
class="h-5 w-5 animate-spin text-ink-3"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
fill="none"
|
||
viewBox="0 0 24 24"
|
||
aria-hidden="true"
|
||
>
|
||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"
|
||
></circle>
|
||
<path
|
||
class="opacity-75"
|
||
fill="currentColor"
|
||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||
></path>
|
||
</svg>
|
||
<span>{m.bulk_edit_loading()}</span>
|
||
</div>
|
||
{:else if error}
|
||
<div
|
||
role="alert"
|
||
class="m-6 rounded-sm border border-danger/40 bg-danger/10 p-4 text-sm text-danger"
|
||
data-testid="bulk-edit-page-error"
|
||
>
|
||
{error}
|
||
</div>
|
||
{:else if entries.length > 0}
|
||
<BulkDocumentEditLayout mode="edit" initialEditEntries={entries} />
|
||
{/if}
|