diff --git a/7project/frontend/src/pages/Dashboard.tsx b/7project/frontend/src/pages/Dashboard.tsx index d32a3f4..c81c876 100644 --- a/7project/frontend/src/pages/Dashboard.tsx +++ b/7project/frontend/src/pages/Dashboard.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState, useCallback } from 'react'; import { type Category, type Transaction, type BalancePoint, deleteTransaction, getCategories, getTransactions, createTransaction, updateTransaction, getBalanceSeries } from '../api'; import AccountPage from './AccountPage'; import AppearancePage from './AppearancePage'; @@ -168,7 +168,14 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { // Sidebar toggle for mobile const [sidebarOpen, setSidebarOpen] = useState(false); - + // Multi-select state for transactions and bulk category assignment + const [selectedTxIds, setSelectedTxIds] = useState([]); + const [bulkCategoryIds, setBulkCategoryIds] = useState([]); + const toggleSelectTx = useCallback((id: number) => { + setSelectedTxIds(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]); + }, []); + const clearSelection = useCallback(() => setSelectedTxIds([]), []); + const selectAllVisible = useCallback((ids: number[]) => setSelectedTxIds(ids), []); async function loadAll() { setLoading(true); @@ -241,7 +248,7 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { } } - useEffect(() => { loadAll(); }, [startDate, endDate]); + useEffect(() => { loadAll(); clearSelection(); }, [startDate, endDate]); const filtered = useMemo(() => { let arr = [...transactions]; @@ -267,6 +274,9 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { const pageEnd = pageStart + pageSize; const visible = sortedDesc.slice(pageStart, pageEnd); + // Reset selection when page or filters impacting visible set change + useEffect(() => { clearSelection(); }, [page, minAmount, maxAmount, filterCategoryId, searchText]); + function categoryNameById(id: number) { return categories.find(c => c.id === id)?.name || `#${id}`; } @@ -416,7 +426,55 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) {
Showing {visible.length} of {filtered.length} (page {Math.min(page + 1, Math.max(1, totalPages))}/{Math.max(1, totalPages)})
-
+
+ {selectedTxIds.length > 0 && ( + <> + Selected: {selectedTxIds.length} + + + + + )}
@@ -424,6 +482,22 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { + @@ -433,7 +507,15 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { {visible.map(t => ( - + + {/* Date cell */}
+ 0 && visible.every(v => selectedTxIds.includes(v.id))} + indeterminate={(visible.some(v => selectedTxIds.includes(v.id)) && !visible.every(v => selectedTxIds.includes(v.id))) as any} + onChange={(e) => { + if (e.currentTarget.checked) { + selectAllVisible(visible.map(v => v.id)); + } else { + // remove only currently visible from selection + setSelectedTxIds(prev => prev.filter(id => !visible.some(v => v.id === id))); + } + }} + /> + Date Amount Description
+ toggleSelectTx(t.id)} + /> + {editingTxId === t.id ? (