FinMK / frontend /src /components /SavingsGoalsWidget.jsx
Kumar
Refactor: Exclude PDF and CSV files from Git to fix HF push error
24e6f5b
import React, { memo } from 'react';
import { Target, TrendingUp, Trash2, Calendar, Plus, CheckCircle } from 'lucide-react';
const SavingsGoalsWidget = memo(({ goals, currencySymbol, onAddGoal, onDeleteGoal, onUpdateGoal }) => {
// Net balance is the same for all goals, we can take it from the first one if available
const availableBalance = goals.length > 0 ? goals[0].current_amount : 0;
return (
<div className="glass-panel" style={{ padding: '2rem', marginBottom: '2rem', background: 'rgba(15, 23, 42, 0.6)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2.5rem' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
<div style={{
width: '48px',
height: '48px',
borderRadius: '14px',
background: 'linear-gradient(135deg, rgba(99, 102, 241, 0.2), rgba(139, 92, 246, 0.2))',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#818cf8',
boxShadow: '0 8px 16px -4px rgba(99, 102, 241, 0.2)'
}}>
<Target size={28} />
</div>
<div>
<h3 style={{ margin: 0, fontSize: '1.4rem', fontWeight: 'bold' }}>Financial Goals</h3>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginTop: '0.2rem' }}>
<span style={{ fontSize: '0.9rem', color: 'var(--text-muted)' }}>Available Balance:</span>
<span style={{ fontSize: '1rem', fontWeight: 'bold', color: '#10b981' }}>{currencySymbol}{availableBalance.toLocaleString()}</span>
</div>
</div>
</div>
<button
onClick={onAddGoal}
className="btn-primary"
style={{
display: 'flex',
alignItems: 'center',
gap: '0.6rem',
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
border: 'none',
color: 'white',
padding: '0.75rem 1.5rem',
borderRadius: '0.8rem',
fontWeight: '600',
boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)'
}}
>
<Plus size={18} /> Set New Goal
</button>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', gap: '2rem' }}>
{goals.length === 0 ? (
<div style={{
gridColumn: '1 / -1',
textAlign: 'center',
padding: '4rem 2rem',
background: 'rgba(255,255,255,0.02)',
borderRadius: '1.5rem',
border: '1px dashed rgba(255,255,255,0.1)'
}}>
<div style={{ fontSize: '3rem', marginBottom: '1.5rem' }}>🎯</div>
<h4 style={{ margin: '0 0 0.5rem 0', fontSize: '1.2rem' }}>No goals active</h4>
<p style={{ color: 'var(--text-muted)', margin: 0 }}>Start by setting a target for your savings!</p>
</div>
) : (
goals.map((goal) => {
const remaining = goal.target_amount - goal.current_amount;
const isAchieved = goal.current_amount >= goal.target_amount;
return (
<div key={goal.id} className="hover-lift" style={{
padding: '1.75rem',
background: isAchieved
? `linear-gradient(145deg, rgba(16, 185, 129, 0.1), rgba(30, 41, 59, 0.4))`
: 'rgba(30, 41, 59, 0.4)',
borderRadius: '1.5rem',
border: `1px solid ${isAchieved ? '#10b98140' : goal.color + '20'}`,
position: 'relative',
overflow: 'hidden',
transition: 'all 0.3s ease'
}}>
{/* Achievement Glow */}
{isAchieved && (
<div style={{
position: 'absolute',
top: '-20%',
right: '-20%',
width: '150px',
height: '150px',
background: 'radial-gradient(circle, rgba(16, 185, 129, 0.1) 0%, transparent 70%)',
pointerEvents: 'none'
}} />
)}
<div style={{ position: 'absolute', top: '1.25rem', right: '1.25rem' }}>
<button
onClick={() => onDeleteGoal(goal.id)}
style={{
background: 'rgba(239, 68, 68, 0.1)',
border: 'none',
color: '#ef4444',
padding: '0.4rem',
borderRadius: '0.6rem',
cursor: 'pointer',
opacity: 0.6,
transition: 'opacity 0.2s'
}}
onMouseEnter={(e) => e.currentTarget.style.opacity = '1'}
onMouseLeave={(e) => e.currentTarget.style.opacity = '0.6'}
>
<Trash2 size={16} />
</button>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '1.25rem', marginBottom: '1.5rem' }}>
<div style={{
width: '56px',
height: '56px',
borderRadius: '16px',
background: isAchieved ? 'rgba(16, 185, 129, 0.15)' : `${goal.color}15`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: isAchieved ? '#10b981' : goal.color,
fontSize: '1.5rem',
boxShadow: `0 4px 12px ${isAchieved ? 'rgba(16, 185, 129, 0.2)' : goal.color + '20'}`
}}>
{isAchieved ? '✨' : <TrendingUp size={28} />}
</div>
<div>
<h4 style={{ margin: 0, fontSize: '1.2rem', fontWeight: 'bold' }}>{goal.name}</h4>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', fontSize: '0.85rem', color: 'var(--text-muted)', marginTop: '0.2rem' }}>
<Calendar size={14} />
<span>Target: {goal.target_date || 'Ongoing'}</span>
</div>
</div>
</div>
<div style={{ marginBottom: '1.5rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '0.6rem', fontSize: '0.95rem' }}>
<span style={{ color: 'var(--text-muted)' }}>Progress</span>
<span style={{ fontWeight: '700', color: isAchieved ? '#10b981' : 'white' }}>
{goal.progress_percentage.toFixed(0)}%
</span>
</div>
<div style={{ height: '10px', background: 'rgba(255,255,255,0.05)', borderRadius: '5px', overflow: 'hidden' }}>
<div style={{
height: '100%',
width: `${goal.progress_percentage}%`,
background: isAchieved
? 'linear-gradient(90deg, #10b981, #34d399)'
: `linear-gradient(90deg, ${goal.color}, ${goal.color}cc)`,
borderRadius: '5px',
transition: 'width 1s cubic-bezier(0.34, 1.56, 0.64, 1)',
boxShadow: isAchieved ? '0 0 15px rgba(16, 185, 129, 0.4)' : `0 0 10px ${goal.color}40`
}} />
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '0.6rem', fontSize: '0.85rem' }}>
<span style={{ color: 'var(--text-muted)' }}>Current: {currencySymbol}{goal.current_amount.toLocaleString()}</span>
<span style={{ color: 'var(--text-muted)' }}>Goal: {currencySymbol}{goal.target_amount.toLocaleString()}</span>
</div>
</div>
{/* Personalized Feedback */}
<div style={{
padding: '1rem',
borderRadius: '1rem',
background: isAchieved ? 'rgba(16, 185, 129, 0.1)' : 'rgba(255, 255, 255, 0.03)',
border: `1px solid ${isAchieved ? '#10b98130' : 'rgba(255, 255, 255, 0.05)'}`,
textAlign: 'center',
animation: isAchieved ? 'pulse-success 2s infinite' : 'none'
}}>
{isAchieved ? (
<div style={{ color: '#10b981', fontWeight: 'bold', fontSize: '0.95rem' }}>
🎉 Congratulations! Goal Achieved!
<p style={{ margin: '0.2rem 0 0 0', fontSize: '0.8rem', opacity: 0.8, fontWeight: 'normal' }}>
You've successfully saved enough for this milestone.
</p>
</div>
) : (
<div style={{ color: 'white', fontWeight: '500', fontSize: '0.9rem' }}>
🚀 Keep going!
<p style={{ margin: '0.2rem 0 0 0', fontSize: '0.85rem', color: 'var(--text-muted)' }}>
Save <span style={{ color: '#f59e0b', fontWeight: 'bold' }}>{currencySymbol}{remaining.toLocaleString()}</span> more to reach your goal.
</p>
</div>
)}
</div>
</div>
);
})
)}
</div>
<style dangerouslySetInnerHTML={{
__html: `
@keyframes pulse-success {
0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(16, 185, 129, 0); }
100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
}
.hover-lift:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.2);
}
`}} />
</div>
);
});
export default SavingsGoalsWidget;