This content originally appeared on DEV Community and was authored by Nebula
import { useState, useEffect, useCallback, useMemo } from "react";
import { Search, Filter, X, RefreshCw, AlertCircle, TrendingUp, TrendingDown, Calendar, DollarSign, PieChart, BarChart3, Download, Eye, EyeOff } from "lucide-react";
import styled from 'styled-components';
import { motion, AnimatePresence } from 'framer-motion';
import { LineChart, Line, AreaChart, Area, BarChart, Bar, PieChart as RechartsPieChart, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
// Design tokens (from your original file)
const colors = {
primary: '#667eea',
primaryDark: '#5a67d8',
secondary: '#764ba2',
success: '#48bb78',
error: '#ff6b6b',
warning: '#f6ad55',
text: '#1a202c',
textSecondary: '#4a5568',
textMuted: '#a0aec0',
background: 'rgba(255, 255, 255, 0.95)',
border: 'rgba(102, 126, 234, 0.2)',
borderHover: '#667eea',
glass: 'rgba(255, 255, 255, 0.95)',
glassLight: 'rgba(255, 255, 255, 0.8)',
shadow: 'rgba(0, 0, 0, 0.1)',
shadowHover: 'rgba(102, 126, 234, 0.1)'
};
const gradients = {
primary: `linear-gradient(135deg, ${colors.primary}, ${colors.secondary})`,
success: `linear-gradient(135deg, ${colors.success}, #38b2ac)`,
error: `linear-gradient(135deg, ${colors.error}, #ee5a52)`,
warning: `linear-gradient(135deg, ${colors.warning}, #ff8c00)`
};
const spacing = {
xs: '4px',
sm: '8px',
md: '12px',
lg: '16px',
xl: '20px',
xxl: '24px',
xxxl: '32px'
};
// Styled Components
const Container = styled(motion.div)`
padding: ${spacing.xxxl};
max-width: 1400px;
margin: 0 auto;
min-height: calc(100vh - 120px);
h1 {
font-size: 36px;
font-weight: 800;
color: ${colors.text};
margin-bottom: ${spacing.sm};
background: ${gradients.primary};
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-align: center;
}
.subtitle {
text-align: center;
color: ${colors.textMuted};
margin-bottom: ${spacing.xxxl};
font-size: 16px;
}
@media (max-width: 768px) {
padding: ${spacing.xl};
h1 {
font-size: 28px;
margin-bottom: ${spacing.xs};
}
}
`;
const StatsGrid = styled.div`
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: ${spacing.xl};
margin-bottom: ${spacing.xxxl};
@media (max-width: 768px) {
grid-template-columns: 1fr;
gap: ${spacing.lg};
}
`;
const StatCard = styled(motion.div)`
background: ${colors.glass};
backdrop-filter: blur(20px);
border-radius: 20px;
padding: ${spacing.xxl};
box-shadow: 0 8px 32px ${colors.shadow};
border: 1px solid rgba(255, 255, 255, 0.2);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: ${props => props.color || gradients.primary};
}
.stat-header {
display: flex;
align-items: center;
gap: ${spacing.md};
margin-bottom: ${spacing.lg};
svg {
color: ${props => props.iconColor || colors.primary};
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: ${colors.textSecondary};
text-transform: uppercase;
letter-spacing: 0.5px;
}
}
.stat-value {
font-size: 32px;
font-weight: 800;
color: ${colors.text};
margin-bottom: ${spacing.sm};
}
.stat-change {
display: flex;
align-items: center;
gap: ${spacing.xs};
font-size: 14px;
font-weight: 600;
&.positive {
color: ${colors.success};
}
&.negative {
color: ${colors.error};
}
}
`;
const FilterSection = styled(motion.div)`
background: ${colors.glass};
backdrop-filter: blur(20px);
border-radius: 20px;
padding: ${spacing.xxl};
margin-bottom: ${spacing.xxxl};
box-shadow: 0 8px 32px ${colors.shadow};
border: 1px solid rgba(255, 255, 255, 0.2);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: ${gradients.primary};
}
.filter-header {
display: flex;
align-items: center;
gap: ${spacing.md};
margin-bottom: ${spacing.xl};
h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: ${colors.text};
}
}
`;
const FiltersGrid = styled.div`
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: ${spacing.lg};
margin-bottom: ${spacing.xl};
@media (max-width: 768px) {
grid-template-columns: 1fr;
}
`;
const FilterSelect = styled.select`
padding: ${spacing.lg} ${spacing.xl};
border: 2px solid ${colors.border};
border-radius: 12px;
font-size: 16px;
font-weight: 500;
color: ${colors.text};
background: ${colors.glassLight};
backdrop-filter: blur(10px);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
&:focus {
outline: none;
border-color: ${colors.borderHover};
box-shadow: 0 0 0 4px ${colors.shadowHover};
background: ${colors.background};
}
`;
const DateInput = styled.input`
padding: ${spacing.lg} ${spacing.xl};
border: 2px solid ${colors.border};
border-radius: 12px;
font-size: 16px;
font-weight: 500;
color: ${colors.text};
background: ${colors.glassLight};
backdrop-filter: blur(10px);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:focus {
outline: none;
border-color: ${colors.borderHover};
box-shadow: 0 0 0 4px ${colors.shadowHover};
background: ${colors.background};
}
`;
const AmountRangeContainer = styled.div`
display: flex;
gap: ${spacing.md};
align-items: center;
input {
flex: 1;
padding: ${spacing.lg};
border: 2px solid ${colors.border};
border-radius: 12px;
font-size: 16px;
font-weight: 500;
color: ${colors.text};
background: ${colors.glassLight};
backdrop-filter: blur(10px);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:focus {
outline: none;
border-color: ${colors.borderHover};
box-shadow: 0 0 0 4px ${colors.shadowHover};
background: ${colors.background};
}
}
span {
color: ${colors.textMuted};
font-weight: 600;
}
`;
const ButtonGroup = styled.div`
display: flex;
gap: ${spacing.md};
justify-content: flex-end;
@media (max-width: 768px) {
justify-content: stretch;
}
`;
const Button = styled(motion.button)`
display: flex;
align-items: center;
gap: ${spacing.sm};
padding: ${spacing.md} ${spacing.xl};
border-radius: 12px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: 2px solid transparent;
${props => props.variant === 'primary' ? `
background: ${gradients.primary};
color: white;
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.4);
&:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.5);
}
` : `
background: transparent;
color: ${colors.textSecondary};
border-color: #e2e8f0;
&:hover:not(:disabled) {
background: #f7fafc;
border-color: #cbd5e0;
}
`}
&:active {
transform: translateY(0);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
@media (max-width: 768px) {
flex: 1;
justify-content: center;
}
`;
const ChartsGrid = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
gap: ${spacing.xl};
margin-bottom: ${spacing.xxxl};
@media (max-width: 1024px) {
grid-template-columns: 1fr;
}
`;
const ChartContainer = styled(motion.div)`
background: ${colors.glass};
backdrop-filter: blur(20px);
border-radius: 20px;
padding: ${spacing.xxl};
box-shadow: 0 8px 32px ${colors.shadow};
border: 1px solid rgba(255, 255, 255, 0.2);
position: relative;
overflow: hidden;
grid-column: ${props => props.fullWidth ? 'span 2' : 'span 1'};
@media (max-width: 1024px) {
grid-column: span 1;
}
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: ${gradients.primary};
}
.chart-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: ${spacing.xl};
h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: ${colors.text};
}
.chart-controls {
display: flex;
gap: ${spacing.sm};
}
}
.chart-wrapper {
height: 300px;
}
`;
const ToggleButton = styled(motion.button)`
padding: ${spacing.sm} ${spacing.md};
border: 2px solid ${colors.border};
border-radius: 8px;
background: ${props => props.active ? gradients.primary : 'transparent'};
color: ${props => props.active ? 'white' : colors.textSecondary};
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
border-color: ${colors.borderHover};
}
`;
const LoadingSpinner = styled(motion.div)`
display: flex;
align-items: center;
justify-content: center;
padding: 64px;
color: ${colors.primary};
svg {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`;
// Mock data generator
const generateMockTransactions = (count = 100) => {
const types = ['DEPOSIT', 'WITHDRAW', 'TRANSFER', 'LOAN'];
const statuses = ['SUCCESS', 'PENDING', 'FAILED'];
const merchants = ['Amazon', 'Starbucks', 'Uber', 'Netflix', 'Spotify', 'Apple', 'Google', 'Microsoft', 'Target', 'Walmart'];
const categories = ['Food & Dining', 'Shopping', 'Transportation', 'Entertainment', 'Bills & Utilities', 'Healthcare', 'Travel', 'Education'];
return Array.from({ length: count }, (_, index) => {
const type = types[Math.floor(Math.random() * types.length)];
const amount = type === 'DEPOSIT'
? Math.floor(Math.random() * 5000) + 100
: -(Math.floor(Math.random() * 1000) + 10);
return {
id: `txn_${index + 1}`,
type,
amount,
status: statuses[Math.floor(Math.random() * statuses.length)],
date: new Date(Date.now() - Math.floor(Math.random() * 90) * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
merchant: merchants[Math.floor(Math.random() * merchants.length)],
category: categories[Math.floor(Math.random() * categories.length)],
description: `Transaction with ${merchants[Math.floor(Math.random() * merchants.length)]}`
};
});
};
// Custom tooltip component
const CustomTooltip = ({ active, payload, label }) => {
if (active && payload && payload.length) {
return (
<div style={{
background: colors.glass,
backdropFilter: 'blur(20px)',
border: `1px solid ${colors.border}`,
borderRadius: '12px',
padding: spacing.md,
boxShadow: `0 8px 32px ${colors.shadow}`
}}>
<p style={{ margin: 0, color: colors.text, fontWeight: '600' }}>{label}</p>
{payload.map((entry, index) => (
<p key={index} style={{ margin: '4px 0', color: entry.color, fontSize: '14px' }}>
{`${entry.name}: $${Math.abs(entry.value).toLocaleString()}`}
</p>
))}
</div>
);
}
return null;
};
const BankingAnalyticsDashboard = () => {
const [transactions, setTransactions] = useState([]);
const [loading, setLoading] = useState(true);
const [filters, setFilters] = useState({
type: 'ALL',
status: 'ALL',
category: 'ALL',
dateFrom: '',
dateTo: '',
amountMin: '',
amountMax: ''
});
const [chartView, setChartView] = useState({
timeChart: 'line',
showBalance: true
});
// Simulate API call
useEffect(() => {
const fetchTransactions = async () => {
setLoading(true);
// Simulate API delay
await new Promise(resolve => setTimeout(resolve, 1000));
setTransactions(generateMockTransactions(150));
setLoading(false);
};
fetchTransactions();
}, []);
// Filter transactions
const filteredTransactions = useMemo(() => {
return transactions.filter(transaction => {
if (filters.type !== 'ALL' && transaction.type !== filters.type) return false;
if (filters.status !== 'ALL' && transaction.status !== filters.status) return false;
if (filters.category !== 'ALL' && transaction.category !== filters.category) return false;
if (filters.dateFrom && transaction.date < filters.dateFrom) return false;
if (filters.dateTo && transaction.date > filters.dateTo) return false;
if (filters.amountMin && Math.abs(transaction.amount) < parseFloat(filters.amountMin)) return false;
if (filters.amountMax && Math.abs(transaction.amount) > parseFloat(filters.amountMax)) return false;
return true;
});
}, [transactions, filters]);
// Calculate statistics
const stats = useMemo(() => {
const totalIncome = filteredTransactions
.filter(t => t.amount > 0)
.reduce((sum, t) => sum + t.amount, 0);
const totalExpenses = filteredTransactions
.filter(t => t.amount < 0)
.reduce((sum, t) => sum + Math.abs(t.amount), 0);
const netBalance = totalIncome - totalExpenses;
const avgTransaction = filteredTransactions.length > 0
? filteredTransactions.reduce((sum, t) => sum + Math.abs(t.amount), 0) / filteredTransactions.length
: 0;
// Calculate month-over-month changes (mock data)
const incomeChange = Math.random() * 20 - 10; // -10% to +10%
const expenseChange = Math.random() * 20 - 10;
const balanceChange = Math.random() * 30 - 15;
return {
totalIncome,
totalExpenses,
netBalance,
avgTransaction,
transactionCount: filteredTransactions.length,
incomeChange,
expenseChange,
balanceChange
};
}, [filteredTransactions]);
// Prepare chart data
const timeSeriesData = useMemo(() => {
const dataMap = {};
let runningBalance = 0;
filteredTransactions
.sort((a, b) => new Date(a.date) - new Date(b.date))
.forEach(transaction => {
const date = transaction.date;
if (!dataMap[date]) {
dataMap[date] = { date, income: 0, expenses: 0, balance: runningBalance };
}
if (transaction.amount > 0) {
dataMap[date].income += transaction.amount;
} else {
dataMap[date].expenses += Math.abs(transaction.amount);
}
runningBalance += transaction.amount;
dataMap[date].balance = runningBalance;
});
return Object.values(dataMap);
}, [filteredTransactions]);
const categoryData = useMemo(() => {
const categoryMap = {};
filteredTransactions.forEach(transaction => {
if (transaction.amount < 0) { // Only expenses
const category = transaction.category;
categoryMap[category] = (categoryMap[category] || 0) + Math.abs(transaction.amount);
}
});
return Object.entries(categoryMap).map(([name, value]) => ({ name, value }));
}, [filteredTransactions]);
const typeData = useMemo(() => {
const typeMap = {};
filteredTransactions.forEach(transaction => {
const type = transaction.type;
typeMap[type] = (typeMap[type] || 0) + Math.abs(transaction.amount);
});
return Object.entries(typeMap).map(([name, value]) => ({ name, value }));
}, [filteredTransactions]);
const handleFilterChange = (key, value) => {
setFilters(prev => ({ ...prev, [key]: value }));
};
const resetFilters = () => {
setFilters({
type: 'ALL',
status: 'ALL',
category: 'ALL',
dateFrom: '',
dateTo: '',
amountMin: '',
amountMax: ''
});
};
const refreshData = () => {
setTransactions(generateMockTransactions(150));
};
const exportData = () => {
// Mock export functionality
const dataStr = JSON.stringify(filteredTransactions, null, 2);
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
const exportFileDefaultName = 'transaction-analytics.json';
const linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportFileDefaultName);
linkElement.click();
};
const pieColors = [colors.primary, colors.secondary, colors.success, colors.warning, colors.error, '#9f7aea', '#38b2ac', '#ed8936'];
if (loading) {
return (
<Container>
<LoadingSpinner
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
<RefreshCw size={32} />
<span style={{ marginLeft: spacing.md, fontSize: '18px', fontWeight: '600' }}>
Loading Analytics...
</span>
</LoadingSpinner>
</Container>
);
}
return (
<Container
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
>
<h1>Transaction Analytics</h1>
<p className="subtitle">Comprehensive insights into your financial activities</p>
{/* Statistics Cards */}
<StatsGrid>
<StatCard
color={gradients.success}
iconColor={colors.success}
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
>
<div className="stat-header">
<TrendingUp size={20} />
<h3>Total Income</h3>
</div>
<div className="stat-value">${stats.totalIncome.toLocaleString()}</div>
<div className={`stat-change ${stats.incomeChange >= 0 ? 'positive' : 'negative'}`}>
{stats.incomeChange >= 0 ? <TrendingUp size={16} /> : <TrendingDown size={16} />}
{Math.abs(stats.incomeChange).toFixed(1)}% from last month
</div>
</StatCard>
<StatCard
color={gradients.error}
iconColor={colors.error}
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
>
<div className="stat-header">
<TrendingDown size={20} />
<h3>Total Expenses</h3>
</div>
<div className="stat-value">${stats.totalExpenses.toLocaleString()}</div>
<div className={`stat-change ${stats.expenseChange <= 0 ? 'positive' : 'negative'}`}>
{stats.expenseChange <= 0 ? <TrendingDown size={16} /> : <TrendingUp size={16} />}
{Math.abs(stats.expenseChange).toFixed(1)}% from last month
</div>
</StatCard>
<StatCard
color={stats.netBalance >= 0 ? gradients.success : gradients.error}
iconColor={stats.netBalance >= 0 ? colors.success : colors.error}
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
>
<div className="stat-header">
<DollarSign size={20} />
<h3>Net Balance</h3>
</div>
<div className="stat-value">${stats.netBalance.toLocaleString()}</div>
<div className={`stat-change ${stats.balanceChange >= 0 ? 'positive' : 'negative'}`}>
{stats.balanceChange >= 0 ? <TrendingUp size={16} /> : <TrendingDown size={16} />}
{Math.abs(stats.balanceChange).toFixed(1)}% from last month
</div>
</StatCard>
<StatCard
color={gradients.primary}
iconColor={colors.primary}
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4 }}
>
<div className="stat-header">
<BarChart3 size={20} />
<h3>Avg Transaction</h3>
</div>
<div className="stat-value">${stats.avgTransaction.toLocaleString()}</div>
<div className="stat-change positive">
<Calendar size={16} />
{stats.transactionCount} transactions
</div>
</StatCard>
</StatsGrid>
{/* Filters */}
<FilterSection
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 }}
>
<div className="filter-header">
<Filter size={20} />
<h3>Filters & Controls</h3>
</div>
<FiltersGrid>
<FilterSelect
value={filters.type}
onChange={(e) => handleFilterChange('type', e.target.value)}
>
<option value="ALL">All Types</option>
<option value="DEPOSIT">Deposits</option>
<option value="WITHDRAW">Withdrawals</option>
<option value="TRANSFER">Transfers</option>
<option value="LOAN">Loans</option>
</FilterSelect>
<FilterSelect
value={filters.status}
onChange={(e) => handleFilterChange('status', e.target.value)}
>
<option value="ALL">All Status</option>
<option value="SUCCESS">Success</option>
<option value="PENDING">Pending</option>
<option value="FAILED">Failed</option>
</FilterSelect>
<FilterSelect
value={filters.category}
onChange={(e) => handleFilterChange('category', e.target.value)}
>
<option value="ALL">All Categories</option>
<option value="Food & Dining">Food & Dining</option>
<option value="Shopping">Shopping</option>
<option value="Transportation">Transportation</option>
<option value="Entertainment">Entertainment</option>
<option value="Bills & Utilities">Bills & Utilities</option>
<option value="Healthcare">Healthcare</option>
<option value="Travel">Travel</option>
<option value="Education">Education</option>
</FilterSelect>
<DateInput
type="date"
placeholder="From Date"
value={filters.dateFrom}
onChange={(e) => handleFilterChange('dateFrom', e.target.value)}
/>
<DateInput
type="date"
placeholder="To Date"
value={filters.dateTo}
onChange={(e) => handleFilterChange('dateTo', e.target.value)}
/>
<AmountRangeContainer>
<input
type="number"
placeholder="Min Amount"
value={filters.amountMin}
onChange={(e) => handleFilterChange('amountMin', e.target.value)}
/>
<span>-</span>
<input
type="number"
placeholder="Max Amount"
value={filters.amountMax}
onChange={(e) => handleFilterChange('amountMax', e.target.value)}
/>
</AmountRangeContainer>
</FiltersGrid>
<ButtonGroup>
<Button onClick={resetFilters}>
<X size={16} />
Clear Filters
</Button>
<Button onClick={refreshData}>
<RefreshCw size={16} />
Refresh Data
</Button>
<Button variant="primary" onClick={exportData}>
<Download size={16} />
Export Data
</Button>
</ButtonGroup>
</FilterSection>
{/* Charts */}
<ChartsGrid>
{/* Time Series Chart */}
<ChartContainer
fullWidth
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.6 }}
>
<div className="chart-header">
<h3>Financial Flow Over Time</h3>
<div className="chart-controls">
<ToggleButton
active={chartView.timeChart === 'line'}
onClick={() => setChartView(prev => ({...prev, timeChart: 'line'}))}
>
Line
</ToggleButton>
<ToggleButton
active={chartView.timeChart === 'area'}
onClick={() => setChartView(prev => ({...prev, timeChart: 'area'}))}
>
Area
</ToggleButton>
<ToggleButton
active={chartView.showBalance}
onClick={() => setChartView(prev => ({...prev, showBalance: !prev.showBalance}))}
>
{chartView.showBalance ? <Eye size={14} /> : <EyeOff size={14} />}
Balance
</ToggleButton>
</div>
</div>
<div className="chart-wrapper">
<ResponsiveContainer width="100%" height="100%">
{chartView.timeChart === 'line' ? (
<LineChart data={timeSeriesData}>
<CartesianGrid strokeDasharray="3 3" stroke={colors.border} />
<XAxis dataKey="date" stroke={colors.textMuted} />
<YAxis stroke={colors.textMuted} />
<Tooltip content={<CustomTooltip />} />
<Legend />
<Line
type="monotone"
dataKey="income"
stroke={colors.success}
strokeWidth={3}
dot={{ fill: colors.success, strokeWidth: 2, r: 4 }}
name="Income"
/>
<Line
type="monotone"
dataKey="expenses"
stroke={colors.error}
strokeWidth={3}
dot={{ fill: colors.error, strokeWidth: 2, r: 4 }}
name="Expenses"
/>
{chartView.showBalance && (
<Line
type="monotone"
dataKey="balance"
stroke={colors.primary}
strokeWidth={3}
dot={{ fill: colors.primary, strokeWidth: 2, r: 4 }}
name="Running Balance"
/>
)}
</LineChart>
) : (
<AreaChart data={timeSeriesData}>
<CartesianGrid strokeDasharray="3 3" stroke={colors.border} />
<XAxis dataKey="date" stroke={colors.textMuted} />
<YAxis stroke={colors.textMuted} />
<Tooltip content={<CustomTooltip />} />
<Legend />
<Area
type="monotone"
dataKey="income"
stackId="1"
stroke={colors.success}
fill={colors.success}
fillOpacity={0.6}
name="Income"
/>
<Area
type="monotone"
dataKey="expenses"
stackId="2"
stroke={colors.error}
fill={colors.error}
fillOpacity={0.6}
name="Expenses"
/>
{chartView.showBalance && (
<Area
type="monotone"
dataKey="balance"
stroke={colors.primary}
fill={colors.primary}
fillOpacity={0.3}
name="Running Balance"
/>
)}
</AreaChart>
)}
</ResponsiveContainer>
</div>
</ChartContainer>
{/* Category Breakdown */}
<ChartContainer
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.7 }}
>
<div className="chart-header">
<h3>Expenses by Category</h3>
</div>
<div className="chart-wrapper">
<ResponsiveContainer width="100%" height="100%">
<RechartsPieChart>
<Pie
data={categoryData}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={100}
paddingAngle={5}
dataKey="value"
>
{categoryData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={pieColors[index % pieColors.length]} />
))}
</Pie>
<Tooltip content={<CustomTooltip />} />
<Legend />
</RechartsPieChart>
</ResponsiveContainer>
</div>
</ChartContainer>
{/* Transaction Types */}
<ChartContainer
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.8 }}
>
<div className="chart-header">
<h3>Transaction Types</h3>
</div>
<div className="chart-wrapper">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={typeData}>
<CartesianGrid strokeDasharray="3 3" stroke={colors.border} />
<XAxis dataKey="name" stroke={colors.textMuted} />
<YAxis stroke={colors.textMuted} />
<Tooltip content={<CustomTooltip />} />
<Bar dataKey="value" fill={colors.primary} radius={[8, 8, 0, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
</ChartContainer>
</ChartsGrid>
{/* Recent Transactions Table */}
<ChartContainer
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.9 }}
>
<div className="chart-header">
<h3>Recent Transactions</h3>
<div className="chart-controls">
<span style={{ fontSize: '14px', color: colors.textMuted }}>
Showing {Math.min(10, filteredTransactions.length)} of {filteredTransactions.length} transactions
</span>
</div>
</div>
<div style={{ overflowX: 'auto' }}>
<table style={{ width: '100%', borderCollapse: 'separate', borderSpacing: 0 }}>
<thead>
<tr style={{ background: gradients.primary }}>
<th style={{ padding: spacing.lg, color: 'white', fontWeight: '600', fontSize: '14px', textAlign: 'left' }}>Date</th>
<th style={{ padding: spacing.lg, color: 'white', fontWeight: '600', fontSize: '14px', textAlign: 'left' }}>Description</th>
<th style={{ padding: spacing.lg, color: 'white', fontWeight: '600', fontSize: '14px', textAlign: 'left' }}>Category</th>
<th style={{ padding: spacing.lg, color: 'white', fontWeight: '600', fontSize: '14px', textAlign: 'left' }}>Type</th>
<th style={{ padding: spacing.lg, color: 'white', fontWeight: '600', fontSize: '14px', textAlign: 'left' }}>Status</th>
<th style={{ padding: spacing.lg, color: 'white', fontWeight: '600', fontSize: '14px', textAlign: 'right' }}>Amount</th>
</tr>
</thead>
<tbody>
{filteredTransactions.slice(0, 10).map((transaction, index) => (
<tr
key={transaction.id}
style={{
background: index % 2 === 0 ? 'rgba(248, 250, 252, 0.5)' : 'transparent',
borderBottom: '1px solid rgba(226, 232, 240, 0.5)'
}}
>
<td style={{ padding: spacing.lg, color: colors.textSecondary, fontWeight: '500' }}>
{new Date(transaction.date).toLocaleDateString()}
</td>
<td style={{ padding: spacing.lg, color: colors.text, fontWeight: '600' }}>
{transaction.description}
</td>
<td style={{ padding: spacing.lg, color: colors.textSecondary }}>
{transaction.category}
</td>
<td style={{ padding: spacing.lg }}>
<TypeBadge type={transaction.type}>
{transaction.type}
</TypeBadge>
</td>
<td style={{ padding: spacing.lg }}>
<StatusBadge status={transaction.status}>
{transaction.status}
</StatusBadge>
</td>
<td style={{
padding: spacing.lg,
textAlign: 'right',
fontWeight: '700',
color: transaction.amount > 0 ? colors.success : colors.error
}}>
{transaction.amount > 0 ? '+' : ''}${transaction.amount.toLocaleString()}
</td>
</tr>
))}
</tbody>
</table>
</div>
</ChartContainer>
</Container>
);
};
// Styled badge components
const StatusBadge = styled.span`
padding: 6px ${spacing.md};
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
${props => props.status === 'SUCCESS' && `
background: linear-gradient(135deg, rgba(72, 187, 120, 0.2), rgba(56, 178, 172, 0.2));
color: ${colors.success};
border: 1px solid rgba(72, 187, 120, 0.3);
`}
${props => props.status === 'PENDING' && `
background: linear-gradient(135deg, rgba(255, 193, 7, 0.2), rgba(255, 152, 0, 0.2));
color: ${colors.warning};
border: 1px solid rgba(255, 193, 7, 0.3);
`}
${props => props.status === 'FAILED' && `
background: linear-gradient(135deg, rgba(255, 107, 107, 0.2), rgba(238, 90, 82, 0.2));
color: ${colors.error};
border: 1px solid rgba(255, 107, 107, 0.3);
`}
`;
const TypeBadge = styled.span`
padding: 6px ${spacing.md};
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
${props => props.type === 'DEPOSIT' && `
background: linear-gradient(135deg, rgba(72, 187, 120, 0.2), rgba(56, 178, 172, 0.2));
color: ${colors.success};
border: 1px solid rgba(72, 187, 120, 0.3);
`}
${props => props.type === 'WITHDRAW' && `
background: linear-gradient(135deg, rgba(255, 107, 107, 0.2), rgba(238, 90, 82, 0.2));
color: ${colors.error};
border: 1px solid rgba(255, 107, 107, 0.3);
`}
${props => props.type === 'TRANSFER' && `
background: linear-gradient(135deg, rgba(102, 126, 234, 0.2), rgba(118, 75, 162, 0.2));
color: ${colors.primary};
border: 1px solid rgba(102, 126, 234, 0.3);
`}
${props => props.type === 'LOAN' && `
background: linear-gradient(135deg, rgba(255, 193, 7, 0.2), rgba(255, 152, 0, 0.2));
color: ${colors.warning};
border: 1px solid rgba(255, 193, 7, 0.3);
`}
`;
export default BankingAnalyticsDashboard
This content originally appeared on DEV Community and was authored by Nebula
Print
Share
Comment
Cite
Upload
Translate
Updates
There are no updates yet.
Click the Upload button above to add an update.

APA
MLA
Nebula | Sciencx (2025-07-02T05:44:48+00:00) analytics. Retrieved from https://www.scien.cx/2025/07/02/analytics/
" » analytics." Nebula | Sciencx - Wednesday July 2, 2025, https://www.scien.cx/2025/07/02/analytics/
HARVARDNebula | Sciencx Wednesday July 2, 2025 » analytics., viewed ,<https://www.scien.cx/2025/07/02/analytics/>
VANCOUVERNebula | Sciencx - » analytics. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/07/02/analytics/
CHICAGO" » analytics." Nebula | Sciencx - Accessed . https://www.scien.cx/2025/07/02/analytics/
IEEE" » analytics." Nebula | Sciencx [Online]. Available: https://www.scien.cx/2025/07/02/analytics/. [Accessed: ]
rf:citation » analytics | Nebula | Sciencx | https://www.scien.cx/2025/07/02/analytics/ |
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.