refactor(#240): deduplicate formatDate, use generated types, always-visible strip
- Add formatMCDate() to $lib/utils/date.ts (locale-aware, medium format);
remove duplicated inline formatDate() from all three column components
- Replace local TranscriptionQueueItemDTO/TranscriptionWeeklyStatsDTO type
declarations with imports from $lib/generated/api across all four components
- Add dashed empty states to SegmentationColumn and TranscriptionColumn
(ReadyColumn already had one)
- Remove outer {#if} from MissionControlStrip so the section is always
visible — each column owns its own empty state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,23 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as m from '$lib/paraglide/messages.js';
|
import * as m from '$lib/paraglide/messages.js';
|
||||||
|
import type { components } from '$lib/generated/api';
|
||||||
import SegmentationColumn from './SegmentationColumn.svelte';
|
import SegmentationColumn from './SegmentationColumn.svelte';
|
||||||
import TranscriptionColumn from './TranscriptionColumn.svelte';
|
import TranscriptionColumn from './TranscriptionColumn.svelte';
|
||||||
import ReadyColumn from './ReadyColumn.svelte';
|
import ReadyColumn from './ReadyColumn.svelte';
|
||||||
|
|
||||||
type TranscriptionQueueItemDTO = {
|
type TranscriptionQueueItemDTO = components['schemas']['TranscriptionQueueItemDTO'];
|
||||||
id: string;
|
type TranscriptionWeeklyStatsDTO = components['schemas']['TranscriptionWeeklyStatsDTO'];
|
||||||
title: string;
|
|
||||||
documentDate?: string;
|
|
||||||
annotationCount: number;
|
|
||||||
textedBlockCount: number;
|
|
||||||
reviewedBlockCount: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TranscriptionWeeklyStatsDTO = {
|
|
||||||
segmentationCount: number;
|
|
||||||
transcriptionCount: number;
|
|
||||||
readyCount: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
segmentationDocs: TranscriptionQueueItemDTO[];
|
segmentationDocs: TranscriptionQueueItemDTO[];
|
||||||
@@ -29,21 +18,16 @@ interface Props {
|
|||||||
let { segmentationDocs, transcriptionDocs, readyDocs, weeklyStats }: Props = $props();
|
let { segmentationDocs, transcriptionDocs, readyDocs, weeklyStats }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if segmentationDocs.length > 0 || transcriptionDocs.length > 0 || readyDocs.length > 0}
|
<section class="mt-4 rounded-sm border border-line bg-surface p-6">
|
||||||
<section class="mt-4 rounded-sm border border-line bg-surface p-6">
|
<h2 class="mb-4 font-sans text-xs font-bold tracking-widest text-ink-3 uppercase">
|
||||||
<h2 class="mb-4 font-sans text-xs font-bold tracking-widest text-ink-3 uppercase">
|
{m.mission_control_heading()}
|
||||||
{m.mission_control_heading()}
|
</h2>
|
||||||
</h2>
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
<SegmentationColumn docs={segmentationDocs} weeklyCount={weeklyStats?.segmentationCount ?? 0} />
|
||||||
<SegmentationColumn
|
<TranscriptionColumn
|
||||||
docs={segmentationDocs}
|
docs={transcriptionDocs}
|
||||||
weeklyCount={weeklyStats?.segmentationCount ?? 0}
|
weeklyCount={weeklyStats?.transcriptionCount ?? 0}
|
||||||
/>
|
/>
|
||||||
<TranscriptionColumn
|
<ReadyColumn docs={readyDocs} weeklyCount={weeklyStats?.readyCount ?? 0} />
|
||||||
docs={transcriptionDocs}
|
</div>
|
||||||
weeklyCount={weeklyStats?.transcriptionCount ?? 0}
|
</section>
|
||||||
/>
|
|
||||||
<ReadyColumn docs={readyDocs} weeklyCount={weeklyStats?.readyCount ?? 0} />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{/if}
|
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as m from '$lib/paraglide/messages.js';
|
import * as m from '$lib/paraglide/messages.js';
|
||||||
import { getLocale } from '$lib/paraglide/runtime.js';
|
import { getLocale } from '$lib/paraglide/runtime.js';
|
||||||
|
import { formatMCDate } from '$lib/utils/date.js';
|
||||||
|
import type { components } from '$lib/generated/api';
|
||||||
|
|
||||||
type TranscriptionQueueItemDTO = {
|
type TranscriptionQueueItemDTO = components['schemas']['TranscriptionQueueItemDTO'];
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
documentDate?: string;
|
|
||||||
annotationCount: number;
|
|
||||||
textedBlockCount: number;
|
|
||||||
reviewedBlockCount: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
docs: TranscriptionQueueItemDTO[];
|
docs: TranscriptionQueueItemDTO[];
|
||||||
@@ -18,14 +13,6 @@ interface Props {
|
|||||||
|
|
||||||
let { docs, weeklyCount }: Props = $props();
|
let { docs, weeklyCount }: Props = $props();
|
||||||
|
|
||||||
function formatDate(dateStr: string): string {
|
|
||||||
return new Intl.DateTimeFormat(getLocale(), {
|
|
||||||
day: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
year: 'numeric'
|
|
||||||
}).format(new Date(dateStr + 'T12:00:00'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function reviewedPct(doc: TranscriptionQueueItemDTO): number {
|
function reviewedPct(doc: TranscriptionQueueItemDTO): number {
|
||||||
if (doc.annotationCount === 0) return 0;
|
if (doc.annotationCount === 0) return 0;
|
||||||
return Math.round((doc.reviewedBlockCount / doc.annotationCount) * 100);
|
return Math.round((doc.reviewedBlockCount / doc.annotationCount) * 100);
|
||||||
@@ -61,7 +48,8 @@ function reviewedPct(doc: TranscriptionQueueItemDTO): number {
|
|||||||
<span class="font-serif text-sm text-ink">{doc.title}</span>
|
<span class="font-serif text-sm text-ink">{doc.title}</span>
|
||||||
<div class="mt-0.5 flex items-center gap-2">
|
<div class="mt-0.5 flex items-center gap-2">
|
||||||
{#if doc.documentDate}
|
{#if doc.documentDate}
|
||||||
<span class="text-xs text-ink-3">{formatDate(doc.documentDate)}</span>
|
<span class="text-xs text-ink-3">{formatMCDate(doc.documentDate, getLocale())}</span
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{#if doc.textedBlockCount > 0}
|
{#if doc.textedBlockCount > 0}
|
||||||
<span class="text-xs font-semibold text-ink">
|
<span class="text-xs font-semibold text-ink">
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as m from '$lib/paraglide/messages.js';
|
import * as m from '$lib/paraglide/messages.js';
|
||||||
import { getLocale } from '$lib/paraglide/runtime.js';
|
import { getLocale } from '$lib/paraglide/runtime.js';
|
||||||
|
import { formatMCDate } from '$lib/utils/date.js';
|
||||||
|
import type { components } from '$lib/generated/api';
|
||||||
|
|
||||||
type TranscriptionQueueItemDTO = {
|
type TranscriptionQueueItemDTO = components['schemas']['TranscriptionQueueItemDTO'];
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
documentDate?: string;
|
|
||||||
annotationCount: number;
|
|
||||||
textedBlockCount: number;
|
|
||||||
reviewedBlockCount: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
docs: TranscriptionQueueItemDTO[];
|
docs: TranscriptionQueueItemDTO[];
|
||||||
@@ -17,14 +12,6 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let { docs, weeklyCount }: Props = $props();
|
let { docs, weeklyCount }: Props = $props();
|
||||||
|
|
||||||
function formatDate(dateStr: string): string {
|
|
||||||
return new Intl.DateTimeFormat(getLocale(), {
|
|
||||||
day: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
year: 'numeric'
|
|
||||||
}).format(new Date(dateStr + 'T12:00:00'));
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if docs.length > 0}
|
{#if docs.length > 0}
|
||||||
@@ -53,11 +40,19 @@ function formatDate(dateStr: string): string {
|
|||||||
>
|
>
|
||||||
<span class="font-serif text-sm text-ink">{doc.title}</span>
|
<span class="font-serif text-sm text-ink">{doc.title}</span>
|
||||||
{#if doc.documentDate}
|
{#if doc.documentDate}
|
||||||
<span class="mt-0.5 text-xs text-ink-3">{formatDate(doc.documentDate)}</span>
|
<span class="mt-0.5 text-xs text-ink-3"
|
||||||
|
>{formatMCDate(doc.documentDate, getLocale())}</span
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div
|
||||||
|
class="flex min-h-[120px] flex-col items-center justify-center rounded-sm border border-dashed border-line bg-surface/50 p-6 text-center"
|
||||||
|
>
|
||||||
|
<p class="text-xs text-ink-3">{m.mission_control_segmentation_empty()}</p>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as m from '$lib/paraglide/messages.js';
|
import * as m from '$lib/paraglide/messages.js';
|
||||||
import { getLocale } from '$lib/paraglide/runtime.js';
|
import { getLocale } from '$lib/paraglide/runtime.js';
|
||||||
|
import { formatMCDate } from '$lib/utils/date.js';
|
||||||
|
import type { components } from '$lib/generated/api';
|
||||||
|
|
||||||
type TranscriptionQueueItemDTO = {
|
type TranscriptionQueueItemDTO = components['schemas']['TranscriptionQueueItemDTO'];
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
documentDate?: string;
|
|
||||||
annotationCount: number;
|
|
||||||
textedBlockCount: number;
|
|
||||||
reviewedBlockCount: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
docs: TranscriptionQueueItemDTO[];
|
docs: TranscriptionQueueItemDTO[];
|
||||||
@@ -18,14 +13,6 @@ interface Props {
|
|||||||
|
|
||||||
let { docs, weeklyCount }: Props = $props();
|
let { docs, weeklyCount }: Props = $props();
|
||||||
|
|
||||||
function formatDate(dateStr: string): string {
|
|
||||||
return new Intl.DateTimeFormat(getLocale(), {
|
|
||||||
day: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
year: 'numeric'
|
|
||||||
}).format(new Date(dateStr + 'T12:00:00'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function blockProgress(doc: TranscriptionQueueItemDTO): number {
|
function blockProgress(doc: TranscriptionQueueItemDTO): number {
|
||||||
if (doc.annotationCount === 0) return 0;
|
if (doc.annotationCount === 0) return 0;
|
||||||
return (doc.textedBlockCount / doc.annotationCount) * 100;
|
return (doc.textedBlockCount / doc.annotationCount) * 100;
|
||||||
@@ -58,7 +45,9 @@ function blockProgress(doc: TranscriptionQueueItemDTO): number {
|
|||||||
>
|
>
|
||||||
<span class="font-serif text-sm text-ink">{doc.title}</span>
|
<span class="font-serif text-sm text-ink">{doc.title}</span>
|
||||||
{#if doc.documentDate}
|
{#if doc.documentDate}
|
||||||
<span class="mt-0.5 text-xs text-ink-3">{formatDate(doc.documentDate)}</span>
|
<span class="mt-0.5 text-xs text-ink-3"
|
||||||
|
>{formatMCDate(doc.documentDate, getLocale())}</span
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{#if doc.textedBlockCount > 0}
|
{#if doc.textedBlockCount > 0}
|
||||||
<div class="mt-1.5 flex items-center gap-2">
|
<div class="mt-1.5 flex items-center gap-2">
|
||||||
@@ -83,4 +72,10 @@ function blockProgress(doc: TranscriptionQueueItemDTO): number {
|
|||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div
|
||||||
|
class="flex min-h-[120px] flex-col items-center justify-center rounded-sm border border-dashed border-line bg-surface/50 p-6 text-center"
|
||||||
|
>
|
||||||
|
<p class="text-xs text-ink-3">{m.mission_control_transcription_empty()}</p>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -19,6 +19,19 @@ export function formatDate(isoDate: string, format: 'short' | 'long' = 'long'):
|
|||||||
}).format(date);
|
}).format(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format an ISO date string for medium-length display (e.g. "15. Jun. 1920").
|
||||||
|
* Uses T12:00:00 to avoid UTC timezone off-by-one.
|
||||||
|
* Pass an explicit BCP 47 locale tag to respect the app locale; defaults to 'de-DE'.
|
||||||
|
*/
|
||||||
|
export function formatMCDate(isoDate: string, locale: string = 'de-DE'): string {
|
||||||
|
return new Intl.DateTimeFormat(locale, {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
year: 'numeric'
|
||||||
|
}).format(new Date(isoDate + 'T12:00:00'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an ISO date string (YYYY-MM-DD) to German display format (DD.MM.YYYY).
|
* Converts an ISO date string (YYYY-MM-DD) to German display format (DD.MM.YYYY).
|
||||||
* Returns an empty string for invalid or empty input.
|
* Returns an empty string for invalid or empty input.
|
||||||
|
|||||||
Reference in New Issue
Block a user