8.7 KiB
adminPanel: архитектура и соглашения
apps/adminPanel — SPA на React 18 + Vite для внутренних операторов (контент, врачи, филиалы, акции). Локально поднимается контейнером adminPanel-local (make local-up), порт по умолчанию 3211.
Связанные страницы:
- CRUD контента в UI — новости, промо, заболевания и т.д.
- Backend CRUD контента — API, на которое смотрит админка.
Стек
| Слой | Технология |
|---|---|
| UI | React, React Router 6 |
| Состояние | Redux Toolkit |
| API | RTK Query (createApi + injectEndpoints) |
| Стили | Bootstrap 4 (SB Admin 2), SCSS-переопределения |
| Редактор HTML | Jodit (TextEditor) |
| Сборка | Vite, ESLint |
Переменные окружения: apps/adminPanel/.env.local → VITE_API_BASE_URL (локально http://localhost:8081).
Структура каталогов
apps/adminPanel/src/
├── api/ # RTK Query: apiSlice + injectEndpoints по доменам
├── components/ # Переиспользуемые UI-блоки
├── config/ # api.js (base URL), при необходимости
├── hooks/ # useSpecialist, useSorting, useSortedPaginated, …
├── pages/ # Экраны (маршруты), в т.ч. *ListPage / Edit* / Add* по доменам
├── routes/ # ProtectedRoute
├── store/ # Redux store, slices (auth, region, utils)
└── styles/ # theme-override.scss
Поток данных
flowchart LR
Page[pages/*] --> RTK[api/*.js]
RTK --> Slice[apiSlice baseQuery]
Slice --> Backend[Symfony API]
Page --> Redux[store/slice]
Redux --> Page
- Чтение списков —
useXxxQueryсrefetchOnMountOrArgChangeтам, где нужен свежий список. - Запись —
useXxxMutation+authHeader()(Bearer изlocalStorage.token). - Регионы — статический справочник в
store/slice/regionSlice.js(91–94). Филиалы — с API (apiFilial).
Аутентификация
POST /user/login→ токен вlocalStorage(apiSlicelogin mutation).ProtectedRouteоборачивает layout сMainPage.- Write-запросы:
authHeader()в mutations.
Локальный админ: local.backend@example.test / local-password (ROLE_ADMIN).
Layout и навигация
| Компонент | Назначение |
|---|---|
MainPage |
Shell: sidebar + navbar + <Outlet /> |
Sidebar / Navbar |
Пункты меню (SidebarNavItem) |
ProtectedRoute |
Редирект на /login без токена |
Новый раздел: добавить Route в App.jsx и ссылку в Sidebar.jsx + Navbar.jsx.
Переиспользуемые компоненты (обязательно брать готовые)
Формы и списки
| Компонент | Когда использовать |
|---|---|
EditElementForm |
Любая карточка редактирования: заголовок, «Сохранить», «Отмена», опционально «Удалить» |
LoadingComponent |
Загрузка данных |
ErrorComponent |
Ошибка загрузки |
NotFindElement |
404 по id |
THead / TBody |
Таблицы с сортировкой и раскрытием строки |
PageNav |
Пагинация (клиентская или после нормализации meta) |
FilterBar |
Фильтр списка врачей: регион + филиал + поиск |
Модалки
| Компонент | Когда использовать |
|---|---|
Modal |
Универсальная модалка (portal, backdrop). Успех сохранения — как в EditStockPage |
ResponseModals |
loading / error / success для длинных операций |
DcodeModal, KodoperModal, StockModal |
Привязка расписания / кодов / акций к врачу |
Не использовать window.alert для успешного сохранения — только Modal с текстом «Изменения внесены».
Редакторы и ввод
| Компонент | Когда использовать |
|---|---|
TextEditor |
HTML-поля (content, anons). Props: content, setContent (не value/onChange) |
TagInput, TagStaticInput, TagKodoperStatic |
Теги, коды операций |
PhoneInput |
Телефон |
Доменные блоки (врач)
| Компонент | Назначение |
|---|---|
CertificatesForm, PortfolioForm, StocksForm |
Вкладки на карточке врача |
Паттерны страниц
Список (legacy, богатый UI)
Пример: StoksListPage, SpecialistListPage, FilialsListPage.
- локальный state: поиск, страница,
expandedId; useOutsideClickпо таблице;- кнопка «Добавить» →
navigate('.../create').
Контент CRUD (6 сущностей)
Рекомендуемая реализация (issues/27-future): общие ContentListPage / ContentEditPage, конфиг contentResources.js, виджеты ContentField, ошибки через parseSaveError и классы content-field--has-error (без window.alert при сохранении).
Альтернатива на issues/27: отдельные страницы по образцу /promotions.
Подробно (маршруты, виджеты, API, поля): admin-panel-content-crud.
Редактирование (прочие домены)
EditStockPage / EditSpecialistPage — отдельная страница под домен, EditElementForm, при необходимости Modal на успех.
API-слой
| Файл | Ресурс |
|---|---|
apiSlice.js |
createApi, login/logout, authHeader |
apiSpecialist.js |
Врачи |
apiStock.js |
Акции (/promotions → stock) |
apiFilial.js |
Филиалы |
apiContent.js |
Контент (6 ресурсов, contentHooks) |
apiDepartment.js, apiLocation.js, … |
Остальные домены |
Новый домен: API.injectEndpoints({ endpoints: (build) => ({ ... }) }), зарегистрировать reducer в store.js (если отдельный slice не нужен — достаточно apiSlice).
Redux
| Slice | Содержимое |
|---|---|
auth |
token, user (login matchers) |
region |
regions: { 91: 'Саратов', … } |
utils |
ITEMS_PER_PAGE, конфиг колонок таблиц |
Хуки
| Хук | Назначение |
|---|---|
useSortedPaginated |
Сортировка + slice для клиентской пагинации |
useSorting |
state сортировки для THead |
useOutsideClick |
Закрыть expanded row / dropdown |
useSpecialist |
Данные врача + filials + mutations |
Чего избегать
- Дублировать разметку карточки вместо
EditElementForm. - Подключать
TextEditorс неверными props — контент не сохранится или упадёт на blur. - Хардкодить URL API в новых экранах — выносить в
config/api.js/apiUrl()отдельной задачей. - Делать generic-обёртки для контента вместо копирования паттерна
StoksListPage/EditStockPage.
Локальный запуск
make local-up
open http://localhost:3211/login
Пересборка в контейнере: docker exec adminPanel-local yarn build.
Ветки Git
| Ветка | Содержание |
|---|---|
dev |
production-like база |
issues/27-future |
контент CRUD: generic-страницы + виджеты полей (см. документацию) |
issues/27 |
контент CRUD: копия паттерна /promotions |
| Backend | backend-content-crud — feature/content-crud-architecture, feature/content-crud-*; issues/27 не трогать |