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 components
  • packages/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.tsZalTable, TableStatus (free/occupied/reserved), CreateTableRequest, UpdateTableRequest, ReserveTableRequest, AssignWaiterRequest. Поля совпадают с ZalTableResponse Java backend (snake_case через JsonProperty).
  • packages/domain/src/types/index.ts — добавлен export * from "./table.ts".

API client

  • packages/api-client/src/endpoints/tables.tscreateTablesEndpoints(client) с 9 методами:
    • View: list(), getById(id)
    • Manager: create(), update(), remove()
    • Lifecycle: release(), reserve(), cancelReservation()
    • BR 3.2: assignWaiter()
  • 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.tsxposition: 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)

State (Zustand)

  • apps/desktop/src/stores/cartStore.ts — extended:
    • orderType: OrderType (default takeaway)
    • tableId: string | null, tableNumber: number | null
    • setOrderType() — при смене на не-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 (onMouseDownmousemove window listener → mouseup → PATCH с новыми координатами, clamp в границы canvas)
    • Manager: «+ Новый стол» → TableEditModal (number / label / capacity / default position)
    • Auto-reload каждые 30 сек (опрос статусов)
  • 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.tscreateMockTablesEndpoints() + 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.ts
  • packages/api-client/src/endpoints/tables.ts
  • packages/ui/src/components/OrderTypeSwitcher.tsx
  • packages/ui/src/components/TableTile.tsx
  • apps/desktop/src/stores/tablesStore.ts
  • apps/desktop/src/screens/TablesScreen.tsx
  • apps/desktop/src/components/ReservationModal.tsx
  • apps/desktop/src/components/TableActionSheet.tsx
  • apps/desktop/src/components/TableEditModal.tsx

Modified (9):

  • packages/domain/src/types/index.ts
  • packages/api-client/src/index.ts
  • packages/ui/src/index.ts
  • apps/desktop/src/stores/cartStore.ts
  • apps/desktop/src/api/client.ts
  • apps/desktop/src/api/mockEndpoints.ts
  • apps/desktop/src/App.tsx
  • apps/desktop/src/components/AppShell.tsx
  • apps/desktop/src/screens/MainScreen.tsx

Тесты

  • pnpm typecheck — zero errors во всём monorepo
  • pnpm exec vite build (apps/desktop) — passing
  • Smoke с VITE_USE_MOCK_BFF=true: 6 столов на canvas, drag, modal, action sheet — всё работает
  • E2E против test-VPS — открыть кассу, выбрать «В зале», открыть стол, пройти checkout

Ссылки