{/* Investment info box */}
{type==='investment' && (
Cara pakai: Isi "Modal" = total uang yang pernah kamu masukkan.
Isi "Nilai sekarang" = harga pasar terkini. App hitung P&L otomatis.
Update "Nilai sekarang" secara berkala (saham: bulanan, kripto: mingguan).
)}
{/* Icon + Color */}
Icon
{ICONS.map(function(ic){
return ;
})}
Warna
{COLORS.map(function(col){
return ;
})}
{/* Nama */}
Nama Akun
{/* Institusi */}
Bank / Institusi (opsional)
{/* Modal investasi */}
{type==='investment' && (
Modal (cost basis) — total uang yang dimasukkan
Rp
)}
{/* Saldo / current value */}
{balanceLabel}
Rp
{/* Live P&L preview for investment */}
{(function(){
if (type!=='investment'||!costBasis||!balance) return null;
var cb = parseInt(costBasis.replace(/\D/g,''),10)||0;
var cur = parseInt(balance.replace(/\D/g,''),10)||0;
if (cb===0) return null;
var pnl = cur - cb;
var pct = cb > 0 ? ((pnl/cb)*100).toFixed(1) : 0;
return (
{autoFreq==='weekly'?'Pilih 1 hari dalam seminggu:':'Pilih hari-hari tertentu:'}
{['Sen','Sel','Rab','Kam','Jum','Sab','Min'].map(function(day){
var on = autoDays.indexOf(day)!==-1;
return ;
})}
)}
Rp
{autoFreq==='daily'?'/hari':autoFreq==='weekly'?'/minggu':autoFreq==='custom'?'/hari':'/bulan'}
{!!monthlyAuto && (function(){
var amt = parseInt(monthlyAuto.replace(/\D/g,'')||'0',10)||0;
var est = autoFreq==='daily'?amt*30:autoFreq==='weekly'?amt*4:autoFreq==='custom'?amt*(autoDays.length||1)*4:amt*12;
var lbl = autoFreq==='daily'?'est. /bulan':autoFreq==='weekly'?'est. /bulan':autoFreq==='custom'?'est. /bulan':'est. /tahun';
return
{fmtRp(est,{short:true})} {lbl}
;
})()}
Pengingat saja — kamu konfirmasi sendiri setiap periode
{/* Link ke rekening */}
{accounts && accounts.filter(function(a){return a.type!=='credit';}).length > 0 && (
Linked ke rekening (opsional)
{goalAccountId &&
Saldo rekening ini akan dikurangi sebesar goal terkumpul (virtual pocket)
);
}
// ─── PROFILE ─────────────────────────────────────────────────────
function ScreenProfile({ theme, accent, auth }) {
var T = theme;
var _s1=React.useState(false),editing=_s1[0],setEditing=_s1[1];
var _s2=React.useState((auth&&auth.profile&&auth.profile.full_name)||''),name=_s2[0],setName=_s2[1];
var _s3=React.useState(false),saving=_s3[0],setSaving=_s3[1];
var _s4=React.useState(''),msg=_s4[0],setMsg=_s4[1];
var _s5=React.useState((auth&&auth.profile&&auth.profile.birth_date)||''),birthDate=_s5[0],setBirthDate=_s5[1];
var _s6=React.useState(false),editingBirth=_s6[0],setEditingBirth=_s6[1];
var profile = auth&&auth.profile;
// Hitung usia dari birth_date
var age = null;
if (birthDate) {
var bd = new Date(birthDate);
var now = new Date();
age = now.getFullYear() - bd.getFullYear() -
(now.getMonth() < bd.getMonth() || (now.getMonth()===bd.getMonth() && now.getDate()
{initial}
{editing ? (
) : (
{(profile&&profile.full_name)||'Kamu'}
)}
{auth&&auth.user&&auth.user.email}
{msg&&
{msg}
}
{/* Birth date field — editable */}
Tanggal Lahir
{editingBirth ? (
) : (
{birthDate
? <>{new Date(birthDate).toLocaleDateString('id-ID',{day:'numeric',month:'long',year:'numeric'})}{age} tahun>
: Belum diisi — tap untuk isi
}
✏️
)}
{!birthDate && (
⚠️ Isi tanggal lahir agar usia di FIRE Calculator otomatis terkunci
);
}
// ─── SECURITY ────────────────────────────────────────────────────
function ScreenSecurity({ theme, accent, auth }) {
var T = theme;
var _s1=React.useState(''),np=_s1[0],setNp=_s1[1];
var _s2=React.useState(''),cp=_s2[0],setCp=_s2[1];
var _s3=React.useState(false),saving=_s3[0],setSaving=_s3[1];
var _s4=React.useState(''),msg=_s4[0],setMsg=_s4[1];
async function handleChange() {
if(np.length<6){setMsg('Minimal 6 karakter');return;}
if(np!==cp){setMsg('Password tidak cocok');return;}
setSaving(true);
var r = await window.SB.auth.updateUser({password:np});
setSaving(false);
if(r.error){setMsg('Error: '+r.error.message);return;}
setMsg('Password berhasil diubah ✓'); setNp(''); setCp('');
setTimeout(function(){setMsg('');},3000);
}
return (
);
}
// ─── EXPORT ──────────────────────────────────────────────────────
function ScreenExport({ theme, accent, auth }) {
var T = theme;
var _s1=React.useState(false),loading=_s1[0],setLoading=_s1[1];
var _s2=React.useState(''),msg=_s2[0],setMsg=_s2[1];
async function exportCSV() {
if(!auth||!auth.family||!window.SB) return;
setLoading(true);
var r = await window.SB.from('transactions').select('date,type,amount,note,account,category:categories(name)')
.eq('family_id',auth.family.id).order('date',{ascending:false});
setLoading(false);
if(r.error){setMsg('Error: '+r.error.message);return;}
var rows = r.data||[];
var csv = 'Tanggal,Tipe,Jumlah,Kategori,Catatan,Akun\n';
rows.forEach(function(t){
csv += [t.date,t.type,t.amount,(t.category&&t.category.name)||'',(t.note||'').replace(/,/g,' '),t.account||''].join(',')+'\n';
});
var blob = new Blob([csv],{type:'text/csv;charset=utf-8;'});
var a = document.createElement('a');
a.href=URL.createObjectURL(blob);
a.download='indiecash-'+new Date().toISOString().split('T')[0]+'.csv';
a.click();
setMsg(rows.length+' transaksi diekspor ✓');
setTimeout(function(){setMsg('');},3000);
}
return (
Export Data
Download semua data transaksi
📊 Export CSV
Semua transaksi dalam format spreadsheet (Excel / Google Sheets)
{msg&&
{msg}
}
);
}
// ─── HELP ────────────────────────────────────────────────────────
function ScreenHelp({ theme, accent }) {
var T = theme;
var _s1=React.useState(null),open=_s1[0],setOpen=_s1[1];
var faqs = [
{q:'Cara tambah transaksi?',a:'Tap tombol + hijau di tengah navbar. Pilih tipe, masukkan nominal, pilih kategori, lalu Simpan.'},
{q:'Cara undang anggota keluarga?',a:'Buka Lainnya → Akun saya. Salin Kode Undangan dan bagikan ke anggota keluarga.'},
{q:'Apa itu FIRE Calculator?',a:'FIRE (Financial Independence, Retire Early) membantu kamu menghitung kapan bisa pensiun berdasarkan tabungan dan pengeluaran.'},
{q:'Bagaimana pocket/envelope budgeting bekerja?',a:'Ketika kamu membuat savings goal dan menghubungkannya ke rekening bank, saldo "tersedia" di rekening itu akan dikurangi sebesar jumlah yang sudah terkumpul di goal tersebut. Contoh: BCA 2.2jt, ada goal Jalan-jalan 800rb yang linked → BCA tersedia = 1.4jt.'},
{q:'Bagaimana pencatatan investasi saham/kripto?',a:'Tambah akun tipe Investasi. Isi "Modal" = total uang yang pernah kamu masukkan. Isi "Nilai sekarang" = harga pasar saat ini. App akan menghitung P&L (profit/loss) otomatis. Update nilai secara berkala sesuai frekuensi yang kamu mau.'},
{q:'Bagaimana scan struk bekerja?',a:'Buka Lainnya → Scan Struk. Foto atau upload gambar struk, AI akan otomatis membaca nominal dan kategori.'},
{q:'Apakah data saya aman?',a:'Data disimpan dengan Row Level Security di Supabase — hanya anggota keluarga yang bisa mengakses data kamu.'},
];
return (
Bantuan
FAQ & cara penggunaan INDIECASH
{faqs.map(function(faq,i){
return (
{faq.q}
⌄
{open===i&&(
{faq.a}
)}
);
})}
💬 Butuh bantuan lebih?
📧 hello@indiecash.id
);
}
function UpdateValModal({ theme, accent, account, onClose, onSave }) {
var T = theme;
var _s1=React.useState(account.balance?account.balance.toLocaleString('id-ID'):''),newVal=_s1[0],setNewVal=_s1[1];
var _s2=React.useState(false),saving=_s2[0],setSaving=_s2[1];
var currentVal = account.balance || 0;
var newValNum = parseInt((newVal||'').replace(/\D/g,''),10) || 0;
var pnlChange = newValNum - currentVal;
var totalPnl = newValNum - (account.cost_basis||0);
async function handleSave() {
if (!newValNum || saving) return;
setSaving(true);
await window.SB.from('accounts').update({
balance: newValNum,
last_updated: new Date().toISOString().split('T')[0],
}).eq('id', account.id);
window.dispatchEvent(new Event('tx-added'));
onSave && onSave();
onClose();
}
return (