diff --git a/frontend/src/components/admin/ReceivedEmailsPanel.tsx b/frontend/src/components/admin/ReceivedEmailsPanel.tsx index c2169816..0693c4af 100644 --- a/frontend/src/components/admin/ReceivedEmailsPanel.tsx +++ b/frontend/src/components/admin/ReceivedEmailsPanel.tsx @@ -21,7 +21,7 @@ export const ReceivedEmailsPanel: React.FC = () => { const { t } = useTranslation(); const { formatDateTime: fmtDateTime } = useLocalizedDate(); const [page, setPage] = useState(1); - const { data, isLoading } = useQuery({ queryKey: ['received-emails', page], queryFn: () => emailService.listReceived({ page, pageSize: 25 }) }); + const { data, isLoading } = useQuery({ queryKey: ['received-emails', page], queryFn: () => emailService.listReceived({ page, pageSize: 25 }), refetchInterval: 30000, refetchOnWindowFocus: true }); if (isLoading) return ; const items = data?.items ?? []; diff --git a/frontend/src/pages/admin/accounting/AccountingInboxPage.tsx b/frontend/src/pages/admin/accounting/AccountingInboxPage.tsx index 0e10aa4d..ffcc0541 100644 --- a/frontend/src/pages/admin/accounting/AccountingInboxPage.tsx +++ b/frontend/src/pages/admin/accounting/AccountingInboxPage.tsx @@ -23,7 +23,9 @@ import { const DISPOSITIONS: Disposition[] = ['rebill', 'durchlaufend', 'eigener_aufwand', 'duplikat', 'abgelehnt']; const PAYMENT_METHODS: PaymentMethod[] = ['bank_transfer', 'cash', 'twint', 'paypal', 'card', 'other']; -const BOOKING_DISPOSITIONS: Disposition[] = ['rebill', 'durchlaufend', 'eigener_aufwand']; +// Only client-facing dispositions book to an event. A "Company expense" +// (eigener_aufwand) always books to the company — no event picker. +const BOOKING_DISPOSITIONS: Disposition[] = ['rebill', 'durchlaufend']; const statusClasses: Record = { unsorted: 'bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-300', @@ -42,11 +44,13 @@ const selectCls = 'w-full rounded-md border border-neutral-300 dark:border-neutr * the triage, view, and mark-paid modals so the admin can always re-read * the slip — #1. */ -const DocumentPreview: React.FC<{ doc: InboundDocument; maxHeight?: string }> = ({ doc, maxHeight = '60vh' }) => { +const DocumentPreview: React.FC<{ doc: InboundDocument; maxHeight?: string; initialPage?: 'first' | 'last' }> = ({ doc, maxHeight = '60vh', initialPage = 'first' }) => { const { t } = useTranslation(); const isPdf = (doc.mimeType || '').includes('pdf'); const pageCount = doc.pageCount || 1; - const [page, setPage] = useState(pageCount); + // Categorisation reads the invoice header (supplier/amount/date) → first page; + // payment needs the Swiss QR-bill → last page. + const [page, setPage] = useState(initialPage === 'last' ? pageCount : 1); const [imgUrl, setImgUrl] = useState(null); const [previewError, setPreviewError] = useState(false); useEffect(() => { @@ -71,7 +75,7 @@ const DocumentPreview: React.FC<{ doc: InboundDocument; maxHeight?: string }> = )} - {isPdf &&

{t('accounting.inbox.qrHint', 'Showing the last page — the Swiss QR-bill usually sits at the bottom.')}

} + {isPdf && initialPage === 'last' &&

{t('accounting.inbox.qrHint', 'Showing the last page — the Swiss QR-bill usually sits at the bottom.')}

} ); }; @@ -96,7 +100,7 @@ const PayModal: React.FC<{ doc: InboundDocument; onClose: () => void; onDone: ()
-
+

{t('accounting.incoming.outstanding', 'Outstanding')}: {doc.totalAmountMinor != null ? formatMoneyMinor(doc.totalAmountMinor, doc.currency || 'CHF') : '—'} @@ -271,7 +275,9 @@ export const AccountingInboxPage: React.FC = () => { const [payDoc, setPayDoc] = useState(null); const [viewDoc, setViewDoc] = useState(null); - const { data, isLoading } = useQuery({ queryKey: ['accounting-inbound'], queryFn: () => accountingService.listInbound({ pageSize: 100 }) }); + // Auto-refresh so emails the IMAP poller ingests in the background appear + // without a manual reload (the poller runs server-side every 60s). + const { data, isLoading } = useQuery({ queryKey: ['accounting-inbound'], queryFn: () => accountingService.listInbound({ pageSize: 100 }), refetchInterval: 30000, refetchOnWindowFocus: true }); const { data: categories } = useQuery({ queryKey: ['expense-categories'], queryFn: () => accountingService.listCategories() }); const upload = useMutation({