Skip to content

Commit 3223d50

Browse files
authored
feat: display both net total and turnover in transactions footer (#53)
- Add calcNetTotalPerCurrency function to calculate signed totals (income positive, expenses negative) - Update transaction list footer to display both net total and turnover - Use semantic HTML markup (<dl>, <dt>, <dd>) for better accessibility - Add color coding: green for positive net, red for negative net - Maintain turnover calculation for total transaction volume Fixes issue where transaction totals did not respect transaction type (income/expense)
1 parent 07e05aa commit 3223d50

File tree

2 files changed

+50
-8
lines changed

2 files changed

+50
-8
lines changed

components/transactions/list.tsx

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { BulkActionsMenu } from "@/components/transactions/bulk-actions"
44
import { Badge } from "@/components/ui/badge"
55
import { Checkbox } from "@/components/ui/checkbox"
66
import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "@/components/ui/table"
7-
import { calcTotalPerCurrency, isTransactionIncomplete } from "@/lib/stats"
7+
import { calcNetTotalPerCurrency, calcTotalPerCurrency, isTransactionIncomplete } from "@/lib/stats"
88
import { cn, formatCurrency } from "@/lib/utils"
99
import { Category, Field, Project, Transaction } from "@/prisma/client"
1010
import { formatDate } from "date-fns"
@@ -112,14 +112,30 @@ export const standardFieldRenderers: Record<string, FieldRenderer> = {
112112
</div>
113113
),
114114
footerValue: (transactions: Transaction[]) => {
115-
const totalPerCurrency = calcTotalPerCurrency(transactions)
115+
const netTotalPerCurrency = calcNetTotalPerCurrency(transactions)
116+
const turnoverPerCurrency = calcTotalPerCurrency(transactions)
117+
116118
return (
117-
<div className="flex flex-col">
118-
{Object.entries(totalPerCurrency).map(([currency, total]) => (
119-
<div key={currency} className="text-sm first:text-base">
120-
{formatCurrency(total, currency)}
121-
</div>
122-
))}
119+
<div className="flex flex-col gap-3 text-right">
120+
<dl className="space-y-1">
121+
<dt className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">Net Total</dt>
122+
{Object.entries(netTotalPerCurrency).map(([currency, total]) => (
123+
<dd
124+
key={`net-${currency}`}
125+
className={cn("text-sm first:text-base font-medium", total >= 0 ? "text-green-600" : "text-red-600")}
126+
>
127+
{formatCurrency(total, currency)}
128+
</dd>
129+
))}
130+
</dl>
131+
<dl className="space-y-1">
132+
<dt className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">Turnover</dt>
133+
{Object.entries(turnoverPerCurrency).map(([currency, total]) => (
134+
<dd key={`turnover-${currency}`} className="text-sm text-muted-foreground">
135+
{formatCurrency(total, currency)}
136+
</dd>
137+
))}
138+
</dl>
123139
</div>
124140
)
125141
},

lib/stats.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,32 @@ export function calcTotalPerCurrency(transactions: Transaction[]): Record<string
1616
)
1717
}
1818

19+
export function calcNetTotalPerCurrency(transactions: Transaction[]): Record<string, number> {
20+
return transactions.reduce(
21+
(acc, transaction) => {
22+
let amount = 0
23+
let currency: string | undefined
24+
if (
25+
transaction.convertedTotal !== null &&
26+
transaction.convertedTotal !== undefined &&
27+
transaction.convertedCurrencyCode
28+
) {
29+
amount = transaction.convertedTotal
30+
currency = transaction.convertedCurrencyCode.toUpperCase()
31+
} else if (transaction.total !== null && transaction.total !== undefined && transaction.currencyCode) {
32+
amount = transaction.total
33+
currency = transaction.currencyCode.toUpperCase()
34+
}
35+
if (currency && amount !== 0) {
36+
const sign = transaction.type === "expense" ? -1 : 1
37+
acc[currency] = (acc[currency] || 0) + amount * sign
38+
}
39+
return acc
40+
},
41+
{} as Record<string, number>
42+
)
43+
}
44+
1945
export const isTransactionIncomplete = (fields: Field[], transaction: Transaction): boolean => {
2046
const incompleteFields = incompleteTransactionFields(fields, transaction)
2147

0 commit comments

Comments
 (0)