Files
gia-gve-certificates/views/index.ejs
2026-07-01 09:18:06 +05:00

174 lines
6.3 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ученики — Справки ГИА/ГВЭ</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<div class="container">
<h1>Ученики</h1>
<nav class="nav">
<a href="/" class="btn">Список учеников</a>
<a href="/import" class="btn btn-green">Импорт XLS</a>
<span class="user-bar"><%= user ? user.name : '' %></span>
<button id="logout-btn" class="btn btn-logout">Выйти</button>
</nav>
<div class="card batch-panel">
<h2>Пакетная печать</h2>
<div class="form-row">
<label>
<input type="radio" name="batch-scope" value="class" checked onchange="toggleBatchMode()"> Класс
</label>
<select id="class-select" class="input">
<option value="">— загрузка... —</option>
</select>
<label style="margin-left:15px">
<input type="radio" name="batch-scope" value="parallel" onchange="toggleBatchMode()"> Параллель
</label>
<select id="parallel-select" class="input" disabled>
<option value="">— загрузка... —</option>
</select>
<label style="margin-left:15px">
<input type="radio" name="batch-scope" value="all" onchange="toggleBatchMode()"> Все ученики
</label>
</div>
<div class="form-row" style="margin-top:8px">
<label>
<input type="radio" name="batch-type" value="gia" checked> ГИА (ОГЭ)
</label>
<label style="margin-left:15px">
<input type="radio" name="batch-type" value="gve"> ГВЭ
</label>
<button id="batch-print-btn" class="btn btn-blue" style="margin-left:15px">Печать справок</button>
</div>
</div>
<div class="search-bar">
<input type="text" id="search" class="input" placeholder="Поиск по ФИО или классу...">
</div>
<table id="students-table">
<thead>
<tr>
<th>№</th>
<th>Фамилия</th>
<th>Имя</th>
<th>Отчество</th>
<th>Класс</th>
<th>Предметов</th>
<th>Действия</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div class="toolbar">
<button id="clear-data" class="btn btn-danger">Очистить все данные</button>
</div>
</div>
<script>
window.__CSRF_TOKEN = '<%= csrfToken %>';
const _origFetch = window.fetch.bind(window);
window.fetch = function(input, init = {}) {
init.credentials = 'include';
const method = (init.method || 'GET').toUpperCase();
if (['POST','PUT','PATCH','DELETE'].includes(method)) {
if (!init.headers) init.headers = {};
init.headers['X-CSRF-Token'] = window.__CSRF_TOKEN || '';
}
return _origFetch(input, init);
};
async function checkAuth() {
const r = await fetch('/api/user');
if (!r.ok) window.location.href = '/login';
}
checkAuth();
</script>
<script>
let allStudents = [];
async function loadStudents(search = '') {
const url = search ? `/api/students?search=${encodeURIComponent(search)}` : '/api/students';
const res = await fetch(url);
allStudents = await res.json();
renderTable(allStudents);
}
async function loadClasses() {
const res = await fetch('/api/classes');
const classes = await res.json();
const clsSel = document.getElementById('class-select');
clsSel.innerHTML = classes.map(c => `<option value="${esc(c)}">${esc(c)}</option>`).join('');
const parallels = [...new Set(classes.map(c => c.charAt(0)).filter(Boolean))].sort();
const parSel = document.getElementById('parallel-select');
parSel.innerHTML = parallels.map(p => `<option value="${esc(p)}">${esc(p)}-е классы</option>`).join('');
}
function toggleBatchMode() {
const scope = document.querySelector('input[name="batch-scope"]:checked').value;
document.getElementById('class-select').disabled = scope !== 'class';
document.getElementById('parallel-select').disabled = scope !== 'parallel';
}
function renderTable(students) {
const tbody = document.querySelector('#students-table tbody');
tbody.innerHTML = students.map((s, i) => `
<tr>
<td>${i + 1}</td>
<td>${esc(s.last_name)}</td>
<td>${esc(s.first_name)}</td>
<td>${esc(s.patronymic)}</td>
<td>${esc(s.class)}</td>
<td>${s.exam_count}</td>
<td><a href="/student/${s.id}" class="btn btn-sm">Открыть</a></td>
</tr>
`).join('');
}
function esc(s) { return (s || '').replace(/[&<>"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c])); }
document.getElementById('search').addEventListener('input', (e) => {
loadStudents(e.target.value);
});
document.getElementById('clear-data').addEventListener('click', async () => {
if (!confirm('Удалить всех учеников и результаты?')) return;
await fetch('/api/data', { method: 'DELETE' });
loadStudents();
});
document.getElementById('batch-print-btn').addEventListener('click', () => {
const scope = document.querySelector('input[name="batch-scope"]:checked').value;
const type = document.querySelector('input[name="batch-type"]:checked').value;
let url = `/batch-print?type=${type}`;
if (scope === 'class') {
const cls = document.getElementById('class-select').value;
if (!cls) return alert('Выберите класс');
url += `&class=${encodeURIComponent(cls)}`;
} else if (scope === 'parallel') {
const par = document.getElementById('parallel-select').value;
if (!par) return alert('Выберите параллель');
url += `&parallel=${encodeURIComponent(par)}`;
}
window.open(url, '_blank');
});
document.getElementById('logout-btn').addEventListener('click', async () => {
await fetch('/api/logout', { method: 'POST' });
window.location.href = '/login';
});
loadStudents();
loadClasses();
</script>
</body>
</html>