feat: режим индикаторов без цветовой кодировки + переустройство /setup
Новая настройка app_settings.indicator_mode (color | accessible), переключается на /setup. В режиме accessible сигналы, которые раньше держались только на цвете, переходят на форму/текст: - качество цены (0-9) — 5-ступенчатый signal-meter вместо градиентной точки/числа; единый модуль web/static/price-quality.js, ветвление по window.QF_INDICATOR_MODE; - вкладка «Ценообразование»: ведущая колонка-meter вместо заливки строк, пометка W вместо амбер-подсветки world-цен, ⚠ вместо красного «загрязнённого» итога. - GET/PUT /api/settings/ui (без рестарта), регистрируется в обоих наборах роутов. /setup переустроен: две колонки одной высоты, кнопка «Вернуться в приложение», исправлен <title>. Документация: bible-local 02/03/04 + decisions/2026-08-31-accessible-indicator-mode.md Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LbRvQgPZpM4SJTaX3iLrXk
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
d97ac447ca
commit
681ec15e2b
@@ -1,16 +1,25 @@
|
||||
// Shared LOT price-quality color scale, used everywhere price_quality is
|
||||
// Shared LOT price-quality indicator, used everywhere price_quality is
|
||||
// displayed (pricelist detail page, configurator search dropdown, configurator
|
||||
// table, pricing tab). price_quality is a 0-9 score set by the external
|
||||
// pricing tool (see bible-local/03-database.md, qt_pricelist_items.price_quality)
|
||||
// — QF only displays it, never computes it. Single source of the color scale
|
||||
// so every view stays visually consistent.
|
||||
// table, pricing tab). price_quality is a 0-9 score set by the external pricing
|
||||
// tool (see bible-local/03-database.md, qt_pricelist_items.price_quality) — QF
|
||||
// only displays it, never computes it. Single source of the visual scale so
|
||||
// every view stays consistent.
|
||||
//
|
||||
// Gradient: 0 = red, 5 = yellow, 9 = green.
|
||||
// Two render modes, chosen per client via window.QF_INDICATOR_MODE (injected by
|
||||
// base.html from app_settings, key indicator_mode):
|
||||
// "color" (default) — hue scale, 0 = red, 5 = yellow, 9 = green.
|
||||
// "accessible" — colour-blind-safe 5-step signal-strength meter,
|
||||
// single neutral hue, magnitude encoded by bar count.
|
||||
// See bible-local/decisions/2026-08-31-accessible-indicator-mode.md.
|
||||
(function () {
|
||||
const RED = [220, 38, 38];
|
||||
const YELLOW = [234, 179, 8];
|
||||
const GREEN = [22, 163, 74];
|
||||
|
||||
function isAccessible() {
|
||||
return window.QF_INDICATOR_MODE === 'accessible';
|
||||
}
|
||||
|
||||
function lerp(c1, c2, t) {
|
||||
return c1.map((v, i) => Math.round(v + (c2[i] - v) * t));
|
||||
}
|
||||
@@ -28,27 +37,53 @@
|
||||
return rgb ? `rgb(${rgb})` : null;
|
||||
}
|
||||
|
||||
// Small colored dot for compact contexts (search dropdown, configurator table cell).
|
||||
// 0-9 score -> 0-4 meter level. 0-1 / 2-3 / 4-5 / 6-7 / 8-9.
|
||||
function priceQualityLevel(quality) {
|
||||
if (typeof quality !== 'number' || Number.isNaN(quality)) return null;
|
||||
const q = Math.max(0, Math.min(9, quality));
|
||||
return Math.min(4, Math.floor(q / 2));
|
||||
}
|
||||
|
||||
// Monochrome 5-step signal-strength meter. size: undefined | 'sm'.
|
||||
function qualityMeterHtml(quality, opts) {
|
||||
const level = priceQualityLevel(quality);
|
||||
if (level === null) return '';
|
||||
const size = opts && opts.size === 'sm' ? ' data-size="sm"' : '';
|
||||
const title = `Качество цены: ${quality}/9`;
|
||||
let bars = '';
|
||||
for (let i = 0; i < 5; i++) {
|
||||
bars += `<i class="${i <= level ? 'on' : ''}"></i>`;
|
||||
}
|
||||
return `<span class="qf-quality-meter"${size} role="img" aria-label="${title}" title="${title}">${bars}</span>`;
|
||||
}
|
||||
|
||||
// Small indicator for compact contexts (search dropdown, configurator table cell).
|
||||
function qualityDotHtml(quality) {
|
||||
if (isAccessible()) return qualityMeterHtml(quality, { size: 'sm' });
|
||||
const color = priceQualityColor(quality);
|
||||
if (!color) return '';
|
||||
return `<span class="inline-block w-2 h-2 rounded-full" style="background-color: ${color}" title="Качество цены: ${quality}/9"></span>`;
|
||||
}
|
||||
|
||||
// Colored number badge for a dedicated "quality" column/cell.
|
||||
// Indicator for a dedicated "quality" column/cell.
|
||||
function qualityBadgeHtml(quality) {
|
||||
if (typeof quality !== 'number') return '<span class="text-gray-400">-</span>';
|
||||
if (isAccessible()) return qualityMeterHtml(quality, { size: 'sm' });
|
||||
const color = priceQualityColor(quality);
|
||||
return `<span class="font-semibold" style="color: ${color}" title="Качество цены: ${quality}/9">${quality}</span>`;
|
||||
}
|
||||
|
||||
// Light row-tint background for table rows, e.g. the pricing tab.
|
||||
// Light row-tint background for table rows, e.g. the pricing tab. Suppressed
|
||||
// in accessible mode — the pricing tab carries its own leading meter column there.
|
||||
function qualityRowStyle(quality) {
|
||||
if (isAccessible()) return '';
|
||||
const rgb = priceQualityRgb(quality);
|
||||
return rgb ? `background-color: rgba(${rgb}, 0.10)` : '';
|
||||
}
|
||||
|
||||
window.priceQualityColor = priceQualityColor;
|
||||
window.priceQualityLevel = priceQualityLevel;
|
||||
window.qualityMeterHtml = qualityMeterHtml;
|
||||
window.qualityDotHtml = qualityDotHtml;
|
||||
window.qualityBadgeHtml = qualityBadgeHtml;
|
||||
window.qualityRowStyle = qualityRowStyle;
|
||||
|
||||
Reference in New Issue
Block a user