feat(schema-workflow): Phase 1b — SchemaDraftDrawer with status-aware actions

This commit is contained in:
Александр Зимин
2026-05-12 11:08:31 +00:00
parent 2a3f2099e5
commit dd3a2d97e5
6 changed files with 1065 additions and 3 deletions
+97
View File
@@ -1,4 +1,5 @@
import { useQuery, queryOptions } from '@tanstack/react-query'
import axios from 'axios'
import {
apiClient,
type AuditFilters,
@@ -6,6 +7,8 @@ import {
type CascadePlan,
type ChangelogDiff,
type ChangelogResponse,
type SchemaDraft,
type SchemaReviewQueuePage,
type DictionaryDefinition,
type DictionaryDetail,
type DlqPage,
@@ -192,6 +195,100 @@ export const useChangelogDiff = (
enabled: Boolean(dictionaryName) && Boolean(entryId),
})
// === Schema workflow drafts (Phase 1a) ================================
/**
* Active schema draft на словаре, если есть. Backend возвращает 404
* {@code no_active_draft} когда нет non-terminal draft'а — мы маппим
* это в null чтобы UI рендерил «no draft» state без error toast'а.
*
* <p>refetchInterval=20s — banner должен быстро отражать «отправили
* на ревью» / «approved» transition от другого пользователя.
*/
export const dictActiveSchemaDraftQuery = (dictName: string) =>
queryOptions({
queryKey: ['schema-draft-active', dictName] as const,
queryFn: async (): Promise<SchemaDraft | null> => {
try {
const { data } = await apiClient.get<SchemaDraft>(
`/dictionaries/${dictName}/drafts/active`,
)
return data
} catch (err) {
// 404 no_active_draft — это not-an-error, отдаём null.
if (axios.isAxiosError(err) && err.response?.status === 404) {
return null
}
throw err
}
},
refetchInterval: 20_000,
})
export const useDictActiveSchemaDraft = (dictName: string | undefined) =>
useQuery({
...dictActiveSchemaDraftQuery(dictName ?? ''),
enabled: Boolean(dictName),
})
/** History всех schema drafts на словаре (для admin audit / settings tab). */
export const dictSchemaDraftsQuery = (dictName: string) =>
queryOptions({
queryKey: ['schema-drafts', dictName] as const,
queryFn: async (): Promise<SchemaDraft[]> => {
const { data } = await apiClient.get<SchemaDraft[]>(
`/dictionaries/${dictName}/drafts`,
)
return data
},
})
export const useDictSchemaDrafts = (dictName: string | undefined) =>
useQuery({
...dictSchemaDraftsQuery(dictName ?? ''),
enabled: Boolean(dictName),
})
/** Single draft drill-in (decision modal, deep-link). */
export const schemaDraftQuery = (dictName: string, draftId: string) =>
queryOptions({
queryKey: ['schema-draft', dictName, draftId] as const,
queryFn: async (): Promise<SchemaDraft> => {
const { data } = await apiClient.get<SchemaDraft>(
`/dictionaries/${dictName}/drafts/${draftId}`,
)
return data
},
})
export const useSchemaDraft = (
dictName: string | undefined,
draftId: string | undefined,
) =>
useQuery({
...schemaDraftQuery(dictName ?? '', draftId ?? ''),
enabled: Boolean(dictName) && Boolean(draftId),
})
/**
* Global pool queue для approvers — все review_pending drafts.
* Backend response: {items, page, size, total}.
*/
export const schemaReviewQueueQuery = (page = 0, size = 50) =>
queryOptions({
queryKey: ['schema-review-queue', page, size] as const,
queryFn: async (): Promise<SchemaReviewQueuePage> => {
const { data } = await apiClient.get<SchemaReviewQueuePage>(
'/admin/schema-reviews/pending',
{ params: { page, size } },
)
return data
},
})
export const useSchemaReviewQueue = (page = 0, size = 50) =>
useQuery(schemaReviewQueueQuery(page, size))
export const recordRawQuery = (dictionaryName: string, businessKey: string) =>
queryOptions({
queryKey: ['record-raw', dictionaryName, businessKey] as const,