feat(#68): lead new document form with file upload, all metadata optional
Restructure the "New Document" page so users can save quickly: - FileSectionNew becomes the first element, redesigned as a prominent upload zone with an icon and large click target - Title field is rendered standalone below the upload zone; it auto-populates from the filename (via parseFilename + stripExtension fallback) unless the user has already typed something - All remaining metadata (who/when, description, transcription) moves into a collapsible "Weitere Details" section that auto-expands when URL prefill data or a form error is present, or when filename parsing detects a date/person - title is no longer required — the form can be saved with only a file - DescriptionSection gains a `hideTitle` prop for use in this layout - `form_label_title` translation key no longer carries a hardcoded `*`; the asterisk is rendered by the template only when `titleRequired` is set (currently only the edit form) - E2E tests added for all three scenarios from the issue Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,30 @@ let selectedReceivers: { id: string; firstName: string; lastName: string }[] = $
|
||||
);
|
||||
|
||||
let parsedSuggestion = $state<FilenameParseResult>({});
|
||||
|
||||
// Title is derived from the filename suggestion unless the user has typed something
|
||||
let titleDirty = $state(false);
|
||||
let titleOverride = $state('');
|
||||
let titleValue = $derived(
|
||||
titleDirty ? titleOverride : (parsedSuggestion.suggestedTitle ?? titleOverride)
|
||||
);
|
||||
|
||||
// Details panel: starts open when prefill data is present or a form error occurred.
|
||||
// Auto-opens when filename parsing finds a date/sender, but never force-closes — user
|
||||
// can always collapse the section manually.
|
||||
let detailsOpen = $state(
|
||||
!!(
|
||||
untrack(() => data.initialSenderId) ||
|
||||
untrack(() => data.initialReceivers).length > 0 ||
|
||||
untrack(() => form)?.error
|
||||
)
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (parsedSuggestion.dateIso || senderId || selectedReceivers.length > 0) {
|
||||
detailsOpen = true;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-4xl px-4 py-8">
|
||||
@@ -49,21 +73,51 @@ let parsedSuggestion = $state<FilenameParseResult>({});
|
||||
{/if}
|
||||
|
||||
<form method="POST" enctype="multipart/form-data" use:enhance class="space-y-6 pb-20">
|
||||
<WhoWhenSection
|
||||
bind:senderId={senderId}
|
||||
bind:selectedReceivers={selectedReceivers}
|
||||
initialSenderName={data.initialSenderName}
|
||||
suggestedDateIso={parsedSuggestion.dateIso ?? ''}
|
||||
suggestedSenderName={parsedSuggestion.personName ?? ''}
|
||||
/>
|
||||
<DescriptionSection
|
||||
bind:tags={tags}
|
||||
titleRequired={true}
|
||||
suggestedTitle={parsedSuggestion.suggestedTitle ?? ''}
|
||||
/>
|
||||
<TranscriptionSection />
|
||||
<!-- File upload — prominent, at the top -->
|
||||
<FileSectionNew onfileParsed={(r) => (parsedSuggestion = r)} />
|
||||
|
||||
<!-- Standalone title card -->
|
||||
<div class="rounded-sm border border-line bg-surface p-6 shadow-sm">
|
||||
<label for="new-title" class="mb-1 block text-sm font-medium text-ink-2"
|
||||
>{m.form_label_title()}</label
|
||||
>
|
||||
<input
|
||||
id="new-title"
|
||||
type="text"
|
||||
name="title"
|
||||
value={titleValue}
|
||||
oninput={(e) => {
|
||||
titleOverride = (e.target as HTMLInputElement).value;
|
||||
titleDirty = true;
|
||||
}}
|
||||
class="block w-full rounded border border-line p-2 text-sm shadow-sm focus:border-ink focus:ring-ink"
|
||||
placeholder="Titel eingeben…"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Collapsible further details -->
|
||||
<details
|
||||
bind:open={detailsOpen}
|
||||
class="group rounded-sm border border-line bg-surface shadow-sm"
|
||||
>
|
||||
<summary class="cursor-pointer list-none px-6 py-4">
|
||||
<span class="text-xs font-bold tracking-widest text-ink-3 uppercase"
|
||||
>{m.doc_more_details()}</span
|
||||
>
|
||||
</summary>
|
||||
<div class="space-y-6 px-0 pb-6">
|
||||
<WhoWhenSection
|
||||
bind:senderId={senderId}
|
||||
bind:selectedReceivers={selectedReceivers}
|
||||
initialSenderName={data.initialSenderName}
|
||||
suggestedDateIso={parsedSuggestion.dateIso ?? ''}
|
||||
suggestedSenderName={parsedSuggestion.personName ?? ''}
|
||||
/>
|
||||
<DescriptionSection bind:tags={tags} hideTitle={true} />
|
||||
<TranscriptionSection />
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<!-- Sticky Save Bar -->
|
||||
<div
|
||||
class="sticky bottom-0 z-10 -mx-4 flex items-center justify-between border-t border-line bg-surface px-6 py-4 shadow-[0_-2px_8px_rgba(0,0,0,0.06)]"
|
||||
|
||||
Reference in New Issue
Block a user