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
Build and Push Multi-Platform Images / build-and-push (push) Successful in 33s
This commit is contained in:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user