create: admin.html, admin.js, help.html, index.html, info.html, info.js, login.html, main.js, style.css, auth.js, package.json, server.js, sqllite.js
This commit is contained in:
committed by
GitVerse
parent
458b1fa927
commit
3fbf7311d8
151
public/info.js
Normal file
151
public/info.js
Normal file
@@ -0,0 +1,151 @@
|
||||
// public/info.js – страница просмотра записей
|
||||
|
||||
let currentUser = null;
|
||||
let currentRegistrations = [];
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
await checkAuth();
|
||||
await loadFilterOptions();
|
||||
loadRegistrations();
|
||||
setupEventListeners();
|
||||
});
|
||||
|
||||
async function checkAuth() {
|
||||
try {
|
||||
const res = await fetch('/api/me');
|
||||
const data = await res.json();
|
||||
if (!data.authenticated || (data.user.role !== 'admin' && data.user.role !== 'user')) {
|
||||
window.location.href = '/login.html';
|
||||
return;
|
||||
}
|
||||
currentUser = data.user;
|
||||
document.getElementById('userInfo').innerHTML = `👋 ${currentUser.full_name} (${currentUser.role})`;
|
||||
} catch (err) {
|
||||
window.location.href = '/login.html';
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFilterOptions() {
|
||||
try {
|
||||
const [classes, teachers, subjects] = await Promise.all([
|
||||
fetch('/api/filter-options/class-names').then(r => r.json()),
|
||||
fetch('/api/filter-options/teachers').then(r => r.json()),
|
||||
fetch('/api/filter-options/subjects').then(r => r.json())
|
||||
]);
|
||||
populateSelect('filterClass', classes, 'Все классы');
|
||||
populateSelect('filterTeacher', teachers, 'Все учителя');
|
||||
populateSelect('filterSubject', subjects, 'Все предметы');
|
||||
} catch (err) {
|
||||
console.error('Ошибка загрузки опций', err);
|
||||
}
|
||||
}
|
||||
|
||||
function populateSelect(selectId, options, defaultLabel) {
|
||||
const select = document.getElementById(selectId);
|
||||
if (!select) return;
|
||||
select.innerHTML = `<option value="">${defaultLabel}</option>`;
|
||||
options.forEach(opt => {
|
||||
const option = document.createElement('option');
|
||||
option.value = opt;
|
||||
option.textContent = opt;
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadRegistrations() {
|
||||
const params = new URLSearchParams({
|
||||
parent_name: document.getElementById('filterParentName').value,
|
||||
class_name: document.getElementById('filterClass').value,
|
||||
subject: document.getElementById('filterSubject').value,
|
||||
teacher: document.getElementById('filterTeacher').value
|
||||
});
|
||||
try {
|
||||
const res = await fetch(`/api/info/registrations?${params}`);
|
||||
currentRegistrations = await res.json();
|
||||
renderTable(currentRegistrations);
|
||||
document.getElementById('recordsCount').innerText = `Найдено: ${currentRegistrations.length}`;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
document.getElementById('tableBody').innerHTML = '<tr><td colspan="9">Ошибка загрузки</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderTable(registrations) {
|
||||
const tbody = document.getElementById('tableBody');
|
||||
if (!registrations.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="9">Нет записей</td></tr>';
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = registrations.map(reg => `
|
||||
<tr>
|
||||
<td>${escapeHtml(reg.parent_name)}</td>
|
||||
<td>${escapeHtml(reg.parent_phone)}</td>
|
||||
<td>${escapeHtml(reg.class_name)}</td>
|
||||
<td>${escapeHtml(reg.subject)}</td>
|
||||
<td>${escapeHtml(reg.teacher)}</td>
|
||||
<td>${escapeHtml(reg.topic || '—')}</td>
|
||||
<td>${escapeHtml(reg.date)}</td>
|
||||
<td>${escapeHtml(reg.time)}</td>
|
||||
<td>${new Date(reg.created_at).toLocaleString()}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return str.replace(/[&<>]/g, function(m) {
|
||||
if (m === '&') return '&';
|
||||
if (m === '<') return '<';
|
||||
if (m === '>') return '>';
|
||||
return m;
|
||||
});
|
||||
}
|
||||
|
||||
function exportToCSV() {
|
||||
if (!currentRegistrations.length) {
|
||||
alert('Нет данных для экспорта');
|
||||
return;
|
||||
}
|
||||
// Заголовки
|
||||
const headers = ['ФИО родителя', 'Телефон', 'Класс', 'Предмет', 'Учитель', 'Тема урока', 'Дата урока', 'Время', 'Дата регистрации'];
|
||||
const rows = currentRegistrations.map(reg => [
|
||||
reg.parent_name,
|
||||
reg.parent_phone,
|
||||
reg.class_name,
|
||||
reg.subject,
|
||||
reg.teacher,
|
||||
reg.topic || '',
|
||||
reg.date,
|
||||
reg.time,
|
||||
new Date(reg.created_at).toLocaleString()
|
||||
]);
|
||||
const csvContent = [headers, ...rows]
|
||||
.map(row => row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(';'))
|
||||
.join('\n');
|
||||
// Добавляем BOM для корректной поддержки кириллицы в Excel
|
||||
const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const link = document.createElement('a');
|
||||
const url = URL.createObjectURL(blob);
|
||||
link.href = url;
|
||||
link.setAttribute('download', 'zapis_roditelei.csv');
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
function setupEventListeners() {
|
||||
document.getElementById('applyFiltersBtn')?.addEventListener('click', () => loadRegistrations());
|
||||
document.getElementById('resetFiltersBtn')?.addEventListener('click', () => {
|
||||
document.getElementById('filterParentName').value = '';
|
||||
document.getElementById('filterClass').value = '';
|
||||
document.getElementById('filterSubject').value = '';
|
||||
document.getElementById('filterTeacher').value = '';
|
||||
loadRegistrations();
|
||||
});
|
||||
document.getElementById('exportBtn')?.addEventListener('click', exportToCSV);
|
||||
document.getElementById('logoutBtn')?.addEventListener('click', async () => {
|
||||
await fetch('/api/logout', { method: 'POST' });
|
||||
window.location.href = '/';
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user