Админка, настройки, логин, кабинеты преподавателя и студента полностью переведены на 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
74 lines
2.4 KiB
JavaScript
74 lines
2.4 KiB
JavaScript
import { apiClient } from '../shared/api-client.js';
|
||
|
||
export {
|
||
addDays,
|
||
dayName,
|
||
formatDate,
|
||
formatWeekLabel,
|
||
parseIsoDate,
|
||
startOfWeek,
|
||
toIsoDate,
|
||
trimTime,
|
||
weekDates,
|
||
weekEndIso,
|
||
WEEK_LENGTH_DAYS
|
||
} from '../shared/dates.js';
|
||
|
||
export const OVERVIEW_DAYS = 14;
|
||
export const GROUP_STORAGE_KEY = 'studentGroupId';
|
||
|
||
export function groupLessonsByDate(lessons) {
|
||
const byDate = new Map();
|
||
(lessons || []).forEach(lesson => {
|
||
if (!byDate.has(lesson.date)) byDate.set(lesson.date, []);
|
||
byDate.get(lesson.date).push(lesson);
|
||
});
|
||
return byDate;
|
||
}
|
||
|
||
export function buildLessonChips(lesson) {
|
||
const groups = Array.isArray(lesson.groupNames) ? lesson.groupNames.join(', ') : '';
|
||
const subgroups = Array.isArray(lesson.subgroupNames) && lesson.subgroupNames.length
|
||
? lesson.subgroupNames.join(', ')
|
||
: lesson.subgroupName;
|
||
return [
|
||
lesson.lessonTypeName,
|
||
subgroups,
|
||
lesson.lessonFormat,
|
||
lesson.teacherName,
|
||
lesson.classroomName,
|
||
groups
|
||
].filter(Boolean);
|
||
}
|
||
|
||
export function buildWeekStatusMessage(lessons) {
|
||
const count = Array.isArray(lessons) ? lessons.length : 0;
|
||
return count > 0 ? `Найдено занятий: ${count}` : 'На этой неделе занятий нет';
|
||
}
|
||
|
||
export function resolveSelectedGroup(groups, savedGroupId = '') {
|
||
if (!Array.isArray(groups) || groups.length === 0) return null;
|
||
return groups.find(group => String(group.id) === String(savedGroupId)) || groups[0];
|
||
}
|
||
|
||
export function groupOptionDescription(group) {
|
||
return [
|
||
group.specialtyCode,
|
||
group.specialtyProfileName,
|
||
group.course ? `${group.course} курс` : ''
|
||
].filter(Boolean).join(' · ');
|
||
}
|
||
|
||
export async function fetchGroupOptions({ query = '', selectedId = '' } = {}, client = apiClient) {
|
||
const params = new URLSearchParams({ query, limit: '20' });
|
||
if (selectedId) params.set('selectedId', selectedId);
|
||
return client.get(`/api/groups/options?${params}`, { errorMessage: 'Не удалось загрузить группы' });
|
||
}
|
||
|
||
export async function fetchSchedule({ groupId, startDate, endDate }, client = apiClient) {
|
||
return client.get(
|
||
`/api/schedule?groupId=${encodeURIComponent(groupId)}&startDate=${startDate}&endDate=${endDate}`,
|
||
{ errorMessage: 'Не удалось загрузить расписание' }
|
||
);
|
||
}
|