u
This commit is contained in:
1081
package-lock.json
generated
1081
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -41,7 +41,7 @@
|
||||
"pdfconvert-wasm": "^0.3.0",
|
||||
"react": "^19.3.1",
|
||||
"react-automat": "file:../automat",
|
||||
"react-canvas-label-editor": "github:sebseb7/react-canvas-label-editor",
|
||||
"react-canvas-label-editor": "github:sebseb7/react-canvas-label-editor#v20.0.0",
|
||||
"react-dom": "^19.3.1",
|
||||
"react-router-dom": "^7.18.4",
|
||||
"react-window": "^2.3.4",
|
||||
@@ -62,6 +62,7 @@
|
||||
},
|
||||
"allowScripts": {
|
||||
"canvas@3.2.3": true,
|
||||
"react-canvas-label-editor@20.0.0": true,
|
||||
"github:sebseb7/react-canvas-label-editor#7f0e31b66ff22da011e872b201017398b14e9dd9": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,5 +48,23 @@ export const labeltemplate = {
|
||||
},
|
||||
// #endregion
|
||||
|
||||
/**
|
||||
* POST /api/labeltemplate/testprint (or /api/labeltemplate/:id/testprint)
|
||||
* @param {number|object} idOrBody
|
||||
* @param {object} [maybeBody]
|
||||
* → 200 { success: true, printerjob?: PrinterJob }
|
||||
*/
|
||||
// #region POST /api/labeltemplate/testprint — testprint
|
||||
testprint(idOrBody, maybeBody) {
|
||||
if (typeof idOrBody === 'object' && idOrBody !== null) {
|
||||
return http('/api/labeltemplate/testprint', { method: 'POST', body: idOrBody });
|
||||
}
|
||||
return http(`/api/labeltemplate/${encodeURIComponent(idOrBody)}/testprint`, {
|
||||
method: 'POST',
|
||||
body: maybeBody || {},
|
||||
});
|
||||
},
|
||||
// #endregion
|
||||
|
||||
// thumb is returned inline as a base64 string in list/get responses
|
||||
};
|
||||
|
||||
@@ -9,27 +9,69 @@ import DialogTitle from '@mui/material/DialogTitle';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import InputAdornment from '@mui/material/InputAdornment';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import ToggleButton from '@mui/material/ToggleButton';
|
||||
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import LabelIcon from '@mui/icons-material/Label';
|
||||
import PrintIcon from '@mui/icons-material/Print';
|
||||
import BackendError from '../../components/BackendError';
|
||||
import { PanelTile, PanelTileGrid } from 'dashgui/panels';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
import { setLastArticleSku } from '../../automats/navigationAutomat';
|
||||
import { labeltemplate } from '../../api';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { labeltemplate, printer } from '../../api';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import {
|
||||
BLUE_BG,
|
||||
SURFACE_BASE,
|
||||
} from '../../colors';
|
||||
|
||||
function filterZplPrinters(agentprinters, fallbackPrinters = []) {
|
||||
const filteredAgent = (Array.isArray(agentprinters) ? agentprinters : []).filter(
|
||||
(p) => p.enabled !== false && (!p.type || p.type.toLowerCase() === 'zpl'),
|
||||
);
|
||||
if (filteredAgent.length > 0) return filteredAgent;
|
||||
|
||||
return (Array.isArray(fallbackPrinters) ? fallbackPrinters : []).filter(
|
||||
(p) => !p.type || p.type.toLowerCase() === 'zpl',
|
||||
);
|
||||
}
|
||||
|
||||
function getPrinterLabel(p) {
|
||||
let label;
|
||||
if (p.model && p.serial) {
|
||||
label = `${p.model} (${p.serial})`;
|
||||
} else if (p.model) {
|
||||
label = p.model;
|
||||
} else if (p.serial) {
|
||||
label = `Drucker (${p.serial})`;
|
||||
} else if (p.desc && p.address) {
|
||||
label = `${p.desc} (${p.address})`;
|
||||
} else if (p.address) {
|
||||
label = p.address;
|
||||
} else if (p.desc) {
|
||||
label = p.desc;
|
||||
} else {
|
||||
label = `Drucker #${p.id}`;
|
||||
}
|
||||
|
||||
if (p.online === false) {
|
||||
return `${label} (offline)`;
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
const CanvasEditor = lazy(async () => {
|
||||
await import('react-canvas-label-editor/style.css');
|
||||
const mod = await import('react-canvas-label-editor');
|
||||
@@ -65,11 +107,15 @@ class ArtikelVorlagenPage extends PureComponent {
|
||||
const initialDpi = DEFAULT_DPI;
|
||||
const initialWidthClass = '2';
|
||||
const initialHeightMm = DEFAULT_HEIGHT_MM;
|
||||
const initialPrinters = filterZplPrinters(companyAutomat.state.agentprinters);
|
||||
|
||||
this.state = {
|
||||
status: 'loading',
|
||||
error: null,
|
||||
templates: [],
|
||||
printers: initialPrinters,
|
||||
printerMenuAnchor: null,
|
||||
printing: false,
|
||||
// Editor dialog state
|
||||
editorOpen: false,
|
||||
editorLoading: false,
|
||||
@@ -88,6 +134,7 @@ class ArtikelVorlagenPage extends PureComponent {
|
||||
};
|
||||
this._cancelled = false;
|
||||
this._currentEditId = null;
|
||||
this._legacyPrinters = [];
|
||||
this.handleRetry = this.handleRetry.bind(this);
|
||||
this.handleOpenNew = this.handleOpenNew.bind(this);
|
||||
this.handleOpenEdit = this.handleOpenEdit.bind(this);
|
||||
@@ -97,16 +144,40 @@ class ArtikelVorlagenPage extends PureComponent {
|
||||
this.handleDpiChange = this.handleDpiChange.bind(this);
|
||||
this.handleHeightMmChange = this.handleHeightMmChange.bind(this);
|
||||
this.handleHeightMmBlur = this.handleHeightMmBlur.bind(this);
|
||||
this.handlePrintClick = this.handlePrintClick.bind(this);
|
||||
this.handleClosePrinterMenu = this.handleClosePrinterMenu.bind(this);
|
||||
this.handleSelectPrinterForPrint = this.handleSelectPrinterForPrint.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
setLastArticleSku(null);
|
||||
this._cancelled = false;
|
||||
void this.loadTemplates();
|
||||
this.unsubPrinters = companyAutomat.subscribe(this, (state) => ({
|
||||
printers: filterZplPrinters(state.agentprinters, this._legacyPrinters),
|
||||
}));
|
||||
void companyAutomat.actions.loadAgentPrinters().catch(() => {});
|
||||
void this.loadFallbackPrinters();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._cancelled = true;
|
||||
this.unsubPrinters?.();
|
||||
}
|
||||
|
||||
async loadFallbackPrinters() {
|
||||
try {
|
||||
const resPrinters = await printer.list();
|
||||
this._legacyPrinters = (resPrinters.printers || []).filter(
|
||||
(p) => !p.type || p.type.toLowerCase() === 'zpl',
|
||||
);
|
||||
if (this._cancelled) return;
|
||||
this.setState({
|
||||
printers: filterZplPrinters(companyAutomat.state.agentprinters, this._legacyPrinters),
|
||||
});
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
async loadTemplates() {
|
||||
@@ -246,9 +317,67 @@ class ArtikelVorlagenPage extends PureComponent {
|
||||
}
|
||||
|
||||
handleEditorClose() {
|
||||
if (this.state.saving) return;
|
||||
if (this.state.saving || this.state.printing) return;
|
||||
this._currentEditId = null;
|
||||
this.setState({ editorOpen: false, editorLoading: false, editorTemplate: null });
|
||||
this.setState({
|
||||
editorOpen: false,
|
||||
editorLoading: false,
|
||||
editorTemplate: null,
|
||||
printerMenuAnchor: null,
|
||||
});
|
||||
}
|
||||
|
||||
handlePrintClick(event) {
|
||||
const { printers } = this.state;
|
||||
if (!printers || printers.length === 0) {
|
||||
enqueueSnackbar('Kein ZPL-Drucker gefunden', { variant: 'warning' });
|
||||
return;
|
||||
}
|
||||
if (printers.length === 1) {
|
||||
void this.executeTestPrint(printers[0]);
|
||||
return;
|
||||
}
|
||||
this.setState({ printerMenuAnchor: event.currentTarget });
|
||||
}
|
||||
|
||||
handleClosePrinterMenu() {
|
||||
this.setState({ printerMenuAnchor: null });
|
||||
}
|
||||
|
||||
handleSelectPrinterForPrint(targetPrinter) {
|
||||
this.setState({ printerMenuAnchor: null });
|
||||
void this.executeTestPrint(targetPrinter);
|
||||
}
|
||||
|
||||
async executeTestPrint(targetPrinter) {
|
||||
if (!targetPrinter?.id) return;
|
||||
const {
|
||||
editorTemplate, editorObjects, editorWidth, editorHeight, editorDpi, editorName,
|
||||
} = this.state;
|
||||
|
||||
this.setState({ printing: true });
|
||||
try {
|
||||
const payload = {
|
||||
printerId: targetPrinter.id,
|
||||
agentprinterId: targetPrinter.printeragentId || targetPrinter.id,
|
||||
templateId: editorTemplate?.id,
|
||||
name: editorName,
|
||||
width: editorWidth,
|
||||
height: editorHeight,
|
||||
dpi: editorDpi,
|
||||
objects: editorObjects,
|
||||
};
|
||||
await labeltemplate.testprint(payload);
|
||||
enqueueSnackbar(`Testdruck an ${getPrinterLabel(targetPrinter)} gesendet.`, {
|
||||
variant: 'success',
|
||||
});
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
} finally {
|
||||
if (!this._cancelled) {
|
||||
this.setState({ printing: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handleEditorSave() {
|
||||
@@ -320,7 +449,7 @@ class ArtikelVorlagenPage extends PureComponent {
|
||||
const {
|
||||
editorOpen, editorLoading, editorTemplate, editorName, editorObjects,
|
||||
editorHeight, editorWidth, editorDpi, editorWidthClass, editorHeightMm,
|
||||
editorClipboard, saving,
|
||||
editorClipboard, saving, printing, printers, printerMenuAnchor,
|
||||
} = this.state;
|
||||
|
||||
const minHeightPx = getMinHeightPx(editorDpi);
|
||||
@@ -438,18 +567,66 @@ class ArtikelVorlagenPage extends PureComponent {
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||
<Button onClick={this.handleEditorClose} disabled={saving}>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={this.handleEditorSave}
|
||||
disabled={saving || editorLoading || !editorName.trim()}
|
||||
>
|
||||
{saving ? <CircularProgress size={18} sx={{ mr: 1 }} /> : null}
|
||||
{saving ? 'Speichert…' : 'Speichern'}
|
||||
</Button>
|
||||
<DialogActions sx={{ px: 3, pb: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Tooltip
|
||||
title={
|
||||
printers.length === 0
|
||||
? 'Kein ZPL-Drucker verfügbar'
|
||||
: printers.length === 1
|
||||
? `Drucken auf ${getPrinterLabel(printers[0])}`
|
||||
: 'Testdruck an Drucker senden'
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
startIcon={printing ? <CircularProgress size={18} /> : <PrintIcon />}
|
||||
endIcon={printers.length > 1 ? <ArrowDropDownIcon /> : null}
|
||||
onClick={this.handlePrintClick}
|
||||
disabled={printing || saving || editorLoading || printers.length === 0}
|
||||
>
|
||||
{printing ? 'Druckt…' : 'Testdruck'}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
{printers.length > 1 && (
|
||||
<Menu
|
||||
anchorEl={printerMenuAnchor}
|
||||
open={Boolean(printerMenuAnchor)}
|
||||
onClose={this.handleClosePrinterMenu}
|
||||
>
|
||||
{printers.map((p) => (
|
||||
<MenuItem
|
||||
key={p.id}
|
||||
onClick={() => this.handleSelectPrinterForPrint(p)}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<PrintIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={getPrinterLabel(p)}
|
||||
secondary={p.serial ? `Seriennummer: ${p.serial}` : undefined}
|
||||
/>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
)}
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Button onClick={this.handleEditorClose} disabled={saving || printing}>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={this.handleEditorSave}
|
||||
disabled={saving || printing || editorLoading || !editorName.trim()}
|
||||
>
|
||||
{saving ? <CircularProgress size={18} sx={{ mr: 1 }} /> : null}
|
||||
{saving ? 'Speichert…' : 'Speichern'}
|
||||
</Button>
|
||||
</Box>
|
||||
</DialogActions>
|
||||
</AppDialog>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user