diff --git a/client/components/SearchFilterPanel.jsx b/client/components/SearchFilterPanel.jsx index ab2f6d2..f405ed4 100644 --- a/client/components/SearchFilterPanel.jsx +++ b/client/components/SearchFilterPanel.jsx @@ -13,6 +13,7 @@ export default function SearchFilterPanel({ children, className, variant = 'default', + headerActions, }) { const embedded = variant === 'embedded'; const ToggleIcon = collapsed ? ChevronUp : ChevronDown; @@ -49,6 +50,7 @@ export default function SearchFilterPanel({ Clear )} + {headerActions} {!collapsed && ( diff --git a/client/components/tracker/TrackerBucket.jsx b/client/components/tracker/TrackerBucket.jsx index a73f698..0d62bf2 100644 --- a/client/components/tracker/TrackerBucket.jsx +++ b/client/components/tracker/TrackerBucket.jsx @@ -1,17 +1,12 @@ import { useState } from 'react'; import { LayoutGroup } from 'framer-motion'; -import { ArrowDown, ArrowUp, Settings2 } from 'lucide-react'; +import { ArrowDown, ArrowUp } from 'lucide-react'; import { cn, fmt } from '@/lib/utils'; import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell, } from '@/components/ui/table'; -import { Button } from '@/components/ui/button'; -import { - DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, - DropdownMenuSeparator, DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; import { TRACKER_SORT_ASC, TRACKER_SORT_DEFAULT, moveInArray } from '@/lib/trackerUtils'; -import { TRACKER_TABLE_COLUMNS, DEFAULT_TRACKER_TABLE_COLUMNS } from '@/lib/trackerTableColumns'; +import { DEFAULT_TRACKER_TABLE_COLUMNS } from '@/lib/trackerTableColumns'; import { TrackerRow as Row } from '@/components/tracker/TrackerRow'; import { MobileTrackerRow } from '@/components/tracker/MobileTrackerRow'; @@ -61,8 +56,6 @@ export function TrackerBucket({ onSort, driftedIds = new Set(), visibleColumns = DEFAULT_TRACKER_TABLE_COLUMNS, - onColumnToggle, - columnsSaving, }) { const [draggingId, setDraggingId] = useState(null); const [dropTargetId, setDropTargetId] = useState(null); @@ -185,37 +178,6 @@ export function TrackerBucket({ {!reorderEnabled && rows.length > 1 && ( Clear filters to reorder )} - - - - - - Visible columns - - - Bill - - {TRACKER_TABLE_COLUMNS.map(column => ( - event.preventDefault()} - onCheckedChange={checked => onColumnToggle?.(column.key, checked)} - > - {column.label} - - ))} - - diff --git a/client/pages/TrackerPage.jsx b/client/pages/TrackerPage.jsx index 6416299..2cf688e 100644 --- a/client/pages/TrackerPage.jsx +++ b/client/pages/TrackerPage.jsx @@ -1,6 +1,6 @@ import { useState, useEffect, useMemo, useCallback } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { ChevronLeft, ChevronRight, AlertCircle, CheckCircle2, Plus, Search, RefreshCw, Landmark, ArrowUpToLine, ArrowUp, ArrowDown, BellOff, EyeOff } from 'lucide-react'; +import { ChevronLeft, ChevronRight, AlertCircle, CheckCircle2, Plus, Search, RefreshCw, Landmark, ArrowUpToLine, ArrowUp, ArrowDown, BellOff, Eye, EyeOff, Settings2 } from 'lucide-react'; import { toast } from 'sonner'; import { api } from '@/api.js'; import { useTracker, useDriftReport } from '@/hooks/useQueries'; @@ -19,6 +19,10 @@ import { import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, } from '@/components/ui/dialog'; +import { + DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, + DropdownMenuSeparator, DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; import StartingAmountsEditDialog from '@/components/tracker/StartingAmountsEditDialog'; import OverdueCommandCenter from '@/components/tracker/OverdueCommandCenter'; @@ -30,7 +34,7 @@ import { TRACKER_SORT_DEFAULT_DIRS, TRACKER_SORT_LABELS, normalizeTrackerSortKey, normalizeTrackerSortDir, sortTrackerRows, } from '@/lib/trackerUtils'; -import { parseTrackerTableColumns, trackerTableColumnsToSetting } from '@/lib/trackerTableColumns'; +import { TRACKER_TABLE_COLUMNS, parseTrackerTableColumns, trackerTableColumnsToSetting } from '@/lib/trackerTableColumns'; import { FilterChip } from '@/components/tracker/FilterChip'; import { SummaryCard, TrendCard } from '@/components/tracker/SummaryCards'; import { PaymentLedgerDialog } from '@/components/tracker/PaymentLedgerDialog'; @@ -118,6 +122,44 @@ function BankProjectionBanner({ bankTracking, onSnooze, onIgnore, busy }) { ); } +function TrackerColumnMenu({ visibleColumns, onColumnToggle, saving }) { + const visibleSet = new Set(visibleColumns); + + return ( + + + + + + Table columns + + + Bill + + {TRACKER_TABLE_COLUMNS.map(column => ( + event.preventDefault()} + onCheckedChange={checked => onColumnToggle?.(column.key, checked)} + > + {column.label} + + ))} + + + ); +} + // ── Main page ────────────────────────────────────────────────────────────── function LateAttributionDialog({ attr, remaining, busy, onAccept, onDismiss }) { if (!attr) return null; @@ -391,6 +433,18 @@ export default function TrackerPage() { tracker_table_columns: trackerTableColumnsToSetting(nextColumns), }); } + + function hideSearchSortPanel() { + saveTrackerSettings({ + tracker_show_search_sort: 'false', + }, 'Search & sort hidden.'); + } + + function showSearchSortPanel() { + saveTrackerSettings({ + tracker_show_search_sort: 'true', + }); + } const toggleFilter = (key) => { const paramMap = { autopay: 'ap', firstBucket: 'b1', fifteenthBucket: 'b2', unpaid: 'un', overdue: 'ov', debt: 'de' }; updateParams({ [paramMap[key]]: !filters[key] }); @@ -438,6 +492,8 @@ export default function TrackerPage() { || filters.debt || hasSort ); + const searchResultLabel = `${filteredRows.length} of ${rows.length} shown`; + const searchSortLabel = hasSort ? `sorted by ${TRACKER_SORT_LABELS[sortKey]} ${sortDir === TRACKER_SORT_ASC ? 'ascending' : 'descending'}` : null; const resetFilters = () => { updateParams({ q: null, fc: null, cy: null, ap: null, b1: null, b2: null, un: null, ov: null, de: null, sort: null, dir: null }); }; @@ -617,9 +673,29 @@ export default function TrackerPage() { collapsed={searchPanelCollapsed} onCollapsedChange={setSearchPanelCollapsed} hasFilters={hasFilters} - resultLabel={`${filteredRows.length} of ${rows.length} shown`} - sortLabel={hasSort ? `sorted by ${TRACKER_SORT_LABELS[sortKey]} ${sortDir === TRACKER_SORT_ASC ? 'ascending' : 'descending'}` : null} + resultLabel={searchResultLabel} + sortLabel={searchSortLabel} onClear={resetFilters} + headerActions={( +
+ + +
+ )} >
)} + {!showSearchSort && ( + + )} {/* ── Summary cards (backend already excludes skipped from totals) ── */} {showSummaryCards && loading ? ( @@ -865,8 +965,8 @@ export default function TrackerPage() { )} {!isError && (first.length > 0 || second.length > 0) && (
- {first.length > 0 && handleReorderBucket('1st', next)} driftedIds={driftedIds} visibleColumns={visibleTableColumns} onColumnToggle={handleTableColumnToggle} columnsSaving={savingTrackerSetting} />} - {second.length > 0 && handleReorderBucket('15th', next)} driftedIds={driftedIds} visibleColumns={visibleTableColumns} onColumnToggle={handleTableColumnToggle} columnsSaving={savingTrackerSetting} />} + {first.length > 0 && handleReorderBucket('1st', next)} driftedIds={driftedIds} visibleColumns={visibleTableColumns} />} + {second.length > 0 && handleReorderBucket('15th', next)} driftedIds={driftedIds} visibleColumns={visibleTableColumns} />}
)}