feat(photos): раздел «Фото» — единая галерея всех загруженных фотографий
Новый read-only раздел для просмотра всех фотографий системы:
GET /api/photos собирает через UNION ALL пять источников — главное фото
записи, фото записи (entry_photos), фотохронику групп, фото учеников и фото
тем модулей. Фильтры search/student_name/group_id/date_from/date_to и
пагинация limit/offset, ответ { photos, total }. Не-admin ограничен
своими филиалами (branch_id), чужой group_id отдаёт 403.
Фронтенд: public/photos.html + public/js/photos.js — сетка превью
(/uploads/thumb/...), бейдж источника, описание, ученик/группа, дата,
ссылка на источник, lightbox по клику, поиск с debounce и пагинация.
Навигация: пункт «Фото» в сайдбаре и плитка в быстрых действиях дашборда.
This commit is contained in:
+60
-14
@@ -49,9 +49,10 @@ function buildSidebar(active) {
|
||||
const base = [
|
||||
{ page: 'dashboard', label: 'Дашборд', icon: 'layout-dashboard' },
|
||||
{ page: 'journal', label: 'Журнал', icon: 'book-open' },
|
||||
{ page: 'notifications', label: 'Уведомления', icon: 'bell' },
|
||||
{ page: 'notifications', label: 'Уведомления', icon: 'bell', badge: true },
|
||||
{ page: 'students', label: 'Ученики', icon: 'graduation-cap' },
|
||||
{ page: 'groups', label: 'Группы', icon: 'users' },
|
||||
{ page: 'photos', label: 'Фото', icon: 'Image' },
|
||||
{ page: 'files', label: 'Файлы', icon: 'folder' },
|
||||
{ page: 'links', label: 'Ссылки', icon: 'link' },
|
||||
{ page: 'trash', label: 'Корзина', icon: 'trash-2' },
|
||||
@@ -65,26 +66,65 @@ function buildSidebar(active) {
|
||||
{ page: 'bans', label: 'Блокировки', icon: 'shield-off' },
|
||||
{ page: 'settings', label: 'Настройки', icon: 'settings' },
|
||||
];
|
||||
const nav = [...base, ...(isAdmin() ? adminNav : [])];
|
||||
const userName = currentUser?.name || currentUser?.username || '';
|
||||
const roleLabel = isAdmin() ? 'Администратор' : 'Тутор';
|
||||
const renderItem = n => {
|
||||
const isAct = n.page === active;
|
||||
const badgeHtml = n.badge ? `<span class="sidebar-badge" id="sidebarNotifyBadge" ${notifyUnread > 0 ? '' : 'hidden'}>${notifyUnread > 99 ? '99+' : notifyUnread}</span>` : '';
|
||||
return `<a href="${n.page}.html" data-nav="${n.page}" class="${isAct ? 'active' : ''}" title="${n.label}">
|
||||
<i data-lucide="${n.icon}"></i>
|
||||
<span class="nav-text">${n.label}</span>
|
||||
${badgeHtml}
|
||||
</a>`;
|
||||
};
|
||||
el.innerHTML = `
|
||||
<div class="logo">
|
||||
<span class="logo-ic" id="sidebarLogo"></span>
|
||||
<span id="sidebarName">Админ-панель</span>
|
||||
</div>
|
||||
<div class="user-box">
|
||||
<div class="user-name">${esc(userName)}</div>
|
||||
<div class="user-role">${roleLabel}</div>
|
||||
<div class="user-avatar"><i data-lucide="${isAdmin() ? 'shield-check' : 'user'}"></i></div>
|
||||
<div class="user-meta">
|
||||
<div class="user-name" title="${esc(userName)}">${esc(userName)}</div>
|
||||
<div class="user-role">${roleLabel}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-scroll-wrap">
|
||||
<nav>
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-title">Основное</div>
|
||||
<div class="nav-items">
|
||||
${base.map(renderItem).join('')}
|
||||
</div>
|
||||
</div>
|
||||
${isAdmin() ? `
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-title">Администрирование</div>
|
||||
<div class="nav-items">
|
||||
${adminNav.map(renderItem).join('')}
|
||||
</div>
|
||||
</div>` : ''}
|
||||
</nav>
|
||||
</div>
|
||||
<nav>
|
||||
${nav.map(n => `<a href="${n.page}.html" data-nav="${n.page}" class="${n.page === active ? 'active' : ''}"><i data-lucide="${n.icon}"></i>${n.label}</a>`).join('')}
|
||||
</nav>
|
||||
<div class="bottom">
|
||||
<div class="version" id="sidebarVersion"></div>
|
||||
<a href="#" class="notify-bell" id="notifyBell" title="Уведомления"><i data-lucide="bell"></i>Уведомления<span class="notify-badge" id="notifyBadge" hidden>0</span></a>
|
||||
<a href="/" data-nav="home"><i data-lucide="home"></i>На главную</a>
|
||||
<a class="logout" href="#" data-action="logout"><i data-lucide="log-out"></i>Выйти</a>
|
||||
<div class="bottom-actions">
|
||||
<a href="#" class="notify-bell" id="notifyBell" title="Быстрые уведомления">
|
||||
<i data-lucide="bell"></i>
|
||||
<span>Уведомления</span>
|
||||
<span class="notify-badge" id="notifyBadge" ${notifyUnread > 0 ? '' : 'hidden'}>${notifyUnread > 99 ? '99+' : notifyUnread}</span>
|
||||
</a>
|
||||
<a href="/" data-nav="home" title="Перейти на главную страницу">
|
||||
<i data-lucide="home"></i>
|
||||
<span>На главную</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="bottom-footer">
|
||||
<a class="logout-btn" href="#" data-action="logout" title="Выйти из аккаунта">
|
||||
<i data-lucide="log-out"></i>
|
||||
<span>Выйти</span>
|
||||
</a>
|
||||
<span class="version" id="sidebarVersion"></span>
|
||||
</div>
|
||||
</div>`;
|
||||
renderIcons();
|
||||
loadVersion();
|
||||
@@ -162,9 +202,15 @@ function notifyTime(ts) {
|
||||
function setNotifyUnread(n) {
|
||||
notifyUnread = Number(n) || 0;
|
||||
const badge = document.getElementById('notifyBadge');
|
||||
if (!badge) return;
|
||||
badge.textContent = notifyUnread > 99 ? '99+' : String(notifyUnread);
|
||||
badge.hidden = notifyUnread <= 0;
|
||||
if (badge) {
|
||||
badge.textContent = notifyUnread > 99 ? '99+' : String(notifyUnread);
|
||||
badge.hidden = notifyUnread <= 0;
|
||||
}
|
||||
const navBadge = document.getElementById('sidebarNotifyBadge');
|
||||
if (navBadge) {
|
||||
navBadge.textContent = notifyUnread > 99 ? '99+' : String(notifyUnread);
|
||||
navBadge.hidden = notifyUnread <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
function notifyItemHtml(n, expanded) {
|
||||
|
||||
Reference in New Issue
Block a user