Задача Егора 2ч.
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
import { api } from '../api.js';
|
||||
import { escapeHtml, showAlert, hideAlert } from '../utils.js';
|
||||
import { escapeHtml } from '../utils.js';
|
||||
import {
|
||||
CONFLICT_CHECK_STATE,
|
||||
conflictCheckPresentation,
|
||||
currentWeekDateRange,
|
||||
formatLocalDate,
|
||||
loadDepartmentSchedules
|
||||
} from '../dashboard-conflicts.js';
|
||||
|
||||
export async function initDashboard() {
|
||||
// DOM Элементы
|
||||
@@ -20,16 +27,27 @@ export async function initDashboard() {
|
||||
let teachers = [];
|
||||
let todayLessons = [];
|
||||
let timeSlots = [];
|
||||
let dashboardDataReady = false;
|
||||
|
||||
btnRecheck.addEventListener('click', async () => {
|
||||
btnRecheck.disabled = true;
|
||||
btnRecheck.textContent = '...';
|
||||
await checkConflicts();
|
||||
btnRecheck.textContent = 'Перепроверить';
|
||||
btnRecheck.disabled = false;
|
||||
btnRecheck.setAttribute('aria-busy', 'true');
|
||||
btnRecheck.textContent = 'Проверка…';
|
||||
try {
|
||||
if (dashboardDataReady) {
|
||||
await checkConflicts();
|
||||
} else {
|
||||
await loadData();
|
||||
}
|
||||
} finally {
|
||||
btnRecheck.textContent = 'Перепроверить';
|
||||
btnRecheck.disabled = false;
|
||||
btnRecheck.removeAttribute('aria-busy');
|
||||
}
|
||||
});
|
||||
|
||||
async function loadData() {
|
||||
dashboardDataReady = false;
|
||||
try {
|
||||
// Загружаем базовые справочники
|
||||
[departments, classrooms, groups, teachers] = await Promise.all([
|
||||
@@ -45,7 +63,7 @@ export async function initDashboard() {
|
||||
metricStudents.textContent = totalStudents;
|
||||
metricTeachers.textContent = teachers.length;
|
||||
|
||||
const todayStr = new Date().toISOString().split('T')[0];
|
||||
const todayStr = formatLocalDate(new Date());
|
||||
|
||||
// Загружаем временные слоты и сегодняшнее расписание
|
||||
const lessonsPromises = departments.map(d =>
|
||||
@@ -75,10 +93,18 @@ export async function initDashboard() {
|
||||
metricClassrooms.textContent = `${classPercent}% (${occupiedClassroomIds.size}/${activeClassrooms.length})`;
|
||||
|
||||
updateRealtimeMonitoring();
|
||||
dashboardDataReady = true;
|
||||
await checkConflicts();
|
||||
} catch (e) {
|
||||
console.error("Dashboard data load error", e);
|
||||
conflictsContainer.innerHTML = `<div class="form-alert error" style="display:block">Ошибка загрузки панели: ${escapeHtml(e.message)}</div>`;
|
||||
dashboardDataReady = false;
|
||||
console.error('Не удалось загрузить данные панели', {
|
||||
errorType: e?.name || 'Error'
|
||||
});
|
||||
conflictsContainer.innerHTML = renderCheckStateCard({
|
||||
tone: 'error',
|
||||
title: 'Проверка не выполнена',
|
||||
description: 'Не удалось загрузить данные панели. Нажмите «Перепроверить», чтобы повторить попытку.'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,25 +173,25 @@ export async function initDashboard() {
|
||||
|
||||
// --- Анализ конфликтов (Red Zone) ---
|
||||
async function checkConflicts() {
|
||||
conflictsContainer.setAttribute('aria-busy', 'true');
|
||||
conflictsContainer.innerHTML = '<div class="loading-row" style="text-align: center; padding: 2rem; color: var(--text-secondary);">Анализ расписания и выявление конфликтов...</div>';
|
||||
|
||||
// Вычисляем даты начала и конца текущей недели
|
||||
const curr = new Date();
|
||||
const day = curr.getDay();
|
||||
const diff = curr.getDate() - day + (day === 0 ? -6 : 1); // Понедельник
|
||||
const monday = new Date(curr.setDate(diff));
|
||||
const sunday = new Date(curr.setDate(diff + 6));
|
||||
|
||||
const mondayStr = monday.toISOString().split('T')[0];
|
||||
const sundayStr = sunday.toISOString().split('T')[0];
|
||||
|
||||
try {
|
||||
// Загружаем расписание на всю неделю по кафедрам
|
||||
const weekLessonsPromises = departments.map(d =>
|
||||
api.get(`/api/schedule/search?departmentId=${d.id}&startDate=${mondayStr}&endDate=${sundayStr}`).catch(() => [])
|
||||
const { startDate, endDate } = currentWeekDateRange(new Date());
|
||||
const scheduleLoad = await loadDepartmentSchedules(
|
||||
departments,
|
||||
department => api.get(
|
||||
`/api/schedule/search?departmentId=${department.id}`
|
||||
+ `&startDate=${startDate}&endDate=${endDate}`
|
||||
)
|
||||
);
|
||||
const weekLessonsArrays = await Promise.all(weekLessonsPromises);
|
||||
const weekLessons = weekLessonsArrays.flat();
|
||||
|
||||
if (scheduleLoad.state === CONFLICT_CHECK_STATE.NOT_RUN) {
|
||||
renderConflicts([], scheduleLoad, { startDate, endDate });
|
||||
return;
|
||||
}
|
||||
|
||||
const weekLessons = scheduleLoad.lessons;
|
||||
|
||||
const conflicts = [];
|
||||
|
||||
@@ -264,31 +290,40 @@ export async function initDashboard() {
|
||||
}
|
||||
});
|
||||
|
||||
renderConflicts(conflicts);
|
||||
renderConflicts(conflicts, scheduleLoad, { startDate, endDate });
|
||||
} catch (e) {
|
||||
console.error("Conflicts check error", e);
|
||||
conflictsContainer.innerHTML = `<div class="form-alert error" style="display:block">Ошибка при анализе конфликтов: ${escapeHtml(e.message)}</div>`;
|
||||
console.error('Не удалось выполнить проверку конфликтов', {
|
||||
errorType: e?.name || 'Error'
|
||||
});
|
||||
conflictsContainer.innerHTML = renderCheckStateCard({
|
||||
tone: 'error',
|
||||
title: 'Проверка не выполнена',
|
||||
description: 'Во время анализа произошла ошибка. Повторите проверку позже.'
|
||||
});
|
||||
} finally {
|
||||
conflictsContainer.removeAttribute('aria-busy');
|
||||
}
|
||||
}
|
||||
|
||||
function renderConflicts(conflicts) {
|
||||
if (!conflicts.length) {
|
||||
conflictsContainer.innerHTML = `
|
||||
<div style="background: rgba(16, 185, 129, 0.08); border: 1px solid rgba(16, 185, 129, 0.2); border-radius: 8px; padding: 1.25rem; display: flex; align-items: center; gap: 1rem; color: #10b981;">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
|
||||
<polyline points="22 4 12 14.01 9 11.01" />
|
||||
</svg>
|
||||
<div>
|
||||
<strong style="display: block; font-size: 0.95rem; margin-bottom: 0.15rem;">Конфликты расписания не обнаружены!</strong>
|
||||
<span style="font-size: 0.85rem; opacity: 0.9;">Все правила расписания на текущую неделю согласованы, накладки преподавателей/аудиторий и переполнения отсутствуют.</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
function renderConflicts(conflicts, scheduleLoad, range) {
|
||||
const stateCards = [];
|
||||
const presentation = conflictCheckPresentation(scheduleLoad, conflicts.length, range);
|
||||
|
||||
if (scheduleLoad.state === CONFLICT_CHECK_STATE.NOT_RUN) {
|
||||
conflictsContainer.innerHTML = renderCheckStateCard(presentation);
|
||||
return;
|
||||
}
|
||||
|
||||
conflictsContainer.innerHTML = conflicts.map(c => {
|
||||
if (presentation) {
|
||||
stateCards.push(renderCheckStateCard(presentation));
|
||||
}
|
||||
|
||||
if (!conflicts.length) {
|
||||
conflictsContainer.innerHTML = stateCards.join('');
|
||||
return;
|
||||
}
|
||||
|
||||
const conflictCards = conflicts.map(c => {
|
||||
const isError = c.severity === 'error';
|
||||
const borderCol = isError ? 'var(--error, #ef4444)' : '#f59e0b';
|
||||
const bgCol = isError ? 'rgba(239, 68, 68, 0.05)' : 'rgba(245, 158, 11, 0.05)';
|
||||
@@ -306,6 +341,27 @@ export async function initDashboard() {
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
conflictsContainer.innerHTML = stateCards.join('') + conflictCards;
|
||||
}
|
||||
|
||||
function renderCheckStateCard({ tone, title, description, meta = '' }) {
|
||||
const icon = tone === 'success'
|
||||
? '<path d="M20 6 9 17l-5-5" />'
|
||||
: tone === 'warning'
|
||||
? '<path d="M12 9v4m0 4h.01" /><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z" />'
|
||||
: '<circle cx="12" cy="12" r="9" /><path d="M12 8v5m0 3h.01" />';
|
||||
return `
|
||||
<section class="dashboard-check-state dashboard-check-state--${tone}">
|
||||
<span class="dashboard-check-state__icon" aria-hidden="true">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round">${icon}</svg>
|
||||
</span>
|
||||
<span class="dashboard-check-state__body">
|
||||
<strong>${escapeHtml(title)}</strong>
|
||||
<span>${escapeHtml(description)}</span>
|
||||
${meta ? `<small>${escapeHtml(meta)}</small>` : ''}
|
||||
</span>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
// Загружаем данные при старте
|
||||
|
||||
Reference in New Issue
Block a user