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 @@
|
||||
/**
|
||||
* 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
34
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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
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