const PAGE_SIZE = 20; let page = 1; let totalFiles = 0; let currentFiles = []; let groupsLoaded = false; let studentsLoaded = false; let searchTimer = null; let mode = 'attached'; function setMode(m) { mode = m === 'detached' ? 'detached' : 'attached'; document.querySelectorAll('#tabToggle button').forEach(b => { b.classList.toggle('active', b.dataset.mode === mode); }); const show = mode === 'attached'; const keep = ['searchInput', 'studentFilter', 'groupFilter', 'dateFrom', 'dateTo'].map(id => document.getElementById(id)); if (!show) { keep.forEach(el => { el.value = ''; }); } keep.forEach(el => { el.parentElement.style.display = show ? '' : 'none'; }); page = 1; loadFiles(); } function isImageFile(name) { return /\.(jpe?g|png|gif|webp|bmp|avif|svg|ico)$/i.test(name || ''); } 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 fileCardHTML(f) { const url = `${API}/api/files/${f.token}`; const thumb = isImageFile(f.name) ? `` : `
📄
`; const target = isImageFile(f.name) ? `href="#" data-img="${esc(url)}"` : `href="${url}" download`; const meta = mode === 'detached' ? `
Откреплён от записи, не привязан
` : `
${esc(f.student_name)} · ${esc(f.group_name)}
`; const action = mode === 'detached' ? `` : ``; return `
${thumb}
${meta}
${new Date(f.created_at).toLocaleString('ru')}${f.size ? ' · ' + humanSize(f.size) : ''}
${action}
`; } function renderGrid() { const grid = document.getElementById('grid'); if (!currentFiles.length) { grid.className = 'grid'; grid.innerHTML = mode === 'detached' ? '
Откреплённых файлов нет
' : '
Файлов не найдено
'; return; } grid.className = 'grid'; grid.innerHTML = currentFiles.map(fileCardHTML).join(''); } async function detachFile(id) { if (!confirm('Открепить файл от записи? Он переместится во вкладку «Откреплённые».')) return; const res = await fetch(`${API}/api/files/${id}/detach`, { method: 'POST', headers: hdr() }); if (res.ok) { showToast('Файл откреплён'); loadFiles(); } else { const err = await res.json().catch(() => ({})); alert(err.error || 'Ошибка'); } } async function deleteFile(id) { if (!confirm('Удалить файл навсегда? Отменить это действие нельзя.')) return; const res = await fetch(`${API}/api/files/${id}`, { method: 'DELETE', headers: hdr() }); if (res.ok) { showToast('Файл удалён'); loadFiles(); } else { const err = await res.json().catch(() => ({})); alert(err.error || 'Ошибка'); } } 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 => `