diff --git a/ordinis-admin-ui/src/api/client.ts b/ordinis-admin-ui/src/api/client.ts
index 5ee7122..5bdd512 100644
--- a/ordinis-admin-ui/src/api/client.ts
+++ b/ordinis-admin-ui/src/api/client.ts
@@ -756,3 +756,18 @@ export const NOTIFICATION_EVENT_TYPES = [
'RecordDraftWithdrawn',
] as const
export type NotificationEventType = (typeof NOTIFICATION_EVENT_TYPES)[number]
+
+/**
+ * Empty-state hint payload (read-api scheduled-summary). Подсчёт записей с
+ * {@code validFrom > now AND validTo > now} в текущем scope view.
+ *
+ *
+ *
{@code count=0} → frontend hint не показывает.
+ *
{@code nearestValidFrom} — ISO datetime ближайшей запланированной;
+ * используется в CTA «Перейти к дате» (time-travel).
+ *
+ */
+export type ScheduledRecordsSummary = {
+ count: number
+ nearestValidFrom: string | null
+}
diff --git a/ordinis-admin-ui/src/api/queries.ts b/ordinis-admin-ui/src/api/queries.ts
index 29ecb77..046c188 100644
--- a/ordinis-admin-ui/src/api/queries.ts
+++ b/ordinis-admin-ui/src/api/queries.ts
@@ -20,6 +20,7 @@ import {
type RecordDependentsPage,
type RecordResponse,
type ReviewQueuePage,
+ type ScheduledRecordsSummary,
type SchemaDependent,
type SearchResponse,
type WebhookDeliveryPage,
@@ -784,6 +785,37 @@ export const useRecords = (
scopeCsv: string,
filter?: RecordsFilter,
) => useQuery(recordsQuery(dictionaryName, scopeCsv, filter))
+
+/**
+ * Scheduled records hint для empty-state. Fetch'ит count записей с
+ * validFrom > now (но validTo > now — т.е. не сразу закрытые). Показывается
+ * пользователю когда список пуст, но в будущем что-то запланировано.
+ *
+ *
Fixes UX bug: user создал запись с validFrom=18.05, открыл catalog 15.05 —
+ * пустая таблица, никаких хинтов. Теперь UI показывает «Запланировано: 1
+ * запись на 18.05 [перейти к дате]».
+ *
+ *
Stale 30s — scheduled state changes медленно. Refetch on focus = true
+ * (default) — пользователь возвращается во вкладку → fresh count.
+ */
+export const scheduledSummaryQuery = (dictionaryName: string, scopeCsv: string) =>
+ queryOptions({
+ queryKey: ['scheduled-summary', dictionaryName, scopeCsv] as const,
+ queryFn: async (): Promise => {
+ const { data } = await apiClient.get(
+ `/${dictionaryName}/records/scheduled-summary`,
+ { params: { as_scope: scopeCsv } },
+ )
+ return data
+ },
+ staleTime: 30_000,
+ })
+
+export const useScheduledSummary = (dictionaryName: string, scopeCsv: string) =>
+ useQuery({
+ ...scheduledSummaryQuery(dictionaryName, scopeCsv),
+ enabled: Boolean(dictionaryName),
+ })
export const useRecordRaw = (
dictionaryName: string,
businessKey: string | undefined,
diff --git a/ordinis-admin-ui/src/i18n.ts b/ordinis-admin-ui/src/i18n.ts
index 563dbce..fda171c 100644
--- a/ordinis-admin-ui/src/i18n.ts
+++ b/ordinis-admin-ui/src/i18n.ts
@@ -349,6 +349,15 @@ i18n
'dict.list.fk.usedBy_many': '← {{count}} использ.',
'dict.list.fk.usedBy_other': '← {{count}} использ.',
'dict.empty': 'В этом справочнике пока нет записей',
+ 'dict.scheduled.hint_one':
+ 'Запланирована {{count}} запись на {{date}}',
+ 'dict.scheduled.hint_few':
+ 'Запланировано {{count}} записи, ближайшая на {{date}}',
+ 'dict.scheduled.hint_many':
+ 'Запланировано {{count}} записей, ближайшая на {{date}}',
+ 'dict.scheduled.hint_other':
+ 'Запланировано {{count}} записей, ближайшая на {{date}}',
+ 'dict.scheduled.cta': 'Перейти к этой дате',
'dict.col.businessKey': 'Бизнес-ключ',
'dict.col.scope': 'Scope',
'dict.col.validFrom': 'Действует с',
@@ -1151,6 +1160,11 @@ i18n
'dict.list.fk.usedBy_one': '← {{count}} used by',
'dict.list.fk.usedBy_other': '← {{count}} used by',
'dict.empty': 'No records in this dictionary yet',
+ 'dict.scheduled.hint_one':
+ '{{count}} record scheduled for {{date}}',
+ 'dict.scheduled.hint_other':
+ '{{count}} records scheduled, earliest on {{date}}',
+ 'dict.scheduled.cta': 'Jump to that date',
'dict.col.businessKey': 'Business key',
'dict.col.scope': 'Scope',
'dict.col.validFrom': 'Valid from',
diff --git a/ordinis-admin-ui/src/routes/dictionaries.$name.tsx b/ordinis-admin-ui/src/routes/dictionaries.$name.tsx
index fce8674..8ba2909 100644
--- a/ordinis-admin-ui/src/routes/dictionaries.$name.tsx
+++ b/ordinis-admin-ui/src/routes/dictionaries.$name.tsx
@@ -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 — её не видно».
+ *
+ *
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 && (
-
+ 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
+ ? (
+
+ )
+ : undefined
+ }
+ />
)}
{recordsResult.data && recordsResult.data.length > 0 && (
diff --git a/ordinis-domain/src/main/java/cloud/nstart/terravault/ordinis/domain/record/DictionaryRecordRepository.java b/ordinis-domain/src/main/java/cloud/nstart/terravault/ordinis/domain/record/DictionaryRecordRepository.java
index bb09e61..2f7a3c2 100644
--- a/ordinis-domain/src/main/java/cloud/nstart/terravault/ordinis/domain/record/DictionaryRecordRepository.java
+++ b/ordinis-domain/src/main/java/cloud/nstart/terravault/ordinis/domain/record/DictionaryRecordRepository.java
@@ -178,4 +178,29 @@ public interface DictionaryRecordRepository extends JpaRepository now),
+ * но запланированы на будущее (validTo > now — т.е. не сразу закрытые).
+ * Используется empty-state hint в admin-UI: «У словаря есть N запланированных
+ * на DD.MM» — fixes UX bug когда пользователь создаёт запись с validFrom
+ * на будущую дату и не видит её в списке.
+ *
+ *
Single query → returns {@code [count Long, nearestValidFrom OffsetDateTime]}.
+ * Если scheduled нет — count=0, nearest=null.
+ *
+ *
Filter по scope тот же что в read-side listActive: respects user's
+ * view permissions. Anonymous (только PUBLIC) → INTERNAL scheduled не учитывается.
+ */
+ @Query("""
+ SELECT COUNT(r), MIN(r.validFrom) FROM DictionaryRecord r
+ WHERE r.dictionaryId = :dictionaryId
+ AND r.dataScope IN :allowedScopes
+ AND r.validFrom > :now
+ AND r.validTo > :now
+ """)
+ List