feat(transcription): add sticky review progress counter to TranscriptionEditView
Shows 'X / Y geprüft' with a brand-mint progress bar at the top of the transcription panel. Derived from the blocks prop — no extra state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,9 @@ let debounceTimers = new SvelteMap<string, ReturnType<typeof setTimeout>>();
|
|||||||
let pendingTexts = new SvelteMap<string, string>();
|
let pendingTexts = new SvelteMap<string, string>();
|
||||||
let sortedBlocks = $derived([...blocks].sort((a, b) => a.sortOrder - b.sortOrder));
|
let sortedBlocks = $derived([...blocks].sort((a, b) => a.sortOrder - b.sortOrder));
|
||||||
let hasBlocks = $derived(blocks.length > 0);
|
let hasBlocks = $derived(blocks.length > 0);
|
||||||
|
let reviewedCount = $derived(blocks.filter((b) => b.reviewed).length);
|
||||||
|
let totalCount = $derived(blocks.length);
|
||||||
|
let reviewProgress = $derived(totalCount > 0 ? (reviewedCount / totalCount) * 100 : 0);
|
||||||
|
|
||||||
function getSaveState(blockId: string): SaveState {
|
function getSaveState(blockId: string): SaveState {
|
||||||
return saveStates.get(blockId) ?? 'idle';
|
return saveStates.get(blockId) ?? 'idle';
|
||||||
@@ -263,8 +266,21 @@ $effect(() => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-full flex-col overflow-y-auto bg-surface p-4">
|
<div class="flex h-full flex-col overflow-y-auto bg-surface">
|
||||||
{#if hasBlocks}
|
{#if hasBlocks}
|
||||||
|
<!-- Sticky review progress header -->
|
||||||
|
<div class="sticky top-0 z-10 border-b border-line bg-surface px-4 pt-3 pb-2">
|
||||||
|
<p class="font-sans text-xs text-ink-2">
|
||||||
|
<span class="font-semibold text-ink">{reviewedCount} / {totalCount}</span> geprüft
|
||||||
|
</p>
|
||||||
|
<div class="bg-brand-sand mt-1.5 h-0.5 w-full overflow-hidden rounded-full">
|
||||||
|
<div
|
||||||
|
class="h-full rounded-full bg-brand-mint transition-all duration-300"
|
||||||
|
style="width: {reviewProgress}%"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-4">
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div
|
<div
|
||||||
class="flex flex-col gap-3"
|
class="flex flex-col gap-3"
|
||||||
@@ -336,6 +352,7 @@ $effect(() => {
|
|||||||
</details>
|
</details>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-1 flex-col items-center justify-center px-6 py-12 text-center">
|
<div class="flex flex-1 flex-col items-center justify-center px-6 py-12 text-center">
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ const block1 = {
|
|||||||
text: 'Block eins',
|
text: 'Block eins',
|
||||||
label: null,
|
label: null,
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
version: 0
|
version: 0,
|
||||||
|
source: 'MANUAL' as const,
|
||||||
|
reviewed: false
|
||||||
};
|
};
|
||||||
const block2 = {
|
const block2 = {
|
||||||
id: 'b2',
|
id: 'b2',
|
||||||
@@ -22,7 +24,9 @@ const block2 = {
|
|||||||
text: 'Block zwei',
|
text: 'Block zwei',
|
||||||
label: null,
|
label: null,
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
version: 0
|
version: 0,
|
||||||
|
source: 'OCR' as const,
|
||||||
|
reviewed: true
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderView(overrides: Record<string, unknown> = {}, service = createConfirmService()) {
|
function renderView(overrides: Record<string, unknown> = {}, service = createConfirmService()) {
|
||||||
@@ -232,3 +236,23 @@ describe('TranscriptionEditView — delete block', () => {
|
|||||||
expect(onDeleteBlock).not.toHaveBeenCalled();
|
expect(onDeleteBlock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─── Review progress counter ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('TranscriptionEditView — review progress counter', () => {
|
||||||
|
it('shows reviewed count and total when blocks exist', async () => {
|
||||||
|
// block1: reviewed=false, block2: reviewed=true → "1 / 2 geprüft"
|
||||||
|
renderView();
|
||||||
|
await expect.element(page.getByText(/1 \/ 2 geprüft/)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows 0 reviewed when no blocks are reviewed', async () => {
|
||||||
|
renderView({ blocks: [block1] }); // block1.reviewed = false
|
||||||
|
await expect.element(page.getByText(/0 \/ 1 geprüft/)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not show progress counter when there are no blocks', async () => {
|
||||||
|
renderView({ blocks: [] });
|
||||||
|
await expect.element(page.getByText(/geprüft/)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user