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:
+6
-7
@@ -240,13 +240,11 @@ function renderShare(data, password) {
|
||||
const showStudentMessage = !!data.show_student_message;
|
||||
const showEntryDate = !!data.show_entry_date;
|
||||
const dayKey = e => {
|
||||
const d = new Date(e.created_at);
|
||||
return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
|
||||
};
|
||||
const dayLabel = k => {
|
||||
const [y,m,d] = k.split('-').map(Number);
|
||||
return new Date(y, m-1, d).toLocaleDateString('ru-RU', { day:'numeric', month:'long', year:'numeric' });
|
||||
try {
|
||||
return new Intl.DateTimeFormat('en-CA', { timeZone: DateTime.tz, year: 'numeric', month: '2-digit', day: '2-digit' }).format(new Date(e.created_at));
|
||||
} catch { return ''; }
|
||||
};
|
||||
const dayLabel = k => fmtDateOnlyLongIso(k);
|
||||
const cardHTML = e => {
|
||||
const body = [
|
||||
!isAnonymized ? `<div class="name">${esc(e.student_name)}</div>` : '',
|
||||
@@ -262,7 +260,7 @@ function renderShare(data, password) {
|
||||
: `<div class="no-photo"><svg viewBox="0 0 240 160" preserveAspectRatio="xMidYMid meet" aria-hidden="true"><circle cx="105" cy="48" r="15"/><path d="M74 140 C74 106 88 92 105 92 C122 92 136 106 136 140 Z"/><circle cx="145" cy="58" r="18"/><path d="M108 144 C108 104 124 88 145 88 C166 88 182 104 182 144 Z"/></svg></div>`}
|
||||
</div>
|
||||
${body ? `<div class="body">${body}</div>` : ''}
|
||||
${showEntryDate ? `<div class="foot">${new Date(e.created_at).toLocaleString('ru',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'})}</div>` : ''}
|
||||
${showEntryDate ? `<div class="foot">${fmtDateTimeY(e.created_at)}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
@@ -288,6 +286,7 @@ document.addEventListener('click', e => {
|
||||
});
|
||||
|
||||
(async()=>{
|
||||
await initDateTime();
|
||||
await loadShare();
|
||||
})();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user