diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java index c2f58389..21618f71 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java @@ -21,9 +21,7 @@ import org.raddatz.familienarchiv.model.ScriptType; import org.raddatz.familienarchiv.model.TrainingLabel; import org.raddatz.familienarchiv.model.Person; import org.raddatz.familienarchiv.model.Tag; -import org.raddatz.familienarchiv.repository.CompletionStatsRow; import org.raddatz.familienarchiv.repository.DocumentRepository; -import org.raddatz.familienarchiv.repository.TranscriptionBlockRepository; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; @@ -64,7 +62,7 @@ public class DocumentService { private final DocumentVersionService documentVersionService; private final AnnotationService annotationService; private final AuditService auditService; - private final TranscriptionBlockRepository transcriptionBlockRepository; + private final TranscriptionBlockQueryService transcriptionBlockQueryService; private final AuditLogQueryService auditLogQueryService; public record StoreResult(Document document, boolean isNew) {} @@ -414,12 +412,7 @@ public class DocumentService { } private Map fetchCompletionPercentages(List docIds) { - if (docIds.isEmpty()) return Map.of(); - Map result = new HashMap<>(); - for (CompletionStatsRow row : transcriptionBlockRepository.findCompletionStatsForDocuments(docIds)) { - result.put(row.getDocumentId(), row.getCompletionPercentage()); - } - return result; + return transcriptionBlockQueryService.getCompletionStats(docIds); } private Sort resolveSort(DocumentSort sort, String dir) { diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/TranscriptionBlockQueryService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/TranscriptionBlockQueryService.java new file mode 100644 index 00000000..945b45d7 --- /dev/null +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/TranscriptionBlockQueryService.java @@ -0,0 +1,27 @@ +package org.raddatz.familienarchiv.service; + +import lombok.RequiredArgsConstructor; +import org.raddatz.familienarchiv.repository.CompletionStatsRow; +import org.raddatz.familienarchiv.repository.TranscriptionBlockRepository; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class TranscriptionBlockQueryService { + + private final TranscriptionBlockRepository blockRepository; + + public Map getCompletionStats(List documentIds) { + if (documentIds.isEmpty()) return Map.of(); + Map result = new HashMap<>(); + for (CompletionStatsRow row : blockRepository.findCompletionStatsForDocuments(documentIds)) { + result.put(row.getDocumentId(), row.getCompletionPercentage()); + } + return result; + } +} diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceSortTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceSortTest.java index 2deee90f..b3905d0e 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceSortTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceSortTest.java @@ -11,7 +11,6 @@ import org.raddatz.familienarchiv.dto.DocumentSort; import org.raddatz.familienarchiv.model.Document; import org.raddatz.familienarchiv.model.DocumentStatus; import org.raddatz.familienarchiv.repository.DocumentRepository; -import org.raddatz.familienarchiv.repository.TranscriptionBlockRepository; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; @@ -33,7 +32,7 @@ class DocumentServiceSortTest { @Mock DocumentVersionService documentVersionService; @Mock AnnotationService annotationService; @Mock AuditLogQueryService auditLogQueryService; - @Mock TranscriptionBlockRepository transcriptionBlockRepository; + @Mock TranscriptionBlockQueryService transcriptionBlockQueryService; @InjectMocks DocumentService documentService; // ─── searchDocuments — DATE sort ────────────────────────────────────────── diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceTest.java index 396a7362..6a3aec0f 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceTest.java @@ -22,7 +22,6 @@ import org.raddatz.familienarchiv.model.DocumentStatus; import org.raddatz.familienarchiv.model.Person; import org.raddatz.familienarchiv.model.Tag; import org.raddatz.familienarchiv.repository.DocumentRepository; -import org.raddatz.familienarchiv.repository.TranscriptionBlockRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; @@ -55,7 +54,7 @@ class DocumentServiceTest { @Mock AnnotationService annotationService; @Mock AuditService auditService; @Mock AuditLogQueryService auditLogQueryService; - @Mock TranscriptionBlockRepository transcriptionBlockRepository; + @Mock TranscriptionBlockQueryService transcriptionBlockQueryService; @InjectMocks DocumentService documentService; // ─── deleteDocument ─────────────────────────────────────────────────────── diff --git a/frontend/src/lib/components/ContributorStack.svelte b/frontend/src/lib/components/ContributorStack.svelte index 3204a7dc..6bb70fd2 100644 --- a/frontend/src/lib/components/ContributorStack.svelte +++ b/frontend/src/lib/components/ContributorStack.svelte @@ -38,6 +38,8 @@ function safeColor(color: string): string { {/each} {#if hasMore} … diff --git a/frontend/src/lib/components/DashboardResumeStrip.svelte b/frontend/src/lib/components/DashboardResumeStrip.svelte index e9c7620f..76eade81 100644 --- a/frontend/src/lib/components/DashboardResumeStrip.svelte +++ b/frontend/src/lib/components/DashboardResumeStrip.svelte @@ -7,6 +7,10 @@ interface Props { } const { resumeDoc }: Props = $props(); + +function safeColor(color: string): string { + return /^#[0-9a-fA-F]{6}$/.test(color) ? color : '#8c9aa3'; +} {#if resumeDoc === null} @@ -94,7 +98,7 @@ const { resumeDoc }: Props = $props(); {#each resumeDoc.collaborators.slice(0, 3) as collab (collab.initials + collab.color)} {collab.initials}{collab.initials} {/each} diff --git a/frontend/src/lib/components/DocumentRow.svelte b/frontend/src/lib/components/DocumentRow.svelte index d4f9534b..bee37ac5 100644 --- a/frontend/src/lib/components/DocumentRow.svelte +++ b/frontend/src/lib/components/DocumentRow.svelte @@ -29,6 +29,10 @@ function tagClass(matched: boolean): string { ? 'inline-flex items-center gap-1 rounded px-2 py-1.5 text-xs font-bold tracking-widest uppercase bg-primary text-primary-fg transition-colors' : 'inline-flex items-center gap-1 rounded px-2 py-1.5 text-xs font-bold tracking-widest uppercase bg-muted text-ink hover:bg-primary hover:text-primary-fg transition-colors'; } + +function safeTagColor(color: string | null | undefined): string { + return color && /^#[0-9a-fA-F]{6}$/.test(color) ? color : '#cdcbbf'; +}
  • @@ -52,7 +56,10 @@ function tagClass(matched: boolean): string { {#if snippetSegments} -

    +

    {#each snippetSegments as seg, i (i)} {#if seg.highlight} { e.stopPropagation(); goto('/documents?tag=' + encodeURIComponent(tag.name)); }} > {#if tag.color} - {/if} {tag.name} diff --git a/frontend/src/routes/DocumentList.svelte b/frontend/src/routes/DocumentList.svelte index bf930769..6db4c8c4 100644 --- a/frontend/src/routes/DocumentList.svelte +++ b/frontend/src/routes/DocumentList.svelte @@ -74,7 +74,7 @@ const yearGroups = $derived.by(() => { class="mb-4 overflow-hidden border border-line bg-surface shadow-sm" >

    - {group.year}