Skip to content

Commit

Permalink
Feature: add edit transaction and wallet feature
Browse files Browse the repository at this point in the history
  • Loading branch information
jerameel committed May 11, 2022
1 parent a42eaa7 commit ee1ee1e
Show file tree
Hide file tree
Showing 23 changed files with 700 additions and 5 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<img src="fastlane/metadata/android/en-US/images/featureGraphic.png"
alt="Get it on F-Droid"
>

# Sushi - Personal Finance

![Android Build Status](https://github.com/jerameel/sushi/workflows/android%20build%20pipeline/badge.svg)
Expand All @@ -13,4 +17,5 @@ A personal finance management app built with react-native.
height="80">](https://play.google.com/store/apps/details?id=com.jerameeldelosreyes.sushi)

## Development

This app is built using `react-native`, make sure to have it setup to get started.
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ android {
applicationId "com.jerameeldelosreyes.sushi"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 5
versionName "1.4"
versionCode 6
versionName "1.5"
}
splits {
abi {
Expand Down
1 change: 1 addition & 0 deletions fastlane/metadata/android/en-US/changelogs/5.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add support for editing wallet and transaction
25 changes: 25 additions & 0 deletions src/components/base/SVG/Edit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from 'react';
import Svg, { Path } from 'react-native-svg';
import { SVGProps } from './props';

const Edit = (props: SVGProps) => {
const { height, width, fill } = props;

// https://react-svgr.com/playground/?native=true&typescript=true
// Paste converted svg below
return (
<Svg
aria-hidden="true"
width={width || '1em'}
height={height || '1em'}
viewBox="0 0 32 32"
{...props}>
<Path
fill={fill || 'currentColor'}
d="M2 26h28v2H2zM25.4 9c.8-.8.8-2 0-2.8l-3.6-3.6c-.8-.8-2-.8-2.8 0l-15 15V24h6.4l15-15zm-5-5L24 7.6l-3 3L17.4 7l3-3zM6 22v-3.6l10-10 3.6 3.6-10 10H6z"
/>
</Svg>
);
};

export default Edit;
1 change: 1 addition & 0 deletions src/components/base/SVG/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* PLOP_INJECT_IMPORT */
export { default as Edit } from './Edit';
export { default as Close } from './Close';
export { default as Settings } from './Settings';
export { default as Delete } from './Delete';
Expand Down
50 changes: 50 additions & 0 deletions src/screens/EditTransaction/container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from 'store';
import { editTransactionAction, Transaction } from 'store/transactions';

import {
EditTransactionPrivateProps,
EditTransactionPublicProps,
} from './props';
import EditTransactionView from './view';

const EditTransactionContainer = (props: EditTransactionPublicProps) => {
const dispatch = useDispatch();
const transactionId = props.route.params?.transactionId || '';

const wallets = useSelector((state: RootState) => state.wallets);
const transactions = useSelector((state: RootState) => state.transactions);
const transaction = transactions[transactionId];

if (!transaction) {
props.navigation.goBack();
}

const editTransaction = (payload: Transaction) => {
// TODO: Improve validation structure
const isTransfer = payload.category.toUpperCase() === 'TRANSFER';
if (
payload.category &&
payload.sourceWalletId &&
((isTransfer &&
payload.destinationWalletId &&
payload.destinationWalletId !== payload.sourceWalletId) ||
!isTransfer)
) {
dispatch(editTransactionAction(payload));
props.navigation.goBack();
}
};

const generatedProps: EditTransactionPrivateProps = {
wallets,
transactions,
transaction,
editTransaction,
};

return <EditTransactionView {...props} {...generatedProps} />;
};

export default EditTransactionContainer;
3 changes: 3 additions & 0 deletions src/screens/EditTransaction/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import EditTransactionScreen from './container';

export default EditTransactionScreen;
18 changes: 18 additions & 0 deletions src/screens/EditTransaction/props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { Transaction, Transactions } from 'store/transactions';
import { Wallets } from 'store/wallets';
import { MainStackParamList } from 'types/Route';

export interface EditTransactionPublicProps
extends NativeStackScreenProps<MainStackParamList, 'EDIT_TRANSACTION'> {}

export interface EditTransactionPrivateProps {
transaction: Transaction;
wallets: Wallets;
transactions: Transactions;
editTransaction: (payload: Transaction) => void;
}

export interface EditTransactionProps
extends EditTransactionPublicProps,
EditTransactionPrivateProps {}
82 changes: 82 additions & 0 deletions src/screens/EditTransaction/styles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { StyleSheet } from 'react-native';
import { useSelector } from 'react-redux';
import { RootState } from 'store';
import { getGlobalStyles, COLORS } from 'theme';

const useStyles = () => {
const theme = useSelector((state: RootState) => state.theme);
const colors = COLORS[theme.base];
const STYLES = getGlobalStyles(theme.base);
const styles = StyleSheet.create({
container: STYLES.CONTAINER,
header: STYLES.HEADER,
headerTitleContainer: {
marginLeft: 8,
},
headerBackAction: {
width: 40,
height: 40,
justifyContent: 'center',
alignItems: 'flex-start',
},
content: {
flex: 1,
},
contentScroll: {
padding: 16,
},
inputContainer: {
marginTop: 16,
},
categorySuggestionsContainer: {
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap',
marginTop: 4,
},
categorySuggestionBadge: {
paddingVertical: 4,
paddingHorizontal: 8,
backgroundColor: colors.AREA_HIGHLIGHT,
borderRadius: 9,
borderWidth: 1,
borderColor: colors.BORDER,
marginRight: 4,
marginTop: 4,
},
categorySuggestionText: {
color: colors.PRIMARY,
},
transactionTypeContainer: {
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap',
marginTop: 4,
},
transactionTypeBadge: {
width: 32,
height: 32,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.AREA_HIGHLIGHT,
borderRadius: 9,
borderWidth: 1,
borderColor: colors.BORDER,
marginRight: 4,
marginTop: 4,
},
transactionTypeBadgeSelected: {
borderColor: colors.PRIMARY,
},
transactionTypeText: {
color: colors.PRIMARY,
fontSize: 18,
},
actionsContainer: {
padding: 16,
},
});
return { styles, colors, theme };
};

export default useStyles;
57 changes: 57 additions & 0 deletions src/screens/EditTransaction/transforms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import groupBy from 'ramda/es/groupBy';
import sortBy from 'ramda/es/sortBy';
import { Transaction, Transactions } from 'store/transactions';
import { Wallets } from 'store/wallets';

export const formatCategory = (category: string) => {
return category
.toLowerCase()
.split(' ')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
};

export const toWalletOptions = (wallets: Wallets) => {
return Object.keys(wallets).map((key) => {
const wallet = wallets[key];
return {
label: wallet.label,
value: wallet.id,
};
});
};

export const getCategorySuggestions = (transactions: Transactions) => {
const transactionsArray = Object.keys(transactions).map((key) => {
const transaction = transactions[key];
return transaction;
});
const groupByCategoryName = groupBy(
(transaction: Transaction) => transaction.category,
);

const groupedByCategoryTransactions = groupByCategoryName(transactionsArray);

const countedCategoryArray = Object.keys(groupedByCategoryTransactions).map(
(category) => {
const transactionCount = groupedByCategoryTransactions[category].length;
return {
category,
count: transactionCount,
};
},
);

const sortByCount = sortBy(
(countedCategory: { category: string; count: number }) =>
-countedCategory.count,
);

const sortedCategories = sortByCount(countedCategoryArray).map(
(countedCategory) => countedCategory.category,
);

const defaultCategories = ['Transfer'];

return [...new Set([...defaultCategories, ...sortedCategories])].slice(0, 8);
};
Loading

0 comments on commit ee1ee1e

Please sign in to comment.