diff --git a/frontend/messages/de.json b/frontend/messages/de.json index 81350a62..766f13f9 100644 --- a/frontend/messages/de.json +++ b/frontend/messages/de.json @@ -557,5 +557,22 @@ "training_seg_too_few_blocks": "Mindestens 5 Segmentierungsblöcke erforderlich (aktuell: {available}).", "transcription_block_segmentation_only": "Nur Segmentierung", "training_chip_kurrent": "Kurrent-Erkennung", - "training_chip_segmentation": "Segmentierung" + "training_chip_segmentation": "Segmentierung", + "mission_control_heading": "Mitarbeiten", + "mission_control_segmentation_heading": "Segmentierung", + "mission_control_segmentation_description": "Textbereiche markieren — keine Vorkenntnisse nötig", + "mission_control_segmentation_cta": "Segmentieren", + "mission_control_segmentation_empty": "Alle Dokumente haben bereits Segmentierungsblöcke.", + "mission_control_transcription_heading": "Transkription", + "mission_control_transcription_description": "Text abschreiben — Kurrent-Kenntnisse hilfreich", + "mission_control_transcription_cta": "Transkribieren", + "mission_control_transcription_empty": "Keine Dokumente warten auf Transkription.", + "mission_control_ready_heading": "Lesefertig", + "mission_control_ready_description": "Vollständig transkribiert und geprüft", + "mission_control_ready_empty": "Noch keine Dokumente vollständig transkribiert.", + "mission_control_ready_empty_cta": "Jetzt mitmachen", + "mission_control_weekly_pulse": "↑ +{count} diese Woche", + "mission_control_expert_badge": "Experten gesucht", + "mission_control_blocks_progress": "{texted} / {total} Blöcke", + "mission_control_reviewed_pct": "{pct}% geprüft" } diff --git a/frontend/messages/en.json b/frontend/messages/en.json index bbbd0f07..54494d0d 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -557,5 +557,22 @@ "training_seg_too_few_blocks": "At least 5 segmentation blocks required (currently: {available}).", "transcription_block_segmentation_only": "Segmentation only", "training_chip_kurrent": "Kurrent recognition", - "training_chip_segmentation": "Segmentation" + "training_chip_segmentation": "Segmentation", + "mission_control_heading": "Contribute", + "mission_control_segmentation_heading": "Segmentation", + "mission_control_segmentation_description": "Mark text areas — no prior knowledge needed", + "mission_control_segmentation_cta": "Segment", + "mission_control_segmentation_empty": "All documents already have segmentation blocks.", + "mission_control_transcription_heading": "Transcription", + "mission_control_transcription_description": "Type out text — Kurrent knowledge helpful", + "mission_control_transcription_cta": "Transcribe", + "mission_control_transcription_empty": "No documents waiting for transcription.", + "mission_control_ready_heading": "Ready to Read", + "mission_control_ready_description": "Fully transcribed and reviewed", + "mission_control_ready_empty": "No documents fully transcribed yet.", + "mission_control_ready_empty_cta": "Start contributing", + "mission_control_weekly_pulse": "↑ +{count} this week", + "mission_control_expert_badge": "Expert needed", + "mission_control_blocks_progress": "{texted} / {total} blocks", + "mission_control_reviewed_pct": "{pct}% reviewed" } diff --git a/frontend/messages/es.json b/frontend/messages/es.json index 2d7aba00..d6335b15 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -557,5 +557,22 @@ "training_seg_too_few_blocks": "Se requieren al menos 5 bloques de segmentación (actualmente: {available}).", "transcription_block_segmentation_only": "Solo segmentación", "training_chip_kurrent": "Reconocimiento Kurrent", - "training_chip_segmentation": "Segmentación" + "training_chip_segmentation": "Segmentación", + "mission_control_heading": "Colaborar", + "mission_control_segmentation_heading": "Segmentación", + "mission_control_segmentation_description": "Marcar áreas de texto — sin conocimientos previos", + "mission_control_segmentation_cta": "Segmentar", + "mission_control_segmentation_empty": "Todos los documentos ya tienen bloques de segmentación.", + "mission_control_transcription_heading": "Transcripción", + "mission_control_transcription_description": "Escribir el texto — conocimiento de Kurrent útil", + "mission_control_transcription_cta": "Transcribir", + "mission_control_transcription_empty": "No hay documentos esperando transcripción.", + "mission_control_ready_heading": "Listo para leer", + "mission_control_ready_description": "Completamente transcrito y revisado", + "mission_control_ready_empty": "Aún no hay documentos completamente transcritos.", + "mission_control_ready_empty_cta": "Empezar a colaborar", + "mission_control_weekly_pulse": "↑ +{count} esta semana", + "mission_control_expert_badge": "Se busca experto", + "mission_control_blocks_progress": "{texted} / {total} bloques", + "mission_control_reviewed_pct": "{pct}% revisado" } diff --git a/frontend/src/lib/components/ExpertBadge.svelte b/frontend/src/lib/components/ExpertBadge.svelte new file mode 100644 index 00000000..7fbdf28b --- /dev/null +++ b/frontend/src/lib/components/ExpertBadge.svelte @@ -0,0 +1,26 @@ + + + + + {m.mission_control_expert_badge()} + diff --git a/frontend/src/lib/components/MissionControlStrip.svelte b/frontend/src/lib/components/MissionControlStrip.svelte new file mode 100644 index 00000000..7bd28697 --- /dev/null +++ b/frontend/src/lib/components/MissionControlStrip.svelte @@ -0,0 +1,45 @@ + + +
+

