fix(ui): SimpleFIN transaction table fixed column sizing (batch 0.33.7.3)
- Table now uses table-fixed + colgroup for fixed column widths - Long transaction text can no longer push action buttons off-screen - Action buttons are compact icon-only with aria-label/title - Long matched bill names are truncated with truncate class - Bump v0.33.7.2 -> v0.33.7.3
This commit is contained in:
parent
32f1568515
commit
392de3264f
|
|
@ -1,5 +1,13 @@
|
||||||
# Bill Tracker — Changelog
|
# Bill Tracker — Changelog
|
||||||
|
|
||||||
|
## v0.33.7.3
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **SimpleFIN transaction table now uses fixed column sizing** — Long transaction text no longer pushes action buttons off-screen. Action buttons are compact icon-only with aria-label/title for accessibility. Long matched bill names are truncated.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v0.33.7.2
|
## v0.33.7.2
|
||||||
|
|
||||||
### 🚀 Features
|
### 🚀 Features
|
||||||
|
|
|
||||||
|
|
@ -521,7 +521,14 @@ export default function TransactionMatchingSection({ refreshKey, simplefinConn }
|
||||||
No transactions found for this filter.
|
No transactions found for this filter.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<table className="w-full text-sm">
|
<table className="w-full min-w-[860px] table-fixed text-sm">
|
||||||
|
<colgroup>
|
||||||
|
<col className="w-[92px]" />
|
||||||
|
<col />
|
||||||
|
<col className="w-[200px]" />
|
||||||
|
<col className="w-[120px]" />
|
||||||
|
<col className="w-[96px]" />
|
||||||
|
</colgroup>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border/50 bg-muted/30 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">
|
<tr className="border-b border-border/50 bg-muted/30 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">
|
||||||
<th className="px-4 py-2 text-left">Date</th>
|
<th className="px-4 py-2 text-left">Date</th>
|
||||||
|
|
@ -540,7 +547,7 @@ export default function TransactionMatchingSection({ refreshKey, simplefinConn }
|
||||||
<td className="px-4 py-3 text-xs text-muted-foreground whitespace-nowrap">
|
<td className="px-4 py-3 text-xs text-muted-foreground whitespace-nowrap">
|
||||||
{transactionDate(tx)}
|
{transactionDate(tx)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 min-w-[240px]">
|
<td className="max-w-0 px-4 py-3">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="truncate text-sm font-medium">{transactionTitle(tx)}</p>
|
<p className="truncate text-sm font-medium">{transactionTitle(tx)}</p>
|
||||||
<p className="mt-0.5 truncate text-xs text-muted-foreground">
|
<p className="mt-0.5 truncate text-xs text-muted-foreground">
|
||||||
|
|
@ -548,11 +555,11 @@ export default function TransactionMatchingSection({ refreshKey, simplefinConn }
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 min-w-[180px]">
|
<td className="px-4 py-3">
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<TransactionStatusBadge tx={tx} />
|
<TransactionStatusBadge tx={tx} />
|
||||||
{tx.matched_bill_name ? (
|
{tx.matched_bill_name ? (
|
||||||
<span className="text-xs text-foreground">{tx.matched_bill_name}</span>
|
<span className="truncate text-xs text-foreground">{tx.matched_bill_name}</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-xs text-muted-foreground">No bill linked</span>
|
<span className="text-xs text-muted-foreground">No bill linked</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -564,29 +571,64 @@ export default function TransactionMatchingSection({ refreshKey, simplefinConn }
|
||||||
)}>
|
)}>
|
||||||
{formatTransactionAmount(tx.amount, tx.currency)}
|
{formatTransactionAmount(tx.amount, tx.currency)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-3 py-3">
|
||||||
<div className="flex justify-end gap-1.5">
|
<div className="flex justify-end gap-1.5 whitespace-nowrap">
|
||||||
{status === 'ignored' ? (
|
{status === 'ignored' ? (
|
||||||
<Button size="sm" variant="outline" type="button" disabled={busy} onClick={() => runTransactionAction(tx, 'unignore')}>
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="outline"
|
||||||
|
type="button"
|
||||||
|
disabled={busy}
|
||||||
|
onClick={() => runTransactionAction(tx, 'unignore')}
|
||||||
|
className="h-8 w-8 shrink-0"
|
||||||
|
aria-label="Unignore transaction"
|
||||||
|
title="Unignore transaction"
|
||||||
|
>
|
||||||
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Eye className="h-3.5 w-3.5" />}
|
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Eye className="h-3.5 w-3.5" />}
|
||||||
<span className="ml-1.5 hidden xl:inline">Unignore</span>
|
<span className="sr-only">Unignore</span>
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{status === 'matched' ? (
|
{status === 'matched' ? (
|
||||||
<Button size="sm" variant="outline" type="button" disabled={busy} onClick={() => runTransactionAction(tx, 'unmatch')}>
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="outline"
|
||||||
|
type="button"
|
||||||
|
disabled={busy}
|
||||||
|
onClick={() => runTransactionAction(tx, 'unmatch')}
|
||||||
|
className="h-8 w-8 shrink-0"
|
||||||
|
aria-label="Unmatch transaction"
|
||||||
|
title="Unmatch transaction"
|
||||||
|
>
|
||||||
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Link2Off className="h-3.5 w-3.5" />}
|
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Link2Off className="h-3.5 w-3.5" />}
|
||||||
<span className="ml-1.5 hidden xl:inline">Unmatch</span>
|
<span className="sr-only">Unmatch</span>
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button size="sm" type="button" disabled={busy || billsLoading} onClick={() => openMatchDialog(tx)}>
|
<Button
|
||||||
|
size="icon"
|
||||||
|
type="button"
|
||||||
|
disabled={busy || billsLoading}
|
||||||
|
onClick={() => openMatchDialog(tx)}
|
||||||
|
className="h-8 w-8 shrink-0"
|
||||||
|
aria-label="Match transaction"
|
||||||
|
title="Match transaction"
|
||||||
|
>
|
||||||
<Link2 className="h-3.5 w-3.5" />
|
<Link2 className="h-3.5 w-3.5" />
|
||||||
<span className="ml-1.5 hidden xl:inline">Match</span>
|
<span className="sr-only">Match</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button size="sm" variant="ghost" type="button" disabled={busy} onClick={() => runTransactionAction(tx, 'ignore')}>
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="ghost"
|
||||||
|
type="button"
|
||||||
|
disabled={busy}
|
||||||
|
onClick={() => runTransactionAction(tx, 'ignore')}
|
||||||
|
className="h-8 w-8 shrink-0"
|
||||||
|
aria-label="Ignore transaction"
|
||||||
|
title="Ignore transaction"
|
||||||
|
>
|
||||||
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <EyeOff className="h-3.5 w-3.5" />}
|
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <EyeOff className="h-3.5 w-3.5" />}
|
||||||
<span className="ml-1.5 hidden xl:inline">Ignore</span>
|
<span className="sr-only">Ignore</span>
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,13 @@ export const APP_NAME = 'BillTracker';
|
||||||
export const RELEASE_NOTES = {
|
export const RELEASE_NOTES = {
|
||||||
version: APP_VERSION,
|
version: APP_VERSION,
|
||||||
date: '2026-05-29',
|
date: '2026-05-29',
|
||||||
|
version: APP_VERSION,
|
||||||
highlights: [
|
highlights: [
|
||||||
|
{
|
||||||
|
icon: '🔄',
|
||||||
|
title: 'SimpleFIN transaction table fix',
|
||||||
|
desc: 'Transaction table now uses fixed column sizing so long text can\'t push action buttons off-screen. Action buttons are compact icon-only with accessible labels.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: '🔄',
|
icon: '🔄',
|
||||||
title: 'SimpleFIN payment backfill',
|
title: 'SimpleFIN payment backfill',
|
||||||
|
|
@ -33,11 +39,6 @@ export const RELEASE_NOTES = {
|
||||||
title: 'Privacy and release notes',
|
title: 'Privacy and release notes',
|
||||||
desc: 'A public Privacy page is available from About, release notes can render images, and this update card now resets from the backend whenever the app version changes.',
|
desc: 'A public Privacy page is available from About, release notes can render images, and this update card now resets from the backend whenever the app version changes.',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
icon: '❄️',
|
|
||||||
title: 'Ramsey Snowball mode',
|
|
||||||
desc: 'Debt Snowball now defaults to smallest-balance-first, keeps custom drag ordering behind a toggle, skips mortgages by default, and adds an inline Ramsey readiness checklist.',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
image: {
|
image: {
|
||||||
src: '/img/doingmypart.jpg',
|
src: '/img/doingmypart.jpg',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "bill-tracker",
|
"name": "bill-tracker",
|
||||||
"version": "0.33.7.2",
|
"version": "0.33.7.3",
|
||||||
"description": "Monthly bill tracking system",
|
"description": "Monthly bill tracking system",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue