Часовой пояс и формат времени (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.
51 lines
2.6 KiB
HTML
51 lines
2.6 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Фото — Админ-панель</title>
|
||
<link rel="stylesheet" href="admin.css">
|
||
<style>
|
||
.ph-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:14px}
|
||
.ph-card{background:var(--card);border:1px solid var(--border);border-radius:12px;overflow:hidden;display:flex;flex-direction:column;min-width:0}
|
||
.ph-card img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block;cursor:zoom-in;background:var(--bg);transition:opacity .15s}
|
||
.ph-card:hover img{opacity:.9}
|
||
.ph-card .ph-body{padding:10px 12px;display:flex;flex-direction:column;gap:4px;min-width:0}
|
||
.ph-badge{align-self:flex-start;background:var(--bg);border:1px solid var(--border);color:var(--muted);border-radius:999px;padding:2px 8px;font-size:.68rem;font-weight:700}
|
||
.ph-title{font-size:.82rem;font-weight:600;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;word-break:break-word}
|
||
.ph-meta{font-size:.72rem;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||
.ph-time{font-size:.7rem;color:var(--muted)}
|
||
.ph-link{font-size:.72rem;color:var(--accent);text-decoration:none}
|
||
.ph-link:hover{text-decoration:underline}
|
||
</style>
|
||
</head>
|
||
<body data-page="photos">
|
||
<div class="layout">
|
||
<div class="sidebar" id="sidebar"></div>
|
||
<div class="main">
|
||
<h2>Фото</h2>
|
||
<div class="filters">
|
||
<div><label>Поиск</label><input type="text" id="searchInput" class="search-input" placeholder="Описание или имя"></div>
|
||
<div><label>Ученик</label><input type="text" id="studentFilter" list="studentFilterList" placeholder="Все / введите имя..."><datalist id="studentFilterList"></datalist></div>
|
||
<div><label>Группа</label><select id="groupFilter"><option value="">Все группы</option></select></div>
|
||
<div><label>Источник</label><select id="sourceFilter"><option value="">Все источники</option></select></div>
|
||
<div><label>С</label><input type="date" id="dateFrom"></div>
|
||
<div><label>По</label><input type="date" id="dateTo"></div>
|
||
</div>
|
||
<div class="ph-grid" id="grid"><div class="empty" style="grid-column:1/-1">Загрузка...</div></div>
|
||
<div class="pager" id="pager"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-overlay" id="imgModal">
|
||
<img id="imgModalSrc">
|
||
</div>
|
||
|
||
<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/photos.js"></script>
|
||
</body>
|
||
</html>
|