feat: add Steuertags concept to separate workflow from content tags
Build and Push Multi-Platform Images / build-and-push (push) Successful in 38s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 38s
- New steuertag_ids setting to mark tags as workflow-only (not editable) - DocumentEditModal shows only content tags (non-Steuertags) as editable chips - Backend preserves Steuertags when saving document tag changes - ManuellBearbeitenPage renders content tag chips under document title - New Steuertags settings tab with multi-select and color preview Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { Modal, Form, Select, DatePicker, Input, Spin, message, Row, Col, Button, Space, Divider } from 'antd';
|
||||
import { Modal, Form, Select, DatePicker, Input, Spin, message, Row, Col, Button, Space, Divider, Tag } from 'antd';
|
||||
import { PlusOutlined, EyeOutlined, SearchOutlined } from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import { posteingangApi } from '../api/posteingang';
|
||||
@@ -7,7 +7,7 @@ import type { DocumentRequirement, PosteingangDocument, Kontonummer } from '../a
|
||||
import { clientsApi } from '../api/inbox';
|
||||
import type { Client } from '../api/inbox';
|
||||
import { paperlessApi } from '../api/paperless';
|
||||
import type { PaperlessDocType, PaperlessCorrespondent } from '../api/paperless';
|
||||
import type { PaperlessDocType, PaperlessCorrespondent, PaperlessTag } from '../api/paperless';
|
||||
import { getEnv } from '../utils/env';
|
||||
import { AuthIframe, openAuthUrl } from '../utils/auth-resource';
|
||||
import DocumentSearchModal from './DocumentSearchModal';
|
||||
@@ -35,6 +35,11 @@ export default function DocumentEditModal({ documentId, document, open, onClose,
|
||||
const [correspondents, setCorrespondents] = useState<PaperlessCorrespondent[]>([]);
|
||||
const [requirements, setRequirements] = useState<DocumentRequirement[]>([]);
|
||||
|
||||
// Tags: alle Tags + als Steuertags markierte IDs; bearbeitbar sind nur Inhaltstags
|
||||
const [allTags, setAllTags] = useState<PaperlessTag[]>([]);
|
||||
const [steuertagIds, setSteuertagIds] = useState<number[]>([]);
|
||||
const contentTags = allTags.filter(t => !steuertagIds.includes(t.id));
|
||||
|
||||
const [kontonummerMissing, setKontonummerMissing] = useState<{ correspondentId: number, nummer: string } | null>(null);
|
||||
const [docTitles, setDocTitles] = useState<Record<number, string>>({});
|
||||
const [searchModalOpen, setSearchModalOpen] = useState<{ field: string, reqId: number } | null>(null);
|
||||
@@ -140,6 +145,14 @@ export default function DocumentEditModal({ documentId, document, open, onClose,
|
||||
}
|
||||
}, [document, form, open]);
|
||||
|
||||
// Tags getrennt setzen: nur Inhaltstags (Steuertags werden nicht angezeigt/bearbeitet)
|
||||
useEffect(() => {
|
||||
if (open && document) {
|
||||
const contentTagIds = (document.tags || []).filter(id => !steuertagIds.includes(id));
|
||||
form.setFieldValue('tags', contentTagIds);
|
||||
}
|
||||
}, [document, open, steuertagIds, form]);
|
||||
|
||||
const ensureCorrespondentInList = async (correspondentId: number | null | undefined) => {
|
||||
if (!correspondentId) return;
|
||||
|
||||
@@ -178,14 +191,18 @@ export default function DocumentEditModal({ documentId, document, open, onClose,
|
||||
const loadInitialData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [clientsData, docTypesData, correspondentsData] = await Promise.all([
|
||||
const [clientsData, docTypesData, correspondentsData, tagsData, steuertagData] = await Promise.all([
|
||||
clientsApi.getMyClients(),
|
||||
paperlessApi.getDocumentTypes(),
|
||||
paperlessApi.getCorrespondents()
|
||||
paperlessApi.getCorrespondents(),
|
||||
paperlessApi.getTags(),
|
||||
paperlessApi.getSteuertagIds(),
|
||||
]);
|
||||
setClients(clientsData);
|
||||
setDocumentTypes(docTypesData);
|
||||
setCorrespondents(correspondentsData);
|
||||
setAllTags(tagsData);
|
||||
setSteuertagIds(steuertagData);
|
||||
|
||||
// If document is already there, ensure its correspondent is in the list
|
||||
if (document?.correspondent) {
|
||||
@@ -258,6 +275,7 @@ export default function DocumentEditModal({ documentId, document, open, onClose,
|
||||
correspondent: values.correspondent,
|
||||
title: values.title,
|
||||
date: values.belegdatum ? values.belegdatum.format('YYYY-MM-DD') : null,
|
||||
tags: values.tags || [],
|
||||
customFields: customFieldsObj,
|
||||
};
|
||||
|
||||
@@ -373,6 +391,36 @@ export default function DocumentEditModal({ documentId, document, open, onClose,
|
||||
<DatePicker style={{ width: '100%' }} format="DD.MM.YYYY" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="tags" label="Tags">
|
||||
<Select
|
||||
mode="multiple"
|
||||
allowClear
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder="Tags auswählen"
|
||||
options={contentTags.map(t => ({ value: t.id, label: t.name }))}
|
||||
tagRender={({ value, closable, onClose }) => {
|
||||
const tag = allTags.find(t => t.id === value);
|
||||
return (
|
||||
<Tag
|
||||
color={tag?.color}
|
||||
style={{ color: tag?.text_color, marginInlineEnd: 4 }}
|
||||
closable={closable}
|
||||
onClose={onClose}
|
||||
>
|
||||
{tag?.name ?? value}
|
||||
</Tag>
|
||||
);
|
||||
}}
|
||||
optionRender={(opt) => {
|
||||
const tag = contentTags.find(t => t.id === opt.value);
|
||||
return tag ? (
|
||||
<Tag color={tag.color} style={{ color: tag.text_color }}>{tag.name}</Tag>
|
||||
) : opt.label;
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{/* Rendering dynamic requirements based on DocumentType */}
|
||||
{requirements.map(req => {
|
||||
if (req.feldId === '2' || (req.isCustomField && req.customFieldIndex === 9)) {
|
||||
|
||||
Reference in New Issue
Block a user