feat: migrate to VitePress from monorepo docs, add test-contour section

This commit is contained in:
sova-bootstrap
2026-05-28 12:29:31 +03:00
parent e90dfe1bd4
commit e3e438df68
76 changed files with 11998 additions and 60 deletions
+183
View File
@@ -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` не трогать |