const PAGE_SIZE = 24; let page = 1; let totalPhotos = 0; let currentPhotos = []; let groupsLoaded = false; let studentsLoaded = false; let sourcesLoaded = false; let searchTimer = null; function humanSize(b) { if (!b) return ''; if (b < 1024) return b + ' Б'; if (b < 1048576) return (b / 1024).toFixed(1) + ' КБ'; return (b / 1048576).toFixed(1) + ' МБ'; } function photoLink(p) { if (p.source_type === 'entry') return `journal.html#entry-${p.source_id}`; if (p.source_type === 'entry_photo') return `journal.html#entry-${p.source_id}`; if (p.source_type === 'group_photo') return 'groups.html'; if (p.source_type === 'student_photo') return 'students.html'; if (p.source_type === 'module_photo') return 'modules.html'; return ''; } function photoCardHTML(p) { const url = p.path; const meta = []; if (p.student_name) meta.push(esc(p.student_name)); if (p.group_name) meta.push(esc(p.group_name)); const link = photoLink(p); return `
${esc(p.source_label)}
${esc(p.title || 'Без описания')}
${meta.length ? `
${meta.join(' · ')}
` : ''}
${fmtFull(p.created_at)}
${link ? `Открыть источник →` : ''}
`; } function renderGrid() { const grid = document.getElementById('grid'); if (!currentPhotos.length) { grid.innerHTML = '
Фотографий не найдено
'; return; } grid.innerHTML = currentPhotos.map(photoCardHTML).join(''); } async function loadGroupOptions() { if (groupsLoaded) return; const sel = document.getElementById('groupFilter'); while (sel.options.length > 1) sel.remove(1); const res = await fetch(`${API}/api/groups`, { headers: hdr() }); const groups = await res.json(); groups.forEach(g => { const o = document.createElement('option'); o.value = g.id; o.textContent = g.name; sel.appendChild(o); }); groupsLoaded = true; } async function loadStudentOptions() { if (studentsLoaded) return; const dl = document.getElementById('studentFilterList'); const res = await fetch(`${API}/api/students/names`, { headers: hdr() }); const names = await res.json(); dl.innerHTML = names.map(n => `