правка визуальных багов

This commit is contained in:
2026-06-01 00:22:07 +03:00
parent f3a9905423
commit 41fde2566d
14 changed files with 484 additions and 88 deletions

View File

@@ -217,12 +217,14 @@ export async function initAcademicCalendar() {
}
academicYearsTbody.innerHTML = academicYears.map(year => `
<tr>
<td>${escapeHtml(year.title)}</td>
<td>${escapeHtml(year.startDate)} - ${escapeHtml(year.endDate)}</td>
<td>${renderSemesterList(year.semesters || [])}</td>
<td>
<button class="btn-edit-classroom btn-edit-year" data-id="${year.id}">Изменить</button>
<button class="btn-delete btn-delete-year" data-id="${year.id}">Удалить</button>
<td class="academic-year-center-cell academic-year-title-cell">${escapeHtml(year.title)}</td>
<td class="academic-year-center-cell academic-year-period-cell">${escapeHtml(formatDisplayDateRange(year.startDate, year.endDate))}</td>
<td class="academic-year-semesters-cell">${renderSemesterList(year.semesters || [])}</td>
<td class="academic-year-actions-cell">
<div class="academic-year-actions">
<button class="btn-edit-classroom btn-edit-year" data-id="${year.id}">Изменить</button>
<button class="btn-delete btn-delete-year" data-id="${year.id}">Удалить</button>
</div>
</td>
</tr>
`).join('');
@@ -230,12 +232,19 @@ export async function initAcademicCalendar() {
function renderSemesterList(items) {
if (!items.length) return '<span class="muted">Нет семестров</span>';
return items.map(semester => `
<div class="schedule-semester-line">
${escapeHtml(semesterLabel(semester))}
<button class="btn-edit-classroom btn-edit-semester" data-id="${semester.id}">Изменить</button>
</div>
`).join('');
return `<div class="academic-year-semesters">${items.map(semester => {
const title = semesterTypeTitle(semester);
const period = formatDisplayDateRange(semester.startDate, semester.endDate);
return `
<div class="academic-year-semester-item">
<div class="academic-year-semester-meta">
<span class="academic-year-semester-type">${escapeHtml(title)}</span>
<span class="academic-year-semester-period">${escapeHtml(period)}</span>
</div>
<button class="btn-edit-classroom btn-edit-semester" data-id="${semester.id}" aria-label="Изменить ${escapeHtml(title)} семестр ${escapeHtml(period)}">Изменить</button>
</div>
`;
}).join('')}</div>`;
}
async function saveAcademicYear(event) {
@@ -282,7 +291,7 @@ export async function initAcademicCalendar() {
if (editSemesterButton) {
const semester = semesters.find(item => item.id == editSemesterButton.dataset.id);
if (!semester) return;
semesterFormTitle.textContent = 'Редактирование семестра';
semesterFormTitle.textContent = `Редактирование семестра: ${semesterTypeTitle(semester)}`;
semesterIdInput.value = semester.id;
semesterYearSelect.value = semester.academicYearId || '';
semesterTypeSelect.value = normalizeSemesterType(semester.semesterType);
@@ -1164,7 +1173,13 @@ export async function initAcademicCalendar() {
function semesterLabel(semester) {
const type = normalizeSemesterType(semester.semesterType);
return `${semester.academicYearTitle || '-'}, ${SEMESTER_LABELS[type] || type} (${semester.startDate} - ${semester.endDate})`;
return `${semester.academicYearTitle || '-'}, ${SEMESTER_LABELS[type] || type} (${formatDisplayDateRange(semester.startDate, semester.endDate)})`;
}
function semesterTypeTitle(semester) {
const type = normalizeSemesterType(semester.semesterType);
const label = SEMESTER_LABELS[type] || type || 'семестр';
return label.charAt(0).toUpperCase() + label.slice(1);
}
function normalizeSemesterType(type) {
@@ -1195,6 +1210,16 @@ export async function initAcademicCalendar() {
return `${day}.${month}.${year}`;
}
function formatDisplayDate(value) {
const [year, month, day] = String(value || '').split('-');
if (!year || !month || !day) return String(value || '');
return `${day}-${month}-${year}`;
}
function formatDisplayDateRange(startDate, endDate) {
return `${formatDisplayDate(startDate)}${formatDisplayDate(endDate)}`;
}
function dateRangeCaption(rows) {
if (!rows.length) return '';
const dates = rows.map(row => row.date).sort();

View File

@@ -13,6 +13,7 @@ async function fetchEquipments() {
function renderEquipmentCheckboxes(equipments, containerId, selectTextId, selectedIds = []) {
const container = document.getElementById(containerId);
if (!container) return;
const selected = new Set(selectedIds.map(id => String(id)));
if (!equipments || !equipments.length) {
container.innerHTML = '<p class="text-muted"><small>Оборудование отсутствует</small></p>';
@@ -21,9 +22,10 @@ function renderEquipmentCheckboxes(equipments, containerId, selectTextId, select
}
container.innerHTML = equipments.map(eq => `
<label class="checkbox-container">
<input type="checkbox" value="${eq.id}" ${selectedIds.includes(eq.id) ? 'checked' : ''}>
<span>${escapeHtml(eq.name)}</span>
<label class="checkbox-item">
<input type="checkbox" value="${escapeHtml(eq.id)}" ${selected.has(String(eq.id)) ? 'checked' : ''}>
<span class="checkmark"></span>
<span class="checkbox-label">${escapeHtml(eq.name)}</span>
</label>
`).join('');

View File

@@ -112,11 +112,11 @@ export async function initDepartmentWorkspace() {
if (!importRowsContainer) return;
importRowsContainer.innerHTML = (rows || [{ code: '', name: '' }]).map((row, index) => `
<div class="subject-import-row" data-index="${index}">
<div class="form-group">
<div class="form-group subject-import-code-field">
<label>Код</label>
<input type="text" class="subject-import-code" value="${escapeHtml(row.code || '')}" placeholder="DB201">
</div>
<div class="form-group">
<div class="form-group subject-import-name-field">
<label>Название</label>
<input type="text" class="subject-import-name" value="${escapeHtml(row.name || '')}" placeholder="Базы данных">
</div>

View File

@@ -171,13 +171,15 @@ export async function initGroups() {
<td>${escapeHtml(specialityLabel(group.specialtyId || group.specialityCode))}</td>
<td>${escapeHtml(group.specialtyProfileName || '-')}</td>
<td><span class="badge ${statusBadgeClass(group)}">${escapeHtml(statusLabel(group))}</span></td>
<td style="text-align: right; white-space: nowrap;">
<button class="btn-edit-classroom btn-edit-group" data-id="${group.id}">Изменить</button>
<button class="btn-primary btn-manage-subgroups" data-id="${group.id}" style="padding: 0.25rem 0.5rem; font-size: 0.85rem;">Подгруппы</button>
<button class="btn-primary btn-manage-calendar" data-id="${group.id}" style="padding: 0.25rem 0.5rem; font-size: 0.85rem;">Календарь</button>
${group.status === 'ARCHIVED'
? `<button class="btn-restore-group" data-id="${group.id}" style="margin-left: 0.5rem;">Восстановить</button>`
: `<button class="btn-delete" data-id="${group.id}" style="margin-left: 0.5rem;">Архив</button>`}
<td class="group-actions-cell">
<div class="group-actions">
<button class="btn-edit-classroom btn-edit-group group-action-btn" data-id="${group.id}">Изменить</button>
<button class="btn-primary btn-manage-subgroups group-action-btn" data-id="${group.id}">Подгруппы</button>
<button class="btn-primary btn-manage-calendar group-action-btn" data-id="${group.id}">Календарь</button>
${group.status === 'ARCHIVED'
? `<button class="btn-restore-group group-action-btn" data-id="${group.id}">Восстановить</button>`
: `<button class="btn-delete group-action-btn" data-id="${group.id}">Архив</button>`}
</div>
</td>
</tr>
`).join('');

View File

@@ -56,6 +56,7 @@ const LESSON_TYPE_LIMITS = [
startWeekInputId: 'schedule-rule-practice-start-week'
}
];
const LESSON_TYPE_ORDER = new Map(LESSON_TYPE_LIMITS.map((config, index) => [config.key, index]));
export async function initSchedule() {
const ruleForm = document.getElementById('schedule-rule-form');
@@ -186,6 +187,7 @@ export async function initSchedule() {
api.get('/api/lesson-types'),
api.get('/api/subgroups')
]);
lessonTypes = sortLessonTypes(lessonTypes);
populateSubjectSelects();
}
@@ -1022,6 +1024,15 @@ export async function initSchedule() {
return { value: lessonType.id, label: lessonType.name || lessonType.lessonType || `ID ${lessonType.id}` };
}
function sortLessonTypes(items) {
return [...items].sort((first, second) => {
const firstOrder = LESSON_TYPE_ORDER.get(lessonTypeKey(first)) ?? LESSON_TYPE_ORDER.size;
const secondOrder = LESSON_TYPE_ORDER.get(lessonTypeKey(second)) ?? LESSON_TYPE_ORDER.size;
return firstOrder - secondOrder
|| naturalCompare(first.name || first.lessonType, second.name || second.lessonType);
});
}
function lessonTypeKeyById(lessonTypeId) {
const lessonType = lessonTypes.find(item => String(item.id) === String(lessonTypeId));
return lessonType ? lessonTypeKey(lessonType) : null;