import React, { useState, useEffect, useMemo } from 'react';
import {
Plus,
Trash2,
CheckCircle,
XCircle,
PieChart,
List,
Calendar,
Wallet,
AlertCircle,
TrendingUp,
History,
CreditCard,
Repeat,
Bell,
LogOut,
User,
Lock,
ArrowRight,
Settings,
Download,
Upload
} from 'lucide-react';
// --- KOMPONEN LOGIN SCREEN ---
const LoginScreen = ({ onLogin }) => {
const [name, setName] = useState('');
const [pin, setPin] = useState('');
const [error, setError] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (!name.trim()) {
setError('Nama harus diisi');
return;
}
if (pin.length < 4) {
setError('PIN minimal 4 karakter');
return;
}
onLogin(name);
};
return (
Selamat Datang
Silakan masuk untuk mengelola keuangan (Offline)
DompetKu v1.0 • Data Tersimpan di Browser
);
};
// --- KOMPONEN UTAMA (APP) ---
export default function App() {
// State Login
const [user, setUser] = useState(() => {
const savedUser = localStorage.getItem('dompetKuUser');
return savedUser ? JSON.parse(savedUser) : null;
});
// State Data Tagihan
const [bills, setBills] = useState(() => {
const saved = localStorage.getItem('myBills');
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
return saved ? JSON.parse(saved) : [
{
id: 1,
title: 'Internet IndiHome',
amount: 350000,
dueDate: tomorrow.toISOString().split('T')[0],
category: 'Internet',
status: 'unpaid',
isRecurring: true
},
{ id: 2, title: 'Cicilan HP', amount: 1500000, dueDate: '2023-10-05', category: 'Cicilan', status: 'paid', currentTenor: 3, totalTenor: 12 },
];
});
const [view, setView] = useState('dashboard');
const [isModalOpen, setIsModalOpen] = useState(false);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [filter, setFilter] = useState('all');
// State Form Input
const [newBill, setNewBill] = useState({
title: '',
amount: '',
dueDate: '',
category: 'Umum',
currentTenor: '',
totalTenor: '',
isRecurring: false
});
// Effects (Simpan otomatis ke LocalStorage)
useEffect(() => {
localStorage.setItem('myBills', JSON.stringify(bills));
}, [bills]);
useEffect(() => {
if (user) {
localStorage.setItem('dompetKuUser', JSON.stringify(user));
} else {
localStorage.removeItem('dompetKuUser');
}
}, [user]);
// Auth Handlers
const handleLogin = (userName) => {
setUser({ name: userName });
};
const handleLogout = () => {
if (window.confirm('Apakah Anda yakin ingin keluar?')) {
setUser(null);
}
};
// --- FEATURE: BACKUP & RESTORE ---
const handleBackup = () => {
const dataToSave = {
user: user,
bills: bills,
backupDate: new Date().toISOString()
};
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(dataToSave));
const downloadAnchorNode = document.createElement('a');
downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute("download", `DompetKu_Backup_${new Date().toISOString().split('T')[0]}.json`);
document.body.appendChild(downloadAnchorNode);
downloadAnchorNode.click();
downloadAnchorNode.remove();
setIsSettingsOpen(false);
};
const handleRestore = (event) => {
const fileReader = new FileReader();
const file = event.target.files[0];
if (!file) return;
fileReader.readAsText(file, "UTF-8");
fileReader.onload = (e) => {
try {
const parsedData = JSON.parse(e.target.result);
if (parsedData.bills && Array.isArray(parsedData.bills)) {
if (window.confirm(`Ditemukan backup tanggal ${parsedData.backupDate ? parsedData.backupDate.split('T')[0] : 'Unknown'}. Timpa data saat ini?`)) {
setBills(parsedData.bills);
// Opsional: Restore user name juga
// if (parsedData.user) setUser(parsedData.user);
alert("Data berhasil dipulihkan!");
setIsSettingsOpen(false);
}
} else {
alert("Format file tidak valid.");
}
} catch (error) {
alert("Gagal membaca file backup.");
}
};
};
// Helpers
const formatRupiah = (number) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(number);
};
const formatDate = (dateString) => {
const options = { day: 'numeric', month: 'long', year: 'numeric' };
return new Date(dateString).toLocaleDateString('id-ID', options);
};
const getDaysDiff = (dateString) => {
const today = new Date();
today.setHours(0, 0, 0, 0);
const due = new Date(dateString);
due.setHours(0, 0, 0, 0);
const diffTime = due - today;
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
};
// Logic Notifikasi
const upcomingBills = useMemo(() => {
return bills.filter(bill => {
if (bill.status === 'paid') return false;
const days = getDaysDiff(bill.dueDate);
return days >= 0 && days <= 3;
});
}, [bills]);
// Logic Statistik
const stats = useMemo(() => {
const totalBills = bills.reduce((acc, curr) => acc + curr.amount, 0);
const totalPaid = bills.filter(b => b.status === 'paid').reduce((acc, curr) => acc + curr.amount, 0);
const totalUnpaid = bills.filter(b => b.status === 'unpaid').reduce((acc, curr) => acc + curr.amount, 0);
const categoryBreakdown = bills.reduce((acc, curr) => {
acc[curr.category] = (acc[curr.category] || 0) + curr.amount;
return acc;
}, {});
return { totalBills, totalPaid, totalUnpaid, categoryBreakdown };
}, [bills]);
// Logic Filter
const filteredBills = bills.filter(bill => {
if (filter === 'all') return true;
return bill.status === filter;
}).sort((a, b) => new Date(a.dueDate) - new Date(b.dueDate));
// CRUD Handlers (Lokal Array Operation)
const handleAddBill = (e) => {
e.preventDefault();
if (!newBill.title || !newBill.amount || !newBill.dueDate) return;
const bill = {
id: Date.now(),
title: newBill.title,
amount: parseFloat(newBill.amount),
dueDate: newBill.dueDate,
category: newBill.category,
status: 'unpaid',
isRecurring: newBill.isRecurring,
currentTenor: !newBill.isRecurring && newBill.currentTenor ? parseInt(newBill.currentTenor) : null,
totalTenor: !newBill.isRecurring && newBill.totalTenor ? parseInt(newBill.totalTenor) : null
};
setBills([bill, ...bills]);
setNewBill({ title: '', amount: '', dueDate: '', category: 'Umum', currentTenor: '', totalTenor: '', isRecurring: false });
setIsModalOpen(false);
};
const handleDelete = (id) => {
if (window.confirm('Yakin ingin menghapus tagihan ini?')) {
setBills(bills.filter(b => b.id !== id));
}
};
const toggleStatus = (id) => {
setBills(bills.map(b => b.id === id ? { ...b, status: b.status === 'paid' ? 'unpaid' : 'paid' } : b));
};
// --- RENDER UTAMA ---
if (!user) {
return ;
}
return (
{/* Header Aplikasi */}
{/* Dekorasi Background Header */}
DompetKu
Halo, {user.name}
{/* Kartu Ringkasan Atas */}
Total Harus Dibayar
{formatRupiah(stats.totalUnpaid)}
Sudah Dibayar
{formatRupiah(stats.totalPaid)}
{/* Konten Utama */}
{/* Notifikasi H-3 */}
{upcomingBills.length > 0 && (
Segera Jatuh Tempo
{upcomingBills.map(bill => {
const days = getDaysDiff(bill.dueDate);
let label = '';
let colorClass = 'text-orange-700';
if (days === 0) { label = 'HARI INI!'; colorClass = 'text-red-600 font-extrabold'; }
else if (days === 1) { label = 'Besok'; colorClass = 'text-orange-700 font-bold'; }
else { label = `${days} hari lagi`; }
return (
{bill.title}
{formatRupiah(bill.amount)}
{label}
)
})}
)}
{/* Navigasi Tab */}
{view === 'dashboard' ? (
<>
{['all', 'unpaid', 'paid'].map((f) => (
))}
{filteredBills.length === 0 ? (
Tidak ada tagihan yang ditemukan.
) : (
filteredBills.map(bill => (
{bill.category}
{bill.totalTenor && (
{bill.currentTenor} / {bill.totalTenor}
)}
{bill.isRecurring && (
Bulanan
)}
{bill.status === 'unpaid' && getDaysDiff(bill.dueDate) < 0 && (
Terlambat
)}
{bill.title}
{formatDate(bill.dueDate)}
{formatRupiah(bill.amount)}
))
)}
>
) : (
Ringkasan Pengeluaran
Total Tagihan{formatRupiah(stats.totalBills)}
Sisa Kewajiban{formatRupiah(stats.totalUnpaid)}
Progress Pembayaran{stats.totalBills > 0 ? Math.round((stats.totalPaid / stats.totalBills) * 100) : 0}%
0 ? (stats.totalPaid / stats.totalBills) * 100 : 0}%` }}>
Per Kategori
{Object.entries(stats.categoryBreakdown).map(([cat, amount]) => (
{cat}{formatRupiah(amount)}
))}
)}
{/* Modal Settings / Data */}
{isSettingsOpen && (
Pengaturan
Backup & Restore
Gunakan fitur ini untuk memindahkan data ke perangkat lain (misal: Laptop ke HP).
)}
{/* Modal Tambah Tagihan */}
{isModalOpen && (
Tambah Tagihan
)}
);
}