Files
OpenLesson/public/main.js

371 lines
15 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/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">&times;</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 '&amp;';
if (m === '<') return '&lt;';
if (m === '>') return '&gt;';
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();
});
});