Skip to content

Commit d908de6

Browse files
committed
style: enhance ReceiptUploadView layout and add ReceiptDetailFields component
1 parent 7453a9a commit d908de6

File tree

6 files changed

+171
-32
lines changed

6 files changed

+171
-32
lines changed

frontend/spa/src/components/InvoiceUploadForm/InvoiceUploadFormDropzone.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ const InvoiceUploadFormDropzone: FC<InvoiceUploadFormDropzoneProps> = ({ onFiles
309309
<div
310310
{...getRootProps()}
311311
className={cn(
312-
'relative rounded-xl border-2 border-dashed flex flex-col justify-center items-center text-center w-full h-full overflow-hidden cursor-pointer transition-all duration-200',
312+
'relative rounded-xl border-2 border-dashed flex flex-col justify-center items-center text-center w-full h-full overflow-hidden cursor-pointer transition-all duration-200 shadow-md',
313313
hasPreview ? 'bg-white dark:bg-gray-950 border-[#A7A7A7] p-2' : 'bg-white dark:bg-gray-950 border-[#A7A7A7] hover:border-primary/50',
314314
isHighlightDrop && 'border-primary bg-primary/5 ring-2 ring-primary/20',
315315
isDragActive && 'border-primary bg-primary/5 scale-[0.99]'

frontend/spa/src/components/views/ReceiptUpload/ReceiptUploadView.tsx

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
66
import { useTranslation } from 'react-i18next';
77
import { useNavigate, useParams } from 'react-router-dom';
88

9-
import { ReceiptFormActions, ReceiptFormFields } from './components';
9+
import { ReceiptDetailFields, ReceiptFormActions, ReceiptFormFields } from './components';
1010
import { useReceiptForm } from './hooks';
1111

1212
import InvoiceUploadFormDropzone from '@/components/InvoiceUploadForm/InvoiceUploadFormDropzone';
@@ -825,36 +825,54 @@ function ReceiptUploadView() {
825825
</div>
826826
)}
827827

828-
<div className="h-full min-w-0 border border-[#A7A7A7] bg-card rounded-[30px] p-5 2xl:p-8">
829-
<ReceiptFormFields
830-
receiptNumber={receiptNumber}
831-
onReceiptNumberChange={handleReceiptNumberChange}
832-
receiptDate={receiptDate}
833-
onReceiptDateChange={handleReceiptDateChange}
834-
dueDate={dueDate}
835-
onDueDateChange={setDueDate}
836-
transactionKind={transactionKind}
837-
onTransactionKindChange={handleTransactionKindChange}
838-
isUnpaid={isUnpaid}
839-
onIsUnpaidChange={setIsUnpaid}
840-
contractPartner={contractPartner}
841-
onContractPartnerChange={handleContractPartnerChange}
842-
bommelId={bommelId}
843-
onBommelIdChange={handleBommelIdChange}
844-
category={category}
845-
onCategoryChange={setCategory}
846-
area={area}
847-
onAreaChange={handleAreaChange}
848-
tags={tags}
849-
onTagsChange={setTags}
850-
grossAmount={grossAmount}
851-
onGrossAmountChange={handleGrossAmountChange}
852-
taxAmount={taxAmount}
853-
onTaxAmountChange={handleTaxAmountChange}
854-
loadingStates={loadingStates}
855-
errors={formErrors}
856-
readOnly={isReadOnly}
857-
/>
828+
<div
829+
className={`h-full min-w-0 border border-[#A7A7A7] rounded-[30px] p-5 2xl:p-8 shadow-md ${isReadOnly ? 'bg-white p-8 2xl:p-10' : 'bg-card'}`}
830+
>
831+
{isReadOnly ? (
832+
<ReceiptDetailFields
833+
receiptNumber={receiptNumber}
834+
receiptDate={receiptDate}
835+
dueDate={dueDate}
836+
transactionKind={transactionKind}
837+
isUnpaid={isUnpaid}
838+
contractPartner={contractPartner}
839+
bommelId={bommelId}
840+
category={category}
841+
area={area}
842+
tags={tags}
843+
grossAmount={grossAmount}
844+
taxAmount={taxAmount}
845+
/>
846+
) : (
847+
<ReceiptFormFields
848+
receiptNumber={receiptNumber}
849+
onReceiptNumberChange={handleReceiptNumberChange}
850+
receiptDate={receiptDate}
851+
onReceiptDateChange={handleReceiptDateChange}
852+
dueDate={dueDate}
853+
onDueDateChange={setDueDate}
854+
transactionKind={transactionKind}
855+
onTransactionKindChange={handleTransactionKindChange}
856+
isUnpaid={isUnpaid}
857+
onIsUnpaidChange={setIsUnpaid}
858+
contractPartner={contractPartner}
859+
onContractPartnerChange={handleContractPartnerChange}
860+
bommelId={bommelId}
861+
onBommelIdChange={handleBommelIdChange}
862+
category={category}
863+
onCategoryChange={setCategory}
864+
area={area}
865+
onAreaChange={handleAreaChange}
866+
tags={tags}
867+
onTagsChange={setTags}
868+
grossAmount={grossAmount}
869+
onGrossAmountChange={handleGrossAmountChange}
870+
taxAmount={taxAmount}
871+
onTaxAmountChange={handleTaxAmountChange}
872+
loadingStates={loadingStates}
873+
errors={formErrors}
874+
/>
875+
)}
858876
</div>
859877
</div>
860878

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { useMemo } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
4+
import { Label } from '@/components/ui/Label';
5+
import { useCategories } from '@/hooks/queries';
6+
import { useBommelsStore } from '@/store/bommels/bommelsStore';
7+
8+
interface ReceiptDetailFieldsProps {
9+
receiptNumber: string;
10+
receiptDate: Date | undefined;
11+
dueDate: Date | undefined;
12+
transactionKind: 'intake' | 'expense' | '';
13+
isUnpaid: boolean;
14+
contractPartner: string;
15+
bommelId: number | null;
16+
category: string;
17+
area: string;
18+
tags: string[];
19+
grossAmount: string;
20+
taxAmount: string;
21+
}
22+
23+
function DetailField({ label, value, fullWidth }: { label: string; value: React.ReactNode; fullWidth?: boolean }) {
24+
return (
25+
<div className={fullWidth ? 'sm:col-span-2' : ''}>
26+
<Label className="text-muted-foreground text-xs">{label}</Label>
27+
<p className="text-sm font-medium mt-1 min-h-[1.25rem]">{value || '—'}</p>
28+
</div>
29+
);
30+
}
31+
32+
export function ReceiptDetailFields({
33+
receiptNumber,
34+
receiptDate,
35+
dueDate,
36+
transactionKind,
37+
isUnpaid,
38+
contractPartner,
39+
bommelId,
40+
category,
41+
area,
42+
tags,
43+
grossAmount,
44+
taxAmount,
45+
}: ReceiptDetailFieldsProps) {
46+
const { t } = useTranslation();
47+
const { data: categories = [] } = useCategories();
48+
const allBommels = useBommelsStore((s) => s.allBommels);
49+
50+
const areaLabels: Record<string, string> = {
51+
IDEELL: t('receipts.areas.ideell'),
52+
ZWECKBETRIEB: t('receipts.areas.zweckbetrieb'),
53+
VERMOEGENSVERWALTUNG: t('receipts.areas.vermoegensverwaltung'),
54+
WIRTSCHAFTLICH: t('receipts.areas.wirtschaftlich'),
55+
UNKNOWN: t('receipts.areas.unknown'),
56+
};
57+
58+
const bommelName = useMemo(() => {
59+
if (!bommelId) return '';
60+
const bommel = allBommels.find((b) => b.id === bommelId);
61+
return bommel?.name ?? '';
62+
}, [bommelId, allBommels]);
63+
64+
const categoryName = useMemo(() => {
65+
if (!category) return '';
66+
const cat = categories.find((c) => String(c.id) === category);
67+
return cat?.name ?? '';
68+
}, [category, categories]);
69+
70+
const formatDate = (date: Date | undefined) => {
71+
if (!date) return '';
72+
return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
73+
};
74+
75+
const formatCurrency = (value: string) => {
76+
if (!value) return '';
77+
return `${value} €`;
78+
};
79+
80+
return (
81+
<div className="grid grid-cols-1 gap-x-4 gap-y-6 2xl:gap-x-8 2xl:gap-y-8 sm:grid-cols-2">
82+
<DetailField label={t('receipts.upload.receiptNumber')} value={receiptNumber} />
83+
<DetailField label={t('receipts.upload.receiptDate')} value={formatDate(receiptDate)} />
84+
85+
<DetailField
86+
label={t('receipts.upload.transactionKind')}
87+
value={transactionKind === 'intake' ? t('receipts.types.income') : transactionKind === 'expense' ? t('receipts.types.expense') : ''}
88+
/>
89+
<DetailField label={t('receipts.upload.paymentStatus')} value={isUnpaid ? t('receipts.upload.unpaid') : t('receipts.upload.paid')} />
90+
91+
<DetailField label={t('receipts.upload.contractPartner')} value={contractPartner} fullWidth />
92+
93+
<DetailField label={t('receipts.upload.bommel')} value={bommelName} />
94+
<DetailField label={t('receipts.upload.area')} value={areaLabels[area] ?? area} />
95+
96+
<DetailField label={t('receipts.upload.dueDate')} value={formatDate(dueDate)} />
97+
<DetailField label={t('receipts.upload.category')} value={categoryName} />
98+
99+
{tags.length > 0 && (
100+
<div className="sm:col-span-2">
101+
<Label className="text-muted-foreground text-xs">{t('receipts.upload.tags')}</Label>
102+
<div className="flex flex-wrap gap-2 mt-1">
103+
{tags.map((tag, idx) => (
104+
<span key={`${tag}-${idx}`} className="px-4 py-1.5 rounded-[15px] bg-purple-100 text-xs font-medium">
105+
{tag}
106+
</span>
107+
))}
108+
</div>
109+
</div>
110+
)}
111+
112+
<DetailField label={t('receipts.upload.taxAmount')} value={formatCurrency(taxAmount)} />
113+
<DetailField label={t('receipts.upload.grossAmount')} value={formatCurrency(grossAmount)} />
114+
</div>
115+
);
116+
}
117+
118+
export default ReceiptDetailFields;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
export { ReceiptDetailFields } from './ReceiptDetailFields';
12
export { ReceiptFormActions } from './ReceiptFormActions';
23
export { ReceiptFormFields } from './ReceiptFormFields';

frontend/spa/src/locales/de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@
400400
"receiptNumber": "Belegnummer",
401401
"receiptDate": "Belegdatum",
402402
"transactionType": "Belegart",
403+
"paymentStatus": "Zahlungsstatus",
403404
"unpaid": "Unbezahlt",
404405
"paid": "Bezahlt",
405406
"contractPartner": "Vertragspartner",

frontend/spa/src/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,7 @@
474474
"receiptNumber": "Receipt number",
475475
"receiptDate": "Receipt date",
476476
"transactionType": "Transaction type",
477+
"paymentStatus": "Payment status",
477478
"unpaid": "Unpaid",
478479
"paid": "Paid",
479480
"contractPartner": "Contract partner",

0 commit comments

Comments
 (0)