1 Задача
This commit is contained in:
@@ -16,6 +16,12 @@ import {
|
||||
const statusLine = document.getElementById('status-line');
|
||||
const weekLabel = document.getElementById('week-label');
|
||||
const datePicker = document.getElementById('date-picker');
|
||||
const absenceForm = document.getElementById('teacher-absence-form');
|
||||
const absenceList = document.getElementById('teacher-absence-list');
|
||||
const absenceStatus = document.getElementById('teacher-absence-status');
|
||||
const absenceStart = document.getElementById('teacher-absence-start');
|
||||
const absenceEnd = document.getElementById('teacher-absence-end');
|
||||
const absenceReason = document.getElementById('teacher-absence-reason');
|
||||
let weekStart = startOfWeek(new Date());
|
||||
|
||||
document.getElementById('logout-link').addEventListener('click', async (event) => {
|
||||
@@ -44,7 +50,92 @@ import {
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('absence-form-toggle').addEventListener('click', event => {
|
||||
const expanded = event.currentTarget.getAttribute('aria-expanded') === 'true';
|
||||
event.currentTarget.setAttribute('aria-expanded', String(!expanded));
|
||||
event.currentTarget.textContent = expanded ? 'Новая заявка' : 'Скрыть форму';
|
||||
absenceForm.hidden = expanded;
|
||||
});
|
||||
absenceForm.addEventListener('submit', submitAbsence);
|
||||
absenceList.addEventListener('click', cancelAbsence);
|
||||
|
||||
const todayIso = toIsoDate(new Date());
|
||||
absenceStart.value = todayIso;
|
||||
absenceEnd.value = todayIso;
|
||||
|
||||
loadSchedule();
|
||||
loadAbsences();
|
||||
|
||||
async function loadAbsences() {
|
||||
absenceList.innerHTML = '<div class="teacher-absence-empty">Загрузка заявок...</div>';
|
||||
try {
|
||||
const items = await fetchJson('/api/teacher-absences');
|
||||
absenceList.innerHTML = items.length ? items.map(item => `
|
||||
<article class="teacher-absence-item">
|
||||
<span class="teacher-absence-marker ${absenceStatusClass(item.status)}"></span>
|
||||
<div>
|
||||
<strong>${escapeHtml(formatIsoDate(item.startDate))} — ${escapeHtml(formatIsoDate(item.endDate))}</strong>
|
||||
<p>${escapeHtml(item.reason || 'Причина не указана')}</p>
|
||||
</div>
|
||||
<span class="teacher-absence-badge ${absenceStatusClass(item.status)}">${escapeHtml(absenceStatusLabel(item.status))}</span>
|
||||
${item.status === 'PENDING' ? `<button class="btn btn-sm btn-danger-subtle" type="button" data-cancel-absence="${item.id}">Отменить</button>` : ''}
|
||||
</article>
|
||||
`).join('') : '<div class="teacher-absence-empty">Заявок об отсутствии пока нет</div>';
|
||||
} catch (error) {
|
||||
absenceList.innerHTML = `<div class="teacher-absence-empty error">${escapeHtml(error.message)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function submitAbsence(event) {
|
||||
event.preventDefault();
|
||||
showAbsenceStatus('', false);
|
||||
if (!absenceStart.value || !absenceEnd.value || !absenceReason.value.trim()) {
|
||||
showAbsenceStatus('Заполните период и причину отсутствия', true);
|
||||
return;
|
||||
}
|
||||
if (absenceEnd.value < absenceStart.value) {
|
||||
showAbsenceStatus('Дата окончания не может быть раньше даты начала', true);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await fetchJson('/api/teacher-absences', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
startDate: absenceStart.value,
|
||||
endDate: absenceEnd.value,
|
||||
reason: absenceReason.value.trim()
|
||||
})
|
||||
});
|
||||
absenceReason.value = '';
|
||||
showAbsenceStatus('Заявка отправлена кафедре', false);
|
||||
await loadAbsences();
|
||||
} catch (error) {
|
||||
showAbsenceStatus(error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelAbsence(event) {
|
||||
const button = event.target.closest('[data-cancel-absence]');
|
||||
if (!button) return;
|
||||
button.disabled = true;
|
||||
try {
|
||||
await fetchJson(`/api/teacher-absences/${encodeURIComponent(button.dataset.cancelAbsence)}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
showAbsenceStatus('Заявка отменена', false);
|
||||
await loadAbsences();
|
||||
} catch (error) {
|
||||
showAbsenceStatus(error.message, true);
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function showAbsenceStatus(message, isError) {
|
||||
absenceStatus.textContent = message || '';
|
||||
absenceStatus.classList.toggle('error', Boolean(isError));
|
||||
absenceStatus.classList.toggle('success', Boolean(message) && !isError);
|
||||
}
|
||||
|
||||
async function loadSchedule() {
|
||||
const startDate = toIsoDate(weekStart);
|
||||
@@ -185,8 +276,8 @@ import {
|
||||
return Array.from({ length: 7 }, (_, index) => addDays(weekStart, index));
|
||||
}
|
||||
|
||||
async function fetchJson(url) {
|
||||
const response = await fetchWithAuth(url);
|
||||
async function fetchJson(url, options = {}) {
|
||||
const response = await fetchWithAuth(url, options);
|
||||
const data = await response.json().catch(() => null);
|
||||
if (response.status === 401) {
|
||||
clearAuthState();
|
||||
@@ -225,6 +316,25 @@ import {
|
||||
return date.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
}
|
||||
|
||||
function formatIsoDate(value) {
|
||||
const [year, month, day] = String(value || '').split('-');
|
||||
return year && month && day ? `${day}.${month}.${year}` : '—';
|
||||
}
|
||||
|
||||
function absenceStatusLabel(status) {
|
||||
return {
|
||||
PENDING: 'На подтверждении',
|
||||
APPROVED: 'Согласовано',
|
||||
REJECTED: 'Отклонено',
|
||||
RESOLVED: 'Замены выполнены',
|
||||
CANCELLED: 'Отменено'
|
||||
}[status] || status || '—';
|
||||
}
|
||||
|
||||
function absenceStatusClass(status) {
|
||||
return `is-${String(status || '').toLowerCase()}`;
|
||||
}
|
||||
|
||||
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