Files
magistr/frontend/react/settings/EduFormsTab.jsx
dipatrik10 97bf3cc50a 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
2026-09-28 23:15:04 +03:00

150 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from 'react';
import { ConfirmDialog } from '../shared/ui/ConfirmDialog.jsx';
import {
createEducationForm,
deleteEducationForm,
fetchEducationForms
} from './settings-service.js';
export function EduFormsTab() {
const [forms, setForms] = useState(null);
const [loadError, setLoadError] = useState(false);
const [name, setName] = useState('');
const [alert, setAlert] = useState({ text: '', isError: false });
const [deleting, setDeleting] = useState(null);
useEffect(() => {
let active = true;
(async () => {
try {
const loaded = await fetchEducationForms();
if (!active) return;
setForms(loaded);
setLoadError(false);
} catch {
if (active) setLoadError(true);
}
})();
return () => {
active = false;
};
}, []);
async function reload() {
try {
setForms(await fetchEducationForms());
setLoadError(false);
} catch {
setLoadError(true);
}
}
async function submit(event) {
event.preventDefault();
const trimmed = name.trim();
if (!trimmed) {
setAlert({ text: 'Введите название', isError: true });
return;
}
try {
const created = await createEducationForm(trimmed);
setAlert({ text: `Форма «${created.name}» создана`, isError: false });
setName('');
await reload();
} catch (error) {
setAlert({ text: error.message || 'Ошибка создания', isError: true });
}
}
async function confirmDelete() {
const form = deleting;
setDeleting(null);
try {
await deleteEducationForm(form.id);
await reload();
} catch (error) {
setAlert({ text: error.message || 'Не удалось удалить форму обучения', isError: true });
}
}
return (
<>
<div className="card create-card">
<h2>Новая форма обучения</h2>
<form onSubmit={submit}>
<div className="form-row">
<div className="form-group">
<label htmlFor="new-ef-name">Название</label>
<input
type="text"
id="new-ef-name"
placeholder="Бакалавриат"
required
value={name}
onChange={event => setName(event.target.value)}
/>
</div>
<button type="submit" className="btn btn-md btn-primary">Создать</button>
</div>
<div
className={`form-alert${alert.text ? (alert.isError ? ' error' : ' success') : ''}`}
role="alert"
>
{alert.text}
</div>
</form>
</div>
<div className="card">
<h2>Все формы обучения</h2>
<div className="table-wrap">
<table>
<thead>
<tr>
<th>ID</th>
<th>Название</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
{loadError ? (
<tr><td colSpan="3" className="loading-row">Ошибка загрузки</td></tr>
) : forms === null ? (
<tr><td colSpan="3" className="loading-row">Загрузка...</td></tr>
) : forms.length === 0 ? (
<tr><td colSpan="3" className="loading-row">Нет форм обучения</td></tr>
) : forms.map(form => (
<tr key={form.id}>
<td>{form.id}</td>
<td>{form.name}</td>
<td>
<button
type="button"
className="btn btn-sm btn-danger"
onClick={() => setDeleting(form)}
>
Удалить
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{deleting && (
<ConfirmDialog
title="Удаление формы обучения"
message="Удалить форму обучения?"
confirmLabel="Удалить"
danger
onClose={() => setDeleting(null)}
onConfirm={() => void confirmDelete()}
/>
)}
</>
);
}