Files
docs/apps/admin-panel.md

184 lines
8.7 KiB
Markdown
Raw Permalink 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.
# adminPanel: архитектура и соглашения
`apps/adminPanel` — SPA на **React 18 + Vite** для внутренних операторов (контент, врачи, филиалы, акции). Локально поднимается контейнером `adminPanel-local` (`make local-up`), порт по умолчанию **3211**.
Связанные страницы:
- [CRUD контента в UI](/apps/admin-panel-content-crud) — новости, промо, заболевания и т.д.
- [Backend CRUD контента](/apps/backend-content-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`).
## Структура каталогов
```text
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
```
## Поток данных
```mermaid
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` (`apiSlice` login 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](/apps/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`.
## Локальный запуск
```bash
make local-up
open http://localhost:3211/login
```
Пересборка в контейнере: `docker exec adminPanel-local yarn build`.
## Ветки Git
| Ветка | Содержание |
| --- | --- |
| `dev` | production-like база |
| `issues/27-future` | контент CRUD: generic-страницы + виджеты полей (см. [документацию](/apps/admin-panel-content-crud)) |
| `issues/27` | контент CRUD: копия паттерна `/promotions` |
| Backend | [backend-content-crud](/apps/backend-content-crud) — `feature/content-crud-architecture`, `feature/content-crud-*`; `issues/27` не трогать |