Files
OpenLesson/public/info.js
2026-04-16 15:07:09 +05:00

256 lines
10 KiB
JavaScript
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.
// 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 => {
let dateStr = '', timeStr = '';
if (reg.topic === 'Консультация' && reg.date && reg.time) {
dateStr = escapeHtml(reg.date);
timeStr = escapeHtml(reg.time);
} else {
dateStr = 'Согласно расписанию';
timeStr = '';
}
return `
<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>${dateStr}</td>
<td>${timeStr}</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 '&amp;';
if (m === '<') return '&lt;';
if (m === '>') return '&gt;';
return m;
});
}
function exportToCSV() {
if (!currentRegistrations.length) {
alert('Нет данных для экспорта');
return;
}
const headers = ['ФИО родителя', 'Телефон', 'Класс', 'Предмет', 'Учитель', 'Тема урока', 'Дата урока', 'Время', 'Дата регистрации'];
const rows = currentRegistrations.map(reg => {
let dateVal = '', timeVal = '';
if (reg.topic === 'Консультация' && reg.date && reg.time) {
dateVal = reg.date;
timeVal = reg.time;
} else {
dateVal = 'Согласно расписанию';
timeVal = '';
}
return [
reg.parent_name,
reg.parent_phone,
reg.class_name,
reg.subject,
reg.teacher,
reg.topic || '',
dateVal,
timeVal,
new Date(reg.created_at).toLocaleString()
];
});
const csvContent = [headers, ...rows]
.map(row => row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(';'))
.join('\n');
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);
}
// Функция выгрузки уникальных родителей для охраны с автоматической шириной столбца и сортировкой по алфавиту
async function exportForGuard() {
try {
const res = await fetch('/api/info/registrations');
const registrations = await res.json();
if (!registrations.length) {
alert('Нет данных для экспорта');
return;
}
const uniqueMap = new Map();
registrations.forEach(reg => {
if (!uniqueMap.has(reg.parent_name)) {
uniqueMap.set(reg.parent_name, { 'ФИО родителя': reg.parent_name });
}
});
let uniqueArray = Array.from(uniqueMap.values());
// Сортировка по ФИО родителя (русский алфавит, case-insensitive)
uniqueArray.sort((a, b) => a['ФИО родителя'].localeCompare(b['ФИО родителя'], 'ru'));
// Вычисляем максимальную длину ФИО
let maxLen = 0;
uniqueArray.forEach(item => {
const len = item['ФИО родителя'].length;
if (len > maxLen) maxLen = len;
});
// Ширина столбца в Excel (в символах) делаем запас +2, минимум 30
const colWidth = Math.max(maxLen + 2, 50);
const ws = XLSX.utils.json_to_sheet(uniqueArray);
// Устанавливаем ширину для столбца A (индекс 0)
ws['!cols'] = [{ wch: colWidth }];
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Список для охраны');
XLSX.writeFile(wb, 'spisok_dlya_ohrany.xlsx');
} catch (err) {
console.error(err);
alert('Ошибка при выгрузке');
}
}
// Функция выгрузки уникальных учителей (без дублирования) с сортировкой по алфавиту
async function exportTeachersForGuard() {
try {
const res = await fetch('/api/info/registrations');
const registrations = await res.json();
if (!registrations.length) {
alert('Нет данных для экспорта');
return;
}
const uniqueTeachers = new Map();
registrations.forEach(reg => {
if (reg.teacher && !uniqueTeachers.has(reg.teacher)) {
uniqueTeachers.set(reg.teacher, { 'Учитель': reg.teacher });
}
});
let uniqueArray = Array.from(uniqueTeachers.values());
// Сортировка по имени учителя (русский алфавит, case-insensitive)
uniqueArray.sort((a, b) => a['Учитель'].localeCompare(b['Учитель'], 'ru'));
if (uniqueArray.length === 0) {
alert('Нет данных об учителях');
return;
}
// Вычисляем максимальную длину имени учителя
let maxLen = 0;
uniqueArray.forEach(item => {
const len = item['Учитель'].length;
if (len > maxLen) maxLen = len;
});
const colWidth = Math.max(maxLen + 2, 30);
const ws = XLSX.utils.json_to_sheet(uniqueArray);
ws['!cols'] = [{ wch: colWidth }];
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Учителя');
XLSX.writeFile(wb, 'teachers_unique.xlsx');
} catch (err) {
console.error(err);
alert('Ошибка при выгрузке учителей');
}
}
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('exportGuardBtn')?.addEventListener('click', exportForGuard);
document.getElementById('exportTeachersBtn')?.addEventListener('click', exportTeachersForGuard);
document.getElementById('logoutBtn')?.addEventListener('click', async () => {
await fetch('/api/logout', { method: 'POST' });
window.location.href = '/';
});
}