feat(records): «Запланировано» badge per row для scheduled future versions

Last item из QA report backlog'а. Banner + empty-state hint показывают
aggregate count, но не помогают find конкретные записи которые скоро
изменятся. Badge per row даёт O(1) visual scan.

Backend (read-api):
- ScheduledRecordsSummary.scheduledBusinessKeys: List<String> (capped 500)
- Repo method findScheduledBusinessKeys (DISTINCT subquery)
- Single fetch вместо N+1 per-row queries

Frontend:
- scheduledByKey Set<string> построен из summary
- Badge variant=info «Запланировано» рядом с businessKey badge'ом
- Title attribute с full RU/EN tooltip
- Не блокирует layout — gap-1.5 с pendingReview badge'ем (могут быть оба)

i18n keys для ru/en — short label «Запланировано» / «Scheduled».
This commit is contained in:
Zimin A.N.
2026-05-16 12:49:42 +03:00
parent dbbefe0510
commit e5a07fc391
6 changed files with 74 additions and 3 deletions
@@ -195,6 +195,16 @@ function DictionaryDetail() {
for (const d of pendingDraftsQuery.data ?? []) set.add(d.businessKey)
return set
}, [pendingDraftsQuery.data])
/**
* Set<businessKey> для записей имеющих scheduled future version. Используется
* рендерить «Запланировано» badge per row в records table — user сразу видит
* у каких записей есть upcoming изменения.
*/
const scheduledByKey = useMemo(() => {
const set = new Set<string>()
for (const bk of scheduledSummary.data?.scheduledBusinessKeys ?? []) set.add(bk)
return set
}, [scheduledSummary.data?.scheduledBusinessKeys])
const createMut = useCreateRecord(name)
const updateMut = useUpdateRecord(name)
// Approval Workflow v2 (D2=A): когда dict отмечен approvalRequired=true,
@@ -1164,6 +1174,19 @@ function DictionaryDetail() {
<Badge variant="warning">{t('dict.pendingReview.label')}</Badge>
</span>
) : null}
{/* «Scheduled» badge: у этой записи есть future version
* (validFrom > now). User видит в общем списке какие
* записи скоро изменятся. CTA для перехода к этой дате
* — banner выше уже линкует на nearestValidFrom. */}
{scheduledByKey.has(r.businessKey) ? (
<span title={t('dict.scheduled.row.tooltip', {
defaultValue: 'У записи есть запланированная будущая версия',
})}>
<Badge variant="info">
{t('dict.scheduled.row.badge', { defaultValue: 'Запланировано' })}
</Badge>
</span>
) : null}
</div>
</TableCell>
<TableCell hideBelow="md">