Часовой пояс и формат времени (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.
43 lines
2.4 KiB
HTML
43 lines
2.4 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>Ошибка — WhatIDo</title>
|
|
<link rel="stylesheet" href="/admin.css">
|
|
<style>
|
|
.error-page{display:flex;align-items:center;justify-content:center;min-height:100dvh;padding:24px;text-align:center}
|
|
.error-card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);padding:48px 40px;max-width:400px;width:100%}
|
|
.error-code{font-size:6rem;font-weight:700;color:var(--accent);line-height:1;margin-bottom:8px}
|
|
.error-title{font-size:1.5rem;font-weight:600;color:var(--text);margin-bottom:12px}
|
|
.error-message{color:var(--muted);font-size:1rem;line-height:1.6;margin-bottom:24px}
|
|
.error-actions{display:flex;gap:12px;justify-content:center;flex-wrap:wrap}
|
|
.error-actions a,.error-actions button{background:var(--accent);color:#fff;border:none;border-radius:8px;padding:12px 24px;font-size:.9rem;font-weight:600;cursor:pointer;text-decoration:none;transition:opacity .15s}
|
|
.error-actions a:hover,.error-actions button:hover{opacity:.85}
|
|
.error-actions .ghost{background:var(--card);border:1px solid var(--border);color:var(--accent)}
|
|
.error-actions .ghost:hover{border-color:var(--accent);background:rgba(37,99,235,.06)}
|
|
.error-details{margin-top:24px;padding-top:24px;border-top:1px solid var(--border);font-size:.75rem;color:var(--muted);text-align:left}
|
|
.error-details pre{white-space:pre-wrap;word-break:break-word;font-family:monospace;background:var(--bg);padding:12px;border-radius:8px;margin-top:8px;max-height:200px;overflow:auto}
|
|
@media(max-width:480px){.error-code{font-size:4rem}.error-card{padding:32px 20px}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="error-page">
|
|
<div class="error-card">
|
|
<div class="error-code" id="errorCode">404</div>
|
|
<h1 class="error-title" id="errorTitle">Страница не найдена</h1>
|
|
<p class="error-message" id="errorMessage">Запрашиваемая страница не существует или была перемещена.</p>
|
|
<div class="error-actions">
|
|
<a href="/" class="ghost">На главную</a>
|
|
<button type="button" id="backBtn">Назад</button>
|
|
</div>
|
|
<div class="error-details" id="errorDetails" style="display:none">
|
|
<strong>Детали:</strong>
|
|
<pre id="errorStack"></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script src="/js/datetime.js"></script>
|
|
<script src="/js/error.js"></script>
|
|
</body>
|
|
</html> |