diff --git a/src/appRouter.jsx b/src/appRouter.jsx
index 318b355..c0701d0 100644
--- a/src/appRouter.jsx
+++ b/src/appRouter.jsx
@@ -38,6 +38,9 @@ const OAuthAuthorizePage = lazy(() => import('./routes/oauth/OAuthAuthorizePage'
const ArticleHistoryPage = lazy(() => import('./routes/infos/ArticleHistoryPage'));
const GlobalHistoryPage = lazy(() => import('./routes/infos/GlobalHistoryPage'));
const LedgerHistoryPage = lazy(() => import('./routes/infos/LedgerHistoryPage'));
+const CompanyAutomatDebugPage = import.meta.env.DEV
+ ? lazy(() => import('./routes/debug/CompanyAutomatPage'))
+ : null;
function LoginRedirect() {
const params = new URLSearchParams(window.location.search);
@@ -131,6 +134,19 @@ const routes = [
{ path: ':entityId', element: },
],
},
+ ...(import.meta.env.DEV
+ ? [
+ {
+ path: 'debug',
+ element: ,
+ children: [
+ { index: true, element: },
+ { path: 'company', element: },
+ { path: 'companyautomat', element: },
+ ],
+ },
+ ]
+ : []),
{ path: '*', element: },
],
},
diff --git a/src/nav.js b/src/nav.js
index 3321c87..8cda9b8 100644
--- a/src/nav.js
+++ b/src/nav.js
@@ -25,6 +25,8 @@ import HistoryIcon from '@mui/icons-material/History';
import PublicIcon from '@mui/icons-material/Public';
import ReceiptLongIcon from '@mui/icons-material/ReceiptLong';
import BarChartIcon from '@mui/icons-material/BarChart';
+import BugReportIcon from '@mui/icons-material/BugReport';
+import BusinessIcon from '@mui/icons-material/Business';
import { isKategorienPath } from './components/category/categoryRoute';
import { deliveryEditorAutomat } from './automats/deliveryEditorAutomat';
import { NAV_ITEM_ACTIVE_ACCENT } from './colors';
@@ -97,6 +99,16 @@ export const NAV_ITEMS = [
icon: InfoIcon,
help: 'Systemstatus, Serververbindung und Mandanteneinstellungen',
},
+ ...(import.meta.env.DEV
+ ? [
+ {
+ path: '/debug',
+ label: 'Debug',
+ icon: BugReportIcon,
+ help: 'Entwickler- und Diagnosewerkzeuge für Automats (nur im Dev-Modus)',
+ },
+ ]
+ : []),
];
class DeliveryNeuIcon extends PureComponent {
@@ -297,6 +309,19 @@ export const SUB_NAV = {
help: 'Statistische Auswertungen und Kennzahlen',
},
],
+ ...(import.meta.env.DEV
+ ? {
+ '/debug': [
+ {
+ segment: '',
+ label: 'Company',
+ title: 'Company Automat',
+ icon: BusinessIcon,
+ help: 'companyAutomat Eingänge, Ausgänge (Actions & Selectors) und internen State inspizieren & steuern',
+ },
+ ],
+ }
+ : {}),
};
export function isNavItemActive(pathname, navPath) {
@@ -359,6 +384,11 @@ export function resolveActiveSub(pathname, sectionPath) {
return '';
}
+ // Treat /debug/company and /debug/companyautomat as segment ''
+ if (sectionPath === '/debug' && (rest === 'company' || rest === 'companyautomat')) {
+ return '';
+ }
+
const active = subNav.find((item) => {
if (item.segment === 'kategorien') {
return isKategorienPath(pathname);
diff --git a/src/routes/debug/CompanyAutomatPage.jsx b/src/routes/debug/CompanyAutomatPage.jsx
new file mode 100644
index 0000000..5fd87f6
--- /dev/null
+++ b/src/routes/debug/CompanyAutomatPage.jsx
@@ -0,0 +1,3354 @@
+import { PureComponent } from 'react';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import Chip from '@mui/material/Chip';
+import Divider from '@mui/material/Divider';
+import Stack from '@mui/material/Stack';
+import TextField from '@mui/material/TextField';
+import Typography from '@mui/material/Typography';
+import PlayArrowIcon from '@mui/icons-material/PlayArrow';
+import RefreshIcon from '@mui/icons-material/Refresh';
+import RestartAltIcon from '@mui/icons-material/RestartAlt';
+import ContentCopyIcon from '@mui/icons-material/ContentCopy';
+import DeleteSweepIcon from '@mui/icons-material/DeleteSweep';
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+import ExpandLessIcon from '@mui/icons-material/ExpandLess';
+import UploadFileIcon from '@mui/icons-material/UploadFile';
+import { enqueueSnackbar } from 'notistack';
+import { PanelTile, PanelTileGrid } from 'dashgui/panels';
+import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
+import authAutomat from '../../automats/authAutomat';
+import companyAutomat, {
+ selectCurrentCompany,
+ selectCompanyId,
+ selectCompanyStatus,
+ selectCompanyName,
+ selectCompanyIso639,
+ selectCompanyLanguageId,
+ selectCompanyTaxrate,
+ selectCompanyReducedTaxrate,
+ selectCompanyTaxfreerate,
+ selectCompanyRootCategoryId,
+ selectNumbers,
+ selectNumbersStatus,
+ selectPosterminals,
+ selectPosterminalStatus,
+ selectHasPosterminals,
+ selectSuppliers,
+ selectSuppliersStatus,
+ selectUnits,
+ selectUnitsStatus,
+ selectChatDrawerWidth,
+ selectWarehouses,
+ selectWarehousesStatus,
+ selectProperties,
+ selectPropertyStatus,
+ selectCustomergroups,
+ selectCustomergroupsStatus,
+ selectCarriers,
+ selectCarriersStatus,
+ selectBrands,
+ selectBrandsStatus,
+ selectAttributes,
+ selectAttributeStatus,
+ selectAccounts,
+ selectAccountsStatus,
+} from '../../automats/companyAutomat';
+import {
+ BLUE_PANEL,
+ PETROL_PANEL,
+ PURPLE_PANEL,
+ GREEN_PANEL,
+ ORANGE_PANEL,
+ YELLOW_PANEL,
+ RED_PANEL,
+ SURFACE_BASE,
+ SURFACE_RAISED,
+ BORDER,
+ TINT_SUBTLE,
+ TINT_CHIP,
+ SELECT_BG,
+ SELECT_BORDER,
+} from '../../colors';
+
+// #region domain definitions & selector registry
+const DOMAIN_DEFINITIONS = [
+ {
+ id: 'company',
+ label: '1. Company Profile',
+ statusKey: 'status',
+ errorKey: 'error',
+ dataKey: 'current',
+ loadAction: 'load',
+ keys: ['current', 'status', 'error'],
+ },
+ {
+ id: 'numbers',
+ label: '2. Number Sequences',
+ statusKey: 'numbersStatus',
+ errorKey: 'numbersError',
+ dataKey: 'numbers',
+ loadAction: 'loadNumbers',
+ keys: ['numbers', 'numbersStatus', 'numbersError'],
+ },
+ {
+ id: 'terminals',
+ label: '3. POS Terminals',
+ statusKey: 'terminalsStatus',
+ errorKey: 'terminalsError',
+ dataKey: 'terminals',
+ loadAction: 'loadTerminals',
+ keys: ['terminals', 'terminalsCompanyId', 'terminalsStatus', 'terminalsError'],
+ },
+ {
+ id: 'suppliers',
+ label: '4. Suppliers',
+ statusKey: 'suppliersStatus',
+ errorKey: 'suppliersError',
+ dataKey: 'suppliers',
+ loadAction: 'loadSuppliers',
+ keys: [
+ 'suppliers',
+ 'articlesBySupplier',
+ 'articlesLoading',
+ 'suppliersStatus',
+ 'suppliersError',
+ ],
+ },
+ {
+ id: 'units',
+ label: '5. Units',
+ statusKey: 'unitsStatus',
+ errorKey: 'unitsError',
+ dataKey: 'units',
+ loadAction: 'loadUnits',
+ keys: ['units', 'unitsStatus', 'unitsError'],
+ },
+ {
+ id: 'userSettings',
+ label: '6. User Settings',
+ statusKey: null,
+ errorKey: null,
+ dataKey: 'viewerPosition',
+ loadAction: null,
+ keys: ['viewerPosition', 'chatDrawerWidth'],
+ },
+ {
+ id: 'warehouses',
+ label: '7. Warehouses',
+ statusKey: 'warehousesStatus',
+ errorKey: 'warehousesError',
+ dataKey: 'warehouses',
+ loadAction: 'loadWarehouses',
+ keys: [
+ 'warehouses',
+ 'filteredWarehouses',
+ 'warehouseFilter',
+ 'warehousesStatus',
+ 'warehousesError',
+ ],
+ },
+ {
+ id: 'properties',
+ label: '8. Properties',
+ statusKey: 'propertiesStatus',
+ errorKey: 'propertiesError',
+ dataKey: 'properties',
+ loadAction: 'loadProperties',
+ keys: ['properties', 'propertiesStatus', 'propertiesError'],
+ },
+ {
+ id: 'customergroups',
+ label: '9. Customer Groups',
+ statusKey: 'customergroupsStatus',
+ errorKey: 'customergroupsError',
+ dataKey: 'customergroups',
+ loadAction: 'loadCustomergroups',
+ keys: [
+ 'customergroups',
+ 'filteredCustomergroups',
+ 'customergroupFilter',
+ 'customergroupsStatus',
+ 'customergroupsError',
+ ],
+ },
+ {
+ id: 'carriers',
+ label: '10. Carriers',
+ statusKey: 'carriersStatus',
+ errorKey: 'carriersError',
+ dataKey: 'carriers',
+ loadAction: 'loadCarriers',
+ keys: [
+ 'carriers',
+ 'filteredCarriers',
+ 'carrierFilter',
+ 'carriersStatus',
+ 'carriersError',
+ ],
+ },
+ {
+ id: 'brands',
+ label: '11. Brands',
+ statusKey: 'brandsStatus',
+ errorKey: 'brandsError',
+ dataKey: 'brands',
+ loadAction: 'loadBrands',
+ keys: ['brands', 'filteredBrands', 'brandFilter', 'brandsStatus', 'brandsError'],
+ },
+ {
+ id: 'attributes',
+ label: '12. Attributes & Values',
+ statusKey: 'attributesStatus',
+ errorKey: 'attributesError',
+ dataKey: 'attributes',
+ loadAction: 'loadAttributes',
+ keys: ['attributes', 'attributesStatus', 'attributesError'],
+ },
+ {
+ id: 'accounts',
+ label: '13. Payment Accounts',
+ statusKey: 'accountsStatus',
+ errorKey: 'accountsError',
+ dataKey: 'accounts',
+ loadAction: 'loadAccounts',
+ keys: ['accounts', 'accountsStatus', 'accountsError'],
+ },
+];
+
+const SELECTOR_DEFINITIONS = [
+ { name: 'selectCurrentCompany', domain: 'company', fn: selectCurrentCompany },
+ { name: 'selectCompanyId', domain: 'company', fn: selectCompanyId },
+ { name: 'selectCompanyStatus', domain: 'company', fn: selectCompanyStatus },
+ { name: 'selectCompanyName', domain: 'company', fn: selectCompanyName },
+ { name: 'selectCompanyIso639', domain: 'company', fn: selectCompanyIso639 },
+ { name: 'selectCompanyLanguageId', domain: 'company', fn: selectCompanyLanguageId },
+ { name: 'selectCompanyTaxrate', domain: 'company', fn: selectCompanyTaxrate },
+ { name: 'selectCompanyReducedTaxrate', domain: 'company', fn: selectCompanyReducedTaxrate },
+ { name: 'selectCompanyTaxfreerate', domain: 'company', fn: selectCompanyTaxfreerate },
+ { name: 'selectCompanyRootCategoryId', domain: 'company', fn: selectCompanyRootCategoryId },
+ { name: 'selectNumbers', domain: 'numbers', fn: selectNumbers },
+ { name: 'selectNumbersStatus', domain: 'numbers', fn: selectNumbersStatus },
+ { name: 'selectPosterminals', domain: 'terminals', fn: selectPosterminals },
+ { name: 'selectPosterminalStatus', domain: 'terminals', fn: selectPosterminalStatus },
+ { name: 'selectHasPosterminals', domain: 'terminals', fn: selectHasPosterminals },
+ { name: 'selectSuppliers', domain: 'suppliers', fn: selectSuppliers },
+ { name: 'selectSuppliersStatus', domain: 'suppliers', fn: selectSuppliersStatus },
+ { name: 'selectUnits', domain: 'units', fn: selectUnits },
+ { name: 'selectUnitsStatus', domain: 'units', fn: selectUnitsStatus },
+ { name: 'selectChatDrawerWidth', domain: 'userSettings', fn: selectChatDrawerWidth },
+ { name: 'selectWarehouses', domain: 'warehouses', fn: selectWarehouses },
+ { name: 'selectWarehousesStatus', domain: 'warehouses', fn: selectWarehousesStatus },
+ { name: 'selectProperties', domain: 'properties', fn: selectProperties },
+ { name: 'selectPropertyStatus', domain: 'properties', fn: selectPropertyStatus },
+ { name: 'selectCustomergroups', domain: 'customergroups', fn: selectCustomergroups },
+ { name: 'selectCustomergroupsStatus', domain: 'customergroups', fn: selectCustomergroupsStatus },
+ { name: 'selectCarriers', domain: 'carriers', fn: selectCarriers },
+ { name: 'selectCarriersStatus', domain: 'carriers', fn: selectCarriersStatus },
+ { name: 'selectBrands', domain: 'brands', fn: selectBrands },
+ { name: 'selectBrandsStatus', domain: 'brands', fn: selectBrandsStatus },
+ { name: 'selectAttributes', domain: 'attributes', fn: selectAttributes },
+ { name: 'selectAttributeStatus', domain: 'attributes', fn: selectAttributeStatus },
+ { name: 'selectAccounts', domain: 'accounts', fn: selectAccounts },
+ { name: 'selectAccountsStatus', domain: 'accounts', fn: selectAccountsStatus },
+];
+// #endregion
+
+// #region formatting & serialization helpers
+function safeJsonStringify(value, space = 2) {
+ try {
+ return JSON.stringify(
+ value,
+ (_key, val) => {
+ if (val instanceof Error) {
+ return { name: val.name, message: val.message, stack: val.stack };
+ }
+ return val;
+ },
+ space,
+ );
+ } catch (err) {
+ return String(err);
+ }
+}
+
+function summarizeValue(val) {
+ if (val === null) return 'null';
+ if (val === undefined) return 'undefined';
+ if (typeof val === 'boolean' || typeof val === 'number') return String(val);
+ if (typeof val === 'string') return val === '' ? '"" (empty)' : `"${val}"`;
+ if (Array.isArray(val)) return `Array(${val.length})`;
+ if (val instanceof Error) return `Error: ${val.message}`;
+ if (typeof val === 'object') {
+ const keys = Object.keys(val);
+ if (val.name && val.id != null) return `{ id: ${val.id}, name: "${val.name}", … (${keys.length} keys) }`;
+ return `Object(${keys.length} keys)`;
+ }
+ return String(val);
+}
+
+function statusChipColor(status) {
+ if (status === 'ready' || status === 'succeeded' || status === 'authed') return 'success';
+ if (status === 'loading') return 'info';
+ if (status === 'failed') return 'error';
+ return 'default';
+}
+
+function parseJsonInput(raw, label = 'JSON') {
+ const trimmed = String(raw ?? '').trim();
+ if (!trimmed) throw new Error(`${label} darf nicht leer sein.`);
+ try {
+ return JSON.parse(trimmed);
+ } catch (err) {
+ throw new Error(`Ungültiges ${label}: ${err.message}`);
+ }
+}
+// #endregion
+
+export default class CompanyAutomatPage extends PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ // Live snapshots from companyAutomat and authAutomat
+ companyState: { ...companyAutomat.state },
+ authState: { ...authAutomat.state },
+ automatMeta: this.readAutomatMeta(),
+
+ // UI navigation & inspection state
+ activeDomain: 'company',
+ stateKeyFilter: '',
+ selectorDomainFilter: 'all',
+ expandedSelectors: {},
+ expandedStateKeys: { current: true },
+ showRawFullState: false,
+
+ // Live state transition log & action execution history
+ mutationLog: [],
+ actionHistory: [],
+ runningAction: null,
+
+ // Action input fields across all 13 domains + direct setState
+ companyUpdateJson: safeJsonStringify(companyAutomat.state.current || { name: 'Demo GmbH' }),
+ numbersReplaceJson: '[]',
+
+ terminalName: '',
+ terminalId: '',
+ terminalCategoryId: '',
+ terminalPatchJson: '{"name": "Kasse 1"}',
+
+ supplierId: '',
+ supplierBodyJson: '{"name": "Neuer Lieferant"}',
+ suppliersReplaceJson: '[]',
+
+ unitUpsertJson: '{"si": "pcs", "symbol": "Stk", "name": "Stück"}',
+ unitDeleteSi: '',
+
+ chatDrawerWidthInput: String(companyAutomat.state.chatDrawerWidth ?? 460),
+
+ warehouseId: '',
+ warehouseBodyJson: '{"name": "Hauptlager"}',
+ warehouseIdsJson: '[1, 2]',
+ warehousesReplaceJson: '[]',
+
+ propertyId: '',
+ propertyBodyJson: '{"name": "Gewicht", "exclusive": false}',
+ propertyIdsJson: '[1, 2]',
+
+ customergroupId: '',
+ customergroupBodyJson: '{"name": "Händler"}',
+ customergroupIdsJson: '[1, 2]',
+ customergroupsReplaceJson: '[]',
+
+ carrierId: '',
+ carrierBodyJson: '{"name": "DHL"}',
+ carrierIdsJson: '[1, 2]',
+ carriersReplaceJson: '[]',
+
+ brandId: '',
+ brandBodyJson: '{"name": "Neue Marke"}',
+ brandIdsJson: '[1, 2]',
+ brandSilentLoad: false,
+ brandImageFile: null,
+
+ attributeId: '',
+ attributeBodyJson: '{"name": "Farbe", "exclusive": false}',
+ attributeIdsJson: '[1, 2]',
+ attributeValueId: '',
+ attributeValueBodyJson: '{"value": "Rot"}',
+ attributeValueIdsJson: '[1, 2]',
+
+ accountsReplaceJson: '[]',
+
+ directSetStateKey: 'status',
+ directSetStateJson: '{"status": "ready"}',
+ };
+
+ this.runAction = this.runAction.bind(this);
+ this.handleDirectSetState = this.handleDirectSetState.bind(this);
+ this.handleCopyJson = this.handleCopyJson.bind(this);
+ }
+
+ // #region lifecycle & subscriptions
+ readAutomatMeta() {
+ return {
+ name: companyAutomat.name,
+ isReady: companyAutomat.isReady,
+ isDirty: companyAutomat.isDirty,
+ persist: companyAutomat.persist,
+ subscriberCount: companyAutomat.subscriberCount,
+ };
+ }
+
+ componentDidMount() {
+ this.unsubCompany = companyAutomat.subscribe((state, partial) => {
+ const changedKeys = partial && typeof partial === 'object' ? Object.keys(partial) : [];
+ const entry = {
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
+ timestamp: new Date().toLocaleTimeString('de-DE', {
+ hour12: false,
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+ fractionalSecondDigits: 3,
+ }),
+ keys: changedKeys,
+ partialSnapshot: partial ? safeJsonStringify(partial) : '{}',
+ };
+
+ this.setState((prev) => ({
+ companyState: { ...state },
+ automatMeta: this.readAutomatMeta(),
+ mutationLog: [entry, ...prev.mutationLog].slice(0, 60),
+ }));
+ });
+
+ this.unsubAuth = authAutomat.subscribe((authState) => {
+ this.setState({
+ authState: { ...authState },
+ automatMeta: this.readAutomatMeta(),
+ });
+ });
+
+ // Update subscriber count after mounting our listener
+ this.setState({ automatMeta: this.readAutomatMeta() });
+ }
+
+ componentWillUnmount() {
+ this.unsubCompany?.();
+ this.unsubAuth?.();
+ }
+ // #endregion
+
+ // #region action execution & helpers
+ // MARK: executes any companyAutomat action or method and records result/timing
+ async runAction(label, invokeFn, argsPreview = '') {
+ const startedAt = performance.now();
+ this.setState({ runningAction: label });
+ try {
+ const result = await invokeFn();
+ const durationMs = Math.round(performance.now() - startedAt);
+ const record = {
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
+ timestamp: new Date().toLocaleTimeString('de-DE', { hour12: false }),
+ label,
+ argsPreview,
+ status: 'fulfilled',
+ durationMs,
+ output: safeJsonStringify(result),
+ };
+ this.setState((prev) => ({
+ runningAction: null,
+ companyState: { ...companyAutomat.state },
+ automatMeta: this.readAutomatMeta(),
+ actionHistory: [record, ...prev.actionHistory].slice(0, 30),
+ }));
+ enqueueSnackbar(`${label} erfolgreich (${durationMs} ms)`, { variant: 'success' });
+ return result;
+ } catch (err) {
+ const durationMs = Math.round(performance.now() - startedAt);
+ const message = err?.message ?? String(err);
+ const record = {
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
+ timestamp: new Date().toLocaleTimeString('de-DE', { hour12: false }),
+ label,
+ argsPreview,
+ status: 'rejected',
+ durationMs,
+ output: message,
+ };
+ this.setState((prev) => ({
+ runningAction: null,
+ companyState: { ...companyAutomat.state },
+ automatMeta: this.readAutomatMeta(),
+ actionHistory: [record, ...prev.actionHistory].slice(0, 30),
+ }));
+ enqueueSnackbar(`${label} fehlgeschlagen: ${message}`, { variant: 'error' });
+ return undefined;
+ }
+ }
+
+ // MARK: directly patches companyAutomat.setState for testing
+ handleDirectSetState() {
+ void this.runAction(
+ 'companyAutomat.setState(partial)',
+ () => {
+ const parsed = parseJsonInput(this.state.directSetStateJson, 'Partial State JSON');
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
+ throw new Error('Partial State muss ein JSON-Objekt sein.');
+ }
+ return companyAutomat.setState(parsed);
+ },
+ this.state.directSetStateJson,
+ );
+ }
+
+ handleCopyJson(value, label = 'JSON') {
+ const text = typeof value === 'string' ? value : safeJsonStringify(value);
+ if (navigator?.clipboard?.writeText) {
+ void navigator.clipboard.writeText(text).then(() => {
+ enqueueSnackbar(`${label} in die Zwischenablage kopiert.`, { variant: 'info' });
+ });
+ }
+ }
+
+ toggleSelectorExpand(name) {
+ this.setState((prev) => ({
+ expandedSelectors: {
+ ...prev.expandedSelectors,
+ [name]: !prev.expandedSelectors[name],
+ },
+ }));
+ }
+
+ toggleStateKeyExpand(key) {
+ this.setState((prev) => ({
+ expandedStateKeys: {
+ ...prev.expandedStateKeys,
+ [key]: !prev.expandedStateKeys[key],
+ },
+ }));
+ }
+
+ loadKeyIntoSetStateEditor(key) {
+ const currentVal = this.state.companyState[key];
+ const payload = { [key]: currentVal };
+ this.setState({
+ activeDomain: 'setState',
+ directSetStateKey: key,
+ directSetStateJson: safeJsonStringify(payload),
+ });
+ }
+ // #endregion
+
+ // #region domain-specific input renderers
+ renderCompanyActions() {
+ const { companyUpdateJson, companyState } = this.state;
+ const act = companyAutomat.actions;
+
+ return (
+
+
+ }
+ onClick={() => this.runAction('actions.load()', () => act.load())}
+ >
+ actions.load()
+
+ }
+ onClick={() => this.runAction('actions.loadAll()', () => act.loadAll())}
+ >
+ actions.loadAll()
+
+ }
+ onClick={() => this.runAction('actions.reset()', () => act.reset())}
+ >
+ actions.reset()
+
+
+
+
+ this.setState({ companyUpdateJson: e.target.value })}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+ }
+ onClick={() =>
+ this.runAction(
+ 'actions.update(payload)',
+ () => act.update(parseJsonInput(this.state.companyUpdateJson, 'Company Payload')),
+ this.state.companyUpdateJson,
+ )
+ }
+ >
+ actions.update(payload) ausführen
+
+
+
+ );
+ }
+
+ renderNumbersActions() {
+ const { numbersReplaceJson, companyState } = this.state;
+ const act = companyAutomat.actions;
+
+ return (
+
+
+ }
+ onClick={() => this.runAction('actions.loadNumbers()', () => act.loadNumbers())}
+ >
+ actions.loadNumbers()
+
+ }
+ onClick={() => this.runAction('actions.reloadNumbers()', () => act.reloadNumbers())}
+ >
+ actions.reloadNumbers()
+
+
+
+
+
+ this.setState({ numbersReplaceJson: e.target.value })}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+ );
+ }
+
+ renderTerminalsActions() {
+ const { terminalName, terminalId, terminalCategoryId, terminalPatchJson } = this.state;
+ const act = companyAutomat.actions;
+
+ return (
+
+
+ }
+ onClick={() => this.runAction('actions.loadTerminals()', () => act.loadTerminals())}
+ >
+ actions.loadTerminals()
+
+
+
+
+
+ this.setState({ terminalName: e.target.value })}
+ />
+
+
+
+
+
+
+ this.setState({ terminalId: e.target.value })}
+ sx={{ width: 140 }}
+ />
+ this.setState({ terminalCategoryId: e.target.value })}
+ sx={{ width: 140 }}
+ />
+ this.setState({ terminalPatchJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ renderSuppliersActions() {
+ const { supplierId, supplierBodyJson, suppliersReplaceJson, companyState } = this.state;
+ const act = companyAutomat.actions;
+
+ return (
+
+
+ }
+ onClick={() => this.runAction('actions.loadSuppliers()', () => act.loadSuppliers())}
+ >
+ actions.loadSuppliers()
+
+ }
+ onClick={() => this.runAction('actions.reloadSuppliers()', () => act.reloadSuppliers())}
+ >
+ actions.reloadSuppliers()
+
+
+
+
+
+ this.setState({ supplierId: e.target.value })}
+ sx={{ width: 150 }}
+ />
+
+
+
+
+ this.setState({ supplierBodyJson: e.target.value })}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+
+
+
+
+ this.setState({ suppliersReplaceJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+ );
+ }
+
+ renderUnitsActions() {
+ const { unitUpsertJson, unitDeleteSi } = this.state;
+ const act = companyAutomat.actions;
+
+ return (
+
+
+ }
+ onClick={() => this.runAction('actions.loadUnits()', () => act.loadUnits())}
+ >
+ actions.loadUnits()
+
+
+
+ this.setState({ unitUpsertJson: e.target.value })}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+
+ this.setState({ unitDeleteSi: e.target.value })}
+ />
+
+
+
+ );
+ }
+
+ renderUserSettingsActions() {
+ const { chatDrawerWidthInput, companyState } = this.state;
+ const act = companyAutomat.actions;
+
+ return (
+
+
+
+ actions.setViewerPosition(position) — aktuell: "{companyState.viewerPosition}"
+
+
+ {['top', 'right', 'below'].map((pos) => (
+
+ ))}
+
+
+
+
+
+
+
+ actions.setChatDrawerWidth(width) — aktuell: {companyState.chatDrawerWidth}px (min. 280)
+
+
+ this.setState({ chatDrawerWidthInput: e.target.value })}
+ sx={{ width: 140 }}
+ />
+
+ {[360, 460, 600].map((preset) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ renderWarehousesActions() {
+ const {
+ warehouseId,
+ warehouseBodyJson,
+ warehouseIdsJson,
+ warehousesReplaceJson,
+ companyState,
+ } = this.state;
+ const act = companyAutomat.actions;
+
+ return (
+
+
+ act.filterWarehouses(q)}
+ />
+
+
+
+
+ }
+ onClick={() => this.runAction('actions.loadWarehouses()', () => act.loadWarehouses())}
+ >
+ actions.loadWarehouses()
+
+ }
+ onClick={() =>
+ this.runAction('actions.reloadWarehouses()', () => act.reloadWarehouses())
+ }
+ >
+ actions.reloadWarehouses()
+
+
+
+
+ this.setState({ warehouseId: e.target.value })}
+ sx={{ width: 140 }}
+ />
+ this.setState({ warehouseBodyJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+ this.setState({ warehouseIdsJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+ this.setState({ warehousesReplaceJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+ );
+ }
+
+ renderPropertiesActions() {
+ const { propertyId, propertyBodyJson, propertyIdsJson } = this.state;
+ const act = companyAutomat.actions;
+
+ return (
+
+
+ }
+ onClick={() => this.runAction('actions.loadProperties()', () => act.loadProperties())}
+ >
+ actions.loadProperties()
+
+
+
+
+ this.setState({ propertyId: e.target.value })}
+ sx={{ width: 140 }}
+ />
+ this.setState({ propertyBodyJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+
+
+
+
+
+ this.setState({ propertyIdsJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+ );
+ }
+
+ renderCustomergroupsActions() {
+ const {
+ customergroupId,
+ customergroupBodyJson,
+ customergroupIdsJson,
+ customergroupsReplaceJson,
+ companyState,
+ } = this.state;
+ const act = companyAutomat.actions;
+
+ return (
+
+
+ act.filterCustomergroups(q)}
+ />
+
+
+
+
+ }
+ onClick={() =>
+ this.runAction('actions.loadCustomergroups()', () => act.loadCustomergroups())
+ }
+ >
+ loadCustomergroups()
+
+ }
+ onClick={() =>
+ this.runAction('actions.reloadCustomergroups()', () => act.reloadCustomergroups())
+ }
+ >
+ reloadCustomergroups()
+
+
+
+
+
+ this.setState({ customergroupId: e.target.value })}
+ sx={{ width: 150 }}
+ />
+ this.setState({ customergroupBodyJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+ this.setState({ customergroupIdsJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+ this.setState({ customergroupsReplaceJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+ );
+ }
+
+ renderCarriersActions() {
+ const {
+ carrierId,
+ carrierBodyJson,
+ carrierIdsJson,
+ carriersReplaceJson,
+ companyState,
+ } = this.state;
+ const act = companyAutomat.actions;
+
+ return (
+
+
+ act.filterCarriers(q)}
+ />
+
+
+
+
+ }
+ onClick={() => this.runAction('actions.loadCarriers()', () => act.loadCarriers())}
+ >
+ actions.loadCarriers()
+
+ }
+ onClick={() => this.runAction('actions.reloadCarriers()', () => act.reloadCarriers())}
+ >
+ actions.reloadCarriers()
+
+
+
+
+ this.setState({ carrierId: e.target.value })}
+ sx={{ width: 140 }}
+ />
+ this.setState({ carrierBodyJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+
+
+
+
+
+ this.setState({ carrierIdsJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+ this.setState({ carriersReplaceJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+ );
+ }
+
+ renderBrandsActions() {
+ const {
+ brandId,
+ brandBodyJson,
+ brandIdsJson,
+ brandImageFile,
+ companyState,
+ } = this.state;
+ const act = companyAutomat.actions;
+
+ return (
+
+
+ act.filterBrands(q)}
+ />
+
+
+
+
+ }
+ onClick={() => this.runAction('actions.loadBrands(false)', () => act.loadBrands(false))}
+ >
+ actions.loadBrands(false)
+
+ }
+ onClick={() => this.runAction('actions.loadBrands(true)', () => act.loadBrands(true))}
+ >
+ actions.loadBrands(true) (silent)
+
+
+
+
+ this.setState({ brandId: e.target.value })}
+ sx={{ width: 140 }}
+ />
+ this.setState({ brandBodyJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+ }
+ >
+ {brandImageFile ? brandImageFile.name : 'Bilddatei wählen…'}
+ this.setState({ brandImageFile: e.target.files?.[0] || null })}
+ />
+
+
+
+
+
+ this.setState({ brandIdsJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+ );
+ }
+
+ renderAttributesActions() {
+ const {
+ attributeId,
+ attributeBodyJson,
+ attributeIdsJson,
+ attributeValueId,
+ attributeValueBodyJson,
+ attributeValueIdsJson,
+ } = this.state;
+ const act = companyAutomat.actions;
+
+ return (
+
+
+ }
+ onClick={() => this.runAction('actions.loadAttributes()', () => act.loadAttributes())}
+ >
+ actions.loadAttributes()
+
+
+
+
+ Attribute (attributename)
+
+
+ this.setState({ attributeId: e.target.value })}
+ sx={{ width: 140 }}
+ />
+ this.setState({ attributeBodyJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+
+
+
+
+
+ this.setState({ attributeIdsJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+
+
+
+ Attribute Values (attributevalue)
+
+
+ this.setState({ attributeValueId: e.target.value })}
+ sx={{ width: 140 }}
+ />
+ this.setState({ attributeValueBodyJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+
+
+
+
+
+ this.setState({ attributeValueIdsJson: e.target.value })}
+ sx={{ flex: 1 }}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+ );
+ }
+
+ renderAccountsActions() {
+ const { accountsReplaceJson, companyState } = this.state;
+ const act = companyAutomat.actions;
+
+ return (
+
+
+ }
+ onClick={() => this.runAction('actions.loadAccounts()', () => act.loadAccounts())}
+ >
+ actions.loadAccounts()
+
+ }
+ onClick={() => this.runAction('actions.reloadAccounts()', () => act.reloadAccounts())}
+ >
+ actions.reloadAccounts()
+
+
+
+
+
+ this.setState({ accountsReplaceJson: e.target.value })}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+
+
+ );
+ }
+
+ renderDirectSetStateActions() {
+ const { directSetStateKey, directSetStateJson, companyState } = this.state;
+ const allKeys = Object.keys(companyState);
+
+ return (
+
+
+ Direktes Patchen von companyAutomat.setState(partial) zum Testen reaktiver
+ Subscriber und UI-Zustände:
+
+
+
+ {allKeys.map((key) => (
+ this.loadKeyIntoSetStateEditor(key)}
+ sx={{ cursor: 'pointer', fontFamily: 'monospace', fontSize: '0.74rem' }}
+ />
+ ))}
+
+
+ this.setState({ directSetStateJson: e.target.value })}
+ slotProps={{
+ input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } },
+ }}
+ />
+
+
+ }
+ onClick={this.handleDirectSetState}
+ >
+ companyAutomat.setState(partial) ausführen
+
+ }
+ onClick={() => this.handleCopyJson(directSetStateJson, 'Partial State')}
+ >
+ JSON kopieren
+
+
+
+ );
+ }
+
+ renderActiveDomainInputs() {
+ switch (this.state.activeDomain) {
+ case 'company':
+ return this.renderCompanyActions();
+ case 'numbers':
+ return this.renderNumbersActions();
+ case 'terminals':
+ return this.renderTerminalsActions();
+ case 'suppliers':
+ return this.renderSuppliersActions();
+ case 'units':
+ return this.renderUnitsActions();
+ case 'userSettings':
+ return this.renderUserSettingsActions();
+ case 'warehouses':
+ return this.renderWarehousesActions();
+ case 'properties':
+ return this.renderPropertiesActions();
+ case 'customergroups':
+ return this.renderCustomergroupsActions();
+ case 'carriers':
+ return this.renderCarriersActions();
+ case 'brands':
+ return this.renderBrandsActions();
+ case 'attributes':
+ return this.renderAttributesActions();
+ case 'accounts':
+ return this.renderAccountsActions();
+ case 'setState':
+ return this.renderDirectSetStateActions();
+ default:
+ return null;
+ }
+ }
+ // #endregion
+
+ // #region main render
+ render() {
+ const {
+ companyState,
+ authState,
+ automatMeta,
+ activeDomain,
+ stateKeyFilter,
+ selectorDomainFilter,
+ expandedSelectors,
+ expandedStateKeys,
+ showRawFullState,
+ mutationLog,
+ actionHistory,
+ runningAction,
+ } = this.state;
+
+ const filteredSelectors = SELECTOR_DEFINITIONS.filter(
+ (sel) => selectorDomainFilter === 'all' || sel.domain === selectorDomainFilter,
+ );
+
+ const stateEntries = Object.entries(companyState).filter(([key]) =>
+ stateKeyFilter
+ ? key.toLowerCase().includes(stateKeyFilter.trim().toLowerCase())
+ : true,
+ );
+
+ return (
+
+ {/* 1. Overview & Automat Lifecycle + Domain Matrix */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Upstream: authAutomat
+
+
+
+
+
+
+
+
+
+
+ Globale Steuerung (Inputs)
+
+
+ }
+ disabled={Boolean(runningAction)}
+ onClick={() =>
+ this.runAction('companyAutomat.actions.loadAll()', () =>
+ companyAutomat.actions.loadAll(),
+ )
+ }
+ >
+ loadAll()
+
+ }
+ disabled={Boolean(runningAction)}
+ onClick={() =>
+ this.runAction('companyAutomat.actions.load()', () =>
+ companyAutomat.actions.load(),
+ )
+ }
+ >
+ load()
+
+ }
+ disabled={Boolean(runningAction)}
+ onClick={() =>
+ this.runAction('companyAutomat.actions.reset()', () =>
+ companyAutomat.actions.reset(),
+ )
+ }
+ >
+ reset()
+
+
+
+
+
+
+
+
+
+
+ {DOMAIN_DEFINITIONS.map((dom) => {
+ const status = dom.statusKey ? companyState[dom.statusKey] : 'ready';
+ const err = dom.errorKey ? companyState[dom.errorKey] : null;
+ const data = companyState[dom.dataKey];
+ const isSelected = activeDomain === dom.id;
+
+ return (
+ this.setState({ activeDomain: dom.id })}
+ sx={{
+ p: 1.25,
+ borderRadius: 1.5,
+ bgcolor: isSelected ? SELECT_BG : SURFACE_BASE,
+ border: '1px solid',
+ borderColor: isSelected ? SELECT_BORDER : BORDER,
+ cursor: 'pointer',
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'space-between',
+ gap: 0.75,
+ }}
+ >
+
+
+ {dom.label}
+
+
+
+
+
+
+ {dom.dataKey}: {summarizeValue(data)}
+
+
+ {dom.loadAction && (
+
+ )}
+
+
+ {err ? (
+
+ Error: {err?.message ?? String(err)}
+
+ ) : null}
+
+ );
+ })}
+
+
+
+
+ {/* 2. Interactive Inputs (Actions & setState) + Action Execution Log */}
+
+
+
+
+ {DOMAIN_DEFINITIONS.map((dom) => (
+ this.setState({ activeDomain: dom.id })}
+ sx={{ cursor: 'pointer', fontWeight: activeDomain === dom.id ? 700 : 500 }}
+ />
+ ))}
+ this.setState({ activeDomain: 'setState' })}
+ sx={{ cursor: 'pointer', fontWeight: activeDomain === 'setState' ? 700 : 500 }}
+ />
+
+
+
+
+
+ {this.renderActiveDomainInputs()}
+
+
+
+
+
+
+
+
+ {actionHistory.length} Aufrufe protokolliert
+
+ }
+ disabled={!actionHistory.length}
+ onClick={() => this.setState({ actionHistory: [] })}
+ >
+ Leeren
+
+
+
+ {actionHistory.length === 0 ? (
+
+ Noch keine Action über das Debug-Panel ausgeführt.
+
+ ) : (
+
+ {actionHistory.map((item) => (
+
+
+
+ {item.label}
+
+
+
+
+ {item.timestamp}
+
+
+
+
+ {item.argsPreview ? (
+
+ args: {item.argsPreview}
+
+ ) : null}
+
+
+ {item.output}
+
+
+ ))}
+
+ )}
+
+
+
+
+ {/* 3. Outputs: Exported Selectors Live Evaluation */}
+
+
+
+
+ this.setState({ selectorDomainFilter: 'all' })}
+ sx={{ cursor: 'pointer' }}
+ />
+ {DOMAIN_DEFINITIONS.map((dom) => (
+ this.setState({ selectorDomainFilter: dom.id })}
+ sx={{ cursor: 'pointer', fontFamily: 'monospace' }}
+ />
+ ))}
+
+
+
+ {filteredSelectors.map((sel) => {
+ let val;
+ let threw = null;
+ try {
+ val = sel.fn();
+ } catch (err) {
+ threw = err;
+ }
+ const expanded = Boolean(expandedSelectors[sel.name]);
+ const isComplex = val !== null && typeof val === 'object';
+
+ return (
+
+
+
+ {sel.name}()
+
+
+
+ {isComplex && (
+
+ )}
+
+
+
+
+ → {threw ? `Throws: ${threw.message}` : summarizeValue(val)}
+
+
+ {expanded && isComplex && (
+
+ {safeJsonStringify(val)}
+
+ )}
+
+ );
+ })}
+
+
+
+
+
+ {/* 4. Internal State Browser & Live Subscription Event Log */}
+
+
+
+
+ this.setState({ stateKeyFilter: val })}
+ />
+
+
+ }
+ onClick={() => this.handleCopyJson(companyState, 'companyAutomat.state')}
+ >
+ State kopieren
+
+
+
+
+ {showRawFullState ? (
+
+ {safeJsonStringify(companyState)}
+
+ ) : (
+
+ {stateEntries.map(([key, value]) => {
+ const expanded = Boolean(expandedStateKeys[key]);
+ const isComplex = value !== null && typeof value === 'object';
+
+ return (
+
+
+
+
+ {key}
+
+
+
+
+
+
+
+ {isComplex && (
+
+ )}
+
+
+
+ {expanded && isComplex && (
+
+ {safeJsonStringify(value)}
+
+ )}
+
+ );
+ })}
+
+ )}
+
+
+
+
+
+
+
+ {mutationLog.length} State-Emissionen empfangen
+
+ }
+ disabled={!mutationLog.length}
+ onClick={() => this.setState({ mutationLog: [] })}
+ >
+ Leeren
+
+
+
+ {mutationLog.length === 0 ? (
+
+ Warte auf State-Änderungen an companyAutomat…
+
+ ) : (
+
+ {mutationLog.map((entry) => (
+
+
+
+ {entry.keys.length > 0 ? (
+ entry.keys.map((k) => (
+
+ ))
+ ) : (
+
+ )}
+
+
+ {entry.timestamp}
+
+
+
+
+ {entry.partialSnapshot}
+
+
+ ))}
+
+ )}
+
+
+
+
+ );
+ }
+ // #endregion
+}