feat: add transaction statistics to TransactionsPage; implement reconciled and categorized percentage calculations, enhance card layout, and update UI components for improved data presentation
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 2m11s

This commit is contained in:
Julien Froidefond
2025-12-23 11:27:06 +01:00
parent 9de7d1a467
commit 01c1f25de2
3 changed files with 78 additions and 6 deletions

View File

@@ -2,7 +2,15 @@
import { useCallback, useMemo } from "react";
import { PageLayout, PageHeader } from "@/components/layout";
import { RefreshCw, Receipt, Euro, ChevronDown, ChevronUp } from "lucide-react";
import {
RefreshCw,
Receipt,
Euro,
ChevronDown,
ChevronUp,
CheckCircle2,
Tag,
} from "lucide-react";
import {
TransactionFilters,
TransactionBulkActions,
@@ -105,6 +113,7 @@ export default function TransactionsPage() {
isLoading: isLoadingChart,
totalAmount: chartTotalAmount,
totalCount: chartTotalCount,
transactions: chartTransactions,
} = useTransactionsChartData({
selectedAccounts,
selectedCategories,
@@ -175,6 +184,23 @@ export default function TransactionsPage() {
const totalAmount = chartTotalAmount ?? 0;
const displayTotalCount = chartTotalCount ?? totalTransactions;
// Calculate percentages from chart transactions (all filtered transactions)
const reconciledPercent = useMemo(() => {
if (chartTransactions.length === 0) return "0.00";
const reconciledCount = chartTransactions.filter(
(t) => t.isReconciled
).length;
return ((reconciledCount / chartTransactions.length) * 100).toFixed(2);
}, [chartTransactions]);
const categorizedPercent = useMemo(() => {
if (chartTransactions.length === 0) return "0.00";
const categorizedCount = chartTransactions.filter(
(t) => t.categoryId !== null
).length;
return ((categorizedCount / chartTransactions.length) * 100).toFixed(2);
}, [chartTransactions]);
// Persist statistics collapsed state in localStorage
const [isStatsExpanded, setIsStatsExpanded] = useLocalStorage(
"transactions-stats-expanded",
@@ -271,7 +297,7 @@ export default function TransactionsPage() {
<CardContent className="pt-0">
{/* Summary cards */}
{!isLoadingTransactions && (
<div className="grid gap-4 grid-cols-1 sm:grid-cols-2 mb-6">
<div className="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 mb-6">
<Card className="card-hover">
<CardContent className="pt-6">
<div className="flex items-center justify-between">
@@ -283,7 +309,10 @@ export default function TransactionsPage() {
{displayTotalCount}
</p>
</div>
<Receipt className="w-8 h-8 text-muted-foreground" />
<Receipt
className="w-8 h-8"
style={{ color: "var(--gray)" }}
/>
</div>
</CardContent>
</Card>
@@ -304,7 +333,49 @@ export default function TransactionsPage() {
{formatCurrency(totalAmount)}
</p>
</div>
<Euro className="w-8 h-8 text-muted-foreground" />
<Euro
className={`w-8 h-8 ${
totalAmount >= 0
? "text-emerald-600"
: "text-red-600"
}`}
/>
</div>
</CardContent>
</Card>
<Card className="card-hover">
<CardContent className="pt-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">
Pointé
</p>
<p className="text-2xl font-bold mt-1 text-primary">
{reconciledPercent}%
</p>
</div>
<CheckCircle2 className="w-8 h-8 text-primary" />
</div>
</CardContent>
</Card>
<Card className="card-hover">
<CardContent className="pt-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">
Catégorisé
</p>
<p
className="text-2xl font-bold mt-1"
style={{ color: "var(--blue)" }}
>
{categorizedPercent}%
</p>
</div>
<Tag
className="w-8 h-8"
style={{ color: "var(--blue)" }}
/>
</div>
</CardContent>
</Card>