Админка, настройки, логин, кабинеты преподавателя и студента полностью переведены на React 19 (модель strangler, docs/REACT_MIGRATION.md). - 15 вкладок админки: lazy-загрузка, общий error boundary, сервисный слой - Удалены legacy-views и переходный слой LegacyTabView (45 файлов) - Сборка esbuild (frontend/scripts/build-react.mjs) с бюджетами gzip на чанки - CSP-хэши, кэширование: chunk'и immutable, CSS/HTML no-cache (react-cache.conf) - Фиксы: lazy-импорт именованных экспортов (краш после авторизации), растяжение #admin-root/#settings-root на всю ширину, возврат subjects/lessonTypes из справочников (краш просмотра расписаний), отказ от заведомо широкого поиска расписания без выбранной цели, центрирование карточки логина - Тесты: 237/237 (npm run check), регрессионные на lazy-вкладки и ширину - Документация: REACT_MIGRATION.md, REACT_MIGRATION_RESULT.md, FRONTEND.md
125 lines
5.6 KiB
JavaScript
125 lines
5.6 KiB
JavaScript
import { apiClient } from '../shared/api-client.js';
|
||
import { formatLocalDate } from '../../admin/js/utils.js';
|
||
|
||
// Срез «Контур публикации» (этап 5, волна 5): чистая логика и контракты API,
|
||
// перенесённые из legacy admin/js/views/schedule-versions.js без изменения поведения.
|
||
|
||
// --- Ключи localStorage для переходов между вкладками расписания ---
|
||
// (общие с конструктором правил и анализом качества)
|
||
|
||
export const SCHEDULE_YEAR_STORAGE_KEY = 'magistr.schedule.yearId';
|
||
export const SCHEDULE_SEMESTER_STORAGE_KEY = 'magistr.schedule.semesterId';
|
||
export const SCHEDULE_OPEN_VERSION_STORAGE_KEY = 'magistr.schedule.openVersionId';
|
||
export const SCHEDULE_OPEN_RULE_STORAGE_KEY = 'magistr.schedule.openRuleId';
|
||
export const SCHEDULE_VERSION_STORAGE_KEY = 'magistr.schedule.versionId';
|
||
export const QUALITY_YEAR_STORAGE_KEY = 'magistr.quality.yearId';
|
||
export const QUALITY_SEMESTER_STORAGE_KEY = 'magistr.quality.semesterId';
|
||
export const QUALITY_VERSION_STORAGE_KEY = 'magistr.quality.versionId';
|
||
|
||
// --- Подписи ---
|
||
|
||
export function historyActionLabel(action) {
|
||
return {
|
||
CREATED: 'Создан черновик',
|
||
PUBLISHED: 'Опубликовано',
|
||
ARCHIVED: 'Архивировано',
|
||
RESTORED: 'Восстановлено'
|
||
}[action] || action;
|
||
}
|
||
|
||
export function versionSemesterLabel(semester) {
|
||
const type = semester.semesterType === 'autumn' ? 'Осенний' : 'Весенний';
|
||
return `${semester.academicYearTitle} · ${type} семестр`;
|
||
}
|
||
|
||
export function formatVersionDateTime(value) {
|
||
if (!value) return 'дата не указана';
|
||
return new Intl.DateTimeFormat('ru-RU', {
|
||
day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit'
|
||
}).format(new Date(value));
|
||
}
|
||
|
||
export function ruleChangeTypeLabel(type) {
|
||
return { ADDED: 'Добавлено', REMOVED: 'Удалено', CHANGED: 'Изменено' }[type] || type;
|
||
}
|
||
|
||
export function draftBasisText(version) {
|
||
return `Основа: ${version.basedOnVersionId ? `версия #${version.basedOnVersionId}` : 'пустое расписание'}`;
|
||
}
|
||
|
||
// --- Группировка версий ---
|
||
|
||
export function splitScheduleVersions(versions) {
|
||
const list = Array.isArray(versions) ? versions : [];
|
||
return {
|
||
published: list.find(version => version.status === 'PUBLISHED') || null,
|
||
drafts: list.filter(version => version.status === 'DRAFT'),
|
||
archive: list.filter(version => version.status === 'ARCHIVED')
|
||
};
|
||
}
|
||
|
||
// --- Сводка проверки перед публикацией ---
|
||
|
||
export function publishValidationSummary(validation, diff) {
|
||
return validation.valid
|
||
? `Проверка пройдена: ${validation.ruleCount} правил, ${validation.slotCount} слотов. `
|
||
+ `Занятия: +${diff.addedLessonCount}, ~${diff.changedLessonCount}, −${diff.removedLessonCount}.`
|
||
: `Публикация заблокирована: ${(validation.issues || []).join('; ')}`;
|
||
}
|
||
|
||
// --- Контракты API ---
|
||
|
||
export function scheduleVersionsEndpoint(semesterId) {
|
||
return `/api/edu-office/schedule/versions?semesterId=${encodeURIComponent(semesterId)}`;
|
||
}
|
||
|
||
export function scheduleVersionHistoryEndpoint(semesterId) {
|
||
return `/api/edu-office/schedule/versions/history?semesterId=${encodeURIComponent(semesterId)}`;
|
||
}
|
||
|
||
export function scheduleVersionDiffEndpoint(versionId) {
|
||
return `/api/edu-office/schedule/versions/${encodeURIComponent(versionId)}/diff`;
|
||
}
|
||
|
||
export function scheduleVersionValidateEndpoint(versionId) {
|
||
return `/api/edu-office/schedule/versions/${encodeURIComponent(versionId)}/validate`;
|
||
}
|
||
|
||
export function scheduleVersionActionEndpoint(versionId, mode) {
|
||
return `/api/edu-office/schedule/versions/${encodeURIComponent(versionId)}/${mode}`;
|
||
}
|
||
|
||
export async function fetchScheduleSemesters(client = apiClient) {
|
||
return client.get('/api/schedule/semesters', { errorMessage: 'Не удалось загрузить семестры.' });
|
||
}
|
||
|
||
export async function fetchScheduleVersions(semesterId, client = apiClient) {
|
||
return client.get(scheduleVersionsEndpoint(semesterId), { errorMessage: 'Не удалось загрузить версии расписания.' });
|
||
}
|
||
|
||
export async function fetchScheduleVersionHistory(semesterId, client = apiClient) {
|
||
return client.get(scheduleVersionHistoryEndpoint(semesterId), { errorMessage: 'Не удалось загрузить журнал действий.' });
|
||
}
|
||
|
||
export async function fetchScheduleVersionDiff(versionId, client = apiClient) {
|
||
return client.get(scheduleVersionDiffEndpoint(versionId), { errorMessage: 'Не удалось сравнить версии расписания.' });
|
||
}
|
||
|
||
export async function fetchScheduleVersionValidation(versionId, client = apiClient) {
|
||
return client.get(scheduleVersionValidateEndpoint(versionId), { errorMessage: 'Не удалось проверить черновик.' });
|
||
}
|
||
|
||
export async function createScheduleDraftVersion(payload, client = apiClient) {
|
||
return client.post('/api/edu-office/schedule/versions', payload, { errorMessage: 'Не удалось создать черновик.' });
|
||
}
|
||
|
||
export async function runScheduleVersionAction(versionId, mode, reason, client = apiClient) {
|
||
return client.post(
|
||
scheduleVersionActionEndpoint(versionId, mode),
|
||
{ reason },
|
||
{ errorMessage: 'Операцию не удалось выполнить.' }
|
||
);
|
||
}
|
||
|
||
export { formatLocalDate };
|