diff --git a/src/components/budget/BudgetTable.tsx b/src/components/budget/BudgetTable.tsx index d68acf0..c777bcd 100644 --- a/src/components/budget/BudgetTable.tsx +++ b/src/components/budget/BudgetTable.tsx @@ -337,11 +337,11 @@ export default function BudgetTable({ rows, onUpdatePlanned, onSplitEvenly }: Bu {subtotalsOnTop ? t("reports.subtotalsOnTop") : t("reports.subtotalsOnBottom")} -
+
- - - + +
+
{t("budget.category")} diff --git a/src/components/shared/TransactionDetailModal.tsx b/src/components/shared/TransactionDetailModal.tsx index a43dfa4..f3f069d 100644 --- a/src/components/shared/TransactionDetailModal.tsx +++ b/src/components/shared/TransactionDetailModal.tsx @@ -1,7 +1,7 @@ -import { useEffect, useState, useCallback } from "react"; +import { useEffect, useState, useCallback, useMemo } from "react"; import { createPortal } from "react-dom"; import { useTranslation } from "react-i18next"; -import { X, Loader2 } from "lucide-react"; +import { X, Loader2, ArrowUp, ArrowDown, Eye, EyeOff } from "lucide-react"; import { getTransactionsByCategory } from "../../services/dashboardService"; import type { TransactionRow } from "../../shared/types"; @@ -10,6 +10,9 @@ const cadFormatter = new Intl.NumberFormat("en-CA", { currency: "CAD", }); +type SortColumn = "date" | "description" | "amount"; +type SortDirection = "asc" | "desc"; + interface TransactionDetailModalProps { categoryId: number | null; categoryName: string; @@ -31,6 +34,9 @@ export default function TransactionDetailModal({ const [rows, setRows] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); + const [sortColumn, setSortColumn] = useState("date"); + const [sortDirection, setSortDirection] = useState("desc"); + const [showAmounts, setShowAmounts] = useState(true); const fetchData = useCallback(async () => { setIsLoading(true); @@ -57,8 +63,42 @@ export default function TransactionDetailModal({ return () => document.removeEventListener("keydown", handleEscape); }, [onClose]); + const handleSort = (column: SortColumn) => { + if (sortColumn === column) { + setSortDirection((d) => (d === "asc" ? "desc" : "asc")); + } else { + setSortColumn(column); + setSortDirection(column === "description" ? "asc" : "desc"); + } + }; + + const sortedRows = useMemo(() => { + const sorted = [...rows]; + const dir = sortDirection === "asc" ? 1 : -1; + sorted.sort((a, b) => { + switch (sortColumn) { + case "date": + return (a.date < b.date ? -1 : a.date > b.date ? 1 : 0) * dir; + case "description": + return a.description.localeCompare(b.description) * dir; + case "amount": + return (a.amount - b.amount) * dir; + default: + return 0; + } + }); + return sorted; + }, [rows, sortColumn, sortDirection]); + const total = rows.reduce((sum, r) => sum + r.amount, 0); + const SortIcon = ({ column }: { column: SortColumn }) => { + if (sortColumn !== column) return null; + return sortDirection === "asc" ? : ; + }; + + const thClass = "px-6 py-2 font-medium cursor-pointer select-none hover:text-[var(--foreground)] transition-colors"; + return createPortal(
- +
+ + +
{/* Body */} @@ -107,34 +156,64 @@ export default function TransactionDetailModal({ - - - + + + {showAmounts && ( + + )} - {rows.map((row) => ( + {sortedRows.map((row) => ( - + {showAmounts && ( + + )} ))} - - - - - - + {showAmounts && ( + + + + + + + )}
{t("transactions.date")}{t("transactions.description")}{t("transactions.amount")} handleSort("date")} + > + + {t("transactions.date")} + + + handleSort("description")} + > + + {t("transactions.description")} + + + handleSort("amount")} + > + + {t("transactions.amount")} + + +
{row.date} {row.description}= 0 ? "text-[var(--positive)]" : "text-[var(--negative)]" - }`}> - {cadFormatter.format(row.amount)} - = 0 ? "text-[var(--positive)]" : "text-[var(--negative)]" + }`}> + {cadFormatter.format(row.amount)} +
{t("charts.total")}= 0 ? "text-[var(--positive)]" : "text-[var(--negative)]" - }`}> - {cadFormatter.format(total)} -
{t("charts.total")}= 0 ? "text-[var(--positive)]" : "text-[var(--negative)]" + }`}> + {cadFormatter.format(total)} +
)} diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index ee6e3c0..c9ca545 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -349,6 +349,10 @@ "budgetVsActual": "Budget vs Actual", "subtotalsOnTop": "Subtotals on top", "subtotalsOnBottom": "Subtotals on bottom", + "detail": { + "showAmounts": "Show amounts", + "hideAmounts": "Hide amounts" + }, "bva": { "monthly": "Monthly", "ytd": "Year-to-Date", diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 92ffe86..6176036 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -349,6 +349,10 @@ "budgetVsActual": "Budget vs R\u00e9el", "subtotalsOnTop": "Sous-totaux en haut", "subtotalsOnBottom": "Sous-totaux en bas", + "detail": { + "showAmounts": "Afficher les montants", + "hideAmounts": "Masquer les montants" + }, "bva": { "monthly": "Mensuel", "ytd": "Cumul annuel",