Files
Ready-to-Planner-Tour/src/components/budget/BudgetOverview.jsx
T
2026-08-27 19:45:15 +07:00

282 lines
9.5 KiB
React

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;