206 lines
9.9 KiB
JavaScript
206 lines
9.9 KiB
JavaScript
import { api } from '../api.js';
|
||
import { escapeHtml, hideAlert, renderTableMessage, showAlert } from '../utils.js';
|
||
import { promptAction } from '../dialog.js';
|
||
import { renderPagination } from '../pagination.js';
|
||
import { debounce, readUrlState, writeUrlState } from '../url-state.js';
|
||
import { markTableReady, renderTableEmpty, renderTableError, renderTableLoading } from '../view-state.js';
|
||
import { trackUxEvent } from '../../../telemetry.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');
|
||
const searchInput = document.getElementById('teacher-requests-search');
|
||
const statusSelect = document.getElementById('teacher-requests-status');
|
||
const pagination = document.getElementById('teacher-requests-pagination');
|
||
let departments = [];
|
||
const restored = readUrlState({ requestQuery: '', requestStatus: 'PENDING', requestPage: '0', requestSize: '25' });
|
||
const state = {
|
||
query: restored.requestQuery,
|
||
status: restored.requestStatus,
|
||
page: Math.max(Number(restored.requestPage) || 0, 0),
|
||
size: [25, 50, 100].includes(Number(restored.requestSize)) ? Number(restored.requestSize) : 25
|
||
};
|
||
searchInput.value = state.query;
|
||
statusSelect.value = state.status;
|
||
|
||
async function loadDepartments() {
|
||
departments = await api.get('/api/departments');
|
||
}
|
||
|
||
async function loadRequests() {
|
||
renderTableLoading(requestsTbody, 7, 'Загрузка заявок...');
|
||
hideAlert('teacher-requests-alert');
|
||
try {
|
||
const params = new URLSearchParams({
|
||
query: state.query,
|
||
page: String(state.page),
|
||
size: String(state.size),
|
||
sort: 'createdAt',
|
||
direction: 'desc'
|
||
});
|
||
if (state.status) params.set('status', state.status);
|
||
const page = await api.get(`/api/teacher-requests/page?${params}`);
|
||
if (!(page.items || []).length && page.totalItems > 0 && state.page > 0) {
|
||
state.page = Math.max(0, page.totalPages - 1);
|
||
return loadRequests();
|
||
}
|
||
const normalizedRequests = page.items || [];
|
||
renderRequests(normalizedRequests);
|
||
updateCount(Number(page.totalItems || 0));
|
||
renderPagination(pagination, page, {
|
||
label: 'заявок',
|
||
onPage: nextPage => { state.page = nextPage; loadRequests(); },
|
||
onSize: size => { state.size = size; state.page = 0; loadRequests(); }
|
||
});
|
||
persistState();
|
||
document.dispatchEvent(new CustomEvent('teacherRequestsChanged', {
|
||
detail: { count: state.status === 'PENDING' && !state.query ? Number(page.totalItems || 0) : undefined }
|
||
}));
|
||
trackUxEvent('реестр_заявок_загружен', { total: Number(page.totalItems || 0), page: state.page });
|
||
} catch (error) {
|
||
renderTableError(requestsTbody, 7, 'Не удалось загрузить заявки преподавателей', loadRequests);
|
||
updateCount(0);
|
||
}
|
||
}
|
||
|
||
function renderRequests(requests) {
|
||
if (!requests.length) {
|
||
renderTableEmpty(requestsTbody, 7, state.query ? 'Заявки не найдены' : 'Заявок с выбранным статусом нет');
|
||
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" autocomplete="username"></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="password" class="teacher-request-password" placeholder="Минимум 8 символов" autocomplete="new-password"></td>
|
||
<td>${escapeHtml(request.comment || '-')}</td>
|
||
<td>
|
||
<div class="teacher-request-actions">
|
||
${request.status === 'PENDING' ? `
|
||
<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>
|
||
` : `<span class="badge">${escapeHtml(request.status === 'APPROVED' ? 'Одобрена' : 'Отклонена')}</span>`}
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
markTableReady(requestsTbody);
|
||
}
|
||
|
||
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 = await promptAction({
|
||
title: 'Отклонить заявку?',
|
||
message: 'Комментарий увидит кафедра, отправившая заявку.',
|
||
confirmLabel: 'Отклонить',
|
||
danger: true,
|
||
input: {
|
||
label: 'Причина отклонения',
|
||
placeholder: 'Укажите, что нужно исправить',
|
||
required: true,
|
||
requiredMessage: 'Укажите причину отклонения'
|
||
}
|
||
});
|
||
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);
|
||
searchInput.addEventListener('input', debounce(() => {
|
||
state.query = searchInput.value.trim();
|
||
state.page = 0;
|
||
loadRequests();
|
||
}));
|
||
statusSelect.addEventListener('change', () => {
|
||
state.status = statusSelect.value;
|
||
state.page = 0;
|
||
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) {
|
||
renderTableMessage(requestsTbody, 7, 'Не удалось загрузить заявки преподавателей');
|
||
}
|
||
|
||
function persistState() {
|
||
writeUrlState({
|
||
requestQuery: state.query,
|
||
requestStatus: state.status === 'PENDING' ? '' : state.status,
|
||
requestPage: state.page || '',
|
||
requestSize: state.size === 25 ? '' : state.size
|
||
});
|
||
}
|
||
}
|