u
This commit is contained in:
@@ -1,12 +1,256 @@
|
||||
import { Component } from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Button from '@mui/material/Button';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import PrintIcon from '@mui/icons-material/Print';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
|
||||
import { labeltemplate, printer, article } from '../../../api';
|
||||
import { notifyError } from '../../../utils/notify';
|
||||
import BackendError from '../../BackendError';
|
||||
|
||||
export default class ArticleLabelTab extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
templates: null,
|
||||
printers: null,
|
||||
selectedTemplateId: '',
|
||||
selectedPrinterId: '',
|
||||
quantity: 1,
|
||||
loading: true,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
async loadData() {
|
||||
this.setState({ loading: true, error: null });
|
||||
try {
|
||||
const [resTemplates, resPrinters] = await Promise.all([
|
||||
labeltemplate.list(),
|
||||
printer.list(),
|
||||
]);
|
||||
|
||||
const templates = resTemplates.labeltemplates || [];
|
||||
const zplPrinters = (resPrinters.printers || []).filter((p) => p.type === 'zpl');
|
||||
|
||||
this.setState({
|
||||
templates,
|
||||
printers: zplPrinters,
|
||||
selectedTemplateId: templates.length > 0 ? templates[0].id : '',
|
||||
selectedPrinterId: zplPrinters.length > 0 ? zplPrinters[0].id : '',
|
||||
loading: false,
|
||||
});
|
||||
} catch (error) {
|
||||
this.setState({ error, loading: false });
|
||||
}
|
||||
}
|
||||
|
||||
handlePrint = async () => {
|
||||
const { selectedTemplateId, selectedPrinterId, quantity } = this.state;
|
||||
const { article: articleData } = this.props;
|
||||
|
||||
if (!selectedTemplateId || !selectedPrinterId || quantity < 1) {
|
||||
enqueueSnackbar('Bitte Vorlage, Drucker und Menge auswählen', { variant: 'warning' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await article.printLabel(articleData.sku, {
|
||||
templateId: selectedTemplateId,
|
||||
printerId: selectedPrinterId,
|
||||
quantity,
|
||||
});
|
||||
enqueueSnackbar('Druckauftrag erfolgreich gesendet', { variant: 'success' });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
}
|
||||
};
|
||||
|
||||
export default class ArticleLabelTab extends Component {
|
||||
render() {
|
||||
const {
|
||||
templates,
|
||||
printers,
|
||||
selectedTemplateId,
|
||||
selectedPrinterId,
|
||||
quantity,
|
||||
loading,
|
||||
error,
|
||||
} = this.state;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box sx={{ p: 4, display: 'flex', justifyContent: 'center' }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<BackendError error={error} onRetry={() => this.loadData()} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (templates.length === 0) {
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Typography color="text.secondary">
|
||||
Es sind keine ZPL-Etikettenvorlagen vorhanden.
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (printers.length === 0) {
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Typography color="text.secondary">
|
||||
Es sind keine ZPL-Drucker konfiguriert.
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Label
|
||||
</Typography>
|
||||
<Box sx={{ p: 3, display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<Box>
|
||||
<Grid container spacing={2}>
|
||||
{templates.map((t) => {
|
||||
const isSelected = t.id === selectedTemplateId;
|
||||
return (
|
||||
<Grid item xs={12} sm={6} md={4} key={t.id}>
|
||||
<Paper
|
||||
variant="outlined"
|
||||
onClick={() => this.setState({ selectedTemplateId: t.id })}
|
||||
sx={{
|
||||
p: 2,
|
||||
cursor: 'pointer',
|
||||
border: '2px solid',
|
||||
borderColor: isSelected ? 'primary.main' : 'divider',
|
||||
bgcolor: isSelected ? 'action.selected' : 'background.paper',
|
||||
boxShadow: isSelected ? 3 : 0,
|
||||
transition: 'all 0.2s',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
position: 'relative',
|
||||
gap: 2,
|
||||
'&:hover': {
|
||||
borderColor: 'primary.main',
|
||||
boxShadow: isSelected ? 3 : 1,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{isSelected && (
|
||||
<CheckCircleIcon
|
||||
color="primary"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
right: 8,
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: 120,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
bgcolor: 'background.default',
|
||||
borderRadius: 1,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{t.thumb ? (
|
||||
<img
|
||||
src={`data:image/png;base64,${t.thumb}`}
|
||||
alt={t.name}
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Typography variant="caption" color="text.disabled">
|
||||
Keine Vorschau
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<Box sx={{ width: '100%', textAlign: 'center' }}>
|
||||
<Typography variant="subtitle2" noWrap title={t.name}>
|
||||
{t.name}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{t.width}x{t.height} ({t.dpi} dpi)
|
||||
</Typography>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 3, alignItems: 'flex-start' }}>
|
||||
<TextField
|
||||
select
|
||||
label="Drucker"
|
||||
value={selectedPrinterId}
|
||||
onChange={(e) => this.setState({ selectedPrinterId: e.target.value })}
|
||||
sx={{ minWidth: 200 }}
|
||||
size="small"
|
||||
>
|
||||
{printers.map((p) => (
|
||||
<MenuItem key={p.id} value={p.id}>
|
||||
{p.desc ? `${p.desc} (${p.address})` : p.address}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
type="number"
|
||||
label="Anzahl"
|
||||
value={quantity}
|
||||
onChange={(e) => {
|
||||
const val = parseInt(e.target.value, 10);
|
||||
this.setState({ quantity: isNaN(val) ? 1 : Math.max(1, val) });
|
||||
}}
|
||||
inputProps={{ min: 1, max: 9999 }}
|
||||
sx={{ width: 100 }}
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large"
|
||||
startIcon={<PrintIcon />}
|
||||
onClick={this.handlePrint}
|
||||
disabled={!selectedTemplateId || !selectedPrinterId || quantity < 1}
|
||||
sx={{ height: 40 }}
|
||||
>
|
||||
Etiketten drucken
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user