fixing frist

This commit is contained in:
23412026_fahrulfahmi 2026-04-18 15:46:38 +07:00
parent 1ec97b320e
commit dbb4abc326
9 changed files with 664 additions and 234 deletions

View File

@ -1,33 +1,47 @@
import { Tabs } from 'expo-router';
import React from 'react';
import { HapticTab } from '@/components/haptic-tab';
import { IconSymbol } from '@/components/ui/icon-symbol';
import { Colors } from '@/constants/theme';
import { useColorScheme } from '@/hooks/use-color-scheme';
import { Tabs } from "expo-router";
import { Ionicons } from "@expo/vector-icons";
export default function TabLayout() {
const colorScheme = useColorScheme();
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
headerShown: false,
tabBarButton: HapticTab,
}}>
tabBarActiveTintColor: "#2563EB",
tabBarInactiveTintColor: "#9CA3AF",
tabBarStyle: {
height: 65,
paddingBottom: 8,
paddingTop: 8,
},
}}
>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />,
title: "Home",
tabBarIcon: ({ color, size }) => (
<Ionicons name="home" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="explore"
name="history"
options={{
title: 'Explore',
tabBarIcon: ({ color }) => <IconSymbol size={28} name="paperplane.fill" color={color} />,
title: "History",
tabBarIcon: ({ color, size }) => (
<Ionicons name="time" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="profile"
options={{
title: "Profile",
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" size={size} color={color} />
),
}}
/>
</Tabs>

View File

@ -1,112 +0,0 @@
import { Image } from 'expo-image';
import { Platform, StyleSheet } from 'react-native';
import { Collapsible } from '@/components/ui/collapsible';
import { ExternalLink } from '@/components/external-link';
import ParallaxScrollView from '@/components/parallax-scroll-view';
import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';
import { IconSymbol } from '@/components/ui/icon-symbol';
import { Fonts } from '@/constants/theme';
export default function TabTwoScreen() {
return (
<ParallaxScrollView
headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
headerImage={
<IconSymbol
size={310}
color="#808080"
name="chevron.left.forwardslash.chevron.right"
style={styles.headerImage}
/>
}>
<ThemedView style={styles.titleContainer}>
<ThemedText
type="title"
style={{
fontFamily: Fonts.rounded,
}}>
Explore
</ThemedText>
</ThemedView>
<ThemedText>This app includes example code to help you get started.</ThemedText>
<Collapsible title="File-based routing">
<ThemedText>
This app has two screens:{' '}
<ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '}
<ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
</ThemedText>
<ThemedText>
The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '}
sets up the tab navigator.
</ThemedText>
<ExternalLink href="https://docs.expo.dev/router/introduction">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Android, iOS, and web support">
<ThemedText>
You can open this project on Android, iOS, and the web. To open the web version, press{' '}
<ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
</ThemedText>
</Collapsible>
<Collapsible title="Images">
<ThemedText>
For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '}
<ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for
different screen densities
</ThemedText>
<Image
source={require('@/assets/images/react-logo.png')}
style={{ width: 100, height: 100, alignSelf: 'center' }}
/>
<ExternalLink href="https://reactnative.dev/docs/images">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Light and dark mode components">
<ThemedText>
This template has light and dark mode support. The{' '}
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect
what the user&apos;s current color scheme is, and so you can adjust UI colors accordingly.
</ThemedText>
<ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Animations">
<ThemedText>
This template includes an example of an animated component. The{' '}
<ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText> component uses
the powerful{' '}
<ThemedText type="defaultSemiBold" style={{ fontFamily: Fonts.mono }}>
react-native-reanimated
</ThemedText>{' '}
library to create a waving hand animation.
</ThemedText>
{Platform.select({
ios: (
<ThemedText>
The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>{' '}
component provides a parallax effect for the header image.
</ThemedText>
),
})}
</Collapsible>
</ParallaxScrollView>
);
}
const styles = StyleSheet.create({
headerImage: {
color: '#808080',
bottom: -90,
left: -35,
position: 'absolute',
},
titleContainer: {
flexDirection: 'row',
gap: 8,
},
});

166
app/(tabs)/history.tsx Normal file
View File

@ -0,0 +1,166 @@
import { View, Text, StyleSheet, ScrollView } from "react-native";
import { Ionicons } from "@expo/vector-icons";
export default function HistoryScreen() {
const transactions = [
{
id: 1,
title: "Gaji Bulanan",
amount: "Rp 5.000.000",
type: "income",
date: "12 April 2026",
icon: "wallet",
},
{
id: 2,
title: "Makan Siang",
amount: "Rp 50.000",
type: "expense",
date: "13 April 2026",
icon: "restaurant",
},
{
id: 3,
title: "Transport",
amount: "Rp 20.000",
type: "expense",
date: "14 April 2026",
icon: "car",
},
{
id: 4,
title: "Freelance Project",
amount: "Rp 1.500.000",
type: "income",
date: "15 April 2026",
icon: "briefcase",
},
];
return (
<ScrollView style={styles.container}>
<Text style={styles.title}>Riwayat Transaksi</Text>
<Text style={styles.subtitle}>
Semua pemasukan dan pengeluaran kamu
</Text>
{transactions.map((item) => (
<View key={item.id} style={styles.card}>
<View style={styles.leftSection}>
<View
style={[
styles.iconContainer,
item.type === "income"
? styles.incomeBg
: styles.expenseBg,
]}
>
<Ionicons
name={item.icon as any}
size={22}
color={item.type === "income" ? "#16A34A" : "#DC2626"}
/>
</View>
<View>
<Text style={styles.transactionTitle}>{item.title}</Text>
<Text style={styles.transactionDate}>{item.date}</Text>
</View>
</View>
<Text
style={[
styles.amount,
item.type === "income"
? styles.incomeText
: styles.expenseText,
]}
>
{item.type === "income" ? "+" : "-"} {item.amount}
</Text>
</View>
))}
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#F5F7FA",
padding: 20,
paddingTop: 55,
},
title: {
fontSize: 30,
fontWeight: "bold",
marginBottom: 6,
},
subtitle: {
fontSize: 15,
color: "#666",
marginBottom: 28,
},
card: {
backgroundColor: "#FFFFFF",
borderRadius: 18,
padding: 18,
marginBottom: 16,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
shadowColor: "#000",
shadowOpacity: 0.05,
shadowRadius: 6,
elevation: 3,
},
leftSection: {
flexDirection: "row",
alignItems: "center",
gap: 14,
},
iconContainer: {
width: 48,
height: 48,
borderRadius: 14,
justifyContent: "center",
alignItems: "center",
},
incomeBg: {
backgroundColor: "#DCFCE7",
},
expenseBg: {
backgroundColor: "#FEE2E2",
},
transactionTitle: {
fontSize: 16,
fontWeight: "600",
marginBottom: 4,
},
transactionDate: {
fontSize: 13,
color: "#777",
},
amount: {
fontSize: 15,
fontWeight: "700",
},
incomeText: {
color: "#16A34A",
},
expenseText: {
color: "#DC2626",
},
});

View File

@ -1,98 +1,136 @@
import { Image } from 'expo-image';
import { Platform, StyleSheet } from 'react-native';
import { HelloWave } from '@/components/hello-wave';
import ParallaxScrollView from '@/components/parallax-scroll-view';
import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';
import { Link } from 'expo-router';
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
import { router } from "expo-router";
import { Ionicons } from "@expo/vector-icons";
export default function HomeScreen() {
return (
<ParallaxScrollView
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
headerImage={
<Image
source={require('@/assets/images/partial-react-logo.png')}
style={styles.reactLogo}
/>
}>
<ThemedView style={styles.titleContainer}>
<ThemedText type="title">Welcome!</ThemedText>
<HelloWave />
</ThemedView>
<ThemedView style={styles.stepContainer}>
<ThemedText type="subtitle">Step 1: Try it</ThemedText>
<ThemedText>
Edit <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> to see changes.
Press{' '}
<ThemedText type="defaultSemiBold">
{Platform.select({
ios: 'cmd + d',
android: 'cmd + m',
web: 'F12',
})}
</ThemedText>{' '}
to open developer tools.
</ThemedText>
</ThemedView>
<ThemedView style={styles.stepContainer}>
<Link href="/modal">
<Link.Trigger>
<ThemedText type="subtitle">Step 2: Explore</ThemedText>
</Link.Trigger>
<Link.Preview />
<Link.Menu>
<Link.MenuAction title="Action" icon="cube" onPress={() => alert('Action pressed')} />
<Link.MenuAction
title="Share"
icon="square.and.arrow.up"
onPress={() => alert('Share pressed')}
/>
<Link.Menu title="More" icon="ellipsis">
<Link.MenuAction
title="Delete"
icon="trash"
destructive
onPress={() => alert('Delete pressed')}
/>
</Link.Menu>
</Link.Menu>
</Link>
<View style={styles.container}>
<Text style={styles.title}>Tabungan H.Hadi</Text>
<Text style={styles.subtitle}>Kelola pemasukan & pengeluaranmu untuk nikah </Text>
<ThemedText>
{`Tap the Explore tab to learn more about what's included in this starter app.`}
</ThemedText>
</ThemedView>
<ThemedView style={styles.stepContainer}>
<ThemedText type="subtitle">Step 3: Get a fresh start</ThemedText>
<ThemedText>
{`When you're ready, run `}
<ThemedText type="defaultSemiBold">npm run reset-project</ThemedText> to get a fresh{' '}
<ThemedText type="defaultSemiBold">app</ThemedText> directory. This will move the current{' '}
<ThemedText type="defaultSemiBold">app</ThemedText> to{' '}
<ThemedText type="defaultSemiBold">app-example</ThemedText>.
</ThemedText>
</ThemedView>
</ParallaxScrollView>
<View style={styles.balanceCard}>
<Text style={styles.balanceLabel}>Total Saldo</Text>
<Text style={styles.balanceAmount}>Rp 500.000.000.000</Text>
</View>
<View style={styles.summaryContainer}>
<View style={styles.summaryCard}>
<Ionicons name="arrow-down-circle" size={30} color="#16A34A" />
<Text style={styles.summaryTitle}>Income</Text>
<Text style={styles.summaryAmount}>Rp 7.000.000</Text>
</View>
<View style={styles.summaryCard}>
<Ionicons name="arrow-up-circle" size={30} color="#DC2626" />
<Text style={styles.summaryTitle}>Expense</Text>
<Text style={styles.summaryAmount}>Rp 2.000.000</Text>
</View>
</View>
<TouchableOpacity
style={styles.buttonIncome}
onPress={() => router.push("/add-income")}
>
<Text style={styles.buttonText}>+ Tambah Pemasukan</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.buttonExpense}
onPress={() => router.push("/add-expense")}
>
<Text style={styles.buttonText}>- Tambah Pengeluaran</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
titleContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
container: {
flex: 1,
backgroundColor: "#F5F7FA",
padding: 24,
paddingTop: 60,
},
stepContainer: {
gap: 8,
title: {
fontSize: 30,
fontWeight: "bold",
marginBottom: 8,
},
reactLogo: {
height: 178,
width: 290,
bottom: 0,
left: 0,
position: 'absolute',
subtitle: {
fontSize: 16,
color: "#666",
marginBottom: 28,
},
balanceCard: {
backgroundColor: "#2563EB",
borderRadius: 20,
padding: 24,
marginBottom: 24,
},
balanceLabel: {
color: "#E0E7FF",
fontSize: 16,
marginBottom: 8,
},
balanceAmount: {
color: "#FFFFFF",
fontSize: 32,
fontWeight: "bold",
},
summaryContainer: {
flexDirection: "row",
justifyContent: "space-between",
marginBottom: 32,
},
summaryCard: {
backgroundColor: "#FFFFFF",
width: "48%",
padding: 20,
borderRadius: 16,
alignItems: "center",
shadowColor: "#000",
shadowOpacity: 0.05,
shadowRadius: 6,
elevation: 3,
},
summaryTitle: {
marginTop: 10,
fontSize: 15,
color: "#666",
},
summaryAmount: {
marginTop: 6,
fontSize: 16,
fontWeight: "600",
},
buttonIncome: {
backgroundColor: "#16A34A",
paddingVertical: 16,
borderRadius: 14,
alignItems: "center",
marginBottom: 16,
},
buttonExpense: {
backgroundColor: "#DC2626",
paddingVertical: 16,
borderRadius: 14,
alignItems: "center",
},
buttonText: {
color: "#FFFFFF",
fontSize: 16,
fontWeight: "600",
},
});

137
app/(tabs)/profile.tsx Normal file
View File

@ -0,0 +1,137 @@
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
import { Ionicons } from "@expo/vector-icons";
export default function ProfileScreen() {
return (
<View style={styles.container}>
{/* Header */}
<Text style={styles.title}>Profile</Text>
<Text style={styles.subtitle}>Informasi pengguna aplikasi</Text>
{/* Profile Card */}
<View style={styles.profileCard}>
<View style={styles.avatar}>
<Ionicons name="person" size={40} color="#FFFFFF" />
</View>
<Text style={styles.name}>Fahrul</Text>
<Text style={styles.email}>fahrul@email.com</Text>
</View>
{/* Info Card */}
<View style={styles.infoCard}>
<View style={styles.infoRow}>
<Ionicons name="wallet-outline" size={22} color="#2563EB" />
<Text style={styles.infoText}>Aplikasi: Hitung Duit</Text>
</View>
<View style={styles.infoRow}>
<Ionicons name="phone-portrait-outline" size={22} color="#2563EB" />
<Text style={styles.infoText}>Versi: 1.0.0</Text>
</View>
<View style={styles.infoRow}>
<Ionicons name="school-outline" size={22} color="#2563EB" />
<Text style={styles.infoText}>Project Tugas Mobile Programming</Text>
</View>
</View>
{/* Logout Button */}
<TouchableOpacity style={styles.logoutButton}>
<Ionicons name="log-out-outline" size={20} color="#FFFFFF" />
<Text style={styles.logoutText}>Logout</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#F5F7FA",
padding: 24,
paddingTop: 60,
},
title: {
fontSize: 30,
fontWeight: "bold",
marginBottom: 6,
},
subtitle: {
fontSize: 15,
color: "#666",
marginBottom: 28,
},
profileCard: {
backgroundColor: "#2563EB",
borderRadius: 20,
padding: 28,
alignItems: "center",
marginBottom: 24,
},
avatar: {
width: 80,
height: 80,
borderRadius: 40,
backgroundColor: "rgba(255,255,255,0.25)",
justifyContent: "center",
alignItems: "center",
marginBottom: 16,
},
name: {
fontSize: 24,
fontWeight: "bold",
color: "#FFFFFF",
marginBottom: 6,
},
email: {
fontSize: 14,
color: "#DBEAFE",
},
infoCard: {
backgroundColor: "#FFFFFF",
borderRadius: 18,
padding: 20,
marginBottom: 28,
shadowColor: "#000",
shadowOpacity: 0.05,
shadowRadius: 6,
elevation: 3,
},
infoRow: {
flexDirection: "row",
alignItems: "center",
gap: 12,
marginBottom: 18,
},
infoText: {
fontSize: 15,
color: "#333",
flex: 1,
},
logoutButton: {
backgroundColor: "#DC2626",
paddingVertical: 16,
borderRadius: 14,
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
gap: 10,
},
logoutText: {
color: "#FFFFFF",
fontSize: 16,
fontWeight: "600",
},
});

View File

@ -1,24 +1,16 @@
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import 'react-native-reanimated';
import { useColorScheme } from '@/hooks/use-color-scheme';
export const unstable_settings = {
anchor: '(tabs)',
};
import { Stack } from "expo-router";
import { StatusBar } from "expo-status-bar";
export default function RootLayout() {
const colorScheme = useColorScheme();
return (
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="modal" options={{ presentation: 'modal', title: 'Modal' }} />
<>
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="(tabs)" />
<Stack.Screen name="add-income" />
<Stack.Screen name="add-expense" />
</Stack>
<StatusBar style="auto" />
</ThemeProvider>
</>
);
}

97
app/add-expense.tsx Normal file
View File

@ -0,0 +1,97 @@
import { useState } from "react";
import {
View,
Text,
TextInput,
StyleSheet,
TouchableOpacity,
} from "react-native";
import { router } from "expo-router";
export default function AddExpenseScreen() {
const [amount, setAmount] = useState("");
const formatRupiah = (value: string) => {
const numberString = value.replace(/[^,\d]/g, "");
const split = numberString.split(",");
const sisa = split[0].length % 3;
let rupiah = split[0].substring(0, sisa);
const ribuan = split[0].substring(sisa).match(/\d{3}/gi);
if (ribuan) {
const separator = sisa ? "." : "";
rupiah += separator + ribuan.join(".");
}
return rupiah ? `Rp ${rupiah}` : "";
};
const handleAmountChange = (value: string) => {
setAmount(formatRupiah(value));
};
return (
<View style={styles.container}>
<Text style={styles.title}>Tambah Pengeluaran</Text>
<TextInput
placeholder="Nama pengeluaran"
style={styles.input}
/>
<TextInput
placeholder="Jumlah uang"
keyboardType="numeric"
value={amount}
onChangeText={handleAmountChange}
style={styles.input}
/>
<TouchableOpacity
style={styles.button}
onPress={() => router.back()}
>
<Text style={styles.buttonText}>Simpan</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
backgroundColor: "#fff",
justifyContent: "center",
},
title: {
fontSize: 28,
fontWeight: "bold",
marginBottom: 24,
textAlign: "center",
},
input: {
borderWidth: 1,
borderColor: "#ddd",
padding: 16,
borderRadius: 12,
marginBottom: 16,
fontSize: 16,
},
button: {
backgroundColor: "#DC2626",
padding: 16,
borderRadius: 12,
alignItems: "center",
},
buttonText: {
color: "#fff",
fontWeight: "600",
fontSize: 16,
},
});

97
app/add-income.tsx Normal file
View File

@ -0,0 +1,97 @@
import { useState } from "react";
import {
View,
Text,
TextInput,
StyleSheet,
TouchableOpacity,
} from "react-native";
import { router } from "expo-router";
export default function AddIncomeScreen() {
const [amount, setAmount] = useState("");
const formatRupiah = (value: string) => {
const numberString = value.replace(/[^,\d]/g, "");
const split = numberString.split(",");
const sisa = split[0].length % 3;
let rupiah = split[0].substring(0, sisa);
const ribuan = split[0].substring(sisa).match(/\d{3}/gi);
if (ribuan) {
const separator = sisa ? "." : "";
rupiah += separator + ribuan.join(".");
}
return rupiah ? `Rp ${rupiah}` : "";
};
const handleAmountChange = (value: string) => {
setAmount(formatRupiah(value));
};
return (
<View style={styles.container}>
<Text style={styles.title}>Tambah Pemasukan</Text>
<TextInput
placeholder="Nama pemasukan"
style={styles.input}
/>
<TextInput
placeholder="Jumlah uang"
keyboardType="numeric"
value={amount}
onChangeText={handleAmountChange}
style={styles.input}
/>
<TouchableOpacity
style={styles.button}
onPress={() => router.back()}
>
<Text style={styles.buttonText}>Simpan</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
backgroundColor: "#fff",
justifyContent: "center",
},
title: {
fontSize: 28,
fontWeight: "bold",
marginBottom: 24,
textAlign: "center",
},
input: {
borderWidth: 1,
borderColor: "#ddd",
padding: 16,
borderRadius: 12,
marginBottom: 16,
fontSize: 16,
},
button: {
backgroundColor: "#16A34A",
padding: 16,
borderRadius: 12,
alignItems: "center",
},
buttonText: {
color: "#fff",
fontWeight: "600",
fontSize: 16,
},
});

1
package-lock.json generated
View File

@ -5811,6 +5811,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",