feat(records): scheduled records empty-state hint
Fixes UX bug: пользователь создаёт запись с validFrom на будущую дату
(e.g. 18.05), открывает catalog 15.05 — пустая таблица, никаких хинтов
о том что запись существует.
Backend (read-api):
- New endpoint GET /api/v1/{dict}/records/scheduled-summary
- Returns {count, nearestValidFrom} для записей с validFrom > now AND
validTo > now в текущем scope view
- Single-aggregate JPA query (COUNT + MIN), respect scope filter same way
as listActive — INTERNAL scheduled не учитывается для PUBLIC consumer'а
Frontend:
- New useScheduledSummary hook + ScheduledRecordsSummary type
- Empty-state в dictionaries.$name now shows: "Запланировано N, ближайшая
на DD.MM [Перейти к этой дате]" с CTA который set'ает time-travel на
nearestValidFrom
- i18n keys для ru/en plural forms
Связан с MR !219 (TimeTravelPicker 30-day future window) — теперь slider
позволяет дойти до даты записи, и empty-state hint позволяет узнать что
запись существует.
This commit is contained in:
@@ -35,6 +35,7 @@ import {
|
||||
useDictPendingDrafts,
|
||||
useRecordRaw,
|
||||
useRecords,
|
||||
useScheduledSummary,
|
||||
type RecordsFilter,
|
||||
} from '@/api/queries'
|
||||
import { useBulkCloseRecords, useBulkExportRecords, useCreateRecord, useUpdateRecord, useCloseRecord, useFormIdempotencyKey, useSubmitDraft } from '@/api/mutations'
|
||||
@@ -169,6 +170,16 @@ function DictionaryDetail() {
|
||||
}
|
||||
: undefined
|
||||
const recordsResult = useRecords(name, 'PUBLIC,INTERNAL,RESTRICTED', filter)
|
||||
/**
|
||||
* Scheduled records hint (post-Phase-B-2 UX fix). Когда records list пустой
|
||||
* на момент now / time-travel, но в БД есть записи с validFrom > now,
|
||||
* показываем «Запланировано N, ближайшая на DD.MM [Перейти к этой дате]».
|
||||
* Fixes user feedback: «создал запись на 18.05 — её не видно».
|
||||
*
|
||||
* <p>Fetch unconditional (cheap single-aggregate query). Frontend сам решает
|
||||
* показывать hint или нет по {@code count > 0}.
|
||||
*/
|
||||
const scheduledSummary = useScheduledSummary(name, 'PUBLIC,INTERNAL,RESTRICTED')
|
||||
/**
|
||||
* Approval Workflow v2 Phase 4: pending drafts на этот dict — для бейджа
|
||||
* "На review" в каждой строке records table. Polled 30s. Bypass'нем если
|
||||
@@ -974,7 +985,30 @@ function DictionaryDetail() {
|
||||
)}
|
||||
|
||||
{recordsResult.data && recordsResult.data.length === 0 && (
|
||||
<EmptyState title={t('dict.empty')} />
|
||||
<EmptyState
|
||||
title={t('dict.empty')}
|
||||
description={
|
||||
scheduledSummary.data && scheduledSummary.data.count > 0 && scheduledSummary.data.nearestValidFrom
|
||||
? t('dict.scheduled.hint', {
|
||||
count: scheduledSummary.data.count,
|
||||
date: new Date(scheduledSummary.data.nearestValidFrom).toLocaleDateString(),
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
action={
|
||||
scheduledSummary.data && scheduledSummary.data.count > 0 && scheduledSummary.data.nearestValidFrom
|
||||
? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setTimeTravelAt(scheduledSummary.data!.nearestValidFrom!)}
|
||||
className="text-cell text-accent hover:underline"
|
||||
>
|
||||
{t('dict.scheduled.cta')}
|
||||
</button>
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{recordsResult.data && recordsResult.data.length > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user