From 6719cf9d51534132bb1dfca0cd361b0388da4f5c Mon Sep 17 00:00:00 2001 From: seb Date: Sat, 31 Oct 2026 20:37:21 +0100 Subject: [PATCH] u --- src/api/agentprinter.js | 66 ++ src/api/index.js | 1 + src/api/printer.js | 25 + src/api/printeragent.js | 5 +- src/automats/companyAutomat.js | 148 ++++- src/automats/index.js | 3 + .../settings/PrinterAgentSettingsPanel.jsx | 574 +++++++++++++++--- 7 files changed, 733 insertions(+), 89 deletions(-) create mode 100644 src/api/agentprinter.js diff --git a/src/api/agentprinter.js b/src/api/agentprinter.js new file mode 100644 index 0000000..97d53b4 --- /dev/null +++ b/src/api/agentprinter.js @@ -0,0 +1,66 @@ +import { http } from './http.js'; + +export const agentprinter = { + /** + * GET /api/agentprinter + * Query: ?printeragentId= + * → { 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', + }); + }, +}; + diff --git a/src/api/index.js b/src/api/index.js index a55eacf..15fca75 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -16,6 +16,7 @@ export { order } from './order.js'; export { posterminal } from './posterminal.js'; export { printer } from './printer.js'; export { printeragent } from './printeragent.js'; +export { agentprinter } from './agentprinter.js'; export { propertyname } from './propertyname.js'; export { quote } from './quote.js'; export { retoure } from './retoure.js'; diff --git a/src/api/printer.js b/src/api/printer.js index 8cdf341..9335d90 100644 --- a/src/api/printer.js +++ b/src/api/printer.js @@ -51,4 +51,29 @@ export const printer = { return http('/api/printer/order', { method: 'PUT', body: { printerIds } }); }, // #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 }; diff --git a/src/api/printeragent.js b/src/api/printeragent.js index d696dad..03547ef 100644 --- a/src/api/printeragent.js +++ b/src/api/printeragent.js @@ -28,8 +28,8 @@ export const printeragent = { /** * PATCH /api/printeragent/:id - * Body: { name?: string, sort?: number } - * Updates name, sort, or both. + * Body: { name?: string, sort?: number, enabled?: boolean } + * Updates name, sort, enabled, or any combination. * → 200 { printeragent: Printeragent } */ update(id, patch = {}) { @@ -38,6 +38,7 @@ export const printeragent = { if (patch.sort !== undefined && patch.sort !== null && patch.sort !== '') { body.sort = Number(patch.sort); } + if (patch.enabled !== undefined) body.enabled = Boolean(patch.enabled); return http(`/api/printeragent/${id}`, { method: 'PATCH', body, diff --git a/src/automats/companyAutomat.js b/src/automats/companyAutomat.js index a371473..8ce2aca 100644 --- a/src/automats/companyAutomat.js +++ b/src/automats/companyAutomat.js @@ -25,7 +25,9 @@ import { company as companyApi, number as numberApi, posterminal as posterminalApi, + printer as printerApi, printeragent as printeragentApi, + agentprinter as agentprinterApi, supplier as supplierApi, unit as unitApi, 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) => [...items].sort((a, b) => String(a.name ?? '').localeCompare(String(b.name ?? ''), 'de')); @@ -247,12 +258,19 @@ const initialState = { terminalsStatus: 'idle', terminalsError: null, - // Printer agents + // Printer agents & agent printers printeragents: [], printeragentsCompanyId: null, printeragentsStatus: 'idle', printeragentsError: null, + agentprinters: [], + agentprintersCompanyId: null, + agentprintersStatus: 'idle', + agentprintersError: null, + + livePrinters: [], // LivePrinter[] from /api/printer/state or SSE printer/printerstate + // 4. Suppliers suppliers: [], articlesBySupplier: {}, @@ -320,6 +338,8 @@ let inflightCompany = null; let inflightNumbers = null; let inflightTerminals = null; let inflightPrinterAgents = null; +let inflightAgentPrinters = null; +let inflightLivePrinters = null; let inflightSuppliers = null; let inflightUnits = null; let inflightWarehouses = null; @@ -514,6 +534,8 @@ const actions = { actions.loadNumbers(), actions.loadTerminals(), actions.loadPrinterAgents(), + actions.loadAgentPrinters(), + actions.loadLivePrinters(), actions.loadSuppliers(), actions.loadUnits(), actions.loadWarehouses(), @@ -531,6 +553,8 @@ const actions = { inflightNumbers = null; inflightTerminals = null; inflightPrinterAgents = null; + inflightAgentPrinters = null; + inflightLivePrinters = null; inflightSuppliers = null; inflightUnits = null; inflightWarehouses = null; @@ -779,6 +803,102 @@ const actions = { await printeragentApi.delete(agentId); companyAutomat.setState({ 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 @@ -1641,6 +1761,27 @@ sseAutomat.actions.on('posterminal', () => { sseAutomat.actions.on('printeragent', () => { 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', () => { void companyAutomat.actions.reloadCustomergroups().catch(() => {}); }); @@ -1686,6 +1827,11 @@ export const selectPrinterAgents = () => companyAutomat.state.printeragents; export const selectPrinterAgentStatus = () => companyAutomat.state.printeragentsStatus; 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 export const selectSuppliers = () => companyAutomat.state.suppliers; export const selectSuppliersStatus = () => companyAutomat.state.suppliersStatus; diff --git a/src/automats/index.js b/src/automats/index.js index ce805af..b45ec03 100644 --- a/src/automats/index.js +++ b/src/automats/index.js @@ -154,6 +154,9 @@ export { selectPrinterAgents, selectPrinterAgentStatus, selectHasPrinterAgents, + selectAgentPrinters, + selectAgentPrinterStatus, + selectLivePrinters, selectSuppliers, selectSuppliersStatus, selectUnits, diff --git a/src/components/settings/PrinterAgentSettingsPanel.jsx b/src/components/settings/PrinterAgentSettingsPanel.jsx index c5cfbfb..ca1afba 100644 --- a/src/components/settings/PrinterAgentSettingsPanel.jsx +++ b/src/components/settings/PrinterAgentSettingsPanel.jsx @@ -3,21 +3,26 @@ 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 FormControlLabel from '@mui/material/FormControlLabel'; import IconButton from '@mui/material/IconButton'; +import Switch from '@mui/material/Switch'; 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 CheckIcon from '@mui/icons-material/Check'; import CloseIcon from '@mui/icons-material/Close'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; import LocalPrintshopOutlinedIcon from '@mui/icons-material/LocalPrintshopOutlined'; +import PrintOutlinedIcon from '@mui/icons-material/PrintOutlined'; import CheckCircleOutlinedIcon from '@mui/icons-material/CheckCircleOutlined'; import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty'; -import QRCode from 'qrcode'; import { enqueueSnackbar } from 'notistack'; +import QRCode from 'qrcode'; import BackendError from '../BackendError'; import companyAutomat from '../../automats/companyAutomat'; import { backendErrorFrom } from '../../utils/backendError'; @@ -114,6 +119,91 @@ class AgentQrCode extends PureComponent { } } +function renderPrinterDiagnostics(printer, live) { + if (!live) { + return ( + + ); + } + + const errors = live.errors || {}; + const errorChips = []; + if (errors.headOpen) { + errorChips.push( + , + ); + } + if (errors.pause) { + errorChips.push( + , + ); + } + if (errors.unreachable) { + errorChips.push( + , + ); + } + if (errors.other) { + errorChips.push( + , + ); + } + + if (errorChips.length > 0) { + return {errorChips}; + } + + return ( + + + {live.labelLength != null ? ( + + ) : null} + + ); +} + export default class PrinterAgentSettingsPanel extends PureComponent { constructor(props) { super(props); @@ -121,6 +211,12 @@ export default class PrinterAgentSettingsPanel extends PureComponent { printeragents: Array.isArray(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 ? backendErrorFrom(companyAutomat.state.printeragentsError) : null, @@ -135,22 +231,32 @@ export default class PrinterAgentSettingsPanel extends PureComponent { this.handleRetry = this.handleRetry.bind(this); this.handleCreateAgent = this.handleCreateAgent.bind(this); this.handleDeleteAgent = this.handleDeleteAgent.bind(this); + this.handleToggleAgentEnabled = this.handleToggleAgentEnabled.bind(this); this.handleStartRename = this.handleStartRename.bind(this); this.handleCancelRename = this.handleCancelRename.bind(this); this.handleSaveRename = this.handleSaveRename.bind(this); this.handleSortDraftChange = this.handleSortDraftChange.bind(this); this.handleSortSubmit = this.handleSortSubmit.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() { this.unsub = companyAutomat.subscribe(this, (state) => ({ 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, })); 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() { @@ -159,6 +265,8 @@ export default class PrinterAgentSettingsPanel extends PureComponent { handleRetry() { void companyAutomat.actions.loadPrinterAgents().catch(notifyError); + void companyAutomat.actions.loadAgentPrinters().catch(notifyError); + void companyAutomat.actions.loadLivePrinters().catch(() => {}); } 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) { 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() { const { error, printeragents, + agentprinters, + livePrinters, newAgentName, creatingAgent, 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 ( @@ -318,8 +502,9 @@ export default class PrinterAgentSettingsPanel extends PureComponent { Drucker-Agenten - Verwalten Sie externe Drucker-Agenten für Druckaufträge. Nach Erstellung eines Agenten - geben Sie die 6-stellige PIN auf dem Agenten-Gerät ein, um die Kopplung abzuschließen. + Verwalten Sie externe Drucker-Agenten und die daran angeschlossenen Drucker. Nach + 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. @@ -327,7 +512,7 @@ export default class PrinterAgentSettingsPanel extends PureComponent { this.setState({ newAgentName: e.target.value })} onKeyDown={(e) => { @@ -346,7 +531,9 @@ export default class PrinterAgentSettingsPanel extends PureComponent { spellCheck: false, }, input: { - startAdornment: , + startAdornment: ( + + ), }, }} sx={{ flex: 1 }} @@ -370,16 +557,23 @@ export default class PrinterAgentSettingsPanel extends PureComponent { Keine Drucker-Agenten angelegt. ) : ( - + {printeragents.map((agent, index) => { const isDeleting = deletingId === agent.id; const isPatching = patchingAgentId === agent.id; const isEditing = editingId === agent.id; const isPaired = agent.pairedAt != null; + const isEnabled = agent.enabled !== false; + const clientState = agent.clientstate; + const isOnline = clientState === 'online'; const pairedDateText = formatDateTime(agent.pairedAt); const lastseenText = formatDateTime(agent.lastseenAt) ?? 'Nie'; const updatedText = formatDateTime(agent.updatedAt) ?? '—'; + const printersForAgent = agentprinters.filter( + (p) => p.printeragentId === agent.id, + ); + return ( - {/* Header: Icon, Name (or Edit input), Status chip, Actions */} + {/* Header: Icon, Name (or Edit input), Status chips, Enabled switch, Delete */} {isEditing ? ( @@ -455,7 +661,11 @@ export default class PrinterAgentSettingsPanel extends PureComponent { ) : ( - + {agent.name} )} + {/* Pairing status badge */} {isPaired ? ( } @@ -488,10 +699,59 @@ export default class PrinterAgentSettingsPanel extends PureComponent { sx={{ fontWeight: 600, fontSize: '0.75rem' }} /> )} + + {/* Live connection state */} + {clientState ? ( + + ) : null} + + {/* Agent version */} + {agent.agentversion ? ( + + + + ) : null} - {/* Delete control */} + {/* Enabled Switch and Delete action */} + void this.handleToggleAgentEnabled(agent, e.target.checked)} + color="success" + /> + } + label={ + + {isEnabled ? 'Aktiv' : 'Inaktiv'} + + } + sx={{ m: 0 }} + /> + - {/* Body: Pairing details with QR code or Paired status info */} + {/* Body: Pairing details with QR code or Discovered Printers */} - - - + + + + + Kopplungs-PIN: - + + } + label={agent.pairingpin || '—'} + onClick={() => { + if (agent.pairingpin) { + navigator.clipboard?.writeText(agent.pairingpin); + enqueueSnackbar('PIN in Zwischenablage kopiert.', { variant: 'info' }); + } + }} + clickable + color="warning" + variant="filled" + sx={{ + fontFamily: 'monospace', + fontWeight: 700, + fontSize: '0.9rem', + letterSpacing: '0.1em', + px: 0.5, + }} + /> + @@ -567,75 +839,205 @@ export default class PrinterAgentSettingsPanel extends PureComponent { variant="outlined" sx={{ fontFamily: 'monospace', fontSize: '0.8rem' }} /> + + { + navigator.clipboard?.writeText(qrPayload); + enqueueSnackbar('QR-Daten kopiert.', { variant: 'info' }); + }} + sx={{ p: 0.5 }} + > + + + + + + Scannen Sie den QR-Code mit dem Drucker-Agenten oder geben Sie Domain und PIN manuell ein. + ); })() ) : ( - - {agent.clientserial ? ( - - ) : null} - {agent.clientmodel ? ( - - ) : null} - {agent.agentversion ? ( - - ) : null} - {agent.clientstate ? ( - - ) : null} + /* Discovered printers section */ + + + + Erkannte Drucker ({printersForAgent.length}) + + + {printersForAgent.length === 0 ? ( + + Keine Drucker erkannt. Schließen Sie einen Drucker per USB an den Agenten an. + + ) : ( + + {printersForAgent.map((p, pIdx) => { + const pEnabled = p.enabled !== false; + const live = + (p.serial ? livePrintersBySerial.get(p.serial) : null) ?? + livePrinters.find( + (lp) => lp.agentId === agent.id && lp.serial === p.serial, + ); + + return ( + + + + + {p.model || 'Drucker'} + + {p.type ? ( + + ) : null} + {p.serial ? ( + + ({p.serial}) + + ) : null} + {renderPrinterDiagnostics(p, live)} + + + + {/* Enabled toggle switch */} + + void this.handleTogglePrinterEnabled(p, e.target.checked) + } + /> + } + label={ + + {pEnabled ? 'Aktiv' : 'Inaktiv'} + + } + sx={{ m: 0 }} + /> + + {/* Reorder buttons */} + + + + void this.handleMovePrinter(p, 'up')} + sx={{ p: 0.25 }} + > + + + + + + + void this.handleMovePrinter(p, 'down')} + sx={{ p: 0.25 }} + > + + + + + + + {/* Delete printer button */} + + void this.handleDeletePrinter(p)} + sx={{ p: 0.5 }} + > + + + + + + ); + })} + + )} )} - {/* Row with Sort Order Controls */} - + {/* Agent sort order controls */} + Reihenfolge: - - void this.handleMoveAgent(agent, 'up')} - sx={{ p: 0.25 }} - > - - - - - - void this.handleMoveAgent(agent, 'down')} - sx={{ p: 0.25 }} - > - - - + + + void this.handleMoveAgent(agent, 'up')} + sx={{ p: 0.25 }} + > + + + + + + + void this.handleMoveAgent(agent, 'down')} + sx={{ p: 0.25 }} + > + + + +