Админка, настройки, логин, кабинеты преподавателя и студента полностью переведены на 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
113 lines
3.9 KiB
JavaScript
113 lines
3.9 KiB
JavaScript
import { apiClient } from '../shared/api-client.js';
|
||
|
||
export const USER_TABLE_COLUMNS = 7;
|
||
export const USER_PAGE_SIZES = [25, 50, 100];
|
||
|
||
export const USER_SORT_OPTIONS = Object.freeze([
|
||
{ value: 'fullName,asc', label: 'По ФИО А–Я' },
|
||
{ value: 'fullName,desc', label: 'По ФИО Я–А' },
|
||
{ value: 'username,asc', label: 'По логину' },
|
||
{ value: 'role,asc', label: 'По роли' },
|
||
{ value: 'status,asc', label: 'По статусу' }
|
||
]);
|
||
|
||
export const ROLE_LABELS = Object.freeze({
|
||
ADMIN: 'Администратор',
|
||
EDUCATION_OFFICE: 'Учебный отдел',
|
||
DEPARTMENT: 'Кафедра',
|
||
SCHEDULE_VIEWER: 'Просмотр расписаний',
|
||
TEACHER: 'Преподаватель',
|
||
STUDENT: 'Студент'
|
||
});
|
||
|
||
export const ROLE_BADGE = Object.freeze({
|
||
ADMIN: 'badge-admin',
|
||
EDUCATION_OFFICE: 'badge-admin',
|
||
DEPARTMENT: 'badge-teacher',
|
||
SCHEDULE_VIEWER: 'badge-student',
|
||
TEACHER: 'badge-teacher',
|
||
STUDENT: 'badge-student'
|
||
});
|
||
|
||
export const ROLE_FILTER_OPTIONS = Object.freeze([
|
||
{ value: 'ADMIN', label: 'Администратор' },
|
||
{ value: 'EDUCATION_OFFICE', label: 'Учебный отдел' },
|
||
{ value: 'DEPARTMENT', label: 'Кафедра' },
|
||
{ value: 'SCHEDULE_VIEWER', label: 'Просмотр расписаний' },
|
||
{ value: 'TEACHER', label: 'Преподаватель' },
|
||
{ value: 'STUDENT', label: 'Студент' }
|
||
]);
|
||
|
||
export function normalizeUsersState(restored = {}) {
|
||
const size = Number(restored.userSize);
|
||
return {
|
||
query: restored.userQuery || '',
|
||
role: restored.userRole || '',
|
||
page: Math.max(Number(restored.userPage) || 0, 0),
|
||
size: USER_PAGE_SIZES.includes(size) ? size : 25,
|
||
sort: USER_SORT_OPTIONS.some(option => option.value === restored.userSort)
|
||
? restored.userSort
|
||
: 'fullName,asc'
|
||
};
|
||
}
|
||
|
||
export function initialUsersState(restored = {}) {
|
||
return normalizeUsersState(restored);
|
||
}
|
||
|
||
export function usersQueryParams(state) {
|
||
const [sort, direction] = String(state.sort || 'fullName,asc').split(',');
|
||
const params = new URLSearchParams({
|
||
query: state.query || '',
|
||
page: String(state.page || 0),
|
||
size: String(state.size || 25),
|
||
sort,
|
||
direction,
|
||
includeArchived: 'true'
|
||
});
|
||
if (state.role) params.set('role', state.role);
|
||
return `/api/users/page?${params}`;
|
||
}
|
||
|
||
export function usersUrlState(state) {
|
||
return {
|
||
userQuery: state.query || '',
|
||
userRole: state.role || '',
|
||
userPage: state.page || '',
|
||
userSize: state.size === 25 ? '' : state.size,
|
||
userSort: state.sort === 'fullName,asc' ? '' : state.sort
|
||
};
|
||
}
|
||
|
||
export function fetchUsersPage(state, client = apiClient) {
|
||
return client.get(usersQueryParams(state), { errorMessage: 'Не удалось загрузить пользователей' });
|
||
}
|
||
|
||
export function createUser({ username, password, role, fullName, jobTitle, departmentId }, client = apiClient) {
|
||
return client.post(
|
||
'/api/users',
|
||
{ username, password, role, fullName, jobTitle, departmentId: Number(departmentId) },
|
||
{ errorMessage: 'Ошибка соединения' }
|
||
);
|
||
}
|
||
|
||
export function archiveUser(id, client = apiClient) {
|
||
return client.delete(`/api/users/${id}`, { errorMessage: 'Ошибка архивирования' });
|
||
}
|
||
|
||
export function restoreUser(id, client = apiClient) {
|
||
return client.post(`/api/users/${id}/restore`, {}, { errorMessage: 'Ошибка восстановления' });
|
||
}
|
||
|
||
export function roleLabel(role) {
|
||
return ROLE_LABELS[role] || role;
|
||
}
|
||
|
||
export function roleBadgeClass(role) {
|
||
return ROLE_BADGE[role] || '';
|
||
}
|
||
|
||
export function usersEmptyMessage(state) {
|
||
return state.query || state.role ? 'Пользователи не найдены' : 'Пользователей пока нет';
|
||
}
|