feat(frontend): wire OCR trigger + review toggle into transcription panel
Some checks failed
CI / Unit & Component Tests (push) Failing after 1s
CI / Backend Unit Tests (push) Failing after 1s
CI / Unit & Component Tests (pull_request) Failing after 1s
CI / Backend Unit Tests (pull_request) Failing after 1s

- OcrTrigger component rendered in the transcription empty state when
  the document has a file and user has write permission
- Review checkmark toggle on each TranscriptionBlock (turquoise when
  reviewed, muted outline when not). Calls PUT .../review to toggle.
- TranscriptionBlockData type: added source + reviewed fields
- +page.svelte: triggerOcr() and reviewToggle() functions wired up
- Paraglide translations (de/en/es) for review toggle + reviewed count

All 687 frontend tests pass.

Refs #226, #230

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-12 22:02:56 +02:00
parent 3aaec01421
commit 8dc9243add
7 changed files with 102 additions and 7 deletions

View File

@@ -2,6 +2,7 @@
import { m } from '$lib/paraglide/messages.js';
import { SvelteMap } from 'svelte/reactivity';
import TranscriptionBlock from './TranscriptionBlock.svelte';
import OcrTrigger from './OcrTrigger.svelte';
import type { TranscriptionBlockData } from '$lib/types';
type SaveState = 'idle' | 'saving' | 'saved' | 'fading' | 'error';
@@ -12,9 +13,13 @@ type Props = {
canComment: boolean;
currentUserId: string | null;
activeAnnotationId?: string | null;
storedScriptType?: string;
canRunOcr?: boolean;
onBlockFocus: (blockId: string) => void;
onSaveBlock: (blockId: string, text: string) => Promise<void>;
onDeleteBlock: (blockId: string) => Promise<void>;
onReviewToggle: (blockId: string) => Promise<void>;
onTriggerOcr?: (scriptType: string) => void;
};
let {
@@ -23,9 +28,13 @@ let {
canComment,
currentUserId,
activeAnnotationId = null,
storedScriptType = '',
canRunOcr = false,
onBlockFocus,
onSaveBlock,
onDeleteBlock
onDeleteBlock,
onReviewToggle,
onTriggerOcr
}: Props = $props();
let activeBlockId: string | null = $state(null);
@@ -282,6 +291,7 @@ $effect(() => {
text={block.text}
label={block.label}
active={activeBlockId === block.id}
reviewed={block.reviewed ?? false}
saveState={getSaveState(block.id)}
canComment={canComment}
currentUserId={currentUserId}
@@ -289,6 +299,7 @@ $effect(() => {
onFocus={() => handleFocus(block.id)}
onDeleteClick={() => handleDelete(block.id)}
onRetry={() => handleRetry(block.id)}
onReviewToggle={() => onReviewToggle(block.id)}
onMoveUp={() => handleMoveUp(block.id)}
onMoveDown={() => handleMoveDown(block.id)}
isFirst={i === 0}
@@ -323,9 +334,26 @@ $effect(() => {
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
/>
</svg>
<p class="max-w-xs text-sm leading-relaxed text-ink-3">
{m.transcription_empty_cta()}
</p>
{#if canRunOcr && onTriggerOcr}
<p class="mb-6 max-w-xs text-sm leading-relaxed text-ink-3">
{m.transcription_empty_title()}
</p>
<div class="w-full max-w-xs">
<OcrTrigger
existingBlockCount={0}
storedScriptType={storedScriptType}
onTrigger={onTriggerOcr}
/>
</div>
<p class="mt-4 text-xs text-ink-3">
{m.transcription_empty_desc()}
</p>
{:else}
<p class="max-w-xs text-sm leading-relaxed text-ink-3">
{m.transcription_empty_cta()}
</p>
{/if}
</div>
{/if}
</div>