auto winn
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
import React, { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import {
|
||||
Wallet,
|
||||
TrendingDown,
|
||||
PiggyBank,
|
||||
AlertTriangle,
|
||||
Pencil,
|
||||
Check,
|
||||
Plus,
|
||||
ArrowUpRight,
|
||||
} from 'lucide-react';
|
||||
import ProgressBar from '../ui/ProgressBar';
|
||||
import Button from '../ui/Button';
|
||||
import { formatCurrency } from '../../utils/formatCurrency';
|
||||
|
||||
const BudgetOverview = ({
|
||||
totalBudget = 0,
|
||||
totalSpent = 0,
|
||||
remainingBudget = 0,
|
||||
percentUsed = 0,
|
||||
isOverBudget = false,
|
||||
currency = 'IDR',
|
||||
onUpdateTotalBudget,
|
||||
onOpenAddExpense,
|
||||
}) => {
|
||||
const [isEditingBudget, setIsEditingBudget] = useState(false);
|
||||
const [budgetInput, setBudgetInput] = useState(totalBudget.toString());
|
||||
|
||||
const handleSaveBudget = (e) => {
|
||||
e.preventDefault();
|
||||
const val = Number(budgetInput) || 0;
|
||||
onUpdateTotalBudget(val);
|
||||
setIsEditingBudget(false);
|
||||
};
|
||||
|
||||
const getRemainingStatus = () => {
|
||||
if (isOverBudget) {
|
||||
return { text: 'Over Budget!', color: 'var(--color-danger)', bg: 'var(--color-danger-bg)' };
|
||||
}
|
||||
if (percentUsed >= 80) {
|
||||
return { text: 'Mendekati Batas', color: 'var(--color-warning)', bg: 'var(--color-warning-bg)' };
|
||||
}
|
||||
return { text: 'Aman & Terkendali', color: 'var(--color-success)', bg: 'var(--color-success-bg)' };
|
||||
};
|
||||
|
||||
const remainingStatus = getRemainingStatus();
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: 'var(--space-8)' }}>
|
||||
{/* 3 Overview Stat Cards Grid */}
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))',
|
||||
gap: 'var(--space-4)',
|
||||
marginBottom: 'var(--space-6)',
|
||||
}}
|
||||
>
|
||||
{/* Card 1: Total Budget */}
|
||||
<div
|
||||
className="glass"
|
||||
style={{
|
||||
padding: 'var(--space-5) var(--space-6)',
|
||||
borderRadius: 'var(--radius-xl)',
|
||||
border: '1px solid var(--color-border)',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 'var(--space-2)' }}>
|
||||
<div style={{ fontSize: 'var(--text-xs)', textTransform: 'uppercase', color: 'var(--color-text-muted)', fontWeight: 700, letterSpacing: '0.04em' }}>
|
||||
Target Total Budget
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setBudgetInput(totalBudget.toString());
|
||||
setIsEditingBudget(!isEditingBudget);
|
||||
}}
|
||||
style={{
|
||||
color: 'var(--color-primary-light)',
|
||||
backgroundColor: 'hsla(220, 90%, 56%, 0.15)',
|
||||
padding: '4px 8px',
|
||||
borderRadius: 'var(--radius-md)',
|
||||
fontSize: '11px',
|
||||
fontWeight: 600,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
}}
|
||||
>
|
||||
<Pencil size={12} /> {isEditingBudget ? 'Tutup' : 'Ubah'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isEditingBudget ? (
|
||||
<form onSubmit={handleSaveBudget} style={{ display: 'flex', gap: 'var(--space-2)', marginTop: 'var(--space-2)' }}>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="100000"
|
||||
className="input-field"
|
||||
value={budgetInput}
|
||||
onChange={(e) => setBudgetInput(e.target.value)}
|
||||
autoFocus
|
||||
style={{ padding: 'var(--space-2) var(--space-3)', fontSize: 'var(--text-sm)' }}
|
||||
/>
|
||||
<Button type="submit" variant="primary" size="sm" icon={Check}>
|
||||
Simpan
|
||||
</Button>
|
||||
</form>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: 'var(--text-2xl)',
|
||||
fontWeight: 800,
|
||||
color: '#ffffff',
|
||||
fontFamily: 'var(--font-display)',
|
||||
letterSpacing: '-0.02em',
|
||||
}}
|
||||
>
|
||||
{formatCurrency(totalBudget, currency)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ fontSize: 'var(--text-xs)', color: 'var(--color-text-secondary)', marginTop: 'var(--space-2)' }}>
|
||||
Alokasi dana keseluruhan trip
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card 2: Total Spent */}
|
||||
<div
|
||||
className="glass"
|
||||
style={{
|
||||
padding: 'var(--space-5) var(--space-6)',
|
||||
borderRadius: 'var(--radius-xl)',
|
||||
border: '1px solid var(--color-border)',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 'var(--space-2)' }}>
|
||||
<div style={{ fontSize: 'var(--text-xs)', textTransform: 'uppercase', color: 'var(--color-text-muted)', fontWeight: 700, letterSpacing: '0.04em' }}>
|
||||
Total Terpakai
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'hsla(38, 95%, 55%, 0.15)',
|
||||
color: 'var(--color-accent-light)',
|
||||
padding: '2px 8px',
|
||||
borderRadius: 'var(--radius-full)',
|
||||
fontSize: '11px',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{percentUsed}% Terpakai
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
fontSize: 'var(--text-2xl)',
|
||||
fontWeight: 800,
|
||||
color: 'var(--color-accent-light)',
|
||||
fontFamily: 'var(--font-display)',
|
||||
letterSpacing: '-0.02em',
|
||||
}}
|
||||
>
|
||||
{formatCurrency(totalSpent, currency)}
|
||||
</div>
|
||||
|
||||
<div style={{ fontSize: 'var(--text-xs)', color: 'var(--color-text-secondary)', marginTop: 'var(--space-2)' }}>
|
||||
Pengeluaran yang tercatat saat ini
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card 3: Remaining Budget */}
|
||||
<div
|
||||
className="glass"
|
||||
style={{
|
||||
padding: 'var(--space-5) var(--space-6)',
|
||||
borderRadius: 'var(--radius-xl)',
|
||||
border: isOverBudget ? '1px solid var(--color-danger)' : '1px solid var(--color-border)',
|
||||
boxShadow: isOverBudget ? '0 0 20px hsla(0, 75%, 55%, 0.25)' : 'none',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 'var(--space-2)' }}>
|
||||
<div style={{ fontSize: 'var(--text-xs)', textTransform: 'uppercase', color: 'var(--color-text-muted)', fontWeight: 700, letterSpacing: '0.04em' }}>
|
||||
Sisa Budget
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: remainingStatus.bg,
|
||||
color: remainingStatus.color,
|
||||
padding: '2px 8px',
|
||||
borderRadius: 'var(--radius-full)',
|
||||
fontSize: '11px',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{remainingStatus.text}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
fontSize: 'var(--text-2xl)',
|
||||
fontWeight: 800,
|
||||
color: isOverBudget ? 'var(--color-danger)' : 'var(--color-success)',
|
||||
fontFamily: 'var(--font-display)',
|
||||
letterSpacing: '-0.02em',
|
||||
}}
|
||||
>
|
||||
{formatCurrency(remainingBudget, currency)}
|
||||
</div>
|
||||
|
||||
<div style={{ fontSize: 'var(--text-xs)', color: 'var(--color-text-secondary)', marginTop: 'var(--space-2)' }}>
|
||||
{isOverBudget ? 'Melebihi alokasi anggaran!' : 'Sisa saldo yang dapat dibelanjakan'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress & Alert Bar */}
|
||||
<div
|
||||
className="glass-dark"
|
||||
style={{
|
||||
padding: 'var(--space-5) var(--space-6)',
|
||||
borderRadius: 'var(--radius-xl)',
|
||||
border: '1px solid var(--color-border)',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 'var(--space-3)' }}>
|
||||
<div style={{ fontSize: 'var(--text-sm)', fontWeight: 600, color: 'var(--color-text-primary)' }}>
|
||||
Persentase Anggaran Terpakai
|
||||
</div>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
icon={Plus}
|
||||
onClick={onOpenAddExpense}
|
||||
>
|
||||
Tambah Pengeluaran
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<ProgressBar
|
||||
value={percentUsed}
|
||||
showPercent={false}
|
||||
height={10}
|
||||
variant={isOverBudget ? 'danger' : percentUsed >= 80 ? 'warning' : 'primary'}
|
||||
/>
|
||||
|
||||
{/* Warning Banner if Over Budget */}
|
||||
{isOverBudget && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 5 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
style={{
|
||||
marginTop: 'var(--space-4)',
|
||||
padding: 'var(--space-3) var(--space-4)',
|
||||
borderRadius: 'var(--radius-md)',
|
||||
backgroundColor: 'var(--color-danger-bg)',
|
||||
border: '1px solid hsla(0, 75%, 55%, 0.3)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 'var(--space-3)',
|
||||
color: 'var(--color-danger)',
|
||||
fontSize: 'var(--text-xs)',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
<AlertTriangle size={16} flexShrink={0} />
|
||||
<span>
|
||||
Perhatian: Pengeluaran Anda melebihi target anggaran sebesar{' '}
|
||||
<strong>{formatCurrency(Math.abs(remainingBudget), currency)}</strong>. Evaluasi kembali pos belanja Anda.
|
||||
</span>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BudgetOverview;
|
||||
Reference in New Issue
Block a user