feat(datetime): timezone/time_format в настройках + единый хелпер дат

Часовой пояс и формат времени (24h/12h) перенесены из браузера в настройки
приложения: валидация IANA-зоны, сид в db/init.sql + db/migration.sql, отдача
в GET /api/public-settings, раздел #sec-datetime в настройках с живым превью.

Границы суток в SQL переведены на tzDayStart/tzDayEnd + bindTz ($TZ$) — раньше
$n::date по TIMESTAMPTZ считал дни в UTC (у контейнера TimeZone=UTC) и молча
сдвигал выборку на день; хардкод Europe/Moscow вычищен, now() заменён на
tzWall().

Новый public/js/datetime.js: три семейства хелперов (instant / чистая
DATE-строка без Date() / чистая TIME-строка), подключён на всех страницах
включая публичные share/report/index, initDateTime() встроен в checkAuth().
Прямые toLocale*/getFullYear/toISOString().slice(0,10) в public/ выпилены.

Попутно: bindTz добавляет параметр только при наличии $TZ$ в SQL (иначе запрос
виснет вечно без global error handler) — регрессия закрыта в api.smoketest.js;
починены off-by-one месяца в report.js и группировка по дате в share.js.
This commit is contained in:
dev
2026-10-04 10:49:48 +03:00
parent 19b2365b4c
commit 3c127b895e
44 changed files with 456 additions and 105 deletions
+26
View File
@@ -22,6 +22,7 @@
<a href="#sec-system" data-nav="sec-system" class="active"><i data-lucide="bar-chart-3"></i> Система</a>
<a href="#sec-stack" data-nav="sec-stack"><i data-lucide="server"></i> Стек</a>
<a href="#sec-brand" data-nav="sec-brand"><i data-lucide="palette"></i> Брендинг</a>
<a href="#sec-datetime" data-nav="sec-datetime"><i data-lucide="clock"></i> Дата и время</a>
<a href="#sec-antispam" data-nav="sec-antispam"><i data-lucide="shield"></i> Антиспам</a>
<a href="#sec-share" data-nav="sec-share"><i data-lucide="link"></i> Публичные ссылки</a>
<a href="#sec-form" data-nav="sec-form"><i data-lucide="file-text"></i> Форма ответа</a>
@@ -91,6 +92,30 @@
</div>
</div>
<div class="settings-card" id="sec-datetime">
<div class="card-head">
<div class="ic"><i data-lucide="clock"></i></div>
<h3>Дата и время</h3>
</div>
<p class="hint">Часовой пояс, в котором показываются даты и время во всех разделах, и формат времени. На сервере дата в базе остаётся той же — меняется только отображение.</p>
<div class="settings-row">
<div class="settings-field">
<label for="timezone">Часовой пояс</label>
<select id="timezone" class="settings-input"></select>
<span class="hint">Определяет, какое время «сейчас» в расписании групп и границы дней в фильтрах</span>
</div>
<div class="settings-field">
<label for="timeFormat">Формат времени</label>
<select id="timeFormat" class="settings-input">
<option value="24h">24 часа (00:30)</option>
<option value="12h">12 часов AM/PM (12:30 PM)</option>
</select>
<span class="hint">Поля ввода времени остаются в формате 24 часов</span>
</div>
</div>
<div class="data-stats" id="timePreview"></div>
</div>
<div class="settings-card" id="sec-antispam">
<div class="card-head">
<div class="ic"><i data-lucide="shield"></i></div>
@@ -412,6 +437,7 @@
<div class="toast" id="toast"></div>
<script src="vendor/lucide.min.js"></script>
<script src="js/datetime.js"></script>
<script src="admin.js"></script>
<script src="js/settings.js"></script>
</body>