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">
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
import type { components } from '$lib/generated/api';
|
||||
import SegmentationColumn from './SegmentationColumn.svelte';
|
||||
import TranscriptionColumn from './TranscriptionColumn.svelte';
|
||||
import ReadyColumn from './ReadyColumn.svelte';
|
||||
|
||||
type TranscriptionQueueItemDTO = {
|
||||
id: string;
|
||||
title: string;
|
||||
documentDate?: string;
|
||||
annotationCount: number;
|
||||
textedBlockCount: number;
|
||||
reviewedBlockCount: number;
|
||||
};
|
||||
|
||||
type TranscriptionWeeklyStatsDTO = {
|
||||
segmentationCount: number;
|
||||
transcriptionCount: number;
|
||||
readyCount: number;
|
||||
};
|
||||
type TranscriptionQueueItemDTO = components['schemas']['TranscriptionQueueItemDTO'];
|
||||
type TranscriptionWeeklyStatsDTO = components['schemas']['TranscriptionWeeklyStatsDTO'];
|
||||
|
||||
interface Props {
|
||||
segmentationDocs: TranscriptionQueueItemDTO[];
|
||||
@@ -29,21 +18,16 @@ interface Props {
|
||||
let { segmentationDocs, transcriptionDocs, readyDocs, weeklyStats }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if segmentationDocs.length > 0 || transcriptionDocs.length > 0 || readyDocs.length > 0}
|
||||
<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">
|
||||
{m.mission_control_heading()}
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<SegmentationColumn
|
||||
docs={segmentationDocs}
|
||||
weeklyCount={weeklyStats?.segmentationCount ?? 0}
|
||||
/>
|
||||
<TranscriptionColumn
|
||||
docs={transcriptionDocs}
|
||||
weeklyCount={weeklyStats?.transcriptionCount ?? 0}
|
||||
/>
|
||||
<ReadyColumn docs={readyDocs} weeklyCount={weeklyStats?.readyCount ?? 0} />
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
<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">
|
||||
{m.mission_control_heading()}
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<SegmentationColumn docs={segmentationDocs} weeklyCount={weeklyStats?.segmentationCount ?? 0} />
|
||||
<TranscriptionColumn
|
||||
docs={transcriptionDocs}
|
||||
weeklyCount={weeklyStats?.transcriptionCount ?? 0}
|
||||
/>
|
||||
<ReadyColumn docs={readyDocs} weeklyCount={weeklyStats?.readyCount ?? 0} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
<script lang="ts">
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
import { getLocale } from '$lib/paraglide/runtime.js';
|
||||
import { formatMCDate } from '$lib/utils/date.js';
|
||||
import type { components } from '$lib/generated/api';
|
||||
|
||||
type TranscriptionQueueItemDTO = {
|
||||
id: string;
|
||||
title: string;
|
||||
documentDate?: string;
|
||||
annotationCount: number;
|
||||
textedBlockCount: number;
|
||||
reviewedBlockCount: number;
|
||||
};
|
||||
type TranscriptionQueueItemDTO = components['schemas']['TranscriptionQueueItemDTO'];
|
||||
|
||||
interface Props {
|
||||
docs: TranscriptionQueueItemDTO[];
|
||||
@@ -18,14 +13,6 @@ interface 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 {
|
||||
if (doc.annotationCount === 0) return 0;
|
||||
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>
|
||||
<div class="mt-0.5 flex items-center gap-2">
|
||||
{#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 doc.textedBlockCount > 0}
|
||||
<span class="text-xs font-semibold text-ink">
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
<script lang="ts">
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
import { getLocale } from '$lib/paraglide/runtime.js';
|
||||
import { formatMCDate } from '$lib/utils/date.js';
|
||||
import type { components } from '$lib/generated/api';
|
||||
|
||||
type TranscriptionQueueItemDTO = {
|
||||
id: string;
|
||||
title: string;
|
||||
documentDate?: string;
|
||||
annotationCount: number;
|
||||
textedBlockCount: number;
|
||||
reviewedBlockCount: number;
|
||||
};
|
||||
type TranscriptionQueueItemDTO = components['schemas']['TranscriptionQueueItemDTO'];
|
||||
|
||||
interface Props {
|
||||
docs: TranscriptionQueueItemDTO[];
|
||||
@@ -17,14 +12,6 @@ interface 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>
|
||||
|
||||
{#if docs.length > 0}
|
||||
@@ -53,11 +40,19 @@ function formatDate(dateStr: string): string {
|
||||
>
|
||||
<span class="font-serif text-sm text-ink">{doc.title}</span>
|
||||
{#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}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</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}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
<script lang="ts">
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
import { getLocale } from '$lib/paraglide/runtime.js';
|
||||
import { formatMCDate } from '$lib/utils/date.js';
|
||||
import type { components } from '$lib/generated/api';
|
||||
|
||||
type TranscriptionQueueItemDTO = {
|
||||
id: string;
|
||||
title: string;
|
||||
documentDate?: string;
|
||||
annotationCount: number;
|
||||
textedBlockCount: number;
|
||||
reviewedBlockCount: number;
|
||||
};
|
||||
type TranscriptionQueueItemDTO = components['schemas']['TranscriptionQueueItemDTO'];
|
||||
|
||||
interface Props {
|
||||
docs: TranscriptionQueueItemDTO[];
|
||||
@@ -18,14 +13,6 @@ interface 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 {
|
||||
if (doc.annotationCount === 0) return 0;
|
||||
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>
|
||||
{#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 doc.textedBlockCount > 0}
|
||||
<div class="mt-1.5 flex items-center gap-2">
|
||||
@@ -83,4 +72,10 @@ function blockProgress(doc: TranscriptionQueueItemDTO): number {
|
||||
{/each}
|
||||
</ul>
|
||||
</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}
|
||||
|
||||
@@ -19,6 +19,19 @@ export function formatDate(isoDate: string, format: 'short' | 'long' = 'long'):
|
||||
}).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).
|
||||
* Returns an empty string for invalid or empty input.
|
||||
|
||||
Reference in New Issue
Block a user