u
This commit is contained in:
66
src/api/agentprinter.js
Normal file
66
src/api/agentprinter.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { http } from './http.js';
|
||||||
|
|
||||||
|
export const agentprinter = {
|
||||||
|
/**
|
||||||
|
* GET /api/agentprinter
|
||||||
|
* Query: ?printeragentId=<id>
|
||||||
|
* → { agentprinters: Agentprinter[] }
|
||||||
|
*/
|
||||||
|
list(printeragentId) {
|
||||||
|
const url =
|
||||||
|
printeragentId != null
|
||||||
|
? `/api/agentprinter?printeragentId=${encodeURIComponent(printeragentId)}`
|
||||||
|
: '/api/agentprinter';
|
||||||
|
return http(url);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/agentprinter/:id
|
||||||
|
* → { agentprinter: Agentprinter }
|
||||||
|
*/
|
||||||
|
get(id) {
|
||||||
|
return http(`/api/agentprinter/${id}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PATCH /api/agentprinter/:id
|
||||||
|
* Body: { enabled?: boolean, sort?: number }
|
||||||
|
* → { agentprinter: Agentprinter }
|
||||||
|
*/
|
||||||
|
update(id, patch = {}) {
|
||||||
|
const body = {};
|
||||||
|
if (patch.enabled !== undefined) body.enabled = Boolean(patch.enabled);
|
||||||
|
if (patch.sort !== undefined && patch.sort !== null && patch.sort !== '') {
|
||||||
|
body.sort = Number(patch.sort);
|
||||||
|
}
|
||||||
|
return http(`/api/agentprinter/${id}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT /api/agentprinter/order
|
||||||
|
* Body: { printeragentId: number, agentprinterIds: number[] }
|
||||||
|
* → { agentprinterIds: number[] }
|
||||||
|
*/
|
||||||
|
order({ printeragentId, agentprinterIds }) {
|
||||||
|
return http('/api/agentprinter/order', {
|
||||||
|
method: 'PUT',
|
||||||
|
body: {
|
||||||
|
printeragentId: Number(printeragentId),
|
||||||
|
agentprinterIds: Array.isArray(agentprinterIds) ? agentprinterIds.map(Number) : [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE /api/agentprinter/:id → 204
|
||||||
|
*/
|
||||||
|
delete(id) {
|
||||||
|
return http(`/api/agentprinter/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@ export { order } from './order.js';
|
|||||||
export { posterminal } from './posterminal.js';
|
export { posterminal } from './posterminal.js';
|
||||||
export { printer } from './printer.js';
|
export { printer } from './printer.js';
|
||||||
export { printeragent } from './printeragent.js';
|
export { printeragent } from './printeragent.js';
|
||||||
|
export { agentprinter } from './agentprinter.js';
|
||||||
export { propertyname } from './propertyname.js';
|
export { propertyname } from './propertyname.js';
|
||||||
export { quote } from './quote.js';
|
export { quote } from './quote.js';
|
||||||
export { retoure } from './retoure.js';
|
export { retoure } from './retoure.js';
|
||||||
|
|||||||
@@ -51,4 +51,29 @@ export const printer = {
|
|||||||
return http('/api/printer/order', { method: 'PUT', body: { printerIds } });
|
return http('/api/printer/order', { method: 'PUT', body: { printerIds } });
|
||||||
},
|
},
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/printer/state
|
||||||
|
* Query: ?serial=, ?agentId=
|
||||||
|
* → { printers: LivePrinter[] }
|
||||||
|
*/
|
||||||
|
// #region GET /api/printer/state — live state
|
||||||
|
state({ serial, agentId } = {}) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (serial) params.set('serial', serial);
|
||||||
|
if (agentId != null) params.set('agentId', agentId);
|
||||||
|
const query = params.toString();
|
||||||
|
return http(`/api/printer/state${query ? `?${query}` : ''}`);
|
||||||
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/printer/:id/state
|
||||||
|
* → { printer: LivePrinter }
|
||||||
|
*/
|
||||||
|
// #region GET /api/printer/:id/state — single printer state
|
||||||
|
printerState(id) {
|
||||||
|
return http(`/api/printer/${id}/state`);
|
||||||
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ export const printeragent = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/printeragent/:id
|
* PATCH /api/printeragent/:id
|
||||||
* Body: { name?: string, sort?: number }
|
* Body: { name?: string, sort?: number, enabled?: boolean }
|
||||||
* Updates name, sort, or both.
|
* Updates name, sort, enabled, or any combination.
|
||||||
* → 200 { printeragent: Printeragent }
|
* → 200 { printeragent: Printeragent }
|
||||||
*/
|
*/
|
||||||
update(id, patch = {}) {
|
update(id, patch = {}) {
|
||||||
@@ -38,6 +38,7 @@ export const printeragent = {
|
|||||||
if (patch.sort !== undefined && patch.sort !== null && patch.sort !== '') {
|
if (patch.sort !== undefined && patch.sort !== null && patch.sort !== '') {
|
||||||
body.sort = Number(patch.sort);
|
body.sort = Number(patch.sort);
|
||||||
}
|
}
|
||||||
|
if (patch.enabled !== undefined) body.enabled = Boolean(patch.enabled);
|
||||||
return http(`/api/printeragent/${id}`, {
|
return http(`/api/printeragent/${id}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body,
|
body,
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ import {
|
|||||||
company as companyApi,
|
company as companyApi,
|
||||||
number as numberApi,
|
number as numberApi,
|
||||||
posterminal as posterminalApi,
|
posterminal as posterminalApi,
|
||||||
|
printer as printerApi,
|
||||||
printeragent as printeragentApi,
|
printeragent as printeragentApi,
|
||||||
|
agentprinter as agentprinterApi,
|
||||||
supplier as supplierApi,
|
supplier as supplierApi,
|
||||||
unit as unitApi,
|
unit as unitApi,
|
||||||
warehouse as warehouseApi,
|
warehouse as warehouseApi,
|
||||||
@@ -178,6 +180,15 @@ const sortPrinterAgents = (agents) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sortAgentPrinters = (printers) => {
|
||||||
|
if (!Array.isArray(printers)) return [];
|
||||||
|
return [...printers].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) =>
|
const sortAccounts = (items) =>
|
||||||
[...items].sort((a, b) => String(a.name ?? '').localeCompare(String(b.name ?? ''), 'de'));
|
[...items].sort((a, b) => String(a.name ?? '').localeCompare(String(b.name ?? ''), 'de'));
|
||||||
|
|
||||||
@@ -247,12 +258,19 @@ const initialState = {
|
|||||||
terminalsStatus: 'idle',
|
terminalsStatus: 'idle',
|
||||||
terminalsError: null,
|
terminalsError: null,
|
||||||
|
|
||||||
// Printer agents
|
// Printer agents & agent printers
|
||||||
printeragents: [],
|
printeragents: [],
|
||||||
printeragentsCompanyId: null,
|
printeragentsCompanyId: null,
|
||||||
printeragentsStatus: 'idle',
|
printeragentsStatus: 'idle',
|
||||||
printeragentsError: null,
|
printeragentsError: null,
|
||||||
|
|
||||||
|
agentprinters: [],
|
||||||
|
agentprintersCompanyId: null,
|
||||||
|
agentprintersStatus: 'idle',
|
||||||
|
agentprintersError: null,
|
||||||
|
|
||||||
|
livePrinters: [], // LivePrinter[] from /api/printer/state or SSE printer/printerstate
|
||||||
|
|
||||||
// 4. Suppliers
|
// 4. Suppliers
|
||||||
suppliers: [],
|
suppliers: [],
|
||||||
articlesBySupplier: {},
|
articlesBySupplier: {},
|
||||||
@@ -320,6 +338,8 @@ let inflightCompany = null;
|
|||||||
let inflightNumbers = null;
|
let inflightNumbers = null;
|
||||||
let inflightTerminals = null;
|
let inflightTerminals = null;
|
||||||
let inflightPrinterAgents = null;
|
let inflightPrinterAgents = null;
|
||||||
|
let inflightAgentPrinters = null;
|
||||||
|
let inflightLivePrinters = null;
|
||||||
let inflightSuppliers = null;
|
let inflightSuppliers = null;
|
||||||
let inflightUnits = null;
|
let inflightUnits = null;
|
||||||
let inflightWarehouses = null;
|
let inflightWarehouses = null;
|
||||||
@@ -514,6 +534,8 @@ const actions = {
|
|||||||
actions.loadNumbers(),
|
actions.loadNumbers(),
|
||||||
actions.loadTerminals(),
|
actions.loadTerminals(),
|
||||||
actions.loadPrinterAgents(),
|
actions.loadPrinterAgents(),
|
||||||
|
actions.loadAgentPrinters(),
|
||||||
|
actions.loadLivePrinters(),
|
||||||
actions.loadSuppliers(),
|
actions.loadSuppliers(),
|
||||||
actions.loadUnits(),
|
actions.loadUnits(),
|
||||||
actions.loadWarehouses(),
|
actions.loadWarehouses(),
|
||||||
@@ -531,6 +553,8 @@ const actions = {
|
|||||||
inflightNumbers = null;
|
inflightNumbers = null;
|
||||||
inflightTerminals = null;
|
inflightTerminals = null;
|
||||||
inflightPrinterAgents = null;
|
inflightPrinterAgents = null;
|
||||||
|
inflightAgentPrinters = null;
|
||||||
|
inflightLivePrinters = null;
|
||||||
inflightSuppliers = null;
|
inflightSuppliers = null;
|
||||||
inflightUnits = null;
|
inflightUnits = null;
|
||||||
inflightWarehouses = null;
|
inflightWarehouses = null;
|
||||||
@@ -779,6 +803,102 @@ const actions = {
|
|||||||
await printeragentApi.delete(agentId);
|
await printeragentApi.delete(agentId);
|
||||||
companyAutomat.setState({
|
companyAutomat.setState({
|
||||||
printeragents: companyAutomat.state.printeragents.filter((a) => a.id !== agentId),
|
printeragents: companyAutomat.state.printeragents.filter((a) => a.id !== agentId),
|
||||||
|
agentprinters: companyAutomat.state.agentprinters.filter((p) => p.printeragentId !== agentId),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Agent printers (agentprinter) & live telemetry
|
||||||
|
// MARK: calls agentprinter.list from ../api/index.js
|
||||||
|
async loadAgentPrinters(printeragentId) {
|
||||||
|
const companyId =
|
||||||
|
authAutomat.state.user?.selectedCompanyId ?? companyAutomat.state.current?.id;
|
||||||
|
if (!companyId) return [];
|
||||||
|
if (inflightAgentPrinters) return inflightAgentPrinters;
|
||||||
|
companyAutomat.setState({ agentprintersStatus: 'loading', agentprintersError: null });
|
||||||
|
inflightAgentPrinters = (async () => {
|
||||||
|
try {
|
||||||
|
const res = await agentprinterApi.list(printeragentId);
|
||||||
|
const agentprinters = sortAgentPrinters(Array.isArray(res.agentprinters) ? res.agentprinters : []);
|
||||||
|
companyAutomat.setState({
|
||||||
|
agentprinters,
|
||||||
|
agentprintersCompanyId: companyId,
|
||||||
|
agentprintersStatus: 'succeeded',
|
||||||
|
agentprintersError: null,
|
||||||
|
});
|
||||||
|
return agentprinters;
|
||||||
|
} catch (err) {
|
||||||
|
companyAutomat.setState({
|
||||||
|
agentprintersStatus: 'failed',
|
||||||
|
agentprintersError: err?.message ?? String(err),
|
||||||
|
});
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
inflightAgentPrinters = null;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
return inflightAgentPrinters;
|
||||||
|
},
|
||||||
|
|
||||||
|
reloadAgentPrinters(printeragentId) {
|
||||||
|
return actions.loadAgentPrinters(printeragentId);
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateAgentPrinter(id, patch) {
|
||||||
|
const res = await agentprinterApi.update(id, patch);
|
||||||
|
const updated = res.agentprinter;
|
||||||
|
companyAutomat.setState({
|
||||||
|
agentprinters: sortAgentPrinters(
|
||||||
|
companyAutomat.state.agentprinters.map((p) =>
|
||||||
|
p.id === id ? { ...p, ...updated } : p,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
return updated;
|
||||||
|
},
|
||||||
|
|
||||||
|
async reorderAgentPrinters({ printeragentId, agentprinterIds }) {
|
||||||
|
await agentprinterApi.order({ printeragentId, agentprinterIds });
|
||||||
|
const idOrder = new Map(agentprinterIds.map((id, index) => [id, index]));
|
||||||
|
companyAutomat.setState({
|
||||||
|
agentprinters: sortAgentPrinters(
|
||||||
|
companyAutomat.state.agentprinters.map((p) =>
|
||||||
|
idOrder.has(p.id) ? { ...p, sort: idOrder.get(p.id) } : p,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteAgentPrinter(id) {
|
||||||
|
await agentprinterApi.delete(id);
|
||||||
|
companyAutomat.setState({
|
||||||
|
agentprinters: companyAutomat.state.agentprinters.filter((p) => p.id !== id),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadLivePrinters() {
|
||||||
|
const companyId =
|
||||||
|
authAutomat.state.user?.selectedCompanyId ?? companyAutomat.state.current?.id;
|
||||||
|
if (!companyId) return [];
|
||||||
|
if (inflightLivePrinters) return inflightLivePrinters;
|
||||||
|
inflightLivePrinters = (async () => {
|
||||||
|
try {
|
||||||
|
const res = await printerApi.state();
|
||||||
|
const livePrinters = Array.isArray(res.printers) ? res.printers : [];
|
||||||
|
companyAutomat.setState({ livePrinters });
|
||||||
|
return livePrinters;
|
||||||
|
} catch (err) {
|
||||||
|
return [];
|
||||||
|
} finally {
|
||||||
|
inflightLivePrinters = null;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
return inflightLivePrinters;
|
||||||
|
},
|
||||||
|
|
||||||
|
setLivePrinters(printers) {
|
||||||
|
companyAutomat.setState({
|
||||||
|
livePrinters: Array.isArray(printers) ? printers : [],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// #endregion
|
// #endregion
|
||||||
@@ -1641,6 +1761,27 @@ sseAutomat.actions.on('posterminal', () => {
|
|||||||
sseAutomat.actions.on('printeragent', () => {
|
sseAutomat.actions.on('printeragent', () => {
|
||||||
void companyAutomat.actions.loadPrinterAgents().catch(() => {});
|
void companyAutomat.actions.loadPrinterAgents().catch(() => {});
|
||||||
});
|
});
|
||||||
|
sseAutomat.actions.on('agentprinter', () => {
|
||||||
|
void companyAutomat.actions.loadAgentPrinters().catch(() => {});
|
||||||
|
});
|
||||||
|
sseAutomat.actions.on('printerstate', (ev) => {
|
||||||
|
try {
|
||||||
|
const payload = typeof ev?.data === 'string' ? JSON.parse(ev.data || '{}') : (ev?.data || {});
|
||||||
|
if (Array.isArray(payload.printers)) {
|
||||||
|
companyAutomat.actions.setLivePrinters(payload.printers);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
void companyAutomat.actions.loadLivePrinters().catch(() => {});
|
||||||
|
});
|
||||||
|
sseAutomat.actions.on('printer', (ev) => {
|
||||||
|
try {
|
||||||
|
const payload = typeof ev?.data === 'string' ? JSON.parse(ev.data || '{}') : (ev?.data || {});
|
||||||
|
if (Array.isArray(payload.printers)) {
|
||||||
|
companyAutomat.actions.setLivePrinters(payload.printers);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
sseAutomat.actions.on('customergroup', () => {
|
sseAutomat.actions.on('customergroup', () => {
|
||||||
void companyAutomat.actions.reloadCustomergroups().catch(() => {});
|
void companyAutomat.actions.reloadCustomergroups().catch(() => {});
|
||||||
});
|
});
|
||||||
@@ -1686,6 +1827,11 @@ export const selectPrinterAgents = () => companyAutomat.state.printeragents;
|
|||||||
export const selectPrinterAgentStatus = () => companyAutomat.state.printeragentsStatus;
|
export const selectPrinterAgentStatus = () => companyAutomat.state.printeragentsStatus;
|
||||||
export const selectHasPrinterAgents = () => companyAutomat.state.printeragents.length > 0;
|
export const selectHasPrinterAgents = () => companyAutomat.state.printeragents.length > 0;
|
||||||
|
|
||||||
|
// Agent printers selectors
|
||||||
|
export const selectAgentPrinters = () => companyAutomat.state.agentprinters;
|
||||||
|
export const selectAgentPrinterStatus = () => companyAutomat.state.agentprintersStatus;
|
||||||
|
export const selectLivePrinters = () => companyAutomat.state.livePrinters;
|
||||||
|
|
||||||
// Suppliers selectors
|
// Suppliers selectors
|
||||||
export const selectSuppliers = () => companyAutomat.state.suppliers;
|
export const selectSuppliers = () => companyAutomat.state.suppliers;
|
||||||
export const selectSuppliersStatus = () => companyAutomat.state.suppliersStatus;
|
export const selectSuppliersStatus = () => companyAutomat.state.suppliersStatus;
|
||||||
|
|||||||
@@ -154,6 +154,9 @@ export {
|
|||||||
selectPrinterAgents,
|
selectPrinterAgents,
|
||||||
selectPrinterAgentStatus,
|
selectPrinterAgentStatus,
|
||||||
selectHasPrinterAgents,
|
selectHasPrinterAgents,
|
||||||
|
selectAgentPrinters,
|
||||||
|
selectAgentPrinterStatus,
|
||||||
|
selectLivePrinters,
|
||||||
selectSuppliers,
|
selectSuppliers,
|
||||||
selectSuppliersStatus,
|
selectSuppliersStatus,
|
||||||
selectUnits,
|
selectUnits,
|
||||||
|
|||||||
@@ -3,21 +3,26 @@ import Box from '@mui/material/Box';
|
|||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Chip from '@mui/material/Chip';
|
import Chip from '@mui/material/Chip';
|
||||||
import Divider from '@mui/material/Divider';
|
import Divider from '@mui/material/Divider';
|
||||||
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import Switch from '@mui/material/Switch';
|
||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
||||||
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
||||||
import CheckIcon from '@mui/icons-material/Check';
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||||
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
|
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
|
||||||
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||||
import LocalPrintshopOutlinedIcon from '@mui/icons-material/LocalPrintshopOutlined';
|
import LocalPrintshopOutlinedIcon from '@mui/icons-material/LocalPrintshopOutlined';
|
||||||
|
import PrintOutlinedIcon from '@mui/icons-material/PrintOutlined';
|
||||||
import CheckCircleOutlinedIcon from '@mui/icons-material/CheckCircleOutlined';
|
import CheckCircleOutlinedIcon from '@mui/icons-material/CheckCircleOutlined';
|
||||||
import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty';
|
import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty';
|
||||||
import QRCode from 'qrcode';
|
|
||||||
import { enqueueSnackbar } from 'notistack';
|
import { enqueueSnackbar } from 'notistack';
|
||||||
|
import QRCode from 'qrcode';
|
||||||
import BackendError from '../BackendError';
|
import BackendError from '../BackendError';
|
||||||
import companyAutomat from '../../automats/companyAutomat';
|
import companyAutomat from '../../automats/companyAutomat';
|
||||||
import { backendErrorFrom } from '../../utils/backendError';
|
import { backendErrorFrom } from '../../utils/backendError';
|
||||||
@@ -114,6 +119,91 @@ class AgentQrCode extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderPrinterDiagnostics(printer, live) {
|
||||||
|
if (!live) {
|
||||||
|
return (
|
||||||
|
<Chip
|
||||||
|
label="Offline"
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
color="default"
|
||||||
|
sx={{ fontSize: '0.7rem', height: 20 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const errors = live.errors || {};
|
||||||
|
const errorChips = [];
|
||||||
|
if (errors.headOpen) {
|
||||||
|
errorChips.push(
|
||||||
|
<Chip
|
||||||
|
key="headOpen"
|
||||||
|
label="Druckkopf offen"
|
||||||
|
size="small"
|
||||||
|
color="error"
|
||||||
|
sx={{ fontSize: '0.7rem', height: 20, fontWeight: 600 }}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (errors.pause) {
|
||||||
|
errorChips.push(
|
||||||
|
<Chip
|
||||||
|
key="pause"
|
||||||
|
label="Pausiert"
|
||||||
|
size="small"
|
||||||
|
color="warning"
|
||||||
|
sx={{ fontSize: '0.7rem', height: 20, fontWeight: 600 }}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (errors.unreachable) {
|
||||||
|
errorChips.push(
|
||||||
|
<Chip
|
||||||
|
key="unreachable"
|
||||||
|
label="Nicht erreichbar"
|
||||||
|
size="small"
|
||||||
|
color="error"
|
||||||
|
sx={{ fontSize: '0.7rem', height: 20, fontWeight: 600 }}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (errors.other) {
|
||||||
|
errorChips.push(
|
||||||
|
<Chip
|
||||||
|
key="other"
|
||||||
|
label="Fehler"
|
||||||
|
size="small"
|
||||||
|
color="error"
|
||||||
|
sx={{ fontSize: '0.7rem', height: 20, fontWeight: 600 }}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorChips.length > 0) {
|
||||||
|
return <Box sx={{ display: 'inline-flex', gap: 0.5, flexWrap: 'wrap' }}>{errorChips}</Box>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'inline-flex', gap: 0.5, alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
|
<Chip
|
||||||
|
label="Bereit"
|
||||||
|
size="small"
|
||||||
|
color="success"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ fontSize: '0.7rem', height: 20, fontWeight: 600 }}
|
||||||
|
/>
|
||||||
|
{live.labelLength != null ? (
|
||||||
|
<Chip
|
||||||
|
label={`Etikett: ${live.labelLength} px`}
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ fontSize: '0.7rem', height: 20 }}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default class PrinterAgentSettingsPanel extends PureComponent {
|
export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -121,6 +211,12 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
printeragents: Array.isArray(companyAutomat.state.printeragents)
|
printeragents: Array.isArray(companyAutomat.state.printeragents)
|
||||||
? companyAutomat.state.printeragents
|
? companyAutomat.state.printeragents
|
||||||
: [],
|
: [],
|
||||||
|
agentprinters: Array.isArray(companyAutomat.state.agentprinters)
|
||||||
|
? companyAutomat.state.agentprinters
|
||||||
|
: [],
|
||||||
|
livePrinters: Array.isArray(companyAutomat.state.livePrinters)
|
||||||
|
? companyAutomat.state.livePrinters
|
||||||
|
: [],
|
||||||
error: companyAutomat.state.printeragentsError
|
error: companyAutomat.state.printeragentsError
|
||||||
? backendErrorFrom(companyAutomat.state.printeragentsError)
|
? backendErrorFrom(companyAutomat.state.printeragentsError)
|
||||||
: null,
|
: null,
|
||||||
@@ -135,22 +231,32 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
this.handleRetry = this.handleRetry.bind(this);
|
this.handleRetry = this.handleRetry.bind(this);
|
||||||
this.handleCreateAgent = this.handleCreateAgent.bind(this);
|
this.handleCreateAgent = this.handleCreateAgent.bind(this);
|
||||||
this.handleDeleteAgent = this.handleDeleteAgent.bind(this);
|
this.handleDeleteAgent = this.handleDeleteAgent.bind(this);
|
||||||
|
this.handleToggleAgentEnabled = this.handleToggleAgentEnabled.bind(this);
|
||||||
this.handleStartRename = this.handleStartRename.bind(this);
|
this.handleStartRename = this.handleStartRename.bind(this);
|
||||||
this.handleCancelRename = this.handleCancelRename.bind(this);
|
this.handleCancelRename = this.handleCancelRename.bind(this);
|
||||||
this.handleSaveRename = this.handleSaveRename.bind(this);
|
this.handleSaveRename = this.handleSaveRename.bind(this);
|
||||||
this.handleSortDraftChange = this.handleSortDraftChange.bind(this);
|
this.handleSortDraftChange = this.handleSortDraftChange.bind(this);
|
||||||
this.handleSortSubmit = this.handleSortSubmit.bind(this);
|
this.handleSortSubmit = this.handleSortSubmit.bind(this);
|
||||||
this.handleMoveAgent = this.handleMoveAgent.bind(this);
|
this.handleMoveAgent = this.handleMoveAgent.bind(this);
|
||||||
|
this.handleTogglePrinterEnabled = this.handleTogglePrinterEnabled.bind(this);
|
||||||
|
this.handleMovePrinter = this.handleMovePrinter.bind(this);
|
||||||
|
this.handleDeletePrinter = this.handleDeletePrinter.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.unsub = companyAutomat.subscribe(this, (state) => ({
|
this.unsub = companyAutomat.subscribe(this, (state) => ({
|
||||||
printeragents: Array.isArray(state.printeragents) ? state.printeragents : [],
|
printeragents: Array.isArray(state.printeragents) ? state.printeragents : [],
|
||||||
|
agentprinters: Array.isArray(state.agentprinters) ? state.agentprinters : [],
|
||||||
|
livePrinters: Array.isArray(state.livePrinters) ? state.livePrinters : [],
|
||||||
error: state.printeragentsError ? backendErrorFrom(state.printeragentsError) : null,
|
error: state.printeragentsError ? backendErrorFrom(state.printeragentsError) : null,
|
||||||
}));
|
}));
|
||||||
if (companyAutomat.state.printeragentsStatus === 'idle') {
|
if (companyAutomat.state.printeragentsStatus === 'idle') {
|
||||||
void companyAutomat.actions.loadPrinterAgents().catch(() => { });
|
void companyAutomat.actions.loadPrinterAgents().catch(() => {});
|
||||||
}
|
}
|
||||||
|
if (companyAutomat.state.agentprintersStatus === 'idle') {
|
||||||
|
void companyAutomat.actions.loadAgentPrinters().catch(() => {});
|
||||||
|
}
|
||||||
|
void companyAutomat.actions.loadLivePrinters().catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@@ -159,6 +265,8 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
|
|
||||||
handleRetry() {
|
handleRetry() {
|
||||||
void companyAutomat.actions.loadPrinterAgents().catch(notifyError);
|
void companyAutomat.actions.loadPrinterAgents().catch(notifyError);
|
||||||
|
void companyAutomat.actions.loadAgentPrinters().catch(notifyError);
|
||||||
|
void companyAutomat.actions.loadLivePrinters().catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCreateAgent() {
|
async handleCreateAgent() {
|
||||||
@@ -200,6 +308,23 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleToggleAgentEnabled(agent, nextValue) {
|
||||||
|
this.setState({ patchingAgentId: agent.id });
|
||||||
|
try {
|
||||||
|
await companyAutomat.actions.updatePrinterAgent(agent.id, {
|
||||||
|
enabled: Boolean(nextValue),
|
||||||
|
});
|
||||||
|
enqueueSnackbar(
|
||||||
|
nextValue ? 'Drucker-Agent aktiviert.' : 'Drucker-Agent deaktiviert.',
|
||||||
|
{ variant: 'success' },
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
notifyError(err);
|
||||||
|
} finally {
|
||||||
|
this.setState({ patchingAgentId: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleStartRename(agent) {
|
handleStartRename(agent) {
|
||||||
this.setState({ editingId: agent.id, editNameDraft: agent.name || '' });
|
this.setState({ editingId: agent.id, editNameDraft: agent.name || '' });
|
||||||
}
|
}
|
||||||
@@ -291,10 +416,63 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleTogglePrinterEnabled(printer, nextValue) {
|
||||||
|
try {
|
||||||
|
await companyAutomat.actions.updateAgentPrinter(printer.id, {
|
||||||
|
enabled: Boolean(nextValue),
|
||||||
|
});
|
||||||
|
enqueueSnackbar(
|
||||||
|
nextValue ? 'Drucker aktiviert.' : 'Drucker deaktiviert.',
|
||||||
|
{ variant: 'success' },
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
notifyError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleMovePrinter(printer, direction) {
|
||||||
|
const { agentprinters } = this.state;
|
||||||
|
const printersForAgent = agentprinters.filter(
|
||||||
|
(p) => p.printeragentId === printer.printeragentId,
|
||||||
|
);
|
||||||
|
const currentIndex = printersForAgent.findIndex((p) => p.id === printer.id);
|
||||||
|
if (currentIndex === -1) return;
|
||||||
|
const targetIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;
|
||||||
|
if (targetIndex < 0 || targetIndex >= printersForAgent.length) return;
|
||||||
|
|
||||||
|
const reordered = [...printersForAgent];
|
||||||
|
const [moved] = reordered.splice(currentIndex, 1);
|
||||||
|
reordered.splice(targetIndex, 0, moved);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await companyAutomat.actions.reorderAgentPrinters({
|
||||||
|
printeragentId: printer.printeragentId,
|
||||||
|
agentprinterIds: reordered.map((p) => p.id),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
notifyError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleDeletePrinter(printer) {
|
||||||
|
const label = printer.model || printer.serial || `#${printer.id}`;
|
||||||
|
if (!window.confirm(`Möchten Sie den Drucker „${label}“ wirklich entfernen?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await companyAutomat.actions.deleteAgentPrinter(printer.id);
|
||||||
|
enqueueSnackbar('Drucker entfernt.', { variant: 'success' });
|
||||||
|
} catch (err) {
|
||||||
|
notifyError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
error,
|
error,
|
||||||
printeragents,
|
printeragents,
|
||||||
|
agentprinters,
|
||||||
|
livePrinters,
|
||||||
newAgentName,
|
newAgentName,
|
||||||
creatingAgent,
|
creatingAgent,
|
||||||
deletingId,
|
deletingId,
|
||||||
@@ -311,6 +489,12 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build lookup maps for fast access
|
||||||
|
const livePrintersBySerial = new Map();
|
||||||
|
for (const lp of livePrinters) {
|
||||||
|
if (lp.serial) livePrintersBySerial.set(lp.serial, lp);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 3, maxWidth: 900, mx: 'auto' }}>
|
<Box sx={{ p: 3, maxWidth: 900, mx: 'auto' }}>
|
||||||
<Box sx={{ mb: 2 }}>
|
<Box sx={{ mb: 2 }}>
|
||||||
@@ -318,8 +502,9 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
Drucker-Agenten
|
Drucker-Agenten
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
Verwalten Sie externe Drucker-Agenten für Druckaufträge. Nach Erstellung eines Agenten
|
Verwalten Sie externe Drucker-Agenten und die daran angeschlossenen Drucker. Nach
|
||||||
geben Sie die 6-stellige PIN auf dem Agenten-Gerät ein, um die Kopplung abzuschließen.
|
Erstellung eines Agenten geben Sie die 6-stellige PIN auf dem Gerät ein oder scannen
|
||||||
|
Sie den QR-Code, um die Kopplung abzuschließen.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -327,7 +512,7 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
<Box sx={{ display: 'flex', gap: 1.5, mb: 3, alignItems: 'center' }}>
|
<Box sx={{ display: 'flex', gap: 1.5, mb: 3, alignItems: 'center' }}>
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
placeholder="Neuer Drucker-Agent Name (z.B. Bar Receipt Printer)"
|
placeholder="Neuer Drucker-Agent Name (z.B. Bar, Küche)"
|
||||||
value={newAgentName}
|
value={newAgentName}
|
||||||
onChange={(e) => this.setState({ newAgentName: e.target.value })}
|
onChange={(e) => this.setState({ newAgentName: e.target.value })}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
@@ -346,7 +531,9 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
spellCheck: false,
|
spellCheck: false,
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
startAdornment: <LocalPrintshopOutlinedIcon sx={{ mr: 1, color: 'text.secondary' }} />,
|
startAdornment: (
|
||||||
|
<LocalPrintshopOutlinedIcon sx={{ mr: 1, color: 'text.secondary' }} />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
sx={{ flex: 1 }}
|
sx={{ flex: 1 }}
|
||||||
@@ -370,16 +557,23 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
<Typography variant="body2">Keine Drucker-Agenten angelegt.</Typography>
|
<Typography variant="body2">Keine Drucker-Agenten angelegt.</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2.5 }}>
|
||||||
{printeragents.map((agent, index) => {
|
{printeragents.map((agent, index) => {
|
||||||
const isDeleting = deletingId === agent.id;
|
const isDeleting = deletingId === agent.id;
|
||||||
const isPatching = patchingAgentId === agent.id;
|
const isPatching = patchingAgentId === agent.id;
|
||||||
const isEditing = editingId === agent.id;
|
const isEditing = editingId === agent.id;
|
||||||
const isPaired = agent.pairedAt != null;
|
const isPaired = agent.pairedAt != null;
|
||||||
|
const isEnabled = agent.enabled !== false;
|
||||||
|
const clientState = agent.clientstate;
|
||||||
|
const isOnline = clientState === 'online';
|
||||||
const pairedDateText = formatDateTime(agent.pairedAt);
|
const pairedDateText = formatDateTime(agent.pairedAt);
|
||||||
const lastseenText = formatDateTime(agent.lastseenAt) ?? 'Nie';
|
const lastseenText = formatDateTime(agent.lastseenAt) ?? 'Nie';
|
||||||
const updatedText = formatDateTime(agent.updatedAt) ?? '—';
|
const updatedText = formatDateTime(agent.updatedAt) ?? '—';
|
||||||
|
|
||||||
|
const printersForAgent = agentprinters.filter(
|
||||||
|
(p) => p.printeragentId === agent.id,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
key={agent.id}
|
key={agent.id}
|
||||||
@@ -387,15 +581,20 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
p: 2,
|
p: 2,
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
border: '1px solid',
|
border: '1px solid',
|
||||||
borderColor: isPaired ? 'divider' : 'warning.light',
|
borderColor: !isEnabled
|
||||||
|
? 'action.disabledBackground'
|
||||||
|
: isPaired
|
||||||
|
? 'divider'
|
||||||
|
: 'warning.light',
|
||||||
bgcolor: 'background.paper',
|
bgcolor: 'background.paper',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: 1.5,
|
gap: 1.5,
|
||||||
transition: 'border-color 0.2s ease',
|
opacity: isEnabled ? 1 : 0.72,
|
||||||
|
transition: 'opacity 0.2s ease, border-color 0.2s ease',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Header: Icon, Name (or Edit input), Status chip, Actions */}
|
{/* Header: Icon, Name (or Edit input), Status chips, Enabled switch, Delete */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -407,7 +606,14 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, flex: 1, minWidth: 260 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, flex: 1, minWidth: 260 }}>
|
||||||
<LocalPrintshopOutlinedIcon
|
<LocalPrintshopOutlinedIcon
|
||||||
sx={{ color: isPaired ? 'primary.main' : 'warning.main', fontSize: 24 }}
|
sx={{
|
||||||
|
color: !isEnabled
|
||||||
|
? 'text.disabled'
|
||||||
|
: isPaired
|
||||||
|
? 'primary.main'
|
||||||
|
: 'warning.main',
|
||||||
|
fontSize: 24,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isEditing ? (
|
{isEditing ? (
|
||||||
@@ -455,7 +661,11 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<Typography variant="subtitle1" fontWeight={600}>
|
<Typography
|
||||||
|
variant="subtitle1"
|
||||||
|
fontWeight={600}
|
||||||
|
sx={{ color: isEnabled ? 'text.primary' : 'text.disabled' }}
|
||||||
|
>
|
||||||
{agent.name}
|
{agent.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -469,6 +679,7 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Pairing status badge */}
|
||||||
{isPaired ? (
|
{isPaired ? (
|
||||||
<Chip
|
<Chip
|
||||||
icon={<CheckCircleOutlinedIcon />}
|
icon={<CheckCircleOutlinedIcon />}
|
||||||
@@ -488,10 +699,59 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
sx={{ fontWeight: 600, fontSize: '0.75rem' }}
|
sx={{ fontWeight: 600, fontSize: '0.75rem' }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Live connection state */}
|
||||||
|
{clientState ? (
|
||||||
|
<Chip
|
||||||
|
label={isOnline ? 'Online' : 'Offline'}
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
color={isOnline ? 'success' : 'default'}
|
||||||
|
sx={{ fontSize: '0.75rem', fontWeight: 600 }}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{/* Agent version */}
|
||||||
|
{agent.agentversion ? (
|
||||||
|
<Tooltip title="Agent Software-Version">
|
||||||
|
<Chip
|
||||||
|
label={`v${agent.agentversion}`}
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
color="info"
|
||||||
|
sx={{ fontFamily: 'monospace', fontWeight: 600, fontSize: '0.75rem' }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Delete control */}
|
{/* Enabled Switch and Delete action */}
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
checked={isEnabled}
|
||||||
|
disabled={isPatching || isDeleting}
|
||||||
|
onChange={(e) => void this.handleToggleAgentEnabled(agent, e.target.checked)}
|
||||||
|
color="success"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
fontSize: '0.8125rem',
|
||||||
|
fontWeight: 500,
|
||||||
|
color: isEnabled ? 'text.primary' : 'text.disabled',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isEnabled ? 'Aktiv' : 'Inaktiv'}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
sx={{ m: 0 }}
|
||||||
|
/>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
aria-label={`Drucker-Agent ${agent.name} löschen`}
|
aria-label={`Drucker-Agent ${agent.name} löschen`}
|
||||||
@@ -504,7 +764,7 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Body: Pairing details with QR code or Paired status info */}
|
{/* Body: Pairing details with QR code or Discovered Printers */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -534,27 +794,39 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
borderColor: 'warning.light',
|
borderColor: 'warning.light',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'inline-flex' }}>
|
<Tooltip title="QR-Code zum Koppeln mit dem Drucker-Agenten">
|
||||||
<AgentQrCode value={qrPayload} size={110} />
|
<Box sx={{ display: 'inline-flex' }}>
|
||||||
</Box>
|
<AgentQrCode value={qrPayload} size={110} />
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, flex: 1, minWidth: 220 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, flex: 1, minWidth: 220 }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap' }}>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ fontWeight: 500 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ fontWeight: 500 }}>
|
||||||
Kopplungs-PIN:
|
Kopplungs-PIN:
|
||||||
</Typography>
|
</Typography>
|
||||||
<Chip
|
<Tooltip title="PIN in die Zwischenablage kopieren">
|
||||||
label={agent.pairingpin || '—'}
|
<Chip
|
||||||
color="warning"
|
icon={<ContentCopyIcon sx={{ fontSize: 14 }} />}
|
||||||
variant="filled"
|
label={agent.pairingpin || '—'}
|
||||||
sx={{
|
onClick={() => {
|
||||||
fontFamily: 'monospace',
|
if (agent.pairingpin) {
|
||||||
fontWeight: 700,
|
navigator.clipboard?.writeText(agent.pairingpin);
|
||||||
fontSize: '0.9rem',
|
enqueueSnackbar('PIN in Zwischenablage kopiert.', { variant: 'info' });
|
||||||
letterSpacing: '0.1em',
|
}
|
||||||
px: 0.5,
|
}}
|
||||||
}}
|
clickable
|
||||||
/>
|
color="warning"
|
||||||
|
variant="filled"
|
||||||
|
sx={{
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: '0.9rem',
|
||||||
|
letterSpacing: '0.1em',
|
||||||
|
px: 0.5,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap' }}>
|
||||||
@@ -567,75 +839,205 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
sx={{ fontFamily: 'monospace', fontSize: '0.8rem' }}
|
sx={{ fontFamily: 'monospace', fontSize: '0.8rem' }}
|
||||||
/>
|
/>
|
||||||
|
<Tooltip title="QR-Daten (JSON) kopieren">
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard?.writeText(qrPayload);
|
||||||
|
enqueueSnackbar('QR-Daten kopiert.', { variant: 'info' });
|
||||||
|
}}
|
||||||
|
sx={{ p: 0.5 }}
|
||||||
|
>
|
||||||
|
<ContentCopyIcon sx={{ fontSize: 14 }} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
Scannen Sie den QR-Code mit dem Drucker-Agenten oder geben Sie Domain und PIN manuell ein.
|
||||||
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
})()
|
})()
|
||||||
) : (
|
) : (
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, flexWrap: 'wrap' }}>
|
/* Discovered printers section */
|
||||||
{agent.clientserial ? (
|
<Box sx={{ p: 1.5, borderRadius: 1.5, bgcolor: 'action.hover' }}>
|
||||||
<Chip
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
|
||||||
label={`S/N: ${agent.clientserial}`}
|
<Typography variant="subtitle2" fontWeight={600} color="text.secondary">
|
||||||
size="small"
|
Erkannte Drucker ({printersForAgent.length})
|
||||||
variant="outlined"
|
</Typography>
|
||||||
sx={{ fontFamily: 'monospace', fontSize: '0.75rem' }}
|
</Box>
|
||||||
/>
|
{printersForAgent.length === 0 ? (
|
||||||
) : null}
|
<Typography
|
||||||
{agent.clientmodel ? (
|
variant="body2"
|
||||||
<Chip
|
color="text.secondary"
|
||||||
label={`Modell: ${agent.clientmodel}`}
|
sx={{ fontStyle: 'italic', py: 0.5 }}
|
||||||
size="small"
|
>
|
||||||
variant="outlined"
|
Keine Drucker erkannt. Schließen Sie einen Drucker per USB an den Agenten an.
|
||||||
sx={{ fontSize: '0.75rem' }}
|
</Typography>
|
||||||
/>
|
) : (
|
||||||
) : null}
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
{agent.agentversion ? (
|
{printersForAgent.map((p, pIdx) => {
|
||||||
<Chip
|
const pEnabled = p.enabled !== false;
|
||||||
label={`v${agent.agentversion}`}
|
const live =
|
||||||
size="small"
|
(p.serial ? livePrintersBySerial.get(p.serial) : null) ??
|
||||||
variant="outlined"
|
livePrinters.find(
|
||||||
color="info"
|
(lp) => lp.agentId === agent.id && lp.serial === p.serial,
|
||||||
sx={{ fontFamily: 'monospace', fontWeight: 600, fontSize: '0.75rem' }}
|
);
|
||||||
/>
|
|
||||||
) : null}
|
return (
|
||||||
{agent.clientstate ? (
|
<Box
|
||||||
<Chip
|
key={p.id}
|
||||||
label={`Status: ${agent.clientstate}`}
|
sx={{
|
||||||
size="small"
|
p: 1.25,
|
||||||
variant="outlined"
|
borderRadius: 1,
|
||||||
sx={{ fontSize: '0.75rem' }}
|
border: '1px solid',
|
||||||
/>
|
borderColor: 'divider',
|
||||||
) : null}
|
bgcolor: 'background.paper',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: 1.5,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
opacity: pEnabled ? 1 : 0.65,
|
||||||
|
transition: 'opacity 0.2s ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap' }}>
|
||||||
|
<PrintOutlinedIcon sx={{ fontSize: 20, color: 'text.secondary' }} />
|
||||||
|
<Typography variant="body2" fontWeight={600}>
|
||||||
|
{p.model || 'Drucker'}
|
||||||
|
</Typography>
|
||||||
|
{p.type ? (
|
||||||
|
<Chip
|
||||||
|
label={p.type.toUpperCase()}
|
||||||
|
size="small"
|
||||||
|
color="primary"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ fontWeight: 600, fontSize: '0.7rem', height: 20 }}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{p.serial ? (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ fontFamily: 'monospace' }}
|
||||||
|
>
|
||||||
|
({p.serial})
|
||||||
|
</Typography>
|
||||||
|
) : null}
|
||||||
|
{renderPrinterDiagnostics(p, live)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
{/* Enabled toggle switch */}
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
checked={pEnabled}
|
||||||
|
onChange={(e) =>
|
||||||
|
void this.handleTogglePrinterEnabled(p, e.target.checked)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
color={pEnabled ? 'text.primary' : 'text.disabled'}
|
||||||
|
>
|
||||||
|
{pEnabled ? 'Aktiv' : 'Inaktiv'}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
sx={{ m: 0 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Reorder buttons */}
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Tooltip title="Nach oben verschieben">
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
disabled={pIdx === 0}
|
||||||
|
onClick={() => void this.handleMovePrinter(p, 'up')}
|
||||||
|
sx={{ p: 0.25 }}
|
||||||
|
>
|
||||||
|
<ArrowUpwardIcon sx={{ fontSize: 16 }} />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Nach unten verschieben">
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
disabled={pIdx === printersForAgent.length - 1}
|
||||||
|
onClick={() => void this.handleMovePrinter(p, 'down')}
|
||||||
|
sx={{ p: 0.25 }}
|
||||||
|
>
|
||||||
|
<ArrowDownwardIcon sx={{ fontSize: 16 }} />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Delete printer button */}
|
||||||
|
<Tooltip title="Drucker entfernen">
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
color="error"
|
||||||
|
onClick={() => void this.handleDeletePrinter(p)}
|
||||||
|
sx={{ p: 0.5 }}
|
||||||
|
>
|
||||||
|
<DeleteOutlinedIcon sx={{ fontSize: 18 }} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Row with Sort Order Controls */}
|
{/* Agent sort order controls */}
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 0.5, pt: 0.5 }}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
gap: 0.5,
|
||||||
|
pt: 0.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
Reihenfolge:
|
Reihenfolge:
|
||||||
</Typography>
|
</Typography>
|
||||||
<span>
|
<Tooltip title="Nach oben verschieben">
|
||||||
<IconButton
|
<span>
|
||||||
size="small"
|
<IconButton
|
||||||
disabled={index === 0 || isPatching || isDeleting}
|
size="small"
|
||||||
onClick={() => void this.handleMoveAgent(agent, 'up')}
|
disabled={index === 0 || isPatching || isDeleting}
|
||||||
sx={{ p: 0.25 }}
|
onClick={() => void this.handleMoveAgent(agent, 'up')}
|
||||||
>
|
sx={{ p: 0.25 }}
|
||||||
<ArrowUpwardIcon sx={{ fontSize: 16 }} />
|
>
|
||||||
</IconButton>
|
<ArrowUpwardIcon sx={{ fontSize: 16 }} />
|
||||||
</span>
|
</IconButton>
|
||||||
|
</span>
|
||||||
<span>
|
</Tooltip>
|
||||||
<IconButton
|
<Tooltip title="Nach unten verschieben">
|
||||||
size="small"
|
<span>
|
||||||
disabled={index === printeragents.length - 1 || isPatching || isDeleting}
|
<IconButton
|
||||||
onClick={() => void this.handleMoveAgent(agent, 'down')}
|
size="small"
|
||||||
sx={{ p: 0.25 }}
|
disabled={index === printeragents.length - 1 || isPatching || isDeleting}
|
||||||
>
|
onClick={() => void this.handleMoveAgent(agent, 'down')}
|
||||||
<ArrowDownwardIcon sx={{ fontSize: 16 }} />
|
sx={{ p: 0.25 }}
|
||||||
</IconButton>
|
>
|
||||||
</span>
|
<ArrowDownwardIcon sx={{ fontSize: 16 }} />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
<TextField
|
<TextField
|
||||||
type="number"
|
type="number"
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
Reference in New Issue
Block a user