u
This commit is contained in:
@@ -2,6 +2,67 @@ import js from '@eslint/js';
|
|||||||
import globals from 'globals';
|
import globals from 'globals';
|
||||||
import reactHooks from 'eslint-plugin-react-hooks';
|
import reactHooks from 'eslint-plugin-react-hooks';
|
||||||
|
|
||||||
|
const banMuiTooltipRule = {
|
||||||
|
meta: {
|
||||||
|
type: 'problem',
|
||||||
|
docs: {
|
||||||
|
description: 'Ban MUI Tooltip component',
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
badDesign: '<Tooltip> is bad design',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create(context) {
|
||||||
|
const muiTooltipIdentifiers = new Set();
|
||||||
|
|
||||||
|
return {
|
||||||
|
ImportDeclaration(node) {
|
||||||
|
if (node.source.value === '@mui/material/Tooltip') {
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
messageId: 'badDesign',
|
||||||
|
});
|
||||||
|
for (const specifier of node.specifiers) {
|
||||||
|
if (specifier.local?.name) {
|
||||||
|
muiTooltipIdentifiers.add(specifier.local.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (node.source.value === '@mui/material') {
|
||||||
|
for (const specifier of node.specifiers) {
|
||||||
|
if (
|
||||||
|
specifier.type === 'ImportSpecifier' &&
|
||||||
|
specifier.imported?.name === 'Tooltip'
|
||||||
|
) {
|
||||||
|
context.report({
|
||||||
|
node: specifier,
|
||||||
|
messageId: 'badDesign',
|
||||||
|
});
|
||||||
|
if (specifier.local?.name) {
|
||||||
|
muiTooltipIdentifiers.add(specifier.local.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
JSXOpeningElement(node) {
|
||||||
|
let tagName = '';
|
||||||
|
if (node.name.type === 'JSXIdentifier') {
|
||||||
|
tagName = node.name.name;
|
||||||
|
} else if (node.name.type === 'JSXMemberExpression') {
|
||||||
|
tagName = node.name.property.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (muiTooltipIdentifiers.has(tagName) || tagName === 'Tooltip') {
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
messageId: 'badDesign',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
ignores: [
|
ignores: [
|
||||||
@@ -32,10 +93,16 @@ export default [
|
|||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
'react-hooks': reactHooks,
|
'react-hooks': reactHooks,
|
||||||
|
react: {
|
||||||
|
rules: {
|
||||||
|
'no-mui-tooltip': banMuiTooltipRule,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'react-hooks/rules-of-hooks': 'error',
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
'react-hooks/exhaustive-deps': 'warn',
|
'react-hooks/exhaustive-deps': 'warn',
|
||||||
|
'react/no-mui-tooltip': 'error',
|
||||||
'no-unused-vars': [
|
'no-unused-vars': [
|
||||||
'warn',
|
'warn',
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"dev:pwa:https": "VITE_HTTPS=1 node scripts/dev.mjs",
|
"dev:pwa:https": "VITE_HTTPS=1 node scripts/dev.mjs",
|
||||||
"cert": "./scripts/generate-cert.sh",
|
"cert": "./scripts/generate-cert.sh",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"preview:https": "VITE_HTTPS=1 vite preview",
|
"preview:https": "VITE_HTTPS=1 vite preview",
|
||||||
"rig:collect": "node test/collect.mjs",
|
"rig:collect": "node test/collect.mjs",
|
||||||
|
|||||||
@@ -41,16 +41,19 @@ export const agentprinter = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* PUT /api/agentprinter/order
|
* PUT /api/agentprinter/order
|
||||||
* Body: { printeragentId: number, agentprinterIds: number[] }
|
* Body: { printeragentId?: number, agentprinterIds: number[] }
|
||||||
* → { agentprinterIds: number[] }
|
* → { agentprinterIds: number[] }
|
||||||
*/
|
*/
|
||||||
order({ printeragentId, agentprinterIds }) {
|
order({ printeragentId, agentprinterIds }) {
|
||||||
|
const body = {
|
||||||
|
agentprinterIds: Array.isArray(agentprinterIds) ? agentprinterIds.map(Number) : [],
|
||||||
|
};
|
||||||
|
if (printeragentId != null) {
|
||||||
|
body.printeragentId = Number(printeragentId);
|
||||||
|
}
|
||||||
return http('/api/agentprinter/order', {
|
return http('/api/agentprinter/order', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: {
|
body,
|
||||||
printeragentId: Number(printeragentId),
|
|
||||||
agentprinterIds: Array.isArray(agentprinterIds) ? agentprinterIds.map(Number) : [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ 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 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';
|
||||||
@@ -259,12 +258,12 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
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') {
|
if (companyAutomat.state.agentprintersStatus === 'idle') {
|
||||||
void companyAutomat.actions.loadAgentPrinters().catch(() => {});
|
void companyAutomat.actions.loadAgentPrinters().catch(() => { });
|
||||||
}
|
}
|
||||||
void companyAutomat.actions.loadLivePrinters().catch(() => {});
|
void companyAutomat.actions.loadLivePrinters().catch(() => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@@ -274,7 +273,7 @@ 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.loadAgentPrinters().catch(notifyError);
|
||||||
void companyAutomat.actions.loadLivePrinters().catch(() => {});
|
void companyAutomat.actions.loadLivePrinters().catch(() => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCreateAgent() {
|
async handleCreateAgent() {
|
||||||
@@ -440,21 +439,17 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
|
|
||||||
async handleMovePrinter(printer, direction) {
|
async handleMovePrinter(printer, direction) {
|
||||||
const { agentprinters } = this.state;
|
const { agentprinters } = this.state;
|
||||||
const printersForAgent = agentprinters.filter(
|
const currentIndex = agentprinters.findIndex((p) => p.id === printer.id);
|
||||||
(p) => p.printeragentId === printer.printeragentId,
|
|
||||||
);
|
|
||||||
const currentIndex = printersForAgent.findIndex((p) => p.id === printer.id);
|
|
||||||
if (currentIndex === -1) return;
|
if (currentIndex === -1) return;
|
||||||
const targetIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;
|
const targetIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;
|
||||||
if (targetIndex < 0 || targetIndex >= printersForAgent.length) return;
|
if (targetIndex < 0 || targetIndex >= agentprinters.length) return;
|
||||||
|
|
||||||
const reordered = [...printersForAgent];
|
const reordered = [...agentprinters];
|
||||||
const [moved] = reordered.splice(currentIndex, 1);
|
const [moved] = reordered.splice(currentIndex, 1);
|
||||||
reordered.splice(targetIndex, 0, moved);
|
reordered.splice(targetIndex, 0, moved);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await companyAutomat.actions.reorderAgentPrinters({
|
await companyAutomat.actions.reorderAgentPrinters({
|
||||||
printeragentId: printer.printeragentId,
|
|
||||||
agentprinterIds: reordered.map((p) => p.id),
|
agentprinterIds: reordered.map((p) => p.id),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -463,6 +458,11 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleDeletePrinter(printer) {
|
async handleDeletePrinter(printer) {
|
||||||
|
const isOnline = Boolean(printer?.online);
|
||||||
|
if (isOnline) {
|
||||||
|
enqueueSnackbar('Online-Drucker können nicht entfernt werden.', { variant: 'warning' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
const label = printer.model || printer.serial || `#${printer.id}`;
|
const label = printer.model || printer.serial || `#${printer.id}`;
|
||||||
if (!window.confirm(`Möchten Sie den Drucker „${label}“ wirklich entfernen?`)) {
|
if (!window.confirm(`Möchten Sie den Drucker „${label}“ wirklich entfernen?`)) {
|
||||||
return;
|
return;
|
||||||
@@ -521,6 +521,7 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
for (const lp of livePrinters) {
|
for (const lp of livePrinters) {
|
||||||
if (lp.serial) livePrintersBySerial.set(lp.serial, lp);
|
if (lp.serial) livePrintersBySerial.set(lp.serial, lp);
|
||||||
}
|
}
|
||||||
|
const agentMap = new Map(printeragents.map((a) => [a.id, a]));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 3, maxWidth: 900, mx: 'auto' }}>
|
<Box sx={{ p: 3, maxWidth: 900, mx: 'auto' }}>
|
||||||
@@ -528,18 +529,13 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
<Typography variant="h6" fontWeight={600} gutterBottom>
|
<Typography variant="h6" fontWeight={600} gutterBottom>
|
||||||
Drucker-Agenten
|
Drucker-Agenten
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
|
||||||
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.
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Add new agent row */}
|
{/* Add new agent row */}
|
||||||
<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, Küche)"
|
placeholder="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) => {
|
||||||
@@ -595,10 +591,6 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
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}
|
||||||
@@ -727,7 +719,6 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
|
|
||||||
{/* Agent version */}
|
{/* Agent version */}
|
||||||
{agent.agentversion ? (
|
{agent.agentversion ? (
|
||||||
<Tooltip title="Agent Software-Version">
|
|
||||||
<Chip
|
<Chip
|
||||||
label={`v${agent.agentversion}`}
|
label={`v${agent.agentversion}`}
|
||||||
size="small"
|
size="small"
|
||||||
@@ -735,7 +726,6 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
color="info"
|
color="info"
|
||||||
sx={{ fontFamily: 'monospace', fontWeight: 600, fontSize: '0.75rem' }}
|
sx={{ fontFamily: 'monospace', fontWeight: 600, fontSize: '0.75rem' }}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
) : null}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -808,18 +798,15 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
borderColor: 'warning.light',
|
borderColor: 'warning.light',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip title="QR-Code zum Koppeln mit dem Drucker-Agenten">
|
|
||||||
<Box sx={{ display: 'inline-flex' }}>
|
<Box sx={{ display: 'inline-flex' }}>
|
||||||
<AgentQrCode value={qrPayload} size={110} />
|
<AgentQrCode value={qrPayload} size={110} />
|
||||||
</Box>
|
</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>
|
||||||
<Tooltip title="PIN in die Zwischenablage kopieren">
|
|
||||||
<Chip
|
<Chip
|
||||||
icon={<ContentCopyIcon sx={{ fontSize: 14 }} />}
|
icon={<ContentCopyIcon sx={{ fontSize: 14 }} />}
|
||||||
label={agent.pairingpin || '—'}
|
label={agent.pairingpin || '—'}
|
||||||
@@ -840,7 +827,6 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
px: 0.5,
|
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' }}>
|
||||||
@@ -853,7 +839,6 @@ 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
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -864,7 +849,6 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
>
|
>
|
||||||
<ContentCopyIcon sx={{ fontSize: 14 }} />
|
<ContentCopyIcon sx={{ fontSize: 14 }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
@@ -874,170 +858,7 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
})()
|
})()
|
||||||
) : (
|
|
||||||
/* Discovered printers section */
|
|
||||||
<Box sx={{ p: 1.5, borderRadius: 1.5, bgcolor: 'action.hover' }}>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
|
|
||||||
<Typography variant="subtitle2" fontWeight={600} color="text.secondary">
|
|
||||||
Erkannte Drucker ({printersForAgent.length})
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
{printersForAgent.length === 0 ? (
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
color="text.secondary"
|
|
||||||
sx={{ fontStyle: 'italic', py: 0.5 }}
|
|
||||||
>
|
|
||||||
Keine Drucker erkannt. Schließen Sie einen Drucker per USB an den Agenten an.
|
|
||||||
</Typography>
|
|
||||||
) : (
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
|
||||||
{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 (
|
|
||||||
<Box
|
|
||||||
key={p.id}
|
|
||||||
sx={{
|
|
||||||
p: 1.25,
|
|
||||||
borderRadius: 1,
|
|
||||||
border: '1px solid',
|
|
||||||
borderColor: 'divider',
|
|
||||||
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}
|
) : 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 }}>
|
|
||||||
{/* Test print button */}
|
|
||||||
<Tooltip title="Test-Etikett an Drucker senden (ZPL)">
|
|
||||||
<span>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
variant="outlined"
|
|
||||||
color="primary"
|
|
||||||
startIcon={<PrintOutlinedIcon sx={{ fontSize: 15 }} />}
|
|
||||||
disabled={testingPrinterId === p.id}
|
|
||||||
onClick={() => void this.handleTestPrint(p)}
|
|
||||||
sx={{
|
|
||||||
textTransform: 'none',
|
|
||||||
fontSize: '0.75rem',
|
|
||||||
py: 0.25,
|
|
||||||
px: 1,
|
|
||||||
minHeight: 26,
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{testingPrinterId === p.id ? 'Druckt…' : 'Testdruck'}
|
|
||||||
</Button>
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{/* 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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Agent sort order controls */}
|
{/* Agent sort order controls */}
|
||||||
<Box
|
<Box
|
||||||
@@ -1052,7 +873,6 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
Reihenfolge:
|
Reihenfolge:
|
||||||
</Typography>
|
</Typography>
|
||||||
<Tooltip title="Nach oben verschieben">
|
|
||||||
<span>
|
<span>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
@@ -1063,8 +883,6 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
<ArrowUpwardIcon sx={{ fontSize: 16 }} />
|
<ArrowUpwardIcon sx={{ fontSize: 16 }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title="Nach unten verschieben">
|
|
||||||
<span>
|
<span>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
@@ -1075,7 +893,6 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
<ArrowDownwardIcon sx={{ fontSize: 16 }} />
|
<ArrowDownwardIcon sx={{ fontSize: 16 }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
|
||||||
<TextField
|
<TextField
|
||||||
type="number"
|
type="number"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -1128,6 +945,187 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
|||||||
})}
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Separate Printers Section */}
|
||||||
|
<Divider sx={{ my: 4 }} />
|
||||||
|
|
||||||
|
<Box sx={{ mb: 2 }}>
|
||||||
|
<Typography variant="h6" fontWeight={600} gutterBottom>
|
||||||
|
Drucker
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{agentprinters.length === 0 ? (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center',
|
||||||
|
py: 5,
|
||||||
|
color: 'text.secondary',
|
||||||
|
bgcolor: 'action.hover',
|
||||||
|
borderRadius: 2,
|
||||||
|
border: '1px dashed',
|
||||||
|
borderColor: 'divider',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PrintOutlinedIcon sx={{ fontSize: 44, mb: 1, opacity: 0.5 }} />
|
||||||
|
<Typography variant="body2">
|
||||||
|
Keine Drucker erkannt. Schließen Sie Drucker per USB an einen gekoppelten Drucker-Agenten an.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
||||||
|
{agentprinters.map((p, pIdx) => {
|
||||||
|
const pEnabled = p.enabled !== false;
|
||||||
|
const live =
|
||||||
|
(p.serial ? livePrintersBySerial.get(p.serial) : null) ??
|
||||||
|
livePrinters.find(
|
||||||
|
(lp) => lp.agentId === p.printeragentId && lp.serial === p.serial,
|
||||||
|
);
|
||||||
|
const isOnline = Boolean(
|
||||||
|
p.online ?? (live ? !live.unreachable && live.status !== 'offline' : false),
|
||||||
|
);
|
||||||
|
const parentAgent = agentMap.get(p.printeragentId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={p.id}
|
||||||
|
sx={{
|
||||||
|
p: 1.5,
|
||||||
|
borderRadius: 1.5,
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
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: 22, 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}
|
||||||
|
{parentAgent ? (
|
||||||
|
<Chip
|
||||||
|
label={`Agent: ${parentAgent.name}`}
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ fontSize: '0.7rem', height: 20 }}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{renderPrinterDiagnostics(p, live)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
{/* Test print button */}
|
||||||
|
<span>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<PrintOutlinedIcon sx={{ fontSize: 15 }} />}
|
||||||
|
disabled={testingPrinterId === p.id}
|
||||||
|
onClick={() => void this.handleTestPrint(p)}
|
||||||
|
sx={{
|
||||||
|
textTransform: 'none',
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
py: 0.25,
|
||||||
|
px: 1,
|
||||||
|
minHeight: 26,
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{testingPrinterId === p.id ? 'Druckt…' : 'Testdruck'}
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* 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 across all printers */}
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
disabled={pIdx === 0}
|
||||||
|
onClick={() => void this.handleMovePrinter(p, 'up')}
|
||||||
|
sx={{ p: 0.25 }}
|
||||||
|
>
|
||||||
|
<ArrowUpwardIcon sx={{ fontSize: 16 }} />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
disabled={pIdx === agentprinters.length - 1}
|
||||||
|
onClick={() => void this.handleMovePrinter(p, 'down')}
|
||||||
|
sx={{ p: 0.25 }}
|
||||||
|
>
|
||||||
|
<ArrowDownwardIcon sx={{ fontSize: 16 }} />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Delete printer button: disabled when online! */}
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
color="error"
|
||||||
|
disabled={isOnline}
|
||||||
|
onClick={() => void this.handleDeletePrinter(p)}
|
||||||
|
sx={{ p: 0.5 }}
|
||||||
|
>
|
||||||
|
<DeleteOutlinedIcon sx={{ fontSize: 18 }} />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import CircularProgress from '@mui/material/CircularProgress';
|
|||||||
import ListItemButton from '@mui/material/ListItemButton';
|
import ListItemButton from '@mui/material/ListItemButton';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import Skeleton from '@mui/material/Skeleton';
|
import Skeleton from '@mui/material/Skeleton';
|
||||||
import Tooltip from '@mui/material/Tooltip';
|
|
||||||
import ViewModuleIcon from '@mui/icons-material/ViewModule';
|
import ViewModuleIcon from '@mui/icons-material/ViewModule';
|
||||||
import Chip from '@mui/material/Chip';
|
import Chip from '@mui/material/Chip';
|
||||||
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
||||||
@@ -207,11 +206,9 @@ class GlobalHistoryRow extends PureComponent {
|
|||||||
|
|
||||||
{/* 4. Meldung / Beschreibung */}
|
{/* 4. Meldung / Beschreibung */}
|
||||||
<Box sx={{ gridColumn: 4, gridRow: 2, minWidth: 0 }}>
|
<Box sx={{ gridColumn: 4, gridRow: 2, minWidth: 0 }}>
|
||||||
<Tooltip title={item.text || item.value || ''} placement="top-start" enterDelay={500}>
|
|
||||||
<Typography variant="body2" sx={ellipsisSx}>
|
<Typography variant="body2" sx={ellipsisSx}>
|
||||||
{item.text || item.value || '–'}
|
{item.text || item.value || '–'}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
</Box>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
);
|
);
|
||||||
@@ -445,11 +442,11 @@ export default class GlobalHistoryPage extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleReload() {
|
handleReload() {
|
||||||
void globalHistoryAutomat.actions.reload().catch(() => {});
|
void globalHistoryAutomat.actions.reload().catch(() => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRetry() {
|
handleRetry() {
|
||||||
void globalHistoryAutomat.actions.requireChunk(0, 0, { immediate: true }).catch(() => {});
|
void globalHistoryAutomat.actions.requireChunk(0, 0, { immediate: true }).catch(() => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
Reference in New Issue
Block a user