Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ build/
# IDEA
.idea/
.vscode/
.vscode/
*.code-workspace

# TO-DO files
**/*[Tt][Oo][Dd][Oo]*

# Codex
.codex/
codex-history.md
69 changes: 69 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# AGENTS.md - Agent Rules (frontend)

React + TypeScript + Vite + Chakra UI v2 + react-query v3 SPA of SouvTech CMS.
Package manager: yarn. Goals: clean, readable code and minimal diffs.

## Read first (short files, pick by task)

- `agent-docs/README.md` - index of docs
- `agent-docs/frontend-architecture.md` - routing, permissions, API layer, react-query, WS
- `agent-docs/frontend-pages-and-components.md` - where pages and reusable components live
- `agent-docs/frontend-conventions.md` - style, dates, pagination, checks, gotchas
- `../backend/agent-docs/business-rules.md` - domain rules (UI language, dates, SKU…)

## Non-negotiables

- Minimal diff: change only what the task requires; no drive-by refactors or cleanups
- Match the style of the touched file; reuse existing components, hooks and patterns
- No new dependencies, no `package.json` version changes, no `tsconfig`/`vite.config`/
`theme.ts` changes without approval; `yarn.lock` changes only with approved deps
- No new top-level directories under `src/`; no barrel `index.ts` files
- Do not rename or move files unless the task requires it
- Never commit, push, deploy or merge unless explicitly asked

## Keep the docs current

- Before changing a page or mechanic, read its `agent-docs/` file; if the doc contradicts
the code, fix the doc in the same PR
- When a change alters routes, permissions, query keys, WebSocket invalidations, contexts,
conventions or workflow described in `agent-docs/`, update the affected file(s) in the same
branch (separate `docs:` commit)
- Domain rules agreed with the client live in `../backend/agent-docs/business-rules.md` -
add new ones there
- Keep files short and topic-split: extend the matching file instead of creating new ones,
register new files in `agent-docs/README.md`

## Structure (`src/`)

`api/` (requests) · `service/` (mutations over api) · `context/` · `hook/` (no JSX) ·
`component/` (feature folders) · `page/` (routed screens) · `type/` · `constant/` · `util/` ·
`asset/`; app setup in `configuration.tsx`, `theme.ts`, `index.tsx`.

## Coding standards

- Absolute imports from `src` (`api/*`, `component/*`, …); relative only inside a feature folder
- Arrow components with typed props: `export const X: FC<XProps> = (props) => { const {…} = props … }`;
`FCC` from `type/fcc` when children are accepted; Chakra props over custom CSS
- No `any`; hooks start with `use` and contain no JSX
- Prettier config in `.prettierrc` (no semicolons, double quotes, width 80, trailing commas)
- Comments only for non-obvious intent: `// NOTE:`, `// TODO:`, `// FIX:`
- UI text in English; permissions via `useUserPermissions`, never hardcoded role checks

## Verification (required)

```bash
yarn install --frozen-lockfile
yarn run lint # tsc --noEmit
yarn run build
npx prettier --check <changed files>
```
Then check the change in the browser against the local backend
(`../backend/agent-docs/local-dev-and-testing.md`).

## Git workflow

- `main` = production (deploy on push, only via PR from `dev`); `dev` = development
- Feature branch with a semantic name from `dev`; Conventional Commits
(`feat:`, `fix:`, `refactor:`, `chore:`, `docs:`, `ci:`)
- PR into `dev`, assign to the author, label `Ready To Merge` when done; PR body:
`## Summary` with bullets; always give the PR link; never merge yourself
1 change: 1 addition & 0 deletions CLAUDE.md
16 changes: 16 additions & 0 deletions agent-docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Документация фронта для кодинг-агентов

React 18 + TypeScript + Vite + Chakra UI v2 + react-query v3 + react-router v6 + axios.
Правила репозитория — `../AGENTS.md`. Общая картина системы и предметной области —
`../../backend/agent-docs/overview.md` и `business-rules.md` там же.

| Файл | Когда читать |
|---|---|
| [frontend-architecture.md](frontend-architecture.md) | Любая правка: провайдеры, маршруты и права, API-слой, react-query, WebSocket, контексты и хуки |
| [frontend-pages-and-components.md](frontend-pages-and-components.md) | Найти страницу/компонент под задачу, повторить существующий паттерн (таблицы, модалки, флоу гравёра) |
| [frontend-conventions.md](frontend-conventions.md) | Стиль кода, импорты, даты, пагинация, фильтры магазинов, тосты, проверки перед сдачей, ловушки |

## Поддержание актуальности
Документация правится вместе с кодом: меняешь маршрут, права, query-ключ, WS-инвалидацию,
контекст или соглашение — обнови соответствующий файл в той же ветке. Нашёл расхождение доки
с кодом — исправь доку. Правила предметной области — в `backend/agent-docs/business-rules.md`.
71 changes: 71 additions & 0 deletions agent-docs/frontend-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Архитектура фронта

## Загрузка приложения
`src/index.tsx` → `App` (ChakraProvider с `theme.ts`) → `AppRouter`:
`BrowserRouter` → `QueryClientProvider` → `WebSocketContextProvider` → `AuthContextProvider` →
side-маршруты (`/auth`, `/noaccess`, `/maintenance`) → `UserContextProvider` → `SearchContextProvider`
→ `PurchaseTabsContextProvider` → `PaginationContextProvider` → `Routes` внутри `AppLayout`
(сайдбар + `Outlet`; без токена редирект на `/auth`; на каждой смене маршрута дёргает
`GET /healthcheck`, при ошибке уводит на `/maintenance`).

## Маршруты и права (`src/configuration.tsx`)
`configuration.sidebarItems` — массив маршрутов `{type: main|child|side, path, component,
permissions, icon, name}`. `permissions` не задан — доступно всем, `[]` — только админу,
список — нужны **все** перечисленные права. Оборачивается `withAuthAndPermission` (`hook/`):
пока грузится пользователь — спиннер, нет прав — `/noaccess`. Страницам с таблицами
оборачивают `TableContextProvider<Filter>`, тикетам и гравировке — `TicketsContextProvider` /
`EngravingContextProvider`. `guideNotionPageId` у страницы → кнопка «?» с гайдом из Notion.
Версия в сайдбаре — `configuration.version`.

## Слой API
- `api/axiosClient.ts`: `baseURL` из `VITE_API_BASE_URL`; в каждый запрос `Authorization:
Bearer <localStorage.token>` и `device-token`; на 401 токен стирается (редирект сделает
`AppLayout`). Ошибка сервера в `error.response.data.detail`.
- `api/<domain>/<entity>.ts` — по функции на эндпоинт: `getAll*`, `get*ById`, `create*`,
`update*`, `delete*`; возвращают `data`. Списки на бэке — **POST с телом**
(`POST /order/all/`, `POST /tickets`, `POST /storage/good/all/`).
- Тело списка собирается `beautifyBody` (`util/apiRequestBody.ts`) из `ApiRequest<Filter>`:
`limit`, `offset`, `shopId → shops: [id]` (если > 0), `sortField → sort_field`,
`sortDirection → sort_direction`, `searchFilter → search_filter`. Ответ — `ApiResponse<T>`
`{count, result}`. Тип `WithId<T>` добавляет `id`.
- Мутации — в `service/<domain>/*.ts`: хуки `use<Entity><Action>Mutation` на `useMutation`
с `invalidateQueries(<ключи>)` в `onSuccess` и `notifyApiError`/`notify` в `onError`
(не везде — при добавлении делай `onError`).

## react-query
`queryClient` с `refetchOnWindowFocus: false`, других дефолтов нет. Ключи: строки для списков
(`ordersResponse`, `storageGoodsList`, `purchasesList`, `ticketsList`, `currentUser`,
`usersList`…), кортежи для параметризованных (`["order", id]`, `["processingOrder", id]`,
`["ticketMessages", ticketId]`, `["ticketsList", searchOrderId]`). Паттерн пагинированных
таблиц: статичный ключ + `useEffect(() => refetch(), [offset, rowsPerPageCount, selectedShopId,
sortField, sortDirection, searchFilter])`. Тикеты — единственный `useInfiniteQuery`
(страницы по `TICKETS_PER_PAGE`, `getNextPageParam` по `count`).

## WebSocket-инвалидация (`context/websocket.tsx`)
Одно соединение на `VITE_WEBSOCKET_BASE_URL`, без реконнекта. Бэкенд шлёт имя очереди
(`constant/ws_queues.py` на бэке), фронт по `switch` инвалидирует ключи:
`purchase`→`purchasesList`; `purchase_delivery`→`purchaseDeliveriesList`; `supplier*`→`suppliersList`;
`file`→закупки и посылки; `comment`→**все запросы**; `order`→`ordersResponse`; `good`→`goodsResponse`;
`user`/`role`→`currentUser`,`usersList`,`rolesList`; `storage`/`storage_good`→`purchaseDeliveriesList`,
`storageGoodsList`,`storageActualInfo`; `production_info`→`goodsWithProductionInfoList`;
`table_access`→`roleTableAccess`,`userTableAccessList`; `ticket`→`ticketsList`,`ticketMessages`.
Новая очередь на бэке → добавить ветку здесь.

## Контексты (`src/context`)
| Контекст | Что держит |
|---|---|
| `auth` | `isAuthenticated` (по токену), `signIn`, `signOut` |
| `user` | `currentUser` (`GET /user/current/`), роли, права (плоский список имён), магазины, `isUserAdmin/Manager/Engraver`, `currentEngraverId`, доступ к колонкам |
| `pagination` | глобальный `rowsPerPageCount` (10/50/100) |
| `table` | generic: `sortField`, `sortDirection`, `searchFilter` для одной таблицы |
| `search` | глобальный текст поиска для карточных страниц (клиентская фильтрация) |
| `purchaseTabs` | индекс вкладки Purchases/Deliveries |
| `tickets` | открытый тикет, его сообщения и processing order |
| `engraving` | активная смена, текущий processing order, редиректы `/engraving` ↔ `/engraving/:id`, модалки перерывов и бездействия, опрос плановых перерывов каждые 10 с |
| `websocket` | только побочный эффект |

## Хуки (`src/hook`)
`usePagination` (страница/offset локально), `useShopFilter` (`selectedShopId`, 0 = все),
`useUserPermissions` (булевы `canReadOrders`, `canEditStorage`… из `constant/permissions.ts`,
имена совпадают с бэкендом), `useUserTableAccess` (колонки по ролям, только Production Info),
`useAuthorizedDevice`, `useCommentInput` (комментарии к объекту), `withAuthAndPermission`.
49 changes: 49 additions & 0 deletions agent-docs/frontend-conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Соглашения и ловушки фронта

## Код
- Импорты абсолютные от `src`: `api/…`, `component/…`, `constant/…`, `context/…`, `hook/…`,
`page/…`, `service/…`, `type/…`, `util/…`. Плюральные алиасы из `tsconfig.paths`
(`components/*`…) битые — не использовать. Относительные импорты запрещены eslint-правилом.
- Компоненты: `export const X: FC<XProps> = (props) => { const {…} = props; … }`, `FCC` из
`type/fcc` для `children`; один основной компонент на файл, без barrel-файлов; Chakra-пропсы
вместо CSS. Хуки начинаются с `use`, без `any`.
- Prettier: 2 пробела, без точек с запятой, двойные кавычки, `printWidth 80`, trailing commas.
Комментарии `// NOTE:` (почему), `// TODO:`, `// FIX:`.

## Данные
- Даты приходят строками: `order.date` как `d.M.yyyy`, остальное ISO. Инструменты —
`util/formatting.ts` (`dateAsStringToDate(str, isUTC)`, `formatDate`, `stringToDate`,
`dateToDateAsString`, `timestampToDate`…) и `util/dates.ts`. Бэкенд хранит UTC, клиент в
America/Chicago; целевой формат для клиента `MM/DD/YYYY`, 12-часовое время (CMS-задача).
- `WithId<T>` вместо `id` в базовых типах; `ApiRequest<Filter>`/`ApiResponse<T>` для запросов.
- Пагинация: `INITIAL_ROWS_PER_PAGE=10`, варианты `[10, 50, 100]`, `TICKETS_PER_PAGE=20`
(`constant/tables.ts`); `offset = currentPage * rowsPerPageCount`.
- Фильтр магазина: `useShopFilter` → `shopId` в запросе → `shops: [id]`; опции — магазины
текущего пользователя (`userShops`), 0 = все.
- `engraving_info` — JSON-строка вариаций Etsy; `parseEngravingInfoStr` убирает служебные
ключи (`listing_id`, `product_id`, `transaction_type`, `OriginalSKU`), персонализация —
`PERSONALIZATION_PARAM`; текст декодировать `he` (Etsy присылает HTML-сущности).
- Права: `constant/permissions.ts` — строки должны совпадать с бэкендом; для UI —
`useUserPermissions().canXxx`. `constant/roles.ts` — `admin`, `manager`, `storager`, `engraver`.

## Проверки перед сдачей
```bash
yarn install --frozen-lockfile
yarn run lint # tsc --noEmit
yarn run build # vite build → build/
npx prettier --check <изменённые файлы>
```
Тестов нет; проверяй в браузере (dev-сервер 5173, токен в `localStorage.token`,
см. `backend/agent-docs/local-dev-and-testing.md`). В dev включён `StrictMode` — двойной запрос
при монтировании нормален.

## Ловушки
- Статичные query-ключи + ручной `refetch()` в таблицах: при добавлении параметра не забудь
его в зависимости `useEffect`, иначе данные не обновятся.
- `PurchasesHistory` использует ключ `ordersResponse` (как `Orders`).
- WS-инвалидация `comment` сбрасывает все запросы; соединение не переподключается.
- `TicketsPanel` скрывает поиск, пока `isLoading` — для поисковых запросов держи
`keepPreviousData`, иначе инпут размонтируется.
- Заголовок `device-token` уходит строкой `"null"`, если устройство не авторизовано.
- Гайды тянутся с `notion-api.splitbee.io` мимо `axiosClient`.
- `NotFound` не подключён к маршрутам; в `/storage` в `configuration.tsx` лишняя запятая в JSX.
Loading
Loading