222 lines
10 KiB
JavaScript
222 lines
10 KiB
JavaScript
import { api } from '../api.js';
|
|
import { escapeHtml, showAlert, hideAlert } from '../utils.js';
|
|
|
|
const ROLE_LABELS = {
|
|
ADMIN: 'Администратор',
|
|
EDUCATION_OFFICE: 'Учебный отдел',
|
|
DEPARTMENT: 'Кафедра',
|
|
SCHEDULE_VIEWER: 'Просмотр расписаний',
|
|
TEACHER: 'Преподаватель',
|
|
STUDENT: 'Студент'
|
|
};
|
|
const ROLE_BADGE = {
|
|
ADMIN: 'badge-admin',
|
|
EDUCATION_OFFICE: 'badge-admin',
|
|
DEPARTMENT: 'badge-teacher',
|
|
SCHEDULE_VIEWER: 'badge-student',
|
|
TEACHER: 'badge-teacher',
|
|
STUDENT: 'badge-student'
|
|
};
|
|
|
|
export async function initUsers() {
|
|
const usersTbody = document.getElementById('users-tbody');
|
|
const createForm = document.getElementById('create-form');
|
|
const teacherRequestsTbody = document.getElementById('teacher-requests-tbody');
|
|
const teacherRequestsRefresh = document.getElementById('teacher-requests-refresh');
|
|
let departments = [];
|
|
|
|
async function loadUsers() {
|
|
try {
|
|
const users = await api.get('/api/users?includeArchived=true');
|
|
renderUsers(users);
|
|
} catch (e) {
|
|
usersTbody.innerHTML =
|
|
'<tr><td colspan="7" class="loading-row">Ошибка загрузки: ' +
|
|
escapeHtml(e.message) + '</td></tr>';
|
|
}
|
|
}
|
|
|
|
async function loadDepartments() {
|
|
departments = await api.get('/api/departments');
|
|
}
|
|
|
|
async function loadTeacherRequests() {
|
|
if (!teacherRequestsTbody) return;
|
|
teacherRequestsTbody.innerHTML = '<tr><td colspan="7" class="loading-row">Загрузка...</td></tr>';
|
|
hideAlert('teacher-requests-alert');
|
|
try {
|
|
const requests = await api.get('/api/teacher-requests?status=PENDING');
|
|
renderTeacherRequests(Array.isArray(requests) ? requests : []);
|
|
} catch (error) {
|
|
teacherRequestsTbody.innerHTML = `<tr><td colspan="7" class="loading-row">${escapeHtml(error.message || 'Ошибка загрузки заявок')}</td></tr>`;
|
|
}
|
|
}
|
|
|
|
function renderTeacherRequests(requests) {
|
|
if (!requests.length) {
|
|
teacherRequestsTbody.innerHTML = '<tr><td colspan="7" class="loading-row">Нет заявок на рассмотрении</td></tr>';
|
|
return;
|
|
}
|
|
teacherRequestsTbody.innerHTML = requests.map(request => `
|
|
<tr data-request-id="${escapeHtml(request.id)}">
|
|
<td>
|
|
<select class="teacher-request-department" data-native-select="true">
|
|
${departments.map(department => `
|
|
<option value="${escapeHtml(department.id)}" ${String(department.id) === String(request.departmentId) ? 'selected' : ''}>
|
|
${escapeHtml(department.departmentName || department.name || department.id)}
|
|
</option>
|
|
`).join('')}
|
|
</select>
|
|
</td>
|
|
<td><input type="text" class="teacher-request-username" value="${escapeHtml(request.username || '')}" maxlength="50"></td>
|
|
<td><input type="text" class="teacher-request-fullname" value="${escapeHtml(request.fullName || '')}" maxlength="255"></td>
|
|
<td><input type="text" class="teacher-request-jobtitle" value="${escapeHtml(request.jobTitle || '')}" maxlength="255"></td>
|
|
<td><input type="text" class="teacher-request-password" placeholder="Минимум 8 символов"></td>
|
|
<td>${escapeHtml(request.comment || '-')}</td>
|
|
<td>
|
|
<div class="teacher-request-actions">
|
|
<button type="button" class="btn btn-sm btn-primary teacher-request-approve">Одобрить</button>
|
|
<button type="button" class="btn btn-sm btn-danger-subtle teacher-request-reject">Отклонить</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
}
|
|
|
|
function renderUsers(users) {
|
|
if (!users || !users.length) {
|
|
usersTbody.innerHTML = '<tr><td colspan="7" class="loading-row">Нет пользователей</td></tr>';
|
|
return;
|
|
}
|
|
|
|
usersTbody.innerHTML = users.map(u => `
|
|
<tr>
|
|
<td>${u.id}</td>
|
|
<td>${escapeHtml(u.username)}</td>
|
|
<td>${escapeHtml(u.fullName || '-')}</td>
|
|
<td>${escapeHtml(u.jobTitle || '-')}</td>
|
|
<td>${escapeHtml(u.departmentName || '-')}</td>
|
|
<td><span class="badge ${ROLE_BADGE[u.role] || ''}">${ROLE_LABELS[u.role] || escapeHtml(u.role)}</span></td>
|
|
<td>
|
|
${u.status === 'ARCHIVED'
|
|
? `<button class="btn-restore-user" data-id="${u.id}">Восстановить</button>`
|
|
: `<button class="btn btn-sm btn-danger-subtle btn-archive-user" data-id="${u.id}">Архивировать</button>`}
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
}
|
|
|
|
createForm.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
hideAlert('create-alert');
|
|
|
|
const username = document.getElementById('new-username').value.trim();
|
|
const password = document.getElementById('new-password').value;
|
|
const role = document.getElementById('new-role').value;
|
|
const fullName = document.getElementById('new-fullname').value.trim();
|
|
const jobTitle = document.getElementById('new-jobtitle').value.trim();
|
|
const departmentId = document.getElementById('new-department').value;
|
|
|
|
if (!username || !password || !fullName || !jobTitle || !departmentId) {
|
|
showAlert('create-alert', 'Заполните все поля', 'error');
|
|
return;
|
|
}
|
|
if (password.length < 8) {
|
|
showAlert('create-alert', 'Пароль должен быть не короче 8 символов', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const data = await api.post('/api/users', {
|
|
username,
|
|
password,
|
|
role,
|
|
fullName,
|
|
jobTitle,
|
|
departmentId: Number(departmentId)
|
|
});
|
|
showAlert('create-alert', `Пользователь "${escapeHtml(data.username)}" создан`, 'success');
|
|
createForm.reset();
|
|
loadUsers();
|
|
} catch (err) {
|
|
showAlert('create-alert', err.message || 'Ошибка соединения', 'error');
|
|
}
|
|
});
|
|
|
|
teacherRequestsRefresh?.addEventListener('click', loadTeacherRequests);
|
|
|
|
teacherRequestsTbody?.addEventListener('click', async (event) => {
|
|
const approveButton = event.target.closest('.teacher-request-approve');
|
|
const rejectButton = event.target.closest('.teacher-request-reject');
|
|
if (!approveButton && !rejectButton) return;
|
|
|
|
const row = event.target.closest('tr[data-request-id]');
|
|
const requestId = row?.dataset.requestId;
|
|
if (!requestId) return;
|
|
|
|
if (approveButton) {
|
|
const payload = {
|
|
departmentId: Number(row.querySelector('.teacher-request-department')?.value),
|
|
username: row.querySelector('.teacher-request-username')?.value.trim(),
|
|
fullName: row.querySelector('.teacher-request-fullname')?.value.trim(),
|
|
jobTitle: row.querySelector('.teacher-request-jobtitle')?.value.trim(),
|
|
password: row.querySelector('.teacher-request-password')?.value
|
|
};
|
|
if (!payload.departmentId || !payload.username || !payload.fullName || !payload.password) {
|
|
showAlert('teacher-requests-alert', 'Заполните кафедру, логин, ФИО и пароль', 'error');
|
|
return;
|
|
}
|
|
if (payload.password.length < 8) {
|
|
showAlert('teacher-requests-alert', 'Пароль должен быть не короче 8 символов', 'error');
|
|
return;
|
|
}
|
|
try {
|
|
await api.post(`/api/teacher-requests/${encodeURIComponent(requestId)}/approve`, payload);
|
|
showAlert('teacher-requests-alert', 'Преподаватель создан, заявка одобрена', 'success');
|
|
await Promise.all([loadTeacherRequests(), loadUsers()]);
|
|
} catch (error) {
|
|
showAlert('teacher-requests-alert', error.message || 'Ошибка одобрения заявки', 'error');
|
|
}
|
|
return;
|
|
}
|
|
|
|
const reviewComment = prompt('Причина отклонения');
|
|
if (reviewComment === null) return;
|
|
try {
|
|
await api.post(`/api/teacher-requests/${encodeURIComponent(requestId)}/reject`, { reviewComment });
|
|
showAlert('teacher-requests-alert', 'Заявка отклонена', 'success');
|
|
await loadTeacherRequests();
|
|
} catch (error) {
|
|
showAlert('teacher-requests-alert', error.message || 'Ошибка отклонения заявки', 'error');
|
|
}
|
|
});
|
|
|
|
usersTbody.addEventListener('click', async (e) => {
|
|
const deleteBtn = e.target.closest('.btn-archive-user');
|
|
if (!deleteBtn) return;
|
|
|
|
if (!confirm('Архивировать пользователя? История расписания и связей сохранится.')) return;
|
|
try {
|
|
await api.delete('/api/users/' + deleteBtn.dataset.id);
|
|
loadUsers();
|
|
} catch (err) {
|
|
alert(err.message || 'Ошибка архивирования');
|
|
}
|
|
});
|
|
|
|
usersTbody.addEventListener('click', async (e) => {
|
|
const restoreBtn = e.target.closest('.btn-restore-user');
|
|
if (!restoreBtn) return;
|
|
|
|
try {
|
|
await api.post('/api/users/' + restoreBtn.dataset.id + '/restore', {});
|
|
loadUsers();
|
|
} catch (err) {
|
|
alert(err.message || 'Ошибка восстановления');
|
|
}
|
|
});
|
|
|
|
await loadDepartments();
|
|
await Promise.all([loadUsers(), loadTeacherRequests()]);
|
|
}
|