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)

setName(e.target.value)} />
setPin(e.target.value)} />
{error && (

{error}

)}
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).

Restore (Buka File)
)} {/* Modal Tambah Tagihan */} {isModalOpen && (

Tambah Tagihan

setNewBill({...newBill, title: e.target.value})} />
setNewBill({...newBill, amount: e.target.value})} />
{ setNewBill({ ...newBill, isRecurring: e.target.checked, currentTenor: e.target.checked ? '' : newBill.currentTenor, totalTenor: e.target.checked ? '' : newBill.totalTenor }) }} />
{!newBill.isRecurring && (
setNewBill({...newBill, currentTenor: e.target.value})} />
setNewBill({...newBill, totalTenor: e.target.value})} />
)}
setNewBill({...newBill, dueDate: e.target.value})} />
)}
); }