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 { 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 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a269ab5..ac5a5d3 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): @@ -304,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): @@ -363,6 +367,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`) @@ -373,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`) @@ -436,6 +442,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: @@ -456,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: @@ -504,6 +514,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 @@ -514,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..5787cfa --- /dev/null +++ b/src/actions/Backup/index.js @@ -0,0 +1,44 @@ +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, 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/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/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/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/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..84a15ea 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 && ( { theme={VictoryTheme.material} colorScale={colorScale} innerRadius={70} - labelRadius={150} + labelRadius={120} width={deviceWidth - 40} data={myData} events={[]} @@ -60,7 +59,7 @@ const ReportDetail = (props) => { theme={VictoryTheme.material} colorScale={colorScale} innerRadius={70} - labelRadius={150} + labelRadius={120} width={deviceWidth - 40} data={expenseData} events={[]} diff --git a/src/components/ReportPage/index.js b/src/components/ReportPage/index.js index 6fe7fe0..87540a7 100644 --- a/src/components/ReportPage/index.js +++ b/src/components/ReportPage/index.js @@ -115,15 +115,15 @@ const mapStateToProps = (state) => { 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 88a74a3..2ed2a9d 100644 --- a/src/components/SettingPage/index.js +++ b/src/components/SettingPage/index.js @@ -1,20 +1,251 @@ +/* eslint-disable dot-notation */ +import _ from 'lodash'; import React from 'react'; -import {StyleSheet, Text, View} from 'react-native'; +import {connect} from 'react-redux'; + +import {readFromFile, writetoFile} from '../../utils/fileManager'; +import {addBackup, resetDatabase, addAccounts} from '../../actions'; + +const sampelData = require('../../assets/sampledata/personal_expense_manager.json'); +import { + SettingsContainer, + SettingsContent, + SettingsButton, + SettingsTitle, + SettingsIcon, + SettingsSubTitle, +} from './styles'; + +const SettingPage = (props) => { + const {latestBackup} = props; + + const importDatabase = () => { + 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) => { + console.warn('erroror==>'); + alert(error); + }); + }; + + const exportDatabase = () => { + 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 && 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, + }; + }); + + writetoFile(allRecordDataForExport) + .then(() => { + props.addBackup({ + title: 'personal_expense_manager.csv', + date: new Date().toISOString(), + callback: function () { + alert('Database Exported Successfully'); + }, + }); + }) + .catch((error) => { + alert(error); + }); + }; + + 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 { + 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 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 SettingPage = () => { return ( - - Settings Page - + + + loadSampleData()}> + + Load Sample Data + + + importDatabase()}> + + Import Database + + + 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}; +}; -const styles = StyleSheet.create({ - wrapper: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, -}); +export default connect(mapStateToProps, { + addBackup, + resetDatabase, + addAccounts, +})(SettingPage); diff --git a/src/components/SettingPage/styles.js b/src/components/SettingPage/styles.js new file mode 100644 index 0000000..529a592 --- /dev/null +++ b/src/components/SettingPage/styles.js @@ -0,0 +1,30 @@ +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)``; + +export const SettingsSubTitle = styled(Text)` + font-size: ${(props) => props.theme.fontSizes.subtitle}; + color: ${(props) => props.theme.colors.ui.gray}; + padding-left: 54; +`; diff --git a/src/helpers/db.js b/src/helpers/db.js index f7421bc..cbbf77a 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,29 @@ export const insertAccount = (title, type, openingBalance, icon) => { (_, result) => { resolve(result); }, + (err) => { + console.warn(err); + reject(err); + }, + ); + }); + }); + + return promise; +}; + +export const insertAccounts = (accounts) => { + 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 +348,101 @@ 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( + `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/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: { 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, }); 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..8d713ba --- /dev/null +++ b/src/utils/fileManager.js @@ -0,0 +1,76 @@ +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'; + 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.DocumentDir}/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 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') + .then((file) => { + resolve(csvToJson(file)); + }) + .catch((error) => reject(error)); + }); +}; + +export {writetoFile, readFromFile};