import React, { useState, useEffect, useMemo } from 'react'; import { Home, ArrowDownCircle, ArrowUpCircle, CreditCard, Settings, Bell, PieChart, Plus, Trash2, Edit2, X, ChevronRight, TrendingDown, Wallet, ShieldCheck, CheckCircle2, Globe, Clock, BarChart2, Menu, Smartphone, ArrowRight } from 'lucide-react'; const defaultCategories = ['Makanan', 'Transportasi', 'Kebutuhan', 'Hiburan', 'Lainnya']; const defaultUser = { name: 'SobatQ', pin: '123' }; const formatRupiah = (number) => { return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 }).format(number); }; const DonutChart = ({ data }) => { const total = data.reduce((sum, item) => sum + item.value, 0); if (total === 0) return
Belum ada data
; let cumulativePercent = 0; return (
{data.map((slice, i) => { const percent = (slice.value / total) * 100; const strokeDasharray = `${percent} ${100 - percent}`; const strokeDashoffset = 100 - cumulativePercent; cumulativePercent += percent; return ( ); })}
Total {formatRupiah(total).replace('Rp', '')}
); }; export default function App() { const [isReady, setIsReady] = useState(false); const [screen, setScreen] = useState('welcome'); // welcome, pin, main const [activeTab, setActiveTab] = useState('dashboard'); const [pin, setPin] = useState(''); const [userConfig, setUserConfig] = useState(defaultUser); const [transactions, setTransactions] = useState([]); const [categories, setCategories] = useState(defaultCategories); const [filterMonth, setFilterMonth] = useState(new Date().getMonth()); const [filterYear, setFilterYear] = useState(new Date().getFullYear()); useEffect(() => { const savedConfig = localStorage.getItem('duweQ_config'); const savedTransactions = localStorage.getItem('duweQ_transactions'); const savedCategories = localStorage.getItem('duweQ_categories'); if (savedConfig) setUserConfig(JSON.parse(savedConfig)); if (savedTransactions) setTransactions(JSON.parse(savedTransactions)); if (savedCategories) setCategories(JSON.parse(savedCategories)); setTimeout(() => setIsReady(true), 800); }, []); useEffect(() => { if (isReady) { localStorage.setItem('duweQ_config', JSON.stringify(userConfig)); localStorage.setItem('duweQ_transactions', JSON.stringify(transactions)); localStorage.setItem('duweQ_categories', JSON.stringify(categories)); } }, [userConfig, transactions, categories, isReady]); const currentMonthTransactions = useMemo(() => { return transactions.filter(t => { const d = new Date(t.date); return d.getMonth() === filterMonth && d.getFullYear() === filterYear; }); }, [transactions, filterMonth, filterYear]); const totalIncome = currentMonthTransactions.filter(t => t.type === 'income').reduce((sum, t) => sum + t.amount, 0); const totalExpense = currentMonthTransactions.filter(t => t.type === 'expense').reduce((sum, t) => sum + t.amount, 0); const totalCredit = currentMonthTransactions.filter(t => t.type === 'credit').reduce((sum, t) => sum + t.amount, 0); const currentBalance = totalIncome - totalExpense - totalCredit; const expenseByCategory = useMemo(() => { const expenses = currentMonthTransactions.filter(t => t.type === 'expense'); const grouped = expenses.reduce((acc, curr) => { acc[curr.category] = (acc[curr.category] || 0) + curr.amount; return acc; }, {}); const colors = ['#f87171', '#fbbf24', '#34d399', '#60a5fa', '#a78bfa', '#f472b6']; return Object.keys(grouped).map((key, index) => ({ name: key, value: grouped[key], color: colors[index % colors.length] })).sort((a, b) => b.value - a.value); }, [currentMonthTransactions]); const isOverspending = (totalExpense + totalCredit) > totalIncome && totalIncome > 0; if (!isReady) { return (

duweQ

); } if (screen === 'welcome') { return (
{/* Navbar */} {/* Hero Section */}

Catat Keuanganmu,
Wujudkan Masa Depanmu

Kelola pengeluaran, pemasukan, dan kredit dengan mudah dalam satu aplikasi online.

Akses kapan saja, di mana saja melalui browser kamu.
{/* Central Mockup Area */}
{/* Floating Left Card (Pemasukan) */}

Pemasukan

Rp 8.600.000

{/* Floating Right Card (Pengeluaran) */}

Pengeluaran

Rp 3.360.000

{/* The Phone Mockup */}
setScreen('pin')} className="w-[300px] h-[620px] bg-white border-[12px] border-gray-900 rounded-[3rem] shadow-2xl relative overflow-hidden cursor-pointer hover:-translate-y-2 transition-transform duration-300 group z-10" > {/* Phone Dynamic Island / Notch */}
{/* Mockup Screen Content */}
{/* Mockup Header */}

duweQ

Ringkasan Keuangan Bulan Ini ▾
{/* Mockup Green Card */}

Saldo Saat Ini

Rp 5.240.000

Pemasukan

Rp 8.600.000

Pengeluaran

Rp 3.360.000

{/* Mockup Categories */}
Kategori Transaksi Lihat Semua >
Pemasukan
Pengeluaran
Kredit
{/* Mockup Chart */}

Total

3.3M

Kebutuhan50%
Transportasi20%
Makanan20%
{/* Mockup Bottom Nav */}
{/* Click Overlay */}
Buka Aplikasi
{/* Feature Cards Bottom (Sesuai Brosur) */}

Aman & Terpercaya

Data keuanganmu terlindungi dengan sistem keamanan tinggi.

Mudah & Cepat

Catat transaksi kapan saja, di mana saja hanya dalam hitungan detik.

Laporan Lengkap

Pantau keuanganmu dengan laporan visual yang mudah dipahami.

Pengingat Pintar

Ingatkan tagihan & cicilan tepat waktu, tidak ada yang terlewat.

); } if (screen === 'pin') { const handlePinPress = (digit) => { if (pin.length < 3) { const newPin = pin + digit; setPin(newPin); if (newPin.length === 3) { setTimeout(() => { if (newPin === userConfig.pin) { setScreen('main'); setPin(''); } else { alert('PIN Salah!'); setPin(''); } }, 300); } } }; return (

Masukkan PIN

Halo {userConfig.name}, selamat datang kembali!

{[0, 1, 2].map((i) => (
i ? 'bg-white scale-110 shadow-lg' : 'bg-green-800'}`} /> ))}
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 'C', 0, '<'].map((key) => ( ))}
); } return (
{/* Header */}

duweQ

Hai, {userConfig.name} 👋

{isOverspending && }
{/* Main Content Area */}
{/* TAB: DASHBOARD */} {activeTab === 'dashboard' && (
{/* Filter */}
{/* Warning Notif */} {isOverspending && (

Peringatan Keuangan!

Pengeluaran & cicilanmu bulan ini melebihi pendapatan.

)} {/* Balance Card 3D Effect */}

Saldo Saat Ini

{formatRupiah(currentBalance)}

Pemasukan

{formatRupiah(totalIncome)}

Pengeluaran

{formatRupiah(totalExpense + totalCredit)}

{/* Categories & Chart */}

Ringkasan Bulan Ini

{expenseByCategory.slice(0, 4).map((cat, i) => (
{cat.name}
{Math.round((cat.value / (totalExpense||1)) * 100)}%
))}
{/* Category List Clickable */}

Kategori Pengeluaran

{expenseByCategory.map((cat, i) => (
setActiveTab('expense')}>

{cat.name}

Lihat detail

{formatRupiah(cat.value)}

))} {expenseByCategory.length === 0 &&

Belum ada pengeluaran.

}
)} {/* TAB: MANAGER (Income, Expense, Credit) */} {['income', 'expense', 'credit'].includes(activeTab) && ( )} {/* TAB: SETTINGS */} {activeTab === 'settings' && (

Pengaturan

setUserConfig({...userConfig, name: e.target.value})} className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-3 text-gray-800 focus:outline-none focus:ring-2 focus:ring-green-500" />
{ const val = e.target.value.replace(/\D/g, ''); if(val.length <= 3) setUserConfig({...userConfig, pin: val}); }} className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-3 text-gray-800 focus:outline-none focus:ring-2 focus:ring-green-500" />

PIN saat ini digunakan untuk login awal.

)}
{/* Bottom Navigation */}
} label="Beranda" active={activeTab === 'dashboard'} onClick={() => setActiveTab('dashboard')} /> } label="Masuk" active={activeTab === 'income'} onClick={() => setActiveTab('income')} color="text-green-400" /> } label="Keluar" active={activeTab === 'expense'} onClick={() => setActiveTab('expense')} color="text-red-400" /> } label="Kredit" active={activeTab === 'credit'} onClick={() => setActiveTab('credit')} color="text-yellow-400" /> } label="Profil" active={activeTab === 'settings'} onClick={() => setActiveTab('settings')} />
); } function NavButton({ icon, label, active, onClick, color = "text-gray-300" }) { return ( ); } function TransactionManager({ type, transactions, setTransactions, categories, setCategories }) { const [isFormOpen, setIsFormOpen] = useState(false); const [formData, setFormData] = useState({ id: null, amount: '', note: '', date: new Date().toISOString().split('T')[0], category: categories[0] }); const [newCat, setNewCat] = useState(''); const titles = { income: { title: 'Pemasukan', icon: }, expense: { title: 'Pengeluaran', icon: }, credit: { title: 'Cicilan Kredit', icon: } }; const currentList = transactions.filter(t => t.type === type).sort((a, b) => new Date(b.date) - new Date(a.date)); const handleSave = () => { if (!formData.amount || !formData.date) return alert('Lengkapi data!'); const newTx = { ...formData, amount: parseFloat(formData.amount), type: type, id: formData.id || Date.now().toString() }; if (formData.id) { setTransactions(transactions.map(t => t.id === formData.id ? newTx : t)); } else { setTransactions([...transactions, newTx]); } setIsFormOpen(false); setFormData({ id: null, amount: '', note: '', date: new Date().toISOString().split('T')[0], category: categories[0] }); }; const handleEdit = (tx) => { setFormData(tx); setIsFormOpen(true); }; const handleDelete = (id) => { if (confirm('Hapus data ini?')) { setTransactions(transactions.filter(t => t.id !== id)); } }; const handleAddCategory = () => { if(newCat && !categories.includes(newCat)) { setCategories([...categories, newCat]); setFormData({...formData, category: newCat}); setNewCat(''); } }; return (

{titles[type].icon} {titles[type].title}

{isFormOpen && (

{formData.id ? 'Edit' : 'Tambah'} {titles[type].title}

setFormData({...formData, amount: e.target.value})} className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-3 text-lg font-bold text-gray-800 focus:ring-2 focus:ring-green-500 outline-none" placeholder="0" />
setFormData({...formData, date: e.target.value})} className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-3 text-gray-800 outline-none" />
{type === 'expense' && (
setNewCat(e.target.value)} placeholder="Kategori baru..." className="flex-1 bg-gray-50 border border-gray-200 rounded-xl px-3 py-2 text-sm outline-none" />
)}
setFormData({...formData, note: e.target.value})} className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-3 text-gray-800 outline-none" placeholder="Keterangan singkat..." />
)}
{currentList.map(tx => (

{formatRupiah(tx.amount)}

{new Date(tx.date).toLocaleDateString('id-ID', {day: 'numeric', month: 'short'})} {type === 'expense' && <>{tx.category}} {tx.note && <>{tx.note}}
))} {currentList.length === 0 && (

Belum ada data {titles[type].title.toLowerCase()}.

)}
); }