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:
committed by
GitVerse
parent
2d007d2359
commit
fe3824b9da
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -94,7 +95,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -244,4 +244,11 @@ form input, form textarea {
|
|||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
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
22
public/xlsx.full.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
15
server.js
15
server.js
@@ -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'));
|
||||||
|
|||||||
@@ -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')");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user