);
}
diff --git a/client/lib/version.js b/client/lib/version.js
index 44087cd..3dd033b 100644
--- a/client/lib/version.js
+++ b/client/lib/version.js
@@ -6,32 +6,32 @@ export const APP_NAME = 'BillTracker';
export const RELEASE_NOTES = {
version: APP_VERSION,
- date: '2026-05-14',
+ date: '2026-05-15',
highlights: [
{
- icon: '❄️',
- title: 'Debt Snowball',
- desc: 'New Snowball page built around Dave Ramsey\'s method: drag-and-drop ordering, attack-target highlight, auto-arrange by balance, and per-bill payoff date that updates live as you type your extra monthly budget.',
+ icon: '📋',
+ title: 'Bills page redesigned',
+ desc: 'The old table is gone. Bills now show as clean cards with icon actions, inline debt details (APR colour-coded, current balance), and a Columns button to choose exactly which fields are displayed — remembered across sessions.',
},
{
- icon: '📉',
- title: 'Payment → Balance sync',
- desc: 'Recording a payment on any debt bill now automatically reduces its current balance (payment minus one month of accrued interest = principal paid). Un-marking a payment reverses the change exactly.',
+ icon: '📈',
+ title: 'Snowball projection is now live',
+ desc: 'The payoff sidebar updates instantly as you type your extra monthly budget — no save required. The projection now includes a minimum-only baseline so you can see exactly how many months and dollars the snowball saves you.',
},
{
- icon: '💳',
- title: 'Debt Details on Bills',
- desc: 'Edit Bill now has a collapsible Debt / Credit Details section: current balance (inline-editable on the Snowball page), minimum payment, and APR. Bills in Credit Cards, Loans, or Mortgage categories are auto-detected.',
+ icon: '🔑',
+ title: 'Login history',
+ desc: 'Your last 3 sign-ins are recorded with timestamp, IP address, and browser. Click the Last Login field on your Profile page to see the full history.',
},
{
- icon: '📊',
- title: 'Avalanche comparison',
- desc: 'The Snowball page sidebar shows your full payoff projection alongside an Avalanche method comparison — see how much interest you\'d save by attacking highest-rate debts first.',
+ icon: '📥',
+ title: 'Import by bill',
+ desc: 'The XLSX import page has a new Bills tab. Select any existing bill and import its entire history from the spreadsheet in one click — no row-by-row review needed.',
},
{
- icon: '🔔',
- title: 'Update notifications',
- desc: 'The app now tracks which version you last saw. On your first login after an update you\'ll see this "What\'s new" panel. Admins can also check for newer releases from the Forgejo repo on the Status page.',
+ icon: '📐',
+ title: 'APR calculation engine',
+ desc: 'New backend math service: monthly interest, months to payoff, total interest, and full amortization schedules. Available via GET /api/bills/:id/amortization.',
},
],
};
diff --git a/client/pages/BillsPage.jsx b/client/pages/BillsPage.jsx
index 362a179..686935f 100644
--- a/client/pages/BillsPage.jsx
+++ b/client/pages/BillsPage.jsx
@@ -1,10 +1,10 @@
-import React, { useEffect, useState, useCallback } from 'react';
-import { Plus, ChevronRight, Trash2 } from 'lucide-react';
+import React, { useEffect, useState, useCallback, useRef } from 'react';
+import { Plus, ChevronRight, SlidersHorizontal } from 'lucide-react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
-import { Skeleton } from '@/components/ui/Skeleton';
+
import {
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
} from '@/components/ui/dialog';
@@ -92,6 +92,115 @@ function validateRange(range) {
return null;
}
+// ── Display preferences ───────────────────────────────────────────────────────
+
+const PREFS_KEY = 'bills-display-prefs-v1';
+
+const PREFS_DEFAULTS = {
+ showCategory: true,
+ showDueDay: true,
+ showAmount: true,
+ showCycle: true,
+ showApr: true,
+ showBalance: true,
+ showMinPayment: true,
+ showAutopay: true,
+ show2fa: true,
+};
+
+const PREFS_LABELS = [
+ ['showCategory', 'Category'],
+ ['showDueDay', 'Due day'],
+ ['showAmount', 'Amount'],
+ ['showCycle', 'Billing cycle'],
+ ['showApr', 'APR'],
+ ['showBalance', 'Balance'],
+ ['showMinPayment', 'Min payment'],
+ ['showAutopay', 'Autopay badge'],
+ ['show2fa', '2FA badge'],
+];
+
+function useDisplayPrefs() {
+ const [prefs, setPrefs] = useState(() => {
+ try {
+ const raw = localStorage.getItem(PREFS_KEY);
+ return raw ? { ...PREFS_DEFAULTS, ...JSON.parse(raw) } : PREFS_DEFAULTS;
+ } catch {
+ return PREFS_DEFAULTS;
+ }
+ });
+
+ const toggle = (key) => {
+ setPrefs(prev => {
+ const next = { ...prev, [key]: !prev[key] };
+ try { localStorage.setItem(PREFS_KEY, JSON.stringify(next)); } catch {}
+ return next;
+ });
+ };
+
+ return { prefs, toggle };
+}
+
+function DisplayPrefsPanel({ prefs, onToggle }) {
+ const [open, setOpen] = useState(false);
+ const ref = useRef(null);
+
+ useEffect(() => {
+ if (!open) return;
+ const handler = (e) => {
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
+ };
+ document.addEventListener('mousedown', handler);
+ return () => document.removeEventListener('mousedown', handler);
+ }, [open]);
+
+ return (
+
- {previewRows.length} preview row{previewRows.length === 1 ? '' : 's'}
+
+ {viewMode === 'rows'
+ ? 'Select rows, apply bulk decisions, then import.'
+ : 'Click a bill to queue its entire history from this file.'}
+