This commit is contained in:
seb
2026-09-23 08:16:14 +02:00
parent 49057dc0d6
commit c94ce20b7a
7 changed files with 306 additions and 437 deletions

View File

@@ -17,7 +17,7 @@
7. [Developer Workflows & Commands](#7-developer-workflows--commands)
8. [Critical Agent Rules & Common Pitfalls](#8-critical-agent-rules--common-pitfalls)
9. [Deep Dive: The Automats (`react-automat`)](#9-deep-dive-the-automats-react-automat)
10. [Deep Dive: Redux Toolkit Architecture](#10-deep-dive-redux-toolkit-architecture)
10. [Deep Dive: Configuration Automats (Company, Category, Brand, Unit & POS Terminals)](#10-deep-dive-configuration-automats-company-category-brand-unit--pos-terminals)
11. [`dashgui` Component Library Integration](#11-dashgui-component-library-integration)
12. [Application Views & Route Hierarchy](#12-application-views--route-hierarchy)
13. [Media, Images & Document Pipelines](#13-media-images--document-pipelines)
@@ -49,8 +49,7 @@ flow:
component: dashfront
runtime: React 19 (PureComponent & Function Components)
state_layers:
automats: [artikelAutomat, deliveryEditorAutomat, authAutomat, supplierAutomat, navigationAutomat, userSettingsAutomat]
redux_slices: [company, category, brand, unit, posterminal]
automats: [artikelAutomat, deliveryEditorAutomat, authAutomat, supplierAutomat, navigationAutomat, userSettingsAutomat, companyAutomat, categoryAutomat, brandAutomat, unitAutomat, posterminalAutomat]
transport: src/api (http(), ApiClient)
egress:
protocol: HTTP (fetch with credentials: "include") & SSE (/api/events)
@@ -87,7 +86,7 @@ flow:
| **Routing** | `react-router-dom` v7 | Configured in [src/appRouter.jsx](file:///home/seb/src/dashfront/src/appRouter.jsx) with HMR router preservation |
| **UI & Theming** | MUI 9 (`@mui/material`, Emotion) | Custom theme in `src/theme/`, base background `#c5ddd8` in `src/colors.js` |
| **Shared GUI** | `dashgui` (`file:../dashgui`) | `PanelTile`, `RecordList`, `Tile`, `SingleLineTextEdit`, `ElevatedPill` |
| **State Management** | `react-automat` + Redux Toolkit | Automats for reactive domain models; Redux for category tree & session bootstrap |
| **State Management** | `react-automat` | Single state layer for all domain & configuration data (articles, categories, company, brands, units, POS terminals, session) |
| **Notifications** | `notistack` | `useSnackbar()` / `<SnackbarProvider>` in [src/main.jsx](file:///home/seb/src/dashfront/src/main.jsx) |
| **Media & Images** | `@jsquash/avif`, `@jsquash/resize`, `thumbhash` | Client-side AVIF encoding and instant thumbnail hash generation |
| **Documents** | `pdfconvert-wasm` | In-browser PDF processing for supplier delivery invoices & receipts |
@@ -104,9 +103,14 @@ src/
│ ├── ApiClient.js # Object-oriented API client (this.article, this.delivery, etc.)
│ ├── StorageManager.js # Client-side cache / local storage management
│ └── storageManagerContext.js # React context provider for StorageManager
├── automats/ # react-automat stores with fine-grained subscriptions
├── automats/ # react-automat state modules (fine-grained subscriptions)
│ ├── artikelAutomat.js # Article cache, deduplicated loads, and reactive single-article state
│ ├── authAutomat.js # Authentication state, company list, company selection
│ ├── authAutomat.js # Authentication state, session bootstrap, company selection
│ ├── companyAutomat.js # Active company config (tax rates, root category, legal details)
│ ├── categoryAutomat.js # Hierarchical category tree, breadcrumbs, and category articles
│ ├── brandAutomat.js # Catalog brands / manufacturer list
│ ├── unitAutomat.js # Units of measure (Stück, kg, Liter, ...)
│ ├── posterminalAutomat.js # POS terminal config and enabled categories
│ ├── deliveryEditorAutomat.js # Multi-line delivery receipt / invoice editor & draft state
│ ├── navigationAutomat.js # Remembers active paths (last delivery path, last article SKU)
│ ├── supplierAutomat.js # Supplier list and supplier-article catalogs
@@ -132,14 +136,6 @@ src/
│ ├── auftrag/ # Quotes (Angebote) and orders (Bestellungen)
│ ├── kunde/ # Customer list, new customer, customer groups, edit page
│ └── infos/ # System and company information page
├── store/ # Redux Toolkit store (legacy & tree slice)
│ ├── index.js # configureStore with dev logger & listener middleware
│ ├── authSlice.js # Redux auth state (session bootstrap, company selection)
│ ├── companySlice.js # Redux company details (tax rates, rootCategoryId)
│ ├── categorySlice.js # Redux category tree, breadcrumbs, category articles
│ ├── brandSlice.js # Redux brand list and lookup
│ ├── posterminalSlice.js # Redux POS terminals configuration
│ └── unitSlice.js # Redux unit of measure definitions
├── theme/ # MUI theme definitions (light/dark palette, typography, components)
├── utils/ # Helpers (apiShape.js, number formatters, notify.js, etc.)
├── appRouter.jsx # react-router-dom route definitions
@@ -193,10 +189,10 @@ In `dashfront`, two state management solutions coexist harmoniously:
}
```
### B. Redux Toolkit (`src/store/`)
- Used for static/relational trees: `company`, hierarchical `category` tree, `brand`, `posterminal`, and `unit`.
- Uses `connect(mapStateToProps, mapDispatchToProps)` to connect to `PureComponent` classes or functional hooks where appropriate.
- Coordinated via `createListenerMiddleware`: when `authAutomat` emits an auth change (`auth/sessionEstablished` or `auth/companyChanged`), listeners automatically dispatch loads for company info, brands, POS terminals, and units.
### B. Configuration Automats (`src/automats/`)
- Company, hierarchical `category` tree, `brand`, `posterminal`, and `unit` state live in dedicated automats (`companyAutomat`, `categoryAutomat`, `brandAutomat`, `unitAutomat`, `posterminalAutomat`).
- Reads use the same fine-grained `subscribe`/selector pattern as the domain automats.
- Cross-automat coordination is done by subscribing to `authAutomat`: when it reaches `authed` with a `selectedCompanyId`, these automats load themselves and reset on `anon` (see [§10](#10-deep-dive-configuration-automats-company-category-brand-unit--pos-terminals)).
---
@@ -278,7 +274,7 @@ npm run preview
5. **Binary / Image Handling**:
- Image uploads convert source images into AVIF variants client-side.
- Frontend components use `thumbhash` for instant blur placeholders while loading full images via `/api/image/{fullHash}` or `/api/thumb/{fullHash}`.
6. **No Hooks in PureComponents**: Never call React hooks (`useState`, `useSelector`, `useEffect`) inside `PureComponent` classes. Automats and `connect()` provide direct reactive subscription without hooks.
6. **No Hooks in PureComponents**: Never call React hooks (`useState`, `useEffect`) inside `PureComponent` classes. Automats provide direct reactive subscription without hooks.
7. **Always Unsubscribe**: Every `automat.subscribe(this, ...)` in `componentDidMount()` must have its cleanup callback called in `componentWillUnmount()`.
---
@@ -301,7 +297,7 @@ All domain Automats are located in [src/automats/](file:///home/seb/src/dashfron
### 2. `authAutomat` ([src/automats/authAutomat.js](file:///home/seb/src/dashfront/src/automats/authAutomat.js))
- **Role**: Manages user authentication lifecycle and multi-tenant company selection.
- **State**: `{ user: AuthUser | null, googleClientId: string | null, status: 'loading' | 'anon' | 'authed' }`.
- **Bridge to Redux**: Calls `emit(action)` registered via `setAuthEventDispatcher(store.dispatch)`. Emits:
- **Consumed by other automats**: The company, category, brand, unit, and posterminal automats `subscribe` to `authAutomat` and (re)load or reset on its state changes. `authAutomat` also emits lifecycle events via the registered `setAuthEventDispatcher`:
- `auth/sessionEstablished`
- `auth/companyChanged`
- `auth/loggedOut`
@@ -326,29 +322,33 @@ All domain Automats are located in [src/automats/](file:///home/seb/src/dashfron
---
## 10. Deep Dive: Redux Toolkit Architecture
## 10. Deep Dive: Configuration Automats (Company, Category, Brand, Unit & POS Terminals)
Redux Toolkit manages static, relational, and configuration data in [src/store/](file:///home/seb/src/dashfront/src/store/):
The remaining static/relational/configuration state is **also** managed by `react-automat`. These automats live alongside the domain automats in [src/automats/](file:///home/seb/src/dashfront/src/automats/) and follow the same subscribe/selector pattern:
### Slices
1. **`categorySlice`**: Hierarchical category tree structure, category breadcrumbs, article-category memberships, category reordering (`moveCategory`, `reorderCategories`).
2. **`companySlice`**: Active company configuration, tax rates (standard, reduced), root category ID, company legal details.
3. **`brandSlice`**: Catalog brands and manufacturer list.
4. **`posterminalSlice`**: Registered point-of-sale hardware terminals.
5. **`unitSlice`**: Allowed units of measure (Stück, kg, Liter, etc.).
1. **`companyAutomat`** ([src/automats/companyAutomat.js](file:///home/seb/src/dashfront/src/automats/companyAutomat.js)): Active company configuration, tax rates (standard, reduced), root category ID, company legal details. Selectors: `selectCompanyTaxrate()`, `selectCompanyReducedTaxrate()`, `selectCompanyRootCategoryId()`, `selectCompanyName()`, …
2. **`categoryAutomat`** ([src/automats/categoryAutomat.js](file:///home/seb/src/dashfront/src/automats/categoryAutomat.js)): Hierarchical category tree (id → node map), breadcrumbs, category navigation, article lists per category, and category/article mutations (`createCategory`, `createArticle`, `deleteCategory`, `moveCategory`).
3. **`brandAutomat`** ([src/automats/brandAutomat.js](file:///home/seb/src/dashfront/src/automats/brandAutomat.js)): Catalog brands / manufacturer list (`load`, `upsert`).
4. **`posterminalAutomat`** ([src/automats/posterminalAutomat.js](file:///home/seb/src/dashfront/src/automats/posterminalAutomat.js)): Registered point-of-sale hardware terminals and their enabled categories.
5. **`unitAutomat`** ([src/automats/unitAutomat.js](file:///home/seb/src/dashfront/src/automats/unitAutomat.js)): Allowed units of measure (Stück, kg, Liter, etc.).
### Automatic Bootstrap with Listener Middleware
In [src/store/index.js](file:///home/seb/src/dashfront/src/store/index.js), `createListenerMiddleware` coordinates data loading automatically when auth events fire:
### Automatic Bootstrap via `authAutomat` Subscriptions
Each of these automats subscribes directly to `authAutomat` at module load and coordinates its own loading:
```js
companyLoadListener(listenerMiddleware);
unitLoadListener(listenerMiddleware);
brandLoadListener(listenerMiddleware);
posterminalLoadListener(listenerMiddleware);
// e.g. src/automats/companyAutomat.js
authAutomat.subscribe((auth) => {
const companyId = auth.user?.selectedCompanyId;
if (auth.status === 'authed' && companyId) {
void companyAutomat.actions.load();
} else if (auth.status === 'anon') {
companyAutomat.actions.reset();
}
});
```
When `auth/sessionEstablished` or `auth/companyChanged` occurs, these listeners trigger thunks to reload company tax settings, brands, units, and POS terminals without boilerplate in React views.
When the auth state becomes `authed` with a `selectedCompanyId` (on session bootstrap, login, or company switch), the company, brand, unit, and posterminal automats load automatically; on logout/anon they reset. `categoryAutomat.loadTree()` then reads the company's `rootCategoryId` from `companyAutomat`.
### Redux Dev Logger
In development mode (`import.meta.env.DEV`), `reduxDevLogger` intercepts actions from `auth/`, `company/`, `category/`, `brand/`, `posterminal/`, and `unit/`, logging structured before/after counts to the browser console.
### Automatic Debugging
All automats are observable in the [AutomatInspector](file:///home/seb/src/dashfront/src/devtools/AutomatInspector.jsx), which lists every automat registered in `window.__AUTOMATS__` with live state, subscriber counts, and actions.
---
@@ -433,7 +433,7 @@ The frontend connects to the backend Server-Sent Events stream at `/api/events`:
- **Architecture**: When mutations occur on `dashback` (whether triggered by this client, another terminal, or the POS), SSE events are broadcast with payload information (e.g. `{ type: 'article-update', sku: '1001' }`).
- **Reactive Cache Refresh**:
- `artikelAutomat.actions.reloadArticle(sku)` re-fetches the updated article silently, updating subscribers without flicker.
- Category updates trigger Redux category slice refreshes.
- Category updates trigger `categoryAutomat` tree/article reloads.
- Delivery and order updates trigger automatic list reloads.
---
@@ -527,7 +527,8 @@ async function handleSavePrice(sku, netPrice) {
### Recipe 3: Calculating Gross Prices with Company Tax Rates
```jsx
// Prices in the database and API are NET prices.
// Gross prices shown to end users must apply company tax rates from Redux companySlice:
// Gross prices shown to end users must apply company tax rates from companyAutomat
// (selectCompanyTaxrate() / selectCompanyReducedTaxrate()):
export function calculateGrossPrice(netPrice, taxRatePercent) {
const net = Number(netPrice) || 0;
const rate = Number(taxRatePercent) || 0;