hfcg
This commit is contained in:
committed by
GitVerse
parent
ae3227f127
commit
2d007d2359
20
package.json
20
package.json
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"axios": "^1.15.0",
|
||||
"connect-sqlite3": "^0.9.16",
|
||||
"dotenv": "^17.4.1",
|
||||
"express": "^5.2.1",
|
||||
"express-session": "^1.19.0",
|
||||
"sqlite3": "^6.0.1"
|
||||
}
|
||||
}
|
||||
{
|
||||
"dependencies": {
|
||||
"axios": "^1.15.0",
|
||||
"connect-sqlite3": "^0.9.16",
|
||||
"dotenv": "^17.4.1",
|
||||
"express": "^5.2.1",
|
||||
"express-session": "^1.19.0",
|
||||
"sqlite3": "^6.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,35 +14,35 @@
|
||||
<main>
|
||||
<div class="admin-controls">
|
||||
<button id="addLessonBtn">+ Добавить урок</button>
|
||||
<div class="filters">
|
||||
<div class="filter-group">
|
||||
<label>Класс</label>
|
||||
<select id="filterClass">
|
||||
<option value="">Все классы</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>Учитель</label>
|
||||
<select id="filterTeacher">
|
||||
<option value="">Все учителя</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>Тема урока</label>
|
||||
<select id="filterTopic">
|
||||
<option value="">Все темы</option>
|
||||
</select>
|
||||
</div>
|
||||
<input type="text" id="filterParallel" placeholder="Параллель (цифра)">
|
||||
<button id="applyFilters">Применить</button>
|
||||
<button id="resetFilters" class="reset-btn">Сбросить</button>
|
||||
</div>
|
||||
<div class="filters">
|
||||
<div class="filter-group">
|
||||
<label>Класс</label>
|
||||
<select id="filterClass">
|
||||
<option value="">Все классы</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>Учитель</label>
|
||||
<select id="filterTeacher">
|
||||
<option value="">Все учителя</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>Тема урока</label>
|
||||
<select id="filterTopic">
|
||||
<option value="">Все темы</option>
|
||||
</select>
|
||||
</div>
|
||||
<input type="text" id="filterParallel" placeholder="Параллель (цифра)">
|
||||
<button id="applyFilters">Применить</button>
|
||||
<button id="resetFilters" class="reset-btn">Сбросить</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="lessonsList" class="lessons-list"></div>
|
||||
|
||||
<!-- Импорт JSON (без даты/времени) -->
|
||||
<!-- Импорт JSON / Excel -->
|
||||
<div class="import-section">
|
||||
<h3>Импорт уроков из JSON</h3>
|
||||
<h3>Импорт уроков из JSON / Excel</h3>
|
||||
<input type="file" id="jsonFileInput" accept=".json, .xlsx, .xls">
|
||||
<div class="import-params">
|
||||
<label>Макс. мест (сколько родителей может записаться):
|
||||
@@ -54,7 +54,9 @@
|
||||
<h4>Предпросмотр (первые 20 записей)</h4>
|
||||
<div style="overflow-x:auto;">
|
||||
<table id="previewTable" border="1" cellpadding="5">
|
||||
<thead><tr><th>Класс</th><th>Предмет</th><th>Учитель</th><th>Тема</th></tr></thead>
|
||||
<thead>
|
||||
<tr><th>Класс</th><th>Предмет</th><th>Учитель</th><th>Тема</th></tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -76,8 +78,8 @@
|
||||
<label>Учитель: <input type="text" id="teacher" required></label>
|
||||
<label>Тема урока: <input type="text" id="topic"></label>
|
||||
<label>Макс. мест: <input type="number" id="maxSlots" required></label>
|
||||
<label>Дата: <input type="date" id="date" required></label>
|
||||
<label>Время: <input type="time" id="time" required></label>
|
||||
<label>Дата: <input type="date" id="date"></label>
|
||||
<label>Время: <input type="time" id="time"></label>
|
||||
<button type="submit">Сохранить</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -92,7 +94,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="https://cdn.sheetjs.com/xlsx-0.20.2/package/dist/xlsx.full.min.js"></script>
|
||||
<!--<script src="https://cdn.sheetjs.com/xlsx-0.20.2/package/dist/xlsx.full.min.js"></script>-->
|
||||
<script src="admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -121,13 +121,19 @@ async function loadLessons(filters = {}) {
|
||||
section.className = 'class-group';
|
||||
section.innerHTML = `<h2>${className}</h2>`;
|
||||
classLessons.forEach(lesson => {
|
||||
let dateTimeStr = '';
|
||||
if (lesson.topic === 'Консультация' && lesson.date && lesson.time) {
|
||||
dateTimeStr = `${lesson.date} ${lesson.time}`;
|
||||
} else {
|
||||
dateTimeStr = 'Согласно расписания';
|
||||
}
|
||||
const div = document.createElement('div');
|
||||
div.className = 'lesson-item';
|
||||
div.innerHTML = `
|
||||
<div>
|
||||
<strong>${lesson.subject}</strong> — ${lesson.teacher}<br>
|
||||
<em>Тема: ${lesson.topic || '—'}</em><br>
|
||||
${lesson.date} ${lesson.time} | Места: ${lesson.current_slots}/${lesson.max_slots}
|
||||
${dateTimeStr} | Мест свободно: ${lesson.max_slots - lesson.current_slots}
|
||||
</div>
|
||||
<div class="lesson-actions">
|
||||
<button class="viewRegBtn" data-id="${lesson.id}">Записи</button>
|
||||
@@ -178,8 +184,8 @@ function openLessonModal(id = null) {
|
||||
document.getElementById('teacher').value = lesson.teacher;
|
||||
document.getElementById('topic').value = lesson.topic || '';
|
||||
document.getElementById('maxSlots').value = lesson.max_slots;
|
||||
document.getElementById('date').value = lesson.date;
|
||||
document.getElementById('time').value = lesson.time;
|
||||
document.getElementById('date').value = lesson.date || '';
|
||||
document.getElementById('time').value = lesson.time || '';
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -252,7 +258,7 @@ function setupEventListeners() {
|
||||
window.location.href = '/';
|
||||
});
|
||||
|
||||
// ========== ИМПОРТ JSON / XLSX ==========
|
||||
// Импорт
|
||||
function parseExcelToRecords(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
@@ -66,7 +66,7 @@ async function loadRegistrations() {
|
||||
document.getElementById('recordsCount').innerText = `Найдено: ${currentRegistrations.length}`;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
document.getElementById('tableBody').innerHTML = '<tr><td colspan="9">Ошибка загрузки</td></tr>';
|
||||
document.getElementById('tableBody').innerHTML = '<td><td colspan="9">Ошибка загрузки</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,19 +76,29 @@ function renderTable(registrations) {
|
||||
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('');
|
||||
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) {
|
||||
@@ -106,23 +116,31 @@ function exportToCSV() {
|
||||
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 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');
|
||||
// Добавляем BOM для корректной поддержки кириллицы в Excel
|
||||
const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const link = document.createElement('a');
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
77
public/k.html
Normal file
77
public/k.html
Normal file
@@ -0,0 +1,77 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Управление корпусами учителей</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<style>
|
||||
.teachers-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: white;
|
||||
border-radius: 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
.teachers-table th, .teachers-table td {
|
||||
border: 1px solid #e2e8f0;
|
||||
padding: 0.75rem;
|
||||
text-align: left;
|
||||
}
|
||||
.teachers-table th {
|
||||
background: #f1f5f9;
|
||||
}
|
||||
.campus-select {
|
||||
padding: 0.4rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #cbd5e1;
|
||||
}
|
||||
.save-btn {
|
||||
background: #2d6a4f;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
.message {
|
||||
margin-top: 1rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.success {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
.error {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Управление корпусами учителей</h1>
|
||||
<div id="userInfo"></div>
|
||||
<button id="logoutBtn">Выйти</button>
|
||||
</header>
|
||||
<main style="padding: 2rem;">
|
||||
<div class="header-actions">
|
||||
<h2>Список учителей</h2>
|
||||
<button id="saveAllBtn" class="save-btn">💾 Сохранить все изменения</button>
|
||||
</div>
|
||||
<div style="overflow-x: auto;">
|
||||
<table class="teachers-table" id="teachersTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Учитель</th>
|
||||
<th>Предмет</th>
|
||||
<th>Корпус</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tableBody">
|
||||
<tr><td colspan="3">Загрузка...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="message" class="message" style="display:none;"></div>
|
||||
</main>
|
||||
<script src="k.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
115
public/k.js
Normal file
115
public/k.js
Normal file
@@ -0,0 +1,115 @@
|
||||
// public/k.js – управление привязкой учителей к корпусам
|
||||
|
||||
let currentUser = null;
|
||||
let teachersList = []; // [{ id, name, subject, campus }]
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
await checkAuth();
|
||||
await loadTeachers();
|
||||
setupEventListeners();
|
||||
});
|
||||
|
||||
async function checkAuth() {
|
||||
try {
|
||||
const res = await fetch('/api/me');
|
||||
const data = await res.json();
|
||||
if (!data.authenticated || data.user.role !== 'admin') {
|
||||
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 loadTeachers() {
|
||||
try {
|
||||
const res = await fetch('/api/teachers');
|
||||
teachersList = await res.json();
|
||||
renderTable();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
document.getElementById('tableBody').innerHTML = '<tr><td colspan="3">Ошибка загрузки</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderTable() {
|
||||
const tbody = document.getElementById('tableBody');
|
||||
if (!teachersList.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="3">Нет учителей</td></tr>';
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = teachersList.map(teacher => `
|
||||
<tr data-id="${teacher.id}">
|
||||
<td>${escapeHtml(teacher.name)}</td>
|
||||
<td>${escapeHtml(teacher.subject || '—')}</td>
|
||||
<td>
|
||||
<select class="campus-select" data-id="${teacher.id}">
|
||||
<option value="" ${teacher.campus === '' ? 'selected' : ''}>Оба корпуса</option>
|
||||
<option value="Феофанова 10" ${teacher.campus === 'Феофанова 10' ? 'selected' : ''}>Феофанова 10</option>
|
||||
<option value="Цветоносная 2" ${teacher.campus === 'Цветоносная 2' ? 'selected' : ''}>Цветоносная 2</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
async function saveAllChanges() {
|
||||
const updates = [];
|
||||
document.querySelectorAll('.campus-select').forEach(select => {
|
||||
const teacherId = parseInt(select.dataset.id);
|
||||
const newCampus = select.value;
|
||||
updates.push({ id: teacherId, campus: newCampus });
|
||||
});
|
||||
|
||||
const messageDiv = document.getElementById('message');
|
||||
messageDiv.style.display = 'block';
|
||||
messageDiv.className = 'message';
|
||||
messageDiv.innerHTML = 'Сохранение...';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/teachers/campus/batch', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ updates })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
messageDiv.className = 'message success';
|
||||
messageDiv.innerHTML = '✅ Изменения сохранены';
|
||||
setTimeout(() => messageDiv.style.display = 'none', 3000);
|
||||
// обновляем локальные данные
|
||||
teachersList = teachersList.map(t => {
|
||||
const update = updates.find(u => u.id === t.id);
|
||||
if (update) t.campus = update.campus;
|
||||
return t;
|
||||
});
|
||||
} else {
|
||||
throw new Error(data.error || 'Ошибка сохранения');
|
||||
}
|
||||
} catch (err) {
|
||||
messageDiv.className = 'message error';
|
||||
messageDiv.innerHTML = `❌ Ошибка: ${err.message}`;
|
||||
setTimeout(() => messageDiv.style.display = 'none', 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function setupEventListeners() {
|
||||
document.getElementById('saveAllBtn')?.addEventListener('click', saveAllChanges);
|
||||
document.getElementById('logoutBtn')?.addEventListener('click', async () => {
|
||||
await fetch('/api/logout', { method: 'POST' });
|
||||
window.location.href = '/';
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return str.replace(/[&<>]/g, function(m) {
|
||||
if (m === '&') return '&';
|
||||
if (m === '<') return '<';
|
||||
if (m === '>') return '>';
|
||||
return m;
|
||||
});
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
let allLessons = [];
|
||||
|
||||
// Загрузка опций для выпадающих списков
|
||||
async function loadFilterOptions() {
|
||||
try {
|
||||
const [classes, teachers, topics] = await Promise.all([
|
||||
@@ -10,7 +9,6 @@ async function loadFilterOptions() {
|
||||
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;
|
||||
@@ -34,7 +32,6 @@ function populateSelect(selectId, options, defaultLabel) {
|
||||
option.textContent = opt;
|
||||
select.appendChild(option);
|
||||
});
|
||||
// Восстанавливаем выбранное значение, если оно ещё допустимо
|
||||
if (currentValue && options.includes(currentValue)) {
|
||||
select.value = currentValue;
|
||||
} else {
|
||||
@@ -42,12 +39,11 @@ function populateSelect(selectId, options, defaultLabel) {
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка всех уроков с сервера
|
||||
async function loadLessons() {
|
||||
try {
|
||||
const res = await fetch('/api/lessons');
|
||||
allLessons = await res.json();
|
||||
updateDependentFilters(); // первоначальное построение зависимых списков
|
||||
updateDependentFilters();
|
||||
applyFilters();
|
||||
} catch (err) {
|
||||
console.error('Ошибка загрузки уроков', err);
|
||||
@@ -55,13 +51,11 @@ async function loadLessons() {
|
||||
}
|
||||
}
|
||||
|
||||
// Обновление зависимых выпадающих списков на основе текущих фильтров
|
||||
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);
|
||||
@@ -73,12 +67,10 @@ function updateDependentFilters() {
|
||||
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();
|
||||
|
||||
// Обновляем select, сохраняя текущие значения, если они допустимы
|
||||
const classSelect = document.getElementById('filterClass');
|
||||
const teacherSelect = document.getElementById('filterTeacher');
|
||||
const topicSelect = document.getElementById('filterTopic');
|
||||
@@ -91,7 +83,6 @@ function updateDependentFilters() {
|
||||
populateSelect('filterTeacher', availableTeachers, 'Все учителя');
|
||||
populateSelect('filterTopic', availableTopics, 'Все темы');
|
||||
|
||||
// Если старое значение не было сброшено populateSelect, восстанавливаем
|
||||
if (oldClass && availableClasses.includes(oldClass)) classSelect.value = oldClass;
|
||||
else classSelect.value = '';
|
||||
if (oldTeacher && availableTeachers.includes(oldTeacher)) teacherSelect.value = oldTeacher;
|
||||
@@ -100,16 +91,12 @@ function updateDependentFilters() {
|
||||
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);
|
||||
|
||||
// Точное совпадение для select (не частичное)
|
||||
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);
|
||||
@@ -117,7 +104,6 @@ function applyFilters() {
|
||||
renderLessons(filtered);
|
||||
}
|
||||
|
||||
// Отрисовка карточек уроков + обновление счётчика
|
||||
function renderLessons(lessons) {
|
||||
const container = document.getElementById('lessonsContainer');
|
||||
const counterSpan = document.getElementById('availableCount');
|
||||
@@ -129,16 +115,27 @@ function renderLessons(lessons) {
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = lessons.map(lesson => `
|
||||
<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>
|
||||
<div class="slots">
|
||||
Свободных мест: ${lesson.max_slots - lesson.current_slots} из ${lesson.max_slots}
|
||||
container.innerHTML = lessons.map(lesson => {
|
||||
// Определяем, что показывать в строке времени
|
||||
let timeHtml = '';
|
||||
if (lesson.topic === 'Консультация' && 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>
|
||||
</div>
|
||||
`).join('');
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
if (counterSpan) counterSpan.textContent = `${lessons.length}`;
|
||||
|
||||
@@ -147,7 +144,6 @@ function renderLessons(lessons) {
|
||||
});
|
||||
}
|
||||
|
||||
// Модальное окно записи (без изменений)
|
||||
function setupModal() {
|
||||
const modal = document.getElementById('modal');
|
||||
const closeSpan = modal.querySelector('.close');
|
||||
@@ -195,11 +191,18 @@ function openModal(lessonId) {
|
||||
const lesson = allLessons.find(l => l.id == lessonId);
|
||||
if (!lesson) return;
|
||||
|
||||
let timeInfo = '';
|
||||
if (lesson.topic === 'Консультация' && 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>${lesson.date} ${lesson.time}</small>
|
||||
<small>${timeInfo}</small>
|
||||
`;
|
||||
document.getElementById('modalMessage').innerHTML = '';
|
||||
document.getElementById('registrationForm').reset();
|
||||
@@ -216,13 +219,11 @@ function escapeHtml(str) {
|
||||
});
|
||||
}
|
||||
|
||||
// Инициализация
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
await loadFilterOptions();
|
||||
await loadLessons();
|
||||
setupModal();
|
||||
|
||||
// Обработчики изменений фильтров
|
||||
const classSelect = document.getElementById('filterClass');
|
||||
const teacherSelect = document.getElementById('filterTeacher');
|
||||
const topicSelect = document.getElementById('filterTopic');
|
||||
|
||||
@@ -174,7 +174,7 @@ form input, form textarea {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3rem;
|
||||
min-width: 150px;
|
||||
min-width: 100px;
|
||||
max-width: 450px;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user