feat(admin-ui): catalog refresh + token migration (Stage 3.2)
This commit is contained in:
@@ -184,7 +184,7 @@ export const SchemaDrivenForm = ({
|
||||
</div>
|
||||
)}
|
||||
{idSource && (
|
||||
<div className="sm:col-span-2 text-2xs text-carbon/60 px-2">
|
||||
<div className="sm:col-span-2 text-2xs text-mute px-2">
|
||||
{t('form.idSourceNote', { field: idSource })}
|
||||
</div>
|
||||
)}
|
||||
@@ -327,9 +327,9 @@ const FieldBody = ({
|
||||
<div>
|
||||
<FieldLabel required={required}>
|
||||
{label}{' '}
|
||||
<span className="text-2xs uppercase tracking-label text-carbon/60 ml-1">i18n</span>
|
||||
<span className="text-2xs uppercase tracking-[0.10em] text-mute ml-1">i18n</span>
|
||||
</FieldLabel>
|
||||
<div className="space-y-2 pl-3 border-l-2 border-regolith mt-1">
|
||||
<div className="space-y-2 pl-3 border-l-2 border-line mt-1">
|
||||
{supportedLocales.map((loc) => (
|
||||
<Controller
|
||||
key={loc}
|
||||
@@ -337,7 +337,7 @@ const FieldBody = ({
|
||||
name={`data.${fieldKey}.${loc}` as `data.${string}`}
|
||||
render={({ field }) => (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs font-mono text-carbon/70 normal-case tracking-normal whitespace-nowrap shrink-0 w-14">
|
||||
<span className="text-xs font-mono text-ink-2 normal-case tracking-normal whitespace-nowrap shrink-0 w-14">
|
||||
{loc === defaultLocale ? <strong>{loc}</strong> : loc}
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
@@ -599,7 +599,7 @@ const DateTimeField = ({ label, value, defaultTime, onChange, hint, error }: Dat
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{hint && !error && <p className="text-2xs text-carbon/60 mt-1">{hint}</p>}
|
||||
{hint && !error && <p className="text-2xs text-mute mt-1">{hint}</p>}
|
||||
{error && <p className="text-2xs text-mars mt-1">{error}</p>}
|
||||
</div>
|
||||
)
|
||||
@@ -797,11 +797,11 @@ const ReferenceSelectField = ({
|
||||
// appearance-none убирает OS default стрелку.
|
||||
// py-2 (8px) match'ит TextInput высоту. pr-10 — место под адорнмент.
|
||||
className={[
|
||||
'w-full appearance-none border rounded-sm pl-3 pr-10 py-2 text-sm font-secondary',
|
||||
'w-full appearance-none border rounded-sm pl-3 pr-10 py-2 text-sm font-display',
|
||||
'bg-white text-black transition-colors',
|
||||
'focus:outline-none focus:ring-1 focus:ring-ultramarain focus:border-ultramarain',
|
||||
errorMsg ? 'border-mars' : 'border-regolith hover:border-carbon/40',
|
||||
isLoading ? 'cursor-wait text-carbon/60' : 'cursor-pointer',
|
||||
'focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent',
|
||||
errorMsg ? 'border-mars' : 'border-line hover:border-carbon/40',
|
||||
isLoading ? 'cursor-wait text-mute' : 'cursor-pointer',
|
||||
].join(' ')}
|
||||
>
|
||||
<option value="">{isLoading ? '…' : '—'}</option>
|
||||
@@ -816,9 +816,9 @@ const ReferenceSelectField = ({
|
||||
aria-hidden="true"
|
||||
>
|
||||
{isLoading ? (
|
||||
<CircleNotchIcon size={16} weight="bold" className="text-ultramarain animate-spin" />
|
||||
<CircleNotchIcon size={16} weight="bold" className="text-accent animate-spin" />
|
||||
) : (
|
||||
<CaretDownIcon size={16} weight="regular" className="text-carbon/40" />
|
||||
<CaretDownIcon size={16} weight="regular" className="text-mute/70" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -163,10 +163,10 @@ export const AoiPickerDialog = ({ isOpen, onClose, onApply, initial }: Props) =>
|
||||
type="button"
|
||||
onClick={() => setDrawMode(active ? 'idle' : mode)}
|
||||
className={[
|
||||
'px-3 py-1.5 text-xs font-secondary rounded-sm border transition-colors',
|
||||
'px-3 py-1.5 text-xs font-display rounded-sm border transition-colors',
|
||||
active
|
||||
? 'border-ultramarain bg-ultramarain text-white'
|
||||
: 'border-regolith bg-white text-carbon hover:border-carbon/40',
|
||||
? 'border-accent bg-accent text-white'
|
||||
: 'border-line bg-white text-ink hover:border-carbon/40',
|
||||
].join(' ')}
|
||||
>
|
||||
{label}
|
||||
@@ -183,7 +183,7 @@ export const AoiPickerDialog = ({ isOpen, onClose, onApply, initial }: Props) =>
|
||||
panelClassName="max-h-[calc(100vh-2rem)] my-4 flex flex-col"
|
||||
bodyClassName="flex-1 flex flex-col gap-3 overflow-hidden"
|
||||
>
|
||||
<p className="text-sm text-carbon/70">{t('aoi.dialog.help')}</p>
|
||||
<p className="text-sm text-ink-2">{t('aoi.dialog.help')}</p>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{toolbarBtn('rectangle', t('aoi.dialog.drawRectangle'))}
|
||||
@@ -192,12 +192,12 @@ export const AoiPickerDialog = ({ isOpen, onClose, onApply, initial }: Props) =>
|
||||
type="button"
|
||||
onClick={handleClear}
|
||||
disabled={!shape}
|
||||
className="px-3 py-1.5 text-xs font-secondary rounded-sm border border-regolith bg-white text-carbon hover:border-carbon/40 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
className="px-3 py-1.5 text-xs font-display rounded-sm border border-line bg-white text-ink hover:border-carbon/40 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
{t('aoi.dialog.clear')}
|
||||
</button>
|
||||
{drawMode === 'polygon' && (
|
||||
<span className="text-2xs text-carbon/60">
|
||||
<span className="text-2xs text-mute">
|
||||
{t('aoi.dialog.polygonHint')}
|
||||
</span>
|
||||
)}
|
||||
@@ -205,7 +205,7 @@ export const AoiPickerDialog = ({ isOpen, onClose, onApply, initial }: Props) =>
|
||||
|
||||
<div
|
||||
ref={mapDivRef}
|
||||
className="rounded-sm border border-regolith shrink-0"
|
||||
className="rounded-sm border border-line shrink-0"
|
||||
style={{ display: 'block', width: '100%', height: 520, minHeight: 520 }}
|
||||
aria-label={t('aoi.dialog.mapAriaLabel')}
|
||||
/>
|
||||
|
||||
@@ -119,7 +119,7 @@ export const CascadeConfirmDialog = ({
|
||||
{plan && (
|
||||
<>
|
||||
{/* Header summary */}
|
||||
<p className="text-sm text-carbon">
|
||||
<p className="text-sm text-ink">
|
||||
{hasBlockers
|
||||
? t('cascade.summary.blocked', {
|
||||
blockers: plan.blockers.length,
|
||||
@@ -144,16 +144,16 @@ export const CascadeConfirmDialog = ({
|
||||
<span className="font-mono">
|
||||
{b.sourceDict}/{b.businessKey}
|
||||
</span>
|
||||
<span className="text-carbon/60">.{b.sourceField}</span>
|
||||
<span className="text-mute">.{b.sourceField}</span>
|
||||
</li>
|
||||
))}
|
||||
{plan.blockers.length > 5 && (
|
||||
<li className="text-carbon/60">
|
||||
<li className="text-mute">
|
||||
{t('cascade.blockers.more', { count: plan.blockers.length - 5 })}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
<p className="mt-2 text-2xs text-carbon/70">
|
||||
<p className="mt-2 text-2xs text-ink-2">
|
||||
{t('cascade.blockers.resolveFirst')}
|
||||
</p>
|
||||
</Alert>
|
||||
@@ -164,7 +164,7 @@ export const CascadeConfirmDialog = ({
|
||||
<section className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<WarningIcon weight="bold" size={14} className="text-horizon" />
|
||||
<h4 className="text-sm font-primary">
|
||||
<h4 className="text-sm font-sans">
|
||||
{t('cascade.cascade.title', { count: plan.cascade.length })}
|
||||
</h4>
|
||||
</div>
|
||||
@@ -176,13 +176,13 @@ export const CascadeConfirmDialog = ({
|
||||
return (
|
||||
<li
|
||||
key={key}
|
||||
className="border border-regolith rounded-sm px-3 py-2 text-2xs"
|
||||
className="border border-line rounded-sm px-3 py-2 text-2xs"
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="font-mono text-ultramarain">
|
||||
<span className="font-mono text-accent">
|
||||
{first.sourceDisplayName ?? first.sourceDict}
|
||||
</span>
|
||||
<span className="font-mono text-carbon/70">
|
||||
<span className="font-mono text-ink-2">
|
||||
.{first.sourceField}
|
||||
</span>
|
||||
<Badge variant={onCloseVariant(first.onClose)}>
|
||||
@@ -190,14 +190,14 @@ export const CascadeConfirmDialog = ({
|
||||
</Badge>
|
||||
<Badge variant="neutral">{entries.length}</Badge>
|
||||
</div>
|
||||
<ul className="text-2xs text-carbon/70 ml-1 space-y-0.5">
|
||||
<ul className="text-2xs text-ink-2 ml-1 space-y-0.5">
|
||||
{sample.map((e) => (
|
||||
<li key={e.recordId} className="font-mono">
|
||||
· {e.businessKey}
|
||||
</li>
|
||||
))}
|
||||
{overflow > 0 && (
|
||||
<li className="text-carbon/50">
|
||||
<li className="text-mute">
|
||||
{t('cascade.cascade.more', { count: overflow })}
|
||||
</li>
|
||||
)}
|
||||
@@ -224,7 +224,7 @@ export const CascadeConfirmDialog = ({
|
||||
<div>
|
||||
<label
|
||||
htmlFor="cascade-reason"
|
||||
className="text-2xs text-carbon/70 uppercase tracking-label"
|
||||
className="text-2xs text-ink-2 uppercase tracking-[0.10em]"
|
||||
>
|
||||
{t('cascade.reason.label')}
|
||||
</label>
|
||||
@@ -242,7 +242,7 @@ export const CascadeConfirmDialog = ({
|
||||
<div>
|
||||
<label
|
||||
htmlFor="cascade-confirm"
|
||||
className="text-2xs text-carbon/70 uppercase tracking-label"
|
||||
className="text-2xs text-ink-2 uppercase tracking-[0.10em]"
|
||||
>
|
||||
{t('cascade.confirmGate.label', { word: 'CONFIRM' })}
|
||||
</label>
|
||||
@@ -264,7 +264,7 @@ export const CascadeConfirmDialog = ({
|
||||
)}
|
||||
|
||||
{/* Footer actions */}
|
||||
<div className="flex items-center justify-end gap-2 pt-2 border-t border-regolith">
|
||||
<div className="flex items-center justify-end gap-2 pt-2 border-t border-line">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onClose}
|
||||
|
||||
@@ -37,10 +37,10 @@ export const DictionaryDependentsPanel = ({ dictionaryName }: Props) => {
|
||||
return (
|
||||
<Panel>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-sm font-primary text-carbon">
|
||||
<h3 className="text-sm font-sans text-ink">
|
||||
{t('lineage.usedBy.title')}
|
||||
</h3>
|
||||
<span className="text-2xs text-carbon/60">
|
||||
<span className="text-2xs text-mute">
|
||||
{t('lineage.usedBy.count', { count: data.length })}
|
||||
</span>
|
||||
</div>
|
||||
@@ -48,21 +48,21 @@ export const DictionaryDependentsPanel = ({ dictionaryName }: Props) => {
|
||||
{data.map((d) => (
|
||||
<li
|
||||
key={`${d.sourceDict}.${d.sourceField}`}
|
||||
className="flex items-center gap-2 px-2.5 py-1.5 rounded-sm border border-regolith bg-white text-2xs"
|
||||
className="flex items-center gap-2 px-2.5 py-1.5 rounded-sm border border-line bg-white text-2xs"
|
||||
>
|
||||
<Link
|
||||
to="/dictionaries/$name"
|
||||
params={{ name: d.sourceDict }}
|
||||
className="text-ultramarain hover:underline font-mono"
|
||||
className="text-accent hover:underline font-mono"
|
||||
aria-label={t('lineage.usedBy.openSourceDict', {
|
||||
dict: d.sourceDisplayName ?? d.sourceDict,
|
||||
})}
|
||||
>
|
||||
{d.sourceDisplayName ?? d.sourceDict}
|
||||
</Link>
|
||||
<span className="font-mono text-carbon/70">.{d.sourceField}</span>
|
||||
<ArrowRightIcon weight="bold" size={12} className="text-carbon/40" />
|
||||
<span className="font-mono text-carbon/70">{d.targetField}</span>
|
||||
<span className="font-mono text-ink-2">.{d.sourceField}</span>
|
||||
<ArrowRightIcon weight="bold" size={12} className="text-mute/70" />
|
||||
<span className="font-mono text-ink-2">{d.targetField}</span>
|
||||
{d.activeRecordsInSourceDict > 0 && (
|
||||
<Badge variant="neutral">
|
||||
{t('lineage.usedBy.activeCount', {
|
||||
|
||||
@@ -54,7 +54,7 @@ const NeighborCard = ({
|
||||
to="/dictionaries/$name"
|
||||
params={{ name: dict.name }}
|
||||
search={{ view: 'hub' }}
|
||||
className={`group block bg-white border border-regolith rounded-lg p-3 transition hover:shadow-hover hover:border-ultramarain/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ultramarain/40 ${
|
||||
className={`group block bg-white border border-line rounded-lg p-3 transition hover:shadow-hover hover:border-accent/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ${
|
||||
side === 'left' ? 'text-right' : 'text-left'
|
||||
}`}
|
||||
>
|
||||
@@ -65,11 +65,11 @@ const NeighborCard = ({
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<h4 className="font-primary text-[14px] font-semibold text-ultramarain truncate group-hover:underline">
|
||||
<h4 className="font-sans text-[14px] font-semibold text-accent truncate group-hover:underline">
|
||||
{dict.displayName ?? dict.name}
|
||||
</h4>
|
||||
</div>
|
||||
<div className="mt-0.5 font-mono text-[10px] text-carbon/60 truncate">
|
||||
<div className="mt-0.5 font-mono text-[10px] text-mute truncate">
|
||||
{dict.name}
|
||||
</div>
|
||||
</Link>
|
||||
@@ -85,7 +85,7 @@ const SectionLabel = ({
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div
|
||||
className={`text-2xs uppercase tracking-label text-carbon/60 mb-2 ${
|
||||
className={`text-2xs uppercase tracking-[0.10em] text-mute mb-2 ${
|
||||
side === 'left' ? 'text-right' : 'text-left'
|
||||
}`}
|
||||
>
|
||||
@@ -141,7 +141,7 @@ export function DictionaryHubView({ detail }: { detail: DictionaryDetail }) {
|
||||
<div className="md:order-1 order-2 space-y-2">
|
||||
<SectionLabel side="left" count={outgoingDicts.length} />
|
||||
{outgoingDicts.length === 0 ? (
|
||||
<div className="text-2xs text-carbon/40 text-right italic">
|
||||
<div className="text-2xs text-mute/70 text-right italic">
|
||||
{t('hub.noOutgoing', { defaultValue: 'нет исходящих ссылок' })}
|
||||
</div>
|
||||
) : (
|
||||
@@ -156,7 +156,7 @@ export function DictionaryHubView({ detail }: { detail: DictionaryDetail }) {
|
||||
{/* Center: focused dict (large card) */}
|
||||
<div className="md:order-2 order-1 relative">
|
||||
<div
|
||||
className="relative bg-white rounded-xl p-5 border-2 border-ultramarain"
|
||||
className="relative bg-white rounded-xl p-5 border-2 border-accent"
|
||||
style={{ boxShadow: '0 18px 36px -22px rgba(20,30,140,.5)' }}
|
||||
>
|
||||
<div className="flex items-start gap-2 mb-2 flex-wrap">
|
||||
@@ -166,22 +166,22 @@ export function DictionaryHubView({ detail }: { detail: DictionaryDetail }) {
|
||||
{t('dict.list.approval', { defaultValue: 'approval' })}
|
||||
</Badge>
|
||||
)}
|
||||
<span className="ml-auto font-mono text-[11px] text-carbon/60">
|
||||
<span className="ml-auto font-mono text-[11px] text-mute">
|
||||
v{detail.schemaVersion}
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="font-primary text-[22px] font-semibold text-ultramarain leading-tight">
|
||||
<h2 className="font-sans text-[22px] font-semibold text-accent leading-tight">
|
||||
{detail.displayName ?? detail.name}
|
||||
</h2>
|
||||
<div className="font-mono text-[11px] text-carbon/60 mt-0.5">
|
||||
<div className="font-mono text-[11px] text-mute mt-0.5">
|
||||
{detail.name}
|
||||
</div>
|
||||
{detail.description && (
|
||||
<p className="text-[13px] text-carbon mt-3 leading-relaxed">
|
||||
<p className="text-[13px] text-ink mt-3 leading-relaxed">
|
||||
{detail.description}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 pt-3 border-t border-regolith flex items-center justify-between text-2xs uppercase tracking-label text-carbon/60">
|
||||
<div className="mt-4 pt-3 border-t border-line flex items-center justify-between text-2xs uppercase tracking-[0.10em] text-mute">
|
||||
<span>{detail.bundle}</span>
|
||||
<span>{detail.supportedLocales.join(', ')}</span>
|
||||
{typeof detail.recordCount === 'number' && (
|
||||
@@ -197,7 +197,7 @@ export function DictionaryHubView({ detail }: { detail: DictionaryDetail }) {
|
||||
<div className="md:order-3 order-3 space-y-2">
|
||||
<SectionLabel side="right" count={incomingDicts.length} />
|
||||
{incomingDicts.length === 0 ? (
|
||||
<div className="text-2xs text-carbon/40 italic">
|
||||
<div className="text-2xs text-mute/70 italic">
|
||||
{t('hub.noIncoming', { defaultValue: 'никто не ссылается' })}
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -68,19 +68,19 @@ export const RecordDependentsPanel = ({ dictionaryName, businessKey }: Props) =>
|
||||
return (
|
||||
<section className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-primary text-carbon">
|
||||
<h3 className="text-sm font-sans text-ink">
|
||||
{t('lineage.refs.title')}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
{stale !== null && stale >= 2 && (
|
||||
<span
|
||||
className="text-2xs text-carbon/60 px-2 py-0.5 rounded-sm border border-regolith"
|
||||
className="text-2xs text-mute px-2 py-0.5 rounded-sm border border-line"
|
||||
title={t('lineage.refs.staleHint')}
|
||||
>
|
||||
{t('lineage.refs.stale', { minutes: stale })}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-2xs text-carbon/60">
|
||||
<span className="text-2xs text-mute">
|
||||
{t('lineage.refs.total', { count: data.total })}
|
||||
</span>
|
||||
</div>
|
||||
@@ -91,16 +91,16 @@ export const RecordDependentsPanel = ({ dictionaryName, businessKey }: Props) =>
|
||||
{data.perSource.map((s) => (
|
||||
<li
|
||||
key={`${s.sourceDict}.${s.sourceField}`}
|
||||
className="flex items-center gap-1.5 px-2 py-1 rounded-sm border border-regolith bg-regolith/30 text-2xs"
|
||||
className="flex items-center gap-1.5 px-2 py-1 rounded-sm border border-line bg-line/30 text-2xs"
|
||||
>
|
||||
<Link
|
||||
to="/dictionaries/$name"
|
||||
params={{ name: s.sourceDict }}
|
||||
className="text-ultramarain hover:underline font-mono"
|
||||
className="text-accent hover:underline font-mono"
|
||||
>
|
||||
{s.sourceDisplayName ?? s.sourceDict}
|
||||
</Link>
|
||||
<span className="font-mono text-carbon/70">.{s.sourceField}</span>
|
||||
<span className="font-mono text-ink-2">.{s.sourceField}</span>
|
||||
<Badge variant="neutral">{s.count}</Badge>
|
||||
<Badge variant={onCloseVariant(s.onClose)}>
|
||||
{t(`lineage.onClose.${s.onClose}`)}
|
||||
@@ -111,7 +111,7 @@ export const RecordDependentsPanel = ({ dictionaryName, businessKey }: Props) =>
|
||||
|
||||
{/* Items list */}
|
||||
{data.items.length > 0 && (
|
||||
<ul className="divide-y divide-regolith border border-regolith rounded-sm">
|
||||
<ul className="divide-y divide-regolith border border-line rounded-sm">
|
||||
{data.items.map((r) => (
|
||||
<li
|
||||
key={r.id}
|
||||
@@ -119,13 +119,13 @@ export const RecordDependentsPanel = ({ dictionaryName, businessKey }: Props) =>
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-mono text-carbon/70">{r.sourceDict}</span>
|
||||
<span className="text-carbon/40">/</span>
|
||||
<span className="font-mono text-ink-2">{r.sourceDict}</span>
|
||||
<span className="text-mute/70">/</span>
|
||||
<Link
|
||||
to="/dictionaries/$name"
|
||||
params={{ name: r.sourceDict }}
|
||||
search={{ q: r.businessKey }}
|
||||
className="text-ultramarain hover:underline font-mono truncate"
|
||||
className="text-accent hover:underline font-mono truncate"
|
||||
aria-label={t('lineage.refs.openSourceRecord', {
|
||||
key: r.businessKey,
|
||||
})}
|
||||
@@ -135,10 +135,10 @@ export const RecordDependentsPanel = ({ dictionaryName, businessKey }: Props) =>
|
||||
<ArrowSquareOutIcon
|
||||
weight="bold"
|
||||
size={12}
|
||||
className="text-carbon/40 shrink-0"
|
||||
className="text-mute/70 shrink-0"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-carbon/60 mt-0.5">
|
||||
<div className="text-mute mt-0.5">
|
||||
{t('lineage.refs.field', { field: r.sourceField })} ·{' '}
|
||||
{t('lineage.refs.created', {
|
||||
when: new Date(r.createdAt).toLocaleString(),
|
||||
@@ -162,7 +162,7 @@ export const RecordDependentsPanel = ({ dictionaryName, businessKey }: Props) =>
|
||||
>
|
||||
{t('pagination.prev')}
|
||||
</Button>
|
||||
<span className="text-carbon/70">
|
||||
<span className="text-ink-2">
|
||||
{t('pagination.pageOf', { page: page + 1, total: totalPages })}
|
||||
</span>
|
||||
<Button
|
||||
|
||||
@@ -40,10 +40,10 @@ export const RecordHistoryDrawer = ({ open, onClose, dictionaryName, businessKey
|
||||
</Alert>
|
||||
)}
|
||||
{data && data.length === 0 && (
|
||||
<p className="text-sm text-carbon/60">{t('history.empty')}</p>
|
||||
<p className="text-sm text-mute">{t('history.empty')}</p>
|
||||
)}
|
||||
{data && data.length > 0 && (
|
||||
<ol className="relative border-l-2 border-regolith pl-5 space-y-4">
|
||||
<ol className="relative border-l-2 border-line pl-5 space-y-4">
|
||||
{data.map((rec, idx) => {
|
||||
const isLatest = idx === 0
|
||||
const displayVersion = data.length - idx
|
||||
@@ -65,7 +65,7 @@ export const RecordHistoryDrawer = ({ open, onClose, dictionaryName, businessKey
|
||||
<span
|
||||
className={`absolute -left-[27px] top-1 h-3 w-3 rounded-full border-2 ${
|
||||
isLatest && status === 'active'
|
||||
? 'bg-ultramarain border-ultramarain'
|
||||
? 'bg-accent border-accent'
|
||||
: status === 'future'
|
||||
? 'bg-orbit border-horizon'
|
||||
: status === 'expired'
|
||||
@@ -74,7 +74,7 @@ export const RecordHistoryDrawer = ({ open, onClose, dictionaryName, businessKey
|
||||
}`}
|
||||
/>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-sm font-primary text-ultramarain">v{displayVersion}</span>
|
||||
<span className="text-sm font-sans text-accent">v{displayVersion}</span>
|
||||
{isLatest && status === 'active' && (
|
||||
<Badge variant="primary">{t('history.current')}</Badge>
|
||||
)}
|
||||
@@ -84,7 +84,7 @@ export const RecordHistoryDrawer = ({ open, onClose, dictionaryName, businessKey
|
||||
<Badge variant="success">{t('history.active')}</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-2xs text-carbon/70 space-y-0.5">
|
||||
<div className="text-2xs text-ink-2 space-y-0.5">
|
||||
<div>
|
||||
<span className="font-mono">{t('history.validFrom')}:</span>{' '}
|
||||
{new Date(rec.validFrom).toLocaleString()}
|
||||
@@ -102,10 +102,10 @@ export const RecordHistoryDrawer = ({ open, onClose, dictionaryName, businessKey
|
||||
)}
|
||||
</div>
|
||||
<details className="mt-2">
|
||||
<summary className="text-2xs text-ultramarain cursor-pointer hover:underline">
|
||||
<summary className="text-2xs text-accent cursor-pointer hover:underline">
|
||||
{t('history.viewData')}
|
||||
</summary>
|
||||
<pre className="mt-1 bg-regolith/30 rounded p-2 text-2xs font-mono overflow-x-auto">
|
||||
<pre className="mt-1 bg-line/30 rounded p-2 text-2xs font-mono overflow-x-auto">
|
||||
{JSON.stringify(rec.data, null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
|
||||
@@ -202,7 +202,7 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
|
||||
|
||||
<div className={activeTab === 'metadata' ? 'block' : 'hidden'}>
|
||||
{!isEdit && (
|
||||
<div className="mb-3 p-3 rounded-md border border-ultramarain/20 bg-ultramarain/4">
|
||||
<div className="mb-3 p-3 rounded-md border border-accent/20 bg-accent/4">
|
||||
<SingleSelect
|
||||
label={t('schema.template.label')}
|
||||
hint={t('schema.template.hint')}
|
||||
@@ -300,7 +300,7 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
|
||||
/>
|
||||
{/* CEO plan E2: per-dict Redis projection opt-in. Default false.
|
||||
Включается когда dict хочет 10k+ RPS на read-api (вместо PG). */}
|
||||
<label className="md:col-span-2 flex items-start gap-3 px-3 py-2 rounded-sm border border-regolith bg-regolith/20 cursor-pointer">
|
||||
<label className="md:col-span-2 flex items-start gap-3 px-3 py-2 rounded-sm border border-line bg-line/20 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={redisProjection}
|
||||
@@ -308,10 +308,10 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
|
||||
className="mt-0.5 size-4 accent-ultramarain"
|
||||
/>
|
||||
<span className="flex-1">
|
||||
<span className="block text-sm font-primary text-carbon">
|
||||
<span className="block text-sm font-sans text-ink">
|
||||
{t('schema.redisProjection.label')}
|
||||
</span>
|
||||
<span className="block text-2xs text-carbon/60 mt-0.5">
|
||||
<span className="block text-2xs text-mute mt-0.5">
|
||||
{t('schema.redisProjection.hint')}
|
||||
</span>
|
||||
</span>
|
||||
@@ -320,7 +320,7 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
|
||||
{/* Approval Workflow v2: per-dict opt-in. Default false. Включается
|
||||
для критичных dicts (ground_station, spacecraft и т.д.) где
|
||||
один admin не должен иметь power публиковать без review. */}
|
||||
<label className="md:col-span-2 flex items-start gap-3 px-3 py-2 rounded-sm border border-orbit/40 bg-orbit/8 cursor-pointer">
|
||||
<label className="md:col-span-2 flex items-start gap-3 px-3 py-2 rounded-sm border border-warn bg-warn-bg cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={approvalRequired}
|
||||
@@ -328,10 +328,10 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
|
||||
className="mt-0.5 size-4 accent-orbit"
|
||||
/>
|
||||
<span className="flex-1">
|
||||
<span className="block text-sm font-primary text-carbon">
|
||||
<span className="block text-sm font-sans text-ink">
|
||||
{t('schema.approvalRequired.label')}
|
||||
</span>
|
||||
<span className="block text-2xs text-carbon/60 mt-0.5">
|
||||
<span className="block text-2xs text-mute mt-0.5">
|
||||
{t('schema.approvalRequired.hint')}
|
||||
</span>
|
||||
</span>
|
||||
@@ -341,7 +341,7 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
|
||||
<div className="md:col-span-2">
|
||||
<label
|
||||
htmlFor="approval-min-role"
|
||||
className="text-2xs uppercase tracking-label text-carbon/70"
|
||||
className="text-2xs uppercase tracking-[0.10em] text-ink-2"
|
||||
>
|
||||
{t('schema.approvalMinRole.label')}
|
||||
</label>
|
||||
@@ -351,7 +351,7 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
|
||||
onChange={(e) => setApprovalMinRole(e.target.value)}
|
||||
placeholder="ordinis:internal"
|
||||
/>
|
||||
<p className="text-2xs text-carbon/60 mt-0.5">
|
||||
<p className="text-2xs text-mute mt-0.5">
|
||||
{t('schema.approvalMinRole.hint')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -364,7 +364,7 @@ export const DictionaryEditorDialog = ({ open, mode, onClose, onSuccess }: Props
|
||||
</div>
|
||||
|
||||
<div className={activeTab === 'preview' ? 'block' : 'hidden'}>
|
||||
<pre className="bg-regolith/30 rounded p-3 text-2xs font-mono overflow-x-auto whitespace-pre-wrap">
|
||||
<pre className="bg-line/30 rounded p-3 text-2xs font-mono overflow-x-auto whitespace-pre-wrap">
|
||||
{JSON.stringify(schemaJson, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
@@ -47,7 +47,7 @@ export const EventsPreviewTab = ({
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm text-carbon/70">
|
||||
<p className="text-sm text-ink-2">
|
||||
{t('schema.events.intro')}
|
||||
</p>
|
||||
|
||||
@@ -57,18 +57,18 @@ export const EventsPreviewTab = ({
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<Badge variant="info">{current.eventType}</Badge>
|
||||
<span className="text-2xs text-carbon/70">
|
||||
<span className="text-2xs text-ink-2">
|
||||
{t('schema.events.topic')}:
|
||||
</span>
|
||||
<code className="text-2xs font-mono text-ultramarain">
|
||||
<code className="text-2xs font-mono text-accent">
|
||||
{current.topic}
|
||||
</code>
|
||||
</div>
|
||||
<p className="text-2xs text-carbon/60">{current.description}</p>
|
||||
<pre className="bg-regolith/30 rounded p-3 text-2xs font-mono overflow-x-auto whitespace-pre-wrap">
|
||||
<p className="text-2xs text-mute">{current.description}</p>
|
||||
<pre className="bg-line/30 rounded p-3 text-2xs font-mono overflow-x-auto whitespace-pre-wrap">
|
||||
{JSON.stringify(current.envelope, null, 2)}
|
||||
</pre>
|
||||
<p className="text-2xs text-carbon/50">
|
||||
<p className="text-2xs text-mute">
|
||||
{t('schema.events.disclaimer')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -49,9 +49,9 @@ export const PropertyEditor = ({
|
||||
const update = (patch: Partial<PropertyDef>) => onChange({ ...prop, ...patch })
|
||||
|
||||
return (
|
||||
<div className="border border-regolith rounded-lg p-3 space-y-3 bg-white">
|
||||
<div className="border border-line rounded-lg p-3 space-y-3 bg-white">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<span className="text-2xs uppercase tracking-label text-carbon/60">
|
||||
<span className="text-2xs uppercase tracking-[0.10em] text-mute">
|
||||
{t('schema.property')} #{index + 1}
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
@@ -119,13 +119,13 @@ export const PropertyEditor = ({
|
||||
|
||||
{prop.kind === 'opaque' && (
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-md p-3 text-2xs space-y-2">
|
||||
<div className="font-medium text-carbon">
|
||||
<div className="font-medium text-ink">
|
||||
{t('schema.opaque.title')}
|
||||
</div>
|
||||
<div className="text-carbon/70">
|
||||
<div className="text-ink-2">
|
||||
{t('schema.opaque.hint')}
|
||||
</div>
|
||||
<pre className="font-mono text-2xs bg-white border border-regolith rounded p-2 overflow-auto max-h-40">
|
||||
<pre className="font-mono text-2xs bg-white border border-line rounded p-2 overflow-auto max-h-40">
|
||||
{JSON.stringify(prop.rawSchema ?? {}, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
@@ -260,7 +260,7 @@ const ReferenceTargetPicker = ({ value, onChange }: ReferenceTargetPickerProps)
|
||||
const isValid = Boolean(refDict && refField)
|
||||
|
||||
return (
|
||||
<div className="border border-ultramarain/20 bg-ultramarain/4 rounded-md p-3 space-y-3">
|
||||
<div className="border border-accent/20 bg-accent/4 rounded-md p-3 space-y-3">
|
||||
<div>
|
||||
<FieldLabel required>{t('schema.reference.title')}</FieldLabel>
|
||||
<FieldHint>{t('schema.reference.hint')}</FieldHint>
|
||||
|
||||
@@ -50,15 +50,15 @@ export function TimeTravelPicker({
|
||||
}, [marks, valueDate])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3 px-4 py-3 rounded-md border border-orbit/40 bg-orbit/4 text-sm">
|
||||
<div className="flex flex-col gap-3 px-4 py-3 rounded-md border border-warn bg-warn-bg text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<ClockCounterClockwiseIcon weight="bold" size={16} className="text-orbit" />
|
||||
<span className="text-2xs font-secondary uppercase tracking-label text-carbon/70">
|
||||
<ClockCounterClockwiseIcon weight="bold" size={16} className="text-warn" />
|
||||
<span className="text-2xs font-display uppercase tracking-[0.10em] text-ink-2">
|
||||
{t('timeTravel.label')}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-auto text-2xs text-carbon/60 hover:text-carbon hover:underline inline-flex items-center gap-1"
|
||||
className="ml-auto text-2xs text-mute hover:text-ink hover:underline inline-flex items-center gap-1"
|
||||
onClick={onClose}
|
||||
>
|
||||
<XIcon weight="bold" size={12} />
|
||||
@@ -314,7 +314,7 @@ function TimelineTrack({
|
||||
)}
|
||||
|
||||
{marks.length > 0 && (
|
||||
<div className="text-2xs text-carbon/50 font-secondary mt-1">
|
||||
<div className="text-2xs text-mute font-display mt-1">
|
||||
{marks.length === 1
|
||||
? '1 точка изменений'
|
||||
: marks.length < 5
|
||||
@@ -340,7 +340,7 @@ function QuickPresets({ onPick }: { onPick: (deltaMs: number) => void }) {
|
||||
]
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<span className="text-2xs uppercase tracking-label text-carbon/60 font-secondary mr-1">
|
||||
<span className="text-2xs uppercase tracking-[0.10em] text-mute font-display mr-1">
|
||||
{t('timeTravel.quickPresets')}
|
||||
</span>
|
||||
{presets.map((p) => (
|
||||
@@ -348,7 +348,7 @@ function QuickPresets({ onPick }: { onPick: (deltaMs: number) => void }) {
|
||||
key={p.key}
|
||||
type="button"
|
||||
onClick={() => onPick(p.deltaMs)}
|
||||
className="px-2.5 py-1 rounded-full border border-regolith hover:border-ultramarain hover:bg-ultramarain/4 text-2xs font-mono uppercase tracking-label text-carbon transition-colors"
|
||||
className="px-2.5 py-1 rounded-full border border-line hover:border-accent hover:bg-accent/4 text-2xs font-mono uppercase tracking-[0.10em] text-ink transition-colors"
|
||||
>
|
||||
{p.label}
|
||||
</button>
|
||||
@@ -386,23 +386,23 @@ function StatusFooter({
|
||||
|
||||
if (!valueDate) {
|
||||
return (
|
||||
<div className="text-2xs text-carbon/60 font-secondary italic">
|
||||
<div className="text-2xs text-mute font-display italic">
|
||||
{t('timeTravel.hint')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-2 pt-2 border-t border-orbit/20">
|
||||
<ClockCounterClockwiseIcon weight="regular" size={14} className="text-orbit shrink-0" />
|
||||
<span className="text-xs font-mono text-carbon/80">
|
||||
<div className="flex flex-wrap items-center gap-2 pt-2 border-t border-warn/30">
|
||||
<ClockCounterClockwiseIcon weight="regular" size={14} className="text-warn shrink-0" />
|
||||
<span className="text-xs font-mono text-ink">
|
||||
{valueDate.toLocaleString(lang === 'en' ? 'en-US' : 'ru-RU', {
|
||||
dateStyle: 'long',
|
||||
timeStyle: 'short',
|
||||
})}
|
||||
</span>
|
||||
{relativeLabel && (
|
||||
<span className="text-2xs text-carbon/60 font-secondary lowercase">
|
||||
<span className="text-2xs text-mute font-display lowercase">
|
||||
({relativeLabel})
|
||||
</span>
|
||||
)}
|
||||
@@ -418,10 +418,10 @@ function StatusFooter({
|
||||
valueDate.getMinutes(),
|
||||
)
|
||||
}
|
||||
className="w-10 px-1.5 py-0.5 rounded-sm border border-regolith bg-white text-2xs font-mono text-center"
|
||||
className="w-10 px-1.5 py-0.5 rounded-sm border border-line bg-white text-2xs font-mono text-center"
|
||||
aria-label={t('timeTravel.hours')}
|
||||
/>
|
||||
<span className="text-carbon/50 font-mono text-2xs">:</span>
|
||||
<span className="text-mute font-mono text-2xs">:</span>
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
@@ -433,13 +433,13 @@ function StatusFooter({
|
||||
Math.max(0, Math.min(59, parseInt(e.target.value, 10) || 0)),
|
||||
)
|
||||
}
|
||||
className="w-10 px-1.5 py-0.5 rounded-sm border border-regolith bg-white text-2xs font-mono text-center"
|
||||
className="w-10 px-1.5 py-0.5 rounded-sm border border-line bg-white text-2xs font-mono text-center"
|
||||
aria-label={t('timeTravel.minutes')}
|
||||
/>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-auto text-2xs text-ultramarain hover:underline"
|
||||
className="ml-auto text-2xs text-accent hover:underline"
|
||||
onClick={onClear}
|
||||
>
|
||||
{t('timeTravel.clear')}
|
||||
|
||||
@@ -29,17 +29,17 @@ export function VersionBadge() {
|
||||
|
||||
return (
|
||||
<span
|
||||
className="inline-flex items-center gap-1.5 text-xs text-carbon/60 font-mono select-none"
|
||||
className="inline-flex items-center gap-1.5 text-xs text-mute font-mono select-none"
|
||||
title={tooltip}
|
||||
>
|
||||
{updateAvailable && (
|
||||
<span
|
||||
aria-label="Доступна новая версия"
|
||||
className="inline-block w-1.5 h-1.5 rounded-full bg-ultramarain animate-pulse"
|
||||
className="inline-block w-1.5 h-1.5 rounded-full bg-accent animate-pulse"
|
||||
/>
|
||||
)}
|
||||
<span>v{clientVersion}</span>
|
||||
<span className="text-carbon/40">·</span>
|
||||
<span className="text-mute/70">·</span>
|
||||
<span>{clientCommit.substring(0, 7)}</span>
|
||||
</span>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user