Добавил вкладку заявок
This commit is contained in:
136
frontend/admin/js/views/teacher-requests.js
Normal file
136
frontend/admin/js/views/teacher-requests.js
Normal file
@@ -0,0 +1,136 @@
|
||||
import { api } from '../api.js';
|
||||
import { escapeHtml, hideAlert, showAlert } from '../utils.js';
|
||||
|
||||
export async function initTeacherRequests() {
|
||||
const requestsTbody = document.getElementById('teacher-requests-tbody');
|
||||
const refreshButton = document.getElementById('teacher-requests-refresh');
|
||||
const requestsCount = document.getElementById('teacher-requests-count');
|
||||
let departments = [];
|
||||
|
||||
async function loadDepartments() {
|
||||
departments = await api.get('/api/departments');
|
||||
}
|
||||
|
||||
async function loadRequests() {
|
||||
requestsTbody.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');
|
||||
const normalizedRequests = Array.isArray(requests) ? requests : [];
|
||||
renderRequests(normalizedRequests);
|
||||
updateCount(normalizedRequests.length);
|
||||
document.dispatchEvent(new CustomEvent('teacherRequestsChanged', {
|
||||
detail: { count: normalizedRequests.length }
|
||||
}));
|
||||
} catch (error) {
|
||||
requestsTbody.innerHTML = `<tr><td colspan="7" class="loading-row">${escapeHtml(error.message || 'Ошибка загрузки заявок')}</td></tr>`;
|
||||
updateCount(0);
|
||||
}
|
||||
}
|
||||
|
||||
function renderRequests(requests) {
|
||||
if (!requests.length) {
|
||||
requestsTbody.innerHTML = '<tr><td colspan="7" class="loading-row">Нет заявок на рассмотрении</td></tr>';
|
||||
return;
|
||||
}
|
||||
requestsTbody.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 updateCount(count) {
|
||||
if (!requestsCount) return;
|
||||
requestsCount.textContent = requestCountLabel(count);
|
||||
}
|
||||
|
||||
function requestCountLabel(count) {
|
||||
const mod10 = count % 10;
|
||||
const mod100 = count % 100;
|
||||
if (mod10 === 1 && mod100 !== 11) return `${count} заявка`;
|
||||
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) return `${count} заявки`;
|
||||
return `${count} заявок`;
|
||||
}
|
||||
|
||||
async function approveRequest(row, requestId) {
|
||||
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 loadRequests();
|
||||
} catch (error) {
|
||||
showAlert('teacher-requests-alert', error.message || 'Ошибка одобрения заявки', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function rejectRequest(requestId) {
|
||||
const reviewComment = prompt('Причина отклонения');
|
||||
if (reviewComment === null) return;
|
||||
try {
|
||||
await api.post(`/api/teacher-requests/${encodeURIComponent(requestId)}/reject`, { reviewComment });
|
||||
showAlert('teacher-requests-alert', 'Заявка отклонена', 'success');
|
||||
await loadRequests();
|
||||
} catch (error) {
|
||||
showAlert('teacher-requests-alert', error.message || 'Ошибка отклонения заявки', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
refreshButton?.addEventListener('click', loadRequests);
|
||||
|
||||
requestsTbody.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) {
|
||||
await approveRequest(row, requestId);
|
||||
return;
|
||||
}
|
||||
await rejectRequest(requestId);
|
||||
});
|
||||
|
||||
try {
|
||||
await loadDepartments();
|
||||
await loadRequests();
|
||||
} catch (error) {
|
||||
requestsTbody.innerHTML = `<tr><td colspan="7" class="loading-row">${escapeHtml(error.message || 'Ошибка загрузки')}</td></tr>`;
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,6 @@ const ROLE_BADGE = {
|
||||
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 {
|
||||
@@ -36,53 +33,6 @@ export async function initUsers() {
|
||||
}
|
||||
}
|
||||
|
||||
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>';
|
||||
@@ -143,54 +93,6 @@ export async function initUsers() {
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
@@ -216,6 +118,5 @@ export async function initUsers() {
|
||||
}
|
||||
});
|
||||
|
||||
await loadDepartments();
|
||||
await Promise.all([loadUsers(), loadTeacherRequests()]);
|
||||
await loadUsers();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user