feat(admin-ui): поиск справочников + счётчик активных записей

Backend:
- DictionaryRecordRepository.countActiveGroupedByDictionary — один GROUP BY
  по всем справочникам с фильтром по scope, без N+1.
- DictionaryResponse.recordCount (nullable) + factory from(d, count).
- DictionaryDefinitionController передаёт счётчики в list/get,
  фильтрованные по currentScopes() пользователя.

Frontend:
- SearchInput над сеткой карточек, client-side filter по
  name/displayName/description (case-insensitive, useDeferredValue).
- Badge "N записей" (i18n plural ru/en) в углу карточки.
- "Показано N из M" рядом с поиском.
- EmptyState когда поиск пустой.
This commit is contained in:
Zimin A.N.
2026-05-06 08:46:45 +03:00
parent b57bc9ef82
commit 171d049fc5
6 changed files with 141 additions and 31 deletions
@@ -6,6 +6,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.time.OffsetDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -66,4 +67,22 @@ public interface DictionaryRecordRepository extends JpaRepository<DictionaryReco
@Param("at") OffsetDateTime at,
@Param("fieldName") String fieldName,
@Param("fieldValue") String fieldValue);
/**
* Активные записи (validFrom <= now < validTo), сгруппированные по словарю и
* отфильтрованные по доступным consumer'у scope'ам. Один запрос на весь список
* справочников — без N+1.
*
* @return массивы [dictionaryId UUID, count Long]
*/
@Query("""
SELECT r.dictionaryId, COUNT(r) FROM DictionaryRecord r
WHERE r.dataScope IN :allowedScopes
AND r.validFrom <= :at
AND r.validTo > :at
GROUP BY r.dictionaryId
""")
List<Object[]> countActiveGroupedByDictionary(
@Param("allowedScopes") Collection<DataScope> allowedScopes,
@Param("at") OffsetDateTime at);
}