feat: завершить миграцию фронтенда на React (этапы 1-5)
Админка, настройки, логин, кабинеты преподавателя и студента полностью переведены на 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
This commit is contained in:
73
frontend/react/student/student-service.js
Normal file
73
frontend/react/student/student-service.js
Normal file
@@ -0,0 +1,73 @@
|
||||
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: 'Не удалось загрузить расписание' }
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user