mirror of
https://github.com/marcogll/TaxHacker_s23.git
synced 2026-01-13 13:25:18 +00:00
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)
This commit is contained in:
@@ -4,7 +4,7 @@ import { BulkActionsMenu } from "@/components/transactions/bulk-actions"
|
|||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Checkbox } from "@/components/ui/checkbox"
|
import { Checkbox } from "@/components/ui/checkbox"
|
||||||
import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||||
import { calcTotalPerCurrency, isTransactionIncomplete } from "@/lib/stats"
|
import { calcNetTotalPerCurrency, calcTotalPerCurrency, isTransactionIncomplete } from "@/lib/stats"
|
||||||
import { cn, formatCurrency } from "@/lib/utils"
|
import { cn, formatCurrency } from "@/lib/utils"
|
||||||
import { Category, Field, Project, Transaction } from "@/prisma/client"
|
import { Category, Field, Project, Transaction } from "@/prisma/client"
|
||||||
import { formatDate } from "date-fns"
|
import { formatDate } from "date-fns"
|
||||||
@@ -112,14 +112,30 @@ export const standardFieldRenderers: Record<string, FieldRenderer> = {
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
footerValue: (transactions: Transaction[]) => {
|
footerValue: (transactions: Transaction[]) => {
|
||||||
const totalPerCurrency = calcTotalPerCurrency(transactions)
|
const netTotalPerCurrency = calcNetTotalPerCurrency(transactions)
|
||||||
|
const turnoverPerCurrency = calcTotalPerCurrency(transactions)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col gap-3 text-right">
|
||||||
{Object.entries(totalPerCurrency).map(([currency, total]) => (
|
<dl className="space-y-1">
|
||||||
<div key={currency} className="text-sm first:text-base">
|
<dt className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">Net Total</dt>
|
||||||
{formatCurrency(total, currency)}
|
{Object.entries(netTotalPerCurrency).map(([currency, total]) => (
|
||||||
</div>
|
<dd
|
||||||
))}
|
key={`net-${currency}`}
|
||||||
|
className={cn("text-sm first:text-base font-medium", total >= 0 ? "text-green-600" : "text-red-600")}
|
||||||
|
>
|
||||||
|
{formatCurrency(total, currency)}
|
||||||
|
</dd>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
<dl className="space-y-1">
|
||||||
|
<dt className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">Turnover</dt>
|
||||||
|
{Object.entries(turnoverPerCurrency).map(([currency, total]) => (
|
||||||
|
<dd key={`turnover-${currency}`} className="text-sm text-muted-foreground">
|
||||||
|
{formatCurrency(total, currency)}
|
||||||
|
</dd>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
26
lib/stats.ts
26
lib/stats.ts
@@ -16,6 +16,32 @@ export function calcTotalPerCurrency(transactions: Transaction[]): Record<string
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function calcNetTotalPerCurrency(transactions: Transaction[]): Record<string, number> {
|
||||||
|
return transactions.reduce(
|
||||||
|
(acc, transaction) => {
|
||||||
|
let amount = 0
|
||||||
|
let currency: string | undefined
|
||||||
|
if (
|
||||||
|
transaction.convertedTotal !== null &&
|
||||||
|
transaction.convertedTotal !== undefined &&
|
||||||
|
transaction.convertedCurrencyCode
|
||||||
|
) {
|
||||||
|
amount = transaction.convertedTotal
|
||||||
|
currency = transaction.convertedCurrencyCode.toUpperCase()
|
||||||
|
} else if (transaction.total !== null && transaction.total !== undefined && transaction.currencyCode) {
|
||||||
|
amount = transaction.total
|
||||||
|
currency = transaction.currencyCode.toUpperCase()
|
||||||
|
}
|
||||||
|
if (currency && amount !== 0) {
|
||||||
|
const sign = transaction.type === "expense" ? -1 : 1
|
||||||
|
acc[currency] = (acc[currency] || 0) + amount * sign
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
{} as Record<string, number>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const isTransactionIncomplete = (fields: Field[], transaction: Transaction): boolean => {
|
export const isTransactionIncomplete = (fields: Field[], transaction: Transaction): boolean => {
|
||||||
const incompleteFields = incompleteTransactionFields(fields, transaction)
|
const incompleteFields = incompleteTransactionFields(fields, transaction)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user