371 lines
15 KiB
JavaScript
371 lines
15 KiB
JavaScript
// public/main.js – страница записи родителей с авторизацией для сотрудников
|
||
|
||
let allLessons = [];
|
||
let currentUser = null;
|
||
|
||
// ---------- Авторизация на главной странице ----------
|
||
async function checkAuthAndRender() {
|
||
try {
|
||
const res = await fetch('/api/me');
|
||
const data = await res.json();
|
||
if (data.authenticated) {
|
||
currentUser = data.user;
|
||
renderAuthButtons(true);
|
||
} else {
|
||
currentUser = null;
|
||
renderAuthButtons(false);
|
||
}
|
||
} catch (err) {
|
||
console.error('Ошибка проверки авторизации', err);
|
||
renderAuthButtons(false);
|
||
}
|
||
}
|
||
|
||
function renderAuthButtons(isLoggedIn) {
|
||
const container = document.getElementById('authContainer');
|
||
if (!container) return;
|
||
if (!isLoggedIn) {
|
||
container.innerHTML = '<button id="employeeBtn" class="employee-btn">👨💼 Для сотрудников</button>';
|
||
const btn = document.getElementById('employeeBtn');
|
||
if (btn) btn.addEventListener('click', showAuthModal);
|
||
} else {
|
||
const role = currentUser.role;
|
||
let links = '';
|
||
if (role === 'admin') {
|
||
links = '<a href="/admin" class="admin-link">📋 Админка</a> | <a href="/info" class="info-link">📊 Список записей</a>';
|
||
} else if (role === 'user') {
|
||
links = '<a href="/info" class="info-link">📊 Список записей</a>';
|
||
} else {
|
||
links = '<span>Доступ не определён</span>';
|
||
}
|
||
container.innerHTML = `
|
||
<div class="user-links">
|
||
${links}
|
||
<button id="logoutBtnHeader" class="logout-btn">🚪 Выйти</button>
|
||
</div>
|
||
`;
|
||
const logoutBtn = document.getElementById('logoutBtnHeader');
|
||
if (logoutBtn) logoutBtn.addEventListener('click', logout);
|
||
}
|
||
}
|
||
|
||
async function logout() {
|
||
try {
|
||
await fetch('/api/logout', { method: 'POST' });
|
||
currentUser = null;
|
||
renderAuthButtons(false);
|
||
// Обновляем список уроков (чтобы сбросить возможные фильтры, связанные с сессией)
|
||
await loadFilterOptions();
|
||
await loadLessons();
|
||
} catch (err) {
|
||
console.error('Ошибка выхода', err);
|
||
}
|
||
}
|
||
|
||
function showAuthModal() {
|
||
let modal = document.getElementById('authModal');
|
||
// Если модального окна нет в DOM – создаём динамически
|
||
if (!modal) {
|
||
modal = document.createElement('div');
|
||
modal.id = 'authModal';
|
||
modal.className = 'modal';
|
||
modal.innerHTML = `
|
||
<div class="modal-content">
|
||
<span class="close">×</span>
|
||
<h3>Вход для сотрудников</h3>
|
||
<form id="authForm">
|
||
<label>Логин: <input type="text" id="authUsername" required></label>
|
||
<label>Пароль: <input type="password" id="authPassword" required></label>
|
||
<button type="submit">Войти</button>
|
||
<div id="authError" class="error" style="color:red; margin-top:10px;"></div>
|
||
</form>
|
||
</div>
|
||
`;
|
||
document.body.appendChild(modal);
|
||
}
|
||
const form = document.getElementById('authForm');
|
||
const errorDiv = document.getElementById('authError');
|
||
const closeSpan = modal.querySelector('.close');
|
||
if (errorDiv) errorDiv.innerText = '';
|
||
if (form) form.reset();
|
||
modal.style.display = 'flex';
|
||
if (closeSpan) closeSpan.onclick = () => modal.style.display = 'none';
|
||
window.onclick = (e) => { if (e.target === modal) modal.style.display = 'none'; };
|
||
|
||
const authForm = document.getElementById('authForm');
|
||
if (authForm) {
|
||
authForm.onsubmit = async (e) => {
|
||
e.preventDefault();
|
||
const username = document.getElementById('authUsername').value.trim();
|
||
const password = document.getElementById('authPassword').value;
|
||
if (!username || !password) {
|
||
if (errorDiv) errorDiv.innerText = 'Заполните логин и пароль';
|
||
return;
|
||
}
|
||
try {
|
||
const res = await fetch('/api/auth', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ username, password })
|
||
});
|
||
const data = await res.json();
|
||
if (res.ok && data.success) {
|
||
modal.style.display = 'none';
|
||
await checkAuthAndRender();
|
||
await loadFilterOptions();
|
||
await loadLessons();
|
||
} else {
|
||
if (errorDiv) errorDiv.innerText = data.message || 'Ошибка входа';
|
||
}
|
||
} catch (err) {
|
||
if (errorDiv) errorDiv.innerText = 'Ошибка соединения';
|
||
}
|
||
};
|
||
}
|
||
}
|
||
|
||
// ---------- Остальная логика (фильтры, уроки, запись) ----------
|
||
async function loadFilterOptions() {
|
||
try {
|
||
const [classes, teachers, topics] = 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/topics').then(r => r.json())
|
||
]);
|
||
window.allClassNames = classes;
|
||
window.allTeachers = teachers;
|
||
window.allTopics = topics;
|
||
|
||
populateSelect('filterClass', window.allClassNames, 'Все классы');
|
||
populateSelect('filterTeacher', window.allTeachers, 'Все учителя');
|
||
populateSelect('filterTopic', window.allTopics, 'Все темы');
|
||
} catch (err) {
|
||
console.error('Ошибка загрузки опций фильтров', err);
|
||
}
|
||
}
|
||
|
||
function populateSelect(selectId, options, defaultLabel) {
|
||
const select = document.getElementById(selectId);
|
||
if (!select) return;
|
||
const currentValue = select.value;
|
||
select.innerHTML = `<option value="">${defaultLabel}</option>`;
|
||
options.forEach(opt => {
|
||
const option = document.createElement('option');
|
||
option.value = opt;
|
||
option.textContent = opt;
|
||
select.appendChild(option);
|
||
});
|
||
if (currentValue && options.includes(currentValue)) {
|
||
select.value = currentValue;
|
||
} else {
|
||
select.value = '';
|
||
}
|
||
}
|
||
|
||
async function loadLessons() {
|
||
try {
|
||
const res = await fetch('/api/lessons');
|
||
allLessons = await res.json();
|
||
updateDependentFilters();
|
||
applyFilters();
|
||
} catch (err) {
|
||
console.error('Ошибка загрузки уроков', err);
|
||
document.getElementById('lessonsContainer').innerHTML = '<p>Ошибка загрузки данных</p>';
|
||
}
|
||
}
|
||
|
||
function updateDependentFilters() {
|
||
const selectedClass = document.getElementById('filterClass').value;
|
||
const selectedTeacher = document.getElementById('filterTeacher').value;
|
||
const selectedTopic = document.getElementById('filterTopic').value;
|
||
|
||
let filteredLessons = allLessons;
|
||
if (selectedClass) {
|
||
filteredLessons = filteredLessons.filter(l => l.class_name === selectedClass);
|
||
}
|
||
if (selectedTeacher) {
|
||
filteredLessons = filteredLessons.filter(l => l.teacher === selectedTeacher);
|
||
}
|
||
if (selectedTopic) {
|
||
filteredLessons = filteredLessons.filter(l => l.topic === selectedTopic);
|
||
}
|
||
|
||
const availableClasses = [...new Set(filteredLessons.map(l => l.class_name))].sort();
|
||
const availableTeachers = [...new Set(filteredLessons.map(l => l.teacher))].sort();
|
||
const availableTopics = [...new Set(filteredLessons.map(l => l.topic).filter(t => t))].sort();
|
||
|
||
const classSelect = document.getElementById('filterClass');
|
||
const teacherSelect = document.getElementById('filterTeacher');
|
||
const topicSelect = document.getElementById('filterTopic');
|
||
|
||
const oldClass = classSelect.value;
|
||
const oldTeacher = teacherSelect.value;
|
||
const oldTopic = topicSelect.value;
|
||
|
||
populateSelect('filterClass', availableClasses, 'Все классы');
|
||
populateSelect('filterTeacher', availableTeachers, 'Все учителя');
|
||
populateSelect('filterTopic', availableTopics, 'Все темы');
|
||
|
||
if (oldClass && availableClasses.includes(oldClass)) classSelect.value = oldClass;
|
||
else classSelect.value = '';
|
||
if (oldTeacher && availableTeachers.includes(oldTeacher)) teacherSelect.value = oldTeacher;
|
||
else teacherSelect.value = '';
|
||
if (oldTopic && availableTopics.includes(oldTopic)) topicSelect.value = oldTopic;
|
||
else topicSelect.value = '';
|
||
}
|
||
|
||
function applyFilters() {
|
||
const classFilter = document.getElementById('filterClass').value;
|
||
const teacherFilter = document.getElementById('filterTeacher').value;
|
||
const topicFilter = document.getElementById('filterTopic').value;
|
||
|
||
let filtered = allLessons.filter(lesson => lesson.available === true);
|
||
if (classFilter) filtered = filtered.filter(lesson => lesson.class_name === classFilter);
|
||
if (teacherFilter) filtered = filtered.filter(lesson => lesson.teacher === teacherFilter);
|
||
if (topicFilter) filtered = filtered.filter(lesson => lesson.topic === topicFilter);
|
||
|
||
renderLessons(filtered);
|
||
}
|
||
|
||
function renderLessons(lessons) {
|
||
const container = document.getElementById('lessonsContainer');
|
||
const counterSpan = document.getElementById('availableCount');
|
||
if (!container) return;
|
||
|
||
if (lessons.length === 0) {
|
||
container.innerHTML = '<p style="text-align:center; grid-column:1/-1;">Нет доступных уроков</p>';
|
||
if (counterSpan) counterSpan.textContent = '0';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = lessons.map(lesson => {
|
||
let timeHtml = '';
|
||
if (lesson.topic && lesson.topic.includes('Консультация') && lesson.date && lesson.time) {
|
||
timeHtml = `<p><strong>Дата/время:</strong> ${escapeHtml(lesson.date)} ${escapeHtml(lesson.time)}</p>`;
|
||
} else {
|
||
timeHtml = `<p><strong>Время:</strong> Согласно расписания</p>`;
|
||
}
|
||
|
||
return `
|
||
<div class="lesson-card" data-id="${lesson.id}">
|
||
<h3>${escapeHtml(lesson.class_name)} | ${escapeHtml(lesson.subject)}</h3>
|
||
<p><strong>Учитель:</strong> ${escapeHtml(lesson.teacher)}</p>
|
||
<p><strong>Тема:</strong> ${escapeHtml(lesson.topic || '—')}</p>
|
||
${timeHtml}
|
||
<div class="slots">
|
||
Свободных мест: ${lesson.max_slots - lesson.current_slots}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
if (counterSpan) counterSpan.textContent = `${lessons.length}`;
|
||
|
||
document.querySelectorAll('.lesson-card').forEach(card => {
|
||
card.addEventListener('click', () => openModal(card.dataset.id));
|
||
});
|
||
}
|
||
|
||
function setupModal() {
|
||
const modal = document.getElementById('modal');
|
||
const closeSpan = modal.querySelector('.close');
|
||
const form = document.getElementById('registrationForm');
|
||
|
||
closeSpan.onclick = () => modal.style.display = 'none';
|
||
window.onclick = (e) => { if (e.target === modal) modal.style.display = 'none'; };
|
||
|
||
form.addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
const lessonId = document.getElementById('lessonId').value;
|
||
const parentName = document.getElementById('parentName').value.trim();
|
||
const parentPhone = document.getElementById('parentPhone').value.trim();
|
||
const messageDiv = document.getElementById('modalMessage');
|
||
|
||
if (!parentName || !parentPhone) {
|
||
messageDiv.innerHTML = '<span style="color:red">Заполните все поля</span>';
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const res = await fetch('/api/register', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ lesson_id: lessonId, parent_name: parentName, parent_phone: parentPhone })
|
||
});
|
||
const data = await res.json();
|
||
if (res.ok) {
|
||
messageDiv.innerHTML = '<span style="color:green">✅ Вы успешно записаны!</span>';
|
||
setTimeout(() => {
|
||
modal.style.display = 'none';
|
||
loadLessons();
|
||
}, 1500);
|
||
} else {
|
||
messageDiv.innerHTML = `<span style="color:red">${data.error || 'Ошибка'}</span>`;
|
||
}
|
||
} catch (err) {
|
||
messageDiv.innerHTML = '<span style="color:red">Ошибка сервера</span>';
|
||
}
|
||
});
|
||
}
|
||
|
||
function openModal(lessonId) {
|
||
const modal = document.getElementById('modal');
|
||
const lesson = allLessons.find(l => l.id == lessonId);
|
||
if (!lesson) return;
|
||
|
||
let timeInfo = '';
|
||
if (lesson.topic && lesson.topic.includes('Консультация') && lesson.date && lesson.time) {
|
||
timeInfo = `${lesson.date} ${lesson.time}`;
|
||
} else {
|
||
timeInfo = 'Согласно расписания';
|
||
}
|
||
|
||
document.getElementById('lessonId').value = lessonId;
|
||
document.getElementById('modalLessonInfo').innerHTML = `
|
||
<strong>${escapeHtml(lesson.class_name)}</strong><br>
|
||
${escapeHtml(lesson.subject)} — ${escapeHtml(lesson.teacher)}<br>
|
||
<small>${timeInfo}</small>
|
||
`;
|
||
document.getElementById('modalMessage').innerHTML = '';
|
||
document.getElementById('registrationForm').reset();
|
||
modal.style.display = 'flex';
|
||
}
|
||
|
||
function escapeHtml(str) {
|
||
if (!str) return '';
|
||
return str.replace(/[&<>]/g, function(m) {
|
||
if (m === '&') return '&';
|
||
if (m === '<') return '<';
|
||
if (m === '>') return '>';
|
||
return m;
|
||
});
|
||
}
|
||
|
||
// ---------- Инициализация ----------
|
||
document.addEventListener('DOMContentLoaded', async () => {
|
||
await checkAuthAndRender();
|
||
await loadFilterOptions();
|
||
await loadLessons();
|
||
setupModal();
|
||
|
||
const classSelect = document.getElementById('filterClass');
|
||
const teacherSelect = document.getElementById('filterTeacher');
|
||
const topicSelect = document.getElementById('filterTopic');
|
||
|
||
function handleFilterChange() {
|
||
updateDependentFilters();
|
||
applyFilters();
|
||
}
|
||
|
||
classSelect?.addEventListener('change', handleFilterChange);
|
||
teacherSelect?.addEventListener('change', handleFilterChange);
|
||
topicSelect?.addEventListener('change', handleFilterChange);
|
||
|
||
document.getElementById('resetFilter')?.addEventListener('click', () => {
|
||
classSelect.value = '';
|
||
teacherSelect.value = '';
|
||
topicSelect.value = '';
|
||
updateDependentFilters();
|
||
applyFilters();
|
||
});
|
||
}); |