Часовой пояс и формат времени (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.
98 lines
4.5 KiB
HTML
98 lines
4.5 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">
|
||
</head>
|
||
<body data-page="links">
|
||
<div class="layout">
|
||
<div class="sidebar" id="sidebar"></div>
|
||
<div class="main">
|
||
<h2>Ссылки</h2>
|
||
<p style="color:var(--muted);font-size:.85rem;margin-bottom:20px;max-width:680px">Каждая ссылка открывает публичную страницу с карточками записей выбранной группы за указанный период — без входа и выбора параметров.</p>
|
||
<div class="list" id="linksList"><div class="empty">Загрузка...</div></div>
|
||
<div class="pager" id="pager"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-overlay" id="linkModal">
|
||
<div class="edit-modal" style="max-width:640px">
|
||
<h3 id="linkModalTitle">Новая ссылка</h3>
|
||
<div class="link-fields">
|
||
<div class="settings-field">
|
||
<label>Название <span style="color:#ef4444">*</span></label>
|
||
<input type="text" id="linkName" class="settings-input" required placeholder="Например: СБ1400 05.09">
|
||
</div>
|
||
<div class="settings-field">
|
||
<label>Группа</label>
|
||
<select id="linkGroup" class="settings-input"><option value="">— без группы —</option></select>
|
||
</div>
|
||
<div class="settings-field">
|
||
<label>Ученик (необязательно)</label>
|
||
<input type="text" id="linkStudent" class="settings-input" list="linkStudentList" placeholder="Все / введите имя..."><datalist id="linkStudentList"></datalist>
|
||
</div>
|
||
<div class="settings-field">
|
||
<label>Ссылка</label>
|
||
<input type="url" id="linkUrl" class="settings-input" placeholder="https://...">
|
||
</div>
|
||
<div class="settings-field full">
|
||
<label>Сообщение</label>
|
||
<textarea id="linkMessage" class="settings-input" rows="3" placeholder="Текст, который увидят на публичной странице"></textarea>
|
||
</div>
|
||
<div class="settings-field">
|
||
<label>С</label>
|
||
<input type="date" id="linkDateFrom" class="settings-input">
|
||
</div>
|
||
<div class="settings-field">
|
||
<label>По</label>
|
||
<input type="date" id="linkDateTo" class="settings-input">
|
||
</div>
|
||
<div class="settings-field">
|
||
<label>Истекает через (дни)</label>
|
||
<input type="number" id="linkExpiresDays" class="settings-input" min="1" max="365" value="7" placeholder="7">
|
||
</div>
|
||
<div class="settings-field">
|
||
<label>Пароль для доступа (необязательно)</label>
|
||
<input type="password" id="linkPassword" class="settings-input" placeholder="Оставьте пустым для открытого доступа">
|
||
</div>
|
||
<div class="settings-field full">
|
||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
||
<input type="checkbox" id="linkShowNames">
|
||
Показывать фамилии и имена учеников
|
||
</label>
|
||
</div>
|
||
<div class="settings-field full">
|
||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
||
<input type="checkbox" id="linkShowMessage">
|
||
Показать сообщение ученика
|
||
</label>
|
||
</div>
|
||
<div class="settings-field full">
|
||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
||
<input type="checkbox" id="linkShowDate">
|
||
Показать дату в карточке записи
|
||
</label>
|
||
</div>
|
||
<div class="settings-field full">
|
||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
||
<input type="checkbox" id="linkShowPhotos">
|
||
Показывать фото группы
|
||
</label>
|
||
</div>
|
||
<div class="card-foot full" style="justify-content:flex-end;gap:8px">
|
||
<button type="button" class="btn-primary ghost" id="linkCancelBtn">Отмена</button>
|
||
<button type="button" class="btn-primary" id="linkSaveBtn">Сохранить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</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/links.js"></script>
|
||
</body>
|
||
</html> |