This commit is contained in:
2026-04-13 10:43:17 +05:00
parent c54f688406
commit 7c6b3ee1a4
4 changed files with 3197 additions and 19 deletions

View File

@@ -43,7 +43,7 @@
<!-- Импорт JSON (без даты/времени) -->
<div class="import-section">
<h3>Импорт уроков из JSON</h3>
<input type="file" id="jsonFileInput" accept=".json">
<input type="file" id="jsonFileInput" accept=".json, .xlsx, .xls">
<div class="import-params">
<label>Макс. мест (сколько родителей может записаться):
<input type="number" id="importMaxSlots" value="4" required>

View File

@@ -2,7 +2,7 @@
let currentUser = null;
let currentPreviewLessons = [];
let allLessonsForFilters = []; // храним все уроки для построения зависимых фильтров
let allLessonsForFilters = [];
document.addEventListener('DOMContentLoaded', async () => {
await checkAuth();
@@ -26,7 +26,6 @@ async function checkAuth() {
}
}
// Загрузка опций для выпадающих списков фильтров (начальные)
async function loadFilterOptions() {
try {
const [classes, teachers, topics] = await Promise.all([
@@ -64,7 +63,6 @@ function populateSelect(selectId, options, defaultLabel) {
}
}
// Обновление зависимых фильтров на основе текущих значений и allLessonsForFilters
function updateDependentFilters() {
const selectedClass = document.getElementById('filterClass').value;
const selectedTeacher = document.getElementById('filterTeacher').value;
@@ -105,9 +103,8 @@ async function loadLessons(filters = {}) {
const params = new URLSearchParams(filters);
const res = await fetch(`/api/admin/lessons?${params}`);
const lessons = await res.json();
// Сохраняем полный список для фильтрации (без учёта фильтров)
allLessonsForFilters = lessons;
updateDependentFilters(); // обновить списки в select на основе всех уроков
updateDependentFilters();
const container = document.getElementById('lessonsList');
if (!container) return;
@@ -228,7 +225,6 @@ function setupEventListeners() {
const topicSelect = document.getElementById('filterTopic');
function handleFilterChange() {
// Обновить зависимые списки, затем применить фильтрацию списка уроков
updateDependentFilters();
loadLessons(getCurrentFilters());
}
@@ -256,19 +252,83 @@ function setupEventListeners() {
window.location.href = '/';
});
// Импорт JSON (без изменений)
// ========== ИМПОРТ JSON / XLSX ==========
function parseExcelToRecords(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function(e) {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
const rows = XLSX.utils.sheet_to_json(firstSheet, { header: 1, defval: "" });
if (!rows || rows.length < 2) {
reject(new Error("Файл не содержит данных"));
return;
}
const headers = rows[0].map(h => String(h || "").trim());
const expectedHeaders = [
"Фамилия Учителя", "Имя Учителя", "Отчество Учителя",
"Предмет", "Тема Урока", "Класс (параллель)", "Класс (буква)"
];
const missing = expectedHeaders.filter(h => !headers.includes(h));
if (missing.length) {
reject(new Error(`В файле отсутствуют столбцы: ${missing.join(", ")}`));
return;
}
const idx = {};
expectedHeaders.forEach(h => { idx[h] = headers.indexOf(h); });
const records = [];
for (let i = 1; i < rows.length; i++) {
const row = rows[i];
if (!row || row.length === 0) continue;
const teacherLast = row[idx["Фамилия Учителя"]] || "";
const teacherFirst = row[idx["Имя Учителя"]] || "";
const teacherPatr = row[idx["Отчество Учителя"]] || "";
const subject = row[idx["Предмет"]] || "";
const topic = row[idx["Тема Урока"]] || "";
const parallelRaw = row[idx["Класс (параллель)"]]?.toString() || "";
const classLetter = row[idx["Класс (буква)"]]?.toString() || "";
if (!teacherLast && !teacherFirst && !subject) continue;
records.push({
"Фамилия Учителя": teacherLast,
"Имя Учителя": teacherFirst,
"Отчество Учителя": teacherPatr,
"Предмет": subject,
"Тема Урока": topic,
"Класс (параллель)": parallelRaw,
"Класс (буква)": classLetter
});
}
resolve(records);
};
reader.onerror = reject;
reader.readAsArrayBuffer(file);
});
}
document.getElementById('previewImportBtn')?.addEventListener('click', async () => {
const file = document.getElementById('jsonFileInput').files[0];
if (!file) return alert('Выберите JSON-файл');
if (!file) return alert('Выберите JSON или Excel файл');
const importMaxSlots = document.getElementById('importMaxSlots').value;
if (!importMaxSlots) return alert('Укажите максимальное количество мест');
const text = await file.text();
let records = null;
const fileExt = file.name.split('.').pop().toLowerCase();
try {
if (fileExt === 'json') {
const text = await file.text();
records = JSON.parse(text);
} else if (fileExt === 'xlsx' || fileExt === 'xls') {
records = await parseExcelToRecords(file);
} else {
alert('Неподдерживаемый формат. Используйте .json, .xlsx или .xls');
return;
}
const response = await fetch('/api/admin/import/preview', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ jsonData: JSON.parse(text) })
body: JSON.stringify({ jsonData: records })
});
const data = await response.json();
if (data.success) {
@@ -293,7 +353,7 @@ function setupEventListeners() {
alert('Ошибка предпросмотра: ' + (data.error || ''));
}
} catch (err) {
alert('Ошибка при разборе JSON: ' + err.message);
alert('Ошибка при разборе файла: ' + err.message);
}
});