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 <noreply@anthropic.com>
This commit is contained in:
@@ -34,4 +34,21 @@ public interface TagRepository extends JpaRepository<Tag, UUID> {
|
||||
SELECT parent_id FROM ancestors
|
||||
""", nativeQuery = true)
|
||||
List<UUID> 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<UUID> findDescendantIdsByName(@Param("name") String name);
|
||||
}
|
||||
|
||||
@@ -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<Set<UUID>> expandTagNamesToDescendantIdSets(List<String> tagNames) {
|
||||
if (tagNames == null || tagNames.isEmpty()) return List.of();
|
||||
return tagNames.stream()
|
||||
.filter(StringUtils::hasText)
|
||||
.map(name -> (Set<UUID>) 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).
|
||||
|
||||
Reference in New Issue
Block a user