Merge branch 'fix/qa-p3p4-batch' into 'main'

fix(ui): QA report P3/P4 batch — 7 cosmetic/UX bugs

See merge request 2-6/2-6-4/terravault/ordinis!231
This commit is contained in:
Александр Зимин
2026-05-15 21:10:42 +00:00
7 changed files with 50 additions and 20 deletions
@@ -208,15 +208,21 @@ export function ExportModal({
disabled={f.disabled} disabled={f.disabled}
title={f.reason} title={f.reason}
className={cn( className={cn(
'flex flex-col items-center justify-center gap-0.5 h-[68px] rounded-md border transition-colors', 'relative flex flex-col items-center justify-center gap-0.5 h-[68px] rounded-md border transition-colors',
format === f.id format === f.id
? 'border-ink bg-surface text-ink ring-1 ring-ink' ? 'border-ink bg-surface text-ink ring-1 ring-ink'
: 'border-line bg-surface text-ink-2 hover:border-line-2 hover:bg-surface-2', : 'border-line bg-surface text-ink-2 hover:border-line-2 hover:bg-surface-2',
f.disabled && 'opacity-40 cursor-not-allowed hover:bg-surface hover:border-line', f.disabled && 'opacity-60 cursor-not-allowed hover:bg-surface hover:border-line',
)} )}
> >
<span className="text-title-md font-semibold leading-none">{f.label}</span> <span className="text-title-md font-semibold leading-none">{f.label}</span>
<span className="text-mono text-mute leading-none">{f.ext}</span> <span className="text-mono text-mute leading-none">{f.ext}</span>
{/* QA BUG-009: явный «скоро» badge вместо опоры только на opacity. */}
{f.disabled && (
<span className="absolute top-1 right-1 text-[9px] font-semibold text-mute bg-surface-2 px-1 rounded-sm uppercase tracking-wider">
{t('export.format.soon', { defaultValue: 'скоро' })}
</span>
)}
</button> </button>
))} ))}
</div> </div>
@@ -48,9 +48,11 @@ export function TopBar({ onMenuClick }: { onMenuClick?: () => void }) {
return ( return (
<span key={i} className="flex items-center gap-2 min-w-0"> <span key={i} className="flex items-center gap-2 min-w-0">
{i > 0 && <span className="text-mute text-title-md font-light">/</span>} {i > 0 && <span className="text-mute text-title-md font-light">/</span>}
{/* QA UX-002: title attribute → hover tooltip когда truncate. */}
{crumb.to && !isLast ? ( {crumb.to && !isLast ? (
<Link <Link
to={crumb.to} to={crumb.to}
title={typeof crumb.label === 'string' ? crumb.label : undefined}
className={ className={
isFirstOfTwoPlus isFirstOfTwoPlus
? 'text-cell text-mute hover:text-ink truncate transition-colors inline-flex items-center gap-1' ? 'text-cell text-mute hover:text-ink truncate transition-colors inline-flex items-center gap-1'
@@ -61,7 +63,10 @@ export function TopBar({ onMenuClick }: { onMenuClick?: () => void }) {
{crumb.label} {crumb.label}
</Link> </Link>
) : ( ) : (
<span className={isLast ? 'text-ink font-semibold truncate' : 'text-ink-2 truncate'}> <span
title={typeof crumb.label === 'string' ? crumb.label : undefined}
className={isLast ? 'text-ink font-semibold truncate' : 'text-ink-2 truncate'}
>
{crumb.label} {crumb.label}
</span> </span>
)} )}
@@ -571,9 +571,15 @@ function TimelineSlider({
* чтобы две DD.MM метки ~ 36px @ text-cap не пересекались на типичной * чтобы две DD.MM метки ~ 36px @ text-cap не пересекались на типичной
* ширине трека ~340px. */} * ширине трека ~340px. */}
<div className="relative h-4 mt-1 text-mono text-cap text-mute"> <div className="relative h-4 mt-1 text-mono text-cap text-mute">
{/* QA UX-004: добавлен 2-digit year к boundary меткам — раньше «05.05»
* без года был неоднозначным на конец декабря / начало января. */}
{pct > 12 && ( {pct > 12 && (
<span className="absolute left-0"> <span
className="absolute left-0"
title={new Date(range.min).toLocaleDateString()}
>
{new Date(range.min).toLocaleDateString(undefined, { {new Date(range.min).toLocaleDateString(undefined, {
year: '2-digit',
month: '2-digit', month: '2-digit',
day: '2-digit', day: '2-digit',
})} })}
@@ -586,8 +592,12 @@ function TimelineSlider({
{dateLabel} {dateLabel}
</span> </span>
{pct < 88 && ( {pct < 88 && (
<span className="absolute right-0"> <span
className="absolute right-0"
title={new Date(range.max).toLocaleDateString()}
>
{new Date(range.max).toLocaleDateString(undefined, { {new Date(range.max).toLocaleDateString(undefined, {
year: '2-digit',
month: '2-digit', month: '2-digit',
day: '2-digit', day: '2-digit',
})} })}
+6 -6
View File
@@ -278,7 +278,7 @@ i18n
// User confusion: 'правки в public были, а тут RESTRICTED' — // User confusion: 'правки в public были, а тут RESTRICTED' —
// потому что у пользователя scope=RESTRICTED привилегия. Label // потому что у пользователя scope=RESTRICTED привилегия. Label
// 'Доступ актора' разъясняет это. // 'Доступ актора' разъясняет это.
'audit.col.scope': 'Доступ актора', 'audit.col.scope': 'Scope',
'audit.col.trace': 'Trace', 'audit.col.trace': 'Trace',
'audit.col.diff': 'Изменения', 'audit.col.diff': 'Изменения',
'audit.action.expand': 'Раскрыть', 'audit.action.expand': 'Раскрыть',
@@ -334,7 +334,7 @@ i18n
'dict.list.deps.short': 'СО СВЯЗЯМИ', 'dict.list.deps.short': 'СО СВЯЗЯМИ',
// Catalog v2 (URL-driven filters + sort + density) // Catalog v2 (URL-driven filters + sort + density)
'dict.list.heading': 'Каталог справочников', 'dict.list.heading': 'Каталог справочников',
'dict.list.filter.scope': 'Фильтр по области', 'dict.list.filter.scope': 'Фильтр по области (можно выбрать несколько)',
'dict.list.sort.label': 'Сортировка', 'dict.list.sort.label': 'Сортировка',
'dict.list.sort.name': 'По названию', 'dict.list.sort.name': 'По названию',
'dict.list.sort.recordCount': 'По количеству записей', 'dict.list.sort.recordCount': 'По количеству записей',
@@ -424,7 +424,7 @@ i18n
'form.cancel': 'Отмена', 'form.cancel': 'Отмена',
'form.save': 'Сохранить', 'form.save': 'Сохранить',
'form.saving': 'Сохранение...', 'form.saving': 'Сохранение...',
'form.delete': 'Удалить', 'form.delete': 'Закрыть запись',
'form.error.required': 'Обязательное поле', 'form.error.required': 'Обязательное поле',
'form.unsavedNFields_one': 'не сохранено · {{count}} поле', 'form.unsavedNFields_one': 'не сохранено · {{count}} поле',
'form.unsavedNFields_few': 'не сохранено · {{count}} поля', 'form.unsavedNFields_few': 'не сохранено · {{count}} поля',
@@ -1103,7 +1103,7 @@ i18n
'audit.col.dictionary': 'Dictionary', 'audit.col.dictionary': 'Dictionary',
'audit.col.businessKey': 'Business key', 'audit.col.businessKey': 'Business key',
'audit.col.user': 'User', 'audit.col.user': 'User',
'audit.col.scope': 'Actor scope', 'audit.col.scope': 'Scope',
'audit.col.trace': 'Trace', 'audit.col.trace': 'Trace',
'audit.col.diff': 'Diff', 'audit.col.diff': 'Diff',
'audit.action.expand': 'Expand', 'audit.action.expand': 'Expand',
@@ -1157,7 +1157,7 @@ i18n
'dict.list.deps.short': 'WITH LINKS', 'dict.list.deps.short': 'WITH LINKS',
// Catalog v2 // Catalog v2
'dict.list.heading': 'Dictionary catalog', 'dict.list.heading': 'Dictionary catalog',
'dict.list.filter.scope': 'Scope filter', 'dict.list.filter.scope': 'Scope filter (multi-select)',
'dict.list.sort.label': 'Sort', 'dict.list.sort.label': 'Sort',
'dict.list.sort.name': 'By name', 'dict.list.sort.name': 'By name',
'dict.list.sort.recordCount': 'By record count', 'dict.list.sort.recordCount': 'By record count',
@@ -1239,7 +1239,7 @@ i18n
'form.cancel': 'Cancel', 'form.cancel': 'Cancel',
'form.save': 'Save', 'form.save': 'Save',
'form.saving': 'Saving...', 'form.saving': 'Saving...',
'form.delete': 'Delete', 'form.delete': 'Close record',
'form.unsavedNFields_one': 'unsaved · {{count}} field', 'form.unsavedNFields_one': 'unsaved · {{count}} field',
'form.unsavedNFields_other': 'unsaved · {{count}} fields', 'form.unsavedNFields_other': 'unsaved · {{count}} fields',
'drawer.collapse': 'Collapse to 520px', 'drawer.collapse': 'Collapse to 520px',
+9 -8
View File
@@ -441,14 +441,15 @@ function AuditTable({ rows }: { rows: AuditEntry[] }) {
<Table> <Table>
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableHeaderCell>{t('audit.col.time')}</TableHeaderCell> {/* QA BUG-011 fix: title attribute → hover tooltip когда text truncate'ится. */}
<TableHeaderCell>{t('audit.col.action')}</TableHeaderCell> <TableHeaderCell title={t('audit.col.time')}>{t('audit.col.time')}</TableHeaderCell>
<TableHeaderCell>{t('audit.col.dictionary')}</TableHeaderCell> <TableHeaderCell title={t('audit.col.action')}>{t('audit.col.action')}</TableHeaderCell>
<TableHeaderCell>{t('audit.col.businessKey')}</TableHeaderCell> <TableHeaderCell title={t('audit.col.dictionary')}>{t('audit.col.dictionary')}</TableHeaderCell>
<TableHeaderCell>{t('audit.col.user')}</TableHeaderCell> <TableHeaderCell title={t('audit.col.businessKey')}>{t('audit.col.businessKey')}</TableHeaderCell>
<TableHeaderCell>{t('audit.col.scope')}</TableHeaderCell> <TableHeaderCell title={t('audit.col.user')}>{t('audit.col.user')}</TableHeaderCell>
<TableHeaderCell>{t('audit.col.trace')}</TableHeaderCell> <TableHeaderCell title={t('audit.col.scope')}>{t('audit.col.scope')}</TableHeaderCell>
<TableHeaderCell>{t('audit.col.diff')}</TableHeaderCell> <TableHeaderCell title={t('audit.col.trace')}>{t('audit.col.trace')}</TableHeaderCell>
<TableHeaderCell title={t('audit.col.diff')}>{t('audit.col.diff')}</TableHeaderCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@@ -839,6 +839,11 @@ function DictionaryDetail() {
search: (prev) => ({ search: (prev) => ({
...prev, ...prev,
tab: tab === 'records' ? undefined : tab, tab: tab === 'records' ? undefined : tab,
// QA UX-003: сбросить records-tab фильтры (search/q + column filters
// + scopes) когда пользователь перешёл на другую вкладку. Иначе
// search:'абв' оставался применённым после возврата на records.
q: undefined,
scopes: undefined,
}), }),
replace: true, replace: true,
}) })
@@ -281,12 +281,15 @@ function DictionariesPage() {
// Scope chips всегда покрашены в свой scope-color (filled bg // Scope chips всегда покрашены в свой scope-color (filled bg
// + colored text) per user feedback ("плашки то покрась"). // + colored text) per user feedback ("плашки то покрась").
// Selected state — ring-1 ring-{scope} + slightly opaque bg. // Selected state — ring-1 ring-{scope} + slightly opaque bg.
className={`h-8 text-cap tracking-[0.14em] uppercase font-semibold px-2.5 rounded-md border inline-flex items-center transition-all focus:outline-none focus:ring-2 focus:ring-accent/40 ${SCOPE_BG_TINT[scope]} ${SCOPE_TEXT[scope]} ${ // QA UX-001: ✓ glyph когда selected — clarifies что это
// multi-select (checkbox-like), не radio.
className={`h-8 text-cap tracking-[0.14em] uppercase font-semibold px-2.5 rounded-md border inline-flex items-center gap-1 transition-all focus:outline-none focus:ring-2 focus:ring-accent/40 ${SCOPE_BG_TINT[scope]} ${SCOPE_TEXT[scope]} ${
selected selected
? `border-current shadow-sm` ? `border-current shadow-sm`
: 'border-transparent opacity-70 hover:opacity-100' : 'border-transparent opacity-70 hover:opacity-100'
}`} }`}
> >
{selected && <span aria-hidden className="text-[10px]"></span>}
{scope} {scope}
</button> </button>
) )