import { Ionicons } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import { useNavigation } 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 } from '../utils/format'; import { getCategoryIcon3d } from '../utils/categoryIcons3d'; import { addRecurring, getCategories } from '../db/repositories'; import type { Category, TransactionType } from '../types'; export function RecurringFormScreen() { const navigation = useNavigation(); const [type, setType] = useState('expense'); const [amountText, setAmountText] = useState(''); const [dayText, setDayText] = useState('1'); const [categories, setCategories] = useState([]); const [selectedCategoryId, setSelectedCategoryId] = useState(null); const [note, setNote] = useState(''); const [saving, setSaving] = useState(false); const loadCategories = useCallback(async (t: TransactionType) => { const cats = await getCategories(t); setCategories(cats); setSelectedCategoryId(cats[0]?.id ?? null); }, []); useEffect(() => { loadCategories(type); }, [loadCategories, type]); const amount = Number(amountText.replace(/[^0-9]/g, '')) || 0; const dayOfMonth = Math.min(31, Math.max(1, Number(dayText.replace(/[^0-9]/g, '')) || 1)); const switchType = (t: TransactionType) => { setType(t); loadCategories(t); }; const handleSave = async () => { if (amount <= 0) { Alert.alert('Nominal kosong', 'Isi nominal dulu ya, Mas.'); return; } setSaving(true); try { await addRecurring({ type, amount, categoryId: selectedCategoryId, note: note.trim(), dayOfMonth, }); Alert.alert('Tersimpan ✅', 'Transaksi berulang aktif. Sisih akan dibuat otomatis tiap bulan.', [ { text: 'Oke', onPress: () => navigation.goBack() }, ]); } catch (e) { Alert.alert('Gagal menyimpan', String(e)); } finally { setSaving(false); } }; return ( 🔁 Transaksi ini akan dibuat otomatis setiap bulan pada tanggal yang ditentukan. {/* 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" /> {/* Tanggal */} Tanggal setiap bulan setDayText(t.replace(/[^0-9]/g, ''))} placeholder="1" placeholderTextColor={colors.border} keyboardType="number-pad" maxLength={2} /> {dayOfMonth > 28 ? 'Tanggal > 28 hanya aktif di bulan yang punya tanggal itu' : 'Setiap bulan pada tanggal ini'} {/* 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) [styles.savePressable, pressed && styles.pressed]} > {saving ? 'Menyimpan...' : 'Simpan'} ); } const styles = StyleSheet.create({ flex: { flex: 1, backgroundColor: colors.background }, container: { flex: 1 }, content: { padding: 16, paddingBottom: 32 }, infoText: { fontSize: 13, color: colors.textMuted, lineHeight: 19, marginBottom: 12, }, 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 }, sectionTitle: { fontSize: 14, fontWeight: '700', color: colors.text, marginBottom: 8, marginTop: 8, }, dayCard: { flexDirection: 'row', alignItems: 'center', gap: 10, paddingVertical: 12, }, dayInput: { fontSize: 22, fontWeight: '800', color: colors.text, minWidth: 44, borderBottomWidth: 2, borderBottomColor: colors.primary, paddingVertical: 2, textAlign: 'center', }, dayHint: { flex: 1, fontSize: 11, color: colors.textMuted, lineHeight: 15 }, 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 }, });