Initial commit with Email Import Wizard and Task Processor updates

This commit is contained in:
2026-05-04 08:02:11 +02:00
commit effdc5d59f
170 changed files with 67739 additions and 0 deletions
@@ -0,0 +1,103 @@
import { useState, useEffect } from 'react';
import { Button, Tooltip, Spin } from 'antd';
import { ScissorOutlined } from '@ant-design/icons';
import { getEnv } from '../utils/env';
import { getAccessToken } from '../auth/oidc';
interface PdfSplitViewerProps {
attachmentId: number;
pageCount: number;
startPage?: number;
endPage?: number;
onSplit: (pageIndex: number) => void;
disabled: boolean;
}
export default function PdfSplitViewer({ attachmentId, pageCount, startPage, endPage, onSplit, disabled }: PdfSplitViewerProps) {
const [token, setToken] = useState<string | null>(null);
useEffect(() => {
getAccessToken().then(t => setToken(t));
}, []);
if (!token) return <Spin />;
if (pageCount === 0) {
return (
<div style={{ padding: 16, background: '#fafafa', borderRadius: 8, textAlign: 'center' }}>
<p>Vorschau nicht verfügbar (Dokument wurde vor dem Update geladen).</p>
</div>
);
}
const actualStart = startPage || 1;
const actualEnd = endPage || pageCount;
const pagesToRender = [];
for (let i = actualStart; i <= actualEnd; i++) {
pagesToRender.push(i);
}
const getImageUrl = (page: number) => {
// We add the token as a query parameter because <img> tags don't support Authorization headers easily.
// However, it's more secure to fetch the blob and create an object URL, or rely on cookie auth.
// Let's fetch the blob and use object URLs to pass the bearer token securely.
return `${getEnv('VITE_API_URL')}/api/email-import/attachments/${attachmentId}/pages/${page}/thumb`;
};
return (
<div style={{ display: 'flex', overflowX: 'auto', padding: '16px 8px', background: '#fafafa', borderRadius: 8 }}>
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
{pagesToRender.map((pageNum) => (
<div key={pageNum} style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ border: '1px solid #d9d9d9', boxShadow: '0 2px 8px rgba(0,0,0,0.1)', background: 'white', height: 200, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<ImageWithAuth url={getImageUrl(pageNum)} token={token} />
</div>
{!disabled && pageNum < actualEnd && (
<div style={{ width: 24, display: 'flex', justifyContent: 'center', zIndex: 10, marginLeft: 8 }}>
<Tooltip title="Hier trennen">
<Button
type="primary"
danger
shape="circle"
size="small"
icon={<ScissorOutlined />}
onClick={() => onSplit(pageNum)}
/>
</Tooltip>
</div>
)}
</div>
))}
</div>
</div>
);
}
function ImageWithAuth({ url, token }: { url: string; token: string }) {
const [imgSrc, setImgSrc] = useState<string | null>(null);
useEffect(() => {
let objectUrl: string;
fetch(url, { headers: { Authorization: `Bearer ${token}` } })
.then(res => {
if (!res.ok) throw new Error('Network response was not ok');
return res.blob();
})
.then(blob => {
objectUrl = URL.createObjectURL(blob);
setImgSrc(objectUrl);
})
.catch(err => {
console.error('Error loading image', err);
});
return () => {
if (objectUrl) URL.revokeObjectURL(objectUrl);
};
}, [url, token]);
if (!imgSrc) return <Spin style={{ margin: '0 20px' }} />;
return <img src={imgSrc} alt="PDF Page" style={{ height: '100%', objectFit: 'contain' }} />;
}