Files
paperlessmanager/paperless-frontend/src/pages/PosteingangPage.tsx
T
bjoernpoettkerandClaude Opus 5.5 e0a6598b35 refactor: Paperless-Dokumentrouten unter /api/paperless/documents/:id
„inbox“ war doppelt belegt (/api/inbox = Scanner, /api/paperless/inbox =
Paperless-Posteingang) und passte nicht zur Manuell-Seite, die dieselben
Routen nutzt. Umbenannt (ohne Alias, nur das eigene Frontend nutzt sie):

  PUT  paperless/inbox/:id            → PUT  paperless/documents/:id
  GET  paperless/inbox/pdf/:id        → GET  paperless/documents/:id/pdf
  GET  paperless/inbox/preview/:id    → GET  paperless/documents/:id/preview
  GET  paperless/inbox/:id/erechnung  → GET  paperless/documents/:id/erechnung

Dabei:
- Dokumentsuche (GET documents) wieder für den Posteingang (VIEW_INBOX)
  statt nur Admin – die Verknüpfungssuche im Bearbeiten-Dialog nutzt sie;
  die Einschränkung aus dem vorigen Commit hatte sie gebrochen.
- Suche und GET documents/:id liefern nur noch id, Titel, ASN, Datum
  (kein OCR-Volltext, keine Statusfelder).
- documents/:id/pdf auch für VIEW_SCANNER (Duplikatvergleich im Scanner).
- E-Rechnungs-Vorschlag prüft wie das Speichern pro Dokument (Tag 1 bzw.
  Manuell-Tag); Vorschau/PDF bewusst nicht (Suchergebnisse, Duplikate).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:46:57 +01:00

182 lines
6.0 KiB
TypeScript

import { useEffect, useState } from 'react';
import { Table, Popover, Button, Space, message, Tooltip, Typography } from 'antd';
import { AuthImage } from '../utils/auth-resource';
import { ReloadOutlined } from '@ant-design/icons';
const { Title, Text } = Typography;
import dayjs from 'dayjs';
import { posteingangApi } from '../api/posteingang';
import type { PosteingangDocument } from '../api/posteingang';
import DocumentEditModal from '../components/DocumentEditModal';
import { getEnv } from '../utils/env';
import { useIsMobile } from '../hooks/useIsMobile';
import MobileCardList from '../components/MobileCardList';
export default function PosteingangPage() {
const isMobile = useIsMobile();
const [data, setData] = useState<PosteingangDocument[]>([]);
const [loading, setLoading] = useState(false);
const [editModalOpen, setEditModalOpen] = useState(false);
const [selectedDoc, setSelectedDoc] = useState<PosteingangDocument | null>(null);
const fetchData = async () => {
setLoading(true);
try {
const docs = await posteingangApi.getList();
setData(docs || []);
} catch (e) {
message.error("Posteingang konnte nicht geladen werden.");
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
// Refresh interval every 30 seconds
const interval = setInterval(() => {
fetchData();
}, 30000);
return () => clearInterval(interval);
}, []);
const handleEdit = (doc: PosteingangDocument) => {
setSelectedDoc(doc);
setEditModalOpen(true);
};
const handleCloseModal = (openNext?: boolean) => {
setEditModalOpen(false);
if (openNext && data.length > 0) {
const currentIndex = data.findIndex(d => d.id === selectedDoc?.id);
const nextIndex = currentIndex !== -1 && currentIndex + 1 < data.length ? currentIndex + 1 : 0;
const nextDoc = data[nextIndex];
setTimeout(() => {
setSelectedDoc(nextDoc);
setEditModalOpen(true);
}, 300);
} else {
setSelectedDoc(null);
}
};
const popoverContent = (id: number) => (
<AuthImage
src={`${getEnv('VITE_API_URL')}/api/paperless/documents/${id}/preview`}
width={400}
/>
);
const getEingangsdatum = (record: PosteingangDocument) => {
const cf = record.customFields?.find((f) => f.field === 9);
return cf?.value ? dayjs(cf.value).format('DD.MM.YYYY') : '-';
};
const columns = [
{
title: 'Vorschau',
key: 'preview',
width: 150,
render: (_: any, record: PosteingangDocument) => (
<Popover content={popoverContent(record.id)} placement="right" trigger="hover">
<AuthImage
src={`${getEnv('VITE_API_URL')}/api/paperless/documents/${record.id}/preview`}
width={100}
style={{ border: '1px solid #d9d9d9', objectFit: 'contain' }}
/>
</Popover>
),
},
{
title: 'Titel',
dataIndex: 'title',
key: 'title',
width: '35%',
},
{
title: 'Eingangsdatum',
key: 'eingangsdatum',
render: (_: any, record: PosteingangDocument) => getEingangsdatum(record),
},
{
title: 'Importiert am',
dataIndex: 'added',
key: 'added',
render: (text: string) => dayjs(text).format('DD.MM.YYYY HH:mm'),
},
{
title: 'Aktionen',
key: 'action',
width: 150,
render: (_: any, record: PosteingangDocument) => (
<Button type="primary" onClick={() => handleEdit(record)}>
Bearbeiten
</Button>
),
},
];
return (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
<Title level={3} style={{ margin: 0 }}>Posteingang</Title>
<Space>
<Tooltip title="Manuell aktualisieren">
<Button icon={<ReloadOutlined />} onClick={fetchData} loading={loading} />
</Tooltip>
</Space>
</div>
{isMobile ? (
<MobileCardList<PosteingangDocument>
dataSource={data}
rowKey="id"
loading={loading}
pagination={{ pageSize: 20, showTotal: (t) => `${t} Dokumente` }}
renderCard={(record) => (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<div style={{ display: 'flex', gap: 12 }}>
<AuthImage
src={`${getEnv('VITE_API_URL')}/api/paperless/documents/${record.id}/preview`}
width={72}
style={{ border: '1px solid #d9d9d9', objectFit: 'contain', flexShrink: 0 }}
/>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, minWidth: 0 }}>
<Text strong>{record.title || '—'}</Text>
<Text type="secondary" style={{ fontSize: 13 }}>
Eingangsdatum: {getEingangsdatum(record)}
</Text>
<Text type="secondary" style={{ fontSize: 13 }}>
Importiert am: {dayjs(record.added).format('DD.MM.YYYY HH:mm')}
</Text>
</div>
</div>
<Button type="primary" block onClick={() => handleEdit(record)}>
Bearbeiten
</Button>
</div>
)}
/>
) : (
<Table
columns={columns}
dataSource={data}
rowKey="id"
loading={loading}
pagination={{ pageSize: 20, showSizeChanger: true, showTotal: (t) => `${t} Dokumente` }}
locale={{ emptyText: 'Keine Einträge vorhanden' }}
/>
)}
<DocumentEditModal
documentId={selectedDoc?.id || null}
document={selectedDoc}
open={editModalOpen}
onClose={handleCloseModal}
onSave={fetchData}
isPosteingang={true}
hasNextDocument={data.length > 1}
/>
</div>
);
}