From a9b2aba55abaf777f9cad47385dd6b668bbe7ca9 Mon Sep 17 00:00:00 2001 From: ribardej Date: Wed, 5 Nov 2025 20:24:33 +0100 Subject: [PATCH] feat(frontend): implemented mobile friendly UI responsiveness --- 7project/frontend/src/appearance.ts | 6 +- 7project/frontend/src/pages/BalanceChart.tsx | 76 ++++++---- .../frontend/src/pages/CategoryPieChart.tsx | 10 +- 7project/frontend/src/pages/Dashboard.tsx | 30 ++-- .../frontend/src/pages/LoginRegisterPage.tsx | 2 +- 7project/frontend/src/ui.css | 143 +++++++++++++++++- 6 files changed, 219 insertions(+), 48 deletions(-) diff --git a/7project/frontend/src/appearance.ts b/7project/frontend/src/appearance.ts index 5f3b1ab..bb44d2d 100644 --- a/7project/frontend/src/appearance.ts +++ b/7project/frontend/src/appearance.ts @@ -13,9 +13,9 @@ export function applyTheme(theme: Theme) { export function applyFontSize(size: FontSize) { const root = document.documentElement; const map: Record = { - small: '14px', - medium: '18px', - large: '22px', + small: '12px', + medium: '15px', + large: '21px', }; root.style.fontSize = map[size]; } diff --git a/7project/frontend/src/pages/BalanceChart.tsx b/7project/frontend/src/pages/BalanceChart.tsx index b7c1d52..82d3aab 100644 --- a/7project/frontend/src/pages/BalanceChart.tsx +++ b/7project/frontend/src/pages/BalanceChart.tsx @@ -1,5 +1,6 @@ // src/BalanceChart.tsx -import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +import { useEffect, useRef, useState } from 'react'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts'; import { type BalancePoint } from '../api'; function formatAmount(n: number) { @@ -10,37 +11,56 @@ function formatDate(dateStr: string) { return new Date(dateStr).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }); } -export default function BalanceChart({ data }: { data: BalancePoint[] }) { +type Props = { data: BalancePoint[]; pxPerPoint?: number }; + +export default function BalanceChart({ data, pxPerPoint = 40 }: Props) { + const wrapRef = useRef(null); + const [containerWidth, setContainerWidth] = useState(0); + + useEffect(() => { + function measure() { + if (!wrapRef.current) return; + setContainerWidth(wrapRef.current.clientWidth); + } + measure(); + const obs = new ResizeObserver(measure); + if (wrapRef.current) obs.observe(wrapRef.current); + return () => obs.disconnect(); + }, []); + if (data.length === 0) { return
No data to display
; } + const desiredWidth = Math.max(containerWidth, Math.max(600, data.length * pxPerPoint)); + return ( - - - - - formatAmount(value as number)} - // Adjusted 'offset' for the Y-axis label. - // A negative offset moves it further away from the axis. - label={{ value: 'Balance', angle: -90, position: 'insideLeft', offset: -30 }} // <-- Change this line - /> - [formatAmount(value as number), 'Balance']} - /> - - - - +
+
+ + + + formatAmount(value as number)} + label={{ value: 'Balance', angle: -90, position: 'insideLeft', offset: -30 }} + /> + [formatAmount(value as number), 'Balance']} + /> + + + +
+
); } \ No newline at end of file diff --git a/7project/frontend/src/pages/CategoryPieChart.tsx b/7project/frontend/src/pages/CategoryPieChart.tsx index 96fbdf0..dcda08d 100644 --- a/7project/frontend/src/pages/CategoryPieChart.tsx +++ b/7project/frontend/src/pages/CategoryPieChart.tsx @@ -92,9 +92,13 @@ export default function CategoryPieCharts({ transactions, categories }: { transa return ( -
- - +
+
+ +
+
+ +
); } \ No newline at end of file diff --git a/7project/frontend/src/pages/Dashboard.tsx b/7project/frontend/src/pages/Dashboard.tsx index d90c33c..2d0e20b 100644 --- a/7project/frontend/src/pages/Dashboard.tsx +++ b/7project/frontend/src/pages/Dashboard.tsx @@ -168,6 +168,9 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { const [editingDescription, setEditingDescription] = useState(''); const [editingDate, setEditingDate] = useState(''); // YYYY-MM-DD + // Sidebar toggle for mobile + const [sidebarOpen, setSidebarOpen] = useState(false) + async function loadAll() { @@ -312,11 +315,11 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { } return ( -
+
+

{current === 'home' ? 'Dashboard' : current === 'manual' ? 'Manual management' : current === 'account' ? 'Account' : 'Appearance'}

Signed in @@ -409,7 +418,7 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) {
- +
@@ -423,7 +432,7 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { {visible.map(t => ( {/* Date cell */} - {/* Amount cell */} - {/* Description cell */} - {/* Categories cell */} - {/* Actions cell */} -
Date
+ {editingTxId === t.id ? ( void }) { + {editingTxId === t.id ? ( void }) { + {editingTxId === t.id ? ( void }) { + {editingTxId === t.id ? (
+ {editingTxId === t.id ? ( -
+
) : ( -
+
@@ -539,6 +548,7 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { onClose={() => setMockModalOpen(false)} onGenerate={handleGenerateMockTransactions} /> + {sidebarOpen &&
setSidebarOpen(false)} />}
); } diff --git a/7project/frontend/src/pages/LoginRegisterPage.tsx b/7project/frontend/src/pages/LoginRegisterPage.tsx index ea3b1e1..d585c88 100644 --- a/7project/frontend/src/pages/LoginRegisterPage.tsx +++ b/7project/frontend/src/pages/LoginRegisterPage.tsx @@ -80,7 +80,7 @@ export default function LoginRegisterPage({ onLoggedIn }: { onLoggedIn: () => vo setPassword(e.target.value)} />
{mode === 'register' && ( -
+
setFirstName(e.target.value)} /> diff --git a/7project/frontend/src/ui.css b/7project/frontend/src/ui.css index 9beeff3..bd69a3b 100644 --- a/7project/frontend/src/ui.css +++ b/7project/frontend/src/ui.css @@ -48,26 +48,49 @@ body[data-theme="dark"] { .card h3 { margin: 0 0 12px; } /* Forms */ -.input, select, textarea { +/* Common field styles (no custom arrow here) */ +.input, textarea { width: 100%; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--border); background-color: var(--panel); color: var(--muted); +} - /* Add these properties specifically for the select element */ +/* Select-only: show custom dropdown arrow */ +select.input { -webkit-appearance: none; -moz-appearance: none; appearance: none; - padding-right: 32px; /* Add space for the custom arrow */ + padding-right: 32px; /* room for the arrow */ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); background-position: right 0.5rem center; background-repeat: no-repeat; background-size: 1.5em 1.5em; cursor: pointer; } + +.pie-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; +} + +@media (max-width: 900px) { + .pie-grid { + grid-template-columns: 1fr; + } +} + +/* Make charts scale nicely within the cards */ +.pie-card canvas, .pie-card svg { + max-width: 100%; + height: auto; + display: block; +} + .input:focus, select:focus, textarea:focus { outline: 2px solid var(--primary); outline-offset: 2px; @@ -151,3 +174,117 @@ body.auth-page #root { justify-content: space-between; align-items: center; } + + +/* Responsive enhancements */ + +/* Off-canvas sidebar + hamburger for mobile */ +@media (max-width: 900px) { + .app-layout { + grid-template-columns: 1fr; + min-height: 100dvh; + position: relative; + } + .sidebar { + position: fixed; + inset: 0 auto 0 0; + width: 80vw; + max-width: 320px; + transform: translateX(-100%); + transition: transform 200ms ease; + z-index: 1000; + overflow-y: auto; + } + .app-layout.sidebar-open .sidebar { + transform: translateX(0); + } + .hamburger { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + margin-right: 8px; + } + .topbar { position: sticky; top: 0; z-index: 500; } +} + +@media (min-width: 901px) { + .hamburger { display: none; } +} + +/* Backdrop when sidebar is open */ +.backdrop { + position: fixed; + inset: 0; + background: rgba(0,0,0,0.45); + z-index: 900; +} + +/* Responsive table: convert to card list on small screens */ +.table.responsive { width: 100%; } +@media (max-width: 700px) { + .table.responsive thead { display: none; } + .table.responsive tbody tr { + display: block; + border: 1px solid var(--border, #2a2f45); + border-radius: 8px; + margin-bottom: 12px; + overflow: hidden; + background: var(--panel); + } + .table.responsive tbody td { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 10px 12px; + border-bottom: 1px solid var(--border); + text-align: left !important; /* override any right align */ + } + .table.responsive tbody td:last-child { border-bottom: 0; } + .table.responsive tbody td::before { + content: attr(data-label); + font-weight: 600; + color: var(--muted); + } + .table.responsive .actions { width: 100%; justify-content: flex-end; } + .table.responsive .amount { font-weight: 600; } +} + +/* Filters and controls wrapping */ +@media (max-width: 900px) { + .form-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} +@media (max-width: 700px) { + .form-row { grid-template-columns: 1fr; } +} + +.table-controls { gap: 12px; } +@media (max-width: 700px) { + .table-controls { flex-direction: column; align-items: stretch; } + .table-controls .actions { width: 100%; } + .table-controls .actions .btn { flex: 1 0 auto; } +} + +/* Touch-friendly sizes */ +.btn, .input, select.input { min-height: 40px; } +.btn.small { min-height: 36px; } + +/* Connection rows on mobile */ +@media (max-width: 700px) { + .connection-row { flex-direction: column; align-items: stretch; gap: 8px; } + .connection-row .btn { width: 100%; } +} + +/* Charts should scale to container */ +.card canvas, .card svg { max-width: 100%; height: auto; display: block; } + + +/* Horizontal scroll container for wide charts */ +.chart-scroll { + overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; /* momentum scroll on iOS */ +} +.chart-inner { min-width: 900px; }