u
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user