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
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 2m11s
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -41,13 +41,13 @@ export function OverviewCards({ data }: OverviewCardsProps) {
|
||||
const reconciled = data.transactions.filter((t) => t.isReconciled).length;
|
||||
const total = data.transactions.length;
|
||||
const reconciledPercent =
|
||||
total > 0 ? Math.round((reconciled / total) * 100) : 0;
|
||||
total > 0 ? ((reconciled / total) * 100).toFixed(2) : "0.00";
|
||||
|
||||
const categorized = data.transactions.filter(
|
||||
(t) => t.categoryId !== null
|
||||
).length;
|
||||
const categorizedPercent =
|
||||
total > 0 ? Math.round((categorized / total) * 100) : 0;
|
||||
total > 0 ? ((categorized / total) * 100).toFixed(2) : "0.00";
|
||||
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat("fr-FR", {
|
||||
|
||||
@@ -220,6 +220,7 @@ export function useTransactionsChartData({
|
||||
isLoading,
|
||||
totalAmount,
|
||||
totalCount,
|
||||
transactions: transactionsData?.transactions || [],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user