# 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 + `` | | `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` не трогать |