Files
WhatIDo/public/groups.html
T
dev 3c127b895e 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.
2026-10-04 10:49:48 +03:00

144 lines
6.8 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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="groups">
<div class="layout">
<div class="sidebar" id="sidebar"></div>
<div class="main">
<h2>Группы</h2>
<div class="add-row" style="align-items:center;gap:8px">
<input type="text" id="newGroup" placeholder="Название группы" style="flex:1;max-width:300px">
<select id="newGroupBranch" style="background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:8px;padding:8px 12px;font-size:.85rem;min-width:180px"><option value="">— без филиала —</option></select>
<select id="newGroupTutor" style="background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:8px;padding:8px 12px;font-size:.85rem;min-width:160px"><option value="">— не назначен —</option></select>
<button type="button" id="addGroupBtn">Добавить</button>
</div>
<div class="list" id="groupList"></div>
</div>
</div>
<div class="modal-overlay" id="photosModal">
<div class="gallery-modal">
<div class="gallery-head">
<h3 id="photosTitle">Фото</h3>
<button class="upload-btn" type="button" id="openPhotoBtn">Загрузить фото</button>
</div>
<div class="photo-grid" id="photoGrid"><div class="empty">Загрузка...</div></div>
<div class="pager" id="photosPager"></div>
<div class="gallery-foot">
<button class="cancel" type="button" id="closePhotosBtn">Закрыть</button>
</div>
</div>
</div>
<div class="modal-overlay" id="photoFormModal">
<div class="edit-modal" style="max-width:480px">
<h3 id="photoFormTitle">Добавить фото</h3>
<form id="photoForm" class="settings-stack" style="gap:12px">
<div class="settings-row">
<div class="settings-field" style="flex:1">
<label>Выбрать файл (jpg/png и т.п.) <span style="color:#ef4444">*</span></label>
<input type="file" id="photoFile" accept="image/*">
</div>
</div>
<div class="settings-row">
<div class="settings-field" style="flex:1">
<div id="photoFormPreview" class="photo-preview" style="display:none"><img id="photoPreviewImg" alt=""></div>
</div>
</div>
<div class="settings-row">
<div class="settings-field" style="flex:1">
<label>Подпись</label>
<textarea id="photoCaption" class="settings-input" placeholder="Например: выступление, тренировка..."></textarea>
</div>
</div>
<div class="settings-row">
<div class="settings-field" style="flex:1">
<label>Дата съёмки</label>
<input type="date" id="photoTakenAt" class="settings-input">
</div>
</div>
<div class="card-foot" style="justify-content:flex-end;gap:8px">
<button type="button" class="btn-primary ghost" id="closePhotoFormBtn">Отмена</button>
<button type="submit" class="btn-primary">Сохранить</button>
</div>
</form>
</div>
</div>
<div class="toast" id="toast"></div>
<div class="modal-overlay" id="groupFilesModal">
<div class="edit-modal" style="max-width:440px">
<h3 id="groupFilesTitle">Архив файлов группы</h3>
<form id="groupFilesForm" class="settings-stack" style="gap:14px">
<div class="settings-row">
<div class="settings-field" style="flex:1">
<label>Группа <span style="color:#ef4444">*</span></label>
<select id="groupFilesGroup" class="settings-input" required></select>
</div>
</div>
<div class="settings-row">
<div class="settings-field" style="flex:1">
<label>Дата с</label>
<input type="date" id="groupFilesFrom" class="settings-input">
</div>
<div class="settings-field" style="flex:1">
<label>Дата по</label>
<input type="date" id="groupFilesTo" class="settings-input">
</div>
</div>
<div class="settings-row">
<div class="settings-field" style="flex:1">
<div class="hint" style="font-size:.78rem;color:var(--muted);margin:0">Файлы всех учеников группы за период. По умолчанию — с самого начала и по сегодня.</div>
</div>
</div>
<div class="settings-field full">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
<input type="checkbox" id="groupFilesIncludeWorks" checked>
Работы (файлы проектов)
</label>
</div>
<div class="settings-field full">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
<input type="checkbox" id="groupFilesIncludePhotos">
Фото записи
</label>
</div>
<div class="settings-field full">
<label>Какие фото выгружать</label>
<div style="display:flex;flex-direction:column;gap:8px">
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-weight:400">
<input type="checkbox" id="groupFilesMainPhoto" checked>
Главное фото каждой записи
</label>
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-weight:400">
<input type="checkbox" id="groupFilesGallery">
Вся галерея записи
</label>
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-weight:400">
<input type="checkbox" id="groupFilesOriginals">
Оригиналы фото (до обработки)
</label>
</div>
<span class="hint" style="font-size:.78rem">Главное фото — текущая версия снимка записи. Галерея — все фото записи. Оригиналы — версии до обработки, они попадают в подпапку «Оригиналы», если обработка уже применялась.</span>
</div>
<div class="card-foot" style="justify-content:flex-end;gap:8px">
<button type="button" class="btn-primary ghost" id="closeGroupFilesBtn">Отмена</button>
<button type="submit" class="btn-primary" id="downloadGroupFilesBtn">Скачать ZIP</button>
</div>
</form>
</div>
</div>
<script src="vendor/lucide.min.js"></script>
<script src="js/datetime.js"></script>
<script src="admin.js"></script>
<script src="vendor/jszip.min.js"></script>
<script src="js/groups.js"></script>
</body>
</html>