Desktop POS — POS Phase 1
Архитектура
erp-pos-desktop — pnpm-monorepo (Tauri 2 + React + Zustand):
apps/desktop/— Tauri shell + screens + stores (consumer)packages/ui/— design tokens + dumb componentspackages/api-client/— endpoint factories (BFF / mock)packages/domain/— TS-типы + cart calc
Шаблон новой фичи: новый Zustand store → endpoint factory → регистрация в apps/desktop/src/api/client.ts (+ mock в mockEndpoints.ts) → screen → <Route> в App.tsx → nav-item в AppShell.tsx.
Что сделано
Domain layer
packages/domain/src/types/table.ts—ZalTable,TableStatus(free/occupied/reserved),CreateTableRequest,UpdateTableRequest,ReserveTableRequest,AssignWaiterRequest. Поля совпадают сZalTableResponseJava backend (snake_case через JsonProperty).packages/domain/src/types/index.ts— добавленexport * from "./table.ts".
API client
packages/api-client/src/endpoints/tables.ts—createTablesEndpoints(client)с 9 методами:- View:
list(),getById(id) - Manager:
create(),update(),remove() - Lifecycle:
release(),reserve(),cancelReservation() - BR 3.2:
assignWaiter()
- View:
packages/api-client/src/index.ts— экспортcreateTablesEndpoints+TablesEndpoints.
UI components
packages/ui/src/components/OrderTypeSwitcher.tsx— segmented control (Takeaway / В зале / Доставка). Active = красный фон, текст белый. Default takeaway.packages/ui/src/components/TableTile.tsx—position: absoluteплитка поx/y(px), цвет рамки/фона по статусу:- free → зелёный (
#0CC268) - occupied → красный (
colors.red) - reserved → жёлтый (
colors.warning) - Showing: number (h2), capacity (small), label (small, optional)
- Поддерживает
selected,onClick,onMouseDown,draggable(для edit-mode)
- free → зелёный (
State (Zustand)
apps/desktop/src/stores/cartStore.ts— extended:orderType: OrderType(defaulttakeaway)tableId: string | null,tableNumber: number | nullsetOrderType()— при смене на не-dine_inсбрасывает столsetTable(id, number)clear()сбрасывает наtakeaway+ null стол
apps/desktop/src/stores/tablesStore.ts(new) —items,loading,error,loaded,byId(id), методыload()/reload()/create()/update()/remove()/reserve()/cancelReservation()/release()/assignWaiter(). Auto-mutates локальный массив после успешных запросов.
Screens
apps/desktop/src/screens/TablesScreen.tsx(new) —/tables:- Canvas с
position: relative,border: dashed redв edit-mode, dotted в view-mode - Render
TableTileдля каждогоt.position_x/y - Click →
TableActionSheet(action menu) - Manager: «Редактировать план» → drag-and-drop через native mouse events (
onMouseDown→mousemovewindow listener →mouseup→ PATCH с новыми координатами, clamp в границы canvas) - Manager: «+ Новый стол» →
TableEditModal(number / label / capacity / default position) - Auto-reload каждые 30 сек (опрос статусов)
- Canvas с
apps/desktop/src/screens/MainScreen.tsx(extended):- Импортирован
OrderTypeSwitcher— отображается под заголовком «Чек» - Если
orderType === "dine_in"— показывается индикатор стола (красный если выбран, жёлтый «Стол не выбран» если нет) + кнопка «Сменить»/«Выбрать» →/tables handlePay/handleSendToKitchen:- Подставляют
order_typeиtable_idизcartStore - Блокируют отправку если
dine_inбезtableId— toast + redirect на/tables
- Подставляют
- Импортирован
Components (modals)
apps/desktop/src/components/ReservationModal.tsx(new) —note(required) +until(optional<input type="datetime-local">). Submit →tablesStore.reserve().apps/desktop/src/components/TableActionSheet.tsx(new) — Modal с кнопками в зависимости от статуса:- free: Открыть заказ / Забронировать
- occupied: Открыть заказ (deep-link на
/orders/:current_order_id) / Освободить - reserved: Открыть заказ (снимет бронь) / Снять бронь
- manager: + Редактировать / Удалить (если не occupied)
apps/desktop/src/components/TableEditModal.tsx(new) — number / label / capacity. Submit либоtablesStore.create()либоupdate().
Wiring
apps/desktop/src/api/client.ts— добавленgetTablesEndpoints()(mock или real)apps/desktop/src/api/mockEndpoints.ts—createMockTablesEndpoints()+seedMockData()создаёт 6 столов разных статусов (free/occupied/reserved/VIP)apps/desktop/src/App.tsx— добавлен<Route path="/tables">(под ProtectedRoute + RequireShift + AppShell)apps/desktop/src/components/AppShell.tsx— добавлен NavItem «Столы» между «Касса» и «Заказы»
Файлы
Created (9):
packages/domain/src/types/table.tspackages/api-client/src/endpoints/tables.tspackages/ui/src/components/OrderTypeSwitcher.tsxpackages/ui/src/components/TableTile.tsxapps/desktop/src/stores/tablesStore.tsapps/desktop/src/screens/TablesScreen.tsxapps/desktop/src/components/ReservationModal.tsxapps/desktop/src/components/TableActionSheet.tsxapps/desktop/src/components/TableEditModal.tsx
Modified (9):
packages/domain/src/types/index.tspackages/api-client/src/index.tspackages/ui/src/index.tsapps/desktop/src/stores/cartStore.tsapps/desktop/src/api/client.tsapps/desktop/src/api/mockEndpoints.tsapps/desktop/src/App.tsxapps/desktop/src/components/AppShell.tsxapps/desktop/src/screens/MainScreen.tsx
Тесты
pnpm typecheck— zero errors во всём monorepopnpm exec vite build(apps/desktop) — passing- Smoke с
VITE_USE_MOCK_BFF=true: 6 столов на canvas, drag, modal, action sheet — всё работает - E2E против test-VPS — открыть кассу, выбрать «В зале», открыть стол, пройти checkout
Ссылки
- Store Service API
- POS BFF — соседняя задача
- Store Service — соседняя задача