575 lines
25 KiB
JavaScript
575 lines
25 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 importRowsContainer = document.getElementById('subject-import-rows');
|
||
const importAddButton = document.getElementById('subject-import-add');
|
||
const assignTeacherForm = document.getElementById('department-teacher-assign-form');
|
||
const assignTeacherSelect = document.getElementById('department-teacher-select');
|
||
const assignTeacherComment = document.getElementById('department-teacher-assign-comment');
|
||
const teacherRequestForm = document.getElementById('department-teacher-request-form');
|
||
const teacherRequestsTbody = document.getElementById('department-teacher-requests-tbody');
|
||
const teacherRequestCount = document.getElementById('department-teacher-request-count');
|
||
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');
|
||
let lastTeachers = null;
|
||
let lastWorkload = null;
|
||
let allTeachers = [];
|
||
let teacherRequests = null;
|
||
let teachersErrorMessage = '';
|
||
let workloadErrorMessage = '';
|
||
|
||
setDefaultDates(startInput, endInput);
|
||
refreshButton?.addEventListener('click', loadWorkspace);
|
||
departmentSelect?.addEventListener('change', loadWorkspace);
|
||
startInput?.addEventListener('change', handleStartDateChange);
|
||
endInput?.addEventListener('change', loadWorkload);
|
||
importForm?.addEventListener('submit', importSubjects);
|
||
assignTeacherForm?.addEventListener('submit', assignTeacherToDepartment);
|
||
teacherRequestForm?.addEventListener('submit', submitTeacherRequest);
|
||
importAddButton?.addEventListener('click', () => renderImportRows([...readImportRows(), { code: '', name: '' }]));
|
||
importRowsContainer?.addEventListener('click', (event) => {
|
||
const removeBtn = event.target.closest('.subject-import-remove');
|
||
if (removeBtn) {
|
||
const rows = readImportRows();
|
||
rows.splice(Number(removeBtn.dataset.index), 1);
|
||
renderImportRows(rows.length ? rows : [{ code: '', name: '' }]);
|
||
}
|
||
});
|
||
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 loadAllTeachers();
|
||
renderImportRows([{ code: '', name: '' }]);
|
||
await loadWorkspace();
|
||
} catch (error) {
|
||
showAlert('department-workspace-alert', error.message || 'Ошибка загрузки кабинета кафедры', 'error');
|
||
}
|
||
|
||
async function loadWorkspace() {
|
||
hideAlert('department-workspace-alert');
|
||
lastTeachers = null;
|
||
lastWorkload = null;
|
||
teachersErrorMessage = '';
|
||
workloadErrorMessage = '';
|
||
renderTeachers();
|
||
teacherRequests = null;
|
||
renderTeacherRequests();
|
||
await Promise.all([loadSubjects(), loadTeachers(), loadWorkload(), loadTeacherRequests()]);
|
||
}
|
||
|
||
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 btn-sm btn-secondary 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 дисциплин';
|
||
}
|
||
}
|
||
|
||
function renderImportRows(rows) {
|
||
if (!importRowsContainer) return;
|
||
importRowsContainer.innerHTML = (rows || [{ code: '', name: '' }]).map((row, index) => `
|
||
<div class="subject-import-row" data-index="${index}">
|
||
<div class="form-group">
|
||
<label>Код</label>
|
||
<input type="text" class="subject-import-code" value="${escapeHtml(row.code || '')}" placeholder="DB201">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Название</label>
|
||
<input type="text" class="subject-import-name" value="${escapeHtml(row.name || '')}" placeholder="Базы данных">
|
||
</div>
|
||
<button type="button" class="btn btn-sm btn-danger subject-import-remove" data-index="${index}">Удалить</button>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function readImportRows() {
|
||
if (!importRowsContainer) return [];
|
||
return Array.from(importRowsContainer.querySelectorAll('.subject-import-row')).map(row => ({
|
||
code: (row.querySelector('.subject-import-code')?.value || '').trim(),
|
||
name: (row.querySelector('.subject-import-name')?.value || '').trim()
|
||
}));
|
||
}
|
||
|
||
async function importSubjects(event) {
|
||
event.preventDefault();
|
||
const alertId = 'department-subject-import-alert';
|
||
hideAlert(alertId);
|
||
const departmentId = selectedDepartmentId();
|
||
const rows = readImportRows()
|
||
.filter(item => item.name)
|
||
.map(item => ({
|
||
code: item.code,
|
||
name: item.name,
|
||
departmentId: Number(departmentId)
|
||
}));
|
||
|
||
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');
|
||
renderImportRows([{ code: '', name: '' }]);
|
||
await loadSubjects();
|
||
} catch (error) {
|
||
showAlert(alertId, error.message || 'Ошибка загрузки дисциплин', 'error');
|
||
}
|
||
}
|
||
|
||
async function assignTeacherToDepartment(event) {
|
||
event.preventDefault();
|
||
const alertId = 'department-teacher-assign-alert';
|
||
hideAlert(alertId);
|
||
const teacherId = assignTeacherSelect?.value;
|
||
const departmentId = selectedDepartmentId();
|
||
if (!departmentId) {
|
||
showAlert(alertId, 'Выберите кафедру', 'error');
|
||
return;
|
||
}
|
||
if (!teacherId) {
|
||
showAlert(alertId, 'Выберите преподавателя', 'error');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await api.post(`/api/department/teachers/${encodeURIComponent(teacherId)}/assignments`, {
|
||
departmentId: Number(departmentId),
|
||
comment: assignTeacherComment?.value.trim() || null
|
||
});
|
||
showAlert(alertId, 'Преподаватель привязан к кафедре', 'success');
|
||
if (assignTeacherComment) assignTeacherComment.value = '';
|
||
await Promise.all([loadTeachers(), loadWorkload()]);
|
||
} catch (error) {
|
||
showAlert(alertId, error.message || 'Ошибка привязки преподавателя', 'error');
|
||
}
|
||
}
|
||
|
||
async function submitTeacherRequest(event) {
|
||
event.preventDefault();
|
||
const alertId = 'department-teacher-request-alert';
|
||
hideAlert(alertId);
|
||
const departmentId = selectedDepartmentId();
|
||
const username = document.getElementById('teacher-request-username')?.value.trim();
|
||
const fullName = document.getElementById('teacher-request-fullname')?.value.trim();
|
||
const jobTitle = document.getElementById('teacher-request-jobtitle')?.value.trim();
|
||
const comment = document.getElementById('teacher-request-comment')?.value.trim();
|
||
if (!departmentId) {
|
||
showAlert(alertId, 'Выберите кафедру', 'error');
|
||
return;
|
||
}
|
||
if (!username || !fullName) {
|
||
showAlert(alertId, 'Заполните логин и ФИО преподавателя', 'error');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await api.post('/api/department/teacher-requests', {
|
||
departmentId: Number(departmentId),
|
||
username,
|
||
fullName,
|
||
jobTitle,
|
||
comment
|
||
});
|
||
showAlert(alertId, 'Заявка отправлена администратору', 'success');
|
||
teacherRequestForm.reset();
|
||
await loadTeacherRequests();
|
||
} 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() {
|
||
lastTeachers = null;
|
||
teachersErrorMessage = '';
|
||
renderTeachers();
|
||
renderTeacherSelect();
|
||
try {
|
||
const teachers = await api.get(`/api/department/teachers?departmentId=${encodeURIComponent(selectedDepartmentId() || '')}`);
|
||
lastTeachers = Array.isArray(teachers) ? teachers : [];
|
||
renderTeachers();
|
||
renderTeacherSelect();
|
||
} catch (error) {
|
||
lastTeachers = [];
|
||
teachersErrorMessage = error.message || 'Ошибка загрузки преподавателей';
|
||
renderTeachers();
|
||
renderTeacherSelect();
|
||
}
|
||
}
|
||
|
||
async function loadAllTeachers() {
|
||
try {
|
||
const teachers = await api.get('/api/users/teachers');
|
||
allTeachers = Array.isArray(teachers) ? teachers : [];
|
||
renderTeacherSelect();
|
||
} catch (error) {
|
||
allTeachers = [];
|
||
if (assignTeacherSelect) {
|
||
assignTeacherSelect.innerHTML = `<option value="">${escapeHtml(error.message || 'Ошибка загрузки преподавателей')}</option>`;
|
||
}
|
||
}
|
||
}
|
||
|
||
async function loadTeacherRequests() {
|
||
teacherRequests = null;
|
||
renderTeacherRequests();
|
||
try {
|
||
const requests = await api.get(`/api/department/teacher-requests?departmentId=${encodeURIComponent(selectedDepartmentId() || '')}`);
|
||
teacherRequests = Array.isArray(requests) ? requests : [];
|
||
renderTeacherRequests();
|
||
} catch (error) {
|
||
teacherRequests = [];
|
||
if (teacherRequestsTbody) {
|
||
teacherRequestsTbody.innerHTML = `<tr><td colspan="6" class="loading-row">${escapeHtml(error.message || 'Ошибка загрузки заявок')}</td></tr>`;
|
||
}
|
||
if (teacherRequestCount) teacherRequestCount.textContent = '0 заявок';
|
||
}
|
||
}
|
||
|
||
async function loadWorkload() {
|
||
if (!startInput.value || !endInput.value) {
|
||
lastWorkload = [];
|
||
workloadErrorMessage = 'Выберите начало и окончание периода';
|
||
renderTeachers();
|
||
return;
|
||
}
|
||
|
||
const params = new URLSearchParams({
|
||
startDate: startInput.value,
|
||
endDate: endInput.value
|
||
});
|
||
if (selectedDepartmentId()) {
|
||
params.set('departmentId', selectedDepartmentId());
|
||
}
|
||
lastWorkload = null;
|
||
workloadErrorMessage = '';
|
||
renderTeachers();
|
||
try {
|
||
const workload = await api.get(`/api/workload/teachers?${params}`);
|
||
lastWorkload = Array.isArray(workload) ? workload : [];
|
||
renderTeachers();
|
||
} catch (error) {
|
||
lastWorkload = [];
|
||
workloadErrorMessage = error.message || 'Ошибка расчёта нагрузки';
|
||
renderTeachers();
|
||
}
|
||
}
|
||
|
||
async function handleStartDateChange() {
|
||
if (startInput.value && endInput.value && startInput.value > endInput.value) {
|
||
endInput.value = '';
|
||
}
|
||
await loadWorkload();
|
||
}
|
||
|
||
function renderTeachers() {
|
||
const container = document.getElementById('department-teachers-list');
|
||
if (!container) return;
|
||
|
||
if (lastTeachers === null || lastWorkload === null) {
|
||
container.textContent = 'Загрузка...';
|
||
return;
|
||
}
|
||
|
||
const teachers = Array.isArray(lastTeachers) ? lastTeachers : [];
|
||
const workload = Array.isArray(lastWorkload) ? lastWorkload : [];
|
||
const workloadByTeacher = new Map();
|
||
const usedWorkloadKeys = new Set();
|
||
|
||
workload
|
||
.filter(item => item && (item.id || item.name))
|
||
.forEach(item => {
|
||
workloadKeys(item).forEach(key => {
|
||
if (!workloadByTeacher.has(key)) {
|
||
workloadByTeacher.set(key, item);
|
||
}
|
||
});
|
||
});
|
||
|
||
const rows = teachers.map(teacher => {
|
||
const workloadItem = workloadKeys(teacher)
|
||
.map(key => workloadByTeacher.get(key))
|
||
.find(Boolean);
|
||
if (workloadItem) {
|
||
workloadKeys(workloadItem).forEach(key => usedWorkloadKeys.add(key));
|
||
}
|
||
return renderTeacherMetric({
|
||
name: teacher.fullName || teacher.username || '-',
|
||
meta: teacher.jobTitle || 'Должность не указана',
|
||
workload: workloadItem
|
||
}, workloadErrorMessage);
|
||
});
|
||
|
||
workload
|
||
.filter(item => item && (item.id || item.name))
|
||
.filter(item => workloadKeys(item).every(key => !usedWorkloadKeys.has(key)))
|
||
.forEach(item => {
|
||
rows.push(renderTeacherMetric({
|
||
name: item.name || '-',
|
||
meta: 'Есть занятия в расписании кафедры',
|
||
workload: item
|
||
}, workloadErrorMessage));
|
||
});
|
||
|
||
if (rows.length) {
|
||
container.innerHTML = rows.join('');
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = `<div class="loading-row">${escapeHtml(teachersErrorMessage || 'Преподаватели не найдены')}</div>`;
|
||
}
|
||
|
||
function renderTeacherSelect() {
|
||
if (!assignTeacherSelect) return;
|
||
if (lastTeachers === null) {
|
||
assignTeacherSelect.innerHTML = '<option value="">Загрузка...</option>';
|
||
return;
|
||
}
|
||
const assignedIds = new Set((lastTeachers || []).map(teacher => String(teacher.id)));
|
||
const availableTeachers = allTeachers.filter(teacher => !assignedIds.has(String(teacher.id)));
|
||
assignTeacherSelect.innerHTML = '<option value="">Выберите преподавателя</option>' +
|
||
availableTeachers.map(teacher => `
|
||
<option value="${escapeHtml(teacher.id)}">
|
||
${escapeHtml(teacher.fullName || teacher.username || `ID ${teacher.id}`)}
|
||
</option>
|
||
`).join('');
|
||
if (!availableTeachers.length) {
|
||
assignTeacherSelect.innerHTML = '<option value="">Нет доступных преподавателей</option>';
|
||
}
|
||
}
|
||
|
||
function renderTeacherRequests() {
|
||
if (!teacherRequestsTbody) return;
|
||
if (teacherRequests === null) {
|
||
teacherRequestsTbody.innerHTML = '<tr><td colspan="6" class="loading-row">Загрузка...</td></tr>';
|
||
if (teacherRequestCount) teacherRequestCount.textContent = 'Загрузка...';
|
||
return;
|
||
}
|
||
if (teacherRequestCount) teacherRequestCount.textContent = requestCountLabel(teacherRequests.length);
|
||
if (!teacherRequests.length) {
|
||
teacherRequestsTbody.innerHTML = '<tr><td colspan="6" class="loading-row">Заявок пока нет</td></tr>';
|
||
return;
|
||
}
|
||
teacherRequestsTbody.innerHTML = teacherRequests.map(request => `
|
||
<tr>
|
||
<td><span class="badge ${requestStatusClass(request.status)}">${escapeHtml(requestStatusLabel(request.status))}</span></td>
|
||
<td>${escapeHtml(request.username || '-')}</td>
|
||
<td>${escapeHtml(request.fullName || '-')}</td>
|
||
<td>${escapeHtml(request.jobTitle || '-')}</td>
|
||
<td>${escapeHtml(request.comment || '-')}</td>
|
||
<td>${escapeHtml(request.reviewComment || formatDateTime(request.reviewedAt) || '-')}</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
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} дисциплин`;
|
||
}
|
||
|
||
function requestCountLabel(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} заявок`;
|
||
}
|
||
|
||
function requestStatusLabel(status) {
|
||
return {
|
||
PENDING: 'На рассмотрении',
|
||
APPROVED: 'Одобрена',
|
||
REJECTED: 'Отклонена'
|
||
}[status] || status || '-';
|
||
}
|
||
|
||
function requestStatusClass(status) {
|
||
return {
|
||
PENDING: 'badge-ef',
|
||
APPROVED: 'badge-student',
|
||
REJECTED: 'badge-admin'
|
||
}[status] || 'badge-ef';
|
||
}
|
||
|
||
function renderTeacherMetric(item, workloadErrorMessage) {
|
||
return `
|
||
<article class="metric-card">
|
||
<strong>${escapeHtml(item.name || '-')}</strong>
|
||
<span>${escapeHtml(item.meta || 'Должность не указана')}</span>
|
||
<small>${escapeHtml(workloadLabel(item.workload, workloadErrorMessage))}</small>
|
||
</article>
|
||
`;
|
||
}
|
||
|
||
function workloadLabel(item, workloadErrorMessage) {
|
||
if (workloadErrorMessage) {
|
||
return workloadErrorMessage;
|
||
}
|
||
if (!item) {
|
||
return '0 ак. ч., 0 занятий за период';
|
||
}
|
||
const hours = String(item.academicHours ?? 0);
|
||
const lessons = String(item.lessonCount ?? 0);
|
||
return `${hours} ак. ч., ${lessons} занятий за период`;
|
||
}
|
||
|
||
function workloadKeys(item) {
|
||
return [
|
||
item.id == null ? null : `id:${item.id}`,
|
||
normalizeTeacherName(item.fullName || item.name || item.username)
|
||
].filter(Boolean);
|
||
}
|
||
|
||
function normalizeTeacherName(value) {
|
||
return value ? `name:${String(value).trim().toLowerCase()}` : null;
|
||
}
|