From 062b807732a4f015b04218ccb07d372f5e95c5d0 Mon Sep 17 00:00:00 2001 From: Kevin Riehl Date: Wed, 27 May 2026 15:44:33 -0700 Subject: [PATCH] Fix ImportStatementDialog overflow on narrow viewports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The review and confirm steps were rendering as wide editable tables inside a `sm:max-w-3xl` dialog (~768px). The inputs alone consumed ~750px before padding, so on smaller desktop windows the content was clipped left/right, and on mobile it was effectively unusable. Two-pronged fix: - Widen the dialog itself to `md:max-w-5xl xl:max-w-6xl` so the table has room on typical desktops. Mobile stays full-width minus margin. - Split each step into responsive layouts gated by Tailwind's `md:` breakpoint. Desktop keeps the editable table (now with fixed-width columns, an inline status badge instead of a separate Status column, and `overflow-auto` as a graceful fallback at intermediate widths). Below `md:`, rows render as stacked cards — checkbox + date + status on top, description full-width, amount + type wrapped, transfer destination dropping below when applicable. Same data, no horizontal scrolling on phones. The Confirm step's expand-to-preview table gets the same treatment: table on desktop, summary cards on mobile. Co-Authored-By: Claude Opus 4.7 (1M context) --- tehriehlbudget-backend/package.json | 2 +- tehriehlbudget-frontend/package.json | 2 +- .../src/components/ImportStatementDialog.tsx | 254 ++++++++++++++---- 3 files changed, 202 insertions(+), 56 deletions(-) diff --git a/tehriehlbudget-backend/package.json b/tehriehlbudget-backend/package.json index 270fce5..cabe28b 100644 --- a/tehriehlbudget-backend/package.json +++ b/tehriehlbudget-backend/package.json @@ -1,6 +1,6 @@ { "name": "tehriehlbudget-backend", - "version": "0.4.0", + "version": "0.4.1", "description": "", "author": "", "private": true, diff --git a/tehriehlbudget-frontend/package.json b/tehriehlbudget-frontend/package.json index 37ce7db..41063c8 100644 --- a/tehriehlbudget-frontend/package.json +++ b/tehriehlbudget-frontend/package.json @@ -1,7 +1,7 @@ { "name": "tehriehlbudget-frontend", "private": true, - "version": "0.4.0", + "version": "0.4.1", "type": "module", "scripts": { "dev": "vite", diff --git a/tehriehlbudget-frontend/src/components/ImportStatementDialog.tsx b/tehriehlbudget-frontend/src/components/ImportStatementDialog.tsx index 77db299..d735929 100644 --- a/tehriehlbudget-frontend/src/components/ImportStatementDialog.tsx +++ b/tehriehlbudget-frontend/src/components/ImportStatementDialog.tsx @@ -335,7 +335,7 @@ export function ImportStatementDialog({ open, onOpenChange, defaultAccountId, on return ( - + Import statement @@ -638,12 +638,12 @@ function ReviewStep({ return ( <>
-
+

Review each row before import. Edit fields inline if needed; uncheck anything you don't want to import.

-
+
@@ -667,16 +667,17 @@ function ReviewStep({ )}
)} -
+ + {/* Desktop / tablet: editable table with horizontal scroll fallback */} +
- Date + Date Description - Amount - Type - Status + Amount + Type @@ -685,7 +686,7 @@ function ReviewStep({ key={r.sourceIndex} className={r.status === 'duplicate' ? 'bg-red-50/50' : undefined} > - + onUpdateRow(r.sourceIndex, { included: e.target.checked })} /> - + onUpdateRow(r.sourceIndex, { date: e.target.value })} /> - - onUpdateRow(r.sourceIndex, { description: e.target.value })} - /> + +
+ + onUpdateRow(r.sourceIndex, { description: e.target.value }) + } + /> + {statusBadge(r.status)} +
{r.duplicateOf && (

Matches existing {formatDate(r.duplicateOf.date)} ·{' '} @@ -720,7 +726,7 @@ function ReviewStep({

)}
- + - + )} - {statusBadge(r.status)} ))}
+ + {/* Mobile: stacked cards. Tables don't fit on a phone screen. */} +
+ {rows.map((r) => ( +
+
+ onUpdateRow(r.sourceIndex, { included: e.target.checked })} + className="mt-2" + /> +
+
+ onUpdateRow(r.sourceIndex, { date: e.target.value })} + /> + {statusBadge(r.status)} +
+ onUpdateRow(r.sourceIndex, { description: e.target.value })} + /> +
+ + onUpdateRow(r.sourceIndex, { + amount: parseFloat(e.target.value) || 0, + }) + } + /> + +
+ {r.type === 'TRANSFER' && ( + + )} + {r.duplicateOf && ( +

+ Matches existing {formatDate(r.duplicateOf.date)} ·{' '} + {currency(r.duplicateOf.amount)} · {r.duplicateOf.description} +

+ )} + {r.transferCandidate && !r.destinationAccountId && ( +

+ Possible transfer with {r.transferCandidate.accountName}. Pick a destination + account above to mark as transfer. +

+ )} +
+
+
+ ))} +
+

{counts.selected} selected · {counts.duplicates} duplicates · {counts.needsReview} need review · {counts.possibleTransfers} possible transfers @@ -902,39 +1022,65 @@ function ConfirmStep({ {counts.selected === 1 ? '' : 's'} about to be imported {expanded && ( -

- - - - Date - Description - Amount - Type - Account - - - - {selectedRows.map((r) => ( - - {formatDate(r.date)} - {r.description} - {currency(r.amount)} - {r.type.charAt(0) + r.type.slice(1).toLowerCase()} - - {sourceAccount?.name ?? '—'} - {r.destinationAccountId && ( - <> - {' '} - {' '} - {accounts.find((a) => a.id === r.destinationAccountId)?.name ?? '—'} - - )} - + <> + {/* Desktop / tablet: read-only summary table */} +
+
+ + + Date + Description + Amount + Type + Account - ))} - -
-
+ + + {selectedRows.map((r) => ( + + {formatDate(r.date)} + {r.description} + {currency(r.amount)} + {r.type.charAt(0) + r.type.slice(1).toLowerCase()} + + {sourceAccount?.name ?? '—'} + {r.destinationAccountId && ( + <> + {' '} + {' '} + {accounts.find((a) => a.id === r.destinationAccountId)?.name ?? '—'} + + )} + + + ))} + + +
+ + {/* Mobile: stacked cards */} +
+ {selectedRows.map((r) => ( +
+
+ {r.description} + {currency(r.amount)} +
+

+ {formatDate(r.date)} · {r.type.charAt(0) + r.type.slice(1).toLowerCase()} ·{' '} + {sourceAccount?.name ?? '—'} + {r.destinationAccountId && ( + <> + {' '} + {' '} + {accounts.find((a) => a.id === r.destinationAccountId)?.name ?? '—'} + + )} +

+
+ ))} +
+ )} {error &&

{error}

} -- 2.52.0