From 23e4a0951c8cfb987adef0a20734fd0ae97c9136 Mon Sep 17 00:00:00 2001 From: seb Date: Wed, 28 Oct 2026 00:27:12 +0100 Subject: [PATCH] u --- src/appRouter.jsx | 16 + src/nav.js | 30 + src/routes/debug/CompanyAutomatPage.jsx | 3354 +++++++++++++++++++++++ 3 files changed, 3400 insertions(+) create mode 100644 src/routes/debug/CompanyAutomatPage.jsx 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 ( + + + + + + + + + this.setState({ companyUpdateJson: e.target.value })} + slotProps={{ + input: { sx: { fontFamily: 'monospace', fontSize: '0.8rem' } }, + }} + /> + + + + + ); + } + + renderNumbersActions() { + const { numbersReplaceJson, companyState } = this.state; + const act = companyAutomat.actions; + + return ( + + + + + + + + + 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 ( + + + + + + + + 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 ( + + + + + + + + + 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 ( + + + + + + 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)} + /> + + + + + + + + + + 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 ( + + + + + + + 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)} + /> + + + + + + + + + + + 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)} + /> + + + + + + + + + + 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)} + /> + + + + + + + + + + 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' } }, + }} + /> + + + + + + + + + + + + + + + + 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 ( + + + + + + + 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 ( + + + + + + + + + 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' } }, + }} + /> + + + + + + + ); + } + + 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) + + + + + + + + + + + + + + + {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 + + + + + {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 })} + /> + + + + + + + {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 + + + + + {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 +}