Kantongku: fitur pilih tanggal saat catat & edit transaksi (backdate)
This commit is contained in:
@@ -35,7 +35,8 @@
|
|||||||
"resizeMode": "contain"
|
"resizeMode": "contain"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"expo-font"
|
"expo-font",
|
||||||
|
"@react-native-community/datetimepicker"
|
||||||
],
|
],
|
||||||
"extra": {
|
"extra": {
|
||||||
"eas": {
|
"eas": {
|
||||||
|
|||||||
Generated
+24
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user