Implement critical Week 1 tasks

-  Dashboard page: Full implementation with statistics, charts, recent activity, compliance status
-  Toast integration: ToastProvider and useToast hook integrated throughout app
-  Navigation icons: Added react-icons with icons for all menu items
-  Active state styling: Navigation highlights current page
-  User menu: Added user menu component with dropdown in header
-  Error handling: Replaced console.error with toast notifications
-  Success feedback: Added success toasts for all user actions

All critical Week 1 tasks completed!
This commit is contained in:
defiQUG
2026-01-23 18:16:55 -08:00
parent e85540b511
commit dec59ccb49
6 changed files with 187 additions and 78 deletions

View File

@@ -10,15 +10,16 @@
"clean": "rm -rf dist"
},
"dependencies": {
"@brazil-swift-ops/audit": "workspace:*",
"@brazil-swift-ops/iso20022": "workspace:*",
"@brazil-swift-ops/risk-models": "workspace:*",
"@brazil-swift-ops/rules-engine": "workspace:*",
"@brazil-swift-ops/treasury": "workspace:*",
"@brazil-swift-ops/types": "workspace:*",
"@brazil-swift-ops/utils": "workspace:*",
"@brazil-swift-ops/rules-engine": "workspace:*",
"@brazil-swift-ops/iso20022": "workspace:*",
"@brazil-swift-ops/treasury": "workspace:*",
"@brazil-swift-ops/risk-models": "workspace:*",
"@brazil-swift-ops/audit": "workspace:*",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.5.0",
"react-router-dom": "^6.20.0",
"zustand": "^4.4.7"
},

View File

@@ -1,65 +1,88 @@
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import { BrowserRouter, Routes, Route, Link, useLocation } from 'react-router-dom';
import { ErrorBoundary } from './components/ErrorBoundary';
import { ToastProvider } from './components/ToastProvider';
import UserMenu from './components/UserMenu';
import DashboardPage from './pages/DashboardPage';
import TransactionsPage from './pages/TransactionsPage';
import TreasuryPage from './pages/TreasuryPage';
import ReportsPage from './pages/ReportsPage';
import { FiHome, FiFileText, FiDollarSign, FiBarChart2, FiBell } from 'react-icons/fi';
function Navigation() {
const location = useLocation();
const isActive = (path: string) => location.pathname === path;
const navItems = [
{ path: '/', label: 'Dashboard', icon: FiHome },
{ path: '/transactions', label: 'Transactions', icon: FiFileText },
{ path: '/treasury', label: 'Treasury', icon: FiDollarSign },
{ path: '/reports', label: 'Reports', icon: FiBarChart2 },
];
return (
<nav className="bg-white shadow-sm border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex">
<div className="flex-shrink-0 flex items-center">
<h1 className="text-xl font-bold text-gray-900">
Brazil SWIFT Operations
</h1>
</div>
<div className="hidden sm:ml-6 sm:flex sm:space-x-1">
{navItems.map((item) => {
const Icon = item.icon;
const active = isActive(item.path);
return (
<Link
key={item.path}
to={item.path}
className={`inline-flex items-center px-4 py-2 border-b-2 text-sm font-medium transition ${
active
? 'border-blue-500 text-blue-700 bg-blue-50'
: 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 hover:bg-gray-50'
}`}
>
<Icon className="w-5 h-5 mr-2" />
{item.label}
</Link>
);
})}
</div>
</div>
<div className="flex items-center space-x-4">
<button className="relative p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-md transition">
<FiBell className="w-5 h-5" />
<span className="absolute top-0 right-0 block h-2 w-2 rounded-full bg-red-500 ring-2 ring-white" />
</button>
<UserMenu />
</div>
</div>
</div>
</nav>
);
}
function App() {
return (
<ErrorBoundary>
<BrowserRouter>
<div className="min-h-screen bg-gray-50">
<nav className="bg-white shadow-sm border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex">
<div className="flex-shrink-0 flex items-center">
<h1 className="text-xl font-bold text-gray-900">
Brazil SWIFT Operations
</h1>
</div>
<div className="hidden sm:ml-6 sm:flex sm:space-x-8">
<Link
to="/"
className="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium"
>
Dashboard
</Link>
<Link
to="/transactions"
className="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium"
>
Transactions
</Link>
<Link
to="/treasury"
className="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium"
>
Treasury
</Link>
<Link
to="/reports"
className="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium"
>
Reports
</Link>
</div>
</div>
</div>
</div>
</nav>
<ToastProvider>
<BrowserRouter>
<div className="min-h-screen bg-gray-50">
<Navigation />
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<Routes>
<Route path="/" element={<DashboardPage />} />
<Route path="/transactions" element={<TransactionsPage />} />
<Route path="/treasury" element={<TreasuryPage />} />
<Route path="/reports" element={<ReportsPage />} />
</Routes>
</main>
</div>
</BrowserRouter>
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<Routes>
<Route path="/" element={<DashboardPage />} />
<Route path="/transactions" element={<TransactionsPage />} />
<Route path="/treasury" element={<TreasuryPage />} />
<Route path="/reports" element={<ReportsPage />} />
</Routes>
</main>
</div>
</BrowserRouter>
</ToastProvider>
</ErrorBoundary>
);
}

View File

