32 KiB
AGENTS.md — Dashfront Developer & Agent Guide
Audience: AI Coding Agents & Pair-Programmers working on
dashfront.
Workspace:/home/seb/src/dashfront
Last Updated: 2026-09-21
Table of Contents
- System Overview & Ecosystem Map
- Technology Stack & Key Libraries
- Directory Layout
- State Management Conventions
- Backend Communication & Authentication Rules
- Route & Navigation Architecture
- Developer Workflows & Commands
- Critical Agent Rules & Common Pitfalls
- Deep Dive: The Automats (
react-automat) - Deep Dive: Configuration Automats (Company, Category, Brand, Unit & POS Terminals)
dashguiComponent Library Integration- Application Views & Route Hierarchy
- Media, Images & Document Pipelines
- Real-time SSE Signals & Invalidation
- Developer Tooling & Debugging
- Agent Cheatsheet & Code Recipes
1. System Overview & Ecosystem Map
dashfront is the React web client and Progressive Web App (PWA) for the Dash ERP/POS system. It is part of a multi-repository workspace under /home/seb/src/:
/home/seb/src/
├── dashfront/ # THIS REPO: Vite + React 19 web/PWA client (port 4209 HTTP, 4308 HTTPS)
├── dashback/ # Backend Express REST/SSE API server (port 4208)
├── dashdbschema/ # LibDashStore (MSSQL data layer, migrations, schema graph, API docs)
├── dashgui/ # Reusable custom UI component library (file:../dashgui)
└── automat/ # react-automat state management library (file:../automat or npm)
End-to-End Data & Execution Flow
# Machine-readable execution flow:
flow:
- step: 1_client
component: dashfront
runtime: React 19 (PureComponent & Function Components)
state_layers:
automats: [artikelAutomat, articleSearchAutomat, articleIndexAutomat, deliveryEditorAutomat, authAutomat, sseAutomat, supplierAutomat, navigationAutomat, userSettingsAutomat, companyAutomat, categoryAutomat, brandAutomat, unitAutomat, posterminalAutomat, deliveryAutomat, quoteAutomat, orderAutomat, entityAutomat, customergroupAutomat, numberAutomat]
transport: src/api (http(), named namespaces)
egress:
protocol: HTTP (fetch with credentials: "include") & SSE (/api/events)
proxy: Vite dev server (/api -> http://localhost:4208)
target: dashback
- step: 2_api_gateway
component: dashback
runtime: Express 5 API server (src/api/routes/*)
security:
auth_middleware: Cookie auth middleware (validates JWT in HTTP-only "token" cookie)
context_injection:
user_auth_id: req.user.googleAuthId
tenant_company_id: req.selectedCompanyId
events: Dispatches real-time SSE mutation events to subscribed clients via /api/events
egress:
interface: In-process method calls (store.<namespace>.<method>())
target: dashdbschema
- step: 3_data_layer
component: dashdbschema
runtime: LibDashStore (src/store/*)
storage: MSSQL connection pool, migrations, and SQL transactions
2. Technology Stack & Key Libraries
| Technology | Version / Spec | Notes |
|---|---|---|
| Build & Dev | Vite 8 (type: module) |
Dev server on 4209, HTTPS on 4308, proxies /api → http://localhost:4208 |
| Framework | React 19 (react, react-dom) |
Uses PureComponent (with Automats & dashgui) and modern function components |
| Routing | react-router-dom v7 |
Configured in src/appRouter.jsx with HMR router preservation |
| UI & Theming | MUI 9 (@mui/material, Emotion) |
Custom theme in src/theme/, base background #85b7ad in src/colors.js |
| Shared GUI | dashgui (file:../dashgui) |
PanelTile, RecordList, Tile, SingleLineTextEdit, ElevatedPill |
| 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 |
| 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 |
| Drag & Drop | @shopify/draggable, sortablejs |
Category re-ordering, article assignments, nested tree nodes |
3. Directory Layout
src/
├── api/ # Backend communication layer
│ └── index.js # Canonical API module (article, category, delivery, entity, …)
├── automats/ # react-automat state modules (fine-grained subscriptions)
│ ├── artikelAutomat.js # Article cache, deduplicated loads, and reactive single-article state
│ ├── 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
│ ├── userSettingsAutomat.js # User preferences (viewer positions, layout settings)
│ └── index.js # Barrel exports for all automats and helper selectors
├── components/ # Shared UI components
│ ├── AppShell.jsx # Main application frame (top bar, sidebar, nav buttons)
│ ├── SectionLayout.jsx # Secondary navigation header for sections (/artikel, /lieferung...)
│ ├── AuthBootstrap.jsx # Session bootstrapper, Google One Tap, company setup gate
│ ├── LoginPage.jsx # Google One Tap / credentials login interface
│ ├── UserMenu.jsx # User avatar, company switcher dropdown, logout action
│ ├── BackendError.jsx # Graceful error banner for backend communication failures
│ ├── article/ # Article tiles, editors, dialogs, photos, price tiers
│ ├── category/ # Category navigation, dialogs, and picker components
│ ├── manifest/ # Manifest / delivery line inspection widgets
│ ├── settings/ # Terminal, unit, and brand settings dialogs
│ └── tiles/ # Tile components extending dashgui base Tile
├── devtools/ # Dev-only debug tools
│ └── AutomatInspector.jsx # Docked overlay inspecting all registered window.__AUTOMATS__
├── pages/ # Top-level view routes
│ ├── artikel/ # Article index, category browser, warehouse, SKU detail editor
│ ├── lieferung/ # Delivery list, new delivery editor, supplier manager, edit page
│ ├── auftrag/ # Quotes (Angebote) and orders (Bestellungen)
│ ├── kunde/ # Customer list, new customer, customer groups, edit page
│ └── infos/ # System and company information page
├── 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
├── nav.js # Navigation items (NAV_ITEMS, SUB_NAV, badges, active path matchers)
├── colors.js # Shared color constants (MAIN_BACKGROUND)
├── main.jsx # React root mount, providers, dev SW unregistration
└── App.jsx # Main router provider wrapper
4. State Management Conventions
In dashfront, two state management solutions coexist harmoniously:
A. react-automat (Recommended for Domain Models & Frequent Updates)
- Skill reference: Read .agents/skills/automat/SKILL.md.
- Lives outside the React lifecycle in module scope (
src/automats/*.js). - Designed for React
PureComponentto bypass unnecessary React reconciliations. - Pattern:
import { PureComponent } from 'react'; import { artikelAutomat, createArticleSelector, getArticle } from '../automats'; class ArticleView extends PureComponent { constructor(props) { super(props); // 1. Synchronous read in constructor — zero blank flash! this.state = { article: getArticle(props.sku), }; } componentDidMount() { // 2. Subscribe with fine-grained selector this.unsub = artikelAutomat.subscribe(this, (state) => ({ article: createArticleSelector(this.props.sku)(state), })); } componentWillUnmount() { // 3. Always unsubscribe this.unsub?.(); } render() { const { article } = this.state; if (!article) return <div>Lädt...</div>; return <div>{article.name}</div>; } }
B. Configuration Automats (src/automats/)
- Company, hierarchical
categorytree,brand,posterminal, andunitstate live in dedicated automats (companyAutomat,categoryAutomat,brandAutomat,unitAutomat,posterminalAutomat). - Company-scoped lists (article index, deliveries, quotes, orders, entities, customer groups, number sequences) live in list automats built by
createListAutomat. - Reads use the same fine-grained
subscribe/selector pattern as the domain automats. - Cross-automat coordination is done by subscribing to
authAutomat: when it reachesauthedwith aselectedCompanyId, these automats load themselves and reset onanon(see §10).
C. No React Context for Data (src/api has no context layer)
- There is no store/context provider in this app. Components import the automat or
src/apinamespace they need directly. There is deliberately nostatic contextTypefor data access. - Call the API through the namespace functions (
article.getBySku(...),delivery.list()), and keep shared/cached state in an automat. - Barrel exports live in src/automats/index.js;
src/main.jsximports it for its side effects so every store is registered before the session bootstraps.
5. Backend Communication & Authentication Rules
Authentication & Cookies
- HTTP-only cookie: The session token is stored in an HTTP-only cookie (
token). credentials: 'include': Everyfetchrequest MUST setcredentials: 'include'. Thehttp()helper in src/api/index.js does this automatically.- Never pass
googleAuthId: The frontend NEVER passesgoogleAuthIdin request bodies or query parameters. The backend middleware extracts the authenticated user directly from the cookie session! - Session Company: Multi-tenancy is scoped to the user's selected company. When a user switches companies (
PUT /api/auth/selected-company), the backend updates the cookie session. All subsequent calls operate on that company.
API Entry Point
Import API namespaces from src/api:
import { article, category, delivery, supplier, entity, company } from '../api';
// Examples:
const { article: data } = await article.getBySku('SKU-1001');
const { deliveries } = await delivery.list();
const { suppliers } = await supplier.list();
6. Route & Navigation Architecture
- Main navigation sections are defined in src/nav.js:
/artikel— Articles overview, SKU editor, Warehouse stock/kategorien/*— Drag-and-drop category tree/lieferung— Inbound deliveries, New delivery editor, Suppliers/auftrag— Quotes (/auftrag) and Orders (/auftrag/bestellung)/kunde— Customers, Customer groups/infos— System settings & company info
- Router Singleton:
- Defined in src/appRouter.jsx.
- Preserved across Vite HMR via
import.meta.hot.data.routerto prevent mid-session resets when hot-reloading code.
- Dynamic Badges:
DeliveryNavIconandDeliveryNeuIconinsrc/nav.jssubscribe todeliveryEditorAutomatto show live uncommitted draft line badges in real time.
7. Developer Workflows & Commands
Run commands from the repository root /home/seb/src/dashfront:
# Start Vite development server (proxies /api -> http://localhost:4208)
npm run dev
# Start development server with local HTTPS (uses certs/key.pem and certs/cert.pem)
npm run dev:https
# Run full PWA production build + preview (to test service worker / offline caching)
npm run dev:pwa
# Generate local self-signed TLS certificates
npm run cert
# Production build
npm run build
# Preview production build
npm run preview
Note
When testing camera-based barcode scanning or Progressive Web App features on mobile devices or local network IP addresses, start the server using
npm run dev:https. Web browsers require a secure context (HTTPS) to grant access to media devices and service workers.
8. Critical Agent Rules & Common Pitfalls
- Do not create dummy/mock backends:
dashbackruns locally athttp://localhost:4208. All/api/*routes are handled by it. - Keep
dashguilinks working:dashguiis imported viafile:../dashgui. Vite has deduplication configured (dedupe: ['react', 'react-dom', ...]invite.config.js) to avoid dual-instance React conflicts. - Article Deletion Restriction: An article CANNOT be deleted if it has child variants (
is_parent: 1orchildCount > 0). The backend returns HTTP 400. - Prices and Multipliers:
- Article prices are tiered by
minmultiplierandcustomergroup_id. - Prices sent to the backend are net prices (
net).
- Article prices are tiered by
- Binary / Image Handling:
- Image uploads convert source images into AVIF variants client-side.
- Frontend components use
thumbhashfor instant blur placeholders while loading full images via/api/image/{fullHash}or/api/thumb/{fullHash}.
- No Hooks in PureComponents: Never call React hooks (
useState,useEffect) insidePureComponentclasses. Automats provide direct reactive subscription without hooks. - Always Unsubscribe: Every
automat.subscribe(this, ...)incomponentDidMount()must have its cleanup callback called incomponentWillUnmount().
9. Deep Dive: The Automats (react-automat)
All domain Automats are located in src/automats/. Each automat exposes an observable state container with explicit action methods:
1. artikelAutomat (src/automats/artikelAutomat.js)
- Role: Cache and reactive distributor for article detail payloads (
GET /api/article/:sku). - Inflight Deduplication: Uses an internal
Map<string, Promise<object>>so concurrent UI tiles or navigation transitions requesting the same SKU share a single HTTP call. - Key Actions:
loadArticle(sku): Fetches if missing, resolves immediately if cached.reloadArticle(sku): Silently re-fetches to update cache (used on SSE mutation events).setArticle(article): Updates cache after mutation responses.setArticleName(sku, name): Optimistic immediate rename.clearArticle(sku)/clear(): Invalidates cache.
- Helper Selectors:
getArticle(sku),hasArticle(sku),createArticleSelector(sku),selectArticle(sku).
2. authAutomat (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' }. - Consumed by other automats: The company, category, brand, unit, and posterminal automats
subscribetoauthAutomatand (re)load or reset on its state changes.authAutomatalso emits lifecycle events via the registeredsetAuthEventDispatcher:auth/sessionEstablishedauth/companyChangedauth/loggedOut
- Actions:
bootstrapSession(session),switchCompany(companyId),logout().
3. deliveryEditorAutomat (src/automats/deliveryEditorAutomat.js)
- Role: State engine for the multi-line inbound delivery receiver and invoice reconciler (src/pages/lieferung/neu.jsx).
- Features:
- Line items editor (
lines,createEmptyLineitem). - Supplier article auto-matching via
buildSupplierArticlesMap(articles)indexing by supplier SKU, internal SKU, and EANs. - PDF/Receipt attachments parsing with OCR integration.
- Reactive draft indicators (provides draft counts for badges in
src/nav.js).
- Line items editor (
4. supplierAutomat (src/automats/supplierAutomat.js)
- Role: Caches supplier directory list and maps articles to their corresponding supplier catalogs (
GET /api/supplier).
5. navigationAutomat (src/automats/navigationAutomat.js)
- Role: Remembers last active subpaths and last visited article SKU. When a user navigates between the main sidebar sections (e.g. from
/lieferungto/artikeland back), it restores the exact active SKU or delivery document instead of resetting to the root list.
6. userSettingsAutomat (src/automats/userSettingsAutomat.js)
- Role: Stores client-side user layout preferences such as viewer panel coordinates and drawer positions.
7. sseAutomat (src/automats/sseAutomat.js)
- Role: Owns the single
EventSourcefor/api/eventsand fans signals out into the domain automats. Reconnects whenever the selected company changes; disconnects on logout. - Built-in handlers:
category→categoryAutomat.loadTree()+invalidateAllCategoryViews();articles→articleIndexAutomat.reload()+ category-view invalidation;warehouse→warehouseAutomat.reload();attributename/propertyname→attributeAutomat.load()/propertyAutomat.load();supplier→supplierAutomat.reload(). - Extension API:
sseAutomat.actions.on(event, handler)— register a handler for a named backend event. Returns an unsubscribe callback. Handlers registered while disconnected attach on the next connect.sseAutomat.actions.watchArticle(sku, handler)— subscribe toarticle-<sku>for one SKU. Exactly one listener is bound per SKU while at least one caller is subscribed; the listener resolves its handler set at call time, so an unwatched SKU is a no-op.invalidateArticle(sku)— reloads the article plus its keyed history/supplier views. Background refreshes stay silent.
8. Company-Scoped List Automats
Built with the shared src/automats/createListAutomat.js factory, which follows the brand/unit/warehouse pattern (status: idle | loading | ready | failed):
| Automat | Backs |
|---|---|
articleIndexAutomat |
Article search index (GET /api/articleIndex), mapped to the recordset shape; also patched optimistically on rename |
deliveryAutomat |
Delivery list |
quoteAutomat / orderAutomat |
Quote / order lists |
entityAutomat / customergroupAutomat |
Customer list and customer groups |
numberAutomat |
Document number sequences |
Each exposes load() (deduplicated), reload() (silent), replace(), ensureLoaded(), and reset(). They subscribe to authAutomat, so they load on authed with a selectedCompanyId and reset to idle on anon.
9. articleSearchAutomat (src/automats/articleSearchAutomat.js)
- Role: Client-side search over the cached article index for the
/artikeloverview.search(target)returns{ categoryFacets, totalCount, recordset, error }; the page subscribes toresultinstead of receiving a callback.
10. articleDetailAutomat (src/automats/articleDetailAutomat.js)
- Role: Two
KeyedAutomatstores keyed by SKU —articleHistoryAutomat(GET /api/article/{sku}/history, paginated client-side) andarticleSupplierAutomat(GET /api/article/{sku}/supplier). Both reload automatically whensseAutomatreports anarticle-<sku>signal.
10. Deep Dive: Configuration Automats (Company, Category, Brand, Unit & POS Terminals)
The remaining static/relational/configuration state is also managed by react-automat. These automats live alongside the domain automats in src/automats/ and follow the same subscribe/selector pattern:
companyAutomat(src/automats/companyAutomat.js): Active company configuration, tax rates (standard, reduced), root category ID, company legal details. Selectors:selectCompanyTaxrate(),selectCompanyReducedTaxrate(),selectCompanyRootCategoryId(),selectCompanyName(), …categoryAutomat(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).brandAutomat(src/automats/brandAutomat.js): Catalog brands / manufacturer list (load,upsert).posterminalAutomat(src/automats/posterminalAutomat.js): Registered point-of-sale hardware terminals and their enabled categories.unitAutomat(src/automats/unitAutomat.js): Allowed units of measure (Stück, kg, Liter, etc.).
Automatic Bootstrap via authAutomat Subscriptions
Each of these automats subscribes directly to authAutomat at module load and coordinates its own loading:
// 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 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.
Automatic Debugging
All automats are observable in the AutomatInspector, which lists every automat registered in window.__AUTOMATS__ with live state, subscriber counts, and actions.
11. dashgui Component Library Integration
dashgui is a shared, high-performance UI library linked from file:../dashgui:
Core Components
PanelTile: Structured card container featuring standard header styling, action slots, collapsible state, and clean borders.RecordList: High-performance list component optimized for large dataset rendering with custom row click, hover, and selection handlers.Tile: Base container element for building responsive dashboard grids.SingleLineTextEdit: Inline editable text field with debounced autosave, blur commit, and escape rollback.ElevatedPill: Compact visual badges for status, tags, and category chips.
React 19 & Deduplication Warning
Because dashgui is symlinked from ../dashgui, it has its own node_modules. To prevent duplicate React, Emotion, or MUI instances from causing runtime context errors (Cannot read properties of null (reading 'useContext')), vite.config.js enforces dedupe:
resolve: {
dedupe: [
'react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime',
'react-router', 'react-router-dom', '@emotion/react', '@emotion/styled',
'@mui/material', '@mui/icons-material'
]
}
12. Application Views & Route Hierarchy
The application routing is defined in src/appRouter.jsx and styled with src/components/AppShell.jsx:
/
├── /artikel
│ ├── index → ArtikelUebersichtPage (Search & filter grid)
│ ├── /kategorien/* → ArtikelKategorienPage (Interactive category tree)
│ ├── /lager → ArtikelLagerPage (Warehouse stock overview)
│ └── /:sku/:tab? → ArtikelSkuPage (SKU detail: info, photos, prices, suppliers, warehouse)
├── /lieferung
│ ├── index → LieferungListePage (Inbound delivery list)
│ ├── /neu → LieferungNeuPage (Receipt editor & OCR reconciliation)
│ ├── /lieferanten → LieferungLieferantenPage (Supplier directory)
│ └── /:deliveryId → LieferungEditPage (Delivery inspection & editing)
├── /auftrag
│ ├── index → AuftragAngebotPage (Quotes / Angebote)
│ ├── /neu → AuftragAngebotNeuPage (Create quote)
│ ├── /:quoteId → AuftragAngebotEditPage (Edit quote)
│ ├── /bestellung → AuftragBestellungPage (Orders / Bestellungen)
│ ├── /bestellung/neu → AuftragBestellungNeuPage (Create order)
│ └── /bestellung/:id → AuftragBestellungEditPage (Edit order)
├── /kunde
│ ├── index → KundeListePage (Customer directory)
│ ├── /neu → KundeNeuPage (Create customer)
│ ├── /gruppen → KundeGruppenPage (Customer groups & price tiers)
│ └── /:entityId → KundeEditPage (Customer profile & address editor)
└── /infos → InfosPage (Company information & POS setup)
13. Media, Images & Document Pipelines
Client-Side AVIF Conversion
To minimize backend CPU usage and reduce bandwidth:
- When a user selects or drops an article photo, the frontend processes the image in-browser using
@jsquash/avifand@jsquash/resize. - Images are scaled to target variant dimensions and compressed into AVIF format before transmission to
/api/article/:sku/photo.
ThumbHash Placeholders
- For instant visual feedback without layout shift, article photos generate a 25-byte binary hash using
thumbhash. - src/components/article/ThumbHashImage.jsx renders the blur preview instantly while the full image loads asynchronously from
/api/thumb/{hash}or/api/image/{hash}.
PDF & Document Ingestion
- src/pages/lieferung/neu.jsx utilizes
pdfconvert-wasmto process supplier PDF delivery notes and invoices directly inside the browser. - Extracted pages are rendered to canvas, displayed for line-by-line inspection, and passed to OCR recognition to populate delivery line items.
14. Real-time SSE Signals & Invalidation
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
categoryAutomattree/article reloads. - Delivery and order updates trigger automatic list reloads.
15. Developer Tooling & Debugging
AutomatInspector (src/devtools/AutomatInspector.jsx)
- Docked overlay on the right side of the screen, active only in development mode (
import.meta.env.DEV). - Auto-discovers all running automats registered in
window.__AUTOMATS__. - Allows developers and agents to:
- Inspect full state JSON trees in real time.
- View subscriber counts and registered actions.
- Trigger actions and test state changes without code modifications.
Vite API Proxy Logging
vite.config.js includes automatic proxy logging for all /api requests and responses. HTTP method, path, request payloads, response statuses, and timing are formatted directly in the Vite terminal.
TLS / HTTPS Development
For testing hardware barcode scanners, camera video feeds, and PWA offline modes:
npm run cert # Generates local certs in ./certs/
npm run dev:https # Runs Vite HTTPS server on port 4308
16. Agent Cheatsheet & Code Recipes
Recipe 1: Consuming an Automat in a React PureComponent
import { PureComponent } from 'react';
import { artikelAutomat, createArticleSelector, getArticle } from '../automats';
export class ArticleTitle extends PureComponent {
constructor(props) {
super(props);
// 1. Read synchronously to avoid blank flash
this.state = {
article: getArticle(props.sku),
};
}
componentDidMount() {
// 2. Subscribe with fine-grained selector
this.unsub = artikelAutomat.subscribe(this, (state) => ({
article: createArticleSelector(this.props.sku)(state),
}));
}
componentDidUpdate(prevProps) {
// 3. Handle prop changes cleanly
if (prevProps.sku !== this.props.sku) {
this.setState({ article: getArticle(this.props.sku) });
this.unsub?.();
this.unsub = artikelAutomat.subscribe(this, (state) => ({
article: createArticleSelector(this.props.sku)(state),
}));
}
}
componentWillUnmount() {
// 4. Always cleanup
this.unsub?.();
}
render() {
const { article } = this.state;
return <h1>{article?.name ?? 'Lädt...'}</h1>;
}
}
Recipe 2: Calling the API and Notifying Errors
import { article } from '../api';
import { enqueueSnackbar } from 'notistack';
import { notifyError } from '../utils/notify';
import { artikelAutomat } from '../automats';
async function handleSavePrice(sku, netPrice) {
try {
const res = await article.updatePrice(sku, { net: netPrice });
artikelAutomat.actions.setArticle(res.article);
enqueueSnackbar('Preis erfolgreich aktualisiert', { variant: 'success' });
} catch (err) {
notifyError(err);
}
}
Recipe 3: Calculating Gross Prices with Company Tax Rates
// Prices in the database and API are NET prices.
// 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;
return Math.round(net * (1 + rate / 100) * 100) / 100;
}