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} />}
)}