feat: add subjects & teacher-subjects management tab in admin panel
This commit is contained in:
@@ -73,6 +73,16 @@
|
||||
const createEquipmentAlert = document.getElementById('create-equipment-alert');
|
||||
const equipmentCheckboxes = document.getElementById('equipment-checkboxes');
|
||||
|
||||
// Subjects
|
||||
const subjectsTbody = document.getElementById('subjects-tbody');
|
||||
const createSubjectForm = document.getElementById('create-subject-form');
|
||||
const createSubjectAlert = document.getElementById('create-subject-alert');
|
||||
const assignTeacherForm = document.getElementById('assign-teacher-form');
|
||||
const assignTeacherAlert = document.getElementById('assign-teacher-alert');
|
||||
const assignTeacherSelect = document.getElementById('assign-teacher-select');
|
||||
const assignSubjectSelect = document.getElementById('assign-subject-select');
|
||||
const teacherSubjectsTbody = document.getElementById('teacher-subjects-tbody');
|
||||
|
||||
// --- Multi-select logic ---
|
||||
function updateSelectText(containerId, textId) {
|
||||
const container = document.getElementById(containerId);
|
||||
@@ -130,6 +140,8 @@
|
||||
let allGroups = [];
|
||||
let allEducationForms = [];
|
||||
let allEquipments = [];
|
||||
let allSubjects = [];
|
||||
let allTeachers = [];
|
||||
|
||||
// ---- Tab Switching ----
|
||||
const TAB_TITLES = {
|
||||
@@ -137,7 +149,8 @@
|
||||
groups: 'Управление группами',
|
||||
'edu-forms': 'Формы обучения',
|
||||
equipments: 'Оборудование',
|
||||
classrooms: 'Аудитории'
|
||||
classrooms: 'Аудитории',
|
||||
subjects: 'Дисциплины и преподаватели'
|
||||
};
|
||||
|
||||
navItems.forEach(item => {
|
||||
@@ -162,6 +175,7 @@
|
||||
if (tab === 'edu-forms') loadEducationForms();
|
||||
if (tab === 'equipments') loadEquipments();
|
||||
if (tab === 'classrooms') { loadEquipments().then(() => loadClassrooms()); }
|
||||
if (tab === 'subjects') { Promise.all([loadSubjects(), loadTeachers()]).then(() => loadTeacherSubjects()); }
|
||||
|
||||
sidebar.classList.remove('open');
|
||||
sidebarOverlay.classList.remove('open');
|
||||
@@ -711,6 +725,178 @@
|
||||
} catch (e) { showAlert(editClassroomAlert, 'Ошибка соединения', 'error'); }
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// SUBJECTS
|
||||
// ============================================================
|
||||
|
||||
async function loadSubjects() {
|
||||
try {
|
||||
const res = await fetch('/api/subjects', {
|
||||
headers: { 'Authorization': 'Bearer ' + token },
|
||||
});
|
||||
allSubjects = await res.json();
|
||||
renderSubjects(allSubjects);
|
||||
populateSubjectSelect(allSubjects);
|
||||
} catch (e) {
|
||||
if (subjectsTbody) subjectsTbody.innerHTML = '<tr><td colspan="3" class="loading-row">Ошибка загрузки</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderSubjects(subjects) {
|
||||
if (!subjects.length) {
|
||||
subjectsTbody.innerHTML = '<tr><td colspan="3" class="loading-row">Нет дисциплин</td></tr>';
|
||||
return;
|
||||
}
|
||||
subjectsTbody.innerHTML = subjects.map(s => `
|
||||
<tr>
|
||||
<td>${s.id}</td>
|
||||
<td>${escapeHtml(s.name)}</td>
|
||||
<td><button class="btn-delete" data-id="${s.id}">Удалить</button></td>
|
||||
</tr>`).join('');
|
||||
}
|
||||
|
||||
function populateSubjectSelect(subjects) {
|
||||
if (!assignSubjectSelect) return;
|
||||
const currentVal = assignSubjectSelect.value;
|
||||
assignSubjectSelect.innerHTML = '<option value="">Выберите дисциплину</option>' +
|
||||
subjects.map(s => `<option value="${s.id}">${escapeHtml(s.name)}</option>`).join('');
|
||||
if (currentVal && subjects.find(s => s.id == currentVal)) {
|
||||
assignSubjectSelect.value = currentVal;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTeachers() {
|
||||
try {
|
||||
const res = await fetch('/api/users/teachers', {
|
||||
headers: { 'Authorization': 'Bearer ' + token },
|
||||
});
|
||||
allTeachers = await res.json();
|
||||
populateTeacherSelect(allTeachers);
|
||||
} catch (e) {
|
||||
if (assignTeacherSelect) assignTeacherSelect.innerHTML = '<option value="">Ошибка загрузки</option>';
|
||||
}
|
||||
}
|
||||
|
||||
function populateTeacherSelect(teachers) {
|
||||
if (!assignTeacherSelect) return;
|
||||
const currentVal = assignTeacherSelect.value;
|
||||
if (!teachers.length) {
|
||||
assignTeacherSelect.innerHTML = '<option value="">Нет преподавателей</option>';
|
||||
return;
|
||||
}
|
||||
assignTeacherSelect.innerHTML = '<option value="">Выберите преподавателя</option>' +
|
||||
teachers.map(t => `<option value="${t.id}">${escapeHtml(t.username)}</option>`).join('');
|
||||
if (currentVal && teachers.find(t => t.id == currentVal)) {
|
||||
assignTeacherSelect.value = currentVal;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTeacherSubjects() {
|
||||
try {
|
||||
const res = await fetch('/api/teacher-subjects', {
|
||||
headers: { 'Authorization': 'Bearer ' + token },
|
||||
});
|
||||
const tsData = await res.json();
|
||||
renderTeacherSubjects(tsData);
|
||||
} catch (e) {
|
||||
if (teacherSubjectsTbody) teacherSubjectsTbody.innerHTML = '<tr><td colspan="3" class="loading-row">Ошибка загрузки</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderTeacherSubjects(tsArray) {
|
||||
if (!tsArray.length) {
|
||||
teacherSubjectsTbody.innerHTML = '<tr><td colspan="3" class="loading-row">Нет привязок</td></tr>';
|
||||
return;
|
||||
}
|
||||
teacherSubjectsTbody.innerHTML = tsArray.map(ts => `
|
||||
<tr>
|
||||
<td>${escapeHtml(ts.username)}</td>
|
||||
<td>${escapeHtml(ts.subjectName)}</td>
|
||||
<td><button class="btn-delete" data-user-id="${ts.userId}" data-subject-id="${ts.subjectId}">Удалить</button></td>
|
||||
</tr>`).join('');
|
||||
}
|
||||
|
||||
createSubjectForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
hideAlert(createSubjectAlert);
|
||||
const name = document.getElementById('new-subject-name').value.trim();
|
||||
if (!name) { showAlert(createSubjectAlert, 'Введите название', 'error'); return; }
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/subjects', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||||
body: JSON.stringify({ name }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
showAlert(createSubjectAlert, `Дисциплина "${data.name}" добавлена`, 'success');
|
||||
createSubjectForm.reset();
|
||||
loadSubjects();
|
||||
} else {
|
||||
showAlert(createSubjectAlert, data.message || 'Ошибка создания', 'error');
|
||||
}
|
||||
} catch (e) { showAlert(createSubjectAlert, 'Ошибка соединения', 'error'); }
|
||||
});
|
||||
|
||||
subjectsTbody.addEventListener('click', async (e) => {
|
||||
const btn = e.target.closest('.btn-delete');
|
||||
if (!btn) return;
|
||||
if (!confirm('Удалить дисциплину?')) return;
|
||||
try {
|
||||
const res = await fetch('/api/subjects/' + btn.dataset.id, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Authorization': 'Bearer ' + token },
|
||||
});
|
||||
if (res.ok) {
|
||||
loadSubjects();
|
||||
loadTeacherSubjects();
|
||||
} else {
|
||||
const data = await res.json();
|
||||
alert(data.message || 'Ошибка удаления');
|
||||
}
|
||||
} catch (e) { alert('Ошибка соединения'); }
|
||||
});
|
||||
|
||||
assignTeacherForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
hideAlert(assignTeacherAlert);
|
||||
const userId = assignTeacherSelect.value;
|
||||
const subjectId = assignSubjectSelect.value;
|
||||
if (!userId) { showAlert(assignTeacherAlert, 'Выберите преподавателя', 'error'); return; }
|
||||
if (!subjectId) { showAlert(assignTeacherAlert, 'Выберите дисциплину', 'error'); return; }
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/teacher-subjects', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||||
body: JSON.stringify({ userId: Number(userId), subjectId: Number(subjectId) }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
showAlert(assignTeacherAlert, 'Привязка создана', 'success');
|
||||
loadTeacherSubjects();
|
||||
} else {
|
||||
showAlert(assignTeacherAlert, data.message || 'Ошибка привязки', 'error');
|
||||
}
|
||||
} catch (e) { showAlert(assignTeacherAlert, 'Ошибка соединения', 'error'); }
|
||||
});
|
||||
|
||||
teacherSubjectsTbody.addEventListener('click', async (e) => {
|
||||
const btn = e.target.closest('.btn-delete');
|
||||
if (!btn) return;
|
||||
if (!confirm('Удалить привязку?')) return;
|
||||
try {
|
||||
const res = await fetch('/api/teacher-subjects', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||||
body: JSON.stringify({ userId: Number(btn.dataset.userId), subjectId: Number(btn.dataset.subjectId) }),
|
||||
});
|
||||
if (res.ok) loadTeacherSubjects();
|
||||
else alert('Ошибка удаления');
|
||||
} catch (e) { alert('Ошибка соединения'); }
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// LOGOUT & INIT
|
||||
// ============================================================
|
||||
|
||||
Reference in New Issue
Block a user