diff --git a/public/admin.js b/public/admin.js
index bc6df29..8e95f2b 100644
--- a/public/admin.js
+++ b/public/admin.js
@@ -23,6 +23,7 @@ function doLogout() {
const NAV = [
{ page: 'dashboard', label: 'Дашборд' },
{ page: 'journal', label: 'Журнал' },
+ { page: 'files', label: 'Файлы' },
{ page: 'links', label: 'Ссылки' },
{ page: 'students', label: 'Ученики' },
{ page: 'groups', label: 'Группы' },
diff --git a/public/files.html b/public/files.html
new file mode 100644
index 0000000..9b26494
--- /dev/null
+++ b/public/files.html
@@ -0,0 +1,158 @@
+
+
+
+
+
+Файлы — Админ-панель
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server.js b/server.js
index cd3007f..24ba1c3 100644
--- a/server.js
+++ b/server.js
@@ -514,6 +514,40 @@ function isImageName(name) {
return /\.(jpe?g|png|gif|webp|bmp|avif|svg|ico)$/i.test(name || '');
}
+app.get('/api/files', requireAdmin, async (req, res) => {
+ const { search, student_name, group_id, date_from, date_to, limit, offset } = req.query;
+ const conditions = [];
+ const params = [];
+ if (search) { params.push(`%${search}%`); conditions.push(`pf.name ILIKE $${params.length}`); }
+ if (student_name) { params.push(student_name); conditions.push(`e.student_name = $${params.length}`); }
+ if (group_id) { params.push(group_id); conditions.push(`e.group_id = $${params.length}`); }
+ if (date_from) { params.push(date_from); conditions.push(`e.created_at >= $${params.length}::date`); }
+ if (date_to) { params.push(date_to); conditions.push(`e.created_at < ($${params.length}::date + interval '1 day')`); }
+ const where = conditions.length ? ' WHERE ' + conditions.join(' AND ') : '';
+ const { rows: crows } = await pool.query(
+ `SELECT count(*)::int AS n FROM project_files pf JOIN entries e ON e.id = pf.entry_id${where}`,
+ params
+ );
+ const total = crows[0].n;
+ let q = `SELECT pf.id, pf.token, pf.name, pf.path, e.student_name, e.created_at, g.name AS group_name
+ FROM project_files pf
+ JOIN entries e ON e.id = pf.entry_id
+ JOIN groups g ON g.id = e.group_id${where}
+ ORDER BY pf.id DESC`;
+ const qparams = params.slice();
+ const lim = parseInt(limit, 10);
+ if (lim > 0) { qparams.push(lim); q += ` LIMIT $${qparams.length}`; }
+ const off = parseInt(offset, 10);
+ if (off > 0) { qparams.push(off); q += ` OFFSET $${qparams.length}`; }
+ const { rows } = await pool.query(q, qparams);
+ const files = rows.map(r => {
+ let size = 0;
+ try { size = fs.statSync(path.join(__dirname, r.path)).size; } catch {}
+ return { id: r.id, token: r.token, name: r.name, student_name: r.student_name, group_name: r.group_name, created_at: r.created_at, size };
+ });
+ res.json({ files, total });
+});
+
app.get('/api/files/:token', async (req, res) => {
const { rows } = await pool.query('SELECT path, name FROM project_files WHERE token = $1', [req.params.token]);
if (!rows.length) return res.status(404).json({ error: 'Not found' });