Merge branch 'feat/reviews-tabs-records-schemas' into 'main'

feat(reviews): tabs UX — Записи / Схемы (D)

See merge request 2-6/2-6-4/terravault/ordinis!133
This commit is contained in:
Александр Зимин
2026-05-12 11:44:46 +00:00
2 changed files with 71 additions and 6 deletions
+8
View File
@@ -500,6 +500,10 @@ i18n
'createDraft.toast.updateFailed': 'Не удалось обновить черновик',
'workflow.schemaDraft.actions.edit': 'Редактировать',
// === Schema review queue panel ===
'reviews.tab.records': 'Записи',
'reviews.tab.schemas': 'Схемы',
'reviews.schema.empty': 'Нет схем на ревью',
'reviews.schema.emptyDescription': 'Когда maker отправит schema draft на ревью, он появится здесь.',
'reviews.schema.title': 'Изменения схем',
'reviews.schema.label': 'Schema workflow',
'reviews.schema.queueTotal_one': '{{count}} draft ждёт ревью',
@@ -1127,6 +1131,10 @@ i18n
'createDraft.toast.updateFailed': 'Failed to update draft',
'workflow.schemaDraft.actions.edit': 'Edit',
// === Schema review queue panel ===
'reviews.tab.records': 'Records',
'reviews.tab.schemas': 'Schemas',
'reviews.schema.empty': 'No schemas under review',
'reviews.schema.emptyDescription': 'Once a maker submits a schema draft for review, it will appear here.',
'reviews.schema.title': 'Schema changes',
'reviews.schema.label': 'Schema workflow',
'reviews.schema.queueTotal_one': '{{count}} draft awaiting review',
+62 -5
View File
@@ -18,6 +18,7 @@ import {
TableHead,
TableHeaderCell,
TableRow,
Tabs,
TextArea,
TextInput,
} from '@/ui'
@@ -49,9 +50,25 @@ type BulkResult = {
failed: { id: string; bk: string; reason: string }[]
}
type ReviewsTab = 'records' | 'schemas'
function ReviewsPage() {
const { t } = useTranslation()
const queue = useReviewQueue(0, 50)
const schemaQueue = useSchemaReviewQueue(0, 50)
const recordsCount = queue.data?.totalElements ?? 0
const schemasCount = schemaQueue.data?.total ?? 0
// Tab дефолтит на records (старее, чаще). Если schema queue не пустой
// а record queue пустой — переключаемся на schemas чтобы reviewer'у не
// пришлось кликать дважды.
const [activeTab, setActiveTab] = useState<ReviewsTab>('records')
// Auto-switch один раз когда queries loaded и records=0/schemas>0.
useMemo(() => {
if (recordsCount === 0 && schemasCount > 0 && activeTab === 'records') {
setActiveTab('schemas')
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [recordsCount, schemasCount])
const [selectedId, setSelectedId] = useState<string | undefined>(undefined)
/** Phase 4: bulk approve/reject. Set<draftId> — стабильное id'шник. */
const [selection, setSelection] = useState<Set<string>>(new Set())
@@ -142,17 +159,57 @@ function ReviewsPage() {
description={t('reviews.description')}
/>
<Tabs
value={activeTab}
onValueChange={(v) => setActiveTab(v as ReviewsTab)}
items={[
{
id: 'records',
label: t('reviews.tab.records', { defaultValue: 'Записи' }),
count:
recordsCount > 0 ? (
<Badge variant="info">{recordsCount}</Badge>
) : undefined,
},
{
id: 'schemas',
label: t('reviews.tab.schemas', { defaultValue: 'Схемы' }),
count:
schemasCount > 0 ? (
<Badge variant="info">{schemasCount}</Badge>
) : undefined,
},
]}
/>
{activeTab === 'schemas' && (
<>
{schemaQueue.isLoading && <LoadingBlock size="md" label={t('loading')} />}
<SchemaReviewQueuePanel />
{schemasCount === 0 && !schemaQueue.isLoading && (
<EmptyState
title={t('reviews.schema.empty', {
defaultValue: 'Нет схем на ревью',
})}
description={t('reviews.schema.emptyDescription', {
defaultValue: 'Когда maker отправит schema draft на ревью, он появится здесь.',
})}
/>
)}
</>
)}
{queue.isLoading && <LoadingBlock size="md" label={t('loading')} />}
{activeTab === 'records' && queue.isLoading && (
<LoadingBlock size="md" label={t('loading')} />
)}
{queue.error && (
{activeTab === 'records' && queue.error && (
<Alert variant="error" title={t('error.failed')}>
{String(queue.error)}
</Alert>
)}
{bulkResult && (
{activeTab === 'records' && bulkResult && (
<Alert
variant={bulkResult.failed.length === 0 ? 'success' : 'warning'}
title={t('reviews.bulk.resultTitle')}
@@ -191,11 +248,11 @@ function ReviewsPage() {
</Alert>
)}
{queue.data && queue.data.totalElements === 0 && (
{activeTab === 'records' && queue.data && queue.data.totalElements === 0 && (
<EmptyState title={t('reviews.empty')} />
)}
{queue.data && queue.data.totalElements > 0 && (
{activeTab === 'records' && queue.data && queue.data.totalElements > 0 && (
<Panel>
<div className="flex items-center justify-between mb-3 gap-3">
<p className="text-cell text-mute">