fixing frist bug

This commit is contained in:
25412011 2026-04-18 15:51:11 +07:00
parent 86c425298c
commit 68cc0d0798
7 changed files with 238 additions and 53 deletions

View File

@ -0,0 +1,94 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { COLORS, FONTS } from '../constants/theme';
import { BalanceInfo } from '../types';
import { formatRupiah } from '../utils/helpers';
interface BalanceCardProps {
balance: BalanceInfo;
}
export const BalanceCard: React.FC<BalanceCardProps> = ({ balance }) => {
return (
<View style={styles.container}>
<View style={styles.row}>
<View style={styles.col}>
<Text style={styles.label}>Pemasukan</Text>
<Text style={[styles.amount, styles.income]}>
+{formatRupiah(balance.income)}
</Text>
</View>
<View style={styles.col}>
<Text style={styles.label}>Pengeluaran</Text>
<Text style={[styles.amount, styles.expense]}>
-{formatRupiah(balance.expense)}
</Text>
</View>
</View>
<View style={styles.divider} />
<View style={styles.totalContainer}>
<Text style={styles.totalLabel}>Total Saldo</Text>
<Text style={styles.totalAmount}>
{formatRupiah(
balance.total
)}
</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: COLORS.card,
margin: 16,
padding: 20,
borderRadius: 12,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
},
col: {
flex: 1,
alignItems: 'center',
},
label: {
fontSize: FONTS.small,
color: COLORS.textSecondary,
marginBottom: 4,
},
amount: {
fontSize: FONTS.regular,
fontWeight: '600',
},
income: {
color: COLORS.income,
},
expense: {
color: COLORS.expense,
},
divider: {
height: 1,
backgroundColor: COLORS.border,
marginVertical: 16,
},
totalContainer: {
alignItems: 'center',
},
totalLabel: {
fontSize: FONTS.small,
color: COLORS.textSecondary,
marginBottom: 4,
},
totalAmount: {
fontSize: FONTS.xlarge,
fontWeight: 'bold',
color: COLORS.text,
},
});

29
components/header.tsx Normal file
View File

@ -0,0 +1,29 @@
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { COLORS, FONTS } from '../constants/theme';
interface HeaderProps {
title: string;
}
export const Header: React.FC<HeaderProps> = ({ title }) => {
return (
<View style={styles.container}>
<Text style={styles.title}>{title}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: COLORS.primary,
padding: 20,
paddingTop: 50,
alignItems: 'center',
},
title: {
fontSize: FONTS.large,
fontWeight: 'bold',
color: COLORS.card,
},
});

View File

@ -1,53 +1,19 @@
/**
* Below are the colors that are used in the app. The colors are defined in the light and dark mode.
* There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc.
*/
export const COLORS = {
income: '#4CAF50',
expense: '#F44336',
background: '#F5F5F5',
card: '#FFFFFF',
text: '#333333',
textSecondary: '#666666',
border: '#E0E0E0',
primary: '#2196F3',
} as const;
import { Platform } from 'react-native';
export const FONTS = {
regular: 16,
small: 14,
large: 24,
xlarge: 32,
} as const;
const tintColorLight = '#0a7ea4';
const tintColorDark = '#fff';
export const Colors = {
light: {
text: '#11181C',
background: '#fff',
tint: tintColorLight,
icon: '#687076',
tabIconDefault: '#687076',
tabIconSelected: tintColorLight,
},
dark: {
text: '#ECEDEE',
background: '#151718',
tint: tintColorDark,
icon: '#9BA1A6',
tabIconDefault: '#9BA1A6',
tabIconSelected: tintColorDark,
},
};
export const Fonts = Platform.select({
ios: {
/** iOS `UIFontDescriptorSystemDesignDefault` */
sans: 'system-ui',
/** iOS `UIFontDescriptorSystemDesignSerif` */
serif: 'ui-serif',
/** iOS `UIFontDescriptorSystemDesignRounded` */
rounded: 'ui-rounded',
/** iOS `UIFontDescriptorSystemDesignMonospaced` */
mono: 'ui-monospace',
},
default: {
sans: 'normal',
serif: 'serif',
rounded: 'normal',
mono: 'monospace',
},
web: {
sans: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
serif: "Georgia, 'Times New Roman', serif",
rounded: "'SF Pro Rounded', 'Hiragino Maru Gothic ProN', Meiryo, 'MS PGothic', sans-serif",
mono: "SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
},
});
14

34
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "1.0.0",
"dependencies": {
"@expo/vector-icons": "^15.0.3",
"@react-native-async-storage/async-storage": "2.2.0",
"@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8",
@ -2797,6 +2798,18 @@
}
}
},
"node_modules/@react-native-async-storage/async-storage": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz",
"integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==",
"license": "MIT",
"dependencies": {
"merge-options": "^3.0.4"
},
"peerDependencies": {
"react-native": "^0.0.0-0 || >=0.65 <1.0"
}
},
"node_modules/@react-native/assets-registry": {
"version": "0.81.5",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.5.tgz",
@ -7855,6 +7868,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-plain-obj": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@ -8833,6 +8855,18 @@
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
"license": "MIT"
},
"node_modules/merge-options": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz",
"integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
"license": "MIT",
"dependencies": {
"is-plain-obj": "^2.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",

View File

@ -12,6 +12,7 @@
},
"dependencies": {
"@expo/vector-icons": "^15.0.3",
"@react-native-async-storage/async-storage": "2.2.0",
"@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8",
@ -31,11 +32,11 @@
"react-dom": "19.1.0",
"react-native": "0.81.5",
"react-native-gesture-handler": "~2.28.0",
"react-native-worklets": "0.5.1",
"react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-web": "~0.21.0"
"react-native-web": "~0.21.0",
"react-native-worklets": "0.5.1"
},
"devDependencies": {
"@types/react": "~19.1.0",

16
types/index.ts Normal file
View File

@ -0,0 +1,16 @@
export interface Transaction {
id: string;
amount: number;
description: string;
type: 'income' | 'expense';
category: string;
date: string;
}
export interface BalanceInfo {
total: number;
income: number;
expense: number;
}

45
utils/helpers.ts Normal file
View File

@ -0,0 +1,45 @@
import { Transaction, BalanceInfo } from '../types';
export const formatRupiah = (amount: number): string => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}).format(amount);
};
export const formatDate = (dateString: string): string => {
const date = new Date(dateString);
return new Intl.DateTimeFormat('id-ID', {
day: 'numeric',
month: 'short',
year: 'numeric',
}).format(date);
};
export const calculateBalance = (transactions: Transaction[]): BalanceInfo => {
const income = transactions
.filter((t) => t.type === 'income')
.reduce((sum, t) => sum + t.amount, 0);
const expense = transactions
.filter((t) => t.type === 'expense')
.reduce((sum, t) => sum + t.amount, 0);
return {
total: income - expense,
income,
expense,
};
};
export const generateId = (): any => {
return
Date.now
().toString(36) + Math.random().toString(36).substr(2);
};
export const getCurrentDate = (): string => {
return new Date().toISOString();
};