Files
magistr/frontend/admin/js/views/department-workspace.js

264 lines
12 KiB
JavaScript

import { api } from '../api.js';
import { escapeHtml, showAlert, hideAlert } from '../utils.js';
const ROLE_DEPARTMENT = 'DEPARTMENT';
export async function initDepartmentWorkspace() {
const role = localStorage.getItem('role');
const userDepartmentId = localStorage.getItem('departmentId');
const departmentField = document.getElementById('department-workspace-department-field');
const departmentSelect = document.getElementById('department-workspace-department');
const startInput = document.getElementById('department-workspace-start');
const endInput = document.getElementById('department-workspace-end');
const refreshButton = document.getElementById('department-workspace-refresh');
const importForm = document.getElementById('department-subject-import-form');
const subjectsTbody = document.getElementById('department-subjects-tbody');
const subjectCount = document.getElementById('department-subject-count');
const commentsCard = document.getElementById('department-comments-card');
const commentsTitle = document.getElementById('department-comments-title');
const commentsList = document.getElementById('department-comment-list');
const commentsForm = document.getElementById('department-comment-form');
const commentsSubjectInput = document.getElementById('department-comment-subject-id');
const commentsTextInput = document.getElementById('department-comment-text');
const commentsCloseButton = document.getElementById('department-comments-close');
setDefaultDates(startInput, endInput);
refreshButton?.addEventListener('click', loadWorkspace);
departmentSelect?.addEventListener('change', loadWorkspace);
startInput?.addEventListener('change', loadWorkload);
endInput?.addEventListener('change', loadWorkload);
importForm?.addEventListener('submit', importSubjects);
commentsForm?.addEventListener('submit', saveComment);
commentsCloseButton?.addEventListener('click', () => {
commentsCard.hidden = true;
commentsSubjectInput.value = '';
});
subjectsTbody?.addEventListener('click', event => {
const button = event.target.closest('.department-comments-open');
if (button) {
openComments(button.dataset.subjectId, button.dataset.subjectName);
}
});
try {
await loadDepartments(role, userDepartmentId);
if (role === ROLE_DEPARTMENT) {
departmentField.hidden = true;
departmentSelect.value = userDepartmentId || '';
}
await loadWorkspace();
} catch (error) {
showAlert('department-workspace-alert', error.message || 'Ошибка загрузки кабинета кафедры', 'error');
}
async function loadWorkspace() {
hideAlert('department-workspace-alert');
await Promise.all([loadSubjects(), loadTeachers(), loadWorkload()]);
}
async function loadSubjects() {
const departmentId = selectedDepartmentId();
subjectsTbody.innerHTML = '<tr><td colspan="4" class="loading-row">Загрузка...</td></tr>';
subjectCount.textContent = 'Загрузка...';
try {
const subjects = await api.get(`/api/department/subjects?departmentId=${encodeURIComponent(departmentId || '')}`);
subjectCount.textContent = subjectCountLabel(subjects.length);
if (!subjects.length) {
subjectsTbody.innerHTML = '<tr><td colspan="4" class="loading-row">Дисциплины не найдены</td></tr>';
return;
}
subjectsTbody.innerHTML = subjects.map(subject => `
<tr>
<td>${escapeHtml(subject.code || '-')}</td>
<td>${escapeHtml(subject.name || '-')}</td>
<td>${escapeHtml(subject.description || '-')}</td>
<td>
<button type="button" class="btn-edit-classroom department-comments-open"
data-subject-id="${escapeHtml(subject.id)}"
data-subject-name="${escapeHtml(subject.name || '')}">
Открыть
</button>
</td>
</tr>
`).join('');
} catch (error) {
subjectsTbody.innerHTML = `<tr><td colspan="4" class="loading-row">${escapeHtml(error.message || 'Ошибка загрузки дисциплин')}</td></tr>`;
subjectCount.textContent = '0 дисциплин';
}
}
async function importSubjects(event) {
event.preventDefault();
const alertId = 'department-subject-import-alert';
hideAlert(alertId);
const departmentId = selectedDepartmentId();
const rows = document.getElementById('department-subject-import-text').value
.split('\n')
.map(row => row.trim())
.filter(Boolean)
.map(row => {
const [code, ...nameParts] = row.split(';');
return {
code: (code || '').trim(),
name: nameParts.join(';').trim(),
departmentId: Number(departmentId)
};
})
.filter(item => item.name);
if (!departmentId) {
showAlert(alertId, 'Выберите кафедру', 'error');
return;
}
if (!rows.length) {
showAlert(alertId, 'Добавьте хотя бы одну строку в формате «код; название»', 'error');
return;
}
try {
const result = await api.post('/api/department/subjects/import', rows);
showAlert(alertId, result.message || 'Дисциплины загружены', 'success');
document.getElementById('department-subject-import-text').value = '';
await loadSubjects();
} catch (error) {
showAlert(alertId, error.message || 'Ошибка загрузки дисциплин', 'error');
}
}
async function openComments(subjectId, subjectName) {
commentsCard.hidden = false;
commentsTitle.textContent = `Комментарии: ${subjectName || 'дисциплина'}`;
commentsSubjectInput.value = subjectId;
commentsTextInput.value = '';
hideAlert('department-comment-alert');
commentsList.textContent = 'Загрузка...';
try {
const comments = await api.get(`/api/department/subjects/${subjectId}/comments`);
commentsList.innerHTML = comments.length
? comments.map(comment => `
<article class="department-comment-item">
<strong>${escapeHtml(comment.authorName || 'Автор не указан')}</strong>
<span>${escapeHtml(formatDateTime(comment.createdAt))}</span>
<p>${escapeHtml(comment.comment)}</p>
</article>
`).join('')
: '<div class="loading-row">Комментариев пока нет</div>';
} catch (error) {
commentsList.innerHTML = `<div class="loading-row">${escapeHtml(error.message || 'Ошибка загрузки комментариев')}</div>`;
}
}
async function saveComment(event) {
event.preventDefault();
const subjectId = commentsSubjectInput.value;
const comment = commentsTextInput.value.trim();
if (!subjectId) {
showAlert('department-comment-alert', 'Выберите дисциплину', 'error');
return;
}
if (!comment) {
showAlert('department-comment-alert', 'Комментарий не должен быть пустым', 'error');
return;
}
try {
await api.post(`/api/department/subjects/${subjectId}/comments`, { comment });
showAlert('department-comment-alert', 'Комментарий сохранён', 'success');
await openComments(subjectId, commentsTitle.textContent.replace('Комментарии: ', ''));
} catch (error) {
showAlert('department-comment-alert', error.message || 'Ошибка сохранения комментария', 'error');
}
}
async function loadTeachers() {
const container = document.getElementById('department-teachers-list');
container.textContent = 'Загрузка...';
try {
const teachers = await api.get(`/api/department/teachers?departmentId=${encodeURIComponent(selectedDepartmentId() || '')}`);
container.innerHTML = teachers.length
? teachers.map(teacher => `
<article class="metric-card">
<strong>${escapeHtml(teacher.fullName || teacher.username || '-')}</strong>
<span>${escapeHtml(teacher.jobTitle || 'Должность не указана')}</span>
</article>
`).join('')
: '<div class="loading-row">Преподаватели не найдены</div>';
} catch (error) {
container.innerHTML = `<div class="loading-row">${escapeHtml(error.message || 'Ошибка загрузки преподавателей')}</div>`;
}
}
async function loadWorkload() {
const container = document.getElementById('department-workload-list');
const params = new URLSearchParams({
startDate: startInput.value,
endDate: endInput.value
});
if (selectedDepartmentId()) {
params.set('departmentId', selectedDepartmentId());
}
container.textContent = 'Загрузка...';
try {
const workload = await api.get(`/api/workload/teachers?${params}`);
container.innerHTML = workload.length
? workload.map(item => `
<article class="metric-card">
<strong>${escapeHtml(item.name || '-')}</strong>
<span>${escapeHtml(String(item.academicHours ?? 0))} ак. ч.</span>
<small>${escapeHtml(String(item.lessonCount ?? 0))} занятий</small>
</article>
`).join('')
: '<div class="loading-row">Нагрузка не найдена</div>';
} catch (error) {
container.innerHTML = `<div class="loading-row">${escapeHtml(error.message || 'Ошибка расчёта нагрузки')}</div>`;
}
}
function selectedDepartmentId() {
return role === ROLE_DEPARTMENT ? userDepartmentId : departmentSelect.value;
}
}
async function loadDepartments(role, userDepartmentId) {
const departments = await api.get('/api/departments');
fillSelect('department-workspace-department', departments, item => item.id, item => item.departmentName, 'Выберите кафедру');
if (role === ROLE_DEPARTMENT && userDepartmentId) {
document.getElementById('department-workspace-department').value = userDepartmentId;
} else if (!document.getElementById('department-workspace-department').value && departments.length) {
document.getElementById('department-workspace-department').value = departments[0].id;
}
}
function fillSelect(id, items, valueFn, labelFn, emptyLabel) {
const select = document.getElementById(id);
if (!select) return;
select.innerHTML = `<option value="">${escapeHtml(emptyLabel)}</option>` +
(items || []).map(item => `<option value="${escapeHtml(valueFn(item))}">${escapeHtml(labelFn(item))}</option>`).join('');
}
function setDefaultDates(startInput, endInput) {
const today = new Date();
const nextWeek = new Date();
nextWeek.setDate(today.getDate() + 7);
startInput.value = toIsoDate(today);
endInput.value = toIsoDate(nextWeek);
}
function toIsoDate(date) {
return date.toISOString().slice(0, 10);
}
function formatDateTime(value) {
if (!value) return '-';
return String(value).replace('T', ' ').slice(0, 16);
}
function subjectCountLabel(count) {
const tail = count % 100;
if (tail >= 11 && tail <= 14) return `${count} дисциплин`;
if (count % 10 === 1) return `${count} дисциплина`;
if ([2, 3, 4].includes(count % 10)) return `${count} дисциплины`;
return `${count} дисциплин`;
}