feat(schema-workflow): Phase 1c — CreateModal + /reviews extension + RBAC stub

This commit is contained in:
Александр Зимин
2026-05-12 11:20:29 +00:00
parent 00fe254ccc
commit ac3395f4de
6 changed files with 455 additions and 4 deletions
+80 -1
View File
@@ -22,7 +22,7 @@ import {
TextInput,
} from '@/ui'
import { CheckCircleIcon, XCircleIcon } from '@phosphor-icons/react'
import { useDraft, useLiveRecord, useReviewQueue } from '@/api/queries'
import { useDraft, useLiveRecord, useReviewQueue, useSchemaReviewQueue } from '@/api/queries'
import { useApproveDraft, useRejectDraft } from '@/api/mutations'
import type { DraftOperation, DraftResponse } from '@/api/client'
@@ -142,6 +142,8 @@ function ReviewsPage() {
description={t('reviews.description')}
/>
<SchemaReviewQueuePanel />
{queue.isLoading && <LoadingBlock size="md" label={t('loading')} />}
{queue.error && (
@@ -507,3 +509,80 @@ function ReviewDrawer({ draftId, onClose }: DrawerProps) {
</Drawer>
)
}
/**
* Schema review pool queue — global view всех \`review_pending\` schema
* draft'ов через {@code GET /admin/schema-reviews/pending}. Reviewer
* жмёт «Открыть» → переходит на dict detail page, banner+drawer
* автоматически подхватятся через {@code useDictActiveSchemaDraft}.
*
* <p>Скрывается если pool пуст — экономит вертикальное место на странице
* для основного record-review queue.
*/
function SchemaReviewQueuePanel() {
const { t } = useTranslation()
const queue = useSchemaReviewQueue(0, 20)
const items = queue.data?.items ?? []
if (queue.isLoading || items.length === 0) return null
return (
<Panel>
<div className="flex items-center justify-between mb-3">
<div>
<h2 className="text-title-md font-semibold">
{t('reviews.schema.title', { defaultValue: 'Изменения схем' })}
</h2>
<p className="text-cell text-mute mt-0.5">
{t('reviews.schema.queueTotal', {
count: queue.data?.total ?? items.length,
defaultValue: `${queue.data?.total ?? items.length} draft(s) ждут ревью`,
})}
</p>
</div>
<Badge variant="info">
{t('reviews.schema.label', { defaultValue: 'Schema workflow' })}
</Badge>
</div>
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>{t('reviews.schema.dictionary', { defaultValue: 'Словарь' })}</TableHeaderCell>
<TableHeaderCell>{t('reviews.schema.fromVersion', { defaultValue: 'От версии' })}</TableHeaderCell>
<TableHeaderCell>{t('reviews.schema.maker', { defaultValue: 'Автор' })}</TableHeaderCell>
<TableHeaderCell>{t('reviews.schema.submittedAt', { defaultValue: 'Отправлен' })}</TableHeaderCell>
<TableHeaderCell align="right">{t('reviews.schema.action', { defaultValue: 'Действие' })}</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{items.map((d) => (
<TableRow key={d.draftId}>
<TableCell>
<Link
to="/dictionaries/$name"
params={{ name: d.dictionaryName }}
className="text-accent hover:underline font-mono"
>
{d.dictionaryName}
</Link>
</TableCell>
<TableCell className="font-mono tabular-nums">v{d.branchedFromVersion}</TableCell>
<TableCell className="font-mono">{d.makerId}</TableCell>
<TableCell className="tabular-nums">
{d.submittedAt ? new Date(d.submittedAt).toLocaleString() : '—'}
</TableCell>
<TableCell align="right">
<Link
to="/dictionaries/$name"
params={{ name: d.dictionaryName }}
className="text-accent hover:underline"
>
{t('reviews.schema.open', { defaultValue: 'Открыть' })}
</Link>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Panel>
)
}