feat(admin-ui): catalog refresh + token migration (Stage 3.2)
This commit is contained in:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user