баг-фикс 30/34
This commit is contained in:
@@ -801,7 +801,7 @@ export async function initAcademicCalendar() {
|
||||
const cellByWeekAndDay = new Map(rows.map(row => [`${row.weekNumber}:${row.dayOfWeek}`, row]));
|
||||
return `
|
||||
<div class="calendar-semester-table-wrap">
|
||||
<table class="calendar-semester-table" style="--calendar-week-columns:${weekSlots.length}; --calendar-min-table-width:${32 + weekSlots.length * 22}px">
|
||||
<table class="calendar-semester-table">
|
||||
<colgroup>
|
||||
<col class="calendar-day-head-col">
|
||||
${weekSlots.map(() => '<col class="calendar-week-col">').join('')}
|
||||
@@ -842,7 +842,6 @@ export async function initAcademicCalendar() {
|
||||
const dayName = shortDayLabel(row.dayOfWeek);
|
||||
const activity = activityById(selected) || activityTypes[0] || {};
|
||||
const code = activity.code || row.activityCode || 'Т';
|
||||
const color = activity.colorCode || '#64748b';
|
||||
return `
|
||||
<td class="calendar-day"
|
||||
data-course="${row.courseNumber}"
|
||||
@@ -853,8 +852,7 @@ export async function initAcademicCalendar() {
|
||||
data-time-scope-id="${timeAssignmentForDate(row.date)?.scopeId || ''}"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="${escapeHtml(`${formatShortDate(row.date)} ${dayName}, ${row.weekNumber} неделя, код ${code}`)}"
|
||||
style="--activity-color:${escapeHtml(color)}">
|
||||
aria-label="${escapeHtml(`${formatShortDate(row.date)} ${dayName}, ${row.weekNumber} неделя, код ${code}`)}">
|
||||
<strong class="calendar-day-code">${escapeHtml(code)}</strong>
|
||||
${timeAssignmentForDate(row.date) ? '<span class="calendar-day-time-marker">вр.</span>' : ''}
|
||||
<select class="calendar-day-activity custom-select-initialized" aria-hidden="true" tabindex="-1">${options(activityTypes.map(toActivityOption), selected)}</select>
|
||||
@@ -921,7 +919,7 @@ export async function initAcademicCalendar() {
|
||||
return;
|
||||
}
|
||||
calendarDayDialogActivitySummary.innerHTML = `
|
||||
<span class="calendar-day-dialog-code" style="--activity-color:${escapeHtml(activity.colorCode || '#64748b')}">${escapeHtml(activity.code)}</span>
|
||||
<span class="calendar-day-dialog-code">${escapeHtml(activity.code)}</span>
|
||||
<div>
|
||||
<strong>${escapeHtml(activity.name)}</strong>
|
||||
<p>${escapeHtml(activity.description || (activity.allowSchedule ? 'Обычные пары разрешены' : 'Обычные пары не генерируются'))}</p>
|
||||
@@ -938,7 +936,7 @@ export async function initAcademicCalendar() {
|
||||
const activity = activityById(commonCalendarDayActivityValue(days));
|
||||
if (!activity) {
|
||||
calendarDayDialogActivitySummary.innerHTML = `
|
||||
<span class="calendar-day-dialog-code" style="--activity-color:#64748b">...</span>
|
||||
<span class="calendar-day-dialog-code">...</span>
|
||||
<div>
|
||||
<strong>Выбрано ячеек: ${days.length}</strong>
|
||||
<p>Выберите код активности, чтобы применить его ко всему выделению</p>
|
||||
@@ -948,7 +946,7 @@ export async function initAcademicCalendar() {
|
||||
}
|
||||
|
||||
calendarDayDialogActivitySummary.innerHTML = `
|
||||
<span class="calendar-day-dialog-code" style="--activity-color:${escapeHtml(activity.colorCode || '#64748b')}">${escapeHtml(activity.code)}</span>
|
||||
<span class="calendar-day-dialog-code">${escapeHtml(activity.code)}</span>
|
||||
<div>
|
||||
<strong>${escapeHtml(activity.name)} · ${days.length} ячеек</strong>
|
||||
<p>${escapeHtml(activity.description || (activity.allowSchedule ? 'Обычные пары разрешены' : 'Обычные пары не генерируются'))}</p>
|
||||
@@ -1041,8 +1039,6 @@ export async function initAcademicCalendar() {
|
||||
const select = day.querySelector('.calendar-day-activity');
|
||||
const activity = activityById(select.value);
|
||||
const code = activity?.code || '-';
|
||||
const color = activity?.colorCode || '#64748b';
|
||||
day.style.setProperty('--activity-color', color);
|
||||
day.querySelector('.calendar-day-code').textContent = code;
|
||||
day.setAttribute('aria-label', `${formatShortDate(day.dataset.date)} ${shortDayLabel(day.dataset.day)}, ${day.dataset.week} неделя, код ${code}`);
|
||||
}
|
||||
@@ -1053,11 +1049,8 @@ export async function initAcademicCalendar() {
|
||||
return;
|
||||
}
|
||||
const tooltip = ensureCalendarDayTooltip();
|
||||
const activity = activityById(day.querySelector('.calendar-day-activity')?.value);
|
||||
tooltip.style.setProperty('--activity-color', activity?.colorCode || '#64748b');
|
||||
tooltip.innerHTML = renderCalendarDayTooltip(day);
|
||||
tooltip.classList.add('visible');
|
||||
positionCalendarDayTooltip(tooltip, day, event);
|
||||
}
|
||||
|
||||
function hideCalendarDayTooltip() {
|
||||
@@ -1076,14 +1069,13 @@ export async function initAcademicCalendar() {
|
||||
function renderCalendarDayTooltip(day) {
|
||||
const activity = activityById(day.querySelector('.calendar-day-activity')?.value);
|
||||
const code = activity?.code || day.querySelector('.calendar-day-code')?.textContent || '-';
|
||||
const color = activity?.colorCode || '#64748b';
|
||||
const timeAssignment = timeAssignmentForDate(day.dataset.date);
|
||||
const timeLabel = timeAssignment
|
||||
? (timeSlotScopes.find(scope => String(scope.id) === String(timeAssignment.scopeId))?.name || 'ручная сетка')
|
||||
: automaticTimeScopeLabel(day.dataset.date).replace('По правилу даты ', '');
|
||||
return `
|
||||
<div class="calendar-day-tooltip-head">
|
||||
<span class="calendar-day-tooltip-code" style="--activity-color:${escapeHtml(color)}"><span>${escapeHtml(code)}</span></span>
|
||||
<span class="calendar-day-tooltip-code"><span>${escapeHtml(code)}</span></span>
|
||||
<div>
|
||||
<strong>${escapeHtml(formatLongDate(day.dataset.date))}</strong>
|
||||
<span>${escapeHtml(shortDayLabel(day.dataset.day))}, ${escapeHtml(day.dataset.week)} неделя</span>
|
||||
@@ -1106,24 +1098,6 @@ export async function initAcademicCalendar() {
|
||||
`;
|
||||
}
|
||||
|
||||
function positionCalendarDayTooltip(tooltip, day, event) {
|
||||
const offset = 14;
|
||||
const rect = day.getBoundingClientRect();
|
||||
const x = event?.clientX ?? rect.left + rect.width / 2;
|
||||
const y = event?.clientY ?? rect.top;
|
||||
const tooltipRect = tooltip.getBoundingClientRect();
|
||||
let left = x + offset;
|
||||
let top = y + offset;
|
||||
if (left + tooltipRect.width > window.innerWidth - 12) {
|
||||
left = x - tooltipRect.width - offset;
|
||||
}
|
||||
if (top + tooltipRect.height > window.innerHeight - 12) {
|
||||
top = y - tooltipRect.height - offset;
|
||||
}
|
||||
tooltip.style.left = `${Math.max(12, left)}px`;
|
||||
tooltip.style.top = `${Math.max(12, top)}px`;
|
||||
}
|
||||
|
||||
function startCalendarDayDragSelection(event, day) {
|
||||
if (event.button !== 0) return;
|
||||
hideCalendarDayTooltip();
|
||||
@@ -1246,10 +1220,9 @@ export async function initAcademicCalendar() {
|
||||
calendarTotals.innerHTML = Array.from(counts.entries())
|
||||
.sort(([a], [b]) => a.localeCompare(b, 'ru'))
|
||||
.map(([code, count]) => {
|
||||
const activity = activityTypes.find(item => item.code === code);
|
||||
const selectedClass = selectedCode === code ? ' is-selected' : '';
|
||||
const suffix = selectedCode === code ? '<span class="calendar-total-selected">выбрано</span>' : '';
|
||||
return `<span class="calendar-total-chip${selectedClass}" style="--chip-color:${escapeHtml(activity?.colorCode || '#64748b')}">${escapeHtml(code)}: ${count}${suffix}</span>`;
|
||||
return `<span class="calendar-total-chip${selectedClass}">${escapeHtml(code)}: ${count}${suffix}</span>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ async function fetchEquipments() {
|
||||
try {
|
||||
return await api.get('/api/equipments');
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch equipments", e);
|
||||
console.error('Не удалось загрузить список оборудования');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,18 +325,16 @@ export async function initDashboard() {
|
||||
|
||||
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)';
|
||||
const badgeIcon = isError
|
||||
? `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>`
|
||||
: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`;
|
||||
|
||||
return `
|
||||
<div style="background: ${bgCol}; border: 1px solid rgba(229, 231, 235, 0.1); border-left: 4px solid ${borderCol}; border-radius: 8px; padding: 1rem; display: flex; gap: 0.75rem; align-items: start;">
|
||||
<div style="margin-top: 0.1rem;">${badgeIcon}</div>
|
||||
<div style="flex: 1;">
|
||||
<h4 style="margin: 0 0 0.25rem 0; font-size: 0.95rem; font-weight: 600; color: ${isError ? 'var(--error, #ef4444)' : '#d97706'};">${escapeHtml(c.title)}</h4>
|
||||
<p style="margin: 0; font-size: 0.85rem; line-height: 1.4; color: var(--text-main);">${c.description}</p>
|
||||
<div class="dashboard-conflict-card dashboard-conflict-card--${isError ? 'error' : 'warning'}">
|
||||
<div class="dashboard-conflict-card__icon">${badgeIcon}</div>
|
||||
<div class="dashboard-conflict-card__body">
|
||||
<h4>${escapeHtml(c.title)}</h4>
|
||||
<p>${escapeHtml(c.description)}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { api } from '../api.js';
|
||||
import { escapeHtml, showAlert, hideAlert } from '../utils.js';
|
||||
import { api, getSession } from '../api.js';
|
||||
import { escapeHtml, formatLocalDate, showAlert, hideAlert } from '../utils.js';
|
||||
|
||||
const ROLE_DEPARTMENT = 'DEPARTMENT';
|
||||
|
||||
export async function initDepartmentWorkspace() {
|
||||
const role = localStorage.getItem('role');
|
||||
const userDepartmentId = localStorage.getItem('departmentId');
|
||||
const session = getSession();
|
||||
const role = session?.role;
|
||||
const userDepartmentId = session?.departmentId;
|
||||
const departmentField = document.getElementById('department-workspace-department-field');
|
||||
const departmentSelect = document.getElementById('department-workspace-department');
|
||||
const startInput = document.getElementById('department-workspace-start');
|
||||
@@ -493,14 +494,10 @@ function fillSelect(id, items, valueFn, labelFn, emptyLabel) {
|
||||
|
||||
function setDefaultDates(startInput, endInput) {
|
||||
const today = new Date();
|
||||
const nextWeek = new Date();
|
||||
const nextWeek = new Date(today);
|
||||
nextWeek.setDate(today.getDate() + 7);
|
||||
startInput.value = toIsoDate(today);
|
||||
endInput.value = toIsoDate(nextWeek);
|
||||
}
|
||||
|
||||
function toIsoDate(date) {
|
||||
return date.toISOString().slice(0, 10);
|
||||
startInput.value = formatLocalDate(today);
|
||||
endInput.value = formatLocalDate(nextWeek);
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
|
||||
@@ -5,7 +5,7 @@ async function fetchEducationForms() {
|
||||
try {
|
||||
return await api.get('/api/education-forms');
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch education forms", e);
|
||||
console.error('Не удалось загрузить формы обучения');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -182,7 +182,7 @@ export async function initGroups() {
|
||||
try {
|
||||
subgroups = await api.get('/api/subgroups');
|
||||
} catch (error) {
|
||||
console.error("Failed to load subgroups", error);
|
||||
console.error('Не удалось загрузить подгруппы');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,7 +560,7 @@ export async function initGroups() {
|
||||
renderSubgroupCapacityFields(count, current.map(item => item.studentCapacity));
|
||||
subgroupFormTitle.textContent = 'Настройка подгрупп';
|
||||
btnSaveSubgroup.textContent = 'Применить';
|
||||
btnCancelSubgroupEdit.style.display = 'none';
|
||||
btnCancelSubgroupEdit.hidden = true;
|
||||
hideAlert('manage-subgroup-alert');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { api } from '../api.js';
|
||||
import { api, getSession } from '../api.js';
|
||||
import { escapeHtml, showAlert, hideAlert } from '../utils.js';
|
||||
|
||||
const ROLE_DEPARTMENT = 'DEPARTMENT';
|
||||
@@ -38,8 +38,9 @@ const MOBILE_QUERY = '(max-width: 760px)';
|
||||
const DEFAULT_SEMESTER_WEEKS = 22;
|
||||
|
||||
export async function initScheduleView() {
|
||||
const role = localStorage.getItem('role');
|
||||
const userDepartmentId = normalizeId(localStorage.getItem('departmentId'));
|
||||
const session = getSession();
|
||||
const role = session?.role;
|
||||
const userDepartmentId = normalizeId(session?.departmentId);
|
||||
const dateInput = document.getElementById('schedule-view-date');
|
||||
const targetSelect = document.getElementById('schedule-view-target');
|
||||
const loadButton = document.getElementById('schedule-view-load');
|
||||
|
||||
@@ -658,13 +658,7 @@ export async function initSchedule() {
|
||||
slotIndex: button.dataset.slotIndex || ''
|
||||
};
|
||||
if (!visualContextMenu) return;
|
||||
const rect = button.getBoundingClientRect();
|
||||
visualContextMenu.hidden = false;
|
||||
const menuRect = visualContextMenu.getBoundingClientRect();
|
||||
const top = Math.min(rect.bottom + 6, window.innerHeight - menuRect.height - 12);
|
||||
const left = Math.min(rect.left, window.innerWidth - menuRect.width - 12);
|
||||
visualContextMenu.style.top = `${Math.max(12, top)}px`;
|
||||
visualContextMenu.style.left = `${Math.max(12, left)}px`;
|
||||
visualContextMenu.querySelector('button')?.focus();
|
||||
}
|
||||
|
||||
@@ -672,8 +666,6 @@ export async function initSchedule() {
|
||||
visualContextTarget = null;
|
||||
if (!visualContextMenu) return;
|
||||
visualContextMenu.hidden = true;
|
||||
visualContextMenu.style.top = '';
|
||||
visualContextMenu.style.left = '';
|
||||
}
|
||||
|
||||
async function handleVisualContextMenuClick(event) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { api } from '../api.js';
|
||||
import { escapeHtml, hideAlert, showAlert } from '../utils.js';
|
||||
import { escapeHtml, hideAlert, renderTableMessage, showAlert } from '../utils.js';
|
||||
|
||||
export async function initTeacherRequests() {
|
||||
const requestsTbody = document.getElementById('teacher-requests-tbody');
|
||||
@@ -23,7 +23,7 @@ export async function initTeacherRequests() {
|
||||
detail: { count: normalizedRequests.length }
|
||||
}));
|
||||
} catch (error) {
|
||||
requestsTbody.innerHTML = `<tr><td colspan="7" class="loading-row">${escapeHtml(error.message || 'Ошибка загрузки заявок')}</td></tr>`;
|
||||
renderTableMessage(requestsTbody, 7, 'Не удалось загрузить заявки преподавателей');
|
||||
updateCount(0);
|
||||
}
|
||||
}
|
||||
@@ -44,10 +44,10 @@ export async function initTeacherRequests() {
|
||||
`).join('')}
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" class="teacher-request-username" value="${escapeHtml(request.username || '')}" maxlength="50"></td>
|
||||
<td><input type="text" class="teacher-request-username" value="${escapeHtml(request.username || '')}" maxlength="50" autocomplete="username"></td>
|
||||
<td><input type="text" class="teacher-request-fullname" value="${escapeHtml(request.fullName || '')}" maxlength="255"></td>
|
||||
<td><input type="text" class="teacher-request-jobtitle" value="${escapeHtml(request.jobTitle || '')}" maxlength="255"></td>
|
||||
<td><input type="text" class="teacher-request-password" placeholder="Минимум 8 символов"></td>
|
||||
<td><input type="password" class="teacher-request-password" placeholder="Минимум 8 символов" autocomplete="new-password"></td>
|
||||
<td>${escapeHtml(request.comment || '-')}</td>
|
||||
<td>
|
||||
<div class="teacher-request-actions">
|
||||
@@ -131,6 +131,6 @@ export async function initTeacherRequests() {
|
||||
await loadDepartments();
|
||||
await loadRequests();
|
||||
} catch (error) {
|
||||
requestsTbody.innerHTML = `<tr><td colspan="7" class="loading-row">${escapeHtml(error.message || 'Ошибка загрузки')}</td></tr>`;
|
||||
renderTableMessage(requestsTbody, 7, 'Не удалось загрузить заявки преподавателей');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ export async function initUniversityStructure() {
|
||||
profileIdInput.value = '';
|
||||
profileFormTitle.textContent = 'Создание профиля';
|
||||
btnSaveProfile.textContent = 'Создать';
|
||||
btnCancelProfileEdit.style.display = 'none';
|
||||
btnCancelProfileEdit.hidden = true;
|
||||
hideAlert('manage-profile-alert');
|
||||
}
|
||||
|
||||
@@ -384,7 +384,7 @@ export async function initUniversityStructure() {
|
||||
profileDescInput.value = profile.description || '';
|
||||
profileFormTitle.textContent = 'Редактирование профиля';
|
||||
btnSaveProfile.textContent = 'Сохранить';
|
||||
btnCancelProfileEdit.style.display = 'inline-block';
|
||||
btnCancelProfileEdit.hidden = false;
|
||||
hideAlert('manage-profile-alert');
|
||||
return;
|
||||
}
|
||||
@@ -451,7 +451,7 @@ export async function initUniversityStructure() {
|
||||
tabProfileSpecSelect.disabled = false;
|
||||
mainProfileFormTitle.textContent = 'Создание профиля';
|
||||
btnTabSaveProfile.textContent = 'Создать';
|
||||
btnTabCancelProfileEdit.style.display = 'none';
|
||||
btnTabCancelProfileEdit.hidden = true;
|
||||
hideAlert('tab-profile-alert');
|
||||
tabProfileSpecSelect.dispatchEvent(new Event('change'));
|
||||
}
|
||||
@@ -507,7 +507,7 @@ export async function initUniversityStructure() {
|
||||
|
||||
mainProfileFormTitle.textContent = 'Редактирование профиля';
|
||||
btnTabSaveProfile.textContent = 'Сохранить';
|
||||
btnTabCancelProfileEdit.style.display = 'inline-block';
|
||||
btnTabCancelProfileEdit.hidden = false;
|
||||
hideAlert('tab-profile-alert');
|
||||
|
||||
createProfileTabForm.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
Reference in New Issue
Block a user