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:
@@ -2,7 +2,7 @@ import { useEffect, useState, useCallback } from 'react';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
Tabs, Typography, Table, Button, Modal, Form, Input, Select,
|
||||
Switch, Checkbox, Popconfirm, message, Card, Tag, Space, Divider, InputNumber, Badge,
|
||||
Switch, Checkbox, Popconfirm, message, Card, Tag, Space, Divider, InputNumber, Badge, Row, Col,
|
||||
} from 'antd';
|
||||
import {
|
||||
UserOutlined, FileTextOutlined, ThunderboltOutlined,
|
||||
@@ -24,6 +24,7 @@ import { apiKeysApi, type ApiKey } from '../api/api-keys';
|
||||
import {
|
||||
barcodeTemplatesApi,
|
||||
type BarcodeTemplate,
|
||||
type LabelInputField,
|
||||
} from '../api/barcode-templates';
|
||||
import {
|
||||
paperlessApi, type PaperlessTag, type PaperlessDocType,
|
||||
@@ -1779,6 +1780,64 @@ function InboxActionsForTemplateEditor({ templateId }: { templateId: number }) {
|
||||
// Eingangsdokumentarten Tab (ehemals Barcode-Vorlagen)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
function LabelElementRow({ listName, remove }: { listName: number; remove: (n: number) => void }) {
|
||||
return (
|
||||
<Card
|
||||
size="small"
|
||||
style={{ marginBottom: 8 }}
|
||||
extra={<Button size="small" danger icon={<DeleteOutlined />} onClick={() => remove(listName)} />}
|
||||
>
|
||||
<Form.Item name={[listName, 'type']} label="Typ" style={{ marginBottom: 8 }}>
|
||||
<Select style={{ width: 120 }} options={[
|
||||
{ value: 'text', label: 'Text' },
|
||||
{ value: 'qr', label: 'QR-Code' },
|
||||
{ value: 'line', label: 'Linie' },
|
||||
]} />
|
||||
</Form.Item>
|
||||
<Form.Item noStyle shouldUpdate>
|
||||
{({ getFieldValue }) => {
|
||||
const type = getFieldValue(['LabelLayout', listName, 'type']);
|
||||
if (type === 'text') return (
|
||||
<Row gutter={8}>
|
||||
<Col span={4}><Form.Item name={[listName, 'x']} label="X (mm)"><InputNumber style={{ width: '100%' }} /></Form.Item></Col>
|
||||
<Col span={4}><Form.Item name={[listName, 'y']} label="Y (mm)"><InputNumber style={{ width: '100%' }} /></Form.Item></Col>
|
||||
<Col span={4}><Form.Item name={[listName, 'fontSize']} label="Schrift (mm)"><InputNumber min={0.5} step={0.5} style={{ width: '100%' }} /></Form.Item></Col>
|
||||
<Col span={4}><Form.Item name={[listName, 'maxWidth']} label="Max. B. (mm)"><InputNumber style={{ width: '100%' }} /></Form.Item></Col>
|
||||
<Col span={4}><Form.Item name={[listName, 'bold']} label="Fett" valuePropName="checked"><Checkbox /></Form.Item></Col>
|
||||
<Col span={4}><Form.Item name={[listName, 'align']} label="Ausrichtung">
|
||||
<Select allowClear style={{ width: '100%' }} options={[
|
||||
{ value: 'left', label: 'Links' },
|
||||
{ value: 'center', label: 'Mitte' },
|
||||
{ value: 'right', label: 'Rechts' },
|
||||
]} />
|
||||
</Form.Item></Col>
|
||||
<Col span={24}><Form.Item name={[listName, 'content']} label="Inhalt"><Input placeholder="{nummer} oder {datum}" /></Form.Item></Col>
|
||||
</Row>
|
||||
);
|
||||
if (type === 'qr') return (
|
||||
<Row gutter={8}>
|
||||
<Col span={4}><Form.Item name={[listName, 'x']} label="X (mm)"><InputNumber style={{ width: '100%' }} /></Form.Item></Col>
|
||||
<Col span={4}><Form.Item name={[listName, 'y']} label="Y (mm)"><InputNumber style={{ width: '100%' }} /></Form.Item></Col>
|
||||
<Col span={4}><Form.Item name={[listName, 'sizeMm']} label="Größe (mm)"><InputNumber style={{ width: '100%' }} /></Form.Item></Col>
|
||||
<Col span={12}><Form.Item name={[listName, 'content']} label="Inhalt"><Input placeholder="{number}" /></Form.Item></Col>
|
||||
</Row>
|
||||
);
|
||||
if (type === 'line') return (
|
||||
<Row gutter={8}>
|
||||
<Col span={4}><Form.Item name={[listName, 'x1']} label="X1 (mm)"><InputNumber style={{ width: '100%' }} /></Form.Item></Col>
|
||||
<Col span={4}><Form.Item name={[listName, 'y1']} label="Y1 (mm)"><InputNumber style={{ width: '100%' }} /></Form.Item></Col>
|
||||
<Col span={4}><Form.Item name={[listName, 'x2']} label="X2 (mm)"><InputNumber style={{ width: '100%' }} /></Form.Item></Col>
|
||||
<Col span={4}><Form.Item name={[listName, 'y2']} label="Y2 (mm)"><InputNumber style={{ width: '100%' }} /></Form.Item></Col>
|
||||
<Col span={4}><Form.Item name={[listName, 'lineWidth']} label="Stärke (mm)"><InputNumber step={0.1} style={{ width: '100%' }} /></Form.Item></Col>
|
||||
</Row>
|
||||
);
|
||||
return null;
|
||||
}}
|
||||
</Form.Item>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function BarcodeTemplatesTab() {
|
||||
const [data, setData] = useState<BarcodeTemplate[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -1806,7 +1865,15 @@ function BarcodeTemplatesTab() {
|
||||
setEditing(null);
|
||||
setTestValue('');
|
||||
form.resetFields();
|
||||
form.setFieldsValue({ SplitBefore: false, DateinameTemplate: '' });
|
||||
form.setFieldsValue({
|
||||
SplitBefore: false,
|
||||
DateinameTemplate: '',
|
||||
LabelEnabled: false,
|
||||
LabelWidthMm: 57,
|
||||
LabelHeightMm: 32,
|
||||
LabelInputFields: [],
|
||||
LabelLayout: [],
|
||||
});
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
@@ -1814,7 +1881,20 @@ function BarcodeTemplatesTab() {
|
||||
setIsNew(false);
|
||||
setEditing(row);
|
||||
setTestValue('');
|
||||
form.setFieldsValue({ Name: row.Name, Regex: row.Regex, SplitBefore: row.SplitBefore, DateinameTemplate: row.DateinameTemplate ?? '' });
|
||||
form.setFieldsValue({
|
||||
Name: row.Name,
|
||||
Regex: row.Regex,
|
||||
SplitBefore: row.SplitBefore,
|
||||
DateinameTemplate: row.DateinameTemplate ?? '',
|
||||
LabelEnabled: row.LabelEnabled ?? false,
|
||||
LabelWidthMm: row.LabelWidthMm ?? 57,
|
||||
LabelHeightMm: row.LabelHeightMm ?? 32,
|
||||
LabelInputFields: row.LabelInputFields ?? [],
|
||||
LabelGetUrl: row.LabelGetUrl ?? '',
|
||||
LabelPrintedUrl: row.LabelPrintedUrl ?? '',
|
||||
LabelReleaseUrl: row.LabelReleaseUrl ?? '',
|
||||
LabelLayout: row.LabelLayout ?? [],
|
||||
});
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
@@ -1960,6 +2040,121 @@ function BarcodeTemplatesTab() {
|
||||
>
|
||||
<Input placeholder="{barcode}_{datum}" />
|
||||
</Form.Item>
|
||||
|
||||
<Divider>Etikett</Divider>
|
||||
|
||||
<Form.Item name="LabelEnabled" valuePropName="checked" label="Etikett-Druck aktivieren">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item noStyle shouldUpdate={(prev, curr) => prev.LabelEnabled !== curr.LabelEnabled}>
|
||||
{({ getFieldValue }) =>
|
||||
getFieldValue('LabelEnabled') ? (
|
||||
<>
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Form.Item name="LabelWidthMm" label="Breite (mm)">
|
||||
<InputNumber min={10} max={300} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item name="LabelHeightMm" label="Höhe (mm)">
|
||||
<InputNumber min={10} max={300} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item label="Eingabefelder">
|
||||
<Form.List name="LabelInputFields">
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
{fields.map(({ key, name, ...rest }) => (
|
||||
<Space key={key} style={{ display: 'flex', marginBottom: 6 }} align="baseline">
|
||||
<Form.Item {...rest} name={[name, 'name']} rules={[{ required: true, message: 'Feldname' }]} noStyle>
|
||||
<Input placeholder="Feldname (z. B. datum)" style={{ width: 160 }} />
|
||||
</Form.Item>
|
||||
<Form.Item {...rest} name={[name, 'label']} noStyle>
|
||||
<Input placeholder="Bezeichnung" style={{ width: 160 }} />
|
||||
</Form.Item>
|
||||
<Form.Item {...rest} name={[name, 'type']} noStyle>
|
||||
<Select style={{ width: 110 }} options={[
|
||||
{ value: 'text', label: 'Text' },
|
||||
{ value: 'number', label: 'Nummer' },
|
||||
{ value: 'date', label: 'Datum' },
|
||||
]} />
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined onClick={() => remove(name)} />
|
||||
</Space>
|
||||
))}
|
||||
<Button type="dashed" onClick={() => add({ type: 'text' })} icon={<PlusOutlined />} size="small">
|
||||
Feld hinzufügen
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item noStyle shouldUpdate>
|
||||
{({ getFieldValue: gfv }) => {
|
||||
const inputFields: LabelInputField[] = gfv('LabelInputFields') ?? [];
|
||||
const chips: string[] = [];
|
||||
for (const f of inputFields) {
|
||||
if (!f?.name) continue;
|
||||
chips.push(`{${f.name}}`);
|
||||
if (f.type === 'date') {
|
||||
chips.push(`{${f.name}.year}`, `{${f.name}.month}`, `{${f.name}.day}`);
|
||||
}
|
||||
}
|
||||
const chipsWithNumber = [...chips, '{number}'];
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="LabelGetUrl" label="GET-URL (liefert Nummer)"
|
||||
extra={chips.length > 0 ? `Platzhalter: ${chips.join(' ')}` : undefined}>
|
||||
<Input placeholder="https://example.com/nummer?feld={feldname}" />
|
||||
</Form.Item>
|
||||
<Form.Item name="LabelPrintedUrl" label="PRINTED-URL (nach erfolgreichem Druck)"
|
||||
extra={chipsWithNumber.length > 0 ? `Platzhalter: ${chipsWithNumber.join(' ')}` : undefined}>
|
||||
<Input placeholder="https://example.com/gedruckt?nr={number}" />
|
||||
</Form.Item>
|
||||
<Form.Item name="LabelReleaseUrl" label="RELEASE-URL (bei Druckfehler)"
|
||||
extra={chipsWithNumber.length > 0 ? `Platzhalter: ${chipsWithNumber.join(' ')}` : undefined}>
|
||||
<Input placeholder="https://example.com/freigeben?nr={number}" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Layout-Elemente"
|
||||
extra={chipsWithNumber.length > 0 ? `Platzhalter für Inhalt: ${chipsWithNumber.join(' ')}` : undefined}>
|
||||
<Form.List name="LabelLayout">
|
||||
{(layoutFields, { add: addEl, remove: removeEl }) => (
|
||||
<>
|
||||
{layoutFields.map(({ key, name: elName }) => (
|
||||
<LabelElementRow key={key} listName={elName} remove={removeEl} />
|
||||
))}
|
||||
<Space>
|
||||
<Button size="small" type="dashed" icon={<PlusOutlined />}
|
||||
onClick={() => addEl({ type: 'text', x: 0, y: 0, fontSize: 3, content: '', bold: false })}>
|
||||
Text
|
||||
</Button>
|
||||
<Button size="small" type="dashed" icon={<PlusOutlined />}
|
||||
onClick={() => addEl({ type: 'qr', x: 0, y: 0, sizeMm: 20, content: '' })}>
|
||||
QR-Code
|
||||
</Button>
|
||||
<Button size="small" type="dashed" icon={<PlusOutlined />}
|
||||
onClick={() => addEl({ type: 'line', x1: 0, y1: 0, x2: 50, y2: 0 })}>
|
||||
Linie
|
||||
</Button>
|
||||
</Space>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
{editing && !isNew && (
|
||||
|
||||
Reference in New Issue
Block a user