diff --git a/src/api/posterminal.js b/src/api/posterminal.js
index 0aa4f00..a30bda9 100644
--- a/src/api/posterminal.js
+++ b/src/api/posterminal.js
@@ -2,8 +2,8 @@ import { http } from './http.js';
export const posterminal = {
/**
- * GET /api/posterminal → { posterminals: [{ id, name, token, pairingpin }] }
- * List all terminals for the selected company.
+ * GET /api/posterminal → { posterminals: [{ id, name, token, pairingpin, sort, enabled, clientversion, ... }] }
+ * List all terminals for the selected company, sorted by sort ASC, id ASC.
*/
// #region GET /api/posterminal — list
list() {
@@ -13,24 +13,29 @@ export const posterminal = {
/**
* PUT /api/posterminal
- * Body: { name: string }
+ * Body: { name: string, sort?: number, enabled?: boolean, rootCategoryId?: number|null, syncRootArticles?: boolean }
* Creates a new terminal (auto-generates 6-digit pin).
- * → 200 { posterminal: { id, name, token, pairingpin } }
+ * → 200 { posterminal: { id, name, token, pairingpin, sort, enabled, clientversion, ... } }
*/
// #region PUT /api/posterminal — create
- create({ name }) {
+ create({ name, sort, enabled, rootCategoryId, syncRootArticles } = {}) {
+ const body = { name };
+ if (sort !== undefined) body.sort = Number(sort);
+ if (enabled !== undefined) body.enabled = Boolean(enabled);
+ if (rootCategoryId !== undefined) body.rootCategoryId = rootCategoryId != null ? Number(rootCategoryId) : null;
+ if (syncRootArticles !== undefined) body.syncRootArticles = Boolean(syncRootArticles);
return http('/api/posterminal', {
method: 'PUT',
- body: { name },
+ body,
});
},
// #endregion
/**
* PATCH /api/posterminal/:id
- * Body: { name?: string, rootCategoryId?: number|null, syncRootArticles?: boolean, pairingpin?: string }
+ * Body: { name?: string, rootCategoryId?: number|null, syncRootArticles?: boolean, pairingpin?: string, sort?: number, enabled?: boolean }
* Update posterminal configuration.
- * → 200 { posterminal }
+ * → 200 { posterminal: { id, name, token, pairingpin, sort, enabled, clientversion, ... } }
*/
// #region PATCH /api/posterminal/:id — update
update(terminalId, patch) {
diff --git a/src/automats/companyAutomat.js b/src/automats/companyAutomat.js
index 657b328..eb2b243 100644
--- a/src/automats/companyAutomat.js
+++ b/src/automats/companyAutomat.js
@@ -159,6 +159,15 @@ const sortBrands = (brands) =>
const sortGroups = (items) =>
[...items].sort((a, b) => (Number(a.sort) || 0) - (Number(b.sort) || 0));
+const sortTerminals = (terminals) => {
+ if (!Array.isArray(terminals)) return [];
+ return [...terminals].sort((a, b) => {
+ const sDiff = (Number(a.sort) || 0) - (Number(b.sort) || 0);
+ if (sDiff !== 0) return sDiff;
+ return (Number(a.id) || 0) - (Number(b.id) || 0);
+ });
+};
+
const sortAccounts = (items) =>
[...items].sort((a, b) => String(a.name ?? '').localeCompare(String(b.name ?? ''), 'de'));
@@ -578,7 +587,7 @@ const actions = {
inflightTerminals = (async () => {
try {
const res = await posterminalApi.list();
- const terminals = Array.isArray(res.posterminals) ? res.posterminals : [];
+ const terminals = sortTerminals(Array.isArray(res.posterminals) ? res.posterminals : []);
companyAutomat.setState({
terminals,
terminalsCompanyId: companyId,
@@ -603,11 +612,15 @@ const actions = {
return actions.loadTerminals();
},
- async createTerminal({ name }) {
- const res = await posterminalApi.create({ name });
+ reloadTerminals() {
+ return actions.loadTerminals();
+ },
+
+ async createTerminal(payload) {
+ const res = await posterminalApi.create(payload);
const terminal = res.posterminal;
companyAutomat.setState({
- terminals: [...companyAutomat.state.terminals, terminal],
+ terminals: sortTerminals([...companyAutomat.state.terminals, terminal]),
});
return terminal;
},
@@ -620,8 +633,10 @@ const actions = {
const res = await posterminalApi.update(terminalId, patch);
const updated = res.posterminal;
companyAutomat.setState({
- terminals: companyAutomat.state.terminals.map((t) =>
- t.id === terminalId ? { ...t, ...updated } : t,
+ terminals: sortTerminals(
+ companyAutomat.state.terminals.map((t) =>
+ t.id === terminalId ? { ...t, ...updated } : t,
+ ),
),
});
return updated;
diff --git a/src/components/settings/PosTerminalSettingsPanel.jsx b/src/components/settings/PosTerminalSettingsPanel.jsx
index 2bb648c..2fb5ed8 100644
--- a/src/components/settings/PosTerminalSettingsPanel.jsx
+++ b/src/components/settings/PosTerminalSettingsPanel.jsx
@@ -11,6 +11,8 @@ import TextField from '@mui/material/TextField';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import AddIcon from '@mui/icons-material/Add';
+import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
+import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
import FolderIcon from '@mui/icons-material/Folder';
import PointOfSaleIcon from '@mui/icons-material/PointOfSale';
@@ -56,6 +58,7 @@ export default class PosTerminalSettingsPanel extends Component {
deletingId: null,
pickerTerminal: null,
patchingTerminalId: null,
+ sortDrafts: {},
};
this.handleRetry = this.handleRetry.bind(this);
this.loadTerminals = this.loadTerminals.bind(this);
@@ -63,6 +66,10 @@ export default class PosTerminalSettingsPanel extends Component {
this.handleDeleteTerminal = this.handleDeleteTerminal.bind(this);
this.handleSetRootCategory = this.handleSetRootCategory.bind(this);
this.handleToggleSyncRootArticles = this.handleToggleSyncRootArticles.bind(this);
+ this.handleToggleEnabled = this.handleToggleEnabled.bind(this);
+ this.handleSortDraftChange = this.handleSortDraftChange.bind(this);
+ this.handleSortSubmit = this.handleSortSubmit.bind(this);
+ this.handleMoveTerminal = this.handleMoveTerminal.bind(this);
this.getCategoryLabel = this.getCategoryLabel.bind(this);
}
@@ -104,13 +111,20 @@ export default class PosTerminalSettingsPanel extends Component {
}
async handleCreateTerminal() {
- const { newTerminalName, enableAllForNew } = this.state;
+ const { newTerminalName, enableAllForNew, terminals } = this.state;
const trimmed = newTerminalName.trim();
if (!trimmed) return;
this.setState({ creatingTerminal: true });
try {
- const created = await companyAutomat.actions.createTerminal({ name: trimmed });
+ const nextSort = terminals.length > 0
+ ? Math.max(...terminals.map((t) => Number(t.sort) || 0)) + 1
+ : 0;
+ const created = await companyAutomat.actions.createTerminal({
+ name: trimmed,
+ sort: nextSort,
+ enabled: true,
+ });
if (enableAllForNew && created?.id != null) {
await companyAutomat.actions.enableAllTerminalCategories({ terminalId: created.id });
@@ -126,6 +140,84 @@ export default class PosTerminalSettingsPanel extends Component {
}
}
+ async handleToggleEnabled(terminal, nextValue) {
+ this.setState({ patchingTerminalId: terminal.id });
+ try {
+ await companyAutomat.actions.updateTerminal(terminal.id, {
+ enabled: Boolean(nextValue),
+ });
+ enqueueSnackbar(nextValue ? 'Terminal aktiviert.' : 'Terminal deaktiviert.', {
+ variant: 'success',
+ });
+ } catch (err) {
+ notifyError(err);
+ } finally {
+ this.setState({ patchingTerminalId: null });
+ }
+ }
+
+ handleSortDraftChange(terminalId, value) {
+ this.setState((prev) => ({
+ sortDrafts: { ...prev.sortDrafts, [terminalId]: value },
+ }));
+ }
+
+ async handleSortSubmit(terminal) {
+ const draft = this.state.sortDrafts[terminal.id];
+ if (draft === undefined) return;
+ const nextSort = Number(draft);
+ if (isNaN(nextSort) || nextSort === (terminal.sort ?? 0)) {
+ this.setState((prev) => {
+ const next = { ...prev.sortDrafts };
+ delete next[terminal.id];
+ return { sortDrafts: next };
+ });
+ return;
+ }
+ this.setState({ patchingTerminalId: terminal.id });
+ try {
+ await companyAutomat.actions.updateTerminal(terminal.id, { sort: nextSort });
+ enqueueSnackbar('Reihenfolge aktualisiert.', { variant: 'success' });
+ } catch (err) {
+ notifyError(err);
+ } finally {
+ this.setState((prev) => {
+ const next = { ...prev.sortDrafts };
+ delete next[terminal.id];
+ return { sortDrafts: next, patchingTerminalId: null };
+ });
+ }
+ }
+
+ async handleMoveTerminal(terminal, direction) {
+ const { terminals } = this.state;
+ const currentIndex = terminals.findIndex((t) => t.id === terminal.id);
+ if (currentIndex === -1) return;
+ const targetIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;
+ if (targetIndex < 0 || targetIndex >= terminals.length) return;
+
+ const other = terminals[targetIndex];
+ const currentSort = terminal.sort ?? 0;
+ const otherSort = other.sort ?? 0;
+
+ let newSort;
+ if (direction === 'up') {
+ newSort = otherSort <= currentSort ? otherSort - 1 : otherSort;
+ } else {
+ newSort = otherSort >= currentSort ? otherSort + 1 : otherSort;
+ }
+
+ this.setState({ patchingTerminalId: terminal.id });
+ try {
+ await companyAutomat.actions.updateTerminal(terminal.id, { sort: newSort });
+ enqueueSnackbar('Reihenfolge aktualisiert.', { variant: 'success' });
+ } catch (err) {
+ notifyError(err);
+ } finally {
+ this.setState({ patchingTerminalId: null });
+ }
+ }
+
async handleDeleteTerminal(terminal) {
this.setState({ deletingId: terminal.id });
try {
@@ -261,9 +353,10 @@ export default class PosTerminalSettingsPanel extends Component {
) : (
- {terminals.map((terminal) => {
+ {terminals.map((terminal, index) => {
const isDeleting = deletingId === terminal.id;
const isPatching = patchingTerminalId === terminal.id;
+ const isEnabled = terminal.enabled !== false && terminal.enabled !== 0 && terminal.enabled != null;
const pairedDateText = formatDateTime(terminal.pairedAt);
const lastseenText = formatDateTime(terminal.lastseenAt) ?? 'Nie';
const updatedText = formatDateTime(terminal.updatedAt) ?? '—';
@@ -275,18 +368,24 @@ export default class PosTerminalSettingsPanel extends Component {
p: 2,
borderRadius: 2,
border: '1px solid',
- borderColor: 'divider',
+ borderColor: isEnabled ? 'divider' : 'action.disabledBackground',
bgcolor: 'background.paper',
display: 'flex',
flexDirection: 'column',
gap: 1.5,
+ opacity: isEnabled ? 1 : 0.72,
+ transition: 'opacity 0.2s ease, border-color 0.2s ease',
}}
>
- {/* Header: Icon, Name, PIN, Delete action */}
+ {/* Header: Icon, Name, PIN, Version, Status Switch, Delete action */}
-
-
+
+
{terminal.name}
{terminal.pairingpin ? (
@@ -310,23 +409,68 @@ export default class PosTerminalSettingsPanel extends Component {
({terminal.clientserial})
) : null}
+ {terminal.clientversion ? (
+
+
+
+ ) : null}
+
+
+
+ void this.handleToggleEnabled(terminal, e.target.checked)}
+ color="success"
+ />
+ }
+ label={
+
+ {isEnabled ? 'Aktiv' : 'Inaktiv'}
+
+ }
+ sx={{ m: 0 }}
+ />
+
+ void this.handleDeleteTerminal(terminal)}
+ disabled={isDeleting || creatingTerminal || isPatching}
+ color="error"
+ >
+ {isDeleting ? (
+
+ ) : (
+
+ )}
+
- void this.handleDeleteTerminal(terminal)}
- disabled={isDeleting || creatingTerminal || isPatching}
- color="error"
- >
- {isDeleting ? (
-
- ) : (
-
- )}
-
- {/* Settings row: Root category picker and syncRootArticles toggle */}
+ {/* Settings row: Root category picker, sort order controls, and syncRootArticles toggle */}
- void this.handleToggleSyncRootArticles(terminal, e.target.checked)}
- />
- }
- label={
-
- Root-Artikel synchronisieren
+
+
+
+ Reihenfolge:
- }
- sx={{ m: 0 }}
- />
+
+
+ void this.handleMoveTerminal(terminal, 'up')}
+ sx={{ p: 0.25 }}
+ >
+
+
+
+
+
+
+ void this.handleMoveTerminal(terminal, 'down')}
+ sx={{ p: 0.25 }}
+ >
+
+
+
+
+ this.handleSortDraftChange(terminal.id, e.target.value)}
+ onBlur={() => void this.handleSortSubmit(terminal)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ e.target.blur();
+ }
+ }}
+ slotProps={{
+ htmlInput: {
+ step: 1,
+ style: { width: 44, padding: '3px 6px', textAlign: 'center', fontSize: '0.8125rem' },
+ },
+ }}
+ sx={{ width: 56 }}
+ />
+
+
+ void this.handleToggleSyncRootArticles(terminal, e.target.checked)}
+ />
+ }
+ label={
+
+ Root-Artikel synchronisieren
+
+ }
+ sx={{ m: 0 }}
+ />
+
{/* Dates row */}
@@ -402,6 +599,11 @@ export default class PosTerminalSettingsPanel extends Component {
Zuletzt online: {lastseenText}
+ {terminal.clientversion && (
+
+ POS-Software: {terminal.clientversion}
+
+ )}
Aktualisiert: {updatedText}