import { useEffect, useState, useCallback } from 'react'; 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'; import type { DocumentRequirement, PosteingangDocument, Kontonummer } from '../api/posteingang'; import { clientsApi } from '../api/inbox'; import type { Client } from '../api/inbox'; import { paperlessApi } 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'; const { Option } = Select; interface Props { documentId: number | null; document: PosteingangDocument | null; open: boolean; onClose: (next?: boolean) => void; onSave: () => void; isPosteingang?: boolean; hasNextDocument?: boolean; } export default function DocumentEditModal({ documentId, document, open, onClose, onSave, isPosteingang = true, hasNextDocument = true }: Props) { const [form] = Form.useForm(); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); const [clients, setClients] = useState([]); const [documentTypes, setDocumentTypes] = useState([]); const [correspondents, setCorrespondents] = useState([]); const [requirements, setRequirements] = useState([]); // Tags: alle Tags + als Steuertags markierte IDs; bearbeitbar sind nur Inhaltstags const [allTags, setAllTags] = useState([]); const [steuertagIds, setSteuertagIds] = useState([]); const contentTags = allTags.filter(t => !steuertagIds.includes(t.id)); const [kontonummerMissing, setKontonummerMissing] = useState<{ correspondentId: number, nummer: string } | null>(null); const [docTitles, setDocTitles] = useState>({}); const [searchModalOpen, setSearchModalOpen] = useState<{ field: string, reqId: number } | null>(null); // Kontonummer state const [kontonummern, setKontonummern] = useState([]); const [kontonummernLoading, setKontonummernLoading] = useState(false); const [newKontonummer, setNewKontonummer] = useState(''); const selectedCorrespondent = Form.useWatch('correspondent', form); const loadKontonummern = useCallback(async (correspondentId: number) => { setKontonummernLoading(true); try { const data = await posteingangApi.getKontonummern(correspondentId); setKontonummern(data); } catch { setKontonummern([]); } finally { setKontonummernLoading(false); } }, []); useEffect(() => { if (selectedCorrespondent) { loadKontonummern(selectedCorrespondent); } else { setKontonummern([]); } }, [selectedCorrespondent, loadKontonummern]); const handleAddKontonummer = async () => { const trimmed = newKontonummer.trim(); if (!trimmed || !selectedCorrespondent) return; try { await posteingangApi.createKontonummer({ correspondentId: selectedCorrespondent, nummer: trimmed }); message.success(`Kontonummer "${trimmed}" hinzugefügt.`); setNewKontonummer(''); await loadKontonummern(selectedCorrespondent); form.setFieldValue('cf_5', trimmed); } catch { message.error('Kontonummer konnte nicht angelegt werden.'); } }; useEffect(() => { const resolveLinkTitles = async () => { const linkFields = requirements.filter(r => r.feldTyp === 'documentlink'); for (const req of linkFields) { const fieldName = req.isCustomField ? `cf_${req.customFieldIndex}` : req.feldId; const val = form.getFieldValue(fieldName); if (val && !docTitles[val]) { try { const d = await paperlessApi.getDocument(val); setDocTitles(prev => ({ ...prev, [val]: d.title })); } catch (e) { // Handle or ignore } } } }; if (open && requirements.length > 0) { resolveLinkTitles(); } }, [requirements, open]); // Removed docTitles to avoid loop, we check val existence anyway useEffect(() => { if (open) { loadInitialData(); } else { form.resetFields(); setRequirements([]); setKontonummerMissing(null); setKontonummern([]); setNewKontonummer(''); } }, [open]); useEffect(() => { if (open && document) { let customFieldsObj: any = {}; if (document.customFields) { document.customFields.forEach(cf => { customFieldsObj[`cf_${cf.field}`] = cf.value; }); } form.setFieldsValue({ mandant: document.owner, documentType: document.documentType, correspondent: document.correspondent, title: document.title, asn: document.asn, belegdatum: document.created ? dayjs(document.created) : null, eingangsdatum: customFieldsObj['cf_9'] ? dayjs(customFieldsObj['cf_9']) : (document.added ? dayjs(document.added) : null), ...customFieldsObj, }); if (document.documentType) { fetchRequirements(document.documentType); } } }, [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; setCorrespondents(prev => { const exists = prev.some(c => Number(c.id) === Number(correspondentId)); if (!exists) { // We need to fetch it. But we can't easily do it inside setCorrespondents. // So we'll do it outside. return prev; } return prev; }); const exists = correspondents.some(c => Number(c.id) === Number(correspondentId)); if (!exists) { try { const currentCorr = await paperlessApi.getCorrespondent(correspondentId); if (currentCorr) { setCorrespondents(prev => { if (prev.some(c => Number(c.id) === Number(currentCorr.id))) return prev; return [...prev, currentCorr]; }); } } catch (e) { console.error("Fehler beim Laden des zugewiesenen Absenders:", e); } } }; useEffect(() => { if (open && document?.correspondent) { ensureCorrespondentInList(document.correspondent); } }, [document?.correspondent, open]); const loadInitialData = async () => { setLoading(true); try { const [clientsData, docTypesData, correspondentsData, tagsData, steuertagData] = await Promise.all([ clientsApi.getMyClients(), paperlessApi.getDocumentTypes(), 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) { const exists = correspondentsData.some(c => Number(c.id) === Number(document.correspondent)); if (!exists) { const currentCorr = await paperlessApi.getCorrespondent(document.correspondent); if (currentCorr) { setCorrespondents([...correspondentsData, currentCorr]); } } } } catch (e) { message.error("Fehler beim Laden der Stammdaten."); } finally { setLoading(false); } }; const fetchRequirements = async (docType: number) => { try { const reqs = await posteingangApi.getRequirements(docType, isPosteingang); setRequirements(reqs); } catch (e) { message.error("Fehler beim Laden der Pflichtfelder."); } }; const [fetchingCorrespondents, setFetchingCorrespondents] = useState(false); const [searchTimeout, setSearchTimeout] = useState(null); const handleCorrespondentSearch = async (value: string) => { if (searchTimeout) clearTimeout(searchTimeout); setSearchTimeout(setTimeout(async () => { setFetchingCorrespondents(true); try { const data = await paperlessApi.getCorrespondents(value); setCorrespondents(data); } catch (e) { message.error("Absender konnten nicht geladen werden."); } finally { setFetchingCorrespondents(false); } }, 500)); }; const handleDocumentTypeChange = (value: number) => { fetchRequirements(value); }; const handleSaveDocument = async (values: any, isNext: boolean = false) => { // Collect custom fields into an array const customFieldsObj: any = {}; Object.keys(values).forEach(key => { if (key.startsWith('cf_')) { const fieldId = parseInt(key.replace('cf_', ''), 10); customFieldsObj[fieldId] = values[key]; } }); if (values.eingangsdatum) { customFieldsObj[9] = values.eingangsdatum.format('YYYY-MM-DD'); } else { customFieldsObj[9] = null; } const payload = { mandant: values.mandant, documentType: values.documentType, correspondent: values.correspondent, title: values.title, date: values.belegdatum ? values.belegdatum.format('YYYY-MM-DD') : null, tags: values.tags || [], customFields: customFieldsObj, }; setSaving(true); try { await posteingangApi.updateDocument(documentId!, payload); message.success("Dokument erfolgreich aktualisiert."); onSave(); onClose(isNext); } catch (e) { message.error("Fehler beim Speichern des Dokuments."); } finally { setSaving(false); } }; const handleSubmit = async (isNext: boolean = false) => { try { const values = await form.validateFields(); // Kontonummer Logic const kontonummerReq = requirements.find(r => r.customFieldIndex === 5); if (kontonummerReq && values[`cf_5`] && values.correspondent) { const kNummer = values[`cf_5`]; const knData = await posteingangApi.getKontonummern(values.correspondent); const exists = knData.some(k => k.Nummer === kNummer); if (!exists) { // Prompt user to save kontonummer setKontonummerMissing({ correspondentId: values.correspondent, nummer: kNummer }); return; // Stop saving, wait for confirmation } } await handleSaveDocument(values, isNext); } catch (e) { // Validation failed console.error("Form validation failed:", e); } }; const confirmKontonummerSave = async (shouldSave: boolean, isNext: boolean = false) => { if (shouldSave && kontonummerMissing) { try { await posteingangApi.createKontonummer(kontonummerMissing); message.success("Kontonummer hinterlegt."); } catch (e) { message.error("Fehler beim Speichern der Kontonummer."); } } setKontonummerMissing(null); const values = form.getFieldsValue(); await handleSaveDocument(values, isNext); }; const handleSelectLink = (doc: any) => { if (searchModalOpen) { form.setFieldValue(searchModalOpen.field, doc.id); setDocTitles(prev => ({ ...prev, [doc.id]: doc.title })); setSearchModalOpen(null); form.validateFields([searchModalOpen.field]); } }; if (documentId === null) return null; return ( <> onClose(false)} width={1400} style={{ top: 20 }} footer={ hasNextDocument ? [ , , ] : [ , ] } >
; if (req.isCustomField && req.customFieldIndex === 5) { // Kontonummer — Select mit Möglichkeit neue anzulegen inputElement = ( setNewKontonummer(e.target.value)} onKeyDown={(e) => e.stopPropagation()} /> )} > {kontonummern.map((k) => ( ))} ); } else if (req.feldId === '1') { inputElement = ( ); } else if (req.feldTyp === 'date') { inputElement = ; } else if (req.feldTyp === 'select' && req.fieldOptions) { inputElement = ( ); } else if (req.feldTyp === 'documentlink') { const fieldName = req.isCustomField ? `cf_${req.customFieldIndex}` : req.feldId === '1' ? 'correspondent' : req.feldId === '2' ? 'belegdatum' : req.feldId === '3' ? 'asn' : req.feldId === '5' ? 'title' : req.feldId; const currentId = form.getFieldValue(fieldName); inputElement = ( setSearchModalOpen({ field: fieldName, reqId: req.id })} suffix={ {currentId && (