feat: improve frontend accessibility, dark mode, and UX
Build and Push Multi-Platform Images / build-and-push (push) Successful in 18s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 18s
- Replace div with semantic button elements in AppLayout for accessibility - Use Ant Design theme tokens instead of hardcoded colors in MailDetailPage - Add loading state to refresh buttons and pagination info - Add German empty state messages to tables - Clean up unnecessary ConfigProvider wrappers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -125,7 +125,7 @@ export default function AppLayout() {
|
||||
}}
|
||||
>
|
||||
{/* Logo / Collapse-Toggle */}
|
||||
<div
|
||||
<button
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
style={{
|
||||
height: 56,
|
||||
@@ -134,7 +134,11 @@ export default function AppLayout() {
|
||||
justifyContent: 'center',
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none',
|
||||
border: 'none',
|
||||
borderBottom: `1px solid ${isDark ? 'rgba(255,255,255,0.08)' : '#e2e4ea'}`,
|
||||
background: 'transparent',
|
||||
width: '100%',
|
||||
padding: 0,
|
||||
transition: 'background 0.2s',
|
||||
}}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.background = isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.02)')}
|
||||
@@ -143,7 +147,7 @@ export default function AppLayout() {
|
||||
<Text strong style={{ color: logoColor, fontSize: collapsed ? 14 : 18, transition: 'font-size 0.2s' }}>
|
||||
{collapsed ? 'PM' : 'Paperless'}
|
||||
</Text>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Navigation Menu */}
|
||||
<Menu
|
||||
@@ -179,7 +183,7 @@ export default function AppLayout() {
|
||||
>
|
||||
{/* Theme Toggle */}
|
||||
<Tooltip title={isDark ? 'Light Mode' : 'Dark Mode'} placement="right">
|
||||
<div
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
style={{
|
||||
display: 'flex',
|
||||
@@ -190,14 +194,17 @@ export default function AppLayout() {
|
||||
cursor: 'pointer',
|
||||
color: subtleColor,
|
||||
justifyContent: collapsed ? 'center' : 'flex-start',
|
||||
transition: 'all 0.2s',
|
||||
border: 'none',
|
||||
background: 'transparent',
|
||||
width: '100%',
|
||||
transition: 'background 0.2s',
|
||||
}}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.background = isDark ? 'rgba(255,255,255,0.08)' : '#eef1f8')}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}
|
||||
>
|
||||
{isDark ? <SunOutlined style={{ fontSize: 16 }} /> : <MoonOutlined style={{ fontSize: 16 }} />}
|
||||
{!collapsed && <Text style={{ color: subtleColor, fontSize: 13 }}>{isDark ? 'Light Mode' : 'Dark Mode'}</Text>}
|
||||
</div>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
{/* User Menu */}
|
||||
|
||||
@@ -1176,7 +1176,7 @@ export default function InboxDetailPage() {
|
||||
<Button icon={<ArrowLeftOutlined />} onClick={() => navigate('/inbox')}>
|
||||
Zurück
|
||||
</Button>
|
||||
<Title level={4} style={{ margin: 0 }}>
|
||||
<Title level={3} style={{ margin: 0 }}>
|
||||
{file.name}
|
||||
</Title>
|
||||
<SourceTag source={file.source} />
|
||||
|
||||
@@ -387,7 +387,7 @@ export default function InboxPage() {
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
allowClear
|
||||
/>
|
||||
<Button icon={<ReloadOutlined />} onClick={load}>
|
||||
<Button icon={<ReloadOutlined />} onClick={load} loading={loading}>
|
||||
Aktualisieren
|
||||
</Button>
|
||||
<Popconfirm
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Card, Button, Space, Spin, Tag, Typography, Table, message, Empty, Popconfirm
|
||||
Card, Button, Space, Spin, Tag, Typography, Table, message, Empty, Popconfirm, theme
|
||||
} from 'antd';
|
||||
import { ArrowLeftOutlined, FileTextOutlined, CloseCircleOutlined, LinkOutlined } from '@ant-design/icons';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
@@ -23,6 +23,7 @@ export default function MailDetailPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [previewLoading, setPreviewLoading] = useState(false);
|
||||
const [wizardOpen, setWizardOpen] = useState(false);
|
||||
const { token } = theme.useToken();
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
@@ -208,7 +209,7 @@ export default function MailDetailPage() {
|
||||
|
||||
{/* Rechte Seite: Anhänge + Vorschau */}
|
||||
<Card size="small" styles={{ body: { padding: 0, display: 'flex', flexDirection: 'column', height: 'calc(100vh - 200px)' } }}>
|
||||
<div style={{ flex: '0 0 auto', borderBottom: '1px solid #303030', maxHeight: 240, overflow: 'auto' }}>
|
||||
<div style={{ flex: '0 0 auto', borderBottom: `1px solid ${token.colorBorder}`, maxHeight: 240, overflow: 'auto' }}>
|
||||
<Table<EmailAttachment>
|
||||
columns={columns}
|
||||
dataSource={attachments}
|
||||
@@ -223,7 +224,7 @@ export default function MailDetailPage() {
|
||||
locale={{ emptyText: <Empty description="Keine Anhänge" image={Empty.PRESENTED_IMAGE_SIMPLE} /> }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ flex: 1, minHeight: 0, background: '#1a1a2e' }}>
|
||||
<div style={{ flex: 1, minHeight: 0, background: token.colorBgLayout }}>
|
||||
{previewLoading ? (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
|
||||
<Spin />
|
||||
@@ -240,7 +241,7 @@ export default function MailDetailPage() {
|
||||
)
|
||||
) : (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%', color: '#888' }}>
|
||||
<Space vertical align="center">
|
||||
<Space direction="vertical" align="center">
|
||||
<FileTextOutlined style={{ fontSize: 48 }} />
|
||||
<Text type="secondary">Kein Anhang ausgewählt</Text>
|
||||
</Space>
|
||||
|
||||
@@ -219,6 +219,7 @@ export default function MailpostfachPage() {
|
||||
loading={loading}
|
||||
rowKey="Id"
|
||||
pagination={{ pageSize: 20, showSizeChanger: true, showTotal: (t) => `${t} E-Mails` }}
|
||||
locale={{ emptyText: 'Keine Einträge vorhanden' }}
|
||||
onRow={(record) => ({
|
||||
onClick: () => navigate(`/mailpostfach/${record.Id}`),
|
||||
style: { cursor: 'pointer' },
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Table, Popover, Button, Space, message, ConfigProvider, Tooltip } from 'antd';
|
||||
import { Table, Popover, Button, Space, message, Tooltip, Typography } from 'antd';
|
||||
|
||||
const { Title } = Typography;
|
||||
import { ReloadOutlined } from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import { posteingangApi } from '../api/posteingang';
|
||||
@@ -97,7 +99,7 @@ export default function ManuellBearbeitenPage() {
|
||||
render: (text: string) => dayjs(text).format('DD.MM.YYYY HH:mm'),
|
||||
},
|
||||
{
|
||||
title: 'Aktion',
|
||||
title: 'Aktionen',
|
||||
key: 'action',
|
||||
width: 150,
|
||||
render: (_: any, record: PosteingangDocument) => (
|
||||
@@ -109,10 +111,9 @@ export default function ManuellBearbeitenPage() {
|
||||
];
|
||||
|
||||
return (
|
||||
<ConfigProvider>
|
||||
<div style={{ padding: '0 24px 24px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||
<h2>Manuell bearbeiten</h2>
|
||||
<div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||
<Title level={3} style={{ margin: 0 }}>Manuell bearbeiten</Title>
|
||||
<Space>
|
||||
<Tooltip title="Manuell aktualisieren">
|
||||
<Button icon={<ReloadOutlined />} onClick={fetchData} loading={loading} />
|
||||
@@ -124,9 +125,10 @@ export default function ManuellBearbeitenPage() {
|
||||
dataSource={data}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{ pageSize: 20 }}
|
||||
pagination={{ pageSize: 20, showSizeChanger: true, showTotal: (t) => `${t} Dokumente` }}
|
||||
locale={{ emptyText: 'Keine Einträge vorhanden' }}
|
||||
/>
|
||||
|
||||
|
||||
<DocumentEditModal
|
||||
documentId={selectedDoc?.id || null}
|
||||
document={selectedDoc}
|
||||
@@ -136,7 +138,6 @@ export default function ManuellBearbeitenPage() {
|
||||
isPosteingang={false}
|
||||
hasNextDocument={data.length > 1}
|
||||
/>
|
||||
</div>
|
||||
</ConfigProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Table, Popover, Button, Space, message, ConfigProvider, Tooltip } from 'antd';
|
||||
import { Table, Popover, Button, Space, message, Tooltip, Typography } from 'antd';
|
||||
import { AuthImage } from '../utils/auth-resource';
|
||||
import { ReloadOutlined } from '@ant-design/icons';
|
||||
|
||||
const { Title } = Typography;
|
||||
import dayjs from 'dayjs';
|
||||
import { posteingangApi } from '../api/posteingang';
|
||||
import type { PosteingangDocument } from '../api/posteingang';
|
||||
@@ -98,7 +100,7 @@ export default function PosteingangPage() {
|
||||
render: (text: string) => dayjs(text).format('DD.MM.YYYY HH:mm'),
|
||||
},
|
||||
{
|
||||
title: 'Aktion',
|
||||
title: 'Aktionen',
|
||||
key: 'action',
|
||||
width: 150,
|
||||
render: (_: any, record: PosteingangDocument) => (
|
||||
@@ -110,10 +112,9 @@ export default function PosteingangPage() {
|
||||
];
|
||||
|
||||
return (
|
||||
<ConfigProvider>
|
||||
<div style={{ padding: '0 24px 24px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||
<h2>Posteingang</h2>
|
||||
<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} />
|
||||
@@ -125,9 +126,10 @@ export default function PosteingangPage() {
|
||||
dataSource={data}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{ pageSize: 20 }}
|
||||
pagination={{ pageSize: 20, showSizeChanger: true, showTotal: (t) => `${t} Dokumente` }}
|
||||
locale={{ emptyText: 'Keine Einträge vorhanden' }}
|
||||
/>
|
||||
|
||||
|
||||
<DocumentEditModal
|
||||
documentId={selectedDoc?.id || null}
|
||||
document={selectedDoc}
|
||||
@@ -137,7 +139,6 @@ export default function PosteingangPage() {
|
||||
isPosteingang={true}
|
||||
hasNextDocument={data.length > 1}
|
||||
/>
|
||||
</div>
|
||||
</ConfigProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -161,6 +161,7 @@ export default function TaskLogPage() {
|
||||
rowKey="TaskId"
|
||||
loading={loading}
|
||||
pagination={{ pageSize: 20 }}
|
||||
locale={{ emptyText: 'Keine Einträge vorhanden' }}
|
||||
/>
|
||||
</div>
|
||||
</ConfigProvider>
|
||||
|
||||
Reference in New Issue
Block a user