feat(admin-ui): Phase 1 lineage UI — "Used by" + record refs cards
dict-relationships-v2 epic, Phase 1 admin UI. Backend endpoints поднимаются
коммитом 46a0a9c, тут — UI surface'ит их.
Что добавлено:
- `DictionaryDependentsPanel` — schema-level "На этот словарь ссылаются:"
panel на странице справочника. Chip per (sourceDict.field → targetField,
active records count, onClose mode badge: block/warn/cascade).
Hide-on-empty: большинство справочников без incoming refs panel не видят.
- `RecordDependentsPanel` — record-level "На эту запись ссылаются:" section
внутри `RecordHistoryDrawer`. Per-source summary chips + paginated items
list (size=20, prev/next). Hide-on-empty.
Types в `client.ts`: `OnCloseAction`, `SchemaDependent`, `RecordDependent`,
`PerSourceSummary`, `RecordDependentsPage`.
Queries в `queries.ts`:
- `useDictionaryDependents(name)` — staleTime 5min (schemas slow-moving),
404/403 → empty.
- `useRecordDependents(dict, key, page, size)` — disabled пока key undefined.
Mount points:
- `routes/dictionaries.$name.tsx`: `<DictionaryDependentsPanel>` после
PageHeader, перед AOI banner.
- `components/record/RecordHistoryDrawer.tsx`: `<RecordDependentsPanel>`
выше existing version timeline.
i18n RU/EN:
- `lineage.usedBy.{title,count,activeCount,openSourceDict}`
- `lineage.refs.{title,total,field,created,openSourceRecord}`
- `lineage.onClose.{BLOCK,WARN,CASCADE}` (показ как лейблы)
- `pagination.{prev,next,pageOf}` — общие keys.
Plurals: `_one/_few/_many/_other` для RU (правильная грамматика count'ов).
Verify:
- pnpm tsc --noEmit: clean.
- pnpm test (vitest): 89/89 PASS (4.04s).
- pnpm build: clean (warnings о chunk size — pre-existing для @nstart/ui).
Бекенд endpoints живут на:
- GET /api/v1/dictionaries/{name}/dependents
- GET /api/v1/records/{dict}/{key}/dependents?page&size
This commit is contained in:
@@ -8,7 +8,9 @@ import {
|
||||
type DlqPage,
|
||||
type FlattenedRecord,
|
||||
type OutboxStats,
|
||||
type RecordDependentsPage,
|
||||
type RecordResponse,
|
||||
type SchemaDependent,
|
||||
type WebhookDeliveryPage,
|
||||
type WebhookSubscription,
|
||||
} from './client'
|
||||
@@ -265,6 +267,70 @@ export const webhookDlqQuery = (page: number, size: number) =>
|
||||
export const useWebhookDlq = (page: number, size: number) =>
|
||||
useQuery(webhookDlqQuery(page, size))
|
||||
|
||||
// === Dependents (Phase 1 dict-relationships-v2) ===
|
||||
|
||||
/**
|
||||
* Schema-level reverse FK list для target dict — какие dict.field ссылаются.
|
||||
* 5 min staleTime: schema-level changes идут через bundle import (редко).
|
||||
*
|
||||
* 404 / 403 → пустой list (consistent с serverside scope-hide).
|
||||
*/
|
||||
export const dictionaryDependentsQuery = (name: string) =>
|
||||
queryOptions({
|
||||
queryKey: ['dictionary-dependents', name] as const,
|
||||
queryFn: async (): Promise<SchemaDependent[]> => {
|
||||
try {
|
||||
const { data } = await apiClient.get<SchemaDependent[]>(
|
||||
`/dictionaries/${encodeURIComponent(name)}/dependents`,
|
||||
)
|
||||
return data
|
||||
} catch (e: unknown) {
|
||||
const status = (e as { response?: { status?: number } })?.response?.status
|
||||
if (status === 404 || status === 403) return []
|
||||
throw e
|
||||
}
|
||||
},
|
||||
staleTime: 5 * 60_000,
|
||||
})
|
||||
|
||||
export const useDictionaryDependents = (name: string | undefined) =>
|
||||
useQuery({
|
||||
...dictionaryDependentsQuery(name ?? ''),
|
||||
enabled: Boolean(name),
|
||||
})
|
||||
|
||||
/**
|
||||
* Record-level dependents — paginated. Disabled пока businessKey пустой
|
||||
* (closed drawer / no selection).
|
||||
*/
|
||||
export const recordDependentsQuery = (
|
||||
dict: string,
|
||||
businessKey: string,
|
||||
page: number,
|
||||
size: number,
|
||||
) =>
|
||||
queryOptions({
|
||||
queryKey: ['record-dependents', dict, businessKey, page, size] as const,
|
||||
queryFn: async (): Promise<RecordDependentsPage> => {
|
||||
const { data } = await apiClient.get<RecordDependentsPage>(
|
||||
`/records/${encodeURIComponent(dict)}/${encodeURIComponent(businessKey)}/dependents`,
|
||||
{ params: { page, size } },
|
||||
)
|
||||
return data
|
||||
},
|
||||
})
|
||||
|
||||
export const useRecordDependents = (
|
||||
dict: string,
|
||||
businessKey: string | undefined,
|
||||
page = 0,
|
||||
size = 50,
|
||||
) =>
|
||||
useQuery({
|
||||
...recordDependentsQuery(dict, businessKey ?? '', page, size),
|
||||
enabled: Boolean(businessKey),
|
||||
})
|
||||
|
||||
export const useDictionaries = () => useQuery(dictionariesQuery)
|
||||
export const useDictionaryDetail = (name: string) => useQuery(dictionaryDetailQuery(name))
|
||||
export const useRecords = (
|
||||
|
||||
Reference in New Issue
Block a user