1 Задача

This commit is contained in:
dipatrik10
2026-08-06 01:10:33 +03:00
parent 28268a38c0
commit 5276729a69
33 changed files with 3569 additions and 19 deletions

View File

@@ -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);