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|jfif|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 => `