feat: add mobile-responsive layout and card views across all pages
Build and Push Multi-Platform Images / build-and-push (push) Successful in 39s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 39s
- New useIsMobile hook and MobileCardList component - AppLayout: hamburger menu + Drawer navigation on mobile - All list pages (Inbox, Posteingang, Manuell, Mail, Freigabe, Zahlung, TaskLog) show card layout on mobile instead of tables - CSS: responsive modal height, horizontal table scroll, text-size-adjust - Backend: Agrarmonitor polling fixes, Zahlung service improvements, IMAP folder service extended, misc controller fixes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,13 +5,22 @@ import type { ColumnsType } from 'antd/es/table';
|
||||
import dayjs from 'dayjs';
|
||||
import { zahlungApi, type ZahlungDocument, type ZahlungOption, type ZahlungFilter } from '../api/zahlung';
|
||||
import { paperlessApi, type PaperlessDocType, type PaperlessCorrespondent } from '../api/paperless';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
import MobileCardList from '../components/MobileCardList';
|
||||
|
||||
const { Title } = Typography;
|
||||
const { Title, Text } = Typography;
|
||||
const FREIGABE_FIELD_ID = 15;
|
||||
const ZAHLUNG_FIELD_ID = 16;
|
||||
const FREIGABE_WERT_FREIGEGEBEN = 'freigegeben';
|
||||
|
||||
const FILTER_OPTIONS: { value: ZahlungFilter; label: string }[] = [
|
||||
{ value: 'ausstehend', label: 'Freigegeben, noch nicht bezahlt' },
|
||||
{ value: 'freigegeben', label: 'Alle freigegebenen' },
|
||||
{ value: 'alle', label: 'Alle' },
|
||||
];
|
||||
|
||||
export default function ZahlungPage() {
|
||||
const isMobile = useIsMobile();
|
||||
const [data, setData] = useState<ZahlungDocument[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -132,7 +141,7 @@ export default function ZahlungPage() {
|
||||
},
|
||||
{
|
||||
title: 'Erstellt',
|
||||
dataIndex: 'created_date',
|
||||
dataIndex: 'created',
|
||||
key: 'created',
|
||||
width: 110,
|
||||
render: (v: string) => v ? dayjs(v).format('DD.MM.YYYY') : '—',
|
||||
@@ -178,45 +187,100 @@ export default function ZahlungPage() {
|
||||
},
|
||||
];
|
||||
|
||||
const paginationConfig = {
|
||||
current: page,
|
||||
pageSize,
|
||||
total,
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: ['25', '50', '100'],
|
||||
onChange: (p: number, ps: number) => {
|
||||
setPage(p);
|
||||
setPageSize(ps);
|
||||
},
|
||||
showTotal: (t: number) => `${t} Belege`,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title level={4} style={{ marginTop: 0, marginBottom: 16 }}>Zahlung</Title>
|
||||
|
||||
<Space style={{ marginBottom: 16 }}>
|
||||
<Radio.Group
|
||||
{isMobile ? (
|
||||
<Select
|
||||
style={{ width: '100%', marginBottom: 16 }}
|
||||
value={filter}
|
||||
onChange={(e) => {
|
||||
onChange={(v) => {
|
||||
setPage(1);
|
||||
setFilter(e.target.value);
|
||||
setFilter(v);
|
||||
}}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
>
|
||||
<Radio.Button value="ausstehend">Freigegeben, noch nicht bezahlt</Radio.Button>
|
||||
<Radio.Button value="freigegeben">Alle freigegebenen</Radio.Button>
|
||||
<Radio.Button value="alle">Alle</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Space>
|
||||
options={FILTER_OPTIONS}
|
||||
/>
|
||||
) : (
|
||||
<Space style={{ marginBottom: 16 }}>
|
||||
<Radio.Group
|
||||
value={filter}
|
||||
onChange={(e) => {
|
||||
setPage(1);
|
||||
setFilter(e.target.value);
|
||||
}}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
>
|
||||
{FILTER_OPTIONS.map((o) => (
|
||||
<Radio.Button key={o.value} value={o.value}>{o.label}</Radio.Button>
|
||||
))}
|
||||
</Radio.Group>
|
||||
</Space>
|
||||
)}
|
||||
|
||||
<Table<ZahlungDocument>
|
||||
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`,
|
||||
}}
|
||||
/>
|
||||
{isMobile ? (
|
||||
<MobileCardList<ZahlungDocument>
|
||||
dataSource={data}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={paginationConfig}
|
||||
emptyText="Keine Belege vorhanden"
|
||||
renderCard={(doc) => {
|
||||
const freigegeben = istFreigegeben(doc);
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<Text strong>{doc.title || '—'}</Text>
|
||||
<Text type="secondary" style={{ fontSize: 13 }}>Dokumenttyp: {getDocTypeName(doc.document_type)}</Text>
|
||||
<Text type="secondary" style={{ fontSize: 13 }}>Absender: {getCorrespondentName(doc.correspondent)}</Text>
|
||||
<Text type="secondary" style={{ fontSize: 13 }}>
|
||||
Erstellt: {doc.created ? dayjs(doc.created).format('DD.MM.YYYY') : '—'}
|
||||
</Text>
|
||||
<div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
|
||||
{renderFreigabeTag(doc)}
|
||||
{renderZahlungTag(doc)}
|
||||
</div>
|
||||
<Button
|
||||
icon={<EuroOutlined />}
|
||||
type="primary"
|
||||
block
|
||||
disabled={!freigegeben}
|
||||
onClick={() => openModal(doc)}
|
||||
>
|
||||
Zahlung verbuchen
|
||||
</Button>
|
||||
{!freigegeben && (
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
Beleg muss zuerst freigegeben werden
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Table<ZahlungDocument>
|
||||
dataSource={data}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
size="small"
|
||||
pagination={paginationConfig}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Modal
|
||||
title="Zahlung verbuchen"
|
||||
|
||||
Reference in New Issue
Block a user