929bf9f45b
Считать портал уже научился по московским суткам. Показывал он вразнобой: часть страниц просила браузер явно про Москву, двадцать мест оставляли пояс на его усмотрение. Человек за пределами Москвы видел у заявки не тот день, что в отчёте, — при том что фильтры и отчёты считают московские сутки. Двадцать мест поправлены: заявки, счета, проводки, сессии и смена пароля, происшествия, запросы субъектов ПДн, ступени цен, интеграция с поставщиком, проекты поставщика, система, посетители, вход под клиентом, кампании, поле автоподбора, СМС и прогрев у отдела продаж, карточка клиента. Одно место оставлено гринвичским НАМЕРЕННО и с объяснением рядом: firstLeadDate собирает дату из уже московских чисел через Date.UTC, и повторный перевод сдвинул бы день назад. Сторож tools/storozh-moskovskogo-vremeni.mjs держит правило дальше: любая печать времени обязана НАЗВАТЬ пояс. Он отличает время от денег - тот же вызов toLocaleString печатает и суммы, им пояс не нужен. Приёмка вырезанием в обе стороны: до правки сторож нашёл ровно 20 мест, после - ноль при 303 проверенных файлах; на пустой папке честно говорит "датчик сломан", а не "чисто". Своих проверок у сторожа десять, написаны до него. Прогон интерфейса: 232 файла проверок, 1743 зелёных, 0 падений. Проверка типов и разметка: 7 замечаний, все были ДО правки и не в моих файлах (проверено прятанием своих правок и повторным прогоном). Заодно выметен мусор в корне: файлы с именами 1', 140, 2771, 3042, 3404, out.txt и app/toArray-скобки - обрывки чужих незакавыченных перенаправлений. Заодно убрана мёртвая функция dirLabel в экране автоподбора: её никто не звал, она была там до этой правки, и линтер из-за неё не пропускал весь файл. Решение владельца. Прогон экрана: 15 файлов проверок, 84 зелёных.
44 lines
1.4 KiB
TypeScript
44 lines
1.4 KiB
TypeScript
/**
|
||
* Sprint 4 Phase B/1 (audit O-refactor-04 хвост): formatters для AdminTenantDetailView
|
||
* + sub-components TenantDetailHeader / TenantDetailTabs.
|
||
*/
|
||
|
||
export function formatRub(v: number): string {
|
||
if (v === 0) return '0 ₽';
|
||
const sign = v < 0 ? '−' : '';
|
||
return sign + new Intl.NumberFormat('ru-RU').format(Math.abs(v)) + ' ₽';
|
||
}
|
||
|
||
export function formatDate(iso: string): string {
|
||
return new Date(iso).toLocaleString('ru-RU', { dateStyle: 'short', timeStyle: 'short', timeZone: 'Europe/Moscow' });
|
||
}
|
||
|
||
export function statusColor(s: string): string {
|
||
if (s === 'active') return 'success';
|
||
if (s === 'trial') return 'info';
|
||
if (s === 'overdue') return 'warning';
|
||
return 'error';
|
||
}
|
||
|
||
export function txTypeLabel(t: string): string {
|
||
return (
|
||
{
|
||
topup: 'Пополнение',
|
||
lead_charge: 'Списание за лид',
|
||
refund: 'Возврат',
|
||
tariff_charge: 'Списание тарифа',
|
||
manual_adjustment: 'Ручная корректировка',
|
||
}[t] ?? t
|
||
);
|
||
}
|
||
|
||
export function txTypeColor(t: string, amount: number): string {
|
||
if (amount > 0) return 'success';
|
||
if (t === 'manual_adjustment') return 'warning';
|
||
return 'error';
|
||
}
|
||
|
||
export function roleLabel(r: string): string {
|
||
return { admin: 'Администратор', manager: 'Менеджер', reader: 'Читатель' }[r] ?? r;
|
||
}
|