This commit is contained in:
seb
2026-10-30 17:40:15 +01:00
parent 56d376c9bd
commit 9cb82745e3

View File

@@ -5,19 +5,18 @@ import Chip from '@mui/material/Chip';
import Divider from '@mui/material/Divider'; import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
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 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 BackendError from '../BackendError'; import BackendError from '../BackendError';
import companyAutomat from '../../automats/companyAutomat'; import companyAutomat from '../../automats/companyAutomat';
@@ -41,6 +40,80 @@ function formatDateTime(val) {
} }
} }
class AgentQrCode extends PureComponent {
constructor(props) {
super(props);
this.state = { dataUrl: null };
}
componentDidMount() {
this.generateQr();
}
componentDidUpdate(prevProps) {
if (prevProps.value !== this.props.value || prevProps.size !== this.props.size) {
this.generateQr();
}
}
async generateQr() {
const { value, size = 110 } = this.props;
if (!value) {
this.setState({ dataUrl: null });
return;
}
try {
const dataUrl = await QRCode.toDataURL(value, {
width: size,
margin: 1,
color: {
dark: '#000000',
light: '#ffffff',
},
errorCorrectionLevel: 'M',
});
this.setState({ dataUrl });
} catch (err) {
console.error('Failed to generate QR code:', err);
}
}
render() {
const { size = 110 } = this.props;
const { dataUrl } = this.state;
if (!dataUrl) {
return (
<Box
sx={{
width: size,
height: size,
bgcolor: 'action.hover',
borderRadius: 1,
}}
/>
);
}
return (
<Box
component="img"
src={dataUrl}
alt="Pairing QR Code"
sx={{
width: size,
height: size,
borderRadius: 1,
border: '1px solid',
borderColor: 'divider',
bgcolor: '#fff',
p: 0.5,
display: 'block',
flexShrink: 0,
}}
/>
);
}
}
export default class PrinterAgentSettingsPanel extends PureComponent { export default class PrinterAgentSettingsPanel extends PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
@@ -76,7 +149,7 @@ 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(() => { });
} }
} }
@@ -385,16 +458,14 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
<Typography variant="subtitle1" fontWeight={600}> <Typography variant="subtitle1" fontWeight={600}>
{agent.name} {agent.name}
</Typography> </Typography>
<Tooltip title="Name bearbeiten"> <IconButton
<IconButton size="small"
size="small" onClick={() => this.handleStartRename(agent)}
onClick={() => this.handleStartRename(agent)} disabled={isPatching || isDeleting}
disabled={isPatching || isDeleting} sx={{ p: 0.5, color: 'text.secondary' }}
sx={{ p: 0.5, color: 'text.secondary' }} >
> <EditOutlinedIcon sx={{ fontSize: 16 }} />
<EditOutlinedIcon sx={{ fontSize: 16 }} /> </IconButton>
</IconButton>
</Tooltip>
</Box> </Box>
)} )}
@@ -433,48 +504,74 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
</Box> </Box>
</Box> </Box>
{/* Body: Pairing details or PIN info */} {/* Body: Pairing details with QR code or Paired status info */}
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
alignItems: 'center', flexDirection: 'column',
justifyContent: 'space-between', gap: 1.5,
flexWrap: 'wrap',
gap: 2,
pt: 0.5, pt: 0.5,
}} }}
> >
{!isPaired ? ( {!isPaired ? (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, flexWrap: 'wrap' }}> (() => {
<Typography variant="body2" color="text.secondary"> const domain = typeof window !== 'undefined' ? window.location.host : '';
Kopplungs-PIN: const qrPayload = JSON.stringify({
</Typography> domain,
<Tooltip title="PIN in die Zwischenablage kopieren"> pin: String(agent.pairingpin || ''),
<Chip });
icon={<ContentCopyIcon sx={{ fontSize: 14 }} />} return (
label={agent.pairingpin || '—'} <Box
onClick={() => {
if (agent.pairingpin) {
navigator.clipboard?.writeText(agent.pairingpin);
enqueueSnackbar('PIN in Zwischenablage kopiert.', { variant: 'info' });
}
}}
clickable
color="warning"
variant="filled"
sx={{ sx={{
fontFamily: 'monospace', display: 'flex',
fontWeight: 700, alignItems: 'center',
fontSize: '0.9rem', gap: 2,
letterSpacing: '0.1em', flexWrap: 'wrap',
px: 0.5, bgcolor: 'action.hover',
p: 1.5,
borderRadius: 1.5,
border: '1px solid',
borderColor: 'warning.light',
}} }}
/> >
</Tooltip> <Box sx={{ display: 'inline-flex' }}>
<Typography variant="caption" color="text.secondary"> <AgentQrCode value={qrPayload} size={110} />
Geben Sie diesen PIN auf dem Drucker-Agent-Gerät ein. </Box>
</Typography>
</Box> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, flex: 1, minWidth: 220 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap' }}>
<Typography variant="body2" color="text.secondary" sx={{ fontWeight: 500 }}>
Kopplungs-PIN:
</Typography>
<Chip
label={agent.pairingpin || '—'}
color="warning"
variant="filled"
sx={{
fontFamily: 'monospace',
fontWeight: 700,
fontSize: '0.9rem',
letterSpacing: '0.1em',
px: 0.5,
}}
/>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap' }}>
<Typography variant="body2" color="text.secondary" sx={{ fontWeight: 500 }}>
Domain:
</Typography>
<Chip
label={domain}
size="small"
variant="outlined"
sx={{ fontFamily: 'monospace', fontSize: '0.8rem' }}
/>
</Box>
</Box>
</Box>
);
})()
) : ( ) : (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, flexWrap: 'wrap' }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, flexWrap: 'wrap' }}>
{agent.clientserial ? ( {agent.clientserial ? (
@@ -513,35 +610,32 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
</Box> </Box>
)} )}
{/* Sort Order Controls */} {/* Row with Sort Order Controls */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, ml: 'auto' }}> <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>
<Tooltip title="Nach oben verschieben"> <span>
<span> <IconButton
<IconButton size="small"
size="small" disabled={index === 0 || isPatching || isDeleting}
disabled={index === 0 || isPatching || isDeleting} onClick={() => void this.handleMoveAgent(agent, 'up')}
onClick={() => void this.handleMoveAgent(agent, 'up')} sx={{ p: 0.25 }}
sx={{ p: 0.25 }} >
> <ArrowUpwardIcon sx={{ fontSize: 16 }} />
<ArrowUpwardIcon sx={{ fontSize: 16 }} /> </IconButton>
</IconButton> </span>
</span>
</Tooltip> <span>
<Tooltip title="Nach unten verschieben"> <IconButton
<span> size="small"
<IconButton disabled={index === printeragents.length - 1 || isPatching || isDeleting}
size="small" onClick={() => void this.handleMoveAgent(agent, 'down')}
disabled={index === printeragents.length - 1 || isPatching || isDeleting} sx={{ p: 0.25 }}
onClick={() => void this.handleMoveAgent(agent, 'down')} >
sx={{ p: 0.25 }} <ArrowDownwardIcon sx={{ fontSize: 16 }} />
> </IconButton>
<ArrowDownwardIcon sx={{ fontSize: 16 }} /> </span>
</IconButton>
</span>
</Tooltip>
<TextField <TextField
type="number" type="number"
size="small" size="small"