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:
@@ -500,6 +500,10 @@ i18n
|
|||||||
'createDraft.toast.updateFailed': 'Не удалось обновить черновик',
|
'createDraft.toast.updateFailed': 'Не удалось обновить черновик',
|
||||||
'workflow.schemaDraft.actions.edit': 'Редактировать',
|
'workflow.schemaDraft.actions.edit': 'Редактировать',
|
||||||
// === Schema review queue panel ===
|
// === Schema review queue panel ===
|
||||||
|
'reviews.tab.records': 'Записи',
|
||||||
|
'reviews.tab.schemas': 'Схемы',
|
||||||
|
'reviews.schema.empty': 'Нет схем на ревью',
|
||||||
|
'reviews.schema.emptyDescription': 'Когда maker отправит schema draft на ревью, он появится здесь.',
|
||||||
'reviews.schema.title': 'Изменения схем',
|
'reviews.schema.title': 'Изменения схем',
|
||||||
'reviews.schema.label': 'Schema workflow',
|
'reviews.schema.label': 'Schema workflow',
|
||||||
'reviews.schema.queueTotal_one': '{{count}} draft ждёт ревью',
|
'reviews.schema.queueTotal_one': '{{count}} draft ждёт ревью',
|
||||||
@@ -1127,6 +1131,10 @@ i18n
|
|||||||
'createDraft.toast.updateFailed': 'Failed to update draft',
|
'createDraft.toast.updateFailed': 'Failed to update draft',
|
||||||
'workflow.schemaDraft.actions.edit': 'Edit',
|
'workflow.schemaDraft.actions.edit': 'Edit',
|
||||||
// === Schema review queue panel ===
|
// === 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.title': 'Schema changes',
|
||||||
'reviews.schema.label': 'Schema workflow',
|
'reviews.schema.label': 'Schema workflow',
|
||||||
'reviews.schema.queueTotal_one': '{{count}} draft awaiting review',
|
'reviews.schema.queueTotal_one': '{{count}} draft awaiting review',
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
TableHead,
|
TableHead,
|
||||||
TableHeaderCell,
|
TableHeaderCell,
|
||||||
TableRow,
|
TableRow,
|
||||||
|
Tabs,
|
||||||
TextArea,
|
TextArea,
|
||||||
TextInput,
|
TextInput,
|
||||||
} from '@/ui'
|
} from '@/ui'
|
||||||
@@ -49,9 +50,25 @@ type BulkResult = {
|
|||||||
failed: { id: string; bk: string; reason: string }[]
|
failed: { id: string; bk: string; reason: string }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ReviewsTab = 'records' | 'schemas'
|
||||||
|
|
||||||
function ReviewsPage() {
|
function ReviewsPage() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const queue = useReviewQueue(0, 50)
|
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)
|
const [selectedId, setSelectedId] = useState<string | undefined>(undefined)
|
||||||
/** Phase 4: bulk approve/reject. Set<draftId> — стабильное id'шник. */
|
/** Phase 4: bulk approve/reject. Set<draftId> — стабильное id'шник. */
|
||||||
const [selection, setSelection] = useState<Set<string>>(new Set())
|
const [selection, setSelection] = useState<Set<string>>(new Set())
|
||||||
@@ -142,17 +159,57 @@ function ReviewsPage() {
|
|||||||
description={t('reviews.description')}
|
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 />
|
<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')}>
|
<Alert variant="error" title={t('error.failed')}>
|
||||||
{String(queue.error)}
|
{String(queue.error)}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{bulkResult && (
|
{activeTab === 'records' && bulkResult && (
|
||||||
<Alert
|
<Alert
|
||||||
variant={bulkResult.failed.length === 0 ? 'success' : 'warning'}
|
variant={bulkResult.failed.length === 0 ? 'success' : 'warning'}
|
||||||
title={t('reviews.bulk.resultTitle')}
|
title={t('reviews.bulk.resultTitle')}
|
||||||
@@ -191,11 +248,11 @@ function ReviewsPage() {
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{queue.data && queue.data.totalElements === 0 && (
|
{activeTab === 'records' && queue.data && queue.data.totalElements === 0 && (
|
||||||
<EmptyState title={t('reviews.empty')} />
|
<EmptyState title={t('reviews.empty')} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{queue.data && queue.data.totalElements > 0 && (
|
{activeTab === 'records' && queue.data && queue.data.totalElements > 0 && (
|
||||||
<Panel>
|
<Panel>
|
||||||
<div className="flex items-center justify-between mb-3 gap-3">
|
<div className="flex items-center justify-between mb-3 gap-3">
|
||||||
<p className="text-cell text-mute">
|
<p className="text-cell text-mute">
|
||||||
|
|||||||
Reference in New Issue
Block a user