3 Задача
This commit is contained in:
@@ -22,6 +22,29 @@ import {
|
||||
const absenceStart = document.getElementById('teacher-absence-start');
|
||||
const absenceEnd = document.getElementById('teacher-absence-end');
|
||||
const absenceReason = document.getElementById('teacher-absence-reason');
|
||||
const preferenceSemester = document.getElementById('preference-semester');
|
||||
const preferenceGrid = document.getElementById('preference-grid');
|
||||
const preferenceMessage = document.getElementById('preference-message');
|
||||
const preferenceDateForm = document.getElementById('preference-date-form');
|
||||
const preferenceDate = document.getElementById('preference-date');
|
||||
const preferenceDateComment = document.getElementById('preference-date-comment');
|
||||
const preferenceDateList = document.getElementById('preference-date-list');
|
||||
const preferenceConsecutive = document.getElementById('preference-consecutive');
|
||||
const preferenceNoGaps = document.getElementById('preference-no-gaps');
|
||||
const requestList = document.getElementById('change-request-list');
|
||||
const requestOverlay = document.getElementById('request-dialog-overlay');
|
||||
const requestForm = document.getElementById('change-request-form');
|
||||
const requestType = document.getElementById('change-request-type');
|
||||
const requestDate = document.getElementById('change-request-date');
|
||||
const requestTime = document.getElementById('change-request-time');
|
||||
const requestClassroom = document.getElementById('change-request-classroom');
|
||||
const requestReason = document.getElementById('change-request-reason');
|
||||
const requestMessage = document.getElementById('request-dialog-message');
|
||||
let preferenceMeta = { semesters: [], timeSlots: [] };
|
||||
let preferences = [];
|
||||
let scheduleLessons = new Map();
|
||||
let activeRequestLesson = null;
|
||||
let activeCandidates = null;
|
||||
let weekStart = startOfWeek(new Date());
|
||||
|
||||
document.getElementById('logout-link').addEventListener('click', async (event) => {
|
||||
@@ -58,6 +81,29 @@ import {
|
||||
});
|
||||
absenceForm.addEventListener('submit', submitAbsence);
|
||||
absenceList.addEventListener('click', cancelAbsence);
|
||||
document.querySelectorAll('[data-workspace-tab]').forEach(button => {
|
||||
button.addEventListener('click', () => switchWorkspace(button.dataset.workspaceTab));
|
||||
});
|
||||
preferenceSemester.addEventListener('change', loadPreferences);
|
||||
preferenceGrid.addEventListener('click', handlePreferenceCell);
|
||||
preferenceDateForm.addEventListener('submit', submitUnavailableDate);
|
||||
preferenceDateList.addEventListener('click', cancelPreference);
|
||||
preferenceConsecutive.addEventListener('change', () => toggleCompactPreference('CONSECUTIVE', preferenceConsecutive));
|
||||
preferenceNoGaps.addEventListener('change', () => toggleCompactPreference('NO_GAPS', preferenceNoGaps));
|
||||
grid.addEventListener('click', event => {
|
||||
const button = event.target.closest('[data-request-lesson]');
|
||||
if (button) openRequestDialog(button.dataset.requestLesson);
|
||||
});
|
||||
document.getElementById('change-request-refresh').addEventListener('click', loadChangeRequests);
|
||||
requestList.addEventListener('click', cancelChangeRequest);
|
||||
document.getElementById('request-dialog-close').addEventListener('click', closeRequestDialog);
|
||||
document.getElementById('request-dialog-cancel').addEventListener('click', closeRequestDialog);
|
||||
requestOverlay.addEventListener('click', event => {
|
||||
if (event.target === requestOverlay) closeRequestDialog();
|
||||
});
|
||||
requestType.addEventListener('change', updateRequestFields);
|
||||
requestDate.addEventListener('change', reloadRequestCandidates);
|
||||
requestForm.addEventListener('submit', submitChangeRequest);
|
||||
|
||||
const todayIso = toIsoDate(new Date());
|
||||
absenceStart.value = todayIso;
|
||||
@@ -65,6 +111,351 @@ import {
|
||||
|
||||
loadSchedule();
|
||||
loadAbsences();
|
||||
loadPreferenceMeta();
|
||||
loadChangeRequests();
|
||||
|
||||
function switchWorkspace(name) {
|
||||
document.querySelectorAll('[data-workspace-tab]').forEach(button => {
|
||||
button.classList.toggle('active', button.dataset.workspaceTab === name);
|
||||
});
|
||||
document.querySelectorAll('[data-workspace-view]').forEach(view => {
|
||||
const active = view.dataset.workspaceView === name;
|
||||
view.hidden = !active;
|
||||
view.classList.toggle('active', active);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadPreferenceMeta() {
|
||||
try {
|
||||
preferenceMeta = await fetchJson('/api/teacher-preferences/meta');
|
||||
const semesters = preferenceMeta.semesters || [];
|
||||
preferenceSemester.innerHTML = semesters.map(semester => `
|
||||
<option value="${semester.id}">${escapeHtml(semesterLabel(semester))}</option>
|
||||
`).join('');
|
||||
const today = toIsoDate(new Date());
|
||||
const current = semesters.find(semester => semester.startDate <= today && semester.endDate >= today);
|
||||
if (current) preferenceSemester.value = String(current.id);
|
||||
await loadPreferences();
|
||||
} catch (error) {
|
||||
showPreferenceMessage(error.message, true);
|
||||
preferenceGrid.innerHTML = '<div class="preference-empty">Не удалось загрузить параметры семестра</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPreferences() {
|
||||
if (!preferenceSemester.value) return;
|
||||
preferenceGrid.innerHTML = '<div class="preference-empty">Загрузка календаря...</div>';
|
||||
try {
|
||||
preferences = await fetchJson(`/api/teacher-preferences?semesterId=${encodeURIComponent(preferenceSemester.value)}`);
|
||||
const semester = selectedSemester();
|
||||
preferenceDate.min = semester?.startDate || '';
|
||||
preferenceDate.max = semester?.endDate || '';
|
||||
if (!preferenceDate.value || preferenceDate.value < preferenceDate.min || preferenceDate.value > preferenceDate.max) {
|
||||
preferenceDate.value = semester?.startDate || '';
|
||||
}
|
||||
renderPreferenceGrid();
|
||||
renderPreferenceDates();
|
||||
syncCompactToggles();
|
||||
showPreferenceMessage('', false);
|
||||
} catch (error) {
|
||||
preferenceGrid.innerHTML = `<div class="preference-empty error">${escapeHtml(error.message)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderPreferenceGrid() {
|
||||
const slots = preferenceMeta.timeSlots || [];
|
||||
if (!slots.length) {
|
||||
preferenceGrid.innerHTML = '<div class="preference-empty">Базовая сетка времени не настроена</div>';
|
||||
return;
|
||||
}
|
||||
const active = preferences.filter(item => ['PENDING', 'APPROVED'].includes(item.status) && item.dayOfWeek);
|
||||
preferenceGrid.innerHTML = [
|
||||
'<div class="preference-grid-corner">Пара</div>',
|
||||
...['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'].map(day => `<div class="preference-grid-day">${day}</div>`),
|
||||
...slots.flatMap(slot => [
|
||||
`<div class="preference-grid-time"><strong>${slot.orderNumber}</strong><span>${trimTime(slot.startTime)}–${trimTime(slot.endTime)}</span></div>`,
|
||||
...Array.from({ length: 7 }, (_, index) => {
|
||||
const item = active.find(candidate => candidate.dayOfWeek === index + 1 && candidate.timeSlotId === slot.id);
|
||||
const typeClass = preferenceTypeClass(item?.preferenceType);
|
||||
const pendingClass = item?.status === 'PENDING' ? ' is-pending' : '';
|
||||
const title = item
|
||||
? `${preferenceTypeLabel(item.preferenceType)} · ${preferenceStatusLabel(item.status)}`
|
||||
: 'Добавить отметку';
|
||||
return `<button type="button" class="preference-cell ${typeClass}${pendingClass}"
|
||||
data-day="${index + 1}" data-slot="${slot.id}" data-preference-id="${item?.id || ''}"
|
||||
data-preference-status="${item?.status || ''}" title="${escapeHtml(title)}">
|
||||
<span>${item ? preferenceTypeSymbol(item.preferenceType) : '+'}</span>
|
||||
</button>`;
|
||||
})
|
||||
])
|
||||
].join('');
|
||||
}
|
||||
|
||||
async function handlePreferenceCell(event) {
|
||||
const button = event.target.closest('.preference-cell');
|
||||
if (!button) return;
|
||||
if (button.dataset.preferenceId) {
|
||||
if (button.dataset.preferenceStatus === 'APPROVED') {
|
||||
showPreferenceMessage('Согласованную отметку может снять кафедра или учебный отдел', true);
|
||||
return;
|
||||
}
|
||||
await deletePreference(button.dataset.preferenceId);
|
||||
return;
|
||||
}
|
||||
button.disabled = true;
|
||||
try {
|
||||
await fetchJson('/api/teacher-preferences', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
semesterId: Number(preferenceSemester.value),
|
||||
preferenceType: document.getElementById('preference-brush').value,
|
||||
dayOfWeek: Number(button.dataset.day),
|
||||
timeSlotId: Number(button.dataset.slot)
|
||||
})
|
||||
});
|
||||
showPreferenceMessage('Отметка отправлена на согласование', false);
|
||||
await loadPreferences();
|
||||
} catch (error) {
|
||||
showPreferenceMessage(error.message, true);
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function submitUnavailableDate(event) {
|
||||
event.preventDefault();
|
||||
if (!preferenceDate.value) return;
|
||||
try {
|
||||
await fetchJson('/api/teacher-preferences', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
semesterId: Number(preferenceSemester.value),
|
||||
preferenceType: 'HARD_UNAVAILABLE',
|
||||
preferenceDate: preferenceDate.value,
|
||||
comment: preferenceDateComment.value.trim() || null
|
||||
})
|
||||
});
|
||||
preferenceDateComment.value = '';
|
||||
showPreferenceMessage('Дата недоступности отправлена на согласование', false);
|
||||
await loadPreferences();
|
||||
} catch (error) {
|
||||
showPreferenceMessage(error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
function renderPreferenceDates() {
|
||||
const dates = preferences.filter(item => item.preferenceDate && item.status !== 'CANCELLED');
|
||||
preferenceDateList.innerHTML = dates.length ? dates.map(item => `
|
||||
<article>
|
||||
<span class="preference-date-mark ${item.status === 'APPROVED' ? 'approved' : ''}"></span>
|
||||
<div><strong>${escapeHtml(formatIsoDate(item.preferenceDate))}</strong><small>${escapeHtml(item.comment || 'Весь день')}</small></div>
|
||||
<span class="preference-status">${escapeHtml(preferenceStatusLabel(item.status))}</span>
|
||||
${item.status === 'PENDING' ? `<button type="button" class="btn btn-sm btn-danger-subtle" data-cancel-preference="${item.id}">Отменить</button>` : ''}
|
||||
</article>
|
||||
`).join('') : '<div class="preference-empty">Даты полной недоступности не добавлены</div>';
|
||||
}
|
||||
|
||||
async function cancelPreference(event) {
|
||||
const button = event.target.closest('[data-cancel-preference]');
|
||||
if (button) await deletePreference(button.dataset.cancelPreference);
|
||||
}
|
||||
|
||||
async function deletePreference(id) {
|
||||
try {
|
||||
await fetchJson(`/api/teacher-preferences/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||
showPreferenceMessage('Ожидающее пожелание отменено', false);
|
||||
await loadPreferences();
|
||||
} catch (error) {
|
||||
showPreferenceMessage(error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
function syncCompactToggles() {
|
||||
syncCompactToggle(preferenceConsecutive, 'CONSECUTIVE');
|
||||
syncCompactToggle(preferenceNoGaps, 'NO_GAPS');
|
||||
}
|
||||
|
||||
function syncCompactToggle(input, type) {
|
||||
const item = preferences.find(candidate => candidate.preferenceType === type && ['PENDING', 'APPROVED'].includes(candidate.status));
|
||||
input.checked = Boolean(item);
|
||||
input.dataset.preferenceId = item?.id || '';
|
||||
input.disabled = item?.status === 'APPROVED';
|
||||
input.closest('.preference-toggle-card')?.classList.toggle('approved', item?.status === 'APPROVED');
|
||||
}
|
||||
|
||||
async function toggleCompactPreference(type, input) {
|
||||
try {
|
||||
if (!input.checked && input.dataset.preferenceId) {
|
||||
await fetchJson(`/api/teacher-preferences/${encodeURIComponent(input.dataset.preferenceId)}`, { method: 'DELETE' });
|
||||
} else if (input.checked) {
|
||||
await fetchJson('/api/teacher-preferences', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ semesterId: Number(preferenceSemester.value), preferenceType: type })
|
||||
});
|
||||
}
|
||||
await loadPreferences();
|
||||
} catch (error) {
|
||||
showPreferenceMessage(error.message, true);
|
||||
await loadPreferences();
|
||||
}
|
||||
}
|
||||
|
||||
function showPreferenceMessage(message, isError) {
|
||||
preferenceMessage.textContent = message || '';
|
||||
preferenceMessage.classList.toggle('error', Boolean(isError));
|
||||
preferenceMessage.classList.toggle('success', Boolean(message) && !isError);
|
||||
}
|
||||
|
||||
async function loadChangeRequests() {
|
||||
requestList.innerHTML = '<div class="request-empty">Загрузка заявок...</div>';
|
||||
try {
|
||||
const items = await fetchJson('/api/teacher-change-requests');
|
||||
document.getElementById('change-request-count').textContent = String(items.filter(item => item.status === 'PENDING').length);
|
||||
requestList.innerHTML = items.length ? items.map(renderChangeRequest).join('')
|
||||
: '<div class="request-empty">Заявок на изменение пока нет. Откройте занятие во вкладке «Расписание».</div>';
|
||||
} catch (error) {
|
||||
requestList.innerHTML = `<div class="request-empty error">${escapeHtml(error.message)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderChangeRequest(item) {
|
||||
const target = item.requestType === 'MOVE'
|
||||
? `${formatIsoDate(item.targetLessonDate || item.lessonDate)}, ${trimTime(item.requestedStartTime)}–${trimTime(item.requestedEndTime)}`
|
||||
: item.requestType === 'CHANGE_CLASSROOM'
|
||||
? item.requestedClassroomName
|
||||
: 'Отмена занятия';
|
||||
const history = (item.history || []).map(entry => `
|
||||
<li><time>${formatInstant(entry.createdAt)}</time><strong>${preferenceStatusLabel(entry.status)}</strong><span>${escapeHtml(entry.comment || 'Без комментария')}</span></li>
|
||||
`).join('');
|
||||
return `<article class="request-ledger-item">
|
||||
<div class="request-ledger-stripe ${String(item.status || '').toLowerCase()}"></div>
|
||||
<div class="request-ledger-main">
|
||||
<span>${escapeHtml(formatIsoDate(item.lessonDate))} · ${trimTime(item.sourceStartTime)}–${trimTime(item.sourceEndTime)}</span>
|
||||
<h3>${escapeHtml(item.subjectName)}</h3>
|
||||
<p>${escapeHtml((item.groupNames || []).join(', '))} · ${escapeHtml(item.sourceClassroomName)}</p>
|
||||
<div class="request-change-line"><b>${escapeHtml(changeTypeLabel(item.requestType))}</b><i>→</i><strong>${escapeHtml(target)}</strong></div>
|
||||
<p class="request-reason">${escapeHtml(item.reason)}</p>
|
||||
${item.reviewComment ? `<p class="request-review-comment">Решение: ${escapeHtml(item.reviewComment)}</p>` : ''}
|
||||
<details><summary>История решения · ${(item.history || []).length}</summary><ol>${history}</ol></details>
|
||||
</div>
|
||||
<div class="request-ledger-side">
|
||||
<span class="request-status ${String(item.status || '').toLowerCase()}">${escapeHtml(preferenceStatusLabel(item.status))}</span>
|
||||
${item.status === 'PENDING' ? `<button type="button" class="btn btn-sm btn-danger-subtle" data-cancel-request="${item.id}">Отозвать</button>` : ''}
|
||||
</div>
|
||||
</article>`;
|
||||
}
|
||||
|
||||
async function openRequestDialog(key) {
|
||||
activeRequestLesson = scheduleLessons.get(key);
|
||||
if (!activeRequestLesson) return;
|
||||
requestOverlay.hidden = false;
|
||||
document.body.classList.add('dialog-open');
|
||||
document.getElementById('request-dialog-lesson').textContent = `${activeRequestLesson.subjectName} · ${formatIsoDate(activeRequestLesson.date)} · ${trimTime(activeRequestLesson.startTime)}–${trimTime(activeRequestLesson.endTime)}`;
|
||||
requestReason.value = '';
|
||||
requestType.value = 'MOVE';
|
||||
setRequestMessage('Проверяем допустимые варианты...', false);
|
||||
try {
|
||||
activeCandidates = await fetchJson(`/api/teacher-change-requests/candidates?baseRuleSlotId=${encodeURIComponent(activeRequestLesson.scheduleRuleSlotId)}&lessonDate=${encodeURIComponent(activeRequestLesson.date)}`);
|
||||
renderCandidateDates();
|
||||
renderCandidateOptions();
|
||||
updateRequestFields();
|
||||
setRequestMessage('Доступны только варианты, прошедшие проверку конфликтов', false);
|
||||
} catch (error) {
|
||||
setRequestMessage(error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
function closeRequestDialog() {
|
||||
requestOverlay.hidden = true;
|
||||
document.body.classList.remove('dialog-open');
|
||||
activeRequestLesson = null;
|
||||
activeCandidates = null;
|
||||
}
|
||||
|
||||
function renderCandidateDates() {
|
||||
requestDate.innerHTML = (activeCandidates?.availableDates || []).map(date => `
|
||||
<option value="${date}" ${date === activeCandidates.checkedDate ? 'selected' : ''}>${formatIsoDate(date)}</option>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function renderCandidateOptions() {
|
||||
requestTime.innerHTML = candidateOptions(activeCandidates?.timeSlots, 'Нет свободных временных слотов');
|
||||
requestClassroom.innerHTML = candidateOptions(activeCandidates?.classrooms, 'Нет свободных аудиторий');
|
||||
}
|
||||
|
||||
function candidateOptions(items, emptyLabel) {
|
||||
const available = (items || []).filter(item => item.available);
|
||||
return available.length
|
||||
? available.map(item => `<option value="${item.id}">${escapeHtml(item.label)}</option>`).join('')
|
||||
: `<option value="">${emptyLabel}</option>`;
|
||||
}
|
||||
|
||||
async function reloadRequestCandidates() {
|
||||
if (!activeRequestLesson || !requestDate.value) return;
|
||||
setRequestMessage('Проверяем выбранную дату...', false);
|
||||
try {
|
||||
activeCandidates = await fetchJson(`/api/teacher-change-requests/candidates?baseRuleSlotId=${encodeURIComponent(activeRequestLesson.scheduleRuleSlotId)}&lessonDate=${encodeURIComponent(activeRequestLesson.date)}&targetDate=${encodeURIComponent(requestDate.value)}`);
|
||||
renderCandidateOptions();
|
||||
setRequestMessage('Предварительная проверка завершена', false);
|
||||
} catch (error) {
|
||||
setRequestMessage(error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
function updateRequestFields() {
|
||||
document.querySelector('[data-request-field="date"]').hidden = requestType.value !== 'MOVE';
|
||||
document.querySelector('[data-request-field="time"]').hidden = requestType.value !== 'MOVE';
|
||||
document.querySelector('[data-request-field="classroom"]').hidden = requestType.value !== 'CHANGE_CLASSROOM';
|
||||
}
|
||||
|
||||
async function submitChangeRequest(event) {
|
||||
event.preventDefault();
|
||||
if (!activeRequestLesson) return;
|
||||
if (!requestReason.value.trim()) {
|
||||
setRequestMessage('Укажите обоснование заявки', true);
|
||||
return;
|
||||
}
|
||||
const type = requestType.value;
|
||||
const payload = {
|
||||
baseRuleSlotId: activeRequestLesson.scheduleRuleSlotId,
|
||||
lessonDate: activeRequestLesson.date,
|
||||
requestType: type,
|
||||
targetLessonDate: type === 'MOVE' ? requestDate.value : null,
|
||||
requestedTimeSlotId: type === 'MOVE' ? Number(requestTime.value) || null : null,
|
||||
requestedClassroomId: type === 'CHANGE_CLASSROOM' ? Number(requestClassroom.value) || null : null,
|
||||
reason: requestReason.value.trim()
|
||||
};
|
||||
try {
|
||||
await fetchJson('/api/teacher-change-requests', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
closeRequestDialog();
|
||||
await loadChangeRequests();
|
||||
switchWorkspace('requests');
|
||||
} catch (error) {
|
||||
setRequestMessage(error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelChangeRequest(event) {
|
||||
const button = event.target.closest('[data-cancel-request]');
|
||||
if (!button) return;
|
||||
try {
|
||||
await fetchJson(`/api/teacher-change-requests/${encodeURIComponent(button.dataset.cancelRequest)}`, { method: 'DELETE' });
|
||||
await loadChangeRequests();
|
||||
} catch (error) {
|
||||
requestList.insertAdjacentHTML('afterbegin', `<div class="request-empty error">${escapeHtml(error.message)}</div>`);
|
||||
}
|
||||
}
|
||||
|
||||
function setRequestMessage(message, isError) {
|
||||
requestMessage.textContent = message || '';
|
||||
requestMessage.classList.toggle('error', Boolean(isError));
|
||||
}
|
||||
|
||||
async function loadAbsences() {
|
||||
absenceList.innerHTML = '<div class="teacher-absence-empty">Загрузка заявок...</div>';
|
||||
@@ -152,6 +543,7 @@ import {
|
||||
renderEmptyWeek('Загрузка...');
|
||||
try {
|
||||
const lessons = await fetchJson(`/api/schedule?teacherId=${encodeURIComponent(teacherId)}&startDate=${startDate}&endDate=${endDate}`);
|
||||
scheduleLessons = new Map(lessons.map(lesson => [lessonKey(lesson), lesson]));
|
||||
renderWeek(lessons);
|
||||
showStatus(lessons.length ? `Найдено занятий: ${lessons.length}` : 'На этой неделе занятий нет', false);
|
||||
} catch (error) {
|
||||
@@ -249,10 +641,19 @@ import {
|
||||
<span class="chip">${escapeHtml(lesson.classroomName)}</span>
|
||||
${groups ? `<span class="chip">${escapeHtml(groups)}</span>` : ''}
|
||||
</div>
|
||||
${lesson.scheduleRuleSlotId && !lesson.scheduleOverrideId ? `
|
||||
<button class="lesson-request-button" type="button" data-request-lesson="${escapeHtml(lessonKey(lesson))}">
|
||||
Запросить изменение
|
||||
</button>
|
||||
` : '<span class="lesson-change-note">Занятие уже изменено учебным отделом</span>'}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function lessonKey(lesson) {
|
||||
return `${lesson.scheduleRuleSlotId}:${lesson.date}`;
|
||||
}
|
||||
|
||||
function collectSlots(lessons) {
|
||||
const slots = new Map();
|
||||
lessons.forEach(lesson => {
|
||||
@@ -335,6 +736,59 @@ import {
|
||||
return `is-${String(status || '').toLowerCase()}`;
|
||||
}
|
||||
|
||||
function selectedSemester() {
|
||||
return (preferenceMeta.semesters || []).find(item => String(item.id) === String(preferenceSemester.value));
|
||||
}
|
||||
|
||||
function semesterLabel(semester) {
|
||||
const type = semester.semesterType === 'AUTUMN' ? 'осенний' : semester.semesterType === 'SPRING' ? 'весенний' : semester.semesterType;
|
||||
return `${semester.academicYearTitle} · ${type} · ${formatIsoDate(semester.startDate)}–${formatIsoDate(semester.endDate)}`;
|
||||
}
|
||||
|
||||
function preferenceTypeLabel(type) {
|
||||
return {
|
||||
HARD_UNAVAILABLE: 'Строго недоступен',
|
||||
SOFT_PREFERRED: 'Предпочтительно',
|
||||
SOFT_UNWANTED: 'Нежелательно',
|
||||
CONSECUTIVE: 'Пары подряд',
|
||||
NO_GAPS: 'Без окон'
|
||||
}[type] || type || '—';
|
||||
}
|
||||
|
||||
function preferenceTypeClass(type) {
|
||||
return {
|
||||
HARD_UNAVAILABLE: 'is-hard',
|
||||
SOFT_PREFERRED: 'is-preferred',
|
||||
SOFT_UNWANTED: 'is-unwanted'
|
||||
}[type] || '';
|
||||
}
|
||||
|
||||
function preferenceTypeSymbol(type) {
|
||||
return { HARD_UNAVAILABLE: '×', SOFT_PREFERRED: '✓', SOFT_UNWANTED: '!' }[type] || '•';
|
||||
}
|
||||
|
||||
function preferenceStatusLabel(status) {
|
||||
return {
|
||||
PENDING: 'На рассмотрении',
|
||||
APPROVED: 'Согласовано',
|
||||
REJECTED: 'Отклонено',
|
||||
CANCELLED: 'Отменено'
|
||||
}[status] || status || '—';
|
||||
}
|
||||
|
||||
function changeTypeLabel(type) {
|
||||
return {
|
||||
MOVE: 'Перенос',
|
||||
CHANGE_CLASSROOM: 'Смена аудитории',
|
||||
CANCEL: 'Отмена'
|
||||
}[type] || type || '—';
|
||||
}
|
||||
|
||||
function formatInstant(value) {
|
||||
if (!value) return '—';
|
||||
return new Date(value).toLocaleString('ru-RU', { dateStyle: 'short', timeStyle: 'short' });
|
||||
}
|
||||
|
||||
function dayName(date) {
|
||||
const value = date.toLocaleDateString('ru-RU', { weekday: 'long' });
|
||||
return value.charAt(0).toUpperCase() + value.slice(1);
|
||||
|
||||
Reference in New Issue
Block a user