fix(admin-ui): reviews — резолвить dictionaryId в имя словаря
В таблицах /reviews (Запись + Мои → Запись) колонка «Объект» показывала
UUID-prefix («254a662f…/REFLEXIO-OPTIC») вместо имени словаря, а ссылка
вела на /dictionaries/<uuid> — 404 (route ожидает name, не UUID).
DraftResponse несёт только dictionaryId (UUID); dictionaryName есть только
у schema-drafts. Резолвим через useDictionaries() — кешируется TanStack
Query, дополнительных запросов не делает.
Fallback на UUID-prefix без link'а, когда dict не в выдаче (scope-hide /
удалён) — лучше чем ведущая в 404 ссылка.
Files:
- routes/reviews.tsx — records-tab table (queue, ReviewsPage)
- components/reviews/MyDraftsPanel.tsx — RecordRow ("Мои" tab)
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
|||||||
TableHeaderCell,
|
TableHeaderCell,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from '@/ui'
|
} from '@/ui'
|
||||||
import { useMyDrafts, useMySchemaDrafts } from '@/api/queries'
|
import { useDictionaries, useMyDrafts, useMySchemaDrafts } from '@/api/queries'
|
||||||
import { UserCell } from '@/lib/useUserDisplay'
|
import { UserCell } from '@/lib/useUserDisplay'
|
||||||
import type {
|
import type {
|
||||||
DraftOperation,
|
DraftOperation,
|
||||||
@@ -303,6 +303,14 @@ function RecordRow({
|
|||||||
onClick: () => void
|
onClick: () => void
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
// DraftResponse несёт только dictionaryId (UUID) — резолвим в name через
|
||||||
|
// global dictionaries list (кешируется TanStack Query). Fallback на
|
||||||
|
// UUID-prefix без link'а если dict не в выдаче (scope-hide / deleted).
|
||||||
|
const dictionaries = useDictionaries()
|
||||||
|
const dictName = useMemo(
|
||||||
|
() => dictionaries.data?.find((d) => d.id === draft.dictionaryId)?.name,
|
||||||
|
[dictionaries.data, draft.dictionaryId],
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
@@ -311,15 +319,23 @@ function RecordRow({
|
|||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Link
|
{dictName ? (
|
||||||
to="/dictionaries/$name"
|
<Link
|
||||||
params={{ name: draft.dictionaryId }}
|
to="/dictionaries/$name"
|
||||||
className="text-mono text-accent hover:underline"
|
params={{ name: dictName }}
|
||||||
>
|
className="text-mono text-accent hover:underline"
|
||||||
<span className="text-mute">{draft.dictionaryId.slice(0, 8)}…</span>
|
>
|
||||||
<span className="mx-1 text-mute">/</span>
|
<span>{dictName}</span>
|
||||||
<span>{draft.businessKey}</span>
|
<span className="mx-1 text-mute">/</span>
|
||||||
</Link>
|
<span>{draft.businessKey}</span>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span className="text-mono" title={draft.dictionaryId}>
|
||||||
|
<span className="text-mute">{draft.dictionaryId.slice(0, 8)}…</span>
|
||||||
|
<span className="mx-1 text-mute">/</span>
|
||||||
|
<span>{draft.businessKey}</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Badge variant={operationVariant(draft.operation)}>{draft.operation}</Badge>
|
<Badge variant={operationVariant(draft.operation)}>{draft.operation}</Badge>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import axios from 'axios'
|
|||||||
import { useAuth } from 'react-oidc-context'
|
import { useAuth } from 'react-oidc-context'
|
||||||
import { ArrowCounterClockwiseIcon, CheckCircleIcon, XCircleIcon } from '@phosphor-icons/react'
|
import { ArrowCounterClockwiseIcon, CheckCircleIcon, XCircleIcon } from '@phosphor-icons/react'
|
||||||
import {
|
import {
|
||||||
|
useDictionaries,
|
||||||
useDraft,
|
useDraft,
|
||||||
useLiveRecord,
|
useLiveRecord,
|
||||||
useMyDrafts,
|
useMyDrafts,
|
||||||
@@ -162,6 +163,15 @@ function ReviewsPage() {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const queue = useReviewQueue(0, 50)
|
const queue = useReviewQueue(0, 50)
|
||||||
const schemaQueue = useSchemaReviewQueue(0, 50)
|
const schemaQueue = useSchemaReviewQueue(0, 50)
|
||||||
|
// DraftResponse несёт только dictionaryId (UUID) — резолвим в name через
|
||||||
|
// global dictionaries list (кешируется TanStack Query, ~free). Fallback на
|
||||||
|
// UUID-prefix когда dict недоступен (scope-hide / deleted).
|
||||||
|
const dictionaries = useDictionaries()
|
||||||
|
const dictNameById = useMemo(() => {
|
||||||
|
const map = new Map<string, string>()
|
||||||
|
for (const d of dictionaries.data ?? []) map.set(d.id, d.name)
|
||||||
|
return map
|
||||||
|
}, [dictionaries.data])
|
||||||
// Counts для "Мои" tab badge — sum of attention-needing drafts (pending +
|
// Counts для "Мои" tab badge — sum of attention-needing drafts (pending +
|
||||||
// WIP). Recompute дешёвый (linear over ≤100 items), refetch interval 30s
|
// WIP). Recompute дешёвый (linear over ≤100 items), refetch interval 30s
|
||||||
// делает badge самозаживляющим без manual invalidation.
|
// делает badge самозаживляющим без manual invalidation.
|
||||||
@@ -463,13 +473,27 @@ function ReviewsPage() {
|
|||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Link
|
{(() => {
|
||||||
to="/dictionaries/$name"
|
const dictName = dictNameById.get(d.dictionaryId)
|
||||||
params={{ name: d.dictionaryId }}
|
if (dictName) {
|
||||||
className="text-mono text-accent hover:underline"
|
return (
|
||||||
>
|
<Link
|
||||||
{d.dictionaryId.slice(0, 8)}…
|
to="/dictionaries/$name"
|
||||||
</Link>
|
params={{ name: dictName }}
|
||||||
|
className="text-mono text-accent hover:underline"
|
||||||
|
>
|
||||||
|
{dictName}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Dict скрыт scope-фильтром или удалён — UUID-prefix
|
||||||
|
// как fallback, без link'а (вёл бы в 404).
|
||||||
|
return (
|
||||||
|
<span className="text-mono text-mute" title={d.dictionaryId}>
|
||||||
|
{d.dictionaryId.slice(0, 8)}…
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-mono">{d.businessKey}</TableCell>
|
<TableCell className="text-mono">{d.businessKey}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
|
|||||||
Reference in New Issue
Block a user