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 зелёных.
30 lines
1.9 KiB
TypeScript
30 lines
1.9 KiB
TypeScript
/**
|
|
* Форматирование дат для UI блокировки источника и баннера нового проекта.
|
|
* Спека: docs/superpowers/specs/2026-06-22-project-source-edit-lock-ux-design.md.
|
|
*/
|
|
|
|
/** ISO-строку → «23 июня» (ru). Пусто/невалид → ''. */
|
|
export function formatLeadDate(iso: string | null | undefined): string {
|
|
if (!iso) return '';
|
|
const d = new Date(iso);
|
|
if (Number.isNaN(d.getTime())) return '';
|
|
// По Москве, а не по часам браузера: у заявки, пришедшей в 00:30 МСК,
|
|
// человек из другого пояса иначе увидит вчерашнее число.
|
|
return new Intl.DateTimeFormat('ru-RU', { day: 'numeric', month: 'long', timeZone: 'Europe/Moscow' }).format(d);
|
|
}
|
|
|
|
/**
|
|
* Дата старта лидов нового проекта по правилу слепка 18:00 МСК:
|
|
* создал до 18:00 МСК → лиды с завтра; после 18:00 → с послезавтра.
|
|
* РФ круглый год UTC+3 (без перехода на летнее время).
|
|
*/
|
|
export function firstLeadDate(now: Date = new Date()): string {
|
|
const msk = new Date(now.getTime() + 3 * 3600 * 1000);
|
|
const addDays = msk.getUTCHours() >= 18 ? 2 : 1;
|
|
const target = new Date(Date.UTC(msk.getUTCFullYear(), msk.getUTCMonth(), msk.getUTCDate() + addDays));
|
|
// Здесь Гринвич выбран осознанно, а не забыт: `target` собран из готовых
|
|
// московских чисел через Date.UTC — это уже календарная дата, и переводить
|
|
// её ещё раз в Москву значило бы сдвинуть день назад.
|
|
return new Intl.DateTimeFormat('ru-RU', { day: 'numeric', month: 'long', timeZone: 'UTC' }).format(target);
|
|
}
|