feat: migrate to VitePress from monorepo docs, add test-contour section
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
# 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` не трогать |
|
||||
Reference in New Issue
Block a user