fixing frist bug
This commit is contained in:
parent
86c425298c
commit
68cc0d0798
94
components/BalanceCard.tsx
Normal file
94
components/BalanceCard.tsx
Normal 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
29
components/header.tsx
Normal 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,
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -1,53 +1,19 @@
|
|||||||
/**
|
export const COLORS = {
|
||||||
* Below are the colors that are used in the app. The colors are defined in the light and dark mode.
|
income: '#4CAF50',
|
||||||
* 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.
|
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';
|
14
|
||||||
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",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
34
package-lock.json
generated
34
package-lock.json
generated
@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^15.0.3",
|
"@expo/vector-icons": "^15.0.3",
|
||||||
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
"@react-navigation/bottom-tabs": "^7.4.0",
|
"@react-navigation/bottom-tabs": "^7.4.0",
|
||||||
"@react-navigation/elements": "^2.6.3",
|
"@react-navigation/elements": "^2.6.3",
|
||||||
"@react-navigation/native": "^7.1.8",
|
"@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": {
|
"node_modules/@react-native/assets-registry": {
|
||||||
"version": "0.81.5",
|
"version": "0.81.5",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.5.tgz",
|
"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"
|
"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": {
|
"node_modules/is-regex": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
"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==",
|
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/merge-stream": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^15.0.3",
|
"@expo/vector-icons": "^15.0.3",
|
||||||
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
"@react-navigation/bottom-tabs": "^7.4.0",
|
"@react-navigation/bottom-tabs": "^7.4.0",
|
||||||
"@react-navigation/elements": "^2.6.3",
|
"@react-navigation/elements": "^2.6.3",
|
||||||
"@react-navigation/native": "^7.1.8",
|
"@react-navigation/native": "^7.1.8",
|
||||||
@ -31,11 +32,11 @@
|
|||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-native": "0.81.5",
|
"react-native": "0.81.5",
|
||||||
"react-native-gesture-handler": "~2.28.0",
|
"react-native-gesture-handler": "~2.28.0",
|
||||||
"react-native-worklets": "0.5.1",
|
|
||||||
"react-native-reanimated": "~4.1.1",
|
"react-native-reanimated": "~4.1.1",
|
||||||
"react-native-safe-area-context": "~5.6.0",
|
"react-native-safe-area-context": "~5.6.0",
|
||||||
"react-native-screens": "~4.16.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": {
|
"devDependencies": {
|
||||||
"@types/react": "~19.1.0",
|
"@types/react": "~19.1.0",
|
||||||
|
|||||||
16
types/index.ts
Normal file
16
types/index.ts
Normal 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
45
utils/helpers.ts
Normal 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();
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user