This commit is contained in:
seb
2026-10-30 16:22:03 +01:00
parent 1b60965cea
commit 28d6fece46
3 changed files with 122 additions and 13 deletions

View File

@@ -19,12 +19,29 @@ import PointOfSaleIcon from '@mui/icons-material/PointOfSale';
import { enqueueSnackbar } from 'notistack';
import BackendError from '../BackendError';
import CategoryPickerDialog from '../article/CategoryPickerDialog';
import authAutomat from '../../automats/authAutomat';
import companyAutomat from '../../automats/companyAutomat';
import categoryAutomat, { whenCategoryTreeReady } from '../../automats/categoryAutomat';
import { categoryDisplayName } from '../../utils/categoryUtils';
import { backendErrorFrom } from '../../utils/backendError';
import { notifyError } from '../../utils/notify';
function parseVhostSettings(str) {
if (!str || typeof str !== 'string') return {};
const result = {};
for (const part of str.split(';')) {
const trimmed = part.trim();
if (!trimmed) continue;
const colonIdx = trimmed.indexOf(':');
if (colonIdx !== -1) {
const key = trimmed.slice(0, colonIdx).trim().toLowerCase();
const val = trimmed.slice(colonIdx + 1).trim();
result[key] = val;
}
}
return result;
}
function formatDateTime(val) {
if (!val) return null;
try {
@@ -52,6 +69,7 @@ export default class PosTerminalSettingsPanel extends Component {
terminals,
categories: categoryAutomat.state.categories,
rootCategoryId: categoryAutomat.state.rootCategoryId,
vhostSettings: authAutomat.state.vhostSettings,
newTerminalName: '',
creatingTerminal: false,
enableAllForNew: true,
@@ -82,6 +100,9 @@ export default class PosTerminalSettingsPanel extends Component {
categories: state.categories,
rootCategoryId: state.rootCategoryId,
}));
this.unsubAuth = authAutomat.subscribe(this, (state) => ({
vhostSettings: state.vhostSettings,
}));
if (categoryAutomat.state.treeStatus === 'idle') {
void categoryAutomat.actions.loadTree();
}
@@ -90,6 +111,7 @@ export default class PosTerminalSettingsPanel extends Component {
componentWillUnmount() {
this.unsub?.();
this.unsubCategory?.();
this.unsubAuth?.();
}
async loadTerminals() {
@@ -275,6 +297,7 @@ export default class PosTerminalSettingsPanel extends Component {
status,
error,
terminals,
vhostSettings,
newTerminalName,
creatingTerminal,
enableAllForNew,
@@ -284,6 +307,11 @@ export default class PosTerminalSettingsPanel extends Component {
rootCategoryId,
} = this.state;
const parsedVhost = parseVhostSettings(vhostSettings);
const posIp = parsedVhost.posip || parsedVhost.ip || null;
const posPort = parsedVhost.posport || parsedVhost.port || null;
const hasVhostSettings = Boolean(posIp || posPort || vhostSettings);
if (status === 'loading' && terminals.length === 0) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 6 }}>
@@ -298,6 +326,36 @@ export default class PosTerminalSettingsPanel extends Component {
return (
<Box sx={{ maxWidth: 680, mx: 'auto', py: 2 }}>
{hasVhostSettings && (
<Box
sx={{
mb: 2,
p: 1.5,
px: 2,
borderRadius: 2,
bgcolor: 'action.hover',
border: '1px solid',
borderColor: 'divider',
display: 'flex',
alignItems: 'center',
gap: 4,
flexWrap: 'wrap',
}}
>
<Typography variant="body2" sx={{ color: 'text.secondary', fontWeight: 500 }}>
Pos Sync IP:{' '}
<Box component="span" sx={{ color: 'text.primary', fontFamily: 'monospace', fontWeight: 600 }}>
{posIp ?? '—'}
</Box>
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary', fontWeight: 500 }}>
Pos Sync Port:{' '}
<Box component="span" sx={{ color: 'text.primary', fontFamily: 'monospace', fontWeight: 600 }}>
{posPort ?? '—'}
</Box>
</Typography>
</Box>
)}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<TextField
size="small"

View File

@@ -53,18 +53,34 @@ const TABS = [
{ id: TAB_KEYS.security, label: 'Sicherheit & MCP', icon: <SecurityIcon />, Panel: McpAuthorizationsPanel },
];
function getDefaultTab(vhost) {
return vhost ? TAB_KEYS.company : TAB_KEYS.health;
}
function getVisibleTabs(vhost) {
if (!vhost) return TABS;
return TABS.filter(
(t) => t.id !== TAB_KEYS.health && t.id !== TAB_KEYS.backup && t.id !== TAB_KEYS.printers
);
}
export default class SettingsDialog extends Component {
constructor(props) {
super(props);
const vhost = Boolean(authAutomat.state.vhost);
this.state = {
tab: TAB_KEYS.health,
tab: getDefaultTab(vhost),
schemaVersion: authAutomat.state.schemaVersion,
vhost,
};
this.handleTabChange = this.handleTabChange.bind(this);
}
componentDidMount() {
this.unsubAuth = authAutomat.subscribe(this, ['schemaVersion']);
this.unsubAuth = authAutomat.subscribe(this, (state) => ({
schemaVersion: state.schemaVersion,
vhost: Boolean(state.vhost),
}));
}
componentWillUnmount() {
@@ -73,12 +89,29 @@ export default class SettingsDialog extends Component {
componentDidUpdate(prevProps) {
const { open } = this.props;
const { vhost } = this.state;
if (!open && prevProps.open) {
this.setState({ tab: TAB_KEYS.health });
this.setState({ tab: getDefaultTab(vhost) });
}
if (open && !prevProps.open) {
const currentVhost = Boolean(authAutomat.state.vhost);
const updates = {};
if (this.state.schemaVersion !== authAutomat.state.schemaVersion) {
this.setState({ schemaVersion: authAutomat.state.schemaVersion });
updates.schemaVersion = authAutomat.state.schemaVersion;
}
if (this.state.vhost !== currentVhost) {
updates.vhost = currentVhost;
}
if (
currentVhost &&
(this.state.tab === TAB_KEYS.health ||
this.state.tab === TAB_KEYS.backup ||
this.state.tab === TAB_KEYS.printers)
) {
updates.tab = getDefaultTab(currentVhost);
}
if (Object.keys(updates).length > 0) {
this.setState(updates);
}
}
}
@@ -89,7 +122,9 @@ export default class SettingsDialog extends Component {
render() {
const { open, onClose } = this.props;
const { tab, schemaVersion } = this.state;
const { tab, schemaVersion, vhost } = this.state;
const visibleTabs = getVisibleTabs(vhost);
const activeTab = visibleTabs.some((t) => t.id === tab) ? tab : visibleTabs[0]?.id;
const paperSx = {
height: '85vh',
@@ -146,7 +181,7 @@ export default class SettingsDialog extends Component {
>
<Tabs
orientation="vertical"
value={tab}
value={activeTab}
onChange={this.handleTabChange}
sx={{
'& .MuiTabs-indicator': {
@@ -182,7 +217,7 @@ export default class SettingsDialog extends Component {
},
}}
>
{TABS.map((t) => (
{visibleTabs.map((t) => (
<Tab
key={t.id}
disableRipple
@@ -205,15 +240,15 @@ export default class SettingsDialog extends Component {
{/* Right content panel */}
<DialogContent sx={{ flex: 1, minHeight: 0, p: 3, overflowY: 'auto' }}>
<Box sx={{ display: 'grid' }}>
{TABS.map((t) => (
{visibleTabs.map((t) => (
<Box
key={t.id}
sx={{
gridArea: '1 / 1',
display: t.id === tab ? 'block' : 'none',
display: t.id === activeTab ? 'block' : 'none',
}}
>
<t.Panel active={t.id === tab} />
<t.Panel active={t.id === activeTab} />
</Box>
))}
</Box>