+ {m.mission_control_heading()} +

+
+ + + +
+
diff --git a/frontend/src/lib/components/ReadyColumn.svelte b/frontend/src/lib/components/ReadyColumn.svelte new file mode 100644 index 00000000..b570ecf2 --- /dev/null +++ b/frontend/src/lib/components/ReadyColumn.svelte @@ -0,0 +1,86 @@ + + +{#if docs.length > 0} +
+
+

+ {m.mission_control_ready_heading()} +

+ {#if weeklyCount > 0} + + {m.mission_control_weekly_pulse({ count: weeklyCount })} + + {/if} +
+

+ {m.mission_control_ready_description()} +

+ +
+{:else} +
+

{m.mission_control_ready_empty()}

+ + {m.mission_control_ready_empty_cta()} + +
+{/if} diff --git a/frontend/src/lib/components/SegmentationColumn.svelte b/frontend/src/lib/components/SegmentationColumn.svelte new file mode 100644 index 00000000..71209817 --- /dev/null +++ b/frontend/src/lib/components/SegmentationColumn.svelte @@ -0,0 +1,71 @@ + + +
+
+

+ {m.mission_control_segmentation_heading()} +

+ {#if weeklyCount > 0} + + {m.mission_control_weekly_pulse({ count: weeklyCount })} + + {/if} +
+

+ {m.mission_control_segmentation_description()} +

+ + {#if docs.length === 0} +

{m.mission_control_segmentation_empty()}

+ {:else} + + {/if} +
diff --git a/frontend/src/lib/components/TranscriptionColumn.svelte b/frontend/src/lib/components/TranscriptionColumn.svelte new file mode 100644 index 00000000..435a981a --- /dev/null +++ b/frontend/src/lib/components/TranscriptionColumn.svelte @@ -0,0 +1,94 @@ + + +
+
+

+ {m.mission_control_transcription_heading()} +

+ {#if weeklyCount > 0} + + {m.mission_control_weekly_pulse({ count: weeklyCount })} + + {/if} +
+

+ {m.mission_control_transcription_description()} +

+ + {#if docs.length === 0} +

{m.mission_control_transcription_empty()}

+ {:else} + + {/if} +
diff --git a/frontend/src/routes/+page.server.ts b/frontend/src/routes/+page.server.ts index e1386db8..6a430477 100644 --- a/frontend/src/routes/+page.server.ts +++ b/frontend/src/routes/+page.server.ts @@ -6,6 +6,8 @@ type IncompleteDocumentDTO = components['schemas']['IncompleteDocumentDTO']; type StatsDTO = components['schemas']['StatsDTO']; type Document = components['schemas']['Document']; type SearchMatchData = components['schemas']['SearchMatchData']; +type TranscriptionQueueItemDTO = components['schemas']['TranscriptionQueueItemDTO']; +type TranscriptionWeeklyStatsDTO = components['schemas']['TranscriptionWeeklyStatsDTO']; export async function load({ url, fetch }) { const q = url.searchParams.get('q') || ''; @@ -82,12 +84,28 @@ export async function load({ url, fetch }) { let stats: StatsDTO | null = null; let incompleteDocs: IncompleteDocumentDTO[] = []; let recentDocs: Document[] = []; + let segmentationDocs: TranscriptionQueueItemDTO[] = []; + let transcriptionDocs: TranscriptionQueueItemDTO[] = []; + let readyDocs: TranscriptionQueueItemDTO[] = []; + let weeklyStats: TranscriptionWeeklyStatsDTO | null = null; if (isDashboard) { - const [statsResult, incompleteResult, recentResult] = await Promise.allSettled([ + const [ + statsResult, + incompleteResult, + recentResult, + segmentationResult, + transcriptionResult, + readyResult, + weeklyStatsResult + ] = await Promise.allSettled([ api.GET('/api/stats'), api.GET('/api/documents/incomplete', { params: { query: { size: 3 } } }), - api.GET('/api/documents/recent-activity', { params: { query: { size: 5 } } }) + api.GET('/api/documents/recent-activity', { params: { query: { size: 5 } } }), + api.GET('/api/transcription/segmentation-queue'), + api.GET('/api/transcription/transcription-queue'), + api.GET('/api/transcription/ready-to-read'), + api.GET('/api/transcription/weekly-stats') ]); if (statsResult.status === 'fulfilled' && statsResult.value.response.ok) { @@ -99,6 +117,18 @@ export async function load({ url, fetch }) { if (recentResult.status === 'fulfilled' && recentResult.value.response.ok) { recentDocs = recentResult.value.data ?? []; } + if (segmentationResult.status === 'fulfilled' && segmentationResult.value.response.ok) { + segmentationDocs = (segmentationResult.value.data ?? []) as TranscriptionQueueItemDTO[]; + } + if (transcriptionResult.status === 'fulfilled' && transcriptionResult.value.response.ok) { + transcriptionDocs = (transcriptionResult.value.data ?? []) as TranscriptionQueueItemDTO[]; + } + if (readyResult.status === 'fulfilled' && readyResult.value.response.ok) { + readyDocs = (readyResult.value.data ?? []) as TranscriptionQueueItemDTO[]; + } + if (weeklyStatsResult.status === 'fulfilled' && weeklyStatsResult.value.response.ok) { + weeklyStats = weeklyStatsResult.value.data ?? null; + } } return { @@ -109,6 +139,10 @@ export async function load({ url, fetch }) { stats, incompleteDocs, recentDocs, + segmentationDocs, + transcriptionDocs, + readyDocs, + weeklyStats, initialValues: { senderName: senderObj?.displayName ?? '', receiverName: receiverObj?.displayName ?? '' @@ -127,6 +161,10 @@ export async function load({ url, fetch }) { stats: null, incompleteDocs: [], recentDocs: [], + segmentationDocs: [], + transcriptionDocs: [], + readyDocs: [], + weeklyStats: null, initialValues: { senderName: '', receiverName: '' }, filters: { q, from, to, senderId, receiverId, tags, sort, dir, tagQ }, error: 'Daten konnten nicht geladen werden.' as string | null diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index bbdfae44..4bf13282 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -9,6 +9,7 @@ import DocumentList from './DocumentList.svelte'; import DashboardResumeStrip from '$lib/components/DashboardResumeStrip.svelte'; import DashboardNeedsMetadata from '$lib/components/DashboardNeedsMetadata.svelte'; import DashboardRecentDocuments from '$lib/components/DashboardRecentDocuments.svelte'; +import MissionControlStrip from '$lib/components/MissionControlStrip.svelte'; import { m } from '$lib/paraglide/messages.js'; let { data } = $props(); @@ -132,6 +133,13 @@ const showRightColumn = $derived(data.canWrite || (data.incompleteDocs?.length ? + + {:else}