From e54a89ab623e290a24f89691e5a8bca346a84b09 Mon Sep 17 00:00:00 2001 From: Suchan Badyakar Date: Mon, 3 May 2021 17:16:27 +1000 Subject: [PATCH 1/8] Add ability to create category from record page --- ios/Podfile.lock | 6 ++++ src/components/CategoryPage/CategoryAdd.js | 7 ++++ src/components/Dashboard/RecordAddIncome.js | 10 +++++- src/components/SettingPage/index.js | 40 ++++++++++++++------- src/components/SettingPage/styles.js | 24 +++++++++++++ src/navigation.js | 3 +- 6 files changed, 76 insertions(+), 14 deletions(-) create mode 100644 src/components/SettingPage/styles.js diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a269ab5..32e922b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -244,6 +244,8 @@ PODS: - React-Core - react-native-sqlite-storage (5.0.0): - React + - react-native-viewpager (5.0.11): + - React-Core - React-RCTActionSheet (0.63.3): - React-Core/RCTActionSheetHeaders (= 0.63.3) - React-RCTAnimation (0.63.3): @@ -363,6 +365,7 @@ DEPENDENCIES: - react-native-image-picker (from `../node_modules/react-native-image-picker`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - react-native-sqlite-storage (from `../node_modules/react-native-sqlite-storage`) + - "react-native-viewpager (from `../node_modules/@react-native-community/viewpager`)" - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) @@ -436,6 +439,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-safe-area-context" react-native-sqlite-storage: :path: "../node_modules/react-native-sqlite-storage" + react-native-viewpager: + :path: "../node_modules/@react-native-community/viewpager" React-RCTActionSheet: :path: "../node_modules/react-native/Libraries/ActionSheetIOS" React-RCTAnimation: @@ -504,6 +509,7 @@ SPEC CHECKSUMS: react-native-image-picker: 5b2f1ea1f9230b131abbfb8a4aa1eb209aba9ed9 react-native-safe-area-context: 86612d2c9a9e94e288319262d10b5f93f0b395f5 react-native-sqlite-storage: ce71689c5a73b79390a1ab213555ae80979a5dc7 + react-native-viewpager: d054b58bf3d377e6b99558da6766463ff7e9b220 React-RCTActionSheet: 53ea72699698b0b47a6421cb1c8b4ab215a774aa React-RCTAnimation: 1befece0b5183c22ae01b966f5583f42e69a83c2 React-RCTBlob: 0b284339cbe4b15705a05e2313a51c6d8b51fa40 diff --git a/src/components/CategoryPage/CategoryAdd.js b/src/components/CategoryPage/CategoryAdd.js index 324fd09..823ac52 100644 --- a/src/components/CategoryPage/CategoryAdd.js +++ b/src/components/CategoryPage/CategoryAdd.js @@ -40,6 +40,13 @@ class CategoryAdd extends Component { const {categoryType} = this.props; if (params) { + const {navigateBackTo} = params; + if (navigateBackTo) { + callback = () => this.props.navigation.navigate(navigateBackTo); + this.onStateChange('type', categoryType); + return; + } + const {category} = params; const {title, type, id, icon} = category; diff --git a/src/components/Dashboard/RecordAddIncome.js b/src/components/Dashboard/RecordAddIncome.js index a9be1b5..17ebb00 100644 --- a/src/components/Dashboard/RecordAddIncome.js +++ b/src/components/Dashboard/RecordAddIncome.js @@ -74,6 +74,11 @@ class RecordAddIncome extends Component { } onStateChange(key, value) { + if (key === 'categoryId' && value === 0) { + this.props.navigation.navigate('CategoryAdd', { + navigateBackTo: 'RecordAddIncome', + }); + } this.setState({ [key]: value, }); @@ -171,7 +176,10 @@ class RecordAddIncome extends Component { accountFrom={this.state.accountFrom} accountTo={this.state.accountTo} attachment={this.state.attachment} - categories={categories} + categories={[ + ...categories, + {id: 0, title: 'Add Category', icon: 'plus', type: ''}, + ]} categoryId={categoryId} /> {this.showError(error)} diff --git a/src/components/SettingPage/index.js b/src/components/SettingPage/index.js index 88a74a3..ba824df 100644 --- a/src/components/SettingPage/index.js +++ b/src/components/SettingPage/index.js @@ -1,20 +1,36 @@ import React from 'react'; -import {StyleSheet, Text, View} from 'react-native'; + +import { + SettingsContainer, + SettingsContent, + SettingsButton, + SettingsTitle, + SettingsIcon, +} from './styles'; const SettingPage = () => { + const importDatabase = () => { + alert('import database'); + }; + + const exportDatabase = () => { + alert('Export database'); + }; + return ( - - Settings Page - + + + importDatabase()}> + + Import Database + + exportDatabase()}> + + Export Database + + + ); }; export default SettingPage; - -const styles = StyleSheet.create({ - wrapper: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, -}); diff --git a/src/components/SettingPage/styles.js b/src/components/SettingPage/styles.js new file mode 100644 index 0000000..c4c2357 --- /dev/null +++ b/src/components/SettingPage/styles.js @@ -0,0 +1,24 @@ +import styled from 'styled-components/native'; +import {Container, View, Text, Button, Icon} from 'native-base'; + +export const SettingsContainer = styled(Container)` + background-color: ${(props) => props.theme.colors.ui.primary}; +`; + +export const SettingsTitle = styled(Text)` + font-size: ${(props) => props.theme.fontSizes.h3}; + color: ${(props) => props.theme.colors.ui.secondary}; + font-weight: ${(props) => 'bold'}; +`; + +export const SettingsIcon = styled(Icon)` + font-size: ${(props) => props.theme.fontSizes.h1}; + color: ${(props) => props.theme.colors.ui.secondary}; +`; + +export const SettingsContent = styled(View)` + flex: 1; + margin-top: 16; +`; + +export const SettingsButton = styled(Button)``; diff --git a/src/navigation.js b/src/navigation.js index 4c7183b..928a7b5 100644 --- a/src/navigation.js +++ b/src/navigation.js @@ -60,6 +60,7 @@ const HomeStack = ({navigation}) => { + ); }; @@ -164,7 +165,7 @@ const SettingStack = ({navigation}) => { screenOptions={{ headerTitle: 'Settings', headerStyle: { - backgroundColor: '#f4511e', + backgroundColor: COLOR_DARK_BLUE, }, headerTintColor: '#fff', headerTitleStyle: { From dc0840c80747578002f633385987de1b0e938ed0 Mon Sep 17 00:00:00 2001 From: Suchan Badyakar Date: Mon, 3 May 2021 23:14:00 +1000 Subject: [PATCH 2/8] Fastlane Android: Releaased new build 34 [ci skip] --- android/app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 329e0c2..09d2d01 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -133,8 +133,8 @@ android { applicationId "io.newplanet.reactnativestarterkit" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 31 - versionName "0.1.2" + versionCode 34 + versionName "0.1.3" } signingConfigs { release { From 3bd063e8a4d4dcdb6bd6578a20d714d7fdae0cce Mon Sep 17 00:00:00 2001 From: Suchan Badyakar Date: Wed, 5 May 2021 09:41:40 +1000 Subject: [PATCH 3/8] Update --- ios/Podfile.lock | 6 + package-lock.json | 41 +++- package.json | 7 +- src/actions/Account/index.js | 12 ++ src/actions/Backup/index.js | 43 ++++ src/actions/index.js | 5 +- src/components/Dashboard/RecordList.js | 4 +- src/components/ReportPage/ReportDetail.js | 3 +- src/components/ReportPage/index.js | 6 +- src/components/SettingPage/index.js | 248 +++++++++++++++++++++- src/components/SettingPage/styles.js | 6 + src/helpers/db.js | 158 ++++++++++++++ src/reducers/backupReducer.js | 21 ++ src/reducers/index.js | 2 + 14 files changed, 545 insertions(+), 17 deletions(-) create mode 100644 src/actions/Backup/index.js create mode 100644 src/reducers/backupReducer.js diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 32e922b..ac5a5d3 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -306,6 +306,8 @@ PODS: - React-Core (= 0.63.3) - React-cxxreact (= 0.63.3) - React-jsi (= 0.63.3) + - rn-fetch-blob (0.12.0): + - React-Core - RNCMaskedView (0.1.10): - React - RNCPicker (1.8.1): @@ -376,6 +378,7 @@ DEPENDENCIES: - React-RCTText (from `../node_modules/react-native/Libraries/Text`) - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - rn-fetch-blob (from `../node_modules/rn-fetch-blob`) - "RNCMaskedView (from `../node_modules/@react-native-community/masked-view`)" - "RNCPicker (from `../node_modules/@react-native-community/picker`)" - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) @@ -461,6 +464,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/Libraries/Vibration" ReactCommon: :path: "../node_modules/react-native/ReactCommon" + rn-fetch-blob: + :path: "../node_modules/rn-fetch-blob" RNCMaskedView: :path: "../node_modules/@react-native-community/masked-view" RNCPicker: @@ -520,6 +525,7 @@ SPEC CHECKSUMS: React-RCTText: 65a6de06a7389098ce24340d1d3556015c38f746 React-RCTVibration: 8e9fb25724a0805107fc1acc9075e26f814df454 ReactCommon: 4167844018c9ed375cc01a843e9ee564399e53c3 + rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba RNCMaskedView: f5c7d14d6847b7b44853f7acb6284c1da30a3459 RNCPicker: 914b557e20b3b8317b084aca9ff4b4edb95f61e4 RNGestureHandler: 9b7e605a741412e20e13c512738a31bd1611759b diff --git a/package-lock.json b/package-lock.json index b868540..8fd113f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react_native_starter_kit", - "version": "0.1.1", + "version": "0.1.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2559,6 +2559,11 @@ } } }, + "base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=" + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -3149,6 +3154,11 @@ "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", "dev": true }, + "convert-csv-to-json": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/convert-csv-to-json/-/convert-csv-to-json-1.3.0.tgz", + "integrity": "sha512-yN/tsSjhIJxjvWl5XwC7grrTFMUc8qI21vlMKT6HwQZWV9X3mJTFjnYWyJiP79RWaE1zVRxqMbjtG8238/HPAA==" + }, "convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -4858,6 +4868,11 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=" + }, "fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -10388,6 +10403,30 @@ "glob": "^7.1.3" } }, + "rn-fetch-blob": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/rn-fetch-blob/-/rn-fetch-blob-0.12.0.tgz", + "integrity": "sha512-+QnR7AsJ14zqpVVUbzbtAjq0iI8c9tCg49tIoKO2ezjzRunN7YL6zFSFSWZm6d+mE/l9r+OeDM3jmb2tBb2WbA==", + "requires": { + "base-64": "0.1.0", + "glob": "7.0.6" + }, + "dependencies": { + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, "rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", diff --git a/package.json b/package.json index d757323..f4012b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react_native_starter_kit", - "version": "0.1.2", + "version": "0.1.3", "private": true, "scripts": { "install-deps-android": "cd android && bundle update && bundle install", @@ -41,7 +41,9 @@ "@react-navigation/stack": "^5.12.8", "@types/styled-components": "^5.1.9", "axios": "^0.21.0", + "convert-csv-to-json": "^1.3.0", "eslint-plugin-prettier": "^3.1.4", + "fs": "0.0.1-security", "lodash": "^4.17.20", "moment": "^2.29.1", "native-base": "^2.13.14", @@ -69,6 +71,7 @@ "redux-devtools-extension": "^2.13.8", "redux-logger": "^3.0.6", "redux-thunk": "^2.3.0", + "rn-fetch-blob": "^0.12.0", "styled-components": "^5.2.3", "uuid": "^8.3.1", "victory-native": "^35.3.1" @@ -94,4 +97,4 @@ "jest": { "preset": "react-native" } -} \ No newline at end of file +} diff --git a/src/actions/Account/index.js b/src/actions/Account/index.js index b665cf3..d22cc65 100644 --- a/src/actions/Account/index.js +++ b/src/actions/Account/index.js @@ -3,6 +3,7 @@ import { insertAccount, removeAccount, updateAccount, + insertAccounts, } from '../../helpers/db'; import { @@ -115,3 +116,14 @@ export const deleteAccount = ({id, callback}) => { } }; }; + +export const addAccounts = (sqlQuery) => { + return async (dispatch) => { + try { + const dbResult = await insertAccounts(sqlQuery); + dispatch({type: ACCOUNT_CREATE, payload: {}}); + } catch (error) { + throw error; + } + }; +}; diff --git a/src/actions/Backup/index.js b/src/actions/Backup/index.js new file mode 100644 index 0000000..4102d85 --- /dev/null +++ b/src/actions/Backup/index.js @@ -0,0 +1,43 @@ +import {insertBackup, fetchBackup, resetData} from '../../helpers/db'; + +export const getBackup = () => { + return async (dispatch) => { + try { + const dbResult = await fetchBackup(); + dispatch({ + type: 'backup_fetch_success', + payload: dbResult, + }); + } catch (error) { + throw error; + } + }; +}; + +export const addBackup = ({title, date, callback}) => { + return async (dispatch) => { + try { + const dbResult = await insertBackup(title, date); + const backupData = { + id: dbResult.insertId.toString(), + title: title, + date: date, + }; + dispatch({type: 'backup_created_success', payload: backupData}); + callback(); + } catch (error) { + throw error; + } + }; +}; + +export const resetDatabase = (accounts, categories, records) => { + return async (dispatch) => { + try { + const dbResult = await resetData(accounts, categories, records); + dispatch({type: 'database_reset_success', payload: {}}); + } catch (error) { + throw error; + } + }; +}; diff --git a/src/actions/index.js b/src/actions/index.js index 08fabca..5b585c3 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -1,4 +1,5 @@ -export * from './Category'; -export * from './Account'; +export * from './Backup'; export * from './Record'; export * from './Report'; +export * from './Account'; +export * from './Category'; diff --git a/src/components/Dashboard/RecordList.js b/src/components/Dashboard/RecordList.js index be8b94d..8593b4e 100644 --- a/src/components/Dashboard/RecordList.js +++ b/src/components/Dashboard/RecordList.js @@ -11,7 +11,7 @@ import RecordEmpty from './RecordEmpty'; import {CATEGORY_TYPE} from '../../constants'; import cs from '../../styles/common'; -import {getRecords, getAccounts, getCategories} from '../../actions'; +import {getRecords, getAccounts, getCategories, getBackup} from '../../actions'; class RecordList extends PureComponent { constructor(props) { @@ -55,6 +55,7 @@ class RecordList extends PureComponent { this.props.getRecords(); this.props.getAccounts(); this.props.getCategories(); + this.props.getBackup(); } render() { @@ -164,6 +165,7 @@ const mapStateToProps = (state) => { }; export default connect(mapStateToProps, { + getBackup, getRecords, getAccounts, getCategories, diff --git a/src/components/ReportPage/ReportDetail.js b/src/components/ReportPage/ReportDetail.js index e7dfa4f..0fb795d 100644 --- a/src/components/ReportPage/ReportDetail.js +++ b/src/components/ReportPage/ReportDetail.js @@ -20,10 +20,9 @@ const ReportDetail = (props) => { let myData = props.data; let expenseData = props.expenseData; - const colorScale = ['tomato', 'orange', 'gold', 'cyan', 'navy']; return ( - myData && ( + myData.length > 0 && ( { selectedReportType, } = state; - const records = _.map(recordList, (val, id) => { + const records = recordList.map((val, id) => { return val; }); - const accounts = _.map(accountList, (val, id) => { + const accounts = accountList.map((val, id) => { return val; }); - const categories = _.map(categoryList, (val, id) => { + const categories = categoryList.map((val, id) => { return val; }); diff --git a/src/components/SettingPage/index.js b/src/components/SettingPage/index.js index ba824df..3bbb51a 100644 --- a/src/components/SettingPage/index.js +++ b/src/components/SettingPage/index.js @@ -1,4 +1,9 @@ +/* eslint-disable dot-notation */ +import _ from 'lodash'; import React from 'react'; +import {connect} from 'react-redux'; +import RNFetchBlob from 'rn-fetch-blob'; +import {addBackup, resetDatabase, addAccounts} from '../../actions'; import { SettingsContainer, @@ -6,17 +11,211 @@ import { SettingsButton, SettingsTitle, SettingsIcon, + SettingsSubTitle, } from './styles'; -const SettingPage = () => { +const SettingPage = (props) => { + const {latestBackup} = props; + const importDatabase = () => { - alert('import database'); + const pathToRead = `${RNFetchBlob.fs.dirs.DownloadDir}/personal_expense_manager.csv`; + readFromFile(pathToRead); + }; + + const exportDatabase = (props) => { + const {records, accounts, categories} = props; + + const allRecordDataForExport = records.map((record) => { + const category = categories.find((cat) => cat.id === record.categoryId); + + const accountFrom = accounts.find((acc) => acc.id === record.payFrom); + + const accountTo = accounts.find((acc) => acc.id === record.payTo); + + return { + id: record.id, + date: record.date, + description: record.description, + amount: record.amount, + place: record.place, + camera: record.camera, + + categoryId: category.id, + categoryTitle: category.title, + categoryIcon: category.icon, + categoryType: category.type, + + payFromId: accountFrom.id, + payFromTitle: accountFrom.title, + payFromIcon: accountFrom.icon, + payFromType: accountFrom.type, + payFromOpeningBalance: accountFrom.openingBalance, + + payToId: accountTo.id, + payToTitle: accountTo.title, + payToIcon: accountTo.icon, + payToType: accountTo.type, + payToOpeningBalance: accountTo.openingBalance, + }; + }); + + // construct csvString + const headerString = + 'RecordId, Date, Description, Amount, Place, Camera, CategoryId, CategoryTitle, CategoryIcon, CategoryType, PayFromId, PayFromTitle, PayFromIcon, PayFromType, PayFromOpeningBalance, PayToId, PayToTitle, PayToIcon, PayToType, PayToOpeningBalance, \n'; + const rowString = allRecordDataForExport + .map( + (r) => + `${r.id},${r.date},${r.description},${r.amount},${r.place},${r.camera},${r.categoryId},${r.categoryTitle},${r.categoryIcon},${r.categoryType},${r.payFromId},${r.payFromTitle},${r.payFromIcon},${r.payFromType},${r.payFromOpeningBalance},${r.payToId},${r.payToTitle},${r.payToIcon},${r.payToType},${r.payToOpeningBalance},\n`, + ) + .join(''); + const csvString = `${headerString}${rowString}`; + + // write the current list of answers to a local csv file + const pathToWrite = `${RNFetchBlob.fs.dirs.DownloadDir}/personal_expense_manager.csv`; + console.log('pathToWrite', pathToWrite); + writetoFile(pathToWrite, csvString); + }; + + const writetoFile = (pathToWrite, csvString) => { + RNFetchBlob.fs + .writeFile(pathToWrite, csvString, 'utf8') + .then(() => { + console.log(`wrote file ${pathToWrite}`); + props.addBackup({ + title: 'personal_expense_manager.csv', + date: new Date().toISOString(), + callback: function () { + console.warn('1'); + }, + }); + }) + .catch((error) => console.error(error)); + }; + + const getAccountSqlQuery = (result) => { + const accountsFromCsv = result.map((res) => { + return { + id: res['PayFromId'] ? res['PayFromId'] : res['PayToId'], + title: res['PayFromId'] ? res['PayFromTitle'] : res['PayToTitle'], + type: res['PayFromId'] ? res['PayFromType'] : res['PayToType'], + openingBalance: res['PayFromId'] + ? res['PayFromOpeningBalance'] + : res['PayToOpeningBalance'], + icon: res['PayFromId'] ? res['PayFromIcon'] : res['PayToIcon'], + }; + }); + + let accounts = []; + let accountSqlQuery = ''; + + accountsFromCsv.forEach((account) => { + let isAccountIdAlreadyPresent = accounts.find((a) => a.id === account.id); + if (!isAccountIdAlreadyPresent && account.id) { + accountSqlQuery += `(${account.id},'${account.title}','${account.type}',${account.openingBalance},'${account.icon}'),`; + accounts.push(account); + } + }); + return accountSqlQuery.slice(0, accountSqlQuery.length - 1); + }; + + const getCategorySqlQuery = (result) => { + const categoriesFromCsv = result.map((res) => { + return { + id: res['CategoryId'], + title: res['CategoryTitle'], + type: res['CategoryType'], + icon: res['CategoryIcon'], + }; + }); + + let categories = []; + let categoriesSqlQuery = ''; + + categoriesFromCsv.forEach((category) => { + let isCatagodyIdAlreadyPresent = categories.find( + (a) => a.id === category.id, + ); + if (!isCatagodyIdAlreadyPresent && category.id) { + categoriesSqlQuery += `(${category.id},'${category.title}','${category.type}','${category.icon}'),`; + categories.push(category); + } + }); + return categoriesSqlQuery.slice(0, categoriesSqlQuery.length - 1); }; - const exportDatabase = () => { - alert('Export database'); + const getRecordSqlQuery = (result) => { + const recordsFromCsv = result.map((res) => { + return { + id: res['RecordId'], + amount: res['Amount'], + date: res['Date'], + categoryId: res['CategoryId'], + payFrom: res['PayFromId'], + payTo: res['PayToId'], + description: res['Description'], + place: res['Place'], + camera: res['Camera'], + }; + }); + + let records = []; + let recordsSqlQuery = ''; + + recordsFromCsv.forEach((record) => { + let isCatagodyIdAlreadyPresent = records.find((a) => a.id === record.id); + if (!isCatagodyIdAlreadyPresent && record.id) { + recordsSqlQuery += `(${record.id},'${record.amount}','${record.date}',${record.categoryId},${record.payFrom},${record.payTo}, '${record.description}','${record.place}', '${record.camera}'),`; + records.push(record); + } + }); + return recordsSqlQuery.slice(0, recordsSqlQuery.length - 1); }; + const readFromFile = (pathToRead) => { + RNFetchBlob.fs + .readFile(pathToRead, 'utf8') + .then((file) => { + try { + console.log(`read from file ${pathToRead}`); + let lines = file.split('\n'); + + let result = []; + + let headers = lines[0].split(','); + + for (let i = 1; i < lines.length; i++) { + let obj = {}; + let currentline = lines[i].split(','); + + for (let j = 0; j < headers.length; j++) { + if (headers[j].trim() && currentline[0]) { + obj[headers[j].trim()] = currentline[j]; + } + } + result.push(obj); + } + + console.warn(result); + + let finalValue = getAccountSqlQuery(result); + let categorySqlQuery = getCategorySqlQuery(result); + let recordSqlQuery = getRecordSqlQuery(result); + console.warn(finalValue); + console.warn(categorySqlQuery); + console.warn(recordSqlQuery); + + props.resetDatabase(finalValue, categorySqlQuery); + + //return result; //JavaScript object + return JSON.stringify(result); //JSON + } catch (error) { + console.error(error); + } + }) + .catch((error) => { + console.error(error); + }); + }; return ( @@ -24,13 +223,50 @@ const SettingPage = () => { Import Database - exportDatabase()}> + Last exported at: + + exportDatabase(props)}> Export Database + + Last exported at: + {latestBackup && new Date(latestBackup.date).toDateString()} + ); }; -export default SettingPage; +const mapStateToProps = (state) => { + const { + record: {list: recordList}, + account: {list: accountList}, + category: {list: categoryList}, + backup: {list: backupList}, + } = state; + + const records = _.map(recordList, (val, id) => { + return val; + }); + + const accounts = _.map(accountList, (val, id) => { + return val; + }); + + const categories = _.map(categoryList, (val, id) => { + return val; + }); + + const latestBackup = backupList[backupList.length - 1]; + return {records, accounts, categories, latestBackup}; +}; + +export default connect(mapStateToProps, { + addBackup, + resetDatabase, + addAccounts, +})(SettingPage); diff --git a/src/components/SettingPage/styles.js b/src/components/SettingPage/styles.js index c4c2357..2358e8d 100644 --- a/src/components/SettingPage/styles.js +++ b/src/components/SettingPage/styles.js @@ -22,3 +22,9 @@ export const SettingsContent = styled(View)` `; export const SettingsButton = styled(Button)``; + +export const SettingsSubTitle = styled(Text)` + font-size: ${(props) => props.theme.fontSizes.subtitle}; + color: ${(props) => props.theme.colors.ui.white}; + padding-left: 16; +`; diff --git a/src/helpers/db.js b/src/helpers/db.js index f7421bc..b90e358 100644 --- a/src/helpers/db.js +++ b/src/helpers/db.js @@ -45,6 +45,20 @@ export const init = () => { }, ); }); + + db.transaction((tx) => { + tx.executeSql( + 'CREATE TABLE IF NOT EXISTS backups (id INTEGER PRIMARY KEY NOT NULL, text TEXT NOT NULL,date TEXT NOT NULL)', + [], + () => { + console.warn('Table backup created'); + resolve(); + }, + (err) => { + reject(err); + }, + ); + }); }); return promise; @@ -59,6 +73,30 @@ export const insertAccount = (title, type, openingBalance, icon) => { (_, result) => { resolve(result); }, + (err) => { + console.warn(err); + reject(err); + }, + ); + }); + }); + + return promise; +}; + +export const insertAccounts = (accounts) => { + debugger; + console.warn( + 'INSERT INTO accounts (id, title, type, openingBalance, icon) VALUES ' + + accounts, + ); + const promise = new Promise((resolve, reject) => { + db.transaction((tx) => { + tx.executeSql( + `INSERT INTO accounts (id, title, type, openingBalance, icon) VALUES ${accounts}`, + (_, result) => { + resolve(result); + }, (err) => { reject(err); }, @@ -311,3 +349,123 @@ export const updateRecord = ( return promise; }; + +export const insertBackup = (text, date) => { + const promise = new Promise((resolve, reject) => { + db.transaction((tx) => { + tx.executeSql( + 'INSERT INTO backups (text, date) VALUES (?,?)', + [text, date], + (_, result) => { + resolve(result); + }, + (err) => { + reject(err); + }, + ); + }); + }); + + return promise; +}; + +export const fetchBackup = () => { + const promise = new Promise((resolve, reject) => { + db.transaction((tx) => { + tx.executeSql( + 'SELECT * FROM backups', + [], + (_, result) => { + let backup = []; + for (let i = 0; i < result.rows.length; i++) { + const row = result.rows.item(i); + backup.push(row); + } + resolve(backup); + }, + (err) => { + reject(err); + }, + ); + }); + }); + + return promise; +}; + +export const resetData = (accounts, categories, records) => { + const promise = new Promise((resolve, reject) => { + db.transaction((tx) => { + tx.executeSql( + 'DELETE FROM accounts', + [], + () => { + tx.executeSql( + 'DELETE FROM categories', + [], + () => { + tx.executeSql( + 'DELETE FROM records', + [], + () => { + tx.executeSql( + 'DELETE FROM backups', + [], + () => { + tx.executeSql( + `INSERT INTO accounts (id, title, type, openingBalance, icon) VALUES ${accounts}`, + (_, result) => { + resolve(); + + tx.executeSql( + `INSERT INTO categories (id, title, type, icon) VALUES ${categories}`, + (_, result) => { + debugger; + tx.executeSql( + `INSERT INTO records (id, amount,date,categoryId,payFrom,payTo,description,place,camera) VALUES ${records}`, + (_, result) => { + resolve(); + }, + (err) => { + reject(err); + }, + ); + }, + (err) => { + debugger; + + reject(err); + }, + ); + }, + (err) => { + reject(err); + }, + ); + }, + (err) => { + reject(err); + }, + ); + }, + (err) => { + reject(err); + }, + ); + }, + (err) => { + reject(err); + }, + ); + }, + (err) => { + console.warn('Error'); + + reject(err); + }, + ); + }); + }); + + return promise; +}; diff --git a/src/reducers/backupReducer.js b/src/reducers/backupReducer.js new file mode 100644 index 0000000..0da951f --- /dev/null +++ b/src/reducers/backupReducer.js @@ -0,0 +1,21 @@ +const initialState = { + list: [], + error: '', + loading: false, +}; + +export default function (state = initialState, action) { + switch (action.type) { + case 'backup_fetch_success': { + return {...state, list: action.payload, loading: false}; + } + case 'backup_created_success': { + return {...state, list: [...state.list, action.payload], loading: false}; + } + case 'database_reset_success': { + return {...state}; + } + default: + return state; + } +} diff --git a/src/reducers/index.js b/src/reducers/index.js index 09c0558..2271c47 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -3,6 +3,7 @@ import recordReducer from './recordReducer'; import accountReducer from './accountReducer'; import reportReducer from './reportTypeReducer'; import categoryReducer from './categoryReducer'; +import backupReducer from './backupReducer'; import categoryTypeReducer from './categoryTypeReducer'; @@ -10,6 +11,7 @@ const rootReducer = combineReducers({ record: recordReducer, account: accountReducer, category: categoryReducer, + backup: backupReducer, selectedReportType: reportReducer, selectedCategoryType: categoryTypeReducer, }); From b08932486829ede37519dc936d5ba47be0e0a94f Mon Sep 17 00:00:00 2001 From: Suchan Badyakar Date: Wed, 5 May 2021 16:14:01 +1000 Subject: [PATCH 4/8] update db functionality --- src/actions/Backup/index.js | 3 +- src/components/SettingPage/index.js | 122 +++++++++------------------ src/components/SettingPage/styles.js | 4 +- src/helpers/db.js | 118 +++++++++++--------------- src/utils/csvToJson.js | 23 +++++ src/utils/fileManager.js | 43 ++++++++++ 6 files changed, 156 insertions(+), 157 deletions(-) create mode 100644 src/utils/csvToJson.js create mode 100644 src/utils/fileManager.js diff --git a/src/actions/Backup/index.js b/src/actions/Backup/index.js index 4102d85..5787cfa 100644 --- a/src/actions/Backup/index.js +++ b/src/actions/Backup/index.js @@ -31,11 +31,12 @@ export const addBackup = ({title, date, callback}) => { }; }; -export const resetDatabase = (accounts, categories, records) => { +export const resetDatabase = (accounts, categories, records, callback) => { return async (dispatch) => { try { const dbResult = await resetData(accounts, categories, records); dispatch({type: 'database_reset_success', payload: {}}); + callback(); } catch (error) { throw error; } diff --git a/src/components/SettingPage/index.js b/src/components/SettingPage/index.js index 3bbb51a..b4df1a0 100644 --- a/src/components/SettingPage/index.js +++ b/src/components/SettingPage/index.js @@ -4,6 +4,8 @@ import React from 'react'; import {connect} from 'react-redux'; import RNFetchBlob from 'rn-fetch-blob'; import {addBackup, resetDatabase, addAccounts} from '../../actions'; +import csvToJson from '../../utils/csvToJson'; +import {readFromFile, writetoFile} from '../../utils/fileManager'; import { SettingsContainer, @@ -18,11 +20,27 @@ const SettingPage = (props) => { const {latestBackup} = props; const importDatabase = () => { - const pathToRead = `${RNFetchBlob.fs.dirs.DownloadDir}/personal_expense_manager.csv`; - readFromFile(pathToRead); + readFromFile() + .then((result) => { + let accountSqlQuery = getAccountSqlQuery(result); + let categorySqlQuery = getCategorySqlQuery(result); + let recordSqlQuery = getRecordSqlQuery(result); + + props.resetDatabase( + accountSqlQuery, + categorySqlQuery, + recordSqlQuery, + () => { + alert('Database Imported Successfully'); + }, + ); + }) + .catch((error) => { + alert(error); + }); }; - const exportDatabase = (props) => { + const exportDatabase = () => { const {records, accounts, categories} = props; const allRecordDataForExport = records.map((record) => { @@ -45,51 +63,33 @@ const SettingPage = (props) => { categoryIcon: category.icon, categoryType: category.type, - payFromId: accountFrom.id, - payFromTitle: accountFrom.title, - payFromIcon: accountFrom.icon, - payFromType: accountFrom.type, - payFromOpeningBalance: accountFrom.openingBalance, - - payToId: accountTo.id, - payToTitle: accountTo.title, - payToIcon: accountTo.icon, - payToType: accountTo.type, - payToOpeningBalance: accountTo.openingBalance, + payFromId: (accountFrom && accountFrom.id) || null, + payFromTitle: (accountFrom && accountFrom.title) || '', + payFromIcon: (accountFrom && accountFrom.icon) || '', + payFromType: (accountFrom && accountFrom.type) || '', + payFromOpeningBalance: (accountFrom && accountFrom.openingBalance) || 0, + + payToId: (accountTo && accountTo.id) || null, + payToTitle: (accountTo && accountTo.title) || '', + payToIcon: (accountTo && accountTo.icon) || '', + payToType: (accountTo && accountTo.type) || '', + payToOpeningBalance: (accountTo && accountTo.openingBalance) || 0, }; }); - // construct csvString - const headerString = - 'RecordId, Date, Description, Amount, Place, Camera, CategoryId, CategoryTitle, CategoryIcon, CategoryType, PayFromId, PayFromTitle, PayFromIcon, PayFromType, PayFromOpeningBalance, PayToId, PayToTitle, PayToIcon, PayToType, PayToOpeningBalance, \n'; - const rowString = allRecordDataForExport - .map( - (r) => - `${r.id},${r.date},${r.description},${r.amount},${r.place},${r.camera},${r.categoryId},${r.categoryTitle},${r.categoryIcon},${r.categoryType},${r.payFromId},${r.payFromTitle},${r.payFromIcon},${r.payFromType},${r.payFromOpeningBalance},${r.payToId},${r.payToTitle},${r.payToIcon},${r.payToType},${r.payToOpeningBalance},\n`, - ) - .join(''); - const csvString = `${headerString}${rowString}`; - - // write the current list of answers to a local csv file - const pathToWrite = `${RNFetchBlob.fs.dirs.DownloadDir}/personal_expense_manager.csv`; - console.log('pathToWrite', pathToWrite); - writetoFile(pathToWrite, csvString); - }; - - const writetoFile = (pathToWrite, csvString) => { - RNFetchBlob.fs - .writeFile(pathToWrite, csvString, 'utf8') + writetoFile(allRecordDataForExport) .then(() => { - console.log(`wrote file ${pathToWrite}`); props.addBackup({ title: 'personal_expense_manager.csv', date: new Date().toISOString(), callback: function () { - console.warn('1'); + alert('Database Exported Successfully'); }, }); }) - .catch((error) => console.error(error)); + .catch((error) => { + alert(error); + }); }; const getAccountSqlQuery = (result) => { @@ -171,51 +171,6 @@ const SettingPage = (props) => { return recordsSqlQuery.slice(0, recordsSqlQuery.length - 1); }; - const readFromFile = (pathToRead) => { - RNFetchBlob.fs - .readFile(pathToRead, 'utf8') - .then((file) => { - try { - console.log(`read from file ${pathToRead}`); - let lines = file.split('\n'); - - let result = []; - - let headers = lines[0].split(','); - - for (let i = 1; i < lines.length; i++) { - let obj = {}; - let currentline = lines[i].split(','); - - for (let j = 0; j < headers.length; j++) { - if (headers[j].trim() && currentline[0]) { - obj[headers[j].trim()] = currentline[j]; - } - } - result.push(obj); - } - - console.warn(result); - - let finalValue = getAccountSqlQuery(result); - let categorySqlQuery = getCategorySqlQuery(result); - let recordSqlQuery = getRecordSqlQuery(result); - console.warn(finalValue); - console.warn(categorySqlQuery); - console.warn(recordSqlQuery); - - props.resetDatabase(finalValue, categorySqlQuery); - - //return result; //JavaScript object - return JSON.stringify(result); //JSON - } catch (error) { - console.error(error); - } - }) - .catch((error) => { - console.error(error); - }); - }; return ( @@ -223,7 +178,6 @@ const SettingPage = (props) => { Import Database - Last exported at: { Export Database - Last exported at: + Last exported at:{' '} {latestBackup && new Date(latestBackup.date).toDateString()} diff --git a/src/components/SettingPage/styles.js b/src/components/SettingPage/styles.js index 2358e8d..529a592 100644 --- a/src/components/SettingPage/styles.js +++ b/src/components/SettingPage/styles.js @@ -25,6 +25,6 @@ export const SettingsButton = styled(Button)``; export const SettingsSubTitle = styled(Text)` font-size: ${(props) => props.theme.fontSizes.subtitle}; - color: ${(props) => props.theme.colors.ui.white}; - padding-left: 16; + color: ${(props) => props.theme.colors.ui.gray}; + padding-left: 54; `; diff --git a/src/helpers/db.js b/src/helpers/db.js index b90e358..3548ec2 100644 --- a/src/helpers/db.js +++ b/src/helpers/db.js @@ -395,76 +395,54 @@ export const fetchBackup = () => { export const resetData = (accounts, categories, records) => { const promise = new Promise((resolve, reject) => { - db.transaction((tx) => { - tx.executeSql( - 'DELETE FROM accounts', - [], - () => { - tx.executeSql( - 'DELETE FROM categories', - [], - () => { - tx.executeSql( - 'DELETE FROM records', - [], - () => { - tx.executeSql( - 'DELETE FROM backups', - [], - () => { - tx.executeSql( - `INSERT INTO accounts (id, title, type, openingBalance, icon) VALUES ${accounts}`, - (_, result) => { - resolve(); - - tx.executeSql( - `INSERT INTO categories (id, title, type, icon) VALUES ${categories}`, - (_, result) => { - debugger; - tx.executeSql( - `INSERT INTO records (id, amount,date,categoryId,payFrom,payTo,description,place,camera) VALUES ${records}`, - (_, result) => { - resolve(); - }, - (err) => { - reject(err); - }, - ); - }, - (err) => { - debugger; - - reject(err); - }, - ); - }, - (err) => { - reject(err); - }, - ); - }, - (err) => { - reject(err); - }, - ); - }, - (err) => { - reject(err); - }, - ); - }, - (err) => { - reject(err); - }, - ); - }, - (err) => { - console.warn('Error'); - - reject(err); - }, - ); - }); + db.transaction( + (tx) => { + tx.executeSql('DELETE FROM accounts'); + tx.executeSql('DELETE FROM categories'); + tx.executeSql('DELETE FROM records'); + + tx.executeSql( + `INSERT INTO accounts (id, title, type, openingBalance, icon) VALUES ${accounts}`, + (tx, resultSet) => { + console.log('resultSet.insertId: ' + resultSet.insertId); + console.log('resultSet.rowsAffected: ' + resultSet.rowsAffected); + }, + (tx, error) => { + console.log('ACCOUNTS INSERT error: ' + JSON.stringify(error)); + }, + ); + + tx.executeSql( + `INSERT INTO categories (id, title, type, icon) VALUES ${categories}`, + (tx, resultSet) => { + console.log('resultSet.insertId: ' + resultSet.insertId); + console.log('resultSet.rowsAffected: ' + resultSet.rowsAffected); + }, + (tx, error) => { + console.log('CATEGORIES INSERT error: ' + JSON.stringify(error)); + }, + ); + + tx.executeSql( + `INSERT INTO records (id, amount,date,categoryId,payFrom,payTo,description,place,camera) VALUES ${records}`, + (tx, resultSet) => { + console.log('resultSet.insertId: ' + resultSet.insertId); + console.log('resultSet.rowsAffected: ' + resultSet.rowsAffected); + }, + (tx, error) => { + console.log('RECORDS INSERT error: ' + JSON.stringify(error)); + }, + ); + }, + (error) => { + console.log('transaction error: ' + error.message); + reject(error.message); + }, + () => { + console.log('transaction ok'); + resolve(); + }, + ); }); return promise; diff --git a/src/utils/csvToJson.js b/src/utils/csvToJson.js new file mode 100644 index 0000000..ca5990d --- /dev/null +++ b/src/utils/csvToJson.js @@ -0,0 +1,23 @@ +const csvToJson = (file) => { + let lines = file.split('\n'); + + let result = []; + + let headers = lines[0].split(','); + + for (let i = 1; i < lines.length; i++) { + let obj = {}; + let currentline = lines[i].split(','); + + for (let j = 0; j < headers.length; j++) { + if (headers[j].trim() && currentline[0]) { + obj[headers[j].trim()] = currentline[j]; + } + } + result.push(obj); + } + + return result; +}; + +export default csvToJson; diff --git a/src/utils/fileManager.js b/src/utils/fileManager.js new file mode 100644 index 0000000..4b66d91 --- /dev/null +++ b/src/utils/fileManager.js @@ -0,0 +1,43 @@ +import RNFetchBlob from 'rn-fetch-blob'; +import csvToJson from '../utils/csvToJson'; + +const writetoFile = (allRecordDataForExport) => { + // construct csvString + const headerString = + 'RecordId, Date, Description, Amount, Place, Camera, CategoryId, CategoryTitle, CategoryIcon, CategoryType, PayFromId, PayFromTitle, PayFromIcon, PayFromType, PayFromOpeningBalance, PayToId, PayToTitle, PayToIcon, PayToType, PayToOpeningBalance, \n'; + const rowString = allRecordDataForExport + .map( + (r) => + `${r.id},${r.date},${r.description},${r.amount},${r.place},${r.camera},${r.categoryId},${r.categoryTitle},${r.categoryIcon},${r.categoryType},${r.payFromId},${r.payFromTitle},${r.payFromIcon},${r.payFromType},${r.payFromOpeningBalance},${r.payToId},${r.payToTitle},${r.payToIcon},${r.payToType},${r.payToOpeningBalance},\n`, + ) + .join(''); + const csvString = `${headerString}${rowString}`; + + // write the current list of answers to a local csv file + const pathToWrite = `${RNFetchBlob.fs.dirs.DownloadDir}/personal_expense_manager.csv`; + + return new Promise((resolve, reject) => { + RNFetchBlob.fs + .writeFile(pathToWrite, csvString, 'utf8') + .then(() => { + console.log(`wrote file ${pathToWrite}`); + resolve(); + }) + .catch((error) => reject(error)); + }); +}; + +const readFromFile = () => { + const pathToRead = `${RNFetchBlob.fs.dirs.DownloadDir}/personal_expense_manager.csv`; + + return new Promise((resolve, reject) => { + RNFetchBlob.fs + .readFile(pathToRead, 'utf8') + .then((file) => { + resolve(csvToJson(file)); + }) + .catch((error) => reject(error)); + }); +}; + +export {writetoFile, readFromFile}; From 76bb1313f51df2b04cfab8edb1e39019218861e7 Mon Sep 17 00:00:00 2001 From: Suchan Badyakar Date: Wed, 5 May 2021 16:15:30 +1000 Subject: [PATCH 5/8] remove debugger --- src/helpers/db.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/helpers/db.js b/src/helpers/db.js index 3548ec2..cbbf77a 100644 --- a/src/helpers/db.js +++ b/src/helpers/db.js @@ -85,7 +85,6 @@ export const insertAccount = (title, type, openingBalance, icon) => { }; export const insertAccounts = (accounts) => { - debugger; console.warn( 'INSERT INTO accounts (id, title, type, openingBalance, icon) VALUES ' + accounts, From 269ef56823c4ba3b6b895808eccf71ee0c145440 Mon Sep 17 00:00:00 2001 From: Suchan Badyakar Date: Wed, 5 May 2021 20:35:35 +1000 Subject: [PATCH 6/8] update --- .../sampledata/personal_expense_manager.json | 553 ++++++++++++++++++ src/components/SettingPage/index.js | 31 +- src/utils/fileManager.js | 39 +- 3 files changed, 617 insertions(+), 6 deletions(-) create mode 100644 src/assets/sampledata/personal_expense_manager.json diff --git a/src/assets/sampledata/personal_expense_manager.json b/src/assets/sampledata/personal_expense_manager.json new file mode 100644 index 0000000..5875ac1 --- /dev/null +++ b/src/assets/sampledata/personal_expense_manager.json @@ -0,0 +1,553 @@ +[{ + "RecordId": 2, + "Date": "2021-05-02T04:22:18.000Z", + "Description": "Petrol", + "Amount": 80, + "Place": "", + "Camera": "", + "CategoryId": 17, + "CategoryTitle": "Transportation", + "CategoryIcon": "car", + "CategoryType": "EXPENSE", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 3, + "Date": "2021-05-02T04:22:44.000Z", + "Description": "Phone", + "Amount": 50, + "Place": "", + "Camera": "", + "CategoryId": 6, + "CategoryTitle": "Phone", + "CategoryIcon": "phone", + "CategoryType": "EXPENSE", + "PayFromId": 4, + "PayFromTitle": "NAB Credit Card", + "PayFromIcon": "credit-card", + "PayFromType": "OTHERS", + "PayFromOpeningBalance": 0, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 4, + "Date": "2021-05-02T04:23:11.000Z", + "Description": "Rent May", + "Amount": 800, + "Place": "", + "Camera": "", + "CategoryId": 9, + "CategoryTitle": "Rent", + "CategoryIcon": "bed", + "CategoryType": "EXPENSE", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 5, + "Date": "2021-05-03T04:23:38.000Z", + "Description": "Coles", + "Amount": 20, + "Place": "", + "Camera": "", + "CategoryId": 14, + "CategoryTitle": "Grocery", + "CategoryIcon": "shopping-cart", + "CategoryType": "EXPENSE", + "PayFromId": 4, + "PayFromTitle": "NAB Credit Card", + "PayFromIcon": "credit-card", + "PayFromType": "OTHERS", + "PayFromOpeningBalance": 0, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 6, + "Date": "2021-05-04T04:24:01.854Z", + "Description": "interest", + "Amount": 10, + "Place": "", + "Camera": "", + "CategoryId": 2, + "CategoryTitle": "Interest", + "CategoryIcon": "dollar", + "CategoryType": "INCOME", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 7, + "Date": "2021-05-04T04:24:16.167Z", + "Description": "electricity", + "Amount": 60, + "Place": "", + "Camera": "", + "CategoryId": 8, + "CategoryTitle": "Electricity", + "CategoryIcon": "bolt", + "CategoryType": "EXPENSE", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 8, + "Date": "2021-05-06T04:24:40.000Z", + "Description": "pizza", + "Amount": 60, + "Place": "", + "Camera": "", + "CategoryId": 16, + "CategoryTitle": "Eat Out", + "CategoryIcon": "home", + "CategoryType": "EXPENSE", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 9, + "Date": "2021-05-09T04:25:05.000Z", + "Description": "Medical insurance", + "Amount": 60, + "Place": "", + "Camera": "", + "CategoryId": 10, + "CategoryTitle": "Medical", + "CategoryIcon": "ambulance", + "CategoryType": "EXPENSE", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 10, + "Date": "2021-05-04T04:25:56.211Z", + "Description": "Vegetables", + "Amount": 30, + "Place": "", + "Camera": "", + "CategoryId": 14, + "CategoryTitle": "Grocery", + "CategoryIcon": "shopping-cart", + "CategoryType": "EXPENSE", + "PayFromId": 4, + "PayFromTitle": "NAB Credit Card", + "PayFromIcon": "credit-card", + "PayFromType": "OTHERS", + "PayFromOpeningBalance": 0, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 11, + "Date": "2021-04-04T04:31:41.000Z", + "Description": "Salary April", + "Amount": 1500, + "Place": "", + "Camera": "", + "CategoryId": 1, + "CategoryTitle": "Salary", + "CategoryIcon": "dollar", + "CategoryType": "INCOME", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 12, + "Date": "2021-05-04T04:32:05.163Z", + "Description": "dividend", + "Amount": 500, + "Place": "", + "Camera": "", + "CategoryId": 4, + "CategoryTitle": "Dividends", + "CategoryIcon": "money", + "CategoryType": "INCOME", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 13, + "Date": "2021-04-03T03:37:59.000Z", + "Description": "misc", + "Amount": 500, + "Place": "", + "Camera": "", + "CategoryId": 5, + "CategoryTitle": "Misc", + "CategoryIcon": "gear", + "CategoryType": "INCOME", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 14, + "Date": "2021-04-04T04:39:33.000Z", + "Description": "Commission", + "Amount": 40, + "Place": "", + "Camera": "", + "CategoryId": 3, + "CategoryTitle": "Commission", + "CategoryIcon": "money", + "CategoryType": "INCOME", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": null, + "PayToTitle": "", + "PayToIcon": "", + "PayToType": "", + "PayToOpeningBalance": 0, + "FIELD21": "" + }, + { + "RecordId": 15, + "Date": "2021-04-04T04:40:07.000Z", + "Description": "Phone", + "Amount": 50, + "Place": "", + "Camera": "", + "CategoryId": 6, + "CategoryTitle": "Phone", + "CategoryIcon": "phone", + "CategoryType": "EXPENSE", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 16, + "Date": "2021-04-17T04:40:33.000Z", + "Description": "gift", + "Amount": 120, + "Place": "", + "Camera": "", + "CategoryId": 15, + "CategoryTitle": "Gift", + "CategoryIcon": "gift", + "CategoryType": "EXPENSE", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 17, + "Date": "2021-05-04T04:42:55.814Z", + "Description": "eat out", + "Amount": 60, + "Place": "", + "Camera": "", + "CategoryId": 16, + "CategoryTitle": "Eat Out", + "CategoryIcon": "home", + "CategoryType": "EXPENSE", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 18, + "Date": "2021-04-04T04:43:07.000Z", + "Description": "house hold", + "Amount": 100, + "Place": "", + "Camera": "", + "CategoryId": 12, + "CategoryTitle": "House", + "CategoryIcon": "home", + "CategoryType": "EXPENSE", + "PayFromId": 4, + "PayFromTitle": "NAB Credit Card", + "PayFromIcon": "credit-card", + "PayFromType": "OTHERS", + "PayFromOpeningBalance": 0, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 19, + "Date": "2021-05-04T04:43:30.343Z", + "Description": "Amazon", + "Amount": 300, + "Place": "", + "Camera": "", + "CategoryId": 11, + "CategoryTitle": "Shopping", + "CategoryIcon": "amazon", + "CategoryType": "EXPENSE", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 20, + "Date": "2021-04-04T04:43:51.000Z", + "Description": "transportation", + "Amount": 100, + "Place": "", + "Camera": "", + "CategoryId": 17, + "CategoryTitle": "Transportation", + "CategoryIcon": "car", + "CategoryType": "EXPENSE", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 21, + "Date": "2021-04-28T04:44:59.000Z", + "Description": "payback", + "Amount": 500, + "Place": "", + "Camera": "", + "CategoryId": 18, + "CategoryTitle": "Payback to NAB", + "CategoryIcon": "credit-card", + "CategoryType": "TRANSFER", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 22, + "Date": "2021-05-04T05:07:15.773Z", + "Description": "COM Shares", + "Amount": 170, + "Place": "", + "Camera": "", + "CategoryId": 7, + "CategoryTitle": "Shares", + "CategoryIcon": "btc", + "CategoryType": "EXPENSE", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 23, + "Date": "2021-05-04T05:08:51.934Z", + "Description": "Uni fee", + "Amount": 500, + "Place": "", + "Camera": "", + "CategoryId": 13, + "CategoryTitle": "Education", + "CategoryIcon": "home", + "CategoryType": "EXPENSE", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 24, + "Date": "2021-04-15T05:13:41.000Z", + "Description": "Coles", + "Amount": 20, + "Place": "", + "Camera": "", + "CategoryId": 14, + "CategoryTitle": "Grocery", + "CategoryIcon": "shopping-cart", + "CategoryType": "EXPENSE", + "PayFromId": 3, + "PayFromTitle": "Cash", + "PayFromIcon": "money", + "PayFromType": "CASH", + "PayFromOpeningBalance": 80, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + }, + { + "RecordId": 25, + "Date": "2021-05-05T05:20:51.499Z", + "Description": "Salay", + "Amount": 1200, + "Place": "", + "Camera": "", + "CategoryId": 1, + "CategoryTitle": "Salary", + "CategoryIcon": "dollar", + "CategoryType": "INCOME", + "PayFromId": 1, + "PayFromTitle": "Bank of Melboune", + "PayFromIcon": "building", + "PayFromType": "BANK", + "PayFromOpeningBalance": 1400, + "PayToId": "1", + "PayToTitle": "Bank of Melboune", + "PayToIcon": "building", + "PayToType": "BANK", + "PayToOpeningBalance": 1400, + "FIELD21": "" + } +] \ No newline at end of file diff --git a/src/components/SettingPage/index.js b/src/components/SettingPage/index.js index b4df1a0..2ed2a9d 100644 --- a/src/components/SettingPage/index.js +++ b/src/components/SettingPage/index.js @@ -2,11 +2,11 @@ import _ from 'lodash'; import React from 'react'; import {connect} from 'react-redux'; -import RNFetchBlob from 'rn-fetch-blob'; -import {addBackup, resetDatabase, addAccounts} from '../../actions'; -import csvToJson from '../../utils/csvToJson'; + import {readFromFile, writetoFile} from '../../utils/fileManager'; +import {addBackup, resetDatabase, addAccounts} from '../../actions'; +const sampelData = require('../../assets/sampledata/personal_expense_manager.json'); import { SettingsContainer, SettingsContent, @@ -36,6 +36,7 @@ const SettingPage = (props) => { ); }) .catch((error) => { + console.warn('erroror==>'); alert(error); }); }; @@ -92,6 +93,25 @@ const SettingPage = (props) => { }); }; + const loadSampleData = () => { + try { + let accountSqlQuery = getAccountSqlQuery(sampelData); + let categorySqlQuery = getCategorySqlQuery(sampelData); + let recordSqlQuery = getRecordSqlQuery(sampelData); + + props.resetDatabase( + accountSqlQuery, + categorySqlQuery, + recordSqlQuery, + () => { + alert('Database Imported Successfully'); + }, + ); + } catch (error) { + alert(error); + } + }; + const getAccountSqlQuery = (result) => { const accountsFromCsv = result.map((res) => { return { @@ -174,6 +194,11 @@ const SettingPage = (props) => { return ( + loadSampleData()}> + + Load Sample Data + + importDatabase()}> Import Database diff --git a/src/utils/fileManager.js b/src/utils/fileManager.js index 4b66d91..8d713ba 100644 --- a/src/utils/fileManager.js +++ b/src/utils/fileManager.js @@ -1,7 +1,36 @@ import RNFetchBlob from 'rn-fetch-blob'; +import {PermissionsAndroid} from 'react-native'; + import csvToJson from '../utils/csvToJson'; +const requestCameraPermission = async () => { + try { + const granted = await PermissionsAndroid.request( + PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, + { + title: 'Warning', + message: 'Allow Permissions to write to a file ?', + buttonNeutral: 'Ask Me Later', + buttonNegative: 'Cancel', + buttonPositive: 'OK', + }, + ); + if (granted === PermissionsAndroid.RESULTS.GRANTED) { + return true; + } else { + return false; + } + } catch (err) { + console.warn(err); + } +}; + const writetoFile = (allRecordDataForExport) => { + const isPermitted = requestCameraPermission(); + if (!isPermitted) { + alert('You need to enable permission to read/write to a file'); + } + // construct csvString const headerString = 'RecordId, Date, Description, Amount, Place, Camera, CategoryId, CategoryTitle, CategoryIcon, CategoryType, PayFromId, PayFromTitle, PayFromIcon, PayFromType, PayFromOpeningBalance, PayToId, PayToTitle, PayToIcon, PayToType, PayToOpeningBalance, \n'; @@ -14,7 +43,7 @@ const writetoFile = (allRecordDataForExport) => { const csvString = `${headerString}${rowString}`; // write the current list of answers to a local csv file - const pathToWrite = `${RNFetchBlob.fs.dirs.DownloadDir}/personal_expense_manager.csv`; + const pathToWrite = `${RNFetchBlob.fs.dirs.DocumentDir}/personal_expense_manager.csv`; return new Promise((resolve, reject) => { RNFetchBlob.fs @@ -28,8 +57,12 @@ const writetoFile = (allRecordDataForExport) => { }; const readFromFile = () => { - const pathToRead = `${RNFetchBlob.fs.dirs.DownloadDir}/personal_expense_manager.csv`; - + const isPermitted = requestCameraPermission(); + if (!isPermitted) { + alert('You need to enable permission to read/write to a file'); + } + const pathToRead = `${RNFetchBlob.fs.dirs.DocumentDir}/personal_expense_manager.csv`; + console.warn(JSON.stringify(RNFetchBlob.fs.dirs)); return new Promise((resolve, reject) => { RNFetchBlob.fs .readFile(pathToRead, 'utf8') From 161d7b20f19cb692b7ff78cc3fe6244bdffb4459 Mon Sep 17 00:00:00 2001 From: Suchan Badyakar Date: Wed, 5 May 2021 20:37:41 +1000 Subject: [PATCH 7/8] update graph --- src/components/ReportPage/ReportDetail.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportPage/ReportDetail.js b/src/components/ReportPage/ReportDetail.js index 0fb795d..84a15ea 100644 --- a/src/components/ReportPage/ReportDetail.js +++ b/src/components/ReportPage/ReportDetail.js @@ -39,7 +39,7 @@ const ReportDetail = (props) => { theme={VictoryTheme.material} colorScale={colorScale} innerRadius={70} - labelRadius={150} + labelRadius={120} width={deviceWidth - 40} data={myData} events={[]} @@ -59,7 +59,7 @@ const ReportDetail = (props) => { theme={VictoryTheme.material} colorScale={colorScale} innerRadius={70} - labelRadius={150} + labelRadius={120} width={deviceWidth - 40} data={expenseData} events={[]} From 30cdc3193e2a04829acfe34093a0b57fa4458975 Mon Sep 17 00:00:00 2001 From: Suchan Badyakar Date: Wed, 5 May 2021 20:45:02 +1000 Subject: [PATCH 8/8] update playstore description --- android/fastlane/metadata/android/en-US/full_description.txt | 2 +- android/fastlane/metadata/android/en-US/short_description.txt | 2 +- android/fastlane/metadata/android/en-US/title.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/fastlane/metadata/android/en-US/full_description.txt b/android/fastlane/metadata/android/en-US/full_description.txt index 539ac29..e679459 100644 --- a/android/fastlane/metadata/android/en-US/full_description.txt +++ b/android/fastlane/metadata/android/en-US/full_description.txt @@ -1 +1 @@ -React Native Starter Kit \ No newline at end of file +A personal expense manager app to manage all your income and expenses, It help budget your finance and get easy reports. \ No newline at end of file diff --git a/android/fastlane/metadata/android/en-US/short_description.txt b/android/fastlane/metadata/android/en-US/short_description.txt index 539ac29..7f293d2 100644 --- a/android/fastlane/metadata/android/en-US/short_description.txt +++ b/android/fastlane/metadata/android/en-US/short_description.txt @@ -1 +1 @@ -React Native Starter Kit \ No newline at end of file +A personal expense manager app to manage all your income and expenses \ No newline at end of file diff --git a/android/fastlane/metadata/android/en-US/title.txt b/android/fastlane/metadata/android/en-US/title.txt index 539ac29..272670b 100644 --- a/android/fastlane/metadata/android/en-US/title.txt +++ b/android/fastlane/metadata/android/en-US/title.txt @@ -1 +1 @@ -React Native Starter Kit \ No newline at end of file +Personal Expense Manager \ No newline at end of file