feat(admin-ui): catalog refresh + token migration (Stage 3.2)

This commit is contained in:
Александр Зимин
2026-05-11 09:08:38 +00:00
parent 3e19fd2cf9
commit 6db2a0c345
24 changed files with 289 additions and 273 deletions
@@ -493,12 +493,12 @@ function DictionaryDetail() {
<PageHeader
breadcrumb={
<div className="flex items-center gap-3 text-sm">
<Link to="/dictionaries" className="text-carbon/70 hover:text-ultramarain">
<Link to="/dictionaries" className="text-ink-2 hover:text-accent">
{t('nav.dictionaries')}
</Link>
{scope && (
<span className="flex items-center gap-1.5 text-carbon/70">
<span className="text-carbon/40">·</span>
<span className="flex items-center gap-1.5 text-ink-2">
<span className="text-mute/70">·</span>
<span
className={`inline-block size-2 rounded-full ${SCOPE_DOT[scope]}`}
aria-hidden="true"
@@ -569,7 +569,7 @@ function DictionaryDetail() {
{/* View toggle: Записи (default table-based view) | Связи (Hub view с
neighbors на боках). Per design handoff B. */}
{detailQuery.data && (
<div role="group" aria-label="View mode" className="flex border border-regolith rounded-sm overflow-hidden w-fit">
<div role="group" aria-label="View mode" className="flex border border-line rounded-sm overflow-hidden w-fit">
{(['records', 'hub'] as const).map((mode) => {
const active = (urlSearch.view ?? 'records') === mode
return (
@@ -586,10 +586,10 @@ function DictionaryDetail() {
})
}
aria-pressed={active}
className={`px-3 py-1 text-2xs uppercase tracking-label transition focus:outline-none focus:ring-2 focus:ring-ultramarain/40 ${
className={`px-3 py-1 text-2xs uppercase tracking-[0.10em] transition focus:outline-none focus:ring-2 focus:ring-accent/40 ${
active
? 'bg-orbit/40 text-ultramarain'
: 'hover:bg-orbit/20 text-carbon'
? 'bg-accent-bg text-accent'
: 'hover:bg-accent-bg/20 text-ink'
}`}
>
{t(`dict.view.${mode}`, {
@@ -611,8 +611,8 @@ function DictionaryDetail() {
)}
{aoi && (
<div className="flex items-center gap-3 px-3 py-2 rounded-sm border border-ultramarain/30 bg-ultramarain/4 text-sm">
<span className="font-mono text-xs text-carbon/80">
<div className="flex items-center gap-3 px-3 py-2 rounded-sm border border-accent/30 bg-accent/4 text-sm">
<span className="font-mono text-xs text-ink">
{aoi.kind === 'bbox'
? t('aoi.activeBbox', { value: aoi.bboxCsv })
: t('aoi.activePolygon', {
@@ -621,7 +621,7 @@ function DictionaryDetail() {
</span>
<button
type="button"
className="text-xs text-ultramarain hover:underline"
className="text-xs text-accent hover:underline"
onClick={handleAoiClear}
>
{t('aoi.clear')}
@@ -644,16 +644,16 @@ function DictionaryDetail() {
{/* Active state — ambient banner если ?at= задан, даже если picker закрыт. */}
{timeTravelAt && !timeTravelOpen && (
<div className="flex items-center gap-3 px-3 py-2 rounded-sm border border-orbit/40 bg-orbit/8 text-sm">
<ClockCounterClockwiseIcon weight="bold" size={16} className="text-orbit shrink-0" />
<span className="font-mono text-xs text-carbon/80">
<div className="flex items-center gap-3 px-3 py-2 rounded-sm border border-warn bg-warn-bg text-sm">
<ClockCounterClockwiseIcon weight="bold" size={16} className="text-warn shrink-0" />
<span className="font-mono text-xs text-ink">
{t('timeTravel.viewing', {
date: new Date(timeTravelAt).toLocaleString(),
})}
</span>
<button
type="button"
className="text-xs text-ultramarain hover:underline ml-auto"
className="text-xs text-accent hover:underline ml-auto"
onClick={() => setTimeTravelAt(undefined)}
>
{t('timeTravel.clear')}
@@ -694,10 +694,10 @@ function DictionaryDetail() {
onClick={() => toggleScope(s)}
aria-pressed={active}
className={[
'inline-flex items-center gap-1.5 px-2.5 py-1 rounded-sm border text-2xs uppercase tracking-label font-secondary transition-colors',
'inline-flex items-center gap-1.5 px-2.5 py-1 rounded-sm border text-2xs uppercase tracking-[0.10em] font-display transition-colors',
active
? 'border-ultramarain bg-ultramarain/8 text-ultramarain'
: 'border-regolith bg-white text-carbon/70 hover:border-carbon/40',
? 'border-accent bg-accent/8 text-accent'
: 'border-line bg-white text-ink-2 hover:border-carbon/40',
].join(' ')}
>
<span
@@ -710,14 +710,14 @@ function DictionaryDetail() {
})}
</div>
{filtersActive && (
<div className="flex items-center gap-2 text-xs text-carbon/70">
<div className="flex items-center gap-2 text-xs text-ink-2">
<span>
{t('dict.filter.matched', { count: filteredCount, total: totalRecords })}
</span>
<button
type="button"
onClick={handleClearFilters}
className="text-ultramarain hover:underline"
className="text-accent hover:underline"
>
{t('dict.filter.clear')}
</button>
@@ -796,7 +796,7 @@ function DictionaryDetail() {
if (v == null || v === '') {
return (
<TableCell key={c.key}>
<span className="text-2xs text-carbon/40"></span>
<span className="text-2xs text-mute/70"></span>
</TableCell>
)
}
@@ -807,8 +807,8 @@ function DictionaryDetail() {
className={[
'inline-block px-1.5 py-0.5 rounded-sm font-mono text-2xs',
c.isFk
? 'bg-ultramarain/8 text-ultramarain'
: 'bg-carbon/8 text-carbon/80',
? 'bg-accent/8 text-accent'
: 'bg-carbon/8 text-ink',
].join(' ')}
>
{text}
@@ -820,7 +820,7 @@ function DictionaryDetail() {
<Badge variant="info">{r.dataScope}</Badge>
</TableCell>
<TableCell>
<span className="text-2xs text-carbon/60">
<span className="text-2xs text-mute">
{new Date(r.validFrom).toLocaleDateString()}
</span>
</TableCell>
@@ -943,7 +943,7 @@ function DictionaryDetail() {
>
{edit.kind === 'close-confirm' && (
<div className="space-y-4">
<p className="text-sm text-carbon">
<p className="text-sm text-ink">
{t('dict.confirmClose.body', { key: edit.record.businessKey })}
</p>
<TextInput
@@ -955,7 +955,7 @@ function DictionaryDetail() {
{serverErrorMessage(closeMut.error) && (
<Alert variant="error">{serverErrorMessage(closeMut.error)}</Alert>
)}
<div className="flex justify-end gap-2 pt-2 border-t border-regolith">
<div className="flex justify-end gap-2 pt-2 border-t border-line">
<Button
type="button"
variant="ghost"
@@ -1062,7 +1062,7 @@ function BulkSelectionToolbar({
<div
className={[
'sticky top-0 z-10 -mx-1 flex items-center justify-between gap-3',
'bg-ultramarain text-white rounded-md shadow-sm px-4 py-2',
'bg-accent text-white rounded-md shadow-sm px-4 py-2',
].join(' ')}
>
<div className="flex items-center gap-3">
@@ -1123,7 +1123,7 @@ function RecordsPagination({
const to = Math.min((page + 1) * pageSize, totalCount)
return (
<div className="flex items-center justify-between flex-wrap gap-3 px-2">
<span className="text-2xs text-carbon/70 tabular-nums">
<span className="text-2xs text-ink-2 tabular-nums">
{t('dict.page.range', { from, to, total: totalCount })}
</span>
<div className="flex items-center gap-3 text-sm">
@@ -1136,7 +1136,7 @@ function RecordsPagination({
>
{t('dict.page.prev')}
</Button>
<span className="tabular-nums text-2xs text-carbon/70">
<span className="tabular-nums text-2xs text-ink-2">
{t('dict.page.of', { cur: page + 1, total: totalPages })}
</span>
<Button
@@ -1202,7 +1202,7 @@ function BulkCloseDialog({
</div>
{(result.skipped.length > 0 || result.errors.length > 0) && (
<div className="border border-regolith rounded p-3 max-h-48 overflow-auto bg-regolith/30">
<div className="border border-line rounded p-3 max-h-48 overflow-auto bg-line/30">
<ul className="text-2xs font-mono space-y-1">
{result.skipped.map((s) => (
<li key={`s-${s.businessKey}`} className="text-saturn">
@@ -1218,7 +1218,7 @@ function BulkCloseDialog({
</div>
)}
<div className="flex justify-end pt-2 border-t border-regolith">
<div className="flex justify-end pt-2 border-t border-line">
<Button type="button" variant="primary" onClick={onClose}>
{t('dict.bulk.dismiss')}
</Button>
@@ -1229,7 +1229,7 @@ function BulkCloseDialog({
return (
<div className="space-y-4">
<p className="text-sm text-carbon">{t('dict.bulk.confirmBody', { count })}</p>
<p className="text-sm text-ink">{t('dict.bulk.confirmBody', { count })}</p>
<TextInput
label={t('dict.confirmClose.reason')}
placeholder={t('dict.confirmClose.reasonPlaceholder')}
@@ -1237,7 +1237,7 @@ function BulkCloseDialog({
onChange={(e) => onReasonChange(e.target.value)}
/>
{serverError && <Alert variant="error">{serverError}</Alert>}
<div className="flex justify-end gap-2 pt-2 border-t border-regolith">
<div className="flex justify-end gap-2 pt-2 border-t border-line">
<Button
type="button"
variant="ghost"