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:
@@ -30,22 +30,22 @@ function renderStats(s, disk) {
|
||||
|
||||
function last14days() {
|
||||
const out = [];
|
||||
for (let i = 13; i >= 0; i--) { const d = new Date(); d.setDate(d.getDate() - i); out.push(d); }
|
||||
const base = todayIso();
|
||||
for (let i = 13; i >= 0; i--) out.push(isoAddDays(base, -i));
|
||||
return out;
|
||||
}
|
||||
|
||||
function renderActivity(activity) {
|
||||
const map = {};
|
||||
activity.forEach(a => { map[a.d] = a.n; });
|
||||
const days = last14days().map(d => {
|
||||
const iso = d.toISOString().slice(0, 10);
|
||||
return { iso, label: d.toLocaleDateString('ru', { day: '2-digit', month: '2-digit' }), n: map[iso] || 0 };
|
||||
const days = last14days().map(iso => {
|
||||
return { iso, label: fmtDayMonthIso(iso), n: map[iso] || 0 };
|
||||
});
|
||||
const max = Math.max(1, ...days.map(x => x.n));
|
||||
const todayIso = new Date().toISOString().slice(0, 10);
|
||||
const todayKey = todayIso();
|
||||
document.getElementById('activityChart').innerHTML = days.map(x => `
|
||||
<div class="bar-col" title="${x.iso} — ${x.n} ${plural(x.n, 'запись', 'записи', 'записей')}">
|
||||
<div class="bar ${x.iso === todayIso ? 'today' : ''}" style="height:${Math.round(x.n / max * 100)}%"></div>
|
||||
<div class="bar ${x.iso === todayKey ? 'today' : ''}" style="height:${Math.round(x.n / max * 100)}%"></div>
|
||||
<div class="bar-day">${x.label}</div>
|
||||
</div>`).join('');
|
||||
}
|
||||
@@ -60,7 +60,7 @@ function renderRecent(rows) {
|
||||
: `<div class="thumb"><i data-lucide="graduation-cap"></i></div>`}
|
||||
<div class="r-info">
|
||||
<div class="r-name">${esc(e.student_name)}</div>
|
||||
<div class="r-meta">${esc(e.group_name)} · ${new Date(e.created_at).toLocaleString('ru', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' })}</div>
|
||||
<div class="r-meta">${esc(e.group_name)} · ${fmtDateTime(e.created_at)}</div>
|
||||
</div>
|
||||
<a class="r-link" href="journal.html" title="В журнал">→</a>
|
||||
</div>`).join('');
|
||||
|
||||
Reference in New Issue
Block a user