create: admin.html, admin.js, help.html, index.html, info.html, info.js, k.html, k.js, login.html, main.js, style.css, xlsx.full.min.js, auth.js, package.json, server.js, sqllite.js

This commit is contained in:
Калугин Олег Александрович
2026-04-13 12:30:38 +00:00
committed by GitVerse
parent 2d007d2359
commit fe3824b9da
7 changed files with 80 additions and 9 deletions

View File

@@ -5,6 +5,7 @@
"dotenv": "^17.4.1", "dotenv": "^17.4.1",
"express": "^5.2.1", "express": "^5.2.1",
"express-session": "^1.19.0", "express-session": "^1.19.0",
"sqlite3": "^6.0.1" "sqlite3": "^6.0.1",
"xlsx": "^0.18.5"
} }
} }

View File

@@ -14,6 +14,7 @@
<main> <main>
<div class="admin-controls"> <div class="admin-controls">
<button id="addLessonBtn">+ Добавить урок</button> <button id="addLessonBtn">+ Добавить урок</button>
<button id="clearDbBtn" class="danger-btn">🧹 Полная очистка базы</button>
<div class="filters"> <div class="filters">
<div class="filter-group"> <div class="filter-group">
<label>Класс</label> <label>Класс</label>
@@ -95,6 +96,7 @@
</div> </div>
</main> </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="/xlsx.full.min.js"></script>
<script src="admin.js"></script> <script src="admin.js"></script>
</body> </body>
</html> </html>

View File

@@ -1,4 +1,4 @@
// public/admin.js панель администратора // public/admin.js панель администратора (локальная XLSX)
let currentUser = null; let currentUser = null;
let currentPreviewLessons = []; let currentPreviewLessons = [];
@@ -258,13 +258,37 @@ function setupEventListeners() {
window.location.href = '/'; window.location.href = '/';
}); });
// Импорт // Кнопка очистки всей базы
document.getElementById('clearDbBtn')?.addEventListener('click', async () => {
const confirmed = confirm('⚠️ ВНИМАНИЕ! Вы собираетесь УДАЛИТЬ ВСЕ уроки и все записи родителей. Это действие необратимо. Продолжить?');
if (!confirmed) return;
try {
const res = await fetch('/api/admin/clear-db', { method: 'POST' });
const data = await res.json();
if (res.ok && data.success) {
alert('База данных успешно очищена.');
loadLessons(getCurrentFilters());
} else {
alert('Ошибка при очистке: ' + (data.error || 'неизвестная ошибка'));
}
} catch (err) {
alert('Ошибка соединения: ' + err.message);
}
});
// Импорт (теперь XLSX всегда глобально доступен)
function parseExcelToRecords(file) { function parseExcelToRecords(file) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = function(e) { reader.onload = function(e) {
const data = new Uint8Array(e.target.result); const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' }); let workbook;
try {
workbook = XLSX.read(data, { type: 'array' });
} catch (err) {
reject(new Error('Ошибка чтения Excel: ' + err.message));
return;
}
const firstSheet = workbook.Sheets[workbook.SheetNames[0]]; const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
const rows = XLSX.utils.sheet_to_json(firstSheet, { header: 1, defval: "" }); const rows = XLSX.utils.sheet_to_json(firstSheet, { header: 1, defval: "" });
if (!rows || rows.length < 2) { if (!rows || rows.length < 2) {

View File

@@ -245,3 +245,10 @@ form input, form textarea {
text-align: center; text-align: center;
} }
} }
.danger-btn {
background: #b91c1c;
margin-left: 1rem;
}
.danger-btn:hover {
background: #991b1b;
}

22
public/xlsx.full.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -242,12 +242,10 @@ function parseLessonFromJsonRecord(record) {
let fields = {}; let fields = {};
if (Array.isArray(record)) { if (Array.isArray(record)) {
// Массив пар [["key","value"], ...]
for (const [key, value] of record) { for (const [key, value] of record) {
fields[key.trim()] = (value || '').toString().trim(); fields[key.trim()] = (value || '').toString().trim();
} }
} else if (typeof record === 'object' && record !== null) { } else if (typeof record === 'object' && record !== null) {
// Обычный объект
fields = { ...record }; fields = { ...record };
Object.keys(fields).forEach(k => { Object.keys(fields).forEach(k => {
fields[k.trim()] = (fields[k] || '').toString().trim(); fields[k.trim()] = (fields[k] || '').toString().trim();
@@ -282,7 +280,7 @@ app.post('/api/admin/import/preview', isAuthenticated, isAdmin, async (req, res)
try { try {
lesson = parseLessonFromJsonRecord(record); lesson = parseLessonFromJsonRecord(record);
} catch(e) { } catch(e) {
continue; // пропускаем битые записи continue;
} }
if (lesson.teacher && lesson.subject && !isNaN(lesson.parallel)) { if (lesson.teacher && lesson.subject && !isNaN(lesson.parallel)) {
preview.push(lesson); preview.push(lesson);
@@ -336,6 +334,17 @@ app.post('/api/admin/import', isAuthenticated, isAdmin, async (req, res) => {
} }
}); });
// --------------------- НОВЫЙ ЭНДПОИНТ: полная очистка базы ---------------------
app.post('/api/admin/clear-db', isAuthenticated, isAdmin, async (req, res) => {
try {
await db.clearAllData();
res.json({ success: true, message: 'База данных очищена' });
} catch (err) {
console.error('Ошибка очистки БД:', err);
res.status(500).json({ error: 'Не удалось очистить базу данных' });
}
});
// --------------------- Статические страницы --------------------- // --------------------- Статические страницы ---------------------
app.get('/admin', (req, res) => { app.get('/admin', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'admin.html')); res.sendFile(path.join(__dirname, 'public', 'admin.html'));

View File

@@ -148,7 +148,6 @@ module.exports = {
`); `);
}, },
// Новая функция для фильтрации записей на странице /info
getRegistrationsWithFilters: (filters) => { getRegistrationsWithFilters: (filters) => {
let sql = ` let sql = `
SELECT r.id, r.parent_name, r.parent_phone, r.created_at, SELECT r.id, r.parent_name, r.parent_phone, r.created_at,
@@ -176,5 +175,12 @@ module.exports = {
} }
sql += ' ORDER BY r.created_at DESC'; sql += ' ORDER BY r.created_at DESC';
return allQuery(sql, params); return allQuery(sql, params);
},
// НОВЫЙ МЕТОД полная очистка базы
clearAllData: async () => {
await runQuery('DELETE FROM registrations');
await runQuery('DELETE FROM lessons');
await runQuery("DELETE FROM sqlite_sequence WHERE name IN ('lessons', 'registrations')");
} }
}; };