Files
docs/apps/admin-panel.md
T

8.7 KiB
Raw Permalink Blame History

adminPanel: архитектура и соглашения

apps/adminPanel — SPA на React 18 + Vite для внутренних операторов (контент, врачи, филиалы, акции). Локально поднимается контейнером adminPanel-local (make local-up), порт по умолчанию 3211.

Связанные страницы:

Стек

Слой Технология
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.localVITE_API_BASE_URL (локально http://localhost:8081).

Структура каталогов

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

Поток данных

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.

Редактирование (прочие домены)

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.

Локальный запуск

make local-up
open http://localhost:3211/login

Пересборка в контейнере: docker exec adminPanel-local yarn build.

Ветки Git

Ветка Содержание
dev production-like база
issues/27-future контент CRUD: generic-страницы + виджеты полей (см. документацию)
issues/27 контент CRUD: копия паттерна /promotions
Backend backend-content-crudfeature/content-crud-architecture, feature/content-crud-*; issues/27 не трогать