Files
gia-gve-certificates/views/student.ejs
2026-07-01 13:48:25 +05:00

242 lines
10 KiB
Plaintext
Raw Permalink 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><%= student.last_name %> <%= student.first_name %> — Справки</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<div class="container">
<nav class="nav">
<a href="/" class="btn">&larr; К списку</a>
<a href="/history" class="btn" style="background:#5a3e85">История</a>
<span class="user-bar"><%= user ? user.name : '' %></span>
<button id="logout-btn" class="btn btn-logout">Выйти</button>
</nav>
<h1><%= student.last_name %> <%= student.first_name %> <%= student.patronymic %></h1>
<p>Класс: <strong><%= student.class %></strong></p>
<div class="card" style="padding:10px 20px">
<div class="form-row">
<label>Номер справки:</label>
<input type="text" id="cert-number" class="input input-sm" value="<%= student.cert_number || '' %>">
<button id="save-cert-number" class="btn btn-sm">Сохранить номер</button>
<span id="cert-msg" class="msg"></span>
</div>
</div>
<% const hasRus = rusResult && rusResult.grade !== null; const hasMath = mathResult && mathResult.grade !== null; %>
<% if (!hasRus || !hasMath) { %>
<div class="banner-warning">
Не заполнены оценки:
<%= !hasRus ? 'русский язык' : '' %>
<%= !hasRus && !hasMath ? 'и' : '' %>
<%= !hasMath ? 'математика' : '' %>.
Заполните обязательные предметы перед печатью.
</div>
<% } %>
<div class="card">
<h2>Обязательные предметы</h2>
<form id="mandatory-form" class="mandatory-form">
<div class="form-row">
<label>Русский язык</label>
<input type="number" id="rus-score" class="input input-sm" placeholder="Первичный балл" value="<%= rusResult ? rusResult.primary_score || '' : '' %>">
<input type="number" id="rus-grade" class="input input-sm" placeholder="Оценка (2-5)" min="2" max="5" value="<%= rusResult ? rusResult.grade || '' : '' %>">
</div>
<div class="form-row">
<label>Математика</label>
<input type="number" id="math-score" class="input input-sm" placeholder="Первичный балл" value="<%= mathResult ? mathResult.primary_score || '' : '' %>">
<input type="number" id="math-grade" class="input input-sm" placeholder="Оценка (2-5)" min="2" max="5" value="<%= mathResult ? mathResult.grade || '' : '' %>">
</div>
<button type="submit" class="btn btn-green">Сохранить</button>
<span id="mandatory-msg" class="msg"></span>
</form>
</div>
<% if (electiveResults.length > 0) { %>
<div class="card">
<h2>Предметы по выбору (из протокола)</h2>
<table>
<thead>
<tr>
<th>Предмет</th>
<th>Первичный балл</th>
<th>Оценка</th>
</tr>
</thead>
<tbody>
<% electiveResults.forEach(r => { %>
<tr>
<td><%= r.subject_name %></td>
<td><%= r.primary_score !== null ? r.primary_score : '—' %></td>
<td><strong><%= r.grade !== null ? r.grade : '—' %></strong></td>
</tr>
<% }) %>
</tbody>
</table>
</div>
<% } %>
<div class="card">
<h2>Сформировать справку</h2>
<form id="cert-form">
<div class="form-row">
<label>
<input type="radio" name="cert-type" value="gia" checked onchange="toggleElectives()">
ГИА (ОГЭ) — 4 предмета, с первичными баллами
</label>
</div>
<div class="form-row">
<label>
<input type="radio" name="cert-type" value="gve" onchange="toggleElectives()">
ГВЭ — 2 предмета, только отметки
</label>
</div>
<div id="electives-block" style="margin-top:10px">
<p>Выберите 12 предмета для справки ГИА:</p>
<% electiveResults.forEach(r => { %>
<label class="checkbox-label">
<input type="checkbox" name="elective" value="<%= r.subject_code %>" class="elective-cb">
<%= r.subject_name %>
</label>
<% }) %>
<span id="elective-msg" class="msg"></span>
</div>
<div class="form-row" style="margin-top:10px">
<label>Дата выдачи:</label>
<input type="date" id="cert-date" class="input input-sm">
</div>
<button type="submit" class="btn btn-blue" style="margin-top:15px">Сформировать и распечатать</button>
</form>
</div>
<% if (groupedHistory && groupedHistory.length > 0) { %>
<div class="card">
<h2>История изменений</h2>
<table>
<thead>
<tr>
<th>Дата</th>
<th>Пользователь</th>
<th>Источник</th>
<th>Изменения</th>
</tr>
</thead>
<tbody>
<% groupedHistory.forEach(g => { %>
<tr>
<td style="white-space:nowrap;font-size:12px"><%= g.changed_at %></td>
<td style="font-size:12px"><%= g.changed_by %></td>
<td style="font-size:12px">
<% if (g.source === 'import') { %>XLS импорт
<% } else if (g.source === 'manual') { %>вручную
<% } else { %><%= g.source %><% } %>
</td>
<td style="font-size:12px">
<% g.changes.forEach((c, ci) => { %>
<div>
<% const subjName = c.subject_name === 'CERT' ? 'Номер справки' : c.subject_name; %>
<%= subjName %>: <%= c.field_name %> <span style="color:#999"><%= c.old_value %></span> → <strong><%= c.new_value %></strong></div>
<% }) %>
</td>
</tr>
<% }) %>
</tbody>
</table>
</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);
};
document.getElementById('logout-btn').addEventListener('click', async () => {
await fetch('/api/logout', { method: 'POST' });
window.location.href = '/login';
});
// Set default date to today
document.getElementById('cert-date').value = new Date().toISOString().split('T')[0];
// Save cert number
document.getElementById('save-cert-number').addEventListener('click', async () => {
const num = document.getElementById('cert-number').value.trim();
const msg = document.getElementById('cert-msg');
if (!num) { msg.textContent = 'Введите номер'; msg.className = 'msg msg-err'; return; }
const res = await fetch('/api/students/<%= student.id %>/cert-number', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cert_number: num })
});
const data = await res.json();
if (res.ok) { msg.textContent = 'Сохранено'; msg.className = 'msg msg-ok'; }
else { msg.textContent = data.error || 'Ошибка'; msg.className = 'msg msg-err'; }
});
document.getElementById('mandatory-form').addEventListener('submit', async (e) => {
e.preventDefault();
const score = v => { const n = parseInt(v,10); return isNaN(n)?null:n; };
const res = await fetch('/api/students/<%= student.id %>/mandatory', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
rus_score: score(document.getElementById('rus-score').value),
rus_grade: score(document.getElementById('rus-grade').value),
math_score: score(document.getElementById('math-score').value),
math_grade: score(document.getElementById('math-grade').value)
})
});
const msg = document.getElementById('mandatory-msg');
if (res.ok) { msg.textContent = 'Сохранено'; msg.className = 'msg msg-ok'; }
else { msg.textContent = 'Ошибка'; msg.className = 'msg msg-err'; }
});
document.querySelectorAll('.elective-cb').forEach(cb => {
cb.addEventListener('change', () => {
const checked = document.querySelectorAll('.elective-cb:checked');
if (checked.length > 2) cb.checked = false;
});
});
function toggleElectives() {
const type = document.querySelector('input[name="cert-type"]:checked').value;
document.getElementById('electives-block').style.display = type === 'gia' ? 'block' : 'none';
}
document.getElementById('cert-form').addEventListener('submit', (e) => {
e.preventDefault();
const type = document.querySelector('input[name="cert-type"]:checked').value;
const electives = [...document.querySelectorAll('.elective-cb:checked')].map(cb => cb.value);
const msg = document.getElementById('elective-msg');
if (type === 'gia' && electives.length < 1) {
msg.textContent = 'Выберите хотя бы 1 предмет для ГИА';
msg.className = 'msg msg-err';
return;
}
msg.textContent = '';
const date = document.getElementById('cert-date').value;
const url = `/certificate/<%= student.id %>?type=${type}&subjects=${electives.join(',')}&date=${date}`;
window.open(url, '_blank');
});
</script>
</body>
</html>