Kantongku: fitur pilih tanggal saat catat & edit transaksi (backdate)

This commit is contained in:
Bro
2026-08-16 21:07:38 +07:00
parent 3ad1c71423
commit c601a688e0
5 changed files with 116 additions and 1 deletions
+2 -1
View File
@@ -35,7 +35,8 @@
"resizeMode": "contain" "resizeMode": "contain"
} }
], ],
"expo-font" "expo-font",
"@react-native-community/datetimepicker"
], ],
"extra": { "extra": {
"eas": { "eas": {
+24
View File
@@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"@expo/vector-icons": "^15.0.2", "@expo/vector-icons": "^15.0.2",
"@react-native-async-storage/async-storage": "2.2.0", "@react-native-async-storage/async-storage": "2.2.0",
"@react-native-community/datetimepicker": "9.1.0",
"@react-navigation/bottom-tabs": "^7.18.16", "@react-navigation/bottom-tabs": "^7.18.16",
"@react-navigation/native": "^7.3.16", "@react-navigation/native": "^7.3.16",
"@react-navigation/native-stack": "^7.18.8", "@react-navigation/native-stack": "^7.18.8",
@@ -1529,6 +1530,29 @@
"react-native": "^0.0.0-0 || >=0.65 <1.0" "react-native": "^0.0.0-0 || >=0.65 <1.0"
} }
}, },
"node_modules/@react-native-community/datetimepicker": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-9.1.0.tgz",
"integrity": "sha512-eadbnk+I2vxvW30iTAsm/qlCnMMAadkifIMYNEB2lzhxN/SvlKc7S2V4k5DyrwjdCbqdcMk3t9K6fnUMcAV34w==",
"license": "MIT",
"dependencies": {
"invariant": "^2.2.4"
},
"peerDependencies": {
"expo": ">=52.0.0",
"react": "*",
"react-native": "*",
"react-native-windows": "*"
},
"peerDependenciesMeta": {
"expo": {
"optional": true
},
"react-native-windows": {
"optional": true
}
}
},
"node_modules/@react-native/assets-registry": { "node_modules/@react-native/assets-registry": {
"version": "0.86.2", "version": "0.86.2",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.86.2.tgz", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.86.2.tgz",
+1
View File
@@ -5,6 +5,7 @@
"dependencies": { "dependencies": {
"@expo/vector-icons": "^15.0.2", "@expo/vector-icons": "^15.0.2",
"@react-native-async-storage/async-storage": "2.2.0", "@react-native-async-storage/async-storage": "2.2.0",
"@react-native-community/datetimepicker": "9.1.0",
"@react-navigation/bottom-tabs": "^7.18.16", "@react-navigation/bottom-tabs": "^7.18.16",
"@react-navigation/native": "^7.3.16", "@react-navigation/native": "^7.3.16",
"@react-navigation/native-stack": "^7.18.8", "@react-navigation/native-stack": "^7.18.8",
+44
View File
@@ -1,3 +1,5 @@
import { Ionicons } from '@expo/vector-icons';
import DateTimePicker from '@react-native-community/datetimepicker';
import { LinearGradient } from 'expo-linear-gradient'; import { LinearGradient } from 'expo-linear-gradient';
import { useFocusEffect, useRoute } from '@react-navigation/native'; import { useFocusEffect, useRoute } from '@react-navigation/native';
import type { RouteProp } from '@react-navigation/native'; import type { RouteProp } from '@react-navigation/native';
@@ -40,6 +42,8 @@ export function AddTransactionScreen() {
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategoryId, setSelectedCategoryId] = useState<number | null>(null); const [selectedCategoryId, setSelectedCategoryId] = useState<number | null>(null);
const [note, setNote] = useState(''); const [note, setNote] = useState('');
const [occurredAt, setOccurredAt] = useState(new Date());
const [showDate, setShowDate] = useState(false);
const [percent, setPercent] = useState(10); const [percent, setPercent] = useState(10);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
@@ -84,6 +88,7 @@ export function AddTransactionScreen() {
amount, amount,
categoryId: selectedCategoryId, categoryId: selectedCategoryId,
note: note.trim(), note: note.trim(),
occurredAt,
}); });
if (type === 'income') { if (type === 'income') {
Alert.alert( Alert.alert(
@@ -181,6 +186,32 @@ export function AddTransactionScreen() {
</View> </View>
)} )}
{/* Tanggal */}
<Text style={styles.sectionTitle}>Tanggal</Text>
<Pressable style={styles.dateRow} onPress={() => setShowDate(true)}>
<Ionicons name="calendar" size={18} color={colors.primaryDark} />
<Text style={styles.dateRowText}>
{occurredAt.toLocaleDateString('id-ID', {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric',
})}
</Text>
<Ionicons name="chevron-down" size={16} color={colors.textMuted} />
</Pressable>
{showDate && (
<DateTimePicker
value={occurredAt}
mode="date"
maximumDate={new Date()}
onChange={(e, d) => {
setShowDate(false);
if (d) setOccurredAt(d);
}}
/>
)}
{/* Kategori */} {/* Kategori */}
<Text style={styles.sectionTitle}>Kategori</Text> <Text style={styles.sectionTitle}>Kategori</Text>
<View style={styles.categoryGrid}> <View style={styles.categoryGrid}>
@@ -297,6 +328,19 @@ const styles = StyleSheet.create({
paddingVertical: 4, paddingVertical: 4,
}, },
amountPreview: { fontSize: 13, color: colors.textMuted }, amountPreview: { fontSize: 13, color: colors.textMuted },
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: { saveHintCard: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
+45
View File
@@ -1,3 +1,5 @@
import { Ionicons } from '@expo/vector-icons';
import DateTimePicker from '@react-native-community/datetimepicker';
import { LinearGradient } from 'expo-linear-gradient'; import { LinearGradient } from 'expo-linear-gradient';
import { useNavigation, useRoute } from '@react-navigation/native'; import { useNavigation, useRoute } from '@react-navigation/native';
import type { RouteProp } from '@react-navigation/native'; import type { RouteProp } from '@react-navigation/native';
@@ -36,6 +38,8 @@ export function EditTransactionScreen() {
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategoryId, setSelectedCategoryId] = useState<number | null>(null); const [selectedCategoryId, setSelectedCategoryId] = useState<number | null>(null);
const [note, setNote] = useState(''); const [note, setNote] = useState('');
const [occurredAt, setOccurredAt] = useState(new Date());
const [showDate, setShowDate] = useState(false);
const [percent, setPercent] = useState(10); const [percent, setPercent] = useState(10);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [loaded, setLoaded] = useState(false); const [loaded, setLoaded] = useState(false);
@@ -52,6 +56,7 @@ export function EditTransactionScreen() {
setAmountText(String(tx.amount)); setAmountText(String(tx.amount));
setSelectedCategoryId(tx.categoryId); setSelectedCategoryId(tx.categoryId);
setNote(tx.note); setNote(tx.note);
setOccurredAt(new Date(tx.occurredAt));
const p = await getSettingNumber('auto_save_percent', 10); const p = await getSettingNumber('auto_save_percent', 10);
setPercent(p); setPercent(p);
const cats = await getCategories(tx.type); const cats = await getCategories(tx.type);
@@ -85,6 +90,7 @@ export function EditTransactionScreen() {
amount, amount,
categoryId: selectedCategoryId, categoryId: selectedCategoryId,
note: note.trim(), note: note.trim(),
occurredAt,
}); });
Alert.alert('Tersimpan ✅', 'Transaksi berhasil diperbarui.', [ Alert.alert('Tersimpan ✅', 'Transaksi berhasil diperbarui.', [
{ text: 'Oke', onPress: () => navigation.goBack() }, { text: 'Oke', onPress: () => navigation.goBack() },
@@ -175,6 +181,32 @@ export function EditTransactionScreen() {
</View> </View>
)} )}
{/* Tanggal */}
<Text style={styles.sectionTitle}>Tanggal</Text>
<Pressable style={styles.dateRow} onPress={() => setShowDate(true)}>
<Ionicons name="calendar" size={18} color={colors.primaryDark} />
<Text style={styles.dateRowText}>
{occurredAt.toLocaleDateString('id-ID', {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric',
})}
</Text>
<Ionicons name="chevron-down" size={16} color={colors.textMuted} />
</Pressable>
{showDate && (
<DateTimePicker
value={occurredAt}
mode="date"
maximumDate={new Date()}
onChange={(e, d) => {
setShowDate(false);
if (d) setOccurredAt(d);
}}
/>
)}
{/* Kategori */} {/* Kategori */}
<Text style={styles.sectionTitle}>Kategori</Text> <Text style={styles.sectionTitle}>Kategori</Text>
<View style={styles.categoryGrid}> <View style={styles.categoryGrid}>
@@ -290,6 +322,19 @@ const styles = StyleSheet.create({
color: colors.text, color: colors.text,
paddingVertical: 4, 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: { saveHintCard: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',