From 8543c727307127fb220093118d9dd208d1df55f4 Mon Sep 17 00:00:00 2001 From: ribardej Date: Wed, 5 Nov 2025 15:49:31 +0100 Subject: [PATCH 1/4] fix(frontend): fixed the layout for chrome based browsers --- 7project/frontend/src/index.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/7project/frontend/src/index.css b/7project/frontend/src/index.css index 08a3ac9..fb92a9f 100644 --- a/7project/frontend/src/index.css +++ b/7project/frontend/src/index.css @@ -24,8 +24,6 @@ a:hover { body { margin: 0; - display: flex; - place-items: center; min-width: 320px; min-height: 100vh; } From 36b1fe887b4b1f14105bf599fa2b4668d20a4abe Mon Sep 17 00:00:00 2001 From: ribardej Date: Wed, 5 Nov 2025 18:00:24 +0100 Subject: [PATCH 2/4] feat(frontend): Added options for modifying and deleting transactions in the UI --- 7project/frontend/src/api.ts | 11 ++ 7project/frontend/src/pages/Dashboard.tsx | 134 ++++++++++++++++++---- 2 files changed, 124 insertions(+), 21 deletions(-) diff --git a/7project/frontend/src/api.ts b/7project/frontend/src/api.ts index 018d900..fb04336 100644 --- a/7project/frontend/src/api.ts +++ b/7project/frontend/src/api.ts @@ -19,6 +19,17 @@ export type Transaction = { date?: string | null; // ISO date (YYYY-MM-DD) }; +export async function deleteTransaction(id: number): Promise { + const res = await fetch(`${getBaseUrl()}/transactions/${id}/delete`, { + method: 'DELETE', + headers: getHeaders('none'), + }); + if (!res.ok) { + const text = await res.text(); + throw new Error(text || 'Failed to delete transaction'); + } +} + function getBaseUrl() { const base = BACKEND_URL?.replace(/\/$/, '') || ''; return base || ''; diff --git a/7project/frontend/src/pages/Dashboard.tsx b/7project/frontend/src/pages/Dashboard.tsx index 2cb9850..d90c33c 100644 --- a/7project/frontend/src/pages/Dashboard.tsx +++ b/7project/frontend/src/pages/Dashboard.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from 'react'; -import { type Category, type Transaction, type BalancePoint, getCategories, getTransactions, createTransaction, updateTransaction, getBalanceSeries } from '../api'; +import { type Category, type Transaction, type BalancePoint, deleteTransaction, getCategories, getTransactions, createTransaction, updateTransaction, getBalanceSeries } from '../api'; import AccountPage from './AccountPage'; import AppearancePage from './AppearancePage'; import BalanceChart from './BalanceChart'; @@ -161,9 +161,14 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { // Manual forms moved to ManualManagement page - // Inline edit state for transaction categories + // Inline edit state for transaction editing const [editingTxId, setEditingTxId] = useState(null); const [editingCategoryIds, setEditingCategoryIds] = useState([]); + const [editingAmount, setEditingAmount] = useState(''); + const [editingDescription, setEditingDescription] = useState(''); + const [editingDate, setEditingDate] = useState(''); // YYYY-MM-DD + + async function loadAll() { setLoading(true); @@ -259,22 +264,50 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { function categoryNameById(id: number) { return categories.find(c => c.id === id)?.name || `#${id}`; } - function beginEditCategories(t: Transaction) { + function beginEditTransaction(t: Transaction) { setEditingTxId(t.id); setEditingCategoryIds([...(t.category_ids || [])]); + setEditingAmount(String(t.amount)); + setEditingDescription(t.description || ''); + setEditingDate(t.date || ''); } - function cancelEditCategories() { + function cancelEditTransaction() { setEditingTxId(null); setEditingCategoryIds([]); + setEditingAmount(''); + setEditingDescription(''); + setEditingDate(''); } - async function saveEditCategories() { + async function saveEditTransaction() { if (editingTxId == null) return; + const amountNum = Number(editingAmount); + if (Number.isNaN(amountNum)) { + alert('Amount must be a number.'); + return; + } try { - const updated = await updateTransaction(editingTxId, { category_ids: editingCategoryIds }); + const updated = await updateTransaction(editingTxId, { + amount: amountNum, + description: editingDescription, + date: editingDate || undefined, + category_ids: editingCategoryIds, + }); setTransactions(prev => prev.map(p => (p.id === updated.id ? updated : p))); - cancelEditCategories(); + // Optionally refresh balance series to reflect changes immediately + try { setBalanceSeries(await getBalanceSeries(startDate || undefined, endDate || undefined)); } catch {} + cancelEditTransaction(); } catch (err: any) { - alert(err?.message || 'Failed to update transaction categories'); + alert(err?.message || 'Failed to update transaction'); + } + } + async function handleDeleteTransaction(id: number) { + if (!confirm('Delete this transaction? This cannot be undone.')) return; + try { + await deleteTransaction(id); + setTransactions(prev => prev.filter(t => t.id !== id)); + try { setBalanceSeries(await getBalanceSeries(startDate || undefined, endDate || undefined)); } catch {} + } catch (err: any) { + alert(err?.message || 'Failed to delete transaction'); } } @@ -383,32 +416,91 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { Amount Description Categories + Actions {visible.map(t => ( - {t.date || ''} - {formatAmount(t.amount)} - {t.description || ''} + {/* Date cell */} {editingTxId === t.id ? ( -
- setEditingDate(e.target.value)} + /> + ) : ( + t.date || '' + )} + + + {/* Amount cell */} + + {editingTxId === t.id ? ( + setEditingAmount(e.target.value)} + style={{ textAlign: 'right' }} + /> + ) : ( + formatAmount(t.amount) + )} + + + {/* Description cell */} + + {editingTxId === t.id ? ( + setEditingDescription(e.target.value)} + /> + ) : ( + t.description || '' + )} + + + {/* Categories cell */} + + {editingTxId === t.id ? ( +
+ - -
) : ( -
- {t.category_ids.map(id => categoryNameById(id)).join(', ') || '—'} - + {t.category_ids.map(id => categoryNameById(id)).join(', ') || '—'} + )} + + + {/* Actions cell */} + + {editingTxId === t.id ? ( +
+ + + +
+ ) : ( +
+ +
)} From a9b2aba55abaf777f9cad47385dd6b668bbe7ca9 Mon Sep 17 00:00:00 2001 From: ribardej Date: Wed, 5 Nov 2025 20:24:33 +0100 Subject: [PATCH 3/4] 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; } From 60560dea99c705657909545b20ca3a957f09956d Mon Sep 17 00:00:00 2001 From: Dejan Ribarovski Date: Wed, 5 Nov 2025 20:39:52 +0100 Subject: [PATCH 4/4] Update 7project/frontend/src/pages/Dashboard.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- 7project/frontend/src/pages/Dashboard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7project/frontend/src/pages/Dashboard.tsx b/7project/frontend/src/pages/Dashboard.tsx index 2d0e20b..31af914 100644 --- a/7project/frontend/src/pages/Dashboard.tsx +++ b/7project/frontend/src/pages/Dashboard.tsx @@ -169,7 +169,7 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { const [editingDate, setEditingDate] = useState(''); // YYYY-MM-DD // Sidebar toggle for mobile - const [sidebarOpen, setSidebarOpen] = useState(false) + const [sidebarOpen, setSidebarOpen] = useState(false);