import { Ionicons } from '@expo/vector-icons'; import DateTimePicker from '@react-native-community/datetimepicker'; import { LinearGradient } from 'expo-linear-gradient'; import { useNavigation, useRoute } from '@react-navigation/native'; import type { RouteProp } from '@react-navigation/native'; import { useCallback, useEffect, useState } from 'react'; import { Alert, Image, KeyboardAvoidingView, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native'; import { colors } from '../theme/colors'; import { formatRibuan, formatRupiah } from '../utils/format'; import { getCategoryIcon3d } from '../utils/categoryIcons3d'; import { getCategories, getSettingNumber, getTransactionById, updateTransaction, } from '../db/repositories'; import type { Category, TransactionType } from '../types'; import type { RootStackParamList } from '../navigation'; export function EditTransactionScreen() { const navigation = useNavigation(); const route = useRoute>(); const { transactionId } = route.params; const [type, setType] = useState('expense'); const [amountText, setAmountText] = useState(''); const [categories, setCategories] = useState([]); const [selectedCategoryId, setSelectedCategoryId] = useState(null); const [note, setNote] = useState(''); const [occurredAt, setOccurredAt] = useState(new Date()); const [showDate, setShowDate] = useState(false); const [percent, setPercent] = useState(10); const [saving, setSaving] = useState(false); const [loaded, setLoaded] = useState(false); useEffect(() => { (async () => { const tx = await getTransactionById(transactionId); if (!tx) { Alert.alert('Transaksi tidak ditemukan', 'Data mungkin sudah dihapus.'); navigation.goBack(); return; } setType(tx.type); setAmountText(String(tx.amount)); setSelectedCategoryId(tx.categoryId); setNote(tx.note); setOccurredAt(new Date(tx.occurredAt)); const p = await getSettingNumber('auto_save_percent', 10); setPercent(p); const cats = await getCategories(tx.type); setCategories(cats); setLoaded(true); })(); }, [transactionId, navigation]); const amount = Number(amountText.replace(/[^0-9]/g, '')) || 0; const savedAmount = type === 'income' ? Math.round((amount * percent) / 100) : 0; const switchType = (t: TransactionType) => { setType(t); loadCategories(t); }; const loadCategories = useCallback(async (t: TransactionType) => { const cats = await getCategories(t); setCategories(cats); }, []); const handleSave = async () => { if (amount <= 0) { Alert.alert('Nominal kosong', 'Isi nominal dulu ya, Mas.'); return; } setSaving(true); try { await updateTransaction(transactionId, { type, amount, categoryId: selectedCategoryId, note: note.trim(), occurredAt, }); Alert.alert('Tersimpan ✅', 'Transaksi berhasil diperbarui.', [ { text: 'Oke', onPress: () => navigation.goBack() }, ]); } catch (e) { Alert.alert('Gagal menyimpan', String(e)); } finally { setSaving(false); } }; if (!loaded) return null; return ( {/* Toggle tipe */} switchType('income')} > + Pemasukan switchType('expense')} > - Pengeluaran {/* Nominal */} NOMINAL setAmountText(t.replace(/[^0-9]/g, ''))} placeholder="Rp 0" placeholderTextColor={colors.border} keyboardType="number-pad" autoFocus /> {type === 'income' && amount > 0 && ( {formatRupiah(savedAmount)} otomatis masuk Tabungan ({percent}%) )} {/* Tanggal */} Tanggal setShowDate(true)}> {occurredAt.toLocaleDateString('id-ID', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric', })} {showDate && ( { setShowDate(false); if (d) setOccurredAt(d); }} /> )} {/* Kategori */} Kategori {categories.map((c) => { const selected = c.id === selectedCategoryId; const icon3d = getCategoryIcon3d(c.name, type); return ( setSelectedCategoryId(c.id)} > {icon3d ? ( ) : ( {c.icon} )} {c.name} ); })} {/* Catatan */} Catatan (opsional) {/* Tombol Simpan fixed */} [styles.savePressable, pressed && styles.pressed]} > {saving ? 'Menyimpan...' : 'Simpan Perubahan'} ); } const styles = StyleSheet.create({ flex: { flex: 1, backgroundColor: colors.background }, container: { flex: 1 }, content: { padding: 16, paddingBottom: 32 }, toggleRow: { flexDirection: 'row', backgroundColor: colors.surface, borderRadius: 14, borderWidth: 1, borderColor: colors.border, padding: 4, marginBottom: 12, }, toggleBtn: { flex: 1, alignItems: 'center', paddingVertical: 10, borderRadius: 10, }, toggleIncome: { backgroundColor: colors.incomeSoft }, toggleExpense: { backgroundColor: colors.expenseSoft }, toggleText: { fontSize: 14, color: colors.textMuted }, card: { backgroundColor: colors.surface, borderRadius: 16, padding: 16, borderWidth: 1, borderColor: colors.border, }, amountCard: { marginBottom: 12, overflow: 'hidden', }, amountIncome: { borderColor: '#BBF7D0' }, amountExpense: { borderColor: '#FECACA' }, amountCoin: { position: 'absolute', right: -14, top: -4, width: 114, height: 115, opacity: 0.3, transform: [{ perspective: 600 }, { rotateY: '-20deg' }], }, cardLabel: { fontSize: 11, fontWeight: '700', letterSpacing: 1, color: colors.textMuted, }, amountInput: { fontSize: 34, fontWeight: '800', color: colors.text, paddingVertical: 4, }, dateRow: { flexDirection: 'row', alignItems: 'center', gap: 8, backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 14, paddingHorizontal: 14, paddingVertical: 12, marginBottom: 4, }, dateRowText: { flex: 1, fontSize: 14, color: colors.text, fontWeight: '600' }, saveHintCard: { flexDirection: 'row', alignItems: 'center', gap: 8, backgroundColor: colors.primarySoft, borderColor: colors.primarySoft, marginBottom: 12, }, saveHintCoin: { width: 26, height: 26 }, saveHintText: { color: colors.primaryDark, fontWeight: '600', fontSize: 13, flex: 1 }, sectionTitle: { fontSize: 14, fontWeight: '700', color: colors.text, marginBottom: 8, marginTop: 8, }, categoryGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 }, categoryChip: { flexDirection: 'row', alignItems: 'center', gap: 6, backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 12, paddingHorizontal: 12, paddingVertical: 8, }, categoryChipSelected: { borderColor: colors.primary, backgroundColor: colors.primarySoft, }, categoryIcon: { fontSize: 16 }, categoryIcon3d: { width: 22, height: 22 }, categoryName: { fontSize: 13, color: colors.text }, noteInput: { paddingVertical: 10, fontSize: 14, color: colors.text }, saveBar: { padding: 16, backgroundColor: colors.background, borderTopWidth: 1, borderTopColor: colors.border, }, savePressable: { borderRadius: 14, shadowColor: colors.primaryDark, shadowOpacity: 0.25, shadowRadius: 8, shadowOffset: { width: 0, height: 4 }, elevation: 4, }, saveButton: { backgroundColor: colors.primary, borderRadius: 14, paddingVertical: 15, alignItems: 'center', }, saveButtonText: { color: colors.white, fontWeight: '700', fontSize: 16 }, pressed: { opacity: 0.85 }, });