feat: implement backend label print agent system for remote label rendering and job management
Build and Push Multi-Platform Images / build-and-push (push) Successful in 33s

This commit is contained in:
2026-05-07 22:46:29 +02:00
parent 0c94e7b999
commit 80f862a0c0
15 changed files with 995 additions and 15 deletions
+106
View File
@@ -1,11 +1,17 @@
import { useCallback, useEffect, useState, type ReactNode } from 'react';
import { useNavigate } from 'react-router-dom';
import dayjs from 'dayjs';
import {
Button,
Card,
DatePicker,
Form,
Input,
InputNumber,
Modal,
Popconfirm,
Popover,
Select,
Space,
Spin,
Table,
@@ -18,6 +24,7 @@ import {
DeleteOutlined,
EyeOutlined,
FolderOpenOutlined,
PrinterOutlined,
QrcodeOutlined,
ReloadOutlined,
ScanOutlined,
@@ -27,6 +34,8 @@ import {
} from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
import { inboxApi, type InboxBarcode, type InboxFile } from '../api/inbox';
import { barcodeTemplatesApi, type BarcodeTemplate } from '../api/barcode-templates';
import { labelPrintAgentApi } from '../api/labelPrintAgent';
const { Title } = Typography;
@@ -123,6 +132,45 @@ export default function InboxPage() {
const [search, setSearch] = useState('');
const [rescanning, setRescanning] = useState(false);
const [printDialogOpen, setPrintDialogOpen] = useState(false);
const [labelTemplates, setLabelTemplates] = useState<BarcodeTemplate[]>([]);
const [selectedTemplate, setSelectedTemplate] = useState<BarcodeTemplate | null>(null);
const [fieldValues, setFieldValues] = useState<Record<string, string>>({});
const [printing, setPrinting] = useState(false);
const openPrintDialog = async () => {
try {
const all = await barcodeTemplatesApi.list();
setLabelTemplates(all.filter((t) => t.LabelEnabled));
} catch {
message.error('Vorlagen konnten nicht geladen werden');
return;
}
setSelectedTemplate(null);
setFieldValues({});
setPrintDialogOpen(true);
};
const handleTemplateSelect = (id: number) => {
const t = labelTemplates.find((t) => t.Id === id) ?? null;
setSelectedTemplate(t);
setFieldValues({});
};
const handlePrint = async () => {
if (!selectedTemplate) return;
setPrinting(true);
try {
await labelPrintAgentApi.createJob(selectedTemplate.Id, fieldValues);
message.success('Druckauftrag erstellt');
setPrintDialogOpen(false);
} catch (err: any) {
message.error(err?.response?.data?.message ?? 'Druckauftrag fehlgeschlagen');
} finally {
setPrinting(false);
}
};
const load = useCallback(async () => {
setLoading(true);
try {
@@ -331,9 +379,67 @@ export default function InboxPage() {
Rescan
</Button>
</Popconfirm>
<Button icon={<PrinterOutlined />} onClick={openPrintDialog}>
Etikett drucken
</Button>
</Space>
</div>
<Modal
title="Etikett drucken"
open={printDialogOpen}
onCancel={() => setPrintDialogOpen(false)}
onOk={handlePrint}
okText="Drucken"
cancelText="Abbrechen"
confirmLoading={printing}
okButtonProps={{ disabled: !selectedTemplate }}
>
<Form layout="vertical">
<Form.Item label="Eingangsdokumentart">
<Select
placeholder="Vorlage wählen…"
options={labelTemplates.map((t) => ({ value: t.Id, label: t.Name }))}
onChange={handleTemplateSelect}
value={selectedTemplate?.Id}
style={{ width: '100%' }}
/>
</Form.Item>
{selectedTemplate?.LabelInputFields?.map((field) => (
<Form.Item key={field.name} label={field.label || field.name}>
{field.type === 'date' ? (
<DatePicker
style={{ width: '100%' }}
value={fieldValues[field.name] ? dayjs(fieldValues[field.name]) : null}
onChange={(d) =>
setFieldValues((prev) => ({
...prev,
[field.name]: d ? d.format('YYYY-MM-DD') : '',
}))
}
/>
) : field.type === 'number' ? (
<InputNumber
style={{ width: '100%' }}
value={fieldValues[field.name] ? Number(fieldValues[field.name]) : undefined}
onChange={(v) =>
setFieldValues((prev) => ({ ...prev, [field.name]: String(v ?? '') }))
}
/>
) : (
<Input
value={fieldValues[field.name] ?? ''}
onChange={(e) =>
setFieldValues((prev) => ({ ...prev, [field.name]: e.target.value }))
}
/>
)}
</Form.Item>
))}
</Form>
</Modal>
<Card>
<Table<InboxFile>
rowKey="id"