From e8e54cc282f9095ff02cb9f15964ee9579e50756 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 16 Apr 2026 16:11:38 +0200 Subject: [PATCH] feat(#221): change TagInput binding to Tag[], add color dots and hierarchy grouping Backend: - TagRepository: add findDescendantIdsByName() recursive CTE query - TagService: add expandTagNamesToDescendantIdSets() for document search Frontend: - TagInput: accept Tag[] (id, name, color, parentId) instead of string[] - Chips show color dot via var(--c-tag-{color}) when tag has color - Suggestions grouped hierarchically: children indented under their parents - Update DescriptionSection, edit/new pages, SearchFilterBar, +page.svelte Co-Authored-By: Claude Sonnet 4.6 --- .../repository/TagRepository.java | 17 ++++ .../familienarchiv/service/TagService.java | 14 +++ frontend/src/lib/components/TagInput.svelte | 86 +++++++++++++++---- .../lib/components/TagInput.svelte.spec.ts | 50 +++++++++-- .../document/DescriptionSection.svelte | 8 +- frontend/src/routes/+page.svelte | 12 +-- frontend/src/routes/SearchFilterBar.svelte | 4 +- .../routes/documents/[id]/edit/+page.svelte | 2 +- .../src/routes/documents/new/+page.svelte | 2 +- frontend/src/routes/enrich/[id]/+page.svelte | 2 +- 10 files changed, 158 insertions(+), 39 deletions(-) diff --git a/backend/src/main/java/org/raddatz/familienarchiv/repository/TagRepository.java b/backend/src/main/java/org/raddatz/familienarchiv/repository/TagRepository.java index a9f2c11d..7e43d52d 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/repository/TagRepository.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/repository/TagRepository.java @@ -34,4 +34,21 @@ public interface TagRepository extends JpaRepository { SELECT parent_id FROM ancestors """, nativeQuery = true) List findAncestorIds(@Param("tagId") UUID tagId); + + /** + * Returns the IDs of the tag with the given name AND all of its descendants + * via a recursive CTE. Used to expand a selected tag to inclusive hierarchy results. + * Includes a depth guard of 50 levels to prevent runaway queries. + */ + @Query(value = """ + WITH RECURSIVE descendants AS ( + SELECT id, 0 AS depth FROM tag WHERE LOWER(name) = LOWER(:name) + UNION ALL + SELECT t.id, d.depth + 1 FROM tag t + JOIN descendants d ON t.parent_id = d.id + WHERE d.depth < 50 + ) + SELECT id FROM descendants + """, nativeQuery = true) + List findDescendantIdsByName(@Param("name") String name); } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/TagService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/TagService.java index 336ed624..a4e47e12 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/TagService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/TagService.java @@ -2,6 +2,7 @@ package org.raddatz.familienarchiv.service; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -16,6 +17,7 @@ import org.raddatz.familienarchiv.repository.TagRepository; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; import org.springframework.web.server.ResponseStatusException; import lombok.RequiredArgsConstructor; @@ -71,6 +73,18 @@ public class TagService { tagRepository.delete(getById(id)); } + /** + * For each tag name, returns the set of that tag's ID plus all descendant IDs. + * Used by DocumentService to expand selected filter tags before applying AND/OR logic. + */ + public List> expandTagNamesToDescendantIdSets(List tagNames) { + if (tagNames == null || tagNames.isEmpty()) return List.of(); + return tagNames.stream() + .filter(StringUtils::hasText) + .map(name -> (Set) new HashSet<>(tagRepository.findDescendantIdsByName(name.trim()))) + .toList(); + } + /** * Returns all tags assembled into a tree. Document counts are not included here * (they are populated by the controller layer if needed, or set to 0). diff --git a/frontend/src/lib/components/TagInput.svelte b/frontend/src/lib/components/TagInput.svelte index 78eaeca8..7a2decc8 100644 --- a/frontend/src/lib/components/TagInput.svelte +++ b/frontend/src/lib/components/TagInput.svelte @@ -1,10 +1,13 @@ @@ -79,7 +121,15 @@ function handleKeydown(e: KeyboardEvent) { {#each tags as tag, i (i)} - {tag} + {#if tag.color} + + {/if} + {tag.name}