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