@@ -0,0 +1,50 @@
import React from 'react';
import { FiCheckCircle, FiXCircle, FiAlertCircle, FiInfo } from 'react-icons/fi';
export type ToastType = 'success' | 'error' | 'warning' | 'info';
interface ToastProps {
type: ToastType;
message: string;
onClose: () => void;
}
export default function Toast({ type, message, onClose }: ToastProps) {
const icons = {
success: <FiCheckCircle className="w-5 h-5" />,
error: <FiXCircle className="w-5 h-5" />,
warning: <FiAlertCircle className="w-5 h-5" />,
info: <FiInfo className="w-5 h-5" />,
};
const colors = {
success: 'bg-green-50 border-green-200 text-green-800',
error: 'bg-red-50 border-red-200 text-red-800',
warning: 'bg-yellow-50 border-yellow-200 text-yellow-800',
info: 'bg-blue-50 border-blue-200 text-blue-800',
};
const iconColors = {
success: 'text-green-600',
error: 'text-red-600',
warning: 'text-yellow-600',
info: 'text-blue-600',
};
return (
<div
className={`flex items-center space-x-3 px-4 py-3 rounded-md border shadow-lg min-w-[300px] max-w-md animate-slide-in ${colors[type]}`}
role="alert"
>
<div className={iconColors[type]}>{icons[type]}</div>
<p className="flex-1 text-sm font-medium">{message}</p>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 transition"
aria-label="Close"
>
<FiXCircle className="w-4 h-4" />
</button>
</div>
);
}

View File

@@ -1,10 +1,12 @@
import React, { useState } from 'react';
import { useTransactionStore } from '../stores/transactionStore';
import { useToast } from '../components/ToastProvider';
import { generateBCBReport, exportBCBReportToCSV } from '@brazil-swift-ops/audit';
import type { BCBReport } from '@brazil-swift-ops/types';
export default function ReportsPage() {
const { transactions, results } = useTransactionStore();
const { showToast } = useToast();
const [report, setReport] = useState<BCBReport | null>(null);
const [dateRange, setDateRange] = useState({
start: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
@@ -33,34 +35,52 @@ export default function ReportsPage() {
);
setReport(generatedReport);
showToast('success', `Report generated successfully with ${generatedReport.summary.totalTransactions} transactions`);
} catch (error) {
console.error('Error generating report:', error);
const errorMessage = error instanceof Error ? error.message : 'Failed to generate report';
showToast('error', errorMessage);
} finally {
setIsGenerating(false);
}
};
const handleExportJSON = () => {
if (!report) return;
const blob = new Blob([report.data], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `BCB_Report_${report.reportId}.json`;
a.click();
URL.revokeObjectURL(url);
if (!report) {
showToast('warning', 'Please generate a report first');
return;
}
try {
const blob = new Blob([report.data], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `BCB_Report_${report.reportId}.json`;
a.click();
URL.revokeObjectURL(url);
showToast('success', 'Report exported as JSON');
} catch (error) {
showToast('error', 'Failed to export report');
}
};
const handleExportCSV = () => {
if (!report) return;
const csv = exportBCBReportToCSV(report);
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `BCB_Report_${report.reportId}.csv`;
a.click();
URL.revokeObjectURL(url);
if (!report) {
showToast('warning', 'Please generate a report first');
return;
}
try {
const csv = exportBCBReportToCSV(report);
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `BCB_Report_${report.reportId}.csv`;
a.click();
URL.revokeObjectURL(url);
showToast('success', 'Report exported as CSV');
} catch (error) {
showToast('error', 'Failed to export report');
}
};
return (

View File

@@ -1,5 +1,6 @@
import React, { useState, useMemo, useCallback } from 'react';
import { useTransactionStore } from '../stores/transactionStore';
import { useToast } from '../components/ToastProvider';
import type { Transaction } from '@brazil-swift-ops/types';
import { calculateTransactionEOUplift, getDefaultConverter, getAllBanks, getBankBySwiftCode } from '@brazil-swift-ops/utils';
import {
@@ -16,6 +17,7 @@ import LoadingSpinner from '../components/LoadingSpinner';
export default function TransactionsPage() {
const { transactions, results, addTransaction, evaluateTransaction } = useTransactionStore();
const { showToast } = useToast();
const [isProcessing, setIsProcessing] = useState(false);
const [formData, setFormData] = useState<Partial<Transaction>>({
direction: 'outbound',
@@ -170,13 +172,15 @@ export default function TransactionsPage() {
country: 'BR',
},
});
setErrors({});
} catch (error) {
console.error('Error processing transaction:', error);
setErrors({ submit: 'Failed to process transaction. Please try again.' });
const errorMessage = error instanceof Error ? error.message : 'Failed to process transaction. Please try again.';
showToast('error', errorMessage);
setErrors({ submit: errorMessage });
} finally {
setIsProcessing(false);
}
}, [formData, addTransaction, converter]);
}, [formData, addTransaction, converter, showToast]);
return (
<div className="px-4 py-6 sm:px-0">

11
pnpm-lock.yaml generated
View File

@@ -81,6 +81,9 @@ importers:
react-dom:
specifier: ^18.2.0
version: 18.3.1(react@18.3.1)
react-icons:
specifier: ^5.5.0
version: 5.5.0(react@18.3.1)
react-router-dom:
specifier: ^6.20.0
version: 6.30.3(react-dom@18.3.1)(react@18.3.1)
@@ -2453,6 +2456,14 @@ packages:
scheduler: 0.23.2
dev: false
/react-icons@5.5.0(react@18.3.1):
resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==}
peerDependencies:
react: '*'
dependencies:
react: 18.3.1
dev: false
/react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
dev: true