diff --git a/7project/frontend/src/pages/Dashboard.tsx b/7project/frontend/src/pages/Dashboard.tsx index ea8bfa9..c4cf021 100644 --- a/7project/frontend/src/pages/Dashboard.tsx +++ b/7project/frontend/src/pages/Dashboard.tsx @@ -4,6 +4,7 @@ import AccountPage from './AccountPage'; import AppearancePage from './AppearancePage'; import BalanceChart from './BalanceChart'; import CategoryPieChart from './CategoryPieChart'; +import MockBankModal, { type MockGenerationOptions } from './MockBankModal'; import { BACKEND_URL } from '../config'; function formatAmount(n: number) { @@ -16,6 +17,8 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { const [categories, setCategories] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [isMockModalOpen, setMockModalOpen] = useState(false); + const [isGenerating, setIsGenerating] = useState(false); // Start CSAS (George) OAuth after login async function startOauthCsas() { @@ -92,6 +95,50 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { } } + async function handleGenerateMockTransactions(options: MockGenerationOptions) { + setIsGenerating(true); + setMockModalOpen(false); + + const { count, minAmount, maxAmount, startDate, endDate, categoryIds } = options; + const newTransactions: Transaction[] = []; + + const startDateTime = new Date(startDate).getTime(); + const endDateTime = new Date(endDate).getTime(); + + for (let i = 0; i < count; i++) { + // Generate random data based on user input + const amount = parseFloat((Math.random() * (maxAmount - minAmount) + minAmount).toFixed(2)); + + const randomTime = Math.random() * (endDateTime - startDateTime) + startDateTime; + const date = new Date(randomTime); + const dateString = date.toISOString().split('T')[0]; + + const randomCategory = categoryIds.length > 0 + ? [categoryIds[Math.floor(Math.random() * categoryIds.length)]] + : []; + + const payload = { + amount, + date: dateString, + category_ids: randomCategory, + }; + + try { + const created = await createTransaction(payload); + newTransactions.push(created); + } catch (err) { + console.error("Failed to create mock transaction:", err); + alert('An error occurred while generating transactions. Check the console.'); + break; + } + } + + setIsGenerating(false); + alert(`${newTransactions.length} mock transactions were successfully generated!`); + + await loadAll(); + } + useEffect(() => { loadAll(); }, [startDate, endDate]); const filtered = useMemo(() => { @@ -178,10 +225,16 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) {
{current === 'home' && ( <> -
-

Bank connections

-

Connect your CSAS (George) account.

- +
+

Bank connections

+
+

Connect your CSAS (George) account.

+ +
+
+

Generate data from a mock bank.

+ +
@@ -267,7 +320,6 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { - @@ -277,7 +329,6 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { {visible.map(t => ( - @@ -322,6 +373,13 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { )} + setMockModalOpen(false)} + onGenerate={handleGenerateMockTransactions} + /> ); } diff --git a/7project/frontend/src/pages/MockBankModal.tsx b/7project/frontend/src/pages/MockBankModal.tsx new file mode 100644 index 0000000..63d32e2 --- /dev/null +++ b/7project/frontend/src/pages/MockBankModal.tsx @@ -0,0 +1,100 @@ +// src/MockBankModal.tsx +import { useState } from 'react'; +import { type Category } from '../api'; + +// Define the shape of the generation options +export interface MockGenerationOptions { + count: number; + minAmount: number; + maxAmount: number; + startDate: string; + endDate: string; + categoryIds: number[]; +} + +interface MockBankModalProps { + isOpen: boolean; + isGenerating: boolean; + categories: Category[]; // Pass in available categories + onClose: () => void; + onGenerate: (options: MockGenerationOptions) => void; +} + +export default function MockBankModal({ isOpen, isGenerating, categories, onClose, onGenerate }: MockBankModalProps) { + // State for all the new form fields + const [count, setCount] = useState('10'); + const [minAmount, setMinAmount] = useState('-200'); + const [maxAmount, setMaxAmount] = useState('200'); + const [startDate, setStartDate] = useState(() => new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]); // Default to one year ago + const [endDate, setEndDate] = useState(() => new Date().toISOString().split('T')[0]); // Default to today + const [selectedCategoryIds, setSelectedCategoryIds] = useState([]); + + if (!isOpen) return null; + + function handleGenerateClick() { + const parsedCount = parseInt(count, 10); + const parsedMinAmount = parseFloat(minAmount); + const parsedMaxAmount = parseFloat(maxAmount); + const parsedStartDate = new Date(startDate); + const parsedEndDate = new Date(endDate); + + // Validation + if ( + isNaN(parsedCount) || parsedCount <= 0 || + isNaN(parsedMinAmount) || isNaN(parsedMaxAmount) || + parsedMaxAmount < parsedMinAmount || + isNaN(parsedStartDate.getTime()) || isNaN(parsedEndDate.getTime()) || + parsedEndDate < parsedStartDate + ) { + alert( + "Please ensure:\n" + + "- Count is a positive number\n" + + "- Min and Max Amount are valid numbers, and Max >= Min\n" + + "- Start and End Date are valid, and End Date >= Start Date" + ); + return; + } + + const options: MockGenerationOptions = { + count: parsedCount, + minAmount: parsedMinAmount, + maxAmount: parsedMaxAmount, + startDate, + endDate, + categoryIds: selectedCategoryIds.map(Number), + }; + + onGenerate(options); + } + + return ( +
+
e.stopPropagation()}> +

Generate Mock Transactions

+

+ Customize the random transactions you'd like to import. +

+
+ setCount(e.target.value)} placeholder="Number of transactions" /> +
+ setMinAmount(e.target.value)} placeholder="Min amount" /> + setMaxAmount(e.target.value)} placeholder="Max amount" /> +
+
+ setStartDate(e.target.value)} placeholder="Earliest date" /> + setEndDate(e.target.value)} placeholder="Latest date" /> +
+ +
+
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/7project/frontend/src/ui.css b/7project/frontend/src/ui.css index d456317..744f50f 100644 --- a/7project/frontend/src/ui.css +++ b/7project/frontend/src/ui.css @@ -122,3 +122,32 @@ body.auth-page #root { /* Utility */ .muted { color: var(--muted); } .space-y > * + * { margin-top: 12px; } + +/* Modal mock bank */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modal-content { + background: var(--panel); + padding: 24px; + border-radius: var(--radius); + box-shadow: var(--shadow); + width: 100%; + max-width: 400px; +} + +.connection-row { + display: flex; + justify-content: space-between; + align-items: center; +}
ID Date Amount Description
{t.id} {t.date || ''} {formatAmount(t.amount)} {t.description || ''}