Files
paperlessmanager/paperless-frontend/src/pages/FreigabePage.tsx
T
bjoernpoettker d5bc1bcee0
Build and Push Multi-Platform Images / build-and-push (push) Successful in 36s
fix: handle object-format select_options from Paperless for Freigabe field
Paperless may return extra_data.select_options as an array of objects
{id, label} instead of plain strings. This caused React error #31
when Ant Design tried to render an object as a child in the Select and
Table components.

- Backend: coerce option items to {id: string, label: string} regardless
  of whether Paperless returns strings or objects
- Frontend: normalize cf.value to a plain string before rendering or
  storing in state, guarding against object-typed values

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 07:01:39 +02:00

219 lines
6.4 KiB
TypeScript

import { useEffect, useState, useCallback } from 'react';
import {
Table, Typography, Tag, Button, Modal, Select, message, Space, Radio,
} from 'antd';
import { CheckCircleOutlined } from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
import dayjs from 'dayjs';
import { freigabeApi, type FreigabeDocument, type FreigabeOption } from '../api/freigabe';
import { paperlessApi, type PaperlessDocType, type PaperlessCorrespondent } from '../api/paperless';
const { Title } = Typography;
const FREIGABE_FIELD_ID = 15;
export default function FreigabePage() {
const [data, setData] = useState<FreigabeDocument[]>([]);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(25);
const [nurNichtFreigegeben, setNurNichtFreigegeben] = useState(true);
const [docTypes, setDocTypes] = useState<PaperlessDocType[]>([]);
const [correspondents, setCorrespondents] = useState<PaperlessCorrespondent[]>([]);
const [freigabeOptions, setFreigabeOptions] = useState<FreigabeOption[]>([]);
const [selectedDoc, setSelectedDoc] = useState<FreigabeDocument | null>(null);
const [modalOpen, setModalOpen] = useState(false);
const [selectedValue, setSelectedValue] = useState<string | null>(null);
const [saving, setSaving] = useState(false);
useEffect(() => {
Promise.all([
paperlessApi.getDocumentTypes(),
paperlessApi.getCorrespondents(),
freigabeApi.getOptions(),
]).then(([dts, corrs, opts]) => {
setDocTypes(dts);
setCorrespondents(corrs);
setFreigabeOptions(opts);
});
}, []);
const fetchData = useCallback(async () => {
setLoading(true);
try {
const result = await freigabeApi.getDocuments(page, pageSize, nurNichtFreigegeben);
setData(result.results ?? []);
setTotal(result.count ?? 0);
} catch {
message.error('Fehler beim Laden der Belege');
} finally {
setLoading(false);
}
}, [page, pageSize, nurNichtFreigegeben]);
useEffect(() => {
fetchData();
}, [fetchData]);
const getDocTypeName = (id: number | null) => {
if (!id) return '—';
return docTypes.find((d) => d.id === id)?.name ?? String(id);
};
const getCorrespondentName = (id: number | null) => {
if (!id) return '—';
return correspondents.find((c) => c.id === id)?.name ?? String(id);
};
const toCfString = (value: any): string | null => {
if (value === null || value === undefined || value === '') return null;
if (typeof value === 'object') return String(value?.id ?? value?.value ?? value?.label ?? '') || null;
return String(value);
};
const getFreigabeValue = (doc: FreigabeDocument) => {
const cf = doc.custom_fields?.find((f) => f.field === FREIGABE_FIELD_ID);
const cfStr = toCfString(cf?.value);
if (!cfStr) return <Tag color="warning">Nicht gesetzt</Tag>;
const opt = freigabeOptions.find((o) => o.id === cfStr);
return <Tag color="success">{opt?.label ?? cfStr}</Tag>;
};
const openModal = (doc: FreigabeDocument) => {
const cf = doc.custom_fields?.find((f) => f.field === FREIGABE_FIELD_ID);
setSelectedDoc(doc);
setSelectedValue(toCfString(cf?.value));
setModalOpen(true);
};
const handleFreigabe = async () => {
if (!selectedDoc) return;
setSaving(true);
try {
await freigabeApi.setFreigabe(selectedDoc.id, selectedValue);
message.success('Freigabe gesetzt');
setModalOpen(false);
setSelectedDoc(null);
fetchData();
} catch {
message.error('Fehler beim Speichern der Freigabe');
} finally {
setSaving(false);
}
};
const columns: ColumnsType<FreigabeDocument> = [
{
title: 'Dokumenttyp',
dataIndex: 'document_type',
key: 'doctype',
render: getDocTypeName,
},
{
title: 'Titel',
dataIndex: 'title',
key: 'title',
ellipsis: true,
},
{
title: 'Erstellt',
dataIndex: 'created_date',
key: 'created',
width: 110,
render: (v: string) => v ? dayjs(v).format('DD.MM.YYYY') : '—',
},
{
title: 'Absender',
dataIndex: 'correspondent',
key: 'correspondent',
render: getCorrespondentName,
},
{
title: 'Freigabe',
key: 'freigabe',
width: 140,
render: (_, doc) => getFreigabeValue(doc),
},
{
title: '',
key: 'action',
width: 130,
render: (_, doc) => (
<Button
icon={<CheckCircleOutlined />}
size="small"
type="primary"
onClick={() => openModal(doc)}
>
Freigabe setzen
</Button>
),
},
];
return (
<>
<Title level={4} style={{ marginTop: 0, marginBottom: 16 }}>Freigabe</Title>
<Space style={{ marginBottom: 16 }}>
<Radio.Group
value={nurNichtFreigegeben}
onChange={(e) => {
setPage(1);
setNurNichtFreigegeben(e.target.value);
}}
optionType="button"
buttonStyle="solid"
>
<Radio.Button value={true}>Nicht freigegeben</Radio.Button>
<Radio.Button value={false}>Alle</Radio.Button>
</Radio.Group>
</Space>
<Table<FreigabeDocument>
dataSource={data}
columns={columns}
rowKey="id"
loading={loading}
size="small"
pagination={{
current: page,
pageSize,
total,
showSizeChanger: true,
pageSizeOptions: ['25', '50', '100'],
onChange: (p, ps) => {
setPage(p);
setPageSize(ps);
},
showTotal: (t) => `${t} Belege`,
}}
/>
<Modal
title="Freigabe setzen"
open={modalOpen}
onOk={handleFreigabe}
onCancel={() => { setModalOpen(false); setSelectedDoc(null); }}
okText="Speichern"
cancelText="Abbrechen"
confirmLoading={saving}
>
<p style={{ marginBottom: 12 }}>
<strong>{selectedDoc?.title}</strong>
</p>
<Select
style={{ width: '100%' }}
placeholder="Freigabe-Status wählen"
allowClear
value={selectedValue ?? undefined}
onChange={(v) => setSelectedValue(v ?? null)}
options={freigabeOptions.map((o) => ({ value: o.id, label: o.label }))}
/>
</Modal>
</>
);
}