From 3f7ba0da291eca097aadf6280b0110fe4942f1b2 Mon Sep 17 00:00:00 2001 From: rojanbade Date: Tue, 27 Jul 2021 14:56:55 +0545 Subject: [PATCH 01/59] project add and detail ui --- package.json | 5 +- src/assets/css/custom.css | 10 ++ src/routes/Router.js | 21 +++ src/views/project/add/index.js | 141 ++++++++++++++++++ src/views/project/detail/balance.js | 33 ++++ src/views/project/detail/detailCard.js | 52 +++++++ src/views/project/detail/index.js | 49 ++++++ src/views/project/detail/pieChart.js | 91 +++++++++++ src/views/project/detail/project.css | 38 +++++ src/views/project/detail/projectInfo.js | 53 +++++++ .../project/detail/tab/beneficiaryList.js | 115 ++++++++++++++ src/views/project/detail/tab/index.js | 62 ++++++++ src/views/project/detail/tab/vendorList.js | 95 ++++++++++++ 13 files changed, 763 insertions(+), 2 deletions(-) create mode 100644 src/views/project/add/index.js create mode 100644 src/views/project/detail/balance.js create mode 100644 src/views/project/detail/detailCard.js create mode 100644 src/views/project/detail/index.js create mode 100644 src/views/project/detail/pieChart.js create mode 100644 src/views/project/detail/project.css create mode 100644 src/views/project/detail/projectInfo.js create mode 100644 src/views/project/detail/tab/beneficiaryList.js create mode 100644 src/views/project/detail/tab/index.js create mode 100644 src/views/project/detail/tab/vendorList.js diff --git a/package.json b/package.json index 5a621fd5..7416421d 100644 --- a/package.json +++ b/package.json @@ -28,16 +28,17 @@ }, "devDependencies": { "axios-mock-adapter": "^1.18.1", + "eslint-config-prettier": "^8.1.0", + "eslint-plugin-prettier": "^3.3.1", "lodash": "^4.17.19", "mixin-deep": "^2.0.1", "node": "^8.15.0", - "eslint-config-prettier": "^8.1.0", - "eslint-plugin-prettier": "^3.3.1", "prettier": "2.2.1" }, "dependencies": { "add": "^2.0.6", "axios": "^0.21.1", + "bootstrap-switch-button-react": "^1.2.0", "chart.js": "^2.9.3", "dexie": "^3.0.3", "eth-crypto": "^1.9.0", diff --git a/src/assets/css/custom.css b/src/assets/css/custom.css index 959b1e72..14e7f8ff 100644 --- a/src/assets/css/custom.css +++ b/src/assets/css/custom.css @@ -152,3 +152,13 @@ a { display: 'flex'; justify-content: space-between; } + +.page-heading { + color: #6B6C72; + font-size: 24px; + font-weight: 400; +} + +.breadcrumb{ + background-color: #fff; +} \ No newline at end of file diff --git a/src/routes/Router.js b/src/routes/Router.js index 21a6473e..0b1083af 100644 --- a/src/routes/Router.js +++ b/src/routes/Router.js @@ -19,6 +19,13 @@ const ListUsers = lazy(() => import('../modules/user/list')); const AddUser = lazy(() => import('../modules/user/add')); const UserDetails = lazy(() => import('../modules/user/edit')); +// ------------------------------Project UI---------------------------------------- + +const ProjectAdd = lazy(() => import('../views/project/add')); +const ProjectDetail = lazy(() => import('../views/project/detail')); + +// -------------------------------------------------------------------------------- + var AppRoutes = [ { path: '/dashboard', @@ -68,6 +75,20 @@ var AppRoutes = [ component: AidList, showInSidebar: true }, + + // ----------------------------Project ui------------------------ + + { + path: '/add_project', + name: 'ProjectAdd', + component: ProjectAdd + }, + { + path: '/detail_project', + name: 'ProjectDetail', + component: ProjectDetail + }, + // ----------------------------------------------------------------- { path: '/beneficiaries', name: 'Beneficiary', diff --git a/src/views/project/add/index.js b/src/views/project/add/index.js new file mode 100644 index 00000000..8e11dcbe --- /dev/null +++ b/src/views/project/add/index.js @@ -0,0 +1,141 @@ +import React, { useState, useEffect } from 'react'; +// import { useToasts } from 'react-toast-notifications'; +import { + Breadcrumb, + BreadcrumbItem, + Card, + CardBody, + Row, + Col, + Form, + FormGroup, + Label, + Input, + Button +} from 'reactstrap'; + +// import { TOAST } from '../../../constants'; +// import { getUser } from '../../../utils/sessionManager'; +// import { UserContext } from '../../../contexts/UserContext'; +// import { AppContext } from '../../../contexts/AppSettingsContext'; +import { History } from '../../../utils/History'; +import WalletUnlock from '../../../modules/global/walletUnlock'; +// import GrowSpinner from '../../../modules/global/GrowSpinner'; + +// const current_user = getUser(); + +const AddProject = () => { + // const { addToast } = useToasts(); + // const { addUser } = useContext(UserContext); + // const { wallet, appSettings, isVerified, loading, setLoading, changeIsverified } = useContext(AppContext); + + const [passcodeModal, setPasscodeModal] = useState(false); + + const [formData, setFormData] = useState({ + name: '', + email: '', + phone: '', + wallet_address: '' + }); + const [selectedInstitution, setSelectedInstitution] = useState(''); + + const handleInputChange = e => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleInstitutionChange = e => { + setSelectedInstitution(e.target.value); + }; + + const handleFormSubmit = e => { + console.log(selectedInstitution); + return; + }; + + const handleCancelClick = () => History.push('/users'); + + const saveUserDetails = () => {}; + + useEffect(saveUserDetails); + + return ( +
+ setPasscodeModal(e)}> +

Projects

+ + + Projects + + Add + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* {loading ? ( + + ) : ( */} +
+ + +
+ {/* )} */} +
+
+
+
+ +
+
+ ); +}; + +export default AddProject; diff --git a/src/views/project/detail/balance.js b/src/views/project/detail/balance.js new file mode 100644 index 00000000..a61d4892 --- /dev/null +++ b/src/views/project/detail/balance.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { Card, CardTitle } from 'reactstrap'; +import './project.css'; + +export default function Balance(props) { + const { title, data, button_name, label } = props; + return ( +
+ +
+ {title || 'No Title'} +
+
+

{data || '0'}

+
+ {label || 'No Label'} +
+
+
+ +
+
+
+
+
+ ); +} diff --git a/src/views/project/detail/detailCard.js b/src/views/project/detail/detailCard.js new file mode 100644 index 00000000..9c21f42d --- /dev/null +++ b/src/views/project/detail/detailCard.js @@ -0,0 +1,52 @@ +import React from 'react'; +import { Card, CardTitle } from 'reactstrap'; +import BootstrapSwitchButton from 'bootstrap-switch-button-react'; +import './project.css'; + +export default function DetailCard(props) { + const { title, button_name, name, name_value, total, total_value } = props; + return ( +
+ +
+
+ + {title || 'No Title'} + +
+ {title === 'Project Details' ? ( + + ) : ( + + )} +
+
+
+
+

{name_value || '0'}

+
{name || 'No Label'}
+
+
+

{total_value || '0'}

+
{total || 'No Label'}
+
+
+
+
+
+ ); +} diff --git a/src/views/project/detail/index.js b/src/views/project/detail/index.js new file mode 100644 index 00000000..4f2bda60 --- /dev/null +++ b/src/views/project/detail/index.js @@ -0,0 +1,49 @@ +import React from 'react'; +import { Breadcrumb, BreadcrumbItem, Row, Col } from 'reactstrap'; + +import Balance from './balance'; +import DetailCard from './detailCard'; +import ProjectInfo from './projectInfo'; +import PieChart from './pieChart'; +import Tabs from './tab/index'; + +const ProjectDetail = () => { + return ( + <> +

Projects

+ + + Projects + + Detail + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default ProjectDetail; diff --git a/src/views/project/detail/pieChart.js b/src/views/project/detail/pieChart.js new file mode 100644 index 00000000..a6dec1c0 --- /dev/null +++ b/src/views/project/detail/pieChart.js @@ -0,0 +1,91 @@ +import React, { useContext, useState, useEffect } from 'react'; +import { Card, CardBody, CardTitle } from 'reactstrap'; +import { Pie } from 'react-chartjs-2'; +import { UserContext } from '../../../contexts/UserContext'; +import { useToasts } from 'react-toast-notifications'; + +let _data = []; +let _labels = []; + +let pieData = { + labels: _labels, + datasets: [ + { + data: _data, + pointRadius: 1, + pointHitRadius: 10, + backgroundColor: ['#245064', '#80D5AA', '#F49786', '#F7C087', '#2b7ec1', '#fb6340', '#527855'], + hoverBackgroundColor: ['#245064', '#80D5AA', '#F49786', '#F7C087', '#2b7ec1', '#fb6340', '#527855'], + hoverOffset: 100 + } + ] +}; + +export default function Chart() { + const { addToast } = useToasts(); + + const { getDashboardStats } = useContext(UserContext); + const [stats, setStats] = useState({ + totalAllocation: 0, + redeemedTokens: 0, + beneficiariesByProject: [] + }); + + const fetchDashboardStats = () => { + getDashboardStats() + .then(d => { + setStats(prevState => ({ + ...prevState, + totalAllocation: d.tokenAllocation.totalAllocation, + redeemedTokens: d.tokenRedemption.totalTokenRedemption, + beneficiariesByProject: d.beneficiary.project + })); + }) + .catch(() => { + addToast('Internal server error!', { + appearance: 'error', + autoDismiss: true + }); + }); + }; + + useEffect(fetchDashboardStats, []); + + const data = stats.beneficiariesByProject; + if (data && data.length) { + _labels = []; + _data = []; + for (let d of data) { + _labels.push(d.name); + _data.push(d.count); + } + pieData.labels = _labels; + pieData.datasets[0].data = _data; + } + + return ( +
+ + + Token Detail +
+ +
+
+
+
+ ); +} diff --git a/src/views/project/detail/project.css b/src/views/project/detail/project.css new file mode 100644 index 00000000..8415d70e --- /dev/null +++ b/src/views/project/detail/project.css @@ -0,0 +1,38 @@ +.title { + color: #6B6C72; + font-size: 18px !important; + font-weight: 500 !important; + font-family: 'Be Vietnam' !important; + font-style: normal; + line-height: 20px; + letter-spacing: 0em; + text-align: left; + margin-bottom: 20px !important; +} + +.stat-card-body { + background: #ffffff; + border-radius: 10px !important; + padding: 20px 30px; +} + +.card-font-medium { + font-size: 18px !important; + font-weight: normal; + font-style: normal; + margin-bottom: 0px !important; +} + +.card-font-bold { + font-size: 24px !important; + font-weight: 500; + margin-bottom: 0px !important; +} + +.sub-title { + font-weight: 400; + margin-bottom: 0px; + color: #9b9b9b; + font-size: 14px; + margin: 0; +} diff --git a/src/views/project/detail/projectInfo.js b/src/views/project/detail/projectInfo.js new file mode 100644 index 00000000..ab393ca9 --- /dev/null +++ b/src/views/project/detail/projectInfo.js @@ -0,0 +1,53 @@ +import React from 'react'; +import { Card, CardTitle } from 'reactstrap'; +import './project.css'; + +export default function ProjectInfo() { + return ( +
+ +
+
+ + More Information + +
+ +
+
+
+
+
+

Susma shahi thakuri

+
Project Manager
+
+
+

Sindhupalchowk, Kavre

+
Location
+
+
+
+
+

Anish lama tamang

+
Assigned Social Mobilizer
+
+
+

2020-02-23

+
Created Date
+
+
+
+

+ Due to landslide, flood and on going covid-19 situation many people living in Sindhupalchowk have been + effected. They dont have food, shelter and good health care. So we with the joint hand from UNICEF are + planning to do rahat distribution in this area. With the aim to serve atleast twenty thousand people in that + area, we have managed one crore rupees. Hope this small gesture bring a little smile and ease their + suffering. +

+
+
+
+ ); +} diff --git a/src/views/project/detail/tab/beneficiaryList.js b/src/views/project/detail/tab/beneficiaryList.js new file mode 100644 index 00000000..a1928b86 --- /dev/null +++ b/src/views/project/detail/tab/beneficiaryList.js @@ -0,0 +1,115 @@ +import React, { useContext, useEffect } from 'react'; +import { Pagination, PaginationItem, PaginationLink, Table } from 'reactstrap'; +import { useToasts } from 'react-toast-notifications'; + +import { AidContext } from '../../../../contexts/AidContext'; + +const List = () => { + const { aids, pagination, listAid } = useContext(AidContext); + const { addToast } = useToasts(); + + const handlePagination = current_page => { + let _start = (current_page - 1) * pagination.limit; + return loadAidList({ start: _start, limit: pagination.limit }); + }; + + const loadAidList = () => { + let query = {}; + listAid(query) + .then() + .catch(() => { + addToast('Something went wrong!', { + appearance: 'error', + autoDismiss: true + }); + }); + }; + + useEffect(loadAidList); + + return ( + <> +
+
+ + +
+ +
+ +
+
+ + + + + + + + + + + + {aids.length ? ( + aids.map(d => { + return ( + + + + + + + + ); + }) + ) : ( + + + + + )} + +
NameAddressPhone numberTotal Token IssuedTotal Token Redeemed
XYZKavre98674532121500500
No data available.
+ + {pagination.totalPages > 1 ? ( + + + handlePagination(1)} /> + + {[...Array(pagination.totalPages)].map((p, i) => ( + handlePagination(i + 1)} + > + {i + 1} + + ))} + + handlePagination(pagination.totalPages)} /> + + + ) : ( + '' + )} + + ); +}; + +export default List; diff --git a/src/views/project/detail/tab/index.js b/src/views/project/detail/tab/index.js new file mode 100644 index 00000000..88d54a93 --- /dev/null +++ b/src/views/project/detail/tab/index.js @@ -0,0 +1,62 @@ +import React, { useState } from 'react'; +import { TabContent, TabPane, Nav, NavItem, NavLink, Card, Row, Col } from 'reactstrap'; +import classnames from 'classnames'; +import BeneficiaryList from './beneficiaryList'; +import VendorList from './vendorList'; + +const Tabs = () => { + const [activeTab, setActiveTab] = useState('1'); + + const toggle = tab => { + if (activeTab !== tab) setActiveTab(tab); + }; + + return ( +
+ +
+ + + + + + + + + + + + + + + + + +
+
+
+ ); +}; + +export default Tabs; diff --git a/src/views/project/detail/tab/vendorList.js b/src/views/project/detail/tab/vendorList.js new file mode 100644 index 00000000..5e086625 --- /dev/null +++ b/src/views/project/detail/tab/vendorList.js @@ -0,0 +1,95 @@ +import React, { useContext, useEffect } from 'react'; +import { Pagination, PaginationItem, PaginationLink, Table } from 'reactstrap'; +import { useToasts } from 'react-toast-notifications'; + +import { AidContext } from '../../../../contexts/AidContext'; + +const List = () => { + const { aids, pagination, listAid } = useContext(AidContext); + const { addToast } = useToasts(); + + const handlePagination = current_page => { + let _start = (current_page - 1) * pagination.limit; + return loadAidList({ start: _start, limit: pagination.limit }); + }; + + const loadAidList = () => { + let query = {}; + listAid(query) + .then() + .catch(() => { + addToast('Something went wrong!', { + appearance: 'error', + autoDismiss: true + }); + }); + }; + + useEffect(loadAidList, []); + + return ( + <> + + + + + + + + + + + + {aids.length ? ( + aids.map(d => { + return ( + + + + + + + + ); + }) + ) : ( + + + + + )} + +
NameAddressPhone numberBalanceTotal Token Redeemed
XYZKavre986745321215000050000
No data available.
+ + {pagination.totalPages > 1 ? ( + + + handlePagination(1)} /> + + {[...Array(pagination.totalPages)].map((p, i) => ( + handlePagination(i + 1)} + > + {i + 1} + + ))} + + handlePagination(pagination.totalPages)} /> + + + ) : ( + '' + )} + + ); +}; + +export default List; From c168c1bcf816bc23b0b57f2c07ff2a8a2d1dbb43 Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Wed, 28 Jul 2021 16:18:17 +0545 Subject: [PATCH 02/59] fix report export --- src/modules/dashboard/beneficiary_by_project/index.js | 3 +-- src/modules/dashboard/tokens_by_project/index.js | 7 +------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/modules/dashboard/beneficiary_by_project/index.js b/src/modules/dashboard/beneficiary_by_project/index.js index 7846e4ff..cb5986e9 100644 --- a/src/modules/dashboard/beneficiary_by_project/index.js +++ b/src/modules/dashboard/beneficiary_by_project/index.js @@ -1,7 +1,6 @@ import React from 'react'; import { Card, CardBody, CardTitle } from 'reactstrap'; import { Pie } from 'react-chartjs-2'; -import moment from 'moment'; import { ExportToExcel } from '../../global/ExportToExcel'; @@ -62,7 +61,7 @@ export default function Index(props) { Beneficiaries by project ({data.length})
{exportData.length ? ( - + ) : ( '' )} diff --git a/src/modules/dashboard/tokens_by_project/index.js b/src/modules/dashboard/tokens_by_project/index.js index ae4b7549..399e49e5 100644 --- a/src/modules/dashboard/tokens_by_project/index.js +++ b/src/modules/dashboard/tokens_by_project/index.js @@ -1,7 +1,6 @@ import React from 'react'; import { Bar } from 'react-chartjs-2'; import { Card, CardBody, CardTitle } from 'reactstrap'; -import moment from 'moment'; import { ExportToExcel } from '../../global/ExportToExcel'; @@ -68,11 +67,7 @@ const Index = props => {
Tokens by project
- {exportData.length ? ( - - ) : ( - '' - )} + {exportData.length ? : ''}

From 59a5be88e50c854a8398418c5454a3839ddd0678 Mon Sep 17 00:00:00 2001 From: rojanbade Date: Fri, 30 Jul 2021 11:48:58 +0545 Subject: [PATCH 03/59] project ui update and beneficiary ui added --- package.json | 1 + src/assets/css/custom.css | 29 +++ src/assets/images/ID.jpg | Bin 0 -> 46832 bytes src/assets/scss/all/custom/_variables.scss | 2 +- src/assets/scss/style.css | 34 +-- src/modules/global/LiteModal.js | 76 ++++-- src/routes/Router.js | 48 ++++ src/views/balance.js | 43 ++++ src/views/beneficiaries/add/index.js | 208 +++++++++++++++++ .../beneficiaries/detail/beneficiaryInfo.js | 69 ++++++ src/views/beneficiaries/detail/index.js | 44 ++++ src/views/detailCard.js | 56 +++++ src/views/{project/detail => }/project.css | 4 + src/views/project/add/index.js | 2 +- src/views/project/detail/balance.js | 33 --- src/views/project/detail/budgetAdd.js | 63 +++++ src/views/project/detail/detailCard.js | 52 ----- src/views/project/detail/index.js | 4 +- src/views/project/detail/projectInfo.js | 39 ++-- src/views/projectInvolved.js | 38 +++ src/views/vendors/add/index.js | 217 ++++++++++++++++++ src/views/vendors/detail/index.js | 0 yarn.lock | 50 ++++ 23 files changed, 971 insertions(+), 141 deletions(-) create mode 100644 src/assets/images/ID.jpg create mode 100644 src/views/balance.js create mode 100644 src/views/beneficiaries/add/index.js create mode 100644 src/views/beneficiaries/detail/beneficiaryInfo.js create mode 100644 src/views/beneficiaries/detail/index.js create mode 100644 src/views/detailCard.js rename src/views/{project/detail => }/project.css (94%) delete mode 100644 src/views/project/detail/balance.js create mode 100644 src/views/project/detail/budgetAdd.js delete mode 100644 src/views/project/detail/detailCard.js create mode 100644 src/views/projectInvolved.js create mode 100644 src/views/vendors/add/index.js create mode 100644 src/views/vendors/detail/index.js diff --git a/package.json b/package.json index 7416421d..cfd65103 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "query-string": "^6.13.1", "react": "^16.13.1", "react-chartjs-2": "^2.10.0", + "react-digit-input": "^2.1.0", "react-dom": "^16.13.1", "react-hook-form": "^5.7.2", "react-iframe": "1.3.0", diff --git a/src/assets/css/custom.css b/src/assets/css/custom.css index 14e7f8ff..ddd79d9b 100644 --- a/src/assets/css/custom.css +++ b/src/assets/css/custom.css @@ -157,8 +157,37 @@ a { color: #6B6C72; font-size: 24px; font-weight: 400; + margin-bottom: 0px ; } .breadcrumb{ background-color: #fff; + padding-left: 0px !important; + font-size: 14px; +} + +.modal-backdrop{ + opacity:1 !important; +} +.input-pin{ + background-color: black; + color: white; + width: 100px; + height: 100px; + text-align: center; + font-size: 36px; + border-style: solid; + border-width: 5px; + border-color: white; + margin-right: 10px; +} + +.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active, .show > .btn-outline-info.dropdown-toggle { + background-color: #2b7ec1; + border-color: #2b7ec1; +} + +.btn-outline-info:hover { + background-color: #2b7ec1; + border-color: #2b7ec1; } \ No newline at end of file diff --git a/src/assets/images/ID.jpg b/src/assets/images/ID.jpg new file mode 100644 index 0000000000000000000000000000000000000000..93fb35f7f2e5d7739aff168a7454b179c979ecee GIT binary patch literal 46832 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 z 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 LmnWaorhot04l?R= literal 0 HcmV?d00001 diff --git a/src/assets/scss/all/custom/_variables.scss b/src/assets/scss/all/custom/_variables.scss index 2783d2ea..5536cd4b 100644 --- a/src/assets/scss/all/custom/_variables.scss +++ b/src/assets/scss/all/custom/_variables.scss @@ -198,7 +198,7 @@ $box-shadow-lg: 0 1rem 3rem rgba($black, 0.175) !default; // ====================================================== $main-body-bg: #fff !default; $body-bg: #eef5f9; -$body-color: #3e5569 !default; +$body-color: #6B6C72 !default; // ====================================================== // Typography diff --git a/src/assets/scss/style.css b/src/assets/scss/style.css index c65bcbbe..88d25c18 100644 --- a/src/assets/scss/style.css +++ b/src/assets/scss/style.css @@ -143,7 +143,7 @@ body { font-size: 0.875rem; font-weight: 400; line-height: 1.5; - color: #3e5569; + color: #5a636b; text-align: left; background-color: #eef5f9; } @@ -2042,7 +2042,7 @@ pre code { .table { width: 100%; margin-bottom: 1rem; - color: #3e5569; + color: #6B6C72; } .table th, .table td { @@ -2087,7 +2087,7 @@ pre code { } .table-hover tbody tr:hover { - color: #3e5569; + color: #6B6C72; background-color: #f6f9fc; } @@ -2564,7 +2564,7 @@ select.form-control:focus::-ms-value { padding-bottom: 0.375rem; margin-bottom: 0; line-height: 1.5; - color: #3e5569; + color: #6B6C72; background-color: transparent; border: solid transparent; border-width: 1px 0; @@ -3059,7 +3059,7 @@ textarea.form-control.is-invalid { .btn { display: inline-block; font-weight: 400; - color: #3e5569; + color: #6B6C72; text-align: center; vertical-align: middle; -webkit-user-select: none; @@ -3093,7 +3093,7 @@ textarea.form-control.is-invalid { } } .btn:hover { - color: #3e5569; + color: #6B6C72; text-decoration: none; } .btn:focus, @@ -4173,7 +4173,7 @@ input[type="button"].btn-block { padding: 0.5rem 0; margin: 0.125rem 0 0; font-size: 0.875rem; - color: #3e5569; + color: #6B6C72; text-align: left; list-style: none; background-color: #fff; @@ -6756,7 +6756,7 @@ a.badge-inverse.focus { background-color: #f6f9fc; } .list-group-item-action:active { - color: #3e5569; + color: #6B6C72; background-color: #e9ecef; } @@ -7719,7 +7719,7 @@ a.close.disabled { .popover-body { padding: 0.5rem 0.75rem; - color: #3e5569; + color: #6B6C72; } .carousel { @@ -12417,7 +12417,7 @@ a.text-inverse:focus { } .text-body { - color: #3e5569 !important; + color: #6B6C72 !important; } .text-muted { @@ -13173,7 +13173,7 @@ Rounds body { margin: 0; overflow-x: hidden; - color: #3e5569; + color: #6B6C72; background: #fff; } @@ -13182,7 +13182,7 @@ html { } a.link { - color: #3e5569; + color: #6B6C72; } a.link:hover, a.link:focus { @@ -13228,7 +13228,7 @@ ul.list-style-none li { list-style: none; } ul.list-style-none li a { - color: #3e5569; + color: #6B6C72; padding: 8px 0px; display: block; text-decoration: none; @@ -14577,7 +14577,7 @@ Preloader .nav-tabs .nav-link, .nav-pills .nav-link { - color: #3e5569; + color: #6B6C72; } .nav-tabs .nav-link:hover, .nav-pills .nav-link:hover { @@ -14906,7 +14906,7 @@ ol.progtrckr li.progtrckr-done:hover:before { .top-navbar .navbar-header[data-logobg="skin6"] .topbartoggler { - color: #3e5569; + color: #6B6C72; } #main-wrapper[data-layout="vertical"] .topbar @@ -16368,7 +16368,7 @@ customizer } .customizer .customizer-tab .nav-item .nav-link { padding: 15px 12px; - color: #3e5569; + color: #6B6C72; border-bottom: 3px solid transparent; } .customizer .customizer-tab .nav-item .nav-link:hover, @@ -16495,7 +16495,7 @@ customizer .contact-app .list-group .list-group-item .list-group-item-action { padding: 12px 15px; display: block; - color: #3e5569; + color: #6B6C72; } .email-app .list-group .list-group-item .list-group-item-action .mdi, .contact-app .list-group .list-group-item .list-group-item-action .mdi { diff --git a/src/modules/global/LiteModal.js b/src/modules/global/LiteModal.js index 47c504b8..330ac8bc 100644 --- a/src/modules/global/LiteModal.js +++ b/src/modules/global/LiteModal.js @@ -1,26 +1,64 @@ -import React from 'react'; -import { Form, Modal, ModalHeader, ModalBody } from 'reactstrap'; +import React, { useState } from 'react'; +import { Form, Modal, ModalBody } from 'reactstrap'; import PropTypes from 'prop-types'; +import useDigitInput from 'react-digit-input'; export default function CustomModal(props) { - const { children, title, loadingMessage, open, size, handleSubmit } = props; - - return ( - <> - -
- {title || 'Modal Title'} - {children || 'No child elements supplied.'} -
- {loadingMessage ? loadingMessage : ''} -
-
-
- - ); + const { + // children, + title, + loadingMessage, + open, + size, + handleSubmit + } = props; + const [value, onChange] = useState(''); + const digits = useDigitInput({ + acceptedCharacters: /^[0-9]$/, + length: 6, + value, + onChange + }); + return ( + <> + +
+ + {/* {children || 'No child elements supplied.'} */} +

{title || 'Modal Title'}

+
+
+ + + + + + +
+
+								"{value}"
+							
+
+
+ {loadingMessage ? loadingMessage : ''} +
+
+
+
+ + ); } CustomModal.propTypes = { - toggle: PropTypes.func.isRequired, - open: PropTypes.bool.isRequired + toggle: PropTypes.func.isRequired, + open: PropTypes.bool.isRequired }; diff --git a/src/routes/Router.js b/src/routes/Router.js index 0b1083af..882224ee 100644 --- a/src/routes/Router.js +++ b/src/routes/Router.js @@ -23,6 +23,20 @@ const UserDetails = lazy(() => import('../modules/user/edit')); const ProjectAdd = lazy(() => import('../views/project/add')); const ProjectDetail = lazy(() => import('../views/project/detail')); +const BudgetAdd = lazy(() => import('../views/project/detail/budgetAdd')); +// -------------------------------------------------------------------------------- + +// ------------------------------Beneficiary UI------------------------------------ + +const BeneficiaryAdd = lazy(() => import('../views/beneficiaries/add')); +const BeneficiaryDetail = lazy(() => import('../views/beneficiaries/detail')); + +// -------------------------------------------------------------------------------- + +// ------------------------------Vendor UI------------------------------------ + +const VendorAdd = lazy(() => import('../views/vendors/add')); +const VendorDetail = lazy(() => import('../views/vendors/detail')); // -------------------------------------------------------------------------------- @@ -88,7 +102,41 @@ var AppRoutes = [ name: 'ProjectDetail', component: ProjectDetail }, + { + path: '/add_budget', + name: 'BudgetAdd', + component: BudgetAdd + }, + // ----------------------------------------------------------------- + + //.............................Beneficiary ui...................... + + { + path: '/add_beneficiary', + name: 'BeneficiaryAdd', + component: BeneficiaryAdd + }, + { + path: '/detail_beneficiary', + name: 'BeneficiaryDetail', + component: BeneficiaryDetail + }, + // ----------------------------------------------------------------- + + //.............................Vendor ui...................... + + { + path: '/add_vendor', + name: 'VendorAdd', + component: VendorAdd + }, + { + path: '/detail_vendor', + name: 'VendorDetail', + component: VendorDetail + }, // ----------------------------------------------------------------- + { path: '/beneficiaries', name: 'Beneficiary', diff --git a/src/views/balance.js b/src/views/balance.js new file mode 100644 index 00000000..c127e312 --- /dev/null +++ b/src/views/balance.js @@ -0,0 +1,43 @@ +import React from 'react'; +import { Card, CardTitle, Col, Row } from 'reactstrap'; +import { useHistory } from 'react-router-dom'; +import './project.css'; + +export default function Balance(props) { + const { title, data, button_name, label } = props; + const history = useHistory(); + const handleClick = () => { + history.push('/add_budget'); + }; + return ( +
+ +
+ {title || 'No Title'} + + +

{data || '0'}

+
+ {label || 'No Label'} +
+ + + + +
+ {/*
+
+
+
*/} +
+
+
+ ); +} diff --git a/src/views/beneficiaries/add/index.js b/src/views/beneficiaries/add/index.js new file mode 100644 index 00000000..8a884bdf --- /dev/null +++ b/src/views/beneficiaries/add/index.js @@ -0,0 +1,208 @@ +import React, { useState, useEffect } from 'react'; +import { + Breadcrumb, + BreadcrumbItem, + Card, + CardBody, + Row, + Col, + Form, + FormGroup, + Label, + Input, + Button +} from 'reactstrap'; +import { History } from '../../../utils/History'; +import WalletUnlock from '../../../modules/global/walletUnlock'; +const AddProject = () => { + const [passcodeModal, setPasscodeModal] = useState(false); + + const [formData, setFormData] = useState({ + project: '', + name: '', + email: '', + age: '', + address: '', + education: '', + profession: '', + governmentID: '', + family_members: '', + adult: '', + child: '', + + wallet_address: '' + }); + const [selectedGender, setSelectedGender] = useState(''); + const [selectedGroup, setSelectedGroup] = useState(''); + + const handleInputChange = e => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleGenderChange = e => { + setSelectedGender(e.target.value); + }; + const handleGroupChange = e => { + setSelectedGroup(e.target.value); + }; + + const handleFormSubmit = e => { + console.log(selectedGender); + console.log(selectedGroup); + + return; + }; + + const handleCancelClick = () => History.push('/users'); + + const saveUserDetails = () => {}; + + useEffect(saveUserDetails); + + return ( +
+ setPasscodeModal(e)}> +

Beneficiary

+ + + Beneficiary + + Add + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + + + + + + + + + + + + + + + {/* {loading ? ( + + ) : ( */} +
+ + +
+ {/* )} */} +
+
+
+
+ +
+
+ ); +}; + +export default AddProject; diff --git a/src/views/beneficiaries/detail/beneficiaryInfo.js b/src/views/beneficiaries/detail/beneficiaryInfo.js new file mode 100644 index 00000000..8626d714 --- /dev/null +++ b/src/views/beneficiaries/detail/beneficiaryInfo.js @@ -0,0 +1,69 @@ +import React from 'react'; +import { Card, CardTitle, Col, Row } from 'reactstrap'; +import '../../project.css'; +import image from '../../../assets/images/ID.jpg'; +export default function BeneficiaryInfo() { + return ( +
+ +
+
+ + More Information + +
+ +
+
+ + +
+

Sindhupalchowk , Kavre

+
Address
+
+
+

1243567

+
Government ID number
+
+
+

Differently able

+
Group
+
+
+

5

+
Number of family member (Adult)
+
+ + +
+

Bachelors in computer science

+
Education
+
+
+

Female

+
Gender
+
+
+

45

+
Age
+
+
+

10

+
Number of family member(Child)
+
+ + +
+

Software Engineer

+
Profession
+
+ certificate + +
+
+
+
+ ); +} diff --git a/src/views/beneficiaries/detail/index.js b/src/views/beneficiaries/detail/index.js new file mode 100644 index 00000000..6723ffe2 --- /dev/null +++ b/src/views/beneficiaries/detail/index.js @@ -0,0 +1,44 @@ +import React from 'react'; +import { Breadcrumb, BreadcrumbItem, Row, Col } from 'reactstrap'; +import Balance from '../../balance'; +import DetailCard from '../../detailCard'; +import BeneficiaryInfo from './beneficiaryInfo'; +import ProjectInvovled from '../../projectInvolved'; + +const projects = [ + { id: '0', name: 'Sindhupalchowk relief' }, + { id: '1', name: 'Flood relief distribution' } +]; +const ProjectDetail = () => { + return ( + <> +

Beneficiary

+ + + Beneficiary + + Detail + + + + + + + + + + + + + + ); +}; + +export default ProjectDetail; diff --git a/src/views/detailCard.js b/src/views/detailCard.js new file mode 100644 index 00000000..9275aee4 --- /dev/null +++ b/src/views/detailCard.js @@ -0,0 +1,56 @@ +import React from 'react'; +import { Card, CardTitle, Col, Row } from 'reactstrap'; +import BootstrapSwitchButton from 'bootstrap-switch-button-react'; +import './project.css'; + +export default function DetailCard(props) { + const { title, button_name, name, name_value, total, total_value } = props; + return ( +
+ +
+ + + + {title || 'No Title'} + + + +
+ {title === 'Project Details' ? ( + + ) : ( + + )} +
+ +
+ + +

{name_value || '0'}

+
{name || 'No Label'}
+ + +

{total_value || '0'}

+
{total || 'No Label'}
+ +
+
+
+
+ ); +} diff --git a/src/views/project/detail/project.css b/src/views/project.css similarity index 94% rename from src/views/project/detail/project.css rename to src/views/project.css index 8415d70e..34597c52 100644 --- a/src/views/project/detail/project.css +++ b/src/views/project.css @@ -36,3 +36,7 @@ font-size: 14px; margin: 0; } + +.card-data{ + margin-bottom: 25px; +} diff --git a/src/views/project/add/index.js b/src/views/project/add/index.js index 8e11dcbe..a8d1b1e1 100644 --- a/src/views/project/add/index.js +++ b/src/views/project/add/index.js @@ -72,7 +72,7 @@ const AddProject = () => { -
+ diff --git a/src/views/project/detail/balance.js b/src/views/project/detail/balance.js deleted file mode 100644 index a61d4892..00000000 --- a/src/views/project/detail/balance.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { Card, CardTitle } from 'reactstrap'; -import './project.css'; - -export default function Balance(props) { - const { title, data, button_name, label } = props; - return ( -
- -
- {title || 'No Title'} -
-
-

{data || '0'}

-
- {label || 'No Label'} -
-
-
- -
-
-
-
-
- ); -} diff --git a/src/views/project/detail/budgetAdd.js b/src/views/project/detail/budgetAdd.js new file mode 100644 index 00000000..4e4208b2 --- /dev/null +++ b/src/views/project/detail/budgetAdd.js @@ -0,0 +1,63 @@ +import React, { useState, useCallback } from 'react'; +import { Button, Card, CardTitle, Col, Form, FormGroup, Input, InputGroup, InputGroupAddon, Row } from 'reactstrap'; +import '../../project.css'; +import UnlockWallet from '../../../modules/global/walletUnlock'; + +export default function BudgetAdd() { + const [inputTokens, setInputToken] = useState(''); + const [passcodeModal, setPasscodeModal] = useState(false); + + const togglePasscodeModal = useCallback(() => setPasscodeModal(!passcodeModal), [passcodeModal]); + + const handleInputChange = e => { + let { value } = e.target; + setInputToken(value); + }; + + const handleTokenSubmit = e => { + e.preventDefault(); + togglePasscodeModal(); + }; + return ( +
+ setPasscodeModal(e)}> + +
+ Budget + + +

10,000,000

+
Total Project Budget
+ + +

50,000

+
Total Redeemed Budget
+ +
+
+
+ +
+ Add Budget + + + + + + + + + + +
+
+
+ ); +} diff --git a/src/views/project/detail/detailCard.js b/src/views/project/detail/detailCard.js deleted file mode 100644 index 9c21f42d..00000000 --- a/src/views/project/detail/detailCard.js +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { Card, CardTitle } from 'reactstrap'; -import BootstrapSwitchButton from 'bootstrap-switch-button-react'; -import './project.css'; - -export default function DetailCard(props) { - const { title, button_name, name, name_value, total, total_value } = props; - return ( -
- -
-
- - {title || 'No Title'} - -
- {title === 'Project Details' ? ( - - ) : ( - - )} -
-
-
-
-

{name_value || '0'}

-
{name || 'No Label'}
-
-
-

{total_value || '0'}

-
{total || 'No Label'}
-
-
-
-
-
- ); -} diff --git a/src/views/project/detail/index.js b/src/views/project/detail/index.js index 4f2bda60..b5122e79 100644 --- a/src/views/project/detail/index.js +++ b/src/views/project/detail/index.js @@ -1,8 +1,8 @@ import React from 'react'; import { Breadcrumb, BreadcrumbItem, Row, Col } from 'reactstrap'; -import Balance from './balance'; -import DetailCard from './detailCard'; +import Balance from '../../balance'; +import DetailCard from '../../detailCard'; import ProjectInfo from './projectInfo'; import PieChart from './pieChart'; import Tabs from './tab/index'; diff --git a/src/views/project/detail/projectInfo.js b/src/views/project/detail/projectInfo.js index ab393ca9..24a4ac96 100644 --- a/src/views/project/detail/projectInfo.js +++ b/src/views/project/detail/projectInfo.js @@ -1,24 +1,30 @@ import React from 'react'; -import { Card, CardTitle } from 'reactstrap'; -import './project.css'; +import { Card, CardTitle, Col, Row } from 'reactstrap'; +import '../../project.css'; export default function ProjectInfo() { return (
-
- - More Information - -
- -
-
-
-
+ + + +

Susma shahi thakuri

Project Manager
@@ -27,8 +33,8 @@ export default function ProjectInfo() {

Sindhupalchowk, Kavre

Location
-
-
+ +

Anish lama tamang

Assigned Social Mobilizer
@@ -37,8 +43,9 @@ export default function ProjectInfo() {

2020-02-23

Created Date
-
-
+ + +

Due to landslide, flood and on going covid-19 situation many people living in Sindhupalchowk have been effected. They dont have food, shelter and good health care. So we with the joint hand from UNICEF are diff --git a/src/views/projectInvolved.js b/src/views/projectInvolved.js new file mode 100644 index 00000000..e163fe14 --- /dev/null +++ b/src/views/projectInvolved.js @@ -0,0 +1,38 @@ +import React from 'react'; +import { Card, CardTitle } from 'reactstrap'; +import './project.css'; + +export default function ProjectInvovled(props) { + const { projects } = props; + return ( +

+ +
+ + Projects Involved + +
+
+
+ {projects ? ( + projects.map(project => ( + + )) + ) : ( +

No projects involved

+ )} +
+
+
+
+
+
+ ); +} diff --git a/src/views/vendors/add/index.js b/src/views/vendors/add/index.js new file mode 100644 index 00000000..8e9ec549 --- /dev/null +++ b/src/views/vendors/add/index.js @@ -0,0 +1,217 @@ +import React, { useState, useEffect } from 'react'; +import { + Breadcrumb, + BreadcrumbItem, + Card, + CardBody, + Row, + Col, + Form, + FormGroup, + Label, + Input, + Button +} from 'reactstrap'; +import { History } from '../../../utils/History'; +import WalletUnlock from '../../../modules/global/walletUnlock'; +const AddProject = () => { + const [passcodeModal, setPasscodeModal] = useState(false); + + const [formData, setFormData] = useState({ + project: '', + name: '', + email: '', + age: '', + address: '', + education: '', + profession: '', + governmentID: '', + family_members: '', + adult: '', + child: '', + + wallet_address: '' + }); + const [selectedGender, setSelectedGender] = useState(''); + const [selectedGroup, setSelectedGroup] = useState(''); + + const handleInputChange = e => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleGenderChange = e => { + setSelectedGender(e.target.value); + }; + const handleGroupChange = e => { + setSelectedGroup(e.target.value); + }; + + const handleFormSubmit = e => { + console.log(selectedGender); + console.log(selectedGroup); + + return; + }; + + const handleCancelClick = () => History.push('/users'); + + const saveUserDetails = () => {}; + + useEffect(saveUserDetails); + + return ( +
+ setPasscodeModal(e)}> +

Vendor

+ + + Vendor + + Add + + + + + +
+ + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + + + + + + + + + + + + + + + {/* {loading ? ( + + ) : ( */} +
+ + +
+ {/* )} */} +
+
+
+
+ +
+
+ ); +}; + +export default AddProject; diff --git a/src/views/vendors/detail/index.js b/src/views/vendors/detail/index.js new file mode 100644 index 00000000..e69de29b diff --git a/yarn.lock b/yarn.lock index 9fc386c2..35c13c44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3625,6 +3625,13 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +bootstrap-switch-button-react@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/bootstrap-switch-button-react/-/bootstrap-switch-button-react-1.2.0.tgz#cbb3d8f208b4f2548500b907ce489713641ea3f5" + integrity sha512-p/l+Rp69VtXpK0Y2PR9XYE1TPuypxF+1CpBPmvLUpwXjCisuxtl2S8AF6PqeLDxizFeHuW5nUE6qnqrZY/9ZQA== + dependencies: + react "^16.11.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -5408,6 +5415,11 @@ escodegen@^1.11.0, escodegen@^1.9.1: optionalDependencies: source-map "~0.6.1" +eslint-config-prettier@^8.1.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" + integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== + eslint-config-react-app@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-5.2.1.tgz#698bf7aeee27f0cea0139eaef261c7bf7dd623df" @@ -5482,6 +5494,13 @@ eslint-plugin-jsx-a11y@6.2.3: has "^1.0.3" jsx-ast-utils "^2.2.1" +eslint-plugin-prettier@^3.3.1: + version "3.4.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.0.tgz#cdbad3bf1dbd2b177e9825737fe63b476a08f0c7" + integrity sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw== + dependencies: + prettier-linter-helpers "^1.0.0" + eslint-plugin-react-hooks@^1.6.1: version "1.7.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz#6210b6d5a37205f0b92858f895a4e827020a7d04" @@ -5967,6 +5986,11 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + fast-glob@^2.0.2: version "2.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" @@ -10309,6 +10333,18 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" + integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== + pretty-bytes@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" @@ -10626,6 +10662,11 @@ react-dev-utils@^10.2.1: strip-ansi "6.0.0" text-table "0.2.0" +react-digit-input@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/react-digit-input/-/react-digit-input-2.1.0.tgz#8b0be6d3ea247fd361855483f21d0aafba341196" + integrity sha512-pGv0CtSmu3Mf4cD79LoYtJI7Wq4dpPiLiY1wvKsNaR+X2sJyk1ETiIxjq6G8i+XJqNXExM6vuytzDqblkkSaFw== + react-dom@^16.13.1, react-dom@^16.3.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f" @@ -10840,6 +10881,15 @@ react-transition-group@^4.3.0: loose-envify "^1.4.0" prop-types "^15.6.2" +react@^16.11.0: + version "16.14.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" + integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + react@^16.13.1, react@^16.3.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" From 7d0a6055b6f8ecfba1ec2c6af71d6fdbe5b2015c Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Tue, 3 Aug 2021 08:01:25 +0545 Subject: [PATCH 04/59] lock file --- yarn.lock | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/yarn.lock b/yarn.lock index 9fc386c2..67572f35 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2931,6 +2931,14 @@ adjust-sourcemap-loader@2.0.0: object-path "0.11.4" regex-parser "2.2.10" +adler-32@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/adler-32/-/adler-32-1.2.0.tgz#6a3e6bf0a63900ba15652808cb15c6813d1a5f25" + integrity sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU= + dependencies: + exit-on-epipe "~1.0.1" + printj "~1.1.0" + aes-js@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" @@ -3625,6 +3633,13 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +bootstrap-switch-button-react@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/bootstrap-switch-button-react/-/bootstrap-switch-button-react-1.2.0.tgz#cbb3d8f208b4f2548500b907ce489713641ea3f5" + integrity sha512-p/l+Rp69VtXpK0Y2PR9XYE1TPuypxF+1CpBPmvLUpwXjCisuxtl2S8AF6PqeLDxizFeHuW5nUE6qnqrZY/9ZQA== + dependencies: + react "^16.11.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3999,6 +4014,15 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +cfb@^1.1.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/cfb/-/cfb-1.2.0.tgz#6a4d0872b525ed60349e1ef51fb4b0bf73eca9a8" + integrity sha512-sXMvHsKCICVR3Naq+J556K+ExBo9n50iKl6LGarlnvuA2035uMlGA/qVrc0wQtow5P1vJEw9UyrKLCbtIKz+TQ== + dependencies: + adler-32 "~1.2.0" + crc-32 "~1.2.0" + printj "~1.1.2" + chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -4215,6 +4239,14 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +codepage@~1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/codepage/-/codepage-1.14.0.tgz#8cbe25481323559d7d307571b0fff91e7a1d2f99" + integrity sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k= + dependencies: + commander "~2.14.1" + exit-on-epipe "~1.0.1" + collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -4285,6 +4317,16 @@ commander@^4.1.1: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== +commander@~2.14.1: + version "2.14.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" + integrity sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw== + +commander@~2.17.1: + version "2.17.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" + integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== + common-tags@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" @@ -4472,6 +4514,14 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" +crc-32@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208" + integrity sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA== + dependencies: + exit-on-epipe "~1.0.1" + printj "~1.1.0" + create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" @@ -5408,6 +5458,11 @@ escodegen@^1.11.0, escodegen@^1.9.1: optionalDependencies: source-map "~0.6.1" +eslint-config-prettier@^8.1.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" + integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== + eslint-config-react-app@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-5.2.1.tgz#698bf7aeee27f0cea0139eaef261c7bf7dd623df" @@ -5482,6 +5537,13 @@ eslint-plugin-jsx-a11y@6.2.3: has "^1.0.3" jsx-ast-utils "^2.2.1" +eslint-plugin-prettier@^3.3.1: + version "3.4.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.0.tgz#cdbad3bf1dbd2b177e9825737fe63b476a08f0c7" + integrity sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw== + dependencies: + prettier-linter-helpers "^1.0.0" + eslint-plugin-react-hooks@^1.6.1: version "1.7.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz#6210b6d5a37205f0b92858f895a4e827020a7d04" @@ -5836,6 +5898,11 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +exit-on-epipe@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692" + integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw== + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -5967,6 +6034,11 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + fast-glob@^2.0.2: version "2.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" @@ -6015,6 +6087,11 @@ feather-icons-react@^0.4.1: resolved "https://registry.yarnpkg.com/feather-icons-react/-/feather-icons-react-0.4.1.tgz#d2333ec7e87c8a54c6879181f7df09d598a38302" integrity sha512-rrnLdXpko9NKBdu2dAL3cdn4XXg6VualIqGH7WaDTZnE/Hz1bKWktQPmSsgDSO83Pj4H3uaPzY73zgecqOjDYQ== +fflate@^0.3.8: + version "0.3.11" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.3.11.tgz#2c440d7180fdeb819e64898d8858af327b042a5d" + integrity sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A== + figgy-pudding@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" @@ -6042,6 +6119,11 @@ file-loader@4.3.0: loader-utils "^1.2.3" schema-utils "^2.5.0" +file-saver@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" + integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== + file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" @@ -6231,6 +6313,11 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= +frac@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/frac/-/frac-1.1.2.tgz#3d74f7f6478c88a1b5020306d747dc6313c74d0b" + integrity sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA== + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -10309,6 +10396,18 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" + integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== + pretty-bytes@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" @@ -10332,6 +10431,11 @@ pretty-format@^24.9.0: ansi-styles "^3.2.0" react-is "^16.8.4" +printj@~1.1.0, printj@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" + integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -10840,6 +10944,15 @@ react-transition-group@^4.3.0: loose-envify "^1.4.0" prop-types "^15.6.2" +react@^16.11.0: + version "16.14.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" + integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + react@^16.13.1, react@^16.3.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" @@ -11887,6 +12000,13 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +ssf@~0.11.2: + version "0.11.2" + resolved "https://registry.yarnpkg.com/ssf/-/ssf-0.11.2.tgz#0b99698b237548d088fc43cdf2b70c1a7512c06c" + integrity sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g== + dependencies: + frac "~1.1.2" + sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -13048,11 +13168,21 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" +wmf@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wmf/-/wmf-1.0.2.tgz#7d19d621071a08c2bdc6b7e688a9c435298cc2da" + integrity sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw== + word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== +word@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/word/-/word-0.3.0.tgz#8542157e4f8e849f4a363a288992d47612db9961" + integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA== + workbox-background-sync@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-4.3.1.tgz#26821b9bf16e9e37fd1d640289edddc08afd1950" @@ -13289,6 +13419,22 @@ xhr@^2.0.4: parse-headers "^2.0.0" xtend "^4.0.0" +xlsx@^0.17.0: + version "0.17.0" + resolved "https://registry.yarnpkg.com/xlsx/-/xlsx-0.17.0.tgz#028176a0140967dcee1817d221678461e47481c8" + integrity sha512-bZ36FSACiAyjoldey1+7it50PMlDp1pcAJrZKcVZHzKd8BC/z6TQ/QAN8onuqcepifqSznR6uKnjPhaGt6ig9A== + dependencies: + adler-32 "~1.2.0" + cfb "^1.1.4" + codepage "~1.14.0" + commander "~2.17.1" + crc-32 "~1.2.0" + exit-on-epipe "~1.0.1" + fflate "^0.3.8" + ssf "~0.11.2" + wmf "~1.0.1" + word "~0.3.0" + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" From 0cef9837fefdc74824b9ec666b8fe0df7a4e47c4 Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Tue, 3 Aug 2021 11:32:34 +0545 Subject: [PATCH 05/59] select populate and project add --- src/constants/index.js | 3 +- src/contexts/AidContext.js | 5 + src/contexts/UserContext.js | 5 + src/modules/aid/add/index.js | 205 ++++++++++++++++++++++++++++++++++ src/modules/aid/list/index.js | 5 +- src/routes/Router.js | 8 +- src/services/aid.js | 12 ++ src/services/users.js | 11 ++ 8 files changed, 251 insertions(+), 3 deletions(-) create mode 100644 src/modules/aid/add/index.js diff --git a/src/constants/index.js b/src/constants/index.js index 13751fa7..318c9fcd 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -1,7 +1,8 @@ module.exports = { APP_CONSTANTS: { PASSCODE_LENGTH: 6, - BULK_BENEFICIARY_LIMIT: 200 + BULK_BENEFICIARY_LIMIT: 200, + FETCH_LIMIT: 100 }, ROLES: { ADMIN: 'Admin', MANAGER: 'Manager' }, DEFAULT_TOKEN: { diff --git a/src/contexts/AidContext.js b/src/contexts/AidContext.js index 2d2ef545..d9a21b27 100644 --- a/src/contexts/AidContext.js +++ b/src/contexts/AidContext.js @@ -136,6 +136,10 @@ export const AidContextProvider = ({ children }) => { return Service.bulkTokenIssueToBeneficiary({ ...payload }); } + function listFinancialInstitutions(params) { + return Service.listFinancialInstitutions(params); + } + return ( { addProjectBudget, changeProjectStatus, getProjectCapital, + listFinancialInstitutions, bulkTokenIssueToBeneficiary }} > diff --git a/src/contexts/UserContext.js b/src/contexts/UserContext.js index 425d96c6..cd6b9651 100644 --- a/src/contexts/UserContext.js +++ b/src/contexts/UserContext.js @@ -64,11 +64,16 @@ export const UserContextProvider = ({ children }) => { return Service.updateRole({ ...data }); } + function listUsersByRole(role) { + return Service.listUsersByRole(role); + } + return ( { + const { addToast } = useToasts(); + const { listFinancialInstitutions, addAid } = useContext(AidContext); + const { loading, setLoading } = useContext(AppContext); + const { listUsersByRole } = useContext(UserContext); + + const [financialInstitutions, setFinancialInstitutions] = useState([]); + const [projectManagers, setProjectManagers] = useState([]); + const [benefUploadFile, setBenefUploadFile] = useState(''); + + const [formData, setFormData] = useState({ + name: '', + location: '', + description: '' + }); + const [selectedInstitutions, setSelectedInstitutions] = useState([]); + const [selectedManager, setSelectedManager] = useState(''); + + const handleInputChange = e => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleFileChange = e => { + setBenefUploadFile(e.target.files[0]); + }; + + const handleInstitutionChange = data => { + const institution_values = data.map(d => d.value); + setSelectedInstitutions(institution_values); + }; + + const handleProjectManagerChange = e => { + setSelectedManager(e.value); + }; + + const handleFormSubmit = e => { + e.preventDefault(); + if (!selectedManager) return addToast('Please select project manager', TOAST.ERROR); + const form_payload = createFormData(formData); + setLoading(true); + addAid(form_payload) + .then(res => { + setLoading(false); + addToast(`Project created with ${res.uploaded_beneficiaries} beneficiary upload`, TOAST.SUCCESS); + History.push('/projects'); + }) + .catch(err => { + setLoading(false); + addToast(err.message, TOAST.ERROR); + }); + }; + + const createFormData = payload => { + const form_data = new FormData(); + for (let property in payload) { + form_data.append(property, payload[property]); + } + if (selectedInstitutions.length) form_data.append('financial_institutions', selectedInstitutions.toString()); + if (benefUploadFile) form_data.append('file', benefUploadFile); + form_data.append('project_manager', selectedManager); + return form_data; + }; + + const handleCancelClick = () => History.push('/projects'); + + useEffect(() => { + async function fetchData() { + const users = await listUsersByRole(ROLES.MANAGER); + const institutions = await listFinancialInstitutions({ limit: APP_CONSTANTS.FETCH_LIMIT }); + if (institutions && institutions.data.length) loadFinancialInstiturions(institutions.data); + if (users && users.data.length) loadProjectManagers(users.data); + } + + fetchData(); + }, [listUsersByRole, listFinancialInstitutions]); + + const loadFinancialInstiturions = data => { + const populate_institutions = data.map(d => { + return { label: d.name, value: d._id }; + }); + setFinancialInstitutions(populate_institutions); + }; + + const loadProjectManagers = users => { + const populate_managers = users.map(u => { + return { label: u.fullname, value: u.wallet_address }; + }); + setProjectManagers(populate_managers); + }; + + return ( +
+

Projects

+ + + Projects + + Add + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {loading ? ( + + ) : ( +
+ + +
+ )} +
+
+
+
+ +
+
+ ); +}; + +export default AddProject; diff --git a/src/modules/aid/list/index.js b/src/modules/aid/list/index.js index 5f0d4aa8..8aeaa865 100644 --- a/src/modules/aid/list/index.js +++ b/src/modules/aid/list/index.js @@ -20,6 +20,7 @@ import moment from 'moment'; import { AidContext } from '../../../contexts/AidContext'; import AidModal from '../../global/CustomModal'; +import { History } from '../../../utils/History'; const List = () => { const { aids, pagination, listAid, addAid } = useContext(AidContext); @@ -34,6 +35,8 @@ const List = () => { setaidModal(!aidModal); }; + const handleAddProjectClick = () => History.push('/add-project'); + const handleProjectStatusChange = e => setProjectStatus(e.target.value); const handleInputChange = e => { @@ -131,7 +134,7 @@ const List = () => {
-
diff --git a/src/routes/Router.js b/src/routes/Router.js index 882224ee..346e2d30 100644 --- a/src/routes/Router.js +++ b/src/routes/Router.js @@ -8,6 +8,8 @@ const AgencyList = lazy(() => import('../modules/agency/list')); const AgencyDetails = lazy(() => import('../modules/agency/details')); const AidList = lazy(() => import('../modules/aid/list')); const AidDetails = lazy(() => import('../modules/aid/details')); +const AddProject = lazy(() => import('../modules/aid/add')); + const AgencyProfile = lazy(() => import('../modules/agency/profile')); const InstitutionList = lazy(() => import('../modules/institution')); const InstitutionDetails = lazy(() => import('../modules/institution/detail/index')); @@ -82,6 +84,11 @@ var AppRoutes = [ name: 'Aid', component: AidDetails }, + { + path: '/add-project', + name: 'Add', + component: AddProject + }, { path: '/projects', name: 'Projects', @@ -91,7 +98,6 @@ var AppRoutes = [ }, // ----------------------------Project ui------------------------ - { path: '/add_project', name: 'ProjectAdd', diff --git a/src/services/aid.js b/src/services/aid.js index 3116e743..7b92544c 100644 --- a/src/services/aid.js +++ b/src/services/aid.js @@ -202,6 +202,18 @@ export async function addBeneficiary(aid, body) { return data; } +export async function listFinancialInstitutions(params) { + const res = await axios({ + url: API.INSTITUTIONS, + method: 'get', + headers: { + access_token: access_token + }, + params + }); + return res.data; +} + export async function addVendor(aid, body) { const { data } = await axios({ url: API.PROJECTS + `/${aid}/vendor`, diff --git a/src/services/users.js b/src/services/users.js index 666e13e6..d1876fd5 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -80,6 +80,17 @@ export async function listUsers(params) { return res.data; } +export async function listUsersByRole(role) { + const res = await axios({ + url: `${API.USERS}/roles/${role}`, + method: 'Get', + headers: { + access_token + } + }); + return res.data; +} + export async function getUserById(userId) { const res = await axios({ url: `${API.USERS}/${userId}`, From 817fd6075aa3b7bd6c2a817729d4a75b4afa49af Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Tue, 3 Aug 2021 12:21:57 +0545 Subject: [PATCH 06/59] error handling and cleanup --- src/modules/aid/add/index.js | 10 ++- src/routes/Router.js | 6 -- src/views/project/add/index.js | 141 --------------------------------- 3 files changed, 8 insertions(+), 149 deletions(-) delete mode 100644 src/views/project/add/index.js diff --git a/src/modules/aid/add/index.js b/src/modules/aid/add/index.js index 8d49b693..8c1a6233 100644 --- a/src/modules/aid/add/index.js +++ b/src/modules/aid/add/index.js @@ -70,7 +70,13 @@ const AddProject = () => { }) .catch(err => { setLoading(false); - addToast(err.message, TOAST.ERROR); + let err_msg = err.message; + if (err_msg === 'Request failed with status code 500') { + addToast('Project has been created', TOAST.SUCCESS); + err_msg = 'Upload failed due to duplicate data!'; + addToast(err_msg, TOAST.ERROR); + History.push('/projects'); + } else addToast(err_msg, TOAST.ERROR); }); }; @@ -145,7 +151,7 @@ const AddProject = () => { - + diff --git a/src/routes/Router.js b/src/routes/Router.js index 346e2d30..2d734eb3 100644 --- a/src/routes/Router.js +++ b/src/routes/Router.js @@ -23,7 +23,6 @@ const UserDetails = lazy(() => import('../modules/user/edit')); // ------------------------------Project UI---------------------------------------- -const ProjectAdd = lazy(() => import('../views/project/add')); const ProjectDetail = lazy(() => import('../views/project/detail')); const BudgetAdd = lazy(() => import('../views/project/detail/budgetAdd')); // -------------------------------------------------------------------------------- @@ -98,11 +97,6 @@ var AppRoutes = [ }, // ----------------------------Project ui------------------------ - { - path: '/add_project', - name: 'ProjectAdd', - component: ProjectAdd - }, { path: '/detail_project', name: 'ProjectDetail', diff --git a/src/views/project/add/index.js b/src/views/project/add/index.js deleted file mode 100644 index a8d1b1e1..00000000 --- a/src/views/project/add/index.js +++ /dev/null @@ -1,141 +0,0 @@ -import React, { useState, useEffect } from 'react'; -// import { useToasts } from 'react-toast-notifications'; -import { - Breadcrumb, - BreadcrumbItem, - Card, - CardBody, - Row, - Col, - Form, - FormGroup, - Label, - Input, - Button -} from 'reactstrap'; - -// import { TOAST } from '../../../constants'; -// import { getUser } from '../../../utils/sessionManager'; -// import { UserContext } from '../../../contexts/UserContext'; -// import { AppContext } from '../../../contexts/AppSettingsContext'; -import { History } from '../../../utils/History'; -import WalletUnlock from '../../../modules/global/walletUnlock'; -// import GrowSpinner from '../../../modules/global/GrowSpinner'; - -// const current_user = getUser(); - -const AddProject = () => { - // const { addToast } = useToasts(); - // const { addUser } = useContext(UserContext); - // const { wallet, appSettings, isVerified, loading, setLoading, changeIsverified } = useContext(AppContext); - - const [passcodeModal, setPasscodeModal] = useState(false); - - const [formData, setFormData] = useState({ - name: '', - email: '', - phone: '', - wallet_address: '' - }); - const [selectedInstitution, setSelectedInstitution] = useState(''); - - const handleInputChange = e => { - setFormData({ ...formData, [e.target.name]: e.target.value }); - }; - - const handleInstitutionChange = e => { - setSelectedInstitution(e.target.value); - }; - - const handleFormSubmit = e => { - console.log(selectedInstitution); - return; - }; - - const handleCancelClick = () => History.push('/users'); - - const saveUserDetails = () => {}; - - useEffect(saveUserDetails); - - return ( -
- setPasscodeModal(e)}> -

Projects

- - - Projects - - Add - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {/* {loading ? ( - - ) : ( */} -
- - -
- {/* )} */} -
-
-
-
- -
-
- ); -}; - -export default AddProject; From 63a1f3a674540002fb3c589ddd5badad5aadaae9 Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Thu, 5 Aug 2021 09:26:24 +0545 Subject: [PATCH 07/59] align project details ui elements --- src/assets/css/project.css | 42 +++++++ src/modules/aid/detail/budgetAdd.js | 63 ++++++++++ src/modules/aid/detail/index.js | 49 ++++++++ src/modules/aid/detail/pieChart.js | 91 ++++++++++++++ src/modules/aid/detail/projectInfo.js | 60 +++++++++ src/modules/aid/detail/tab/beneficiaryList.js | 115 ++++++++++++++++++ src/modules/aid/detail/tab/index.js | 62 ++++++++++ src/modules/aid/detail/tab/vendorList.js | 95 +++++++++++++++ src/modules/global/DetailsCard.js | 56 +++++++++ src/modules/ui_components/balance/index.js | 43 +++++++ src/routes/Router.js | 2 +- 11 files changed, 677 insertions(+), 1 deletion(-) create mode 100644 src/assets/css/project.css create mode 100644 src/modules/aid/detail/budgetAdd.js create mode 100644 src/modules/aid/detail/index.js create mode 100644 src/modules/aid/detail/pieChart.js create mode 100644 src/modules/aid/detail/projectInfo.js create mode 100644 src/modules/aid/detail/tab/beneficiaryList.js create mode 100644 src/modules/aid/detail/tab/index.js create mode 100644 src/modules/aid/detail/tab/vendorList.js create mode 100644 src/modules/global/DetailsCard.js create mode 100644 src/modules/ui_components/balance/index.js diff --git a/src/assets/css/project.css b/src/assets/css/project.css new file mode 100644 index 00000000..6caf11be --- /dev/null +++ b/src/assets/css/project.css @@ -0,0 +1,42 @@ +.title { + color: #6b6c72; + font-size: 18px !important; + font-weight: 500 !important; + font-family: 'Be Vietnam' !important; + font-style: normal; + line-height: 20px; + letter-spacing: 0em; + text-align: left; + margin-bottom: 20px !important; +} + +.stat-card-body { + background: #ffffff; + border-radius: 10px !important; + padding: 20px 30px; +} + +.card-font-medium { + font-size: 18px !important; + font-weight: normal; + font-style: normal; + margin-bottom: 0px !important; +} + +.card-font-bold { + font-size: 24px !important; + font-weight: 500; + margin-bottom: 0px !important; +} + +.sub-title { + font-weight: 400; + margin-bottom: 0px; + color: #9b9b9b; + font-size: 14px; + margin: 0; +} + +.card-data { + margin-bottom: 25px; +} diff --git a/src/modules/aid/detail/budgetAdd.js b/src/modules/aid/detail/budgetAdd.js new file mode 100644 index 00000000..4e4208b2 --- /dev/null +++ b/src/modules/aid/detail/budgetAdd.js @@ -0,0 +1,63 @@ +import React, { useState, useCallback } from 'react'; +import { Button, Card, CardTitle, Col, Form, FormGroup, Input, InputGroup, InputGroupAddon, Row } from 'reactstrap'; +import '../../project.css'; +import UnlockWallet from '../../../modules/global/walletUnlock'; + +export default function BudgetAdd() { + const [inputTokens, setInputToken] = useState(''); + const [passcodeModal, setPasscodeModal] = useState(false); + + const togglePasscodeModal = useCallback(() => setPasscodeModal(!passcodeModal), [passcodeModal]); + + const handleInputChange = e => { + let { value } = e.target; + setInputToken(value); + }; + + const handleTokenSubmit = e => { + e.preventDefault(); + togglePasscodeModal(); + }; + return ( +
+ setPasscodeModal(e)}> + +
+ Budget + + +

10,000,000

+
Total Project Budget
+ + +

50,000

+
Total Redeemed Budget
+ +
+
+
+ +
+ Add Budget +
+ + + + + + + + +
+
+
+
+ ); +} diff --git a/src/modules/aid/detail/index.js b/src/modules/aid/detail/index.js new file mode 100644 index 00000000..d055d4e0 --- /dev/null +++ b/src/modules/aid/detail/index.js @@ -0,0 +1,49 @@ +import React from 'react'; +import { Breadcrumb, BreadcrumbItem, Row, Col } from 'reactstrap'; + +import Balance from '../../ui_components/balance'; +import DetailCard from '../../global/DetailsCard'; +import ProjectInfo from './projectInfo'; +import PieChart from './pieChart'; +import Tabs from './tab/index'; + +const ProjectDetail = () => { + return ( + <> +

Projects

+ + + Projects + + Detail + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default ProjectDetail; diff --git a/src/modules/aid/detail/pieChart.js b/src/modules/aid/detail/pieChart.js new file mode 100644 index 00000000..a6dec1c0 --- /dev/null +++ b/src/modules/aid/detail/pieChart.js @@ -0,0 +1,91 @@ +import React, { useContext, useState, useEffect } from 'react'; +import { Card, CardBody, CardTitle } from 'reactstrap'; +import { Pie } from 'react-chartjs-2'; +import { UserContext } from '../../../contexts/UserContext'; +import { useToasts } from 'react-toast-notifications'; + +let _data = []; +let _labels = []; + +let pieData = { + labels: _labels, + datasets: [ + { + data: _data, + pointRadius: 1, + pointHitRadius: 10, + backgroundColor: ['#245064', '#80D5AA', '#F49786', '#F7C087', '#2b7ec1', '#fb6340', '#527855'], + hoverBackgroundColor: ['#245064', '#80D5AA', '#F49786', '#F7C087', '#2b7ec1', '#fb6340', '#527855'], + hoverOffset: 100 + } + ] +}; + +export default function Chart() { + const { addToast } = useToasts(); + + const { getDashboardStats } = useContext(UserContext); + const [stats, setStats] = useState({ + totalAllocation: 0, + redeemedTokens: 0, + beneficiariesByProject: [] + }); + + const fetchDashboardStats = () => { + getDashboardStats() + .then(d => { + setStats(prevState => ({ + ...prevState, + totalAllocation: d.tokenAllocation.totalAllocation, + redeemedTokens: d.tokenRedemption.totalTokenRedemption, + beneficiariesByProject: d.beneficiary.project + })); + }) + .catch(() => { + addToast('Internal server error!', { + appearance: 'error', + autoDismiss: true + }); + }); + }; + + useEffect(fetchDashboardStats, []); + + const data = stats.beneficiariesByProject; + if (data && data.length) { + _labels = []; + _data = []; + for (let d of data) { + _labels.push(d.name); + _data.push(d.count); + } + pieData.labels = _labels; + pieData.datasets[0].data = _data; + } + + return ( +
+ + + Token Detail +
+ +
+
+
+
+ ); +} diff --git a/src/modules/aid/detail/projectInfo.js b/src/modules/aid/detail/projectInfo.js new file mode 100644 index 00000000..42bd0912 --- /dev/null +++ b/src/modules/aid/detail/projectInfo.js @@ -0,0 +1,60 @@ +import React from 'react'; +import { Card, CardTitle, Col, Row } from 'reactstrap'; +import '../../../assets/css/project.css'; + +export default function ProjectInfo() { + return ( +
+ +
+ + + + More Information + + + + + + + + +
+

Susma shahi thakuri

+
Project Manager
+
+
+

Sindhupalchowk, Kavre

+
Location
+
+ + +
+

Anish lama tamang

+
Assigned Social Mobilizer
+
+
+

2020-02-23

+
Created Date
+
+ +
+ +

+ Due to landslide, flood and on going covid-19 situation many people living in Sindhupalchowk have been + effected. They dont have food, shelter and good health care. So we with the joint hand from UNICEF are + planning to do rahat distribution in this area. With the aim to serve atleast twenty thousand people in that + area, we have managed one crore rupees. Hope this small gesture bring a little smile and ease their + suffering. +

+
+
+
+ ); +} diff --git a/src/modules/aid/detail/tab/beneficiaryList.js b/src/modules/aid/detail/tab/beneficiaryList.js new file mode 100644 index 00000000..a1928b86 --- /dev/null +++ b/src/modules/aid/detail/tab/beneficiaryList.js @@ -0,0 +1,115 @@ +import React, { useContext, useEffect } from 'react'; +import { Pagination, PaginationItem, PaginationLink, Table } from 'reactstrap'; +import { useToasts } from 'react-toast-notifications'; + +import { AidContext } from '../../../../contexts/AidContext'; + +const List = () => { + const { aids, pagination, listAid } = useContext(AidContext); + const { addToast } = useToasts(); + + const handlePagination = current_page => { + let _start = (current_page - 1) * pagination.limit; + return loadAidList({ start: _start, limit: pagination.limit }); + }; + + const loadAidList = () => { + let query = {}; + listAid(query) + .then() + .catch(() => { + addToast('Something went wrong!', { + appearance: 'error', + autoDismiss: true + }); + }); + }; + + useEffect(loadAidList); + + return ( + <> +
+
+ + +
+ +
+ +
+
+ + + + + + + + + + + + {aids.length ? ( + aids.map(d => { + return ( + + + + + + + + ); + }) + ) : ( + + + + + )} + +
NameAddressPhone numberTotal Token IssuedTotal Token Redeemed
XYZKavre98674532121500500
No data available.
+ + {pagination.totalPages > 1 ? ( + + + handlePagination(1)} /> + + {[...Array(pagination.totalPages)].map((p, i) => ( + handlePagination(i + 1)} + > + {i + 1} + + ))} + + handlePagination(pagination.totalPages)} /> + + + ) : ( + '' + )} + + ); +}; + +export default List; diff --git a/src/modules/aid/detail/tab/index.js b/src/modules/aid/detail/tab/index.js new file mode 100644 index 00000000..88d54a93 --- /dev/null +++ b/src/modules/aid/detail/tab/index.js @@ -0,0 +1,62 @@ +import React, { useState } from 'react'; +import { TabContent, TabPane, Nav, NavItem, NavLink, Card, Row, Col } from 'reactstrap'; +import classnames from 'classnames'; +import BeneficiaryList from './beneficiaryList'; +import VendorList from './vendorList'; + +const Tabs = () => { + const [activeTab, setActiveTab] = useState('1'); + + const toggle = tab => { + if (activeTab !== tab) setActiveTab(tab); + }; + + return ( +
+ +
+ + + + + + + + + + + + + + + + + +
+
+
+ ); +}; + +export default Tabs; diff --git a/src/modules/aid/detail/tab/vendorList.js b/src/modules/aid/detail/tab/vendorList.js new file mode 100644 index 00000000..5e086625 --- /dev/null +++ b/src/modules/aid/detail/tab/vendorList.js @@ -0,0 +1,95 @@ +import React, { useContext, useEffect } from 'react'; +import { Pagination, PaginationItem, PaginationLink, Table } from 'reactstrap'; +import { useToasts } from 'react-toast-notifications'; + +import { AidContext } from '../../../../contexts/AidContext'; + +const List = () => { + const { aids, pagination, listAid } = useContext(AidContext); + const { addToast } = useToasts(); + + const handlePagination = current_page => { + let _start = (current_page - 1) * pagination.limit; + return loadAidList({ start: _start, limit: pagination.limit }); + }; + + const loadAidList = () => { + let query = {}; + listAid(query) + .then() + .catch(() => { + addToast('Something went wrong!', { + appearance: 'error', + autoDismiss: true + }); + }); + }; + + useEffect(loadAidList, []); + + return ( + <> + + + + + + + + + + + + {aids.length ? ( + aids.map(d => { + return ( + + + + + + + + ); + }) + ) : ( + + + + + )} + +
NameAddressPhone numberBalanceTotal Token Redeemed
XYZKavre986745321215000050000
No data available.
+ + {pagination.totalPages > 1 ? ( + + + handlePagination(1)} /> + + {[...Array(pagination.totalPages)].map((p, i) => ( + handlePagination(i + 1)} + > + {i + 1} + + ))} + + handlePagination(pagination.totalPages)} /> + + + ) : ( + '' + )} + + ); +}; + +export default List; diff --git a/src/modules/global/DetailsCard.js b/src/modules/global/DetailsCard.js new file mode 100644 index 00000000..6d97c6b8 --- /dev/null +++ b/src/modules/global/DetailsCard.js @@ -0,0 +1,56 @@ +import React from 'react'; +import { Card, CardTitle, Col, Row } from 'reactstrap'; +import BootstrapSwitchButton from 'bootstrap-switch-button-react'; +import '../../assets/css/project.css'; + +export default function DetailsCard(props) { + const { title, button_name, name, name_value, total, total_value } = props; + return ( +
+ +
+ + + + {title || 'No Title'} + + + +
+ {title === 'Project Details' ? ( + + ) : ( + + )} +
+ +
+ + +

{name_value || '0'}

+
{name || 'No Label'}
+ + +

{total_value || '0'}

+
{total || 'No Label'}
+ +
+
+
+
+ ); +} diff --git a/src/modules/ui_components/balance/index.js b/src/modules/ui_components/balance/index.js new file mode 100644 index 00000000..9d5b6259 --- /dev/null +++ b/src/modules/ui_components/balance/index.js @@ -0,0 +1,43 @@ +import React from 'react'; +import { Card, CardTitle, Col, Row } from 'reactstrap'; +import { useHistory } from 'react-router-dom'; +import '../../../assets/css/project.css'; + +export default function Balance(props) { + const { title, data, button_name, label } = props; + const history = useHistory(); + const handleClick = () => { + history.push('/add_budget'); + }; + return ( +
+ +
+ {title || 'No Title'} + + +

{data || '0'}

+
+ {label || 'No Label'} +
+ + + + +
+ {/*
+
+
+
*/} +
+
+
+ ); +} diff --git a/src/routes/Router.js b/src/routes/Router.js index 2d734eb3..fd8d3eee 100644 --- a/src/routes/Router.js +++ b/src/routes/Router.js @@ -7,7 +7,7 @@ const Vendor = lazy(() => import('../modules/vendor')); const AgencyList = lazy(() => import('../modules/agency/list')); const AgencyDetails = lazy(() => import('../modules/agency/details')); const AidList = lazy(() => import('../modules/aid/list')); -const AidDetails = lazy(() => import('../modules/aid/details')); +const AidDetails = lazy(() => import('../modules/aid/detail')); const AddProject = lazy(() => import('../modules/aid/add')); const AgencyProfile = lazy(() => import('../modules/agency/profile')); From 9fdc07ca7adde736e748059c7898c147354a82ee Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Fri, 6 Aug 2021 07:29:47 +0545 Subject: [PATCH 08/59] project details and activate suspend toggle --- src/constants/index.js | 4 ++ src/modules/aid/detail/index.js | 72 ++++++++++++++----- src/modules/aid/detail/projectInfo.js | 18 +++-- src/modules/aid/detail/tab/beneficiaryList.js | 2 +- src/modules/global/DetailsCard.js | 14 +++- 5 files changed, 78 insertions(+), 32 deletions(-) diff --git a/src/constants/index.js b/src/constants/index.js index 318c9fcd..e419ad2f 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -22,5 +22,9 @@ module.exports = { appearance: 'success', autoDismiss: true } + }, + PROJECT_STATUS: { + ACTIVE: 'active', + SUSPENDED: 'suspended' } }; diff --git a/src/modules/aid/detail/index.js b/src/modules/aid/detail/index.js index d055d4e0..d4f3ad27 100644 --- a/src/modules/aid/detail/index.js +++ b/src/modules/aid/detail/index.js @@ -1,13 +1,49 @@ -import React from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Breadcrumb, BreadcrumbItem, Row, Col } from 'reactstrap'; +import { useToasts } from 'react-toast-notifications'; +import { AidContext } from '../../../contexts/AidContext'; +import DetailsCard from '../../global/DetailsCard'; import Balance from '../../ui_components/balance'; -import DetailCard from '../../global/DetailsCard'; import ProjectInfo from './projectInfo'; import PieChart from './pieChart'; -import Tabs from './tab/index'; +import Tabs from './tab'; +import { TOAST, PROJECT_STATUS } from '../../../constants'; + +export default function Index(props) { + const { id } = props.match.params; + + const { addToast } = useToasts(); + const { getAidDetails, changeProjectStatus } = useContext(AidContext); + + const [projectDetails, setProjectDetails] = useState(null); + + const handleStatusChange = status => { + const success_label = status === PROJECT_STATUS.SUSPENDED ? 'Suspended' : 'Activated'; + changeProjectStatus(id, status) + .then(d => { + setProjectDetails(d); + addToast(`Project has been ${success_label}`, TOAST.SUCCESS); + }) + .catch(err => { + addToast(err.message, TOAST.ERROR); + }); + }; + + const fetchProjectDetails = () => { + getAidDetails(id) + .then(res => { + setProjectDetails(res); + }) + .catch(err => { + addToast(err.message, TOAST.ERROR); + }); + }; + + useEffect(fetchProjectDetails, []); + + console.log('===>', projectDetails); -const ProjectDetail = () => { return ( <>

Projects

@@ -19,14 +55,18 @@ const ProjectDetail = () => { - + {projectDetails && ( + + )} @@ -34,9 +74,7 @@ const ProjectDetail = () => { - - - + {projectDetails && } @@ -44,6 +82,4 @@ const ProjectDetail = () => { ); -}; - -export default ProjectDetail; +} diff --git a/src/modules/aid/detail/projectInfo.js b/src/modules/aid/detail/projectInfo.js index 42bd0912..55451905 100644 --- a/src/modules/aid/detail/projectInfo.js +++ b/src/modules/aid/detail/projectInfo.js @@ -1,8 +1,10 @@ import React from 'react'; +import moment from 'moment'; import { Card, CardTitle, Col, Row } from 'reactstrap'; import '../../../assets/css/project.css'; -export default function ProjectInfo() { +export default function ProjectInfo({ projectDetails }) { + const { social_mobilizer, project_manager, location, description, created_at } = projectDetails; return (
@@ -26,32 +28,28 @@ export default function ProjectInfo() {
-

Susma shahi thakuri

+

{project_manager || '-'}

Project Manager
-

Sindhupalchowk, Kavre

+

{location || '-'}

Location
-

Anish lama tamang

+

{social_mobilizer || '-'}

Assigned Social Mobilizer
-

2020-02-23

+

{moment(created_at).format('ll')}

Created Date

- Due to landslide, flood and on going covid-19 situation many people living in Sindhupalchowk have been - effected. They dont have food, shelter and good health care. So we with the joint hand from UNICEF are - planning to do rahat distribution in this area. With the aim to serve atleast twenty thousand people in that - area, we have managed one crore rupees. Hope this small gesture bring a little smile and ease their - suffering. + {description || 'Project description not available...'}

diff --git a/src/modules/aid/detail/tab/beneficiaryList.js b/src/modules/aid/detail/tab/beneficiaryList.js index a1928b86..275f6033 100644 --- a/src/modules/aid/detail/tab/beneficiaryList.js +++ b/src/modules/aid/detail/tab/beneficiaryList.js @@ -25,7 +25,7 @@ const List = () => { }); }; - useEffect(loadAidList); + useEffect(loadAidList, []); return ( <> diff --git a/src/modules/global/DetailsCard.js b/src/modules/global/DetailsCard.js index 6d97c6b8..0b06d31c 100644 --- a/src/modules/global/DetailsCard.js +++ b/src/modules/global/DetailsCard.js @@ -1,10 +1,18 @@ import React from 'react'; import { Card, CardTitle, Col, Row } from 'reactstrap'; import BootstrapSwitchButton from 'bootstrap-switch-button-react'; + import '../../assets/css/project.css'; +import { PROJECT_STATUS } from '../../constants'; export default function DetailsCard(props) { - const { title, button_name, name, name_value, total, total_value } = props; + const { title, button_name, name, name_value, total, total_value, status, handleStatusChange } = props; + + const handleSwitchChange = e => { + const _status = e === true ? PROJECT_STATUS.ACTIVE : PROJECT_STATUS.SUSPENDED; + handleStatusChange(_status); + }; + return (
@@ -19,13 +27,13 @@ export default function DetailsCard(props) {
{title === 'Project Details' ? ( ) : ( - - - - + {loading ? ( + + ) : ( +
+ + + + + + + + +
+ )}
diff --git a/src/modules/aid/detail/index.js b/src/modules/aid/detail/index.js index 89582dcd..51180658 100644 --- a/src/modules/aid/detail/index.js +++ b/src/modules/aid/detail/index.js @@ -93,7 +93,13 @@ export default function Index(props) { )} - + diff --git a/src/modules/global/LiteModal.js b/src/modules/global/LiteModal.js index 330ac8bc..e14ed62d 100644 --- a/src/modules/global/LiteModal.js +++ b/src/modules/global/LiteModal.js @@ -4,14 +4,7 @@ import PropTypes from 'prop-types'; import useDigitInput from 'react-digit-input'; export default function CustomModal(props) { - const { - // children, - title, - loadingMessage, - open, - size, - handleSubmit - } = props; + const { toggle, title, loadingMessage, open, size, handleSubmit } = props; const [value, onChange] = useState(''); const digits = useDigitInput({ acceptedCharacters: /^[0-9]$/, @@ -23,7 +16,7 @@ export default function CustomModal(props) { <> {/* {children || 'No child elements supplied.'} */} + {/* Close */}

{title || 'Modal Title'}

+
@@ -44,11 +39,11 @@ export default function CustomModal(props) {
-
+							{/* 
 								"{value}"
-							
+
*/}
-
+
{loadingMessage ? loadingMessage : ''}
diff --git a/src/modules/global/PasscodeModal.js b/src/modules/global/PasscodeModal.js new file mode 100644 index 00000000..2bfc5207 --- /dev/null +++ b/src/modules/global/PasscodeModal.js @@ -0,0 +1,98 @@ +import React, { useState, useEffect, useContext, useCallback } from 'react'; +import { Form, Modal, ModalBody } from 'reactstrap'; +import PropTypes from 'prop-types'; +import useDigitInput from 'react-digit-input'; + +import { APP_CONSTANTS } from '../../constants'; +import { AppContext } from '../../contexts/AppSettingsContext'; +import Wallet from '../../utils/blockchain/wallet'; + +const PASSCODE_LEN = APP_CONSTANTS.PASSCODE_LENGTH; + +export default function PasscodeModal(props) { + const { toggleModal, title, isOpen, size, handleSubmit } = props; + const { wallet, walletPasscode, setWallet, changeIsverified, setWalletPasscode } = useContext(AppContext); + + const [value, onChange] = useState(''); + const [loadingMessage, setLoadingMessage] = useState(''); + + const digits = useDigitInput({ + acceptedCharacters: /^[0-9]$/, + length: 6, + value, + onChange + }); + + const verifyWallet = useCallback( + async input_value => { + try { + onChange(''); + if (wallet && walletPasscode) { + if (input_value !== walletPasscode) return setLoadingMessage('Please enter correct passcode!'); + changeIsverified(true); + setLoadingMessage('Wallet verified!'); + } else { + setLoadingMessage('Verifying wallet, please wallet...'); + const w = await Wallet.loadWallet(input_value); + setWalletPasscode(input_value); + changeIsverified(true); + setLoadingMessage('Wallet verified!'); + setWallet(w); + } + } catch (err) { + setLoadingMessage('Please enter correct passcode!'); + } + }, + [changeIsverified, setWallet, setWalletPasscode, wallet, walletPasscode] + ); + + useEffect(() => { + const input_value = value.trim(); + if (input_value.length === PASSCODE_LEN) { + verifyWallet(input_value); + } + }, [changeIsverified, setWallet, setWalletPasscode, value, value.length, verifyWallet, wallet, walletPasscode]); + + return ( + <> + +
+ + {/* {children || 'No child elements supplied.'} */} + {/* Close */} +

{title || 'Verify Passcode'}

+ +
+
+ + + + + + +
+ {/*
+								"{value}"
+							
*/} +
+
+ {loadingMessage ? loadingMessage : ''} +
+
+
+
+ + ); +} + +PasscodeModal.propTypes = { + toggleModal: PropTypes.func.isRequired, + isOpen: PropTypes.bool.isRequired +}; diff --git a/src/modules/ui_components/balance/index.js b/src/modules/ui_components/balance/index.js index 9d5b6259..ba45018f 100644 --- a/src/modules/ui_components/balance/index.js +++ b/src/modules/ui_components/balance/index.js @@ -4,10 +4,10 @@ import { useHistory } from 'react-router-dom'; import '../../../assets/css/project.css'; export default function Balance(props) { - const { title, data, button_name, label } = props; + const { title, data, button_name, label, projectId } = props; const history = useHistory(); const handleClick = () => { - history.push('/add_budget'); + history.push(`/add_budget/${projectId}`); }; return (
diff --git a/src/routes/Router.js b/src/routes/Router.js index fd8d3eee..951983f1 100644 --- a/src/routes/Router.js +++ b/src/routes/Router.js @@ -20,12 +20,7 @@ const Onboard = lazy(() => import('../modules/onboard')); const ListUsers = lazy(() => import('../modules/user/list')); const AddUser = lazy(() => import('../modules/user/add')); const UserDetails = lazy(() => import('../modules/user/edit')); - -// ------------------------------Project UI---------------------------------------- - -const ProjectDetail = lazy(() => import('../views/project/detail')); -const BudgetAdd = lazy(() => import('../views/project/detail/budgetAdd')); -// -------------------------------------------------------------------------------- +const BudgetAdd = lazy(() => import('../modules/aid/detail/budgetAdd')); // ------------------------------Beneficiary UI------------------------------------ @@ -96,18 +91,11 @@ var AppRoutes = [ showInSidebar: true }, - // ----------------------------Project ui------------------------ { - path: '/detail_project', - name: 'ProjectDetail', - component: ProjectDetail - }, - { - path: '/add_budget', + path: '/add_budget/:projectId', name: 'BudgetAdd', component: BudgetAdd }, - // ----------------------------------------------------------------- //.............................Beneficiary ui...................... From 94d1857940c993d6f6e88ef89ed044f4a50d6e2d Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Sun, 8 Aug 2021 09:30:56 +0545 Subject: [PATCH 13/59] update project details --- src/contexts/AidContext.js | 25 +-- src/modules/aid/detail/projectInfo.js | 8 +- src/modules/aid/edit/index.js | 273 ++++++++++++++++++++++++++ src/routes/Router.js | 6 + src/services/aid.js | 15 ++ 5 files changed, 315 insertions(+), 12 deletions(-) create mode 100644 src/modules/aid/edit/index.js diff --git a/src/contexts/AidContext.js b/src/contexts/AidContext.js index e6e97313..6442ebdb 100644 --- a/src/contexts/AidContext.js +++ b/src/contexts/AidContext.js @@ -3,6 +3,7 @@ import aidReduce from '../reducers/aidReducer'; import * as Service from '../services/aid'; import ACTION from '../actions/aid'; import { AppContext } from './AppSettingsContext'; +import { get } from '../services/institution'; const initialState = { aids: [], @@ -25,17 +26,17 @@ export const AidContext = createContext(initialState); export const AidContextProvider = ({ children }) => { const [state, dispatch] = useReducer(aidReduce, initialState); const { appSettings, changeIsverified } = useContext(AppContext); - function getAidDetails(aidId) { - return new Promise((resolve, reject) => { - Service.getAidDetails(aidId) - .then(res => { - dispatch({ type: ACTION.GET_AID_SUCCESS, res }); - resolve(res); - }) - .catch(err => { - reject(err); - }); - }); + + function getAidDetails(projectId) { + return Service.getAidDetails(projectId); + } + + function updateAid(projectId, payload) { + return Service.updateAid(projectId, payload); + } + + function getInstitution(institutionId) { + return get(institutionId); } const addProjectBudget = useCallback( @@ -160,10 +161,12 @@ export const AidContextProvider = ({ children }) => { aid_details: state.aid_details, available_tokens: state.available_tokens, total_tokens: state.total_tokens, + updateAid, addAid, listAid, setLoading, getAidBalance, + getInstitution, resetLoading, vendorsByAid, getAidDetails, diff --git a/src/modules/aid/detail/projectInfo.js b/src/modules/aid/detail/projectInfo.js index b3396ce8..3f1b985b 100644 --- a/src/modules/aid/detail/projectInfo.js +++ b/src/modules/aid/detail/projectInfo.js @@ -1,10 +1,15 @@ import React from 'react'; import moment from 'moment'; import { Card, CardTitle, Col, Row } from 'reactstrap'; + +import { History } from '../../../utils/History'; + import '../../../assets/css/project.css'; export default function ProjectInfo({ projectDetails }) { - const { social_mobilizer, project_manager, location, description, created_at } = projectDetails; + const { _id, social_mobilizer, project_manager, location, description, created_at } = projectDetails; + + const handleEditClick = () => History.push(`/edit-project/${_id}`); return (
@@ -19,6 +24,7 @@ export default function ProjectInfo({ projectDetails }) { + +
+ )} + + + + + + +
+ ); +}; + +export default EditProject; diff --git a/src/routes/Router.js b/src/routes/Router.js index 951983f1..92a715d1 100644 --- a/src/routes/Router.js +++ b/src/routes/Router.js @@ -9,6 +9,7 @@ const AgencyDetails = lazy(() => import('../modules/agency/details')); const AidList = lazy(() => import('../modules/aid/list')); const AidDetails = lazy(() => import('../modules/aid/detail')); const AddProject = lazy(() => import('../modules/aid/add')); +const EditProject = lazy(() => import('../modules/aid/edit')); const AgencyProfile = lazy(() => import('../modules/agency/profile')); const InstitutionList = lazy(() => import('../modules/institution')); @@ -78,6 +79,11 @@ var AppRoutes = [ name: 'Aid', component: AidDetails }, + { + path: '/edit-project/:id', + name: 'Project', + component: EditProject + }, { path: '/add-project', name: 'Add', diff --git a/src/services/aid.js b/src/services/aid.js index 7b92544c..f8619c8c 100644 --- a/src/services/aid.js +++ b/src/services/aid.js @@ -176,6 +176,21 @@ export function addAid(payload) { }); } +export function updateAid(projectId, payload) { + return new Promise((resolve, reject) => { + axios + .put(`${API.PROJECTS}/${projectId}`, payload, { + headers: { access_token: access_token } + }) + .then(res => { + resolve(res.data); + }) + .catch(err => { + reject(err); + }); + }); +} + export async function listAid(params) { let { data } = await axios({ url: API.PROJECTS, From 24b2d5f9edbc496018dcbde65f1d38abd6ba684f Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Sun, 8 Aug 2021 09:48:03 +0545 Subject: [PATCH 14/59] breadcrumb resuable compoment --- src/modules/aid/add/index.js | 23 ++++--------------- src/modules/aid/detail/budgetAdd.js | 4 ++++ src/modules/aid/detail/index.js | 10 +++----- src/modules/aid/detail/projectInfo.js | 3 +-- src/modules/ui_components/breadcrumb/index.js | 16 +++++++++++++ 5 files changed, 28 insertions(+), 28 deletions(-) create mode 100644 src/modules/ui_components/breadcrumb/index.js diff --git a/src/modules/aid/add/index.js b/src/modules/aid/add/index.js index 8c1a6233..79fc6cab 100644 --- a/src/modules/aid/add/index.js +++ b/src/modules/aid/add/index.js @@ -1,18 +1,6 @@ import React, { useState, useEffect, useContext } from 'react'; import { useToasts } from 'react-toast-notifications'; -import { - Breadcrumb, - BreadcrumbItem, - Card, - CardBody, - Row, - Col, - Form, - FormGroup, - Label, - Input, - Button -} from 'reactstrap'; +import { Card, CardBody, Row, Col, Form, FormGroup, Label, Input, Button } from 'reactstrap'; import { TOAST, APP_CONSTANTS, ROLES } from '../../../constants'; import { AidContext } from '../../../contexts/AidContext'; @@ -21,6 +9,7 @@ import { UserContext } from '../../../contexts/UserContext'; import { History } from '../../../utils/History'; import SelectWrapper from '../../global/SelectWrapper'; import GrowSpinner from '../../../modules/global/GrowSpinner'; +import BreadCrumb from '../../ui_components/breadcrumb'; const AddProject = () => { const { addToast } = useToasts(); @@ -121,12 +110,8 @@ const AddProject = () => { return (

Projects

- - - Projects - - Add - + + diff --git a/src/modules/aid/detail/budgetAdd.js b/src/modules/aid/detail/budgetAdd.js index 8a8db1e0..2be095c3 100644 --- a/src/modules/aid/detail/budgetAdd.js +++ b/src/modules/aid/detail/budgetAdd.js @@ -10,6 +10,7 @@ import { AppContext } from '../../../contexts/AppSettingsContext'; import PasscodeModal from '../../global/PasscodeModal'; import { TOAST } from '../../../constants'; import GrowSpinner from '../../global/GrowSpinner'; +import BreadCrumb from '../../ui_components/breadcrumb'; export default function BudgetAdd({ match }) { const { addToast } = useToasts(); @@ -73,6 +74,9 @@ export default function BudgetAdd({ match }) { return (
+

Project

+ +
Budget diff --git a/src/modules/aid/detail/index.js b/src/modules/aid/detail/index.js index 51180658..72015341 100644 --- a/src/modules/aid/detail/index.js +++ b/src/modules/aid/detail/index.js @@ -1,5 +1,5 @@ import React, { useContext, useEffect, useState } from 'react'; -import { Breadcrumb, BreadcrumbItem, Row, Col } from 'reactstrap'; +import { Row, Col } from 'reactstrap'; import { useToasts } from 'react-toast-notifications'; import { AidContext } from '../../../contexts/AidContext'; @@ -10,6 +10,7 @@ import ProjectInfo from './projectInfo'; import PieChart from './pieChart'; import Tabs from './tab'; import { TOAST, PROJECT_STATUS } from '../../../constants'; +import BreadCrumb from '../../ui_components/breadcrumb'; export default function Index(props) { const { id } = props.match.params; @@ -71,12 +72,7 @@ export default function Index(props) { return ( <>

Projects

- - - Projects - - Detail - + {projectDetails && ( diff --git a/src/modules/aid/detail/projectInfo.js b/src/modules/aid/detail/projectInfo.js index 3f1b985b..3987607f 100644 --- a/src/modules/aid/detail/projectInfo.js +++ b/src/modules/aid/detail/projectInfo.js @@ -3,7 +3,6 @@ import moment from 'moment'; import { Card, CardTitle, Col, Row } from 'reactstrap'; import { History } from '../../../utils/History'; - import '../../../assets/css/project.css'; export default function ProjectInfo({ projectDetails }) { @@ -14,7 +13,7 @@ export default function ProjectInfo({ projectDetails }) { return (
-
+
diff --git a/src/modules/ui_components/breadcrumb/index.js b/src/modules/ui_components/breadcrumb/index.js new file mode 100644 index 00000000..4558f08b --- /dev/null +++ b/src/modules/ui_components/breadcrumb/index.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { Breadcrumb, BreadcrumbItem } from 'reactstrap'; +import { Link } from 'react-router-dom'; + +export default function index({ root_label, current_label, redirect_path }) { + return ( + <> + + + {root_label || 'Home'} + + {current_label || 'No Label'} + + + ); +} From a0e87ff73f22d00c66b9cd4264dfac0163d66d7d Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Tue, 10 Aug 2021 09:35:47 +0545 Subject: [PATCH 15/59] beneficiary list and pagination --- src/assets/css/custom.css | 34 ++++++----- src/contexts/AidContext.js | 19 +++++- src/modules/aid/detail/index.js | 2 +- src/modules/aid/detail/tab/beneficiaryList.js | 58 +++++++------------ src/modules/aid/detail/tab/index.js | 19 +++++- src/modules/aid/detail/tab/vendorList.js | 26 ++------- 6 files changed, 79 insertions(+), 79 deletions(-) diff --git a/src/assets/css/custom.css b/src/assets/css/custom.css index 31d01d50..707d3a07 100644 --- a/src/assets/css/custom.css +++ b/src/assets/css/custom.css @@ -76,11 +76,11 @@ a:focus { height: 50px; border-left: 2px solid rgba(43, 126, 193, 0.5); } -.active { +/* .active { background: rgba(43, 126, 193, 0.1); border-right: 3px solid #2b7ec1; color: #2b7ec1 !important; -} +} */ .card { box-shadow: 0px 4px 15px 3px rgba(196, 196, 196, 0.15); -moz-box-shadow: 0px 4px 15px 3px rgba(196, 196, 196, 0.15); @@ -171,22 +171,22 @@ a { } .page-heading { - color: #6B6C72; + color: #6b6c72; font-size: 24px; font-weight: 400; - margin-bottom: 0px ; + margin-bottom: 0px; } -.breadcrumb{ - background-color: #fff; +.breadcrumb { + background-color: #fff; padding-left: 0px !important; font-size: 14px; } -.modal-backdrop{ - opacity:1 !important; +.modal-backdrop { + opacity: 1 !important; } -.input-pin{ +.input-pin { background-color: black; color: white; width: 100px; @@ -194,17 +194,19 @@ a { text-align: center; font-size: 36px; border-style: solid; - border-width: 5px; + border-width: 5px; border-color: white; margin-right: 10px; } -.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active, .show > .btn-outline-info.dropdown-toggle { - background-color: #2b7ec1; - border-color: #2b7ec1; +.btn-outline-info:not(:disabled):not(.disabled):active, +.btn-outline-info:not(:disabled):not(.disabled).active, +.show > .btn-outline-info.dropdown-toggle { + background-color: #2b7ec1; + border-color: #2b7ec1; } .btn-outline-info:hover { - background-color: #2b7ec1; - border-color: #2b7ec1; -} \ No newline at end of file + background-color: #2b7ec1; + border-color: #2b7ec1; +} diff --git a/src/contexts/AidContext.js b/src/contexts/AidContext.js index 6442ebdb..adae0b76 100644 --- a/src/contexts/AidContext.js +++ b/src/contexts/AidContext.js @@ -7,7 +7,7 @@ import { get } from '../services/institution'; const initialState = { aids: [], - pagination: { total: 0, limit: 20, start: 0, currentPage: 1, totalPages: 0 }, + pagination: { total: 0, limit: 10, start: 0, currentPage: 1, totalPages: 0 }, beneficiary_list: [], beneficiary_pagination: { total: 0, @@ -102,7 +102,7 @@ export const AidContextProvider = ({ children }) => { }); } - function beneficiaryByAid(aidId, params) { + const beneficiaryByAid = useCallback((aidId, params) => { return new Promise((resolve, reject) => { Service.beneficiaryByAid(aidId, params) .then(res => { @@ -113,7 +113,20 @@ export const AidContextProvider = ({ children }) => { reject(err); }); }); - } + }, []); + + // function beneficiaryByAid(aidId, params) { + // return new Promise((resolve, reject) => { + // Service.beneficiaryByAid(aidId, params) + // .then(res => { + // dispatch({ type: ACTION.BENEF_LIST_SUCCSS, res }); + // resolve(res); + // }) + // .catch(err => { + // reject(err); + // }); + // }); + // } function addAid(payload) { return new Promise((resolve, reject) => { diff --git a/src/modules/aid/detail/index.js b/src/modules/aid/detail/index.js index 72015341..80c9bf9f 100644 --- a/src/modules/aid/detail/index.js +++ b/src/modules/aid/detail/index.js @@ -105,7 +105,7 @@ export default function Index(props) { - + ); } diff --git a/src/modules/aid/detail/tab/beneficiaryList.js b/src/modules/aid/detail/tab/beneficiaryList.js index 275f6033..c78ddbc9 100644 --- a/src/modules/aid/detail/tab/beneficiaryList.js +++ b/src/modules/aid/detail/tab/beneficiaryList.js @@ -1,32 +1,16 @@ -import React, { useContext, useEffect } from 'react'; +import React, { useContext } from 'react'; import { Pagination, PaginationItem, PaginationLink, Table } from 'reactstrap'; -import { useToasts } from 'react-toast-notifications'; import { AidContext } from '../../../../contexts/AidContext'; -const List = () => { - const { aids, pagination, listAid } = useContext(AidContext); - const { addToast } = useToasts(); +const List = ({ beneficiaries, projectId }) => { + const { beneficiary_pagination, beneficiaryByAid } = useContext(AidContext); const handlePagination = current_page => { - let _start = (current_page - 1) * pagination.limit; - return loadAidList({ start: _start, limit: pagination.limit }); + let _start = (current_page - 1) * beneficiary_pagination.limit; + return beneficiaryByAid(projectId, { start: _start }); }; - const loadAidList = () => { - let query = {}; - listAid(query) - .then() - .catch(() => { - addToast('Something went wrong!', { - appearance: 'error', - autoDismiss: true - }); - }); - }; - - useEffect(loadAidList, []); - return ( <>
@@ -44,9 +28,9 @@ const List = () => {
- + */}
@@ -55,20 +39,18 @@ const List = () => { - - + - {aids.length ? ( - aids.map(d => { + {beneficiaries.length > 0 ? ( + beneficiaries.map(d => { return ( - - - - - + + + + ); }) @@ -81,7 +63,7 @@ const List = () => {
Name Address Phone numberTotal Token IssuedTotal Token RedeemedGovt. ID
XYZKavre98674532121500500{d.name}{d.address || '-'}{d.phone}{d.govt_id || '-'}
- {pagination.totalPages > 1 ? ( + {beneficiary_pagination.totalPages > 1 ? ( { handlePagination(1)} /> - {[...Array(pagination.totalPages)].map((p, i) => ( + {[...Array(beneficiary_pagination.totalPages)].map((p, i) => ( handlePagination(i + 1)} > {i + 1} ))} - handlePagination(pagination.totalPages)} /> + handlePagination(beneficiary_pagination.totalPages)} + /> ) : ( diff --git a/src/modules/aid/detail/tab/index.js b/src/modules/aid/detail/tab/index.js index 88d54a93..43c4c70d 100644 --- a/src/modules/aid/detail/tab/index.js +++ b/src/modules/aid/detail/tab/index.js @@ -1,16 +1,29 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState, useContext, useCallback } from 'react'; import { TabContent, TabPane, Nav, NavItem, NavLink, Card, Row, Col } from 'reactstrap'; import classnames from 'classnames'; + import BeneficiaryList from './beneficiaryList'; import VendorList from './vendorList'; +import { AidContext } from '../../../../contexts/AidContext'; + +const Tabs = ({ projectId }) => { + const { beneficiaryByAid, beneficiary_list } = useContext(AidContext); -const Tabs = () => { const [activeTab, setActiveTab] = useState('1'); const toggle = tab => { if (activeTab !== tab) setActiveTab(tab); }; + const fetchData = useCallback(async () => { + await beneficiaryByAid(projectId); + }, [beneficiaryByAid, projectId]); + + useEffect(() => { + console.log('Effect...'); + fetchData(); + }, [fetchData]); + return (
@@ -41,7 +54,7 @@ const Tabs = () => { - + diff --git a/src/modules/aid/detail/tab/vendorList.js b/src/modules/aid/detail/tab/vendorList.js index 5e086625..4c03d20d 100644 --- a/src/modules/aid/detail/tab/vendorList.js +++ b/src/modules/aid/detail/tab/vendorList.js @@ -1,31 +1,17 @@ -import React, { useContext, useEffect } from 'react'; +import React, { useContext } from 'react'; import { Pagination, PaginationItem, PaginationLink, Table } from 'reactstrap'; -import { useToasts } from 'react-toast-notifications'; import { AidContext } from '../../../../contexts/AidContext'; const List = () => { - const { aids, pagination, listAid } = useContext(AidContext); - const { addToast } = useToasts(); + const { pagination } = useContext(AidContext); const handlePagination = current_page => { let _start = (current_page - 1) * pagination.limit; - return loadAidList({ start: _start, limit: pagination.limit }); + console.log({ _start }); }; - const loadAidList = () => { - let query = {}; - listAid(query) - .then() - .catch(() => { - addToast('Something went wrong!', { - appearance: 'error', - autoDismiss: true - }); - }); - }; - - useEffect(loadAidList, []); + const vendors = []; return ( <> @@ -40,8 +26,8 @@ const List = () => { - {aids.length ? ( - aids.map(d => { + {vendors.length ? ( + vendors.map(d => { return ( XYZ From cb2d6a168d9b3e82fd2a2519f12b4533269cbeef Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Tue, 10 Aug 2021 10:41:45 +0545 Subject: [PATCH 16/59] bulk qrcode export --- src/modules/aid/detail/tab/beneficiaryList.js | 89 ++++++++++++++++++- src/utils/printBeneficiary.js | 57 ++++++++++++ 2 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 src/utils/printBeneficiary.js diff --git a/src/modules/aid/detail/tab/beneficiaryList.js b/src/modules/aid/detail/tab/beneficiaryList.js index c78ddbc9..4833d3b5 100644 --- a/src/modules/aid/detail/tab/beneficiaryList.js +++ b/src/modules/aid/detail/tab/beneficiaryList.js @@ -1,18 +1,96 @@ -import React, { useContext } from 'react'; -import { Pagination, PaginationItem, PaginationLink, Table } from 'reactstrap'; +import React, { useContext, useState } from 'react'; +import { Pagination, PaginationItem, PaginationLink, Table, FormGroup, InputGroup, Input } from 'reactstrap'; +import { useToasts } from 'react-toast-notifications'; +import QRCode from 'qrcode'; import { AidContext } from '../../../../contexts/AidContext'; +import { AppContext } from '../../../../contexts/AppSettingsContext'; +import { APP_CONSTANTS, TOAST } from '../../../../constants'; +import { htmlResponse } from '../../../../utils/printBeneficiary'; +import ModalWrapper from '../../../global/CustomModal'; + +const ACTION = { + BULK_QR: 'bulk_qrcode_export', + BULK_ISSUE: 'bulk_token_issue' +}; const List = ({ beneficiaries, projectId }) => { + const { addToast } = useToasts(); const { beneficiary_pagination, beneficiaryByAid } = useContext(AidContext); + const { loading } = useContext(AppContext); + + const [amount, setAmount] = useState(''); + const [amountModal, setAmountModal] = useState(false); + const [currentAction, setCurrentAction] = useState(''); + + const toggleAmountModal = action => { + if (action) setCurrentAction(action); + setAmountModal(!amountModal); + }; + + const handleAmountChange = e => setAmount(e.target.value); + + const handleModalFormSubmit = e => { + e.preventDefault(); + if (currentAction === ACTION.BULK_QR) return handleBulkQrExport(); + }; const handlePagination = current_page => { let _start = (current_page - 1) * beneficiary_pagination.limit; return beneficiaryByAid(projectId, { start: _start }); }; + const convertQrToImg = async data => { + let result = []; + for (let d of data) { + const imgUrl = await QRCode.toDataURL(`phone:+977${d.phone}?amount=${amount ? amount : null}`); + result.push({ imgUrl, phone: d.phone }); + } + return result; + }; + + const handleBulkQrExport = async () => { + const res = await beneficiaryByAid(projectId, { limit: APP_CONSTANTS.BULK_BENEFICIARY_LIMIT }); + if (!res || !res.data.length) return addToast('No beneficiay available', TOAST.ERROR); + return printBulkQrCode(res.data); + }; + + const printBulkQrCode = async data => { + const qrcodeImages = await convertQrToImg(data); + const html = htmlResponse(data, qrcodeImages); + toggleAmountModal(); + setAmount(''); + var newWindow = window.open('', 'Print QR', 'fullscreen=yes'), + document = newWindow.document.open(); + document.write(html); + document.close(); + setTimeout(function () { + newWindow.print(); + newWindow.close(); + }, 250); + }; + return ( <> + + + + + + +
-
diff --git a/src/utils/printBeneficiary.js b/src/utils/printBeneficiary.js new file mode 100644 index 00000000..17964813 --- /dev/null +++ b/src/utils/printBeneficiary.js @@ -0,0 +1,57 @@ +export const htmlResponse = (data, qrcodeImages) => { + let html = ` + + + + + `; + // eslint-disable-next-line array-callback-return + data.map((d, i) => { + const name = `Name: ${d.name}`; + const address = `Address: ${d.address}`; + const govtID = `Govt. ID: ${d.govt_id}`; + const found = qrcodeImages.find(f => f.phone === d.phone); + if (i % 2 === 0) { + html += ` +
+
+ +
+
+
`; + } else { + html += ` +
+ +
+
+
+
`; + } + }); + if (data.length % 2 !== 0) { + html += ` +
+
+
`; + } + html += ''; + return html; +}; From 8df692ff68a0d076297b40af498a5c6c65c6fb3e Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Wed, 11 Aug 2021 08:53:45 +0545 Subject: [PATCH 17/59] pagination and modal ui realign --- src/assets/css/custom.css | 3 +- src/contexts/AidContext.js | 23 ++-- src/modules/aid/detail/tab/beneficiaryList.js | 115 +++++++++++++++--- src/modules/global/CustomModal.js | 2 +- src/modules/global/PasscodeModal.js | 11 +- 5 files changed, 115 insertions(+), 39 deletions(-) diff --git a/src/assets/css/custom.css b/src/assets/css/custom.css index 707d3a07..6fce95d7 100644 --- a/src/assets/css/custom.css +++ b/src/assets/css/custom.css @@ -184,7 +184,8 @@ a { } .modal-backdrop { - opacity: 1 !important; + opacity: 0.4 !important; + background-color: #2b7ec1; } .input-pin { background-color: black; diff --git a/src/contexts/AidContext.js b/src/contexts/AidContext.js index adae0b76..730c2d81 100644 --- a/src/contexts/AidContext.js +++ b/src/contexts/AidContext.js @@ -115,19 +115,6 @@ export const AidContextProvider = ({ children }) => { }); }, []); - // function beneficiaryByAid(aidId, params) { - // return new Promise((resolve, reject) => { - // Service.beneficiaryByAid(aidId, params) - // .then(res => { - // dispatch({ type: ACTION.BENEF_LIST_SUCCSS, res }); - // resolve(res); - // }) - // .catch(err => { - // reject(err); - // }); - // }); - // } - function addAid(payload) { return new Promise((resolve, reject) => { Service.addAid(payload) @@ -153,9 +140,13 @@ export const AidContextProvider = ({ children }) => { }); } - function bulkTokenIssueToBeneficiary(payload) { - return Service.bulkTokenIssueToBeneficiary({ ...payload }); - } + const bulkTokenIssueToBeneficiary = useCallback( + payload => { + changeIsverified(false); + return Service.bulkTokenIssueToBeneficiary({ ...payload }); + }, + [changeIsverified] + ); function listFinancialInstitutions(params) { return Service.listFinancialInstitutions(params); diff --git a/src/modules/aid/detail/tab/beneficiaryList.js b/src/modules/aid/detail/tab/beneficiaryList.js index 4833d3b5..ae4be982 100644 --- a/src/modules/aid/detail/tab/beneficiaryList.js +++ b/src/modules/aid/detail/tab/beneficiaryList.js @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react'; +import React, { useContext, useState, useEffect, useCallback } from 'react'; import { Pagination, PaginationItem, PaginationLink, Table, FormGroup, InputGroup, Input } from 'reactstrap'; import { useToasts } from 'react-toast-notifications'; import QRCode from 'qrcode'; @@ -8,6 +8,8 @@ import { AppContext } from '../../../../contexts/AppSettingsContext'; import { APP_CONSTANTS, TOAST } from '../../../../constants'; import { htmlResponse } from '../../../../utils/printBeneficiary'; import ModalWrapper from '../../../global/CustomModal'; +import PasscodeModal from '../../../global/PasscodeModal'; +import GrowSpinner from '../../../global/GrowSpinner'; const ACTION = { BULK_QR: 'bulk_qrcode_export', @@ -16,12 +18,17 @@ const ACTION = { const List = ({ beneficiaries, projectId }) => { const { addToast } = useToasts(); - const { beneficiary_pagination, beneficiaryByAid } = useContext(AidContext); - const { loading } = useContext(AppContext); + const { beneficiary_pagination, beneficiaryByAid, bulkTokenIssueToBeneficiary } = useContext(AidContext); + const { loading, setLoading, wallet, isVerified, appSettings } = useContext(AppContext); const [amount, setAmount] = useState(''); const [amountModal, setAmountModal] = useState(false); const [currentAction, setCurrentAction] = useState(''); + const [beneficiaryPhones, setBeneficiaryPhones] = useState([]); // For bulk issue + const [beneficiaryTokens, setBeneficiaryTokens] = useState([]); // For bulk issue + const [passcodeModal, setPasscodeModal] = useState(false); + + const togglePasscodeModal = () => setPasscodeModal(!passcodeModal); const toggleAmountModal = action => { if (action) setCurrentAction(action); @@ -33,6 +40,7 @@ const List = ({ beneficiaries, projectId }) => { const handleModalFormSubmit = e => { e.preventDefault(); if (currentAction === ACTION.BULK_QR) return handleBulkQrExport(); + if (currentAction === ACTION.BULK_ISSUE) return handleBulkTokenIssue(); }; const handlePagination = current_page => { @@ -49,6 +57,25 @@ const List = ({ beneficiaries, projectId }) => { return result; }; + const handleBulkTokenIssue = async () => { + let beneficiary_tokens = []; + if (!amount) return addToast('Please enter token amount', TOAST.ERROR); + const { data } = await beneficiaryByAid(projectId, { limit: APP_CONSTANTS.BULK_BENEFICIARY_LIMIT }); + if (!data || !data.length) return addToast('No beneficiary available', TOAST.ERROR); + if (data.length) { + const beneficiary_phones = data.map(d => d.phone); + const len = beneficiary_phones.length; + if (len < 1) return addToast('No phone number found', TOAST.ERROR); + for (let i = 0; i < len; i++) { + beneficiary_tokens.push(amount); + } + setBeneficiaryTokens(beneficiary_tokens); + setBeneficiaryPhones(beneficiary_phones); + toggleAmountModal(); + togglePasscodeModal(); + } + }; + const handleBulkQrExport = async () => { const res = await beneficiaryByAid(projectId, { limit: APP_CONSTANTS.BULK_BENEFICIARY_LIMIT }); if (!res || !res.data.length) return addToast('No beneficiay available', TOAST.ERROR); @@ -70,8 +97,51 @@ const List = ({ beneficiaries, projectId }) => { }, 250); }; + const submitBulkTokenIssue = useCallback(async () => { + if (wallet && isVerified) { + try { + setPasscodeModal(false); + setLoading(true); + const { contracts } = appSettings.agency; + let res = await bulkTokenIssueToBeneficiary({ + projectId: projectId, + phone_numbers: beneficiaryPhones, + token_amounts: beneficiaryTokens, + contract_address: contracts.rahat, + wallet + }); + if (res) { + setAmount(''); + const total_ben = beneficiaryPhones.length; + return addToast(`Each of ${total_ben} beneficiary has been assigned ${amount} tokens`, TOAST.SUCCESS); + } + } catch (err) { + addToast(err.message, TOAST.ERROR); + } finally { + setLoading(false); + } + } + }, [ + addToast, + amount, + appSettings.agency, + beneficiaryPhones, + beneficiaryTokens, + bulkTokenIssueToBeneficiary, + isVerified, + projectId, + setLoading, + wallet + ]); + + useEffect(() => { + submitBulkTokenIssue(); + }, [submitBulkTokenIssue, isVerified]); + return ( <> + + {
-
- - -
+ {loading ? ( + + ) : ( +
+ + +
+ )}
{/* )} diff --git a/src/modules/global/PasscodeModal.js b/src/modules/global/PasscodeModal.js index 2bfc5207..1987919f 100644 --- a/src/modules/global/PasscodeModal.js +++ b/src/modules/global/PasscodeModal.js @@ -67,7 +67,16 @@ export default function PasscodeModal(props) { {/* {children || 'No child elements supplied.'} */} {/* Close */} -

{title || 'Verify Passcode'}

+
+

+ {title || 'Verify Passcode'}   + + + + + +

+
From 356a321f826521443a6297e17a5edcefa852824a Mon Sep 17 00:00:00 2001 From: Manjik Shrestha Date: Wed, 11 Aug 2021 19:36:42 +0545 Subject: [PATCH 18/59] mobilizer CRUD --- src/actions/mobilizer.js | 13 + src/constants/api.js | 1 + src/contexts/MobilizerContext.js | 159 ++++++++++++ src/modules/mobilizer/detail/detail.js | 337 +++++++++++++++++++++++++ src/modules/mobilizer/detail/index.js | 13 + src/modules/mobilizer/index.js | 13 + src/modules/mobilizer/list.js | 301 ++++++++++++++++++++++ src/reducers/mobilizerReducer.js | 43 ++++ src/routes/Router.js | 18 +- src/services/mobilizer.js | 135 ++++++++++ 10 files changed, 1032 insertions(+), 1 deletion(-) create mode 100644 src/actions/mobilizer.js create mode 100644 src/contexts/MobilizerContext.js create mode 100644 src/modules/mobilizer/detail/detail.js create mode 100644 src/modules/mobilizer/detail/index.js create mode 100644 src/modules/mobilizer/index.js create mode 100644 src/modules/mobilizer/list.js create mode 100644 src/reducers/mobilizerReducer.js create mode 100644 src/services/mobilizer.js diff --git a/src/actions/mobilizer.js b/src/actions/mobilizer.js new file mode 100644 index 00000000..30832cfd --- /dev/null +++ b/src/actions/mobilizer.js @@ -0,0 +1,13 @@ +module.exports = { + LIST: "LIST_MOBILIZER", + LIST_AID: "LIST_AID", + SET_AID: "SET_AID", + SET_MOBILIZER: "SET_MOBILIZER", + ADD: "ADD_MOBILIZER", + REMOVE: "REMOVE_MOBILIZER", + UPDATE: "UPDATE_MOBILIZER", + GET: "GET_MOBILIZER", + SET_LOADING: "set_mobilizer_loading", + RESET_LOADING: "reset_mobilizer_reset_loading", + MOBILIZER_TX: "MOBILIZER_TX_HISTORY", +}; diff --git a/src/constants/api.js b/src/constants/api.js index 8ff30a94..0c872d63 100644 --- a/src/constants/api.js +++ b/src/constants/api.js @@ -14,4 +14,5 @@ module.exports = { USERS: API_PATH + "/users", VENDORS: API_PATH + "/vendors", FAUCET:process.env.REACT_APP_BLOCKCHAIN_FAUCET, + MOBILIZERS: API_PATH + "/mobilizers", }; diff --git a/src/contexts/MobilizerContext.js b/src/contexts/MobilizerContext.js new file mode 100644 index 00000000..6a654930 --- /dev/null +++ b/src/contexts/MobilizerContext.js @@ -0,0 +1,159 @@ +import React, { createContext, useReducer,useContext } from "react"; +import mobilizerReduce from "../reducers/mobilizerReducer"; +import * as Service from "../services/mobilizer"; +import * as AidService from "../services/aid"; +import ACTION from "../actions/mobilizer"; +import {AppContext} from './AppSettingsContext'; + +const initialState = { + list: [], + pagination: { limit: 10, start: 0, total: 0, currentPage: 1, totalPages: 0 }, + aid: "", + aids: [], + mobilizer: {}, + loading: false, + transactionHistory: [], +}; + +export const MobilizerContext = createContext(initialState); +export const MobilizerContextProvider = ({ children }) => { + const [state, dispatch] = useReducer(mobilizerReduce, initialState); + const {wallet,appSettings,changeIsverified} = useContext(AppContext); + + async function getMobilizerBalance(contract_addr, wallet_address) { + return Service.getMobilizerBalance(contract_addr, wallet_address); + } + + async function listAid() { + const d = await AidService.listAid({ start: 0, limit: 20 }); + dispatch({ type: ACTION.LIST_AID, data: { aids: d.data } }); + } + + function setAid(aid) { + dispatch({ type: ACTION.SET_AID, data: aid }); + } + + function clear() { + dispatch({ + type: ACTION.LIST, + data: { + limit: 10, + start: 0, + total: 0, + data: [], + page: 0, + name: "", + phone: "", + }, + }); + } + + async function approveMobilizer( payload) { + const { rahat:rahatContractAddr } = appSettings.agency.contracts; + const res = await Service.approveMobilizer(wallet,payload,rahatContractAddr); + changeIsverified(false); + if (res) { + setMobilizer(res.data); + return res.data; + } + } + + async function changeMobilizerStatus(mobilizerId, status) { + const res = await Service.changeMobilizerStaus(mobilizerId, status); + setMobilizer(res.data); + return res.data; + } + + function setMobilizer(b) { + dispatch({ type: ACTION.SET_MOBILIZER, data: b }); + } + + async function getMobilizerDetails(id) { + const data = await Service.get(id); + setMobilizer(data); + return data; + } + + function addMobilizer(e) { + e.preventDefault(); + const formData = new FormData(e.target); + let payload = { + name: formData.get("name"), + phone: formData.get("phone"), + wallet_address: formData.get("ethaddress"), + email: formData.get("email"), + address: formData.get("address"), + govt_id: formData.get("govt_id"), + }; + + return new Promise((resolve, reject) => { + Service.add(payload) + .then((res) => { + resolve(res); + }) + .catch((err) => { + reject(err); + }); + }); + } + + function setLoading() { + dispatch({ type: ACTION.SET_LOADING }); + } + + function resetLoading() { + dispatch({ type: ACTION.RESET_LOADING }); + } + + async function listMobilizer(params) { + console.log("listing MOB"); + let res = await Service.list(params); + if (res) { + dispatch({ + type: ACTION.LIST, + data: res, + }); + return res; + } + } + + async function getMobilizerTransactions(mobilizerId) { + let res = await Service.mobilizerTransactions(mobilizerId); + if (res) { + dispatch({ + type: ACTION.MOBILIZER_TX, + data: res, + }); + return res; + } + } + + return ( + + {children} + + ); +}; diff --git a/src/modules/mobilizer/detail/detail.js b/src/modules/mobilizer/detail/detail.js new file mode 100644 index 00000000..3c31650e --- /dev/null +++ b/src/modules/mobilizer/detail/detail.js @@ -0,0 +1,337 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { useToasts } from 'react-toast-notifications'; + +import { Card, CardBody, Row, Col, Input, ButtonGroup, Button, Table, CardSubtitle, CardTitle } from 'reactstrap'; +import Swal from 'sweetalert2'; + +import { MobilizerContext } from '../../../contexts/MobilizerContext'; +import { AppContext } from '../../../contexts/AppSettingsContext'; +import profilePhoto from '../../../assets/images/users/user_avatar.svg'; +import IdPhoto from '../../../assets/images/id-icon-1.png'; +import UnlockWallet from '../../global/walletUnlock'; +import Loading from '../../global/Loading'; + +const EXPLORER_URL = process.env.REACT_APP_BLOCKCHAIN_EXPLORER; +const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY; + +export default function DetailsForm(props) { + const mobilizerId = props.params.id; + const { addToast } = useToasts(); + const { + mobilizer, + getMobilizerDetails, + approveMobilizer, + changeMobilizerStatus, + getMobilizerBalance, + getMobilizerTransactions, + transactionHistory + } = useContext(MobilizerContext); + const { appSettings, isVerified, changeIsverified } = useContext(AppContext); + const [mobilizerBalance, setMobilizerBalance] = useState(''); + const [passcodeModal, setPasscodeModal] = useState(false); + const [loading, setLoading] = useState(false); + const togglePasscodeModal = () => setPasscodeModal(!passcodeModal); + + const loadMobilizerDetails = () => { + getMobilizerDetails(mobilizerId) + .then(d => { + getMobilizerTransactions(mobilizerId); + getBalance(d.wallet_address); + }) + .catch(() => { + addToast('Something went wrong on server!', { + appearance: 'error', + autoDismiss: true + }); + }); + }; + + const getBalance = async wallet => { + if (appSettings && appSettings.agency) { + try { + const { token } = appSettings.agency.contracts; + let d = await getMobilizerBalance(token, wallet); + setMobilizerBalance(d); + } catch { + addToast('Invalid Mobilizer wallet address!', { + appearance: 'error', + autoDismiss: true + }); + } + } + }; + + const handleChangeStatus = async status => { + let swal = await Swal.fire({ + title: 'Are you sure?', + text: `Mobilizer will be marked as ${status}`, + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: 'Yes' + }); + if (swal.isConfirmed) { + try { + await changeMobilizerStatus(mobilizerId, status); + addToast(`Mobilizer marked as ${status}`, { + appearance: 'success', + autoDismiss: true + }); + } catch (e) { + addToast('Something went wrong on server!', { + appearance: 'error', + autoDismiss: true + }); + } + } + }; + + const submitMobilizerApproval = e => { + if (!isVerified) return; + setLoading(true); + let payload = { + status: 'active', + wallet_address: mobilizer.wallet_address, + mobilizerId: mobilizerId + }; + approveMobilizer(payload) + .then(() => { + changeIsverified(false); + setLoading(false); + togglePasscodeModal(); + addToast('Mobilizer approved successfully.', { + appearance: 'success', + autoDismiss: true + }); + }) + .catch(() => { + changeIsverified(false); + setLoading(false); + togglePasscodeModal(); + addToast('Invalid mobilizer wallet address!', { + appearance: 'error', + autoDismiss: true + }); + }); + }; + + const handleMobilizerApprove = async e => { + e.preventDefault(); + let swal = await Swal.fire({ + title: 'Are you sure?', + text: `You want to approve this mobilizer!`, + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: 'Yes' + }); + if (swal.isConfirmed) togglePasscodeModal(); + }; + + useEffect(loadMobilizerDetails, []); + useEffect(submitMobilizerApproval, [isVerified]); + + const mobilizer_status = mobilizer && mobilizer.agencies ? mobilizer.agencies[0].status : 'new'; + + return ( + <> + setPasscodeModal(e)}> + + + + + +
+
+
+ user +
+
+
{mobilizer ? mobilizer.name : ''}
+

Current balance: {mobilizerBalance || 0}

+
+
+ {mobilizer_status !== 'new' ? ( + + + + + ) : loading ? ( + + ) : ( + + )} +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Status + +
Phone + +
Email + +
Government ID + +
Wallet Address + +
Address + +
+
+ +
+ user +
+
+
+
+ +
+ + + + + + Transaction history + {transactionHistory.length > 0 && ( + {transactionHistory.length} transactions found. + )} + + + + + + + + + + + + {transactionHistory && transactionHistory.length ? ( + transactionHistory.map((tx, index) => { + return ( + + + + + + + + ); + }) + ) : ( + + + + )} + +
FromToValueTypeBlockchain Tx
{tx.from || ''}{tx.to || '-'}{tx.value || '-'} + {(tx.tag === 'sent' && tx.to === appSettings.agency.contracts.rahat_admin + ? 'redeem' + : tx.tag) || '-'} + + + Verify + +
No data available.
+
+
+ +
+ + ); +} diff --git a/src/modules/mobilizer/detail/index.js b/src/modules/mobilizer/detail/index.js new file mode 100644 index 00000000..8aac16da --- /dev/null +++ b/src/modules/mobilizer/detail/index.js @@ -0,0 +1,13 @@ +import React from "react"; +import { MobilizerContextProvider } from "../../../contexts/MobilizerContext"; +import Detail from "./detail"; + +const index = props => { + return ( + + + + ); +}; + +export default index; diff --git a/src/modules/mobilizer/index.js b/src/modules/mobilizer/index.js new file mode 100644 index 00000000..cd697f50 --- /dev/null +++ b/src/modules/mobilizer/index.js @@ -0,0 +1,13 @@ +import React from "react"; +import { MobilizerContextProvider } from "../../contexts/MobilizerContext"; +import List from "./list"; + +const index = () => { + return ( + + + + ); +}; + +export default index; diff --git a/src/modules/mobilizer/list.js b/src/modules/mobilizer/list.js new file mode 100644 index 00000000..d6b635b3 --- /dev/null +++ b/src/modules/mobilizer/list.js @@ -0,0 +1,301 @@ +import React, { useState, useEffect, useContext } from 'react'; +import { MobilizerContext } from '../../contexts/MobilizerContext'; +import { useToasts } from 'react-toast-notifications'; +import { Link } from 'react-router-dom'; + +import { + Card, + CardBody, + CardTitle, + Button, + Input, + Pagination, + PaginationItem, + PaginationLink, + Modal, + ModalHeader, + ModalBody, + ModalFooter, + Form, + Table, + Row, + Col, + CustomInput +} from 'reactstrap'; + +import displayPic from '../../assets/images/users/user_avatar.svg'; + +const searchOptions = { PHONE: 'phone', NAME: 'name' }; + +const Mobilizer = () => { + const { addToast } = useToasts(); + const [model, setModel] = useState(false); + const [filter, setFilter] = useState({ + searchPlaceholder: 'Enter phone number...', + searchBy: 'phone' + }); + + const { listMobilizer, list, pagination, addMobilizer } = useContext(MobilizerContext); + + const toggle = () => setModel(!model); + + const fetchList = query => { + console.log("LSIISHD") + let params = { ...pagination, ...query }; + console.log({params}); + listMobilizer(params) + .then() + .catch(() => { + addToast('Something went wrong!', { + appearance: 'error', + autoDismiss: true + }); + }); + }; + + useEffect(fetchList, []); + + const handleFilterChange = e => { + let { value } = e.target; + if (value === searchOptions.NAME) { + setFilter({ + searchPlaceholder: 'Enter name...', + searchBy: searchOptions.NAME + }); + } + if (value === searchOptions.PHONE) { + setFilter({ + searchPlaceholder: 'Enter phone number...', + searchBy: searchOptions.PHONE + }); + } + fetchList({ start: 0, limit: pagination.limit }); + }; + + const handleSearchInputChange = e => { + const { value } = e.target; + if (filter.searchBy === searchOptions.PHONE) { + return fetchList({ start: 0, limit: pagination.limit, phone: value }); + } + if (filter.searchBy === searchOptions.NAME) { + return fetchList({ start: 0, limit: pagination.limit, name: value }); + } + fetchList({ start: 0, limit: pagination.limit }); + }; + + const handlePagination = current_page => { + let _start = (current_page - 1) * pagination.limit; + return fetchList({ start: _start, limit: pagination.limit }); + }; + + return ( +
+
+ + + + Mobilizers + +
+ + + + +
+ +
+
+ + +
+ +
+ +
+
+ + + + + + + + + + + + {list.length ? ( + list.map((e, i) => ( + + + + + + + )) + ) : ( + + + + )} + +
NamePhoneAddressAction
+
+
+ user +
+
+
{e.name}
+ {e.email ? e.email : '-'} +
+
+
{e.phone}{e.address} + + Details + +
+ No data available +
+ + {pagination.totalPages > 1 ? ( + + + handlePagination(1)} /> + + {[...Array(pagination.totalPages)].map((p, i) => ( + handlePagination(i + 1)} + > + {i + 1} + + ))} + + handlePagination(pagination.totalPages)} /> + + + ) : ( + '' + )} +
+
+
+ + +
{ + e.preventDefault(); + addMobilizer(e) + .then(() => { + addToast('Vendor Added Successfully', { + appearance: 'success', + autoDismiss: true + }); + fetchList({}); + toggle(); + }) + .catch(err => + addToast(err.message, { + appearance: 'error', + autoDismiss: true + }) + ); + }} + > + +
+

Add Vendor

+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+ +
+ +
+
+
+
+ + + + +
+
+
+
+ ); +}; + +export default Mobilizer; diff --git a/src/reducers/mobilizerReducer.js b/src/reducers/mobilizerReducer.js new file mode 100644 index 00000000..b005634b --- /dev/null +++ b/src/reducers/mobilizerReducer.js @@ -0,0 +1,43 @@ +import ACTION from '../actions/mobilizer'; + +export default (state, action) => { + switch (action.type) { + case `${ACTION.LIST}`: + return { + ...state, + list: action.data.data, + pagination: { + limit: parseInt(action.data.limit), + start: parseInt(action.data.start), + total: parseInt(action.data.total), + currentPage: parseInt(action.data.page), + totalPages: Math.ceil(action.data.total / action.data.limit), + }, + query: { name: action.data.name, phone: action.data.phone }, + }; + + case `${ACTION.MOBILIZER_TX}`: + return { ...state, transactionHistory: action.data }; + + case `${ACTION.LIST_AID}`: + return { + ...state, + aids: action.data.aids, + }; + + case `${ACTION.SET_AID}`: + return { + ...state, + aid: action.data, + }; + + case `${ACTION.SET_MOBILIZER}`: + return { + ...state, + mobilizer: action.data, + }; + + default: + return state; + } +}; diff --git a/src/routes/Router.js b/src/routes/Router.js index fd8d3eee..6af20242 100644 --- a/src/routes/Router.js +++ b/src/routes/Router.js @@ -2,7 +2,7 @@ import { lazy } from 'react'; const Dashboard = lazy(() => import('../modules/dashboard/Dashboard')); const Beneficiary = lazy(() => import('../modules/beneficary')); - +const Mobilizer = lazy(() => import('../modules/mobilizer')); const Vendor = lazy(() => import('../modules/vendor')); const AgencyList = lazy(() => import('../modules/agency/list')); const AgencyDetails = lazy(() => import('../modules/agency/details')); @@ -15,6 +15,7 @@ const InstitutionList = lazy(() => import('../modules/institution')); const InstitutionDetails = lazy(() => import('../modules/institution/detail/index')); const VendorDetails = lazy(() => import('../modules/vendor/detail/index')); const BeneficiaryDetails = lazy(() => import('../modules/beneficary/detail/index')); +const MobilizerDetails = lazy(() => import('../modules/mobilizer/detail/index')); const Onboard = lazy(() => import('../modules/onboard')); const ListUsers = lazy(() => import('../modules/user/list')); @@ -41,6 +42,8 @@ const VendorDetail = lazy(() => import('../views/vendors/detail')); // -------------------------------------------------------------------------------- + + var AppRoutes = [ { path: '/dashboard', @@ -78,6 +81,12 @@ var AppRoutes = [ name: 'Vendor', component: VendorDetails }, + + { + path: '/mobilizers/:id', + name: 'Mobilizer', + component: MobilizerDetails + }, { path: '/projects/:id', name: 'Aid', @@ -151,6 +160,13 @@ var AppRoutes = [ component: Vendor, showInSidebar: true }, + { + path: '/mobilizers', + name: 'Mobilizers', + icon: 'git-merge', + component: Mobilizer, + showInSidebar: true + }, { path: '/add_user', name: 'Users', diff --git a/src/services/mobilizer.js b/src/services/mobilizer.js new file mode 100644 index 00000000..796cac69 --- /dev/null +++ b/src/services/mobilizer.js @@ -0,0 +1,135 @@ +import axios from "axios"; + +import API from "../constants/api"; +import { getUserToken } from "../utils/sessionManager"; +import CONTRACT from "../constants/contracts"; +import { getContractByProvider } from "../blockchain/abi"; + +const access_token = getUserToken(); +const faucet_auth_token = process.env.REACT_APP_BLOCKCHAIN_FAUCET_AUTH_TOKEN; + +const mapTestContract = (contract) => ({ + addAdmin: contract.addAdmin, + balanceOf: contract.balanceOf, +}); + +export async function getMobilizerBalance(contract_address, wallet_addr) { + const contract = await getContractByProvider(contract_address, CONTRACT.AIDTOKEN); + const myContract = mapTestContract(contract); + const data = await myContract.balanceOf(wallet_addr); + if (!data) return "Mobilizer not found!"; + return data.toNumber(); +} + +export async function approveMobilizer(wallet, payload,contract_address) { + try{ + const contract = await getContractByProvider(contract_address, CONTRACT.RAHAT); + const signerContract = contract.connect(wallet); + const myContract = mapTestContract(signerContract); + const data = await myContract.addAdmin(payload.wallet_address); + if (!data) return "Mobilizer approve failed!"; + const res = await changeMobilizerStaus(payload.mobilizerId, payload.status); + getEth({address:payload.wallet_address}); + return res; + } + catch(e){ + console.log(e); + throw Error(e); + } +} + +export async function changeMobilizerStaus(mobilizerId, status) { + return axios.patch( + `${API.MOBILIZERS}/${mobilizerId}/status/`, + { status: status }, + { + headers: { access_token: access_token }, + } + ); +} + +export async function list(params) { + console.log("MOBILIXERS") + const res = await axios({ + url: API.MOBILIZERS, + method: "get", + headers: { + access_token, + }, + params, + }); + return res.data; +} + +export async function get(id) { + const res = await axios({ + url: API.MOBILIZERS + "/" + id, + method: "get", + headers: { + access_token, + }, + }); + return res.data; +} + +export async function mobilizerTransactions(mobilizerId) { + const res = await axios({ + url: `${API.MOBILIZERS}/${mobilizerId}/transactions`, + method: "get", + headers: { access_token }, + }); + return res.data; +} + +export async function listByAid(aid, params) { + const res = await axios({ + url: API.MOBILIZERS + `/aid/${aid}/mobilizer`, + method: "get", + headers: { + access_token, + }, + params, + }); + return res.data; +} + +export function add(payload) { + return new Promise((resolve, reject) => { + axios + .post(`${API.MOBILIZERS}`, payload, { + headers: { access_token: access_token }, + }) + .then((res) => { + if (res.statusText === "OK") { + resolve(res.data); + } + reject(res.data); + }) + .catch((err) => { + reject(err); + }); + }); +} + +export async function approve({ mobilizerId }) { + const res = await axios({ + url: API.MOBILIZERS + `/approve`, + method: "post", + headers: { + access_token, + }, + data: { mobilizerId }, + }); + + return res.data; +} + +export async function getEth({ address }) { + const res = await axios({ + url: API.FAUCET, + method: "post", + data: { address,token:faucet_auth_token }, + }); + + return res.data; +} From f48c9cc86f44bef7fefa31b7fece3491d6ad7656 Mon Sep 17 00:00:00 2001 From: Manjik Shrestha Date: Thu, 12 Aug 2021 08:29:39 +0545 Subject: [PATCH 19/59] added organizaiton --- src/contexts/MobilizerContext.js | 1 + src/modules/mobilizer/detail/detail.js | 12 ++++++++++++ src/modules/mobilizer/list.js | 10 ++++++++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/contexts/MobilizerContext.js b/src/contexts/MobilizerContext.js index 6a654930..223aec2c 100644 --- a/src/contexts/MobilizerContext.js +++ b/src/contexts/MobilizerContext.js @@ -84,6 +84,7 @@ export const MobilizerContextProvider = ({ children }) => { email: formData.get("email"), address: formData.get("address"), govt_id: formData.get("govt_id"), + organization:formData.get("organization") }; return new Promise((resolve, reject) => { diff --git a/src/modules/mobilizer/detail/detail.js b/src/modules/mobilizer/detail/detail.js index 3c31650e..7c676800 100644 --- a/src/modules/mobilizer/detail/detail.js +++ b/src/modules/mobilizer/detail/detail.js @@ -259,6 +259,18 @@ export default function DetailsForm(props) { /> + + Organization + + + +
diff --git a/src/modules/mobilizer/list.js b/src/modules/mobilizer/list.js index d6b635b3..e733307f 100644 --- a/src/modules/mobilizer/list.js +++ b/src/modules/mobilizer/list.js @@ -212,7 +212,7 @@ const Mobilizer = () => { e.preventDefault(); addMobilizer(e) .then(() => { - addToast('Vendor Added Successfully', { + addToast('Mobilizer Added Successfully', { appearance: 'success', autoDismiss: true }); @@ -229,7 +229,7 @@ const Mobilizer = () => { >
-

Add Vendor

+

Add Mobilizer

@@ -284,6 +284,12 @@ const Mobilizer = () => {

+
+ +
+ +
+
From 7fff82e8f552e307d5867db8bc019a0b66155af4 Mon Sep 17 00:00:00 2001 From: Manjik Shrestha Date: Thu, 12 Aug 2021 11:56:42 +0545 Subject: [PATCH 20/59] fix passcode modal --- src/modules/global/LiteModal.js | 48 ++++++++------------------------- 1 file changed, 11 insertions(+), 37 deletions(-) diff --git a/src/modules/global/LiteModal.js b/src/modules/global/LiteModal.js index e14ed62d..e6f432e9 100644 --- a/src/modules/global/LiteModal.js +++ b/src/modules/global/LiteModal.js @@ -1,10 +1,10 @@ import React, { useState } from 'react'; -import { Form, Modal, ModalBody } from 'reactstrap'; +import { Form, Modal, ModalBody,ModalHeader } from 'reactstrap'; import PropTypes from 'prop-types'; import useDigitInput from 'react-digit-input'; export default function CustomModal(props) { - const { toggle, title, loadingMessage, open, size, handleSubmit } = props; + const { children,toggle, title, loadingMessage, open, size, handleSubmit } = props; const [value, onChange] = useState(''); const digits = useDigitInput({ acceptedCharacters: /^[0-9]$/, @@ -14,41 +14,15 @@ export default function CustomModal(props) { }); return ( <> - -
- - {/* {children || 'No child elements supplied.'} */} - {/* Close */} -

{title || 'Modal Title'}

- -
-
- - - - - - -
- {/*
-								"{value}"
-							
*/} -
-
- {loadingMessage ? loadingMessage : ''} -
-
-
-
+ +
+ {title || 'Modal Title'} + {children || 'No child elements supplied.'} +
+ {loadingMessage ? loadingMessage : ''} +
+
+
); } From 0944b250fe7ef75a1827be9be1af40636f0637b5 Mon Sep 17 00:00:00 2001 From: Manjik Shrestha Date: Thu, 12 Aug 2021 11:56:57 +0545 Subject: [PATCH 21/59] assign mobilizer to project --- src/contexts/MobilizerContext.js | 9 +- src/modules/beneficary/detail/detail.js | 2 + src/modules/mobilizer/detail/detail.js | 168 ++++++++++++++++++++++-- src/services/mobilizer.js | 13 +- 4 files changed, 176 insertions(+), 16 deletions(-) diff --git a/src/contexts/MobilizerContext.js b/src/contexts/MobilizerContext.js index 223aec2c..45a5abb9 100644 --- a/src/contexts/MobilizerContext.js +++ b/src/contexts/MobilizerContext.js @@ -24,9 +24,16 @@ export const MobilizerContextProvider = ({ children }) => { return Service.getMobilizerBalance(contract_addr, wallet_address); } + + async function getAvailableBalance(proejctId, rahatAdminContractAddr) { + const { rahat:rahatContractAddr } = appSettings.agency.contracts; + return AidService.loadAidBalance(proejctId, rahatContractAddr); + } + async function listAid() { const d = await AidService.listAid({ start: 0, limit: 20 }); dispatch({ type: ACTION.LIST_AID, data: { aids: d.data } }); + return d; } function setAid(aid) { @@ -107,7 +114,6 @@ export const MobilizerContextProvider = ({ children }) => { } async function listMobilizer(params) { - console.log("listing MOB"); let res = await Service.list(params); if (res) { dispatch({ @@ -152,6 +158,7 @@ export const MobilizerContextProvider = ({ children }) => { changeMobilizerStatus, getMobilizerBalance, getMobilizerTransactions, + getAvailableBalance }} > {children} diff --git a/src/modules/beneficary/detail/detail.js b/src/modules/beneficary/detail/detail.js index 920ed512..0cd1e632 100644 --- a/src/modules/beneficary/detail/detail.js +++ b/src/modules/beneficary/detail/detail.js @@ -23,6 +23,8 @@ import Swal from 'sweetalert2'; import { BeneficiaryContext } from '../../../contexts/BeneficiaryContext'; import { AppContext } from '../../../contexts/AppSettingsContext'; +import PasscodeModal from '../../global/PasscodeModal'; + import profilePhoto from '../../../assets/images/users/user_avatar.svg'; import UnlockWallet from '../../global/walletUnlock'; diff --git a/src/modules/mobilizer/detail/detail.js b/src/modules/mobilizer/detail/detail.js index 7c676800..cafc7810 100644 --- a/src/modules/mobilizer/detail/detail.js +++ b/src/modules/mobilizer/detail/detail.js @@ -1,7 +1,15 @@ import React, { useContext, useEffect, useState } from 'react'; import { useToasts } from 'react-toast-notifications'; +import Select from 'react-select'; +import { Link } from 'react-router-dom'; -import { Card, CardBody, Row, Col, Input, ButtonGroup, Button, Table, CardSubtitle, CardTitle } from 'reactstrap'; +import { Card, + CardBody, Row, Col, Input, ButtonGroup, Button, Table, CardSubtitle, CardTitle,Label,FormGroup, + InputGroup, + Modal, + ModalBody, + ModalHeader, + ModalFooter } from 'reactstrap'; import Swal from 'sweetalert2'; import { MobilizerContext } from '../../../contexts/MobilizerContext'; @@ -24,13 +32,22 @@ export default function DetailsForm(props) { changeMobilizerStatus, getMobilizerBalance, getMobilizerTransactions, - transactionHistory + transactionHistory, + getAvailableBalance, + listAid, } = useContext(MobilizerContext); const { appSettings, isVerified, changeIsverified } = useContext(AppContext); const [mobilizerBalance, setMobilizerBalance] = useState(''); const [passcodeModal, setPasscodeModal] = useState(false); const [loading, setLoading] = useState(false); const togglePasscodeModal = () => setPasscodeModal(!passcodeModal); + const [modal, setModal] = useState(false); + const [projectOptions, setProjectOptions] = useState([]); + const [inputTokens, setInputTokens] = useState(null); + const [selectedProject, setSelectedProject] = useState(''); + const [availableBalance, setAvailableBalance] = useState(null); + const [showAlert, setShowAlert] = useState(false); + const loadMobilizerDetails = () => { getMobilizerDetails(mobilizerId) @@ -87,13 +104,37 @@ export default function DetailsForm(props) { } }; + const handleTokenChange = e => { + setInputTokens(e.target.value); + }; + + + const handleSelectProject = async e => { + try { + setLoading(true); + setSelectedProject(e.value); + //const { rahat_admin } = appSettings.agency.contracts; + let d = await getAvailableBalance(e.value); + setAvailableBalance(d); + setShowAlert(true); + setLoading(false); + } catch { + setShowAlert(false); + addToast('Failed to fetch availabe balance!', { + appearance: 'error', + autoDismiss: true + }); + setLoading(false); + } + }; + const submitMobilizerApproval = e => { if (!isVerified) return; setLoading(true); let payload = { status: 'active', wallet_address: mobilizer.wallet_address, - mobilizerId: mobilizerId + projectId: selectedProject }; approveMobilizer(payload) .then(() => { @@ -116,20 +157,65 @@ export default function DetailsForm(props) { }); }; + const toggleModal = () => { + setModal(prevState => !prevState); + resetTokenIssueForm(); + }; + + const resetTokenIssueForm = () => { + setInputTokens(null); + setAvailableBalance(''); + setShowAlert(false); + }; + + const handleMobilizerApprove = async e => { e.preventDefault(); - let swal = await Swal.fire({ - title: 'Are you sure?', - text: `You want to approve this mobilizer!`, - icon: 'warning', - showCancelButton: true, - confirmButtonColor: '#3085d6', - cancelButtonColor: '#d33', - confirmButtonText: 'Yes' - }); - if (swal.isConfirmed) togglePasscodeModal(); + // let swal = await Swal.fire({ + // title: 'Are you sure?', + // text: `You want to approve this mobilizer!`, + // icon: 'warning', + // showCancelButton: true, + // confirmButtonColor: '#3085d6', + // cancelButtonColor: '#d33', + // confirmButtonText: 'Yes' + // }); + // if (swal.isConfirmed) + toggleModal(); + togglePasscodeModal(); }; + + const fetchProjectList = () => { + listAid() + .then(d => { + sanitizeProjectOptions(d.data); + }) + .catch(() => { + addToast('Something went wrong!', { + appearance: 'error', + autoDismiss: true + }); + }); + }; + + const sanitizeProjectOptions = data => { + let options = []; + if (data && data.length) { + for (let d of data) { + let obj = {}; + obj.value = d._id; + obj.label = d.name; + options.push(obj); + } + setProjectOptions(options); + return; + } + setProjectOptions(options); + }; + + useEffect(fetchProjectList, []); + useEffect(loadMobilizerDetails, []); useEffect(submitMobilizerApproval, [isVerified]); @@ -139,6 +225,60 @@ export default function DetailsForm(props) { <> setPasscodeModal(e)}> + + + + + Issue Token + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + + + + + + + + + + + + + + + {/* {loading ? ( + + ) : ( */} +
+ + +
+ {/* )} */} +
+ + + + + +
+ ); +}; + +export default Add; diff --git a/src/modules/beneficary/add/index.js b/src/modules/beneficary/add/index.js new file mode 100644 index 00000000..b839eeb9 --- /dev/null +++ b/src/modules/beneficary/add/index.js @@ -0,0 +1,14 @@ +import React from 'react'; + +import { BeneficiaryContextProvider } from '../../../contexts/BeneficiaryContext'; +import AddBeneficiary from './add'; + +export default function Index() { + return ( + <> + + + + + ); +} diff --git a/src/modules/beneficary/list.js b/src/modules/beneficary/list.js index 8e7a37d2..ba672dfb 100644 --- a/src/modules/beneficary/list.js +++ b/src/modules/beneficary/list.js @@ -12,22 +12,17 @@ import { PaginationItem, PaginationLink, CustomInput, - Modal, - ModalHeader, - ModalBody, - ModalFooter, Table, - Form, Row, Col } from 'reactstrap'; import displayPic from '../../assets/images/users/user_avatar.svg'; +import { History } from '../../utils/History'; const searchOptions = { PHONE: 'phone', NAME: 'name', PROJECT: 'project' }; const Beneficiary = () => { const { addToast } = useToasts(); - const [model, setModel] = useState(false); const [filter, setFilter] = useState({ searchPlaceholder: 'Enter phone number...', @@ -35,7 +30,7 @@ const Beneficiary = () => { }); const [selectedProject, setSelectedProject] = useState(''); - const { listBeneficiary, list, pagination, listAid, projectList, addBeneficiary } = useContext(BeneficiaryContext); + const { listBeneficiary, list, pagination, listAid, projectList } = useContext(BeneficiaryContext); const handleFilterChange = e => { let { value } = e.target; @@ -76,8 +71,6 @@ const Beneficiary = () => { fetchList({ start: 0, limit: pagination.limit }); }; - const toggle = () => setModel(!model); - const fetchList = query => { let params = { ...pagination, ...query }; listBeneficiary(params) @@ -111,6 +104,8 @@ const Beneficiary = () => { return fetchList(query); }; + const handleAddClick = () => History.push('/add-beneficiary'); + return (
@@ -169,7 +164,7 @@ const Beneficiary = () => {
-
@@ -253,139 +248,6 @@ const Beneficiary = () => {
- - -
{ - e.preventDefault(); - addBeneficiary(e) - .then(d => { - addToast('Beneficiary Added successfully', { - appearance: 'success', - autoDismiss: true - }); - fetchList(); - toggle(); - }) - .catch(err => - addToast(err.message, { - appearance: 'error', - autoDismiss: true - }) - ); - }} - > - -
-

Add Beneficiary

-
-
- -
-
- -
- - - - {projectList.map(e => ( - - ))} - -
-
- -
- -
-
-
-
-
- -
- -
-
- -
- -
-
-
-
-
- -
- -
- -
- -
- -
-
-
- -
-
- -
- -
-
- -
- -
-
-
-
- - - - - -
-
-
); }; diff --git a/src/routes/Router.js b/src/routes/Router.js index 8bf6c367..8b518a74 100644 --- a/src/routes/Router.js +++ b/src/routes/Router.js @@ -1,31 +1,44 @@ import { lazy } from 'react'; -const Dashboard = lazy(() => import('../modules/dashboard/Dashboard')); -const Beneficiary = lazy(() => import('../modules/beneficary')); -const Mobilizer = lazy(() => import('../modules/mobilizer')); -const Vendor = lazy(() => import('../modules/vendor')); +// Agency const AgencyList = lazy(() => import('../modules/agency/list')); const AgencyDetails = lazy(() => import('../modules/agency/details')); -const AidList = lazy(() => import('../modules/aid/list')); -const AidDetails = lazy(() => import('../modules/aid/detail')); -const AddProject = lazy(() => import('../modules/aid/add')); -const EditProject = lazy(() => import('../modules/aid/edit')); - const AgencyProfile = lazy(() => import('../modules/agency/profile')); + +// Beneficiary +const Beneficiary = lazy(() => import('../modules/beneficary')); +const BeneficiaryDetails = lazy(() => import('../modules/beneficary/detail/index')); +const AddBeneficiary = lazy(() => import('../modules/beneficary/add')); + +// Institutions const InstitutionList = lazy(() => import('../modules/institution')); const InstitutionDetails = lazy(() => import('../modules/institution/detail/index')); -const VendorDetails = lazy(() => import('../modules/vendor/detail/index')); -const BeneficiaryDetails = lazy(() => import('../modules/beneficary/detail/index')); -const MobilizerDetails = lazy(() => import('../modules/mobilizer/detail/index')); +// Misc +const Dashboard = lazy(() => import('../modules/dashboard/Dashboard')); const Onboard = lazy(() => import('../modules/onboard')); + +// Mobilizer +const Mobilizer = lazy(() => import('../modules/mobilizer')); +const MobilizerDetails = lazy(() => import('../modules/mobilizer/detail/index')); + +// Project +const AidList = lazy(() => import('../modules/aid/list')); +const AidDetails = lazy(() => import('../modules/aid/detail')); +const AddProject = lazy(() => import('../modules/aid/add')); +const EditProject = lazy(() => import('../modules/aid/edit')); +const BudgetAdd = lazy(() => import('../modules/aid/detail/budgetAdd')); + +// Users const ListUsers = lazy(() => import('../modules/user/list')); const AddUser = lazy(() => import('../modules/user/add')); const UserDetails = lazy(() => import('../modules/user/edit')); -const BudgetAdd = lazy(() => import('../modules/aid/detail/budgetAdd')); -// ------------------------------Beneficiary UI------------------------------------ +// Vendor +const Vendor = lazy(() => import('../modules/vendor')); +const VendorDetails = lazy(() => import('../modules/vendor/detail/index')); +// ------------------------------Beneficiary UI------------------------------------ const BeneficiaryAdd = lazy(() => import('../views/beneficiaries/add')); const BeneficiaryDetail = lazy(() => import('../views/beneficiaries/detail')); @@ -38,9 +51,7 @@ const VendorDetail = lazy(() => import('../views/vendors/detail')); // -------------------------------------------------------------------------------- - - -var AppRoutes = [ +let AppRoutes = [ { path: '/dashboard', name: 'Dashboard', @@ -72,10 +83,20 @@ var AppRoutes = [ name: 'Beneficiary', component: BeneficiaryDetails }, + { - path: '/vendors/:id', - name: 'Vendor', - component: VendorDetails + path: '/add-beneficiary', + name: 'Beneficiary', + icon: 'users', + component: AddBeneficiary + }, + + { + path: '/beneficiaries', + name: 'Beneficiary', + icon: 'users', + component: Beneficiary, + showInSidebar: true }, { @@ -139,13 +160,10 @@ var AppRoutes = [ component: VendorDetail }, // ----------------------------------------------------------------- - { - path: '/beneficiaries', - name: 'Beneficiary', - icon: 'users', - component: Beneficiary, - showInSidebar: true + path: '/vendors/:id', + name: 'Vendor', + component: VendorDetails }, { path: '/vendors', @@ -154,6 +172,7 @@ var AppRoutes = [ component: Vendor, showInSidebar: true }, + { path: '/mobilizers', name: 'Mobilizers', From a75537d60070d27fbbfb9eb0063f38cc37c23ea7 Mon Sep 17 00:00:00 2001 From: rojanbade Date: Fri, 13 Aug 2021 12:33:49 +0545 Subject: [PATCH 23/59] vendor UI --- src/assets/images/MOU.png | Bin 0 -> 72935 bytes src/assets/images/sign.png | Bin 0 -> 30993 bytes src/views/balance.js | 4 - .../beneficiaries/detail/beneficiaryInfo.js | 10 +- src/views/beneficiaries/detail/index.js | 4 +- src/views/project/detail/budgetAdd.js | 25 ++--- src/views/project/detail/index.js | 2 +- src/views/totalCard.js | 27 +++++ src/views/vendors/add/index.js | 45 ++------ src/views/vendors/detail/index.js | 45 ++++++++ .../vendors/detail/transactionHistory.js | 59 ++++++++++ src/views/vendors/detail/vendorDetail.js | 40 +++++++ src/views/vendors/detail/vendorInfo.js | 79 ++++++++++++++ yarn.lock | 101 ++++++++++++++++++ 14 files changed, 377 insertions(+), 64 deletions(-) create mode 100644 src/assets/images/MOU.png create mode 100644 src/assets/images/sign.png create mode 100644 src/views/totalCard.js create mode 100644 src/views/vendors/detail/transactionHistory.js create mode 100644 src/views/vendors/detail/vendorDetail.js create mode 100644 src/views/vendors/detail/vendorInfo.js diff --git a/src/assets/images/MOU.png b/src/assets/images/MOU.png new file mode 100644 index 0000000000000000000000000000000000000000..7d93e5ee78591a358928c83d4ee22fd4e17f264a GIT binary patch literal 72935 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 GdGsGYRb;aO literal 0 HcmV?d00001 diff --git a/src/assets/images/sign.png b/src/assets/images/sign.png new file mode 100644 index 0000000000000000000000000000000000000000..b9bba1f9edd79a872f9eefbe328f42c8ab88b467 GIT binary patch literal 30993 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 - {/*

-
-
-
*/}
diff --git a/src/views/beneficiaries/detail/beneficiaryInfo.js b/src/views/beneficiaries/detail/beneficiaryInfo.js index 8626d714..4b65ecb7 100644 --- a/src/views/beneficiaries/detail/beneficiaryInfo.js +++ b/src/views/beneficiaries/detail/beneficiaryInfo.js @@ -18,7 +18,7 @@ export default function BeneficiaryInfo() {
- +

Sindhupalchowk , Kavre

Address
@@ -36,7 +36,7 @@ export default function BeneficiaryInfo() {
Number of family member (Adult)
- +

Bachelors in computer science

Education
@@ -54,12 +54,14 @@ export default function BeneficiaryInfo() {
Number of family member(Child)
- +

Software Engineer

Profession
- certificate +
+ certificate +
diff --git a/src/views/beneficiaries/detail/index.js b/src/views/beneficiaries/detail/index.js index 6723ffe2..deefbc22 100644 --- a/src/views/beneficiaries/detail/index.js +++ b/src/views/beneficiaries/detail/index.js @@ -9,7 +9,7 @@ const projects = [ { id: '0', name: 'Sindhupalchowk relief' }, { id: '1', name: 'Flood relief distribution' } ]; -const ProjectDetail = () => { +const BeneficiaryDetail = () => { return ( <>

Beneficiary

@@ -41,4 +41,4 @@ const ProjectDetail = () => { ); }; -export default ProjectDetail; +export default BeneficiaryDetail; diff --git a/src/views/project/detail/budgetAdd.js b/src/views/project/detail/budgetAdd.js index 4e4208b2..56fff10d 100644 --- a/src/views/project/detail/budgetAdd.js +++ b/src/views/project/detail/budgetAdd.js @@ -1,7 +1,8 @@ import React, { useState, useCallback } from 'react'; -import { Button, Card, CardTitle, Col, Form, FormGroup, Input, InputGroup, InputGroupAddon, Row } from 'reactstrap'; +import { Button, Card, CardTitle, Form, FormGroup, Input, InputGroup, InputGroupAddon } from 'reactstrap'; import '../../project.css'; import UnlockWallet from '../../../modules/global/walletUnlock'; +import TotalCard from '../../totalCard'; export default function BudgetAdd() { const [inputTokens, setInputToken] = useState(''); @@ -21,21 +22,13 @@ export default function BudgetAdd() { return (
setPasscodeModal(e)}> - -
- Budget - - -

10,000,000

-
Total Project Budget
- - -

50,000

-
Total Redeemed Budget
- -
-
-
+
Add Budget diff --git a/src/views/project/detail/index.js b/src/views/project/detail/index.js index b5122e79..6cfad3ac 100644 --- a/src/views/project/detail/index.js +++ b/src/views/project/detail/index.js @@ -29,7 +29,7 @@ const ProjectDetail = () => { /> - + diff --git a/src/views/totalCard.js b/src/views/totalCard.js new file mode 100644 index 00000000..b9b14703 --- /dev/null +++ b/src/views/totalCard.js @@ -0,0 +1,27 @@ +import React from 'react'; +import { Card, CardTitle, Col, Row } from 'reactstrap'; +import './project.css'; + +export default function Balance(props) { + const { title, data1, data2, sub_title1, sub_title2 } = props; + + return ( +
+ +
+ {title} + + +

{data1}

+
{sub_title1}
+ + +

{data2}

+
{sub_title2}
+ +
+
+
+
+ ); +} diff --git a/src/views/vendors/add/index.js b/src/views/vendors/add/index.js index 8e9ec549..bc7fa0d6 100644 --- a/src/views/vendors/add/index.js +++ b/src/views/vendors/add/index.js @@ -33,7 +33,6 @@ const AddProject = () => { wallet_address: '' }); const [selectedGender, setSelectedGender] = useState(''); - const [selectedGroup, setSelectedGroup] = useState(''); const handleInputChange = e => { setFormData({ ...formData, [e.target.name]: e.target.value }); @@ -42,13 +41,9 @@ const AddProject = () => { const handleGenderChange = e => { setSelectedGender(e.target.value); }; - const handleGroupChange = e => { - setSelectedGroup(e.target.value); - }; const handleFormSubmit = e => { console.log(selectedGender); - console.log(selectedGroup); return; }; @@ -134,7 +129,7 @@ const AddProject = () => {
- +
@@ -142,49 +137,25 @@ const AddProject = () => { - +
- +
- - - - - - - - - - - - - + +
+
- +
- +
- - - - - - - - - - - - - - {/* {loading ? ( diff --git a/src/views/vendors/detail/index.js b/src/views/vendors/detail/index.js index e69de29b..9c90b83c 100644 --- a/src/views/vendors/detail/index.js +++ b/src/views/vendors/detail/index.js @@ -0,0 +1,45 @@ +import React from 'react'; +import { Breadcrumb, BreadcrumbItem, Row, Col } from 'reactstrap'; +import TotalCard from '../../totalCard'; +import VendorDetail from './vendorDetail'; +import VendorInfo from './vendorInfo'; +import ProjectInvovled from '../../projectInvolved'; +import TransactionHistory from './transactionHistory'; + +const projects = [ + { id: '0', name: 'Sindhupalchowk relief' }, + { id: '1', name: 'Flood relief distribution' } +]; +const Index = () => { + return ( + <> +

Vendors

+ + + Vendors + + Detail + + + + + + + + + + + + + + + ); +}; + +export default Index; diff --git a/src/views/vendors/detail/transactionHistory.js b/src/views/vendors/detail/transactionHistory.js new file mode 100644 index 00000000..6c4912fa --- /dev/null +++ b/src/views/vendors/detail/transactionHistory.js @@ -0,0 +1,59 @@ +import React from 'react'; +import { Card, CardBody, CardTitle, CustomInput, Table, Row, Col } from 'reactstrap'; + +const TransactionHistory = () => { + return ( +
+ {/*
*/} + +
+ + + Transaction History + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FromToDateValueTypeTX
xxxxxxxxxxxx123xxxxxxxxxxxx45623-05-2020200,000Receivedxxxxxxxxxxx45
+
+
+ {/*
*/} + +
+
+ ); +}; + +export default TransactionHistory; diff --git a/src/views/vendors/detail/vendorDetail.js b/src/views/vendors/detail/vendorDetail.js new file mode 100644 index 00000000..519e53b5 --- /dev/null +++ b/src/views/vendors/detail/vendorDetail.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { Card, CardTitle, Col, Row } from 'reactstrap'; +import '../../project.css'; +import displayPic from '../../../assets/images/users/user_avatar.svg'; + +export default function VendorDetail(props) { + const { name } = props; + return ( +
+ +
+ + Vendor Detail + + + + +
+ user +
+

{name || ''}

+
Name
+
+
+ + + + +
+
+
+
+ ); +} diff --git a/src/views/vendors/detail/vendorInfo.js b/src/views/vendors/detail/vendorInfo.js new file mode 100644 index 00000000..2656d4b1 --- /dev/null +++ b/src/views/vendors/detail/vendorInfo.js @@ -0,0 +1,79 @@ +import React from 'react'; +import { Card, CardTitle, Col, Row } from 'reactstrap'; +import '../../project.css'; +import image from '../../../assets/images/ID.jpg'; +import sign from '../../../assets/images/sign.png'; +import MOU from '../../../assets/images/MOU.png'; + +export default function VendorInfo() { + return ( +
+ +
+
+ + More Information + +
+ +
+
+ + +
+

Susma cold store

+
Shop name
+
+ +
+

susma@gmail.com

+
Email
+
+
+

Bachelors in computer science

+
Education
+
+
+

NIC Bank LTD

+
Bank name
+
+
+

120975345678

+
Bank account number
+
+ + +
+

Female

+
Gender
+
+ +
+

9867544232

+
Phone number
+
+
+

Sindhupalchowk , Kavre

+
Address
+
+
+

1043325

+
PAN number
+
+
+

Kathmandu

+
Bank branch
+
+ + + certificate + signature + +
+
+
+
+ ); +} diff --git a/yarn.lock b/yarn.lock index 35c13c44..77230cd0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2931,6 +2931,14 @@ adjust-sourcemap-loader@2.0.0: object-path "0.11.4" regex-parser "2.2.10" +adler-32@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/adler-32/-/adler-32-1.2.0.tgz#6a3e6bf0a63900ba15652808cb15c6813d1a5f25" + integrity sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU= + dependencies: + exit-on-epipe "~1.0.1" + printj "~1.1.0" + aes-js@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" @@ -4006,6 +4014,15 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +cfb@^1.1.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/cfb/-/cfb-1.2.0.tgz#6a4d0872b525ed60349e1ef51fb4b0bf73eca9a8" + integrity sha512-sXMvHsKCICVR3Naq+J556K+ExBo9n50iKl6LGarlnvuA2035uMlGA/qVrc0wQtow5P1vJEw9UyrKLCbtIKz+TQ== + dependencies: + adler-32 "~1.2.0" + crc-32 "~1.2.0" + printj "~1.1.2" + chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -4222,6 +4239,14 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +codepage@~1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/codepage/-/codepage-1.14.0.tgz#8cbe25481323559d7d307571b0fff91e7a1d2f99" + integrity sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k= + dependencies: + commander "~2.14.1" + exit-on-epipe "~1.0.1" + collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -4292,6 +4317,16 @@ commander@^4.1.1: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== +commander@~2.14.1: + version "2.14.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" + integrity sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw== + +commander@~2.17.1: + version "2.17.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" + integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== + common-tags@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" @@ -4479,6 +4514,14 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" +crc-32@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208" + integrity sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA== + dependencies: + exit-on-epipe "~1.0.1" + printj "~1.1.0" + create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" @@ -5855,6 +5898,11 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +exit-on-epipe@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692" + integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw== + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -6039,6 +6087,11 @@ feather-icons-react@^0.4.1: resolved "https://registry.yarnpkg.com/feather-icons-react/-/feather-icons-react-0.4.1.tgz#d2333ec7e87c8a54c6879181f7df09d598a38302" integrity sha512-rrnLdXpko9NKBdu2dAL3cdn4XXg6VualIqGH7WaDTZnE/Hz1bKWktQPmSsgDSO83Pj4H3uaPzY73zgecqOjDYQ== +fflate@^0.3.8: + version "0.3.11" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.3.11.tgz#2c440d7180fdeb819e64898d8858af327b042a5d" + integrity sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A== + figgy-pudding@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" @@ -6066,6 +6119,11 @@ file-loader@4.3.0: loader-utils "^1.2.3" schema-utils "^2.5.0" +file-saver@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" + integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== + file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" @@ -6255,6 +6313,11 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= +frac@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/frac/-/frac-1.1.2.tgz#3d74f7f6478c88a1b5020306d747dc6313c74d0b" + integrity sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA== + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -10368,6 +10431,11 @@ pretty-format@^24.9.0: ansi-styles "^3.2.0" react-is "^16.8.4" +printj@~1.1.0, printj@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" + integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -11937,6 +12005,13 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +ssf@~0.11.2: + version "0.11.2" + resolved "https://registry.yarnpkg.com/ssf/-/ssf-0.11.2.tgz#0b99698b237548d088fc43cdf2b70c1a7512c06c" + integrity sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g== + dependencies: + frac "~1.1.2" + sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -13098,11 +13173,21 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" +wmf@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wmf/-/wmf-1.0.2.tgz#7d19d621071a08c2bdc6b7e688a9c435298cc2da" + integrity sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw== + word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== +word@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/word/-/word-0.3.0.tgz#8542157e4f8e849f4a363a288992d47612db9961" + integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA== + workbox-background-sync@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-4.3.1.tgz#26821b9bf16e9e37fd1d640289edddc08afd1950" @@ -13339,6 +13424,22 @@ xhr@^2.0.4: parse-headers "^2.0.0" xtend "^4.0.0" +xlsx@^0.17.0: + version "0.17.0" + resolved "https://registry.yarnpkg.com/xlsx/-/xlsx-0.17.0.tgz#028176a0140967dcee1817d221678461e47481c8" + integrity sha512-bZ36FSACiAyjoldey1+7it50PMlDp1pcAJrZKcVZHzKd8BC/z6TQ/QAN8onuqcepifqSznR6uKnjPhaGt6ig9A== + dependencies: + adler-32 "~1.2.0" + cfb "^1.1.4" + codepage "~1.14.0" + commander "~2.17.1" + crc-32 "~1.2.0" + exit-on-epipe "~1.0.1" + fflate "^0.3.8" + ssf "~0.11.2" + wmf "~1.0.1" + word "~0.3.0" + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" From 7222647c61f3a1e16ec7d3213d3a71e5c9c106cd Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Mon, 16 Aug 2021 12:22:16 +0545 Subject: [PATCH 24/59] add beneficiary and view details --- src/constants/index.js | 38 +++ src/contexts/BeneficiaryContext.js | 266 ++++++++-------- src/modules/beneficary/add/add.js | 195 +++++++++--- src/modules/beneficary/detail/_detail.js | 64 ++++ .../beneficary/detail/beneficiaryInfo.js | 80 +++++ src/modules/beneficary/detail/index.js | 16 +- src/modules/beneficary/edit/edit.js | 290 ++++++++++++++++++ src/modules/beneficary/edit/index.js | 14 + src/modules/ui_components/projects/index.js | 39 +++ src/routes/Router.js | 15 +- src/utils/index.js | 4 + 11 files changed, 834 insertions(+), 187 deletions(-) create mode 100644 src/modules/beneficary/detail/_detail.js create mode 100644 src/modules/beneficary/detail/beneficiaryInfo.js create mode 100644 src/modules/beneficary/edit/edit.js create mode 100644 src/modules/beneficary/edit/index.js create mode 100644 src/modules/ui_components/projects/index.js create mode 100644 src/utils/index.js diff --git a/src/constants/index.js b/src/constants/index.js index e419ad2f..63b01c2f 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -26,5 +26,43 @@ module.exports = { PROJECT_STATUS: { ACTIVE: 'active', SUSPENDED: 'suspended' + }, + GROUPS: { + DIFFERENTLY_ABLED: { + label: 'Differently Abled', + value: 'Differently_Abled' + }, + MATERNITY: { + label: 'Maternity', + value: 'Maternity' + }, + SENIOR_CITIZENS: { + label: 'Senior Citizens', + value: 'Senior_Citizens' + }, + COVID_VICTIM: { + label: 'Covid Victim', + value: 'Covid_Victim' + }, + NATURAL_CLIMATE_VICTIM: { + label: 'Natural Calamities Victim', + value: 'Natural_Calamities_Victim' + }, + UNDER_PRIVILAGED: { + label: 'Under Privileged', + value: 'Under_Privileged' + }, + SEVERE_HEATH_ISSUES: { + label: 'Severe Health Issues', + value: 'Severe_Health_Issues' + }, + SINGLE_WOMAN: { + label: 'Single Women', + value: 'Single_Women' + }, + ORPHAN: { + label: 'Orphan', + value: 'Orphan' + } } }; diff --git a/src/contexts/BeneficiaryContext.js b/src/contexts/BeneficiaryContext.js index f7b402c8..92f6e77b 100644 --- a/src/contexts/BeneficiaryContext.js +++ b/src/contexts/BeneficiaryContext.js @@ -1,139 +1,141 @@ -import React, { createContext, useReducer ,useContext} from "react"; -import beneficiaryReduce from "../reducers/beneficiaryReducer"; -import * as Service from "../services/beneficiary"; -import * as AidService from "../services/aid"; -import ACTION from "../actions/beneficiary"; -import {AppContext} from './AppSettingsContext'; +import React, { createContext, useReducer, useContext, useCallback } from 'react'; +import beneficiaryReduce from '../reducers/beneficiaryReducer'; +import * as Service from '../services/beneficiary'; +import * as AidService from '../services/aid'; +import ACTION from '../actions/beneficiary'; +import { AppContext } from './AppSettingsContext'; +import { APP_CONSTANTS } from '../constants'; const initialState = { - list: [], - pagination: { limit: 10, start: 0, total: 0, currentPage: 1, totalPages: 0 }, - aid: {}, - projectList: [], - beneficiary: {}, - tokenBalance: 0, + list: [], + pagination: { limit: 10, start: 0, total: 0, currentPage: 1, totalPages: 0 }, + aid: {}, + projectList: [], + beneficiary: {}, + tokenBalance: 0 }; export const BeneficiaryContext = createContext(initialState); export const BeneficiaryContextProvider = ({ children }) => { - const [state, dispatch] = useReducer(beneficiaryReduce, initialState); - const {wallet,appSettings,changeIsverified} = useContext(AppContext); - - async function getAvailableBalance(proejctId, rahatAdminContractAddr) { - const { rahat:rahatContractAddr } = appSettings.agency.contracts; - return AidService.loadAidBalance(proejctId, rahatContractAddr); - } - - const issueTokens = async (payload) => { - const { rahat:rahatContractAddr } = appSettings.agency.contracts; - await AidService.issueBeneficiaryToken(wallet,payload, rahatContractAddr); - changeIsverified(false); - let balance = await Service.getBeneficiaryBalance( - payload.phone, - rahatContractAddr - ); - dispatch({ type: ACTION.SET_TOKEN_BALANCE, data: balance }); - return payload; - }; - - async function getBeneficiaryBalance(phone, contract_address) { - const balance = await Service.getBeneficiaryBalance( - phone, - contract_address - ); - return balance; - } - - async function listAid() { - const d = await AidService.listAid({ start: 0, limit: 50 }); - dispatch({ type: ACTION.LIST_AID, data: { projectList: d.data } }); - return d; - } - - function setAid(aid) { - dispatch({ type: ACTION.SET_AID, data: aid }); - } - - function clear() { - dispatch({ - type: ACTION.LIST, - data: { - limit: 10, - start: 0, - total: 0, - data: [], - page: 0, - name: "", - phone: "", - }, - }); - } - - function setBeneficiary(b) { - dispatch({ type: ACTION.SET_BENEFICIARY, data: b }); - } - - const addBeneficiary = async (event) => { - event.preventDefault(); - const formData = new FormData(event.target); - - let payload = { - name: formData.get("name"), - phone: formData.get("phone"), - govt_id: formData.get("govt_id"), - email: formData.get("email"), - address: formData.get("address"), - wallet_address: formData.get("wallet_address"), - project_id: formData.get("aid"), - }; - let d = await Service.addBeneficiary(payload); - return d; - }; - - async function listBeneficiary(params) { - let res = await Service.listBeneficiary(params); - if (res) { - dispatch({ - type: ACTION.LIST, - res, - }); - return res; - } - } - - const importBeneficiary = async () => { - let beneficiaries = await Service.importBeneficiary({}); - for (let b of beneficiaries) await Service.addBeneficiary(b); - }; - - async function getBeneficiaryDetails(id) { - let data = await Service.get(id); - setBeneficiary(data); - return data; - } - return ( - - {children} - - ); + const [state, dispatch] = useReducer(beneficiaryReduce, initialState); + const { wallet, appSettings, changeIsverified } = useContext(AppContext); + + async function getAvailableBalance(proejctId, rahatAdminContractAddr) { + const { rahat: rahatContractAddr } = appSettings.agency.contracts; + return AidService.loadAidBalance(proejctId, rahatContractAddr); + } + + const issueTokens = async payload => { + const { rahat: rahatContractAddr } = appSettings.agency.contracts; + await AidService.issueBeneficiaryToken(wallet, payload, rahatContractAddr); + changeIsverified(false); + let balance = await Service.getBeneficiaryBalance(payload.phone, rahatContractAddr); + dispatch({ type: ACTION.SET_TOKEN_BALANCE, data: balance }); + return payload; + }; + + async function getBeneficiaryBalance(phone, contract_address) { + const balance = await Service.getBeneficiaryBalance(phone, contract_address); + return balance; + } + + const listAid = useCallback(() => { + return AidService.listAid({ limit: APP_CONSTANTS.FETCH_LIMIT }); + }, []); + + // async function listAid() { + // const d = await AidService.listAid({ start: 0, limit: 50 }); + // dispatch({ type: ACTION.LIST_AID, data: { projectList: d.data } }); + // return d; + // } + + function setAid(aid) { + dispatch({ type: ACTION.SET_AID, data: aid }); + } + + function clear() { + dispatch({ + type: ACTION.LIST, + data: { + limit: 10, + start: 0, + total: 0, + data: [], + page: 0, + name: '', + phone: '' + } + }); + } + + function setBeneficiary(b) { + dispatch({ type: ACTION.SET_BENEFICIARY, data: b }); + } + + const addBeneficiary = payload => { + return Service.addBeneficiary(payload); + }; + + // const addBeneficiary = async event => { + // event.preventDefault(); + // const formData = new FormData(event.target); + + // let payload = { + // name: formData.get('name'), + // phone: formData.get('phone'), + // govt_id: formData.get('govt_id'), + // email: formData.get('email'), + // address: formData.get('address'), + // wallet_address: formData.get('wallet_address'), + // project_id: formData.get('aid') + // }; + // let d = await Service.addBeneficiary(payload); + // return d; + // }; + + async function listBeneficiary(params) { + let res = await Service.listBeneficiary(params); + if (res) { + dispatch({ + type: ACTION.LIST, + res + }); + return res; + } + } + + const importBeneficiary = async () => { + let beneficiaries = await Service.importBeneficiary({}); + for (let b of beneficiaries) await Service.addBeneficiary(b); + }; + + const getBeneficiaryDetails = useCallback(id => { + return Service.get(id); + }, []); + + return ( + + {children} + + ); }; diff --git a/src/modules/beneficary/add/add.js b/src/modules/beneficary/add/add.js index 23d963a5..2342c87a 100644 --- a/src/modules/beneficary/add/add.js +++ b/src/modules/beneficary/add/add.js @@ -1,34 +1,91 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback, useContext } from 'react'; import { Card, CardBody, Row, Col, Form, FormGroup, Label, Input, Button } from 'reactstrap'; +import { useToasts } from 'react-toast-notifications'; + import { History } from '../../../utils/History'; import BreadCrumb from '../../ui_components/breadcrumb'; +import { GROUPS, TOAST } from '../../../constants'; +import { BeneficiaryContext } from '../../../contexts/BeneficiaryContext'; +import SelectWrapper from '../../global/SelectWrapper'; const Add = () => { + const { addToast } = useToasts(); + const { listAid, addBeneficiary } = useContext(BeneficiaryContext); + const [formData, setFormData] = useState({ - project: '', name: '', + phone: '', email: '', - age: '', address: '', + address_temporary: '', + govt_id: '' + }); + + const [extras, setExtras] = useState({ + age: '', education: '', profession: '', - governmentID: '', family_members: '', adult: '', - child: '', - - wallet_address: '' + child: '' }); - const handleInputChange = () => {}; + const [projectList, setProjectList] = useState([]); + + const [selectedGender, setSelectedGender] = useState(''); + const [selectedGroup, setSelectedGroup] = useState(''); + const [selectedProject, setSelectedProject] = useState(''); + + const handleInputChange = e => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleExtraInfoChange = e => { + setExtras({ ...extras, [e.target.name]: e.target.value }); + }; + + const handleFormSubmit = e => { + e.preventDefault(); + if (!selectedProject) return addToast('Please select project', TOAST.ERROR); + + if (selectedGroup) extras.group = selectedGroup; + const payload = { ...formData, extras: { ...extras } }; + payload.project_id = selectedProject; + if (selectedGender) payload.gender = selectedGender; + addBeneficiary(payload) + .then(() => { + addToast('Beneficiary added successfully', TOAST.SUCCESS); + History.push('/beneficiaries'); + }) + .catch(err => { + addToast(err.message, TOAST.ERROR); + }); + }; - const handleFormSubmit = () => {}; + const handleGenderChange = e => setSelectedGender(e.target.value); - const handleGenderChange = () => {}; + const handleGroupChange = e => setSelectedGroup(e.target.value); - const handleGroupChange = () => {}; + const handleProjectChange = d => setSelectedProject(d.value); - const handleCancelClick = () => {}; + const handleCancelClick = () => History.push('/beneficiaries'); + + const loadProjects = useCallback(async () => { + const projects = await listAid(); + if (projects && projects.data.length) { + const select_options = projects.data.map(p => { + return { + label: p.name, + value: p._id + }; + }); + setProjectList(select_options); + } + }, [listAid]); + + useEffect(() => { + loadProjects(); + }, [loadProjects]); return (
@@ -42,22 +99,42 @@ const Add = () => {
- + + + + + + + + + + + + + + + + - - - + + + @@ -65,33 +142,55 @@ const Add = () => {
- +
- - - - - + + + + + + + + + + + + + + + +
- +
@@ -99,9 +198,17 @@ const Add = () => { - +
- +
@@ -109,15 +216,17 @@ const Add = () => { - - - - - - - - - + + + + + + + + + @@ -126,19 +235,25 @@ const Add = () => {
- +
- + - + diff --git a/src/modules/beneficary/detail/_detail.js b/src/modules/beneficary/detail/_detail.js new file mode 100644 index 00000000..a92f6873 --- /dev/null +++ b/src/modules/beneficary/detail/_detail.js @@ -0,0 +1,64 @@ +import React, { useContext, useCallback, useEffect, useState } from 'react'; +import { Row, Col } from 'reactstrap'; + +import Balance from '../../ui_components/balance'; +import DetailsCard from '../../global/DetailsCard'; +import BeneficiaryInfo from './beneficiaryInfo'; +import ProjectInvovled from '../../ui_components/projects'; +import BreadCrumb from '../../ui_components/breadcrumb'; + +import { BeneficiaryContext } from '../../../contexts/BeneficiaryContext'; + +const BenefDetails = ({ params }) => { + const { id } = params; + const { getBeneficiaryDetails } = useContext(BeneficiaryContext); + + const [basicInfo, setBasicInfo] = useState({}); + const [extras, setExtras] = useState({}); + const [projectList, setProjectList] = useState([]); + + const fetchBeneficiaryDetails = useCallback(async () => { + const details = await getBeneficiaryDetails(id); + if (details && details.extras) setExtras(details.extras); + setBasicInfo(details); + const projects = details.projects.map(d => { + return { id: d._id, name: d.name }; + }); + setProjectList(projects); + }, [getBeneficiaryDetails, id]); + + useEffect(() => { + console.log('EFFECT!'); + fetchBeneficiaryDetails(); + }, [fetchBeneficiaryDetails]); + + console.log('BASIC==>', basicInfo); + + return ( + <> +

Beneficiary

+ + + + + + + + + + + {basicInfo && } + + + + ); +}; + +export default BenefDetails; diff --git a/src/modules/beneficary/detail/beneficiaryInfo.js b/src/modules/beneficary/detail/beneficiaryInfo.js new file mode 100644 index 00000000..5462d3cf --- /dev/null +++ b/src/modules/beneficary/detail/beneficiaryInfo.js @@ -0,0 +1,80 @@ +import React from 'react'; +import { Card, CardTitle, Col, Row } from 'reactstrap'; + +import '../../../assets/css/project.css'; +import image from '../../../assets/images/ID.jpg'; +import { formatWord } from '../../../utils'; +import { History } from '../../../utils/History'; + +export default function BeneficiaryInfo({ basicInfo, extras }) { + const handleEditClick = () => History.push(`/edit-beneficiary/${basicInfo._id}`); + + return ( +
+ +
+
+ + More Information + +
+ +
+
+ + +
+

{basicInfo.address || '-'}

+
Address
+
+
+

{basicInfo.govt_id || '-'}

+
Government ID number
+
+
+

{extras && extras.group ? formatWord(extras.group) : '-'}

+
Group
+
+
+

{extras && extras.adult ? extras.adult : '-'}

+
Number of family member (Adult)
+
+ + +
+

{extras && extras.education ? extras.education : '-'}

+
Education
+
+
+

{basicInfo.gender || '-'}

+
Gender
+
+
+

{extras && extras.age ? extras.age : '-'}

+
Age
+
+
+

{extras && extras.child ? extras.child : '-'}

+
Number of family member(Child)
+
+ + +
+

{extras && extras.profession ? extras.profession : '-'}

+
Profession
+
+ certificate + +
+
+
+
+ ); +} diff --git a/src/modules/beneficary/detail/index.js b/src/modules/beneficary/detail/index.js index 3566dedc..dac20fed 100644 --- a/src/modules/beneficary/detail/index.js +++ b/src/modules/beneficary/detail/index.js @@ -1,13 +1,13 @@ -import React from "react"; -import { BeneficiaryContextProvider } from "../../../contexts/BeneficiaryContext"; -import Detail from "./detail"; +import React from 'react'; +import { BeneficiaryContextProvider } from '../../../contexts/BeneficiaryContext'; +import Detail from './_detail'; const index = props => { - return ( - - - - ); + return ( + + + + ); }; export default index; diff --git a/src/modules/beneficary/edit/edit.js b/src/modules/beneficary/edit/edit.js new file mode 100644 index 00000000..a51e7bbc --- /dev/null +++ b/src/modules/beneficary/edit/edit.js @@ -0,0 +1,290 @@ +import React, { useState, useEffect, useCallback, useContext } from 'react'; +import { Card, CardBody, Row, Col, Form, FormGroup, Label, Input, Button } from 'reactstrap'; +import { useToasts } from 'react-toast-notifications'; + +import { History } from '../../../utils/History'; +import BreadCrumb from '../../ui_components/breadcrumb'; +import { GROUPS, TOAST } from '../../../constants'; +import { BeneficiaryContext } from '../../../contexts/BeneficiaryContext'; +import SelectWrapper from '../../global/SelectWrapper'; + +const Edit = ({ beneficiaryId }) => { + console.log({ beneficiaryId }); + const { addToast } = useToasts(); + const { listAid, addBeneficiary } = useContext(BeneficiaryContext); + + const [formData, setFormData] = useState({ + name: '', + phone: '', + email: '', + address: '', + address_temporary: '', + govt_id: '' + }); + + const [extras, setExtras] = useState({ + age: '', + education: '', + profession: '', + family_members: '', + adult: '', + child: '' + }); + + const [projectList, setProjectList] = useState([]); + + const [selectedGender, setSelectedGender] = useState(''); + const [selectedGroup, setSelectedGroup] = useState(''); + const [selectedProject, setSelectedProject] = useState(''); + + const handleInputChange = e => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleExtraInfoChange = e => { + setExtras({ ...extras, [e.target.name]: e.target.value }); + }; + + const handleFormSubmit = e => { + e.preventDefault(); + if (!selectedProject) return addToast('Please select project', TOAST.ERROR); + + if (selectedGroup) extras.group = selectedGroup; + const payload = { ...formData, extras: { ...extras } }; + payload.project_id = selectedProject; + if (selectedGender) payload.gender = selectedGender; + addBeneficiary(payload) + .then(() => { + addToast('Beneficiary added successfully', TOAST.SUCCESS); + History.push('/beneficiaries'); + }) + .catch(err => { + addToast(err.message, TOAST.ERROR); + }); + }; + + const handleGenderChange = e => setSelectedGender(e.target.value); + + const handleGroupChange = e => setSelectedGroup(e.target.value); + + const handleProjectChange = d => setSelectedProject(d.value); + + const handleCancelClick = () => History.push('/beneficiaries'); + + const loadProjects = useCallback(async () => { + const projects = await listAid(); + if (projects && projects.data.length) { + const select_options = projects.data.map(p => { + return { + label: p.name, + value: p._id + }; + }); + setProjectList(select_options); + } + }, [listAid]); + + useEffect(() => { + loadProjects(); + }, [loadProjects]); + + return ( +
+

Beneficiary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + + + + + + + + + + + + + + + {/* {loading ? ( + + ) : ( */} +
+ + +
+ {/* )} */} +
+ +
+
+ +
+
+ ); +}; + +export default Edit; diff --git a/src/modules/beneficary/edit/index.js b/src/modules/beneficary/edit/index.js new file mode 100644 index 00000000..ce918911 --- /dev/null +++ b/src/modules/beneficary/edit/index.js @@ -0,0 +1,14 @@ +import React from 'react'; + +import { BeneficiaryContextProvider } from '../../../contexts/BeneficiaryContext'; +import EditBeneficiary from './edit'; + +export default function Index({ match }) { + return ( + <> + + + + + ); +} diff --git a/src/modules/ui_components/projects/index.js b/src/modules/ui_components/projects/index.js new file mode 100644 index 00000000..5020620b --- /dev/null +++ b/src/modules/ui_components/projects/index.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { Card, CardTitle } from 'reactstrap'; + +import '../../../assets/css/project.css'; + +export default function ProjectsInvolved(props) { + const { projects } = props; + return ( +
+ +
+ + Projects Involved + +
+
+
+ {projects ? ( + projects.map(project => ( + + )) + ) : ( +

No projects available...

+ )} +
+
+
+
+
+
+ ); +} diff --git a/src/routes/Router.js b/src/routes/Router.js index 8b518a74..d7d5031f 100644 --- a/src/routes/Router.js +++ b/src/routes/Router.js @@ -9,6 +9,7 @@ const AgencyProfile = lazy(() => import('../modules/agency/profile')); const Beneficiary = lazy(() => import('../modules/beneficary')); const BeneficiaryDetails = lazy(() => import('../modules/beneficary/detail/index')); const AddBeneficiary = lazy(() => import('../modules/beneficary/add')); +const EditBeneficiary = lazy(() => import('../modules/beneficary/edit')); // Institutions const InstitutionList = lazy(() => import('../modules/institution')); @@ -39,7 +40,6 @@ const Vendor = lazy(() => import('../modules/vendor')); const VendorDetails = lazy(() => import('../modules/vendor/detail/index')); // ------------------------------Beneficiary UI------------------------------------ -const BeneficiaryAdd = lazy(() => import('../views/beneficiaries/add')); const BeneficiaryDetail = lazy(() => import('../views/beneficiaries/detail')); // -------------------------------------------------------------------------------- @@ -84,6 +84,13 @@ let AppRoutes = [ component: BeneficiaryDetails }, + { + path: '/edit-beneficiary/:id', + name: 'Beneficiary', + icon: 'users', + component: EditBeneficiary + }, + { path: '/add-beneficiary', name: 'Beneficiary', @@ -134,12 +141,6 @@ let AppRoutes = [ }, //.............................Beneficiary ui...................... - - { - path: '/add_beneficiary', - name: 'BeneficiaryAdd', - component: BeneficiaryAdd - }, { path: '/detail_beneficiary', name: 'BeneficiaryDetail', diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 00000000..158e0b50 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,4 @@ +export const formatWord = word => { + if (!word) return '-'; + return word.replace(/_/g, ' '); +}; From fb93703f4e0dcc78aa752635070d3757831c6b8b Mon Sep 17 00:00:00 2001 From: rojanbade Date: Mon, 16 Aug 2021 13:44:35 +0545 Subject: [PATCH 25/59] merge conflict resolved --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 413833cb..2510668b 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "file-saver": "^2.0.5", "history": "4.7.2", "moment": "^2.26.0", - "node-sass": "^4.14.1", + "node-sass": "4.14.1", "prop-types": "^15.7.2", "qrcode.react": "^1.0.0", "query-string": "^6.13.1", From 0e14b6cb2cc1d13e76dc5cd123ec0c6b6e2af79b Mon Sep 17 00:00:00 2001 From: rojanbade Date: Mon, 16 Aug 2021 16:08:10 +0545 Subject: [PATCH 26/59] wallet login issue resolved --- src/modules/authentication/Wallet.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/modules/authentication/Wallet.js b/src/modules/authentication/Wallet.js index c9a1ad91..178c0ec6 100644 --- a/src/modules/authentication/Wallet.js +++ b/src/modules/authentication/Wallet.js @@ -6,12 +6,15 @@ import { AppContext } from '../../contexts/AppSettingsContext'; import Logo from '../../assets/images/rahat-logo-blue.png'; import DataService from '../../services/db'; import './wallet.css'; +import { useToasts } from 'react-toast-notifications'; +import { TOAST } from '../../constants'; const API_SERVER = process.env.REACT_APP_API_SERVER; const WSS_SERVER = API_SERVER.replace('http', 'ws'); const QR_REFRESH_TIME = 30000; // 30 const Wallet = () => { + const { addToast } = useToasts(); const ws = useRef(null); const [qroptions, setQrOptions] = useState({}); const [clientId, setclientId] = useState(''); @@ -72,6 +75,9 @@ const Wallet = () => { ws.current.onmessage = async e => { const data = JSON.parse(e.data); + if (data.action === 'unauthorized') { + return addToast('User not authorized!', TOAST.ERROR); + } if (data.data && data.data.token) { const { id, token } = data.data; setclientId(id.toString()); From bde3fea7803365953cd8968b691f19cbf6c5e06f Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Tue, 17 Aug 2021 08:55:49 +0545 Subject: [PATCH 27/59] update beneficiary details --- src/contexts/BeneficiaryContext.js | 5 + src/modules/beneficary/add/add.js | 14 ++- src/modules/beneficary/detail/_detail.js | 3 - src/modules/beneficary/edit/edit.js | 86 +++++++++++---- src/services/beneficiary.js | 127 +++++++++++++---------- 5 files changed, 147 insertions(+), 88 deletions(-) diff --git a/src/contexts/BeneficiaryContext.js b/src/contexts/BeneficiaryContext.js index 92f6e77b..49c5aecc 100644 --- a/src/contexts/BeneficiaryContext.js +++ b/src/contexts/BeneficiaryContext.js @@ -76,6 +76,10 @@ export const BeneficiaryContextProvider = ({ children }) => { return Service.addBeneficiary(payload); }; + const updateBeneficiary = (id, payload) => { + return Service.updateBeneficiary(id, payload); + }; + // const addBeneficiary = async event => { // event.preventDefault(); // const formData = new FormData(event.target); @@ -127,6 +131,7 @@ export const BeneficiaryContextProvider = ({ children }) => { listAid, issueTokens, addBeneficiary, + updateBeneficiary, setBeneficiary, listBeneficiary, importBeneficiary, diff --git a/src/modules/beneficary/add/add.js b/src/modules/beneficary/add/add.js index 2342c87a..7db06d6b 100644 --- a/src/modules/beneficary/add/add.js +++ b/src/modules/beneficary/add/add.js @@ -34,7 +34,7 @@ const Add = () => { const [selectedGender, setSelectedGender] = useState(''); const [selectedGroup, setSelectedGroup] = useState(''); - const [selectedProject, setSelectedProject] = useState(''); + const [selectedProjects, setSelectedProjects] = useState(''); const handleInputChange = e => { setFormData({ ...formData, [e.target.name]: e.target.value }); @@ -46,11 +46,11 @@ const Add = () => { const handleFormSubmit = e => { e.preventDefault(); - if (!selectedProject) return addToast('Please select project', TOAST.ERROR); + if (!selectedProjects.length) return addToast('Please select project', TOAST.ERROR); if (selectedGroup) extras.group = selectedGroup; const payload = { ...formData, extras: { ...extras } }; - payload.project_id = selectedProject; + payload.projects = selectedProjects; if (selectedGender) payload.gender = selectedGender; addBeneficiary(payload) .then(() => { @@ -66,7 +66,10 @@ const Add = () => { const handleGroupChange = e => setSelectedGroup(e.target.value); - const handleProjectChange = d => setSelectedProject(d.value); + const handleProjectChange = data => { + const values = data.map(d => d.value); + setSelectedProjects(values.toString()); + }; const handleCancelClick = () => History.push('/beneficiaries'); @@ -100,8 +103,9 @@ const Add = () => { diff --git a/src/modules/beneficary/detail/_detail.js b/src/modules/beneficary/detail/_detail.js index a92f6873..2ef63c38 100644 --- a/src/modules/beneficary/detail/_detail.js +++ b/src/modules/beneficary/detail/_detail.js @@ -28,12 +28,9 @@ const BenefDetails = ({ params }) => { }, [getBeneficiaryDetails, id]); useEffect(() => { - console.log('EFFECT!'); fetchBeneficiaryDetails(); }, [fetchBeneficiaryDetails]); - console.log('BASIC==>', basicInfo); - return ( <>

Beneficiary

diff --git a/src/modules/beneficary/edit/edit.js b/src/modules/beneficary/edit/edit.js index a51e7bbc..c4442ece 100644 --- a/src/modules/beneficary/edit/edit.js +++ b/src/modules/beneficary/edit/edit.js @@ -9,9 +9,8 @@ import { BeneficiaryContext } from '../../../contexts/BeneficiaryContext'; import SelectWrapper from '../../global/SelectWrapper'; const Edit = ({ beneficiaryId }) => { - console.log({ beneficiaryId }); const { addToast } = useToasts(); - const { listAid, addBeneficiary } = useContext(BeneficiaryContext); + const { listAid, updateBeneficiary, getBeneficiaryDetails } = useContext(BeneficiaryContext); const [formData, setFormData] = useState({ name: '', @@ -32,10 +31,11 @@ const Edit = ({ beneficiaryId }) => { }); const [projectList, setProjectList] = useState([]); + const [existingProjects, setExistingProjects] = useState([]); const [selectedGender, setSelectedGender] = useState(''); const [selectedGroup, setSelectedGroup] = useState(''); - const [selectedProject, setSelectedProject] = useState(''); + const [selectedProjects, setSelectedProjects] = useState(''); const handleInputChange = e => { setFormData({ ...formData, [e.target.name]: e.target.value }); @@ -47,15 +47,15 @@ const Edit = ({ beneficiaryId }) => { const handleFormSubmit = e => { e.preventDefault(); - if (!selectedProject) return addToast('Please select project', TOAST.ERROR); + if (!selectedProjects.length) return addToast('Please select project', TOAST.ERROR); if (selectedGroup) extras.group = selectedGroup; const payload = { ...formData, extras: { ...extras } }; - payload.project_id = selectedProject; + payload.projects = selectedProjects; if (selectedGender) payload.gender = selectedGender; - addBeneficiary(payload) + updateBeneficiary(beneficiaryId, payload) .then(() => { - addToast('Beneficiary added successfully', TOAST.SUCCESS); + addToast('Beneficiary updated successfully', TOAST.SUCCESS); History.push('/beneficiaries'); }) .catch(err => { @@ -67,26 +67,52 @@ const Edit = ({ beneficiaryId }) => { const handleGroupChange = e => setSelectedGroup(e.target.value); - const handleProjectChange = d => setSelectedProject(d.value); + const handleProjectChange = data => { + const values = data.map(d => d.value); + setSelectedProjects(values.toString()); + }; const handleCancelClick = () => History.push('/beneficiaries'); + const createProjectSelectOptions = projects => { + const select_options = projects.map(p => { + return { + label: p.name, + value: p._id + }; + }); + return select_options; + }; + + const loadBeneficiaryDetails = useCallback(async () => { + const d = await getBeneficiaryDetails(beneficiaryId); + const { extras, projects, name, phone, email, address, address_temporary, govt_id } = d; + if (projects && projects.length) { + const project_ids = projects.map(p => p._id); + setSelectedProjects(project_ids.toString()); + const select_options = createProjectSelectOptions(projects); + setExistingProjects(select_options); + } + setExtras(extras); + if (extras.group) setSelectedGroup(extras.group); + delete d.extras; + setFormData({ name, phone, email, address, address_temporary, govt_id }); + const { gender } = d; + if (gender !== 'U') setSelectedGender(gender); + }, [beneficiaryId, getBeneficiaryDetails]); + const loadProjects = useCallback(async () => { const projects = await listAid(); if (projects && projects.data.length) { - const select_options = projects.data.map(p => { - return { - label: p.name, - value: p._id - }; - }); + const select_options = createProjectSelectOptions(projects.data); setProjectList(select_options); } }, [listAid]); useEffect(() => { loadProjects(); - }, [loadProjects]); + loadBeneficiaryDetails(); + }, [loadBeneficiaryDetails, loadProjects]); return (
@@ -100,12 +126,26 @@ const Edit = ({ beneficiaryId }) => {
- + {existingProjects.length > 0 && ( + + )} + + {existingProjects.length < 1 && ( + + )} @@ -131,7 +171,7 @@ const Edit = ({ beneficiaryId }) => { - + @@ -215,7 +255,7 @@ const Edit = ({ beneficiaryId }) => { - + diff --git a/src/services/beneficiary.js b/src/services/beneficiary.js index beddbf42..0939344f 100644 --- a/src/services/beneficiary.js +++ b/src/services/beneficiary.js @@ -1,79 +1,92 @@ -import axios from "axios"; +import axios from 'axios'; -import { getUserToken } from "../utils/sessionManager"; -import API from "../constants/api"; -import CONTRACT from "../constants/contracts"; -import { getContractByProvider } from "../blockchain/abi"; +import { getUserToken } from '../utils/sessionManager'; +import API from '../constants/api'; +import CONTRACT from '../constants/contracts'; +import { getContractByProvider } from '../blockchain/abi'; const access_token = getUserToken(); -const mapTestContract = (contract) => ({ - tokenBalance: contract.tokenBalance, +const mapTestContract = contract => ({ + tokenBalance: contract.tokenBalance }); export async function getBeneficiaryBalance(phone, contract_address) { - const contract = await getContractByProvider(contract_address, CONTRACT.RAHAT); - const myContract = mapTestContract(contract); - const data = await myContract.tokenBalance(phone); - if (!data) return "No balance!"; - return data.toNumber(); - } + const contract = await getContractByProvider(contract_address, CONTRACT.RAHAT); + const myContract = mapTestContract(contract); + const data = await myContract.tokenBalance(phone); + if (!data) return 'No balance!'; + return data.toNumber(); +} export async function listBeneficiary(params) { - const res = await axios({ - url: API.BENEFICARIES, - method: "get", - headers: { - access_token, - }, - params, - }); - return res.data; + const res = await axios({ + url: API.BENEFICARIES, + method: 'get', + headers: { + access_token + }, + params + }); + return res.data; } export async function get(id) { - const res = await axios({ - url: API.BENEFICARIES + "/" + id, - method: "get", - headers: { - access_token, - }, - }); - return res.data; + const res = await axios({ + url: API.BENEFICARIES + '/' + id, + method: 'get', + headers: { + access_token + } + }); + return res.data; } export async function listByAid(aid, params) { - const res = await axios({ - url: API.PROJECTS + `/${aid}/beneficiaries`, - method: "get", - headers: { - access_token, - }, - params, - }); - return res.data; + const res = await axios({ + url: API.PROJECTS + `/${aid}/beneficiaries`, + method: 'get', + headers: { + access_token + }, + params + }); + return res.data; } export async function addBeneficiary(body) { - const res = await axios({ - url: API.BENEFICARIES, - method: "post", - headers: { - access_token, - }, - data: body, - }); + const res = await axios({ + url: API.BENEFICARIES, + method: 'post', + headers: { + access_token + }, + data: body + }); + + return res.data; +} + +export async function updateBeneficiary(id, body) { + const res = await axios({ + url: `${API.BENEFICARIES}/${id}`, + method: 'put', + headers: { + access_token + }, + data: body + }); - return res.data; + return res.data; } export async function importBeneficiary(body) { - const { data } = await axios({ - url: API.BENEFICARIES + `/import`, - method: "post", - headers: { - access_token, - }, - data: body, - }); - return data; + const { data } = await axios({ + url: API.BENEFICARIES + `/import`, + method: 'post', + headers: { + access_token + }, + data: body + }); + return data; } From 5053033a209162de66bc3eb3a3dd7c2eb9771350 Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Tue, 17 Aug 2021 09:36:37 +0545 Subject: [PATCH 28/59] fetch current balance --- src/contexts/BeneficiaryContext.js | 7 +++-- src/modules/beneficary/detail/_detail.js | 30 +++++++++++++++++++--- src/modules/global/Loading.js | 20 ++++++--------- src/modules/ui_components/balance/index.js | 7 +++-- 4 files changed, 43 insertions(+), 21 deletions(-) diff --git a/src/contexts/BeneficiaryContext.js b/src/contexts/BeneficiaryContext.js index 49c5aecc..6abbab85 100644 --- a/src/contexts/BeneficiaryContext.js +++ b/src/contexts/BeneficiaryContext.js @@ -34,10 +34,9 @@ export const BeneficiaryContextProvider = ({ children }) => { return payload; }; - async function getBeneficiaryBalance(phone, contract_address) { - const balance = await Service.getBeneficiaryBalance(phone, contract_address); - return balance; - } + const getBeneficiaryBalance = useCallback(async (phone, contract_address) => { + return Service.getBeneficiaryBalance(phone, contract_address); + }, []); const listAid = useCallback(() => { return AidService.listAid({ limit: APP_CONSTANTS.FETCH_LIMIT }); diff --git a/src/modules/beneficary/detail/_detail.js b/src/modules/beneficary/detail/_detail.js index 2ef63c38..9098bbac 100644 --- a/src/modules/beneficary/detail/_detail.js +++ b/src/modules/beneficary/detail/_detail.js @@ -7,25 +7,43 @@ import BeneficiaryInfo from './beneficiaryInfo'; import ProjectInvovled from '../../ui_components/projects'; import BreadCrumb from '../../ui_components/breadcrumb'; +import { AppContext } from '../../../contexts/AppSettingsContext'; import { BeneficiaryContext } from '../../../contexts/BeneficiaryContext'; const BenefDetails = ({ params }) => { const { id } = params; - const { getBeneficiaryDetails } = useContext(BeneficiaryContext); + const { getBeneficiaryDetails, getBeneficiaryBalance } = useContext(BeneficiaryContext); + const { appSettings } = useContext(AppContext); const [basicInfo, setBasicInfo] = useState({}); const [extras, setExtras] = useState({}); const [projectList, setProjectList] = useState([]); + const [currentBalance, setCurrentBalance] = useState(''); + + const [fetching, setFetching] = useState(false); + + const fetchCurrentBalance = useCallback( + async phone => { + const parsed_phone = parseInt(phone); + const { rahat } = appSettings.agency.contracts; + setFetching(true); + const balance = await getBeneficiaryBalance(parsed_phone, rahat); + setCurrentBalance(balance); + setFetching(false); + }, + [appSettings.agency.contracts, getBeneficiaryBalance] + ); const fetchBeneficiaryDetails = useCallback(async () => { const details = await getBeneficiaryDetails(id); + await fetchCurrentBalance(details.phone); if (details && details.extras) setExtras(details.extras); setBasicInfo(details); const projects = details.projects.map(d => { return { id: d._id, name: d.name }; }); setProjectList(projects); - }, [getBeneficiaryDetails, id]); + }, [fetchCurrentBalance, getBeneficiaryDetails, id]); useEffect(() => { fetchBeneficiaryDetails(); @@ -47,7 +65,13 @@ const BenefDetails = ({ params }) => { /> - + diff --git a/src/modules/global/Loading.js b/src/modules/global/Loading.js index facd78d6..b0db4edb 100644 --- a/src/modules/global/Loading.js +++ b/src/modules/global/Loading.js @@ -1,16 +1,12 @@ -import React from "react"; +import React from 'react'; const Loading = () => { - return ( -
-
- Loading... -
-
- ); + return ( +
+
+ Loading... +
+
+ ); }; export default Loading; diff --git a/src/modules/ui_components/balance/index.js b/src/modules/ui_components/balance/index.js index ba45018f..cacf6333 100644 --- a/src/modules/ui_components/balance/index.js +++ b/src/modules/ui_components/balance/index.js @@ -3,8 +3,10 @@ import { Card, CardTitle, Col, Row } from 'reactstrap'; import { useHistory } from 'react-router-dom'; import '../../../assets/css/project.css'; +import Loading from '../../global/Loading'; + export default function Balance(props) { - const { title, data, button_name, label, projectId } = props; + const { title, data, button_name, label, projectId, fetching } = props; const history = useHistory(); const handleClick = () => { history.push(`/add_budget/${projectId}`); @@ -16,7 +18,8 @@ export default function Balance(props) { {title || 'No Title'} -

{data || '0'}

+ {fetching ? :

{data || '0'}

} +
{label || 'No Label'}
From 7d247f0f170a63f3513ab22238d194615e10f925 Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Tue, 17 Aug 2021 21:01:01 +0545 Subject: [PATCH 29/59] issue token to project based beneficiaries --- src/contexts/BeneficiaryContext.js | 18 +- src/modules/beneficary/detail/_detail.js | 85 ---- src/modules/beneficary/detail/detail.js | 517 +++++++-------------- src/modules/beneficary/detail/index.js | 2 +- src/modules/global/CustomModal.js | 14 +- src/modules/ui_components/balance/index.js | 22 +- src/routes/Router.js | 13 - 7 files changed, 210 insertions(+), 461 deletions(-) delete mode 100644 src/modules/beneficary/detail/_detail.js diff --git a/src/contexts/BeneficiaryContext.js b/src/contexts/BeneficiaryContext.js index 6abbab85..43dc11b1 100644 --- a/src/contexts/BeneficiaryContext.js +++ b/src/contexts/BeneficiaryContext.js @@ -18,21 +18,21 @@ const initialState = { export const BeneficiaryContext = createContext(initialState); export const BeneficiaryContextProvider = ({ children }) => { const [state, dispatch] = useReducer(beneficiaryReduce, initialState); - const { wallet, appSettings, changeIsverified } = useContext(AppContext); + const { appSettings, changeIsverified } = useContext(AppContext); async function getAvailableBalance(proejctId, rahatAdminContractAddr) { const { rahat: rahatContractAddr } = appSettings.agency.contracts; return AidService.loadAidBalance(proejctId, rahatContractAddr); } - const issueTokens = async payload => { - const { rahat: rahatContractAddr } = appSettings.agency.contracts; - await AidService.issueBeneficiaryToken(wallet, payload, rahatContractAddr); - changeIsverified(false); - let balance = await Service.getBeneficiaryBalance(payload.phone, rahatContractAddr); - dispatch({ type: ACTION.SET_TOKEN_BALANCE, data: balance }); - return payload; - }; + const issueTokens = useCallback( + async (payload, wallet) => { + changeIsverified(false); + const { rahat: rahatContractAddr } = appSettings.agency.contracts; + return AidService.issueBeneficiaryToken(wallet, payload, rahatContractAddr); + }, + [appSettings.agency.contracts, changeIsverified] + ); const getBeneficiaryBalance = useCallback(async (phone, contract_address) => { return Service.getBeneficiaryBalance(phone, contract_address); diff --git a/src/modules/beneficary/detail/_detail.js b/src/modules/beneficary/detail/_detail.js deleted file mode 100644 index 9098bbac..00000000 --- a/src/modules/beneficary/detail/_detail.js +++ /dev/null @@ -1,85 +0,0 @@ -import React, { useContext, useCallback, useEffect, useState } from 'react'; -import { Row, Col } from 'reactstrap'; - -import Balance from '../../ui_components/balance'; -import DetailsCard from '../../global/DetailsCard'; -import BeneficiaryInfo from './beneficiaryInfo'; -import ProjectInvovled from '../../ui_components/projects'; -import BreadCrumb from '../../ui_components/breadcrumb'; - -import { AppContext } from '../../../contexts/AppSettingsContext'; -import { BeneficiaryContext } from '../../../contexts/BeneficiaryContext'; - -const BenefDetails = ({ params }) => { - const { id } = params; - const { getBeneficiaryDetails, getBeneficiaryBalance } = useContext(BeneficiaryContext); - const { appSettings } = useContext(AppContext); - - const [basicInfo, setBasicInfo] = useState({}); - const [extras, setExtras] = useState({}); - const [projectList, setProjectList] = useState([]); - const [currentBalance, setCurrentBalance] = useState(''); - - const [fetching, setFetching] = useState(false); - - const fetchCurrentBalance = useCallback( - async phone => { - const parsed_phone = parseInt(phone); - const { rahat } = appSettings.agency.contracts; - setFetching(true); - const balance = await getBeneficiaryBalance(parsed_phone, rahat); - setCurrentBalance(balance); - setFetching(false); - }, - [appSettings.agency.contracts, getBeneficiaryBalance] - ); - - const fetchBeneficiaryDetails = useCallback(async () => { - const details = await getBeneficiaryDetails(id); - await fetchCurrentBalance(details.phone); - if (details && details.extras) setExtras(details.extras); - setBasicInfo(details); - const projects = details.projects.map(d => { - return { id: d._id, name: d.name }; - }); - setProjectList(projects); - }, [fetchCurrentBalance, getBeneficiaryDetails, id]); - - useEffect(() => { - fetchBeneficiaryDetails(); - }, [fetchBeneficiaryDetails]); - - return ( - <> -

Beneficiary

- - - - - - - - - - - {basicInfo && } - - - - ); -}; - -export default BenefDetails; diff --git a/src/modules/beneficary/detail/detail.js b/src/modules/beneficary/detail/detail.js index 0cd1e632..51c12bf0 100644 --- a/src/modules/beneficary/detail/detail.js +++ b/src/modules/beneficary/detail/detail.js @@ -1,384 +1,225 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useCallback, useEffect, useState } from 'react'; +import { Row, Col, FormGroup, Label, InputGroup, Input } from 'reactstrap'; import { useToasts } from 'react-toast-notifications'; -import Select from 'react-select'; -import { Link } from 'react-router-dom'; -import { - Card, - CardBody, - Row, - Col, - Input, - Button, - Table, - FormGroup, - Label, - InputGroup, - Modal, - ModalBody, - ModalHeader, - ModalFooter -} from 'reactstrap'; -import Swal from 'sweetalert2'; - -import { BeneficiaryContext } from '../../../contexts/BeneficiaryContext'; -import { AppContext } from '../../../contexts/AppSettingsContext'; +import Balance from '../../ui_components/balance'; +import DetailsCard from '../../global/DetailsCard'; +import BeneficiaryInfo from './beneficiaryInfo'; +import ProjectInvovled from '../../ui_components/projects'; +import BreadCrumb from '../../ui_components/breadcrumb'; import PasscodeModal from '../../global/PasscodeModal'; +import ModalWrapper from '../../global/CustomModal'; +import SelectWrapper from '../../global/SelectWrapper'; +import { TOAST } from '../../../constants'; -import profilePhoto from '../../../assets/images/users/user_avatar.svg'; -import UnlockWallet from '../../global/walletUnlock'; +import { AppContext } from '../../../contexts/AppSettingsContext'; +import { BeneficiaryContext } from '../../../contexts/BeneficiaryContext'; +import { History } from '../../../utils/History'; -export default function DetailsForm(props) { +const BenefDetails = ({ params }) => { + const { id } = params; const { addToast } = useToasts(); - const [tokenBalance, setTokenBalance] = useState(''); - const [loading, setLoading] = useState(false); - const [modal, setModal] = useState(false); - const [projectOptions, setProjectOptions] = useState([]); - const [inputTokens, setInputTokens] = useState(null); - const [selectedProject, setSelectedProject] = useState(''); - const [availableBalance, setAvailableBalance] = useState(null); - const [showAlert, setShowAlert] = useState(false); - const [passcodeModal, setPasscodeModal] = useState(false); - - const beneficiaryId = props.params.id; - const togglePasscodeModal = () => setPasscodeModal(!passcodeModal); + const { getBeneficiaryDetails, getBeneficiaryBalance, getAvailableBalance, issueTokens } = useContext( + BeneficiaryContext + ); + const { isVerified, wallet, loading, setLoading, appSettings } = useContext(AppContext); - const { - issueTokens, - beneficiary_detail, - getBeneficiaryDetails, - getBeneficiaryBalance, - listAid, - getAvailableBalance - } = useContext(BeneficiaryContext); + const [basicInfo, setBasicInfo] = useState({}); + const [extras, setExtras] = useState({}); + const [projectList, setProjectList] = useState([]); + const [currentBalance, setCurrentBalance] = useState(''); + const [inputTokens, setInputTokens] = useState(''); + const [projectOptions, setProjectOptions] = useState([]); - const { appSettings, isVerified, changeIsverified } = useContext(AppContext); + const [fetching, setFetching] = useState(false); + const [passcodeModal, setPasscodeModal] = useState(false); + const [projectModal, setProjectModal] = useState(false); + const [availableBalance, setAvailableBalance] = useState(''); + const [showAlert, setShowAlert] = useState(false); + const [selectedProject, setSelectedProject] = useState(''); - const handleTokenChange = e => { - setInputTokens(e.target.value); + const toggleProjectModal = () => { + // If opening modal, reset fields + if (!projectModal) { + setShowAlert(false); + setAvailableBalance(''); + setInputTokens(''); + setSelectedProject(''); + } + setProjectModal(!projectModal); }; + const handleInputTokenChange = e => setInputTokens(e.target.value); - const handleSelectProject = async e => { + const togglePasscodeModal = useCallback(() => { + setPasscodeModal(!passcodeModal); + }, [passcodeModal]); + + const handleProjectChange = async d => { try { + setSelectedProject(d.value); setLoading(true); - setSelectedProject(e.value); - //const { rahat_admin } = appSettings.agency.contracts; - let d = await getAvailableBalance(e.value); - setAvailableBalance(d); + const balance = await getAvailableBalance(d.value); + setAvailableBalance(balance); setShowAlert(true); setLoading(false); - } catch { - setShowAlert(false); - addToast('Failed to fetch availabe balance!', { - appearance: 'error', - autoDismiss: true - }); + } catch (err) { setLoading(false); + setShowAlert(false); + addToast('Failed to fetch project balance', TOAST.ERROR); } }; - const handleIssueToken = e => { + const handleIssueToken = () => toggleProjectModal(); + + const handleIssueSubmit = e => { e.preventDefault(); - if (!selectedProject || !inputTokens) { - Swal.fire({ - icon: 'error', - title: 'Attention', - text: 'Project and input token is required!' - }); - return; - } - if (inputTokens > availableBalance) { - Swal.fire({ - icon: 'error', - title: 'Attention', - text: 'Input balance can not be greater than available balance!' - }); - return; - } + if (!selectedProject) return addToast('Please select project', TOAST.ERROR); + if (inputTokens > availableBalance) return addToast('Input tokens must be less than available', TOAST.ERROR); + toggleProjectModal(); togglePasscodeModal(); }; - const submitIssueTokenDetails = () => { - if (!isVerified) return; + const submitRequest = useCallback( + async (payload, wallet) => { + try { + setLoading(true); + await issueTokens(payload, wallet); + addToast(`${payload.claimable} tokens issued successfully`, TOAST.SUCCESS); + setLoading(false); + History.push('/beneficiaries'); + } catch (err) { + setLoading(false); + addToast(err.message, TOAST.ERROR); + } + }, + [addToast, issueTokens, setLoading] + ); + + const issueBeneficiaryToken = useCallback(async () => { const payload = { claimable: +inputTokens, - phone: +beneficiary_detail.phone, + phone: +basicInfo.phone, projectId: selectedProject }; - setLoading(true); - issueTokens(payload) - .then(() => { - changeIsverified(false); - toggleModal(); - addToast(`${payload.claimable} tokens assigned to beneficiary.`, { - appearance: 'success', - autoDismiss: true - }); - getBalance(payload.phone); - setLoading(false); - resetTokenIssueForm(); - togglePasscodeModal(); - }) - .catch(err => { - changeIsverified(false); - togglePasscodeModal(); - addToast(err.message, { - appearance: 'error', - autoDismiss: true - }); - }); - }; - - const toggleModal = () => { - setModal(prevState => !prevState); - resetTokenIssueForm(); - }; - - const resetTokenIssueForm = () => { - setInputTokens(null); - setAvailableBalance(''); - setSelectedProject(null); - setShowAlert(false); - }; + if (isVerified && wallet) { + setPasscodeModal(false); + return submitRequest(payload, wallet); + } + }, [basicInfo.phone, inputTokens, isVerified, selectedProject, submitRequest, wallet]); + + const fetchCurrentBalance = useCallback( + async phone => { + const parsed_phone = parseInt(phone); + const { rahat } = appSettings.agency.contracts; + setFetching(true); + const balance = await getBeneficiaryBalance(parsed_phone, rahat); + setCurrentBalance(balance); + setFetching(false); + }, + [appSettings.agency.contracts, getBeneficiaryBalance] + ); - const getBalance = phone => { - getBeneficiaryBalance(+phone, contractAddress) - .then(bal => { - setTokenBalance(bal); - }) - .catch(() => { - addToast('Internal server error!', { - appearance: 'error', - autoDismiss: true - }); + const fetchBeneficiaryDetails = useCallback(async () => { + const details = await getBeneficiaryDetails(id); + await fetchCurrentBalance(details.phone); + if (details && details.extras) setExtras(details.extras); + setBasicInfo(details); + if (details.projects && details.projects.length) { + const projects = details.projects.map(d => { + return { id: d._id, name: d.name }; }); - }; - - const loadBeneficiaryDetails = () => { - getBeneficiaryDetails(beneficiaryId) - .then(d => { - getBalance(d.phone); - }) - .catch(() => { - addToast('Internal server error!', { - appearance: 'error', - autoDismiss: true - }); + setProjectList(projects); + // Render select options + const select_options = details.projects.map(d => { + return { label: d.name, value: d._id }; }); - }; - - const fetchProjectList = () => { - listAid() - .then(d => { - sanitizeProjectOptions(d.data); - }) - .catch(() => { - addToast('Something went wrong!', { - appearance: 'error', - autoDismiss: true - }); - }); - }; - - const sanitizeProjectOptions = data => { - let options = []; - if (data && data.length) { - for (let d of data) { - let obj = {}; - obj.value = d._id; - obj.label = d.name; - options.push(obj); - } - setProjectOptions(options); - return; + setProjectOptions(select_options); } - setProjectOptions(options); - }; + }, [fetchCurrentBalance, getBeneficiaryDetails, id]); - useEffect(fetchProjectList, []); - useEffect(loadBeneficiaryDetails, []); - useEffect(submitIssueTokenDetails, [isVerified]); + useEffect(() => { + fetchBeneficiaryDetails(); + }, [fetchBeneficiaryDetails]); - const contractAddress = appSettings && appSettings.agency ? appSettings.agency.contracts.rahat : ''; + useEffect(() => { + issueBeneficiaryToken(); + }, [issueBeneficiaryToken, isVerified]); return ( <> - setPasscodeModal(e)}> - - - Issue Token - - - - -
- - - - -
- - {showAlert && availableBalance > 0 ? ( -
- Availabe Balance: {availableBalance} -
- ) : showAlert ? ( -
-
-

- Project has ZERO balance. You can add here. -

-
+ + + + {showAlert && availableBalance > 0 ? ( +
+ Availabe Balance: {availableBalance} +
+ ) : showAlert ? ( +
+
+

Project has ZERO balance.

- ) : ( - '' - )} - - - - {loading ? ( - <> -
- Loading... -
- +
) : ( - <> - {availableBalance && availableBalance > 0 ? ( - <> - - - - ) : ( - '' - )} - + '' )} - - +
+ + +

Beneficiary

+ - - - -
-
-
- user -
-
-
{beneficiary_detail ? beneficiary_detail.name : ''}
-

Current balance: {tokenBalance}

-
-
- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Phone - -
Email - -
Government ID - -
Wallet Address - -
Permanent Address - -
Temporary Address - -
-
-
-
-
+ + + + +
+ + {basicInfo && } + + ); -} +}; + +export default BenefDetails; diff --git a/src/modules/beneficary/detail/index.js b/src/modules/beneficary/detail/index.js index dac20fed..1a501bdf 100644 --- a/src/modules/beneficary/detail/index.js +++ b/src/modules/beneficary/detail/index.js @@ -1,6 +1,6 @@ import React from 'react'; import { BeneficiaryContextProvider } from '../../../contexts/BeneficiaryContext'; -import Detail from './_detail'; +import Detail from './detail'; const index = props => { return ( diff --git a/src/modules/global/CustomModal.js b/src/modules/global/CustomModal.js index 8e53c07e..e45cdcad 100644 --- a/src/modules/global/CustomModal.js +++ b/src/modules/global/CustomModal.js @@ -19,13 +19,15 @@ export default function CustomModal(props) { {loading ? ( ) : ( - + <> + + + )} - )} diff --git a/src/modules/ui_components/balance/index.js b/src/modules/ui_components/balance/index.js index cacf6333..0cdb4b37 100644 --- a/src/modules/ui_components/balance/index.js +++ b/src/modules/ui_components/balance/index.js @@ -6,7 +6,7 @@ import '../../../assets/css/project.css'; import Loading from '../../global/Loading'; export default function Balance(props) { - const { title, data, button_name, label, projectId, fetching } = props; + const { title, data, button_name, label, projectId, fetching, action, handleIssueToken, loading } = props; const history = useHistory(); const handleClick = () => { history.push(`/add_budget/${projectId}`); @@ -25,14 +25,18 @@ export default function Balance(props) {
- + {loading ? ( + + ) : ( + + )}
{/*
diff --git a/src/routes/Router.js b/src/routes/Router.js index d7d5031f..2fb144dd 100644 --- a/src/routes/Router.js +++ b/src/routes/Router.js @@ -39,11 +39,6 @@ const UserDetails = lazy(() => import('../modules/user/edit')); const Vendor = lazy(() => import('../modules/vendor')); const VendorDetails = lazy(() => import('../modules/vendor/detail/index')); -// ------------------------------Beneficiary UI------------------------------------ -const BeneficiaryDetail = lazy(() => import('../views/beneficiaries/detail')); - -// -------------------------------------------------------------------------------- - // ------------------------------Vendor UI------------------------------------ const VendorAdd = lazy(() => import('../views/vendors/add')); @@ -140,14 +135,6 @@ let AppRoutes = [ component: BudgetAdd }, - //.............................Beneficiary ui...................... - { - path: '/detail_beneficiary', - name: 'BeneficiaryDetail', - component: BeneficiaryDetail - }, - // ----------------------------------------------------------------- - //.............................Vendor ui...................... { From e8433c45c45949fb39130b6921e83c0f4249f7bc Mon Sep 17 00:00:00 2001 From: rojanbade Date: Thu, 19 Aug 2021 11:37:24 +0545 Subject: [PATCH 30/59] vendor api integration --- src/contexts/VendorContext.js | 301 +++++++------ src/modules/vendor/add/add.js | 213 ++++++++++ src/modules/vendor/add/index.js | 14 + src/modules/vendor/detail/detail.js | 397 ++++-------------- .../vendor/detail/transactionHistory.js | 59 +++ src/modules/vendor/detail/vendorDetail.js | 40 ++ src/modules/vendor/detail/vendorInfo.js | 89 ++++ src/modules/vendor/edit/edit.js | 213 ++++++++++ src/modules/vendor/edit/index.js | 14 + src/routes/Router.js | 12 + yarn.lock | 2 +- 11 files changed, 884 insertions(+), 470 deletions(-) create mode 100644 src/modules/vendor/add/add.js create mode 100644 src/modules/vendor/add/index.js create mode 100644 src/modules/vendor/detail/transactionHistory.js create mode 100644 src/modules/vendor/detail/vendorDetail.js create mode 100644 src/modules/vendor/detail/vendorInfo.js create mode 100644 src/modules/vendor/edit/edit.js create mode 100644 src/modules/vendor/edit/index.js diff --git a/src/contexts/VendorContext.js b/src/contexts/VendorContext.js index 5df8e23e..e91eec70 100644 --- a/src/contexts/VendorContext.js +++ b/src/contexts/VendorContext.js @@ -1,158 +1,157 @@ -import React, { createContext, useReducer,useContext } from "react"; -import vendorReduce from "../reducers/vendorReducer"; -import * as Service from "../services/vendor"; -import * as AidService from "../services/aid"; -import ACTION from "../actions/vendor"; -import {AppContext} from './AppSettingsContext'; +import React, { createContext, useReducer, useContext } from 'react'; +import vendorReduce from '../reducers/vendorReducer'; +import * as Service from '../services/vendor'; +import * as AidService from '../services/aid'; +import ACTION from '../actions/vendor'; +import { AppContext } from './AppSettingsContext'; const initialState = { - list: [], - pagination: { limit: 10, start: 0, total: 0, currentPage: 1, totalPages: 0 }, - aid: "", - aids: [], - vendor: {}, - loading: false, - transactionHistory: [], + list: [], + pagination: { limit: 10, start: 0, total: 0, currentPage: 1, totalPages: 0 }, + aid: '', + aids: [], + vendor: {}, + loading: false, + transactionHistory: [] }; export const VendorContext = createContext(initialState); export const VendorContextProvider = ({ children }) => { - const [state, dispatch] = useReducer(vendorReduce, initialState); - const {wallet,appSettings,changeIsverified} = useContext(AppContext); - - async function getVendorBalance(contract_addr, wallet_address) { - return Service.getVendorBalance(contract_addr, wallet_address); - } - - async function listAid() { - const d = await AidService.listAid({ start: 0, limit: 20 }); - dispatch({ type: ACTION.LIST_AID, data: { aids: d.data } }); - } - - function setAid(aid) { - dispatch({ type: ACTION.SET_AID, data: aid }); - } - - function clear() { - dispatch({ - type: ACTION.LIST, - data: { - limit: 10, - start: 0, - total: 0, - data: [], - page: 0, - name: "", - phone: "", - }, - }); - } - - async function approveVendor( payload) { - const { rahat:rahatContractAddr } = appSettings.agency.contracts; - const res = await Service.approveVendor(wallet,payload,rahatContractAddr); - changeIsverified(false); - if (res) { - setVendor(res.data); - return res.data; - } - } - - async function changeVendorStatus(vendorId, status) { - const res = await Service.changeVendorStaus(vendorId, status); - setVendor(res.data); - return res.data; - } - - function setVendor(b) { - dispatch({ type: ACTION.SET_VENDOR, data: b }); - } - - async function getVendorDetails(id) { - const data = await Service.get(id); - setVendor(data); - return data; - } - - function addVendor(e) { - e.preventDefault(); - const formData = new FormData(e.target); - let payload = { - name: formData.get("name"), - phone: formData.get("phone"), - wallet_address: formData.get("ethaddress"), - email: formData.get("email"), - address: formData.get("address"), - govt_id: formData.get("govt_id"), - }; - - return new Promise((resolve, reject) => { - Service.add(payload) - .then((res) => { - resolve(res); - }) - .catch((err) => { - reject(err); - }); - }); - } - - function setLoading() { - dispatch({ type: ACTION.SET_LOADING }); - } - - function resetLoading() { - dispatch({ type: ACTION.RESET_LOADING }); - } - - async function listVendor(params) { - let res = await Service.list(params); - if (res) { - dispatch({ - type: ACTION.LIST, - data: res, - }); - return res; - } - } - - async function getVendorTransactions(vendorId) { - let res = await Service.vendorTransactions(vendorId); - if (res) { - dispatch({ - type: ACTION.VENDOR_TX, - data: res, - }); - return res; - } - } - - return ( - - {children} - - ); + const [state, dispatch] = useReducer(vendorReduce, initialState); + const { wallet, appSettings, changeIsverified } = useContext(AppContext); + + async function getVendorBalance(contract_addr, wallet_address) { + return Service.getVendorBalance(contract_addr, wallet_address); + } + + async function listAid() { + const d = await AidService.listAid({ start: 0, limit: 20 }); + dispatch({ type: ACTION.LIST_AID, data: { aids: d.data } }); + } + + function setAid(aid) { + dispatch({ type: ACTION.SET_AID, data: aid }); + } + + function clear() { + dispatch({ + type: ACTION.LIST, + data: { + limit: 10, + start: 0, + total: 0, + data: [], + page: 0, + name: '', + phone: '' + } + }); + } + + async function approveVendor(payload) { + const { rahat: rahatContractAddr } = appSettings.agency.contracts; + const res = await Service.approveVendor(wallet, payload, rahatContractAddr); + changeIsverified(false); + if (res) { + setVendor(res.data); + return res.data; + } + } + + async function changeVendorStatus(vendorId, status) { + const res = await Service.changeVendorStaus(vendorId, status); + setVendor(res.data); + return res.data; + } + + function setVendor(b) { + dispatch({ type: ACTION.SET_VENDOR, data: b }); + } + + async function getVendorDetails(id) { + const data = await Service.get(id); + setVendor(data); + return data; + } + + function addVendor(payload) { + // const formData = new FormData(e.target); + // let payload = { + // name: formData.get("name"), + // phone: formData.get("phone"), + // wallet_address: formData.get("ethaddress"), + // email: formData.get("email"), + // address: formData.get("address"), + // govt_id: formData.get("govt_id"), + // }; + + return new Promise((resolve, reject) => { + Service.add(payload) + .then(res => { + resolve(res); + }) + .catch(err => { + reject(err); + }); + }); + } + + function setLoading() { + dispatch({ type: ACTION.SET_LOADING }); + } + + function resetLoading() { + dispatch({ type: ACTION.RESET_LOADING }); + } + + async function listVendor(params) { + let res = await Service.list(params); + if (res) { + dispatch({ + type: ACTION.LIST, + data: res + }); + return res; + } + } + + async function getVendorTransactions(vendorId) { + let res = await Service.vendorTransactions(vendorId); + if (res) { + dispatch({ + type: ACTION.VENDOR_TX, + data: res + }); + return res; + } + } + + return ( + + {children} + + ); }; diff --git a/src/modules/vendor/add/add.js b/src/modules/vendor/add/add.js new file mode 100644 index 00000000..a9e69dc0 --- /dev/null +++ b/src/modules/vendor/add/add.js @@ -0,0 +1,213 @@ +import React, { useState, useEffect, useContext } from 'react'; +import { + Breadcrumb, + BreadcrumbItem, + Card, + CardBody, + Row, + Col, + Form, + FormGroup, + Label, + Input, + Button +} from 'reactstrap'; +import { useToasts } from 'react-toast-notifications'; +import { VendorContext } from '../../../contexts/VendorContext'; +import { History } from '../../../utils/History'; +import { TOAST } from '../../../constants'; +import WalletUnlock from '../../../modules/global/walletUnlock'; + +const Add = () => { + const { addToast } = useToasts(); + const { addVendor } = useContext(VendorContext); + + const [passcodeModal, setPasscodeModal] = useState(false); + + const [formData, setFormData] = useState({ + name: '', + // shop_name: '', + phone: '', + email: '', + address: '', + // education: '', + // pan_number: '', + governmentID: '', + wallet_address: '', + bank_branch: '', + bank_name: '', + account_number: '' + }); + const [selectedGender, setSelectedGender] = useState(''); + + const handleInputChange = e => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleGenderChange = e => { + setSelectedGender(e.target.value); + }; + + const handleFormSubmit = e => { + e.preventDefault(); + const payload = { ...formData }; + if (selectedGender) payload.gender = selectedGender; + console.log('vendor', payload); + addVendor(payload) + .then(() => { + addToast('Vendor added successfully', TOAST.SUCCESS); + History.push('/vendors'); + }) + .catch(err => { + addToast(err.message, TOAST.ERROR); + }); + }; + + const handleCancelClick = () => History.push('/users'); + + const saveUserDetails = () => {}; + + useEffect(saveUserDetails); + + return ( +
+ setPasscodeModal(e)}> +

Vendor

+ + + Vendor + + Add + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + +
+ +
+ +
+ + + +
+ +
+ + + {/* {loading ? ( + + ) : ( */} +
+ + +
+ {/* )} */} +
+
+
+
+ +
+
+ ); +}; + +export default Add; diff --git a/src/modules/vendor/add/index.js b/src/modules/vendor/add/index.js new file mode 100644 index 00000000..e826c67d --- /dev/null +++ b/src/modules/vendor/add/index.js @@ -0,0 +1,14 @@ +import React from 'react'; + +import { VendorContextProvider } from '../../../contexts/VendorContext'; +import AddVendor from './add'; + +export default function Index() { + return ( + <> + + + + + ); +} diff --git a/src/modules/vendor/detail/detail.js b/src/modules/vendor/detail/detail.js index 02babb8f..f183a253 100644 --- a/src/modules/vendor/detail/detail.js +++ b/src/modules/vendor/detail/detail.js @@ -1,337 +1,98 @@ -import React, { useContext, useEffect, useState } from 'react'; -import { useToasts } from 'react-toast-notifications'; - -import { Card, CardBody, Row, Col, Input, ButtonGroup, Button, Table, CardSubtitle, CardTitle } from 'reactstrap'; -import Swal from 'sweetalert2'; - +import React, { useContext, useCallback, useEffect, useState } from 'react'; +import { Breadcrumb, BreadcrumbItem, Row, Col, Card, CardTitle } from 'reactstrap'; +// import TotalCard from '../../totalCard'; +import Balance from '../../ui_components/balance'; +import VendorInfo from './vendorInfo'; +import ProjectInvovled from '../../ui_components/projects'; +import TransactionHistory from './transactionHistory'; import { VendorContext } from '../../../contexts/VendorContext'; -import { AppContext } from '../../../contexts/AppSettingsContext'; -import profilePhoto from '../../../assets/images/users/user_avatar.svg'; -import IdPhoto from '../../../assets/images/id-icon-1.png'; -import UnlockWallet from '../../global/walletUnlock'; -import Loading from '../../global/Loading'; - -const EXPLORER_URL = process.env.REACT_APP_BLOCKCHAIN_EXPLORER; -const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY; - -export default function DetailsForm(props) { - const vendorId = props.params.id; - const { addToast } = useToasts(); - const { - vendor, - getVendorDetails, - approveVendor, - changeVendorStatus, - getVendorBalance, - getVendorTransactions, - transactionHistory - } = useContext(VendorContext); - const { appSettings, isVerified, changeIsverified } = useContext(AppContext); - const [vendorBalance, setVendorBalance] = useState(''); - const [passcodeModal, setPasscodeModal] = useState(false); - const [loading, setLoading] = useState(false); - const togglePasscodeModal = () => setPasscodeModal(!passcodeModal); - - const loadVendorDetails = () => { - getVendorDetails(vendorId) - .then(d => { - getVendorTransactions(vendorId); - getBalance(d.wallet_address); - }) - .catch(() => { - addToast('Something went wrong on server!', { - appearance: 'error', - autoDismiss: true - }); - }); - }; +import displayPic from '../../../assets/images/users/user_avatar.svg'; - const getBalance = async wallet => { - if (appSettings && appSettings.agency) { - try { - const { token } = appSettings.agency.contracts; - let d = await getVendorBalance(token, wallet); - setVendorBalance(d); - } catch { - addToast('Invalid vendor wallet address!', { - appearance: 'error', - autoDismiss: true - }); - } - } - }; +const projects = [ + { id: '0', name: 'Sindhupalchowk relief' }, + { id: '1', name: 'Flood relief distribution' } +]; +const Index = ({ params }) => { + const { id } = params; + const { getVendorDetails } = useContext(VendorContext); + const [basicInfo, setBasicInfo] = useState({}); - const handleChangeStatus = async status => { - let swal = await Swal.fire({ - title: 'Are you sure?', - text: `Vendor will be marked as ${status}`, - icon: 'warning', - showCancelButton: true, - confirmButtonColor: '#3085d6', - cancelButtonColor: '#d33', - confirmButtonText: 'Yes' - }); - if (swal.isConfirmed) { - try { - await changeVendorStatus(vendorId, status); - addToast(`Vendor marked as ${status}`, { - appearance: 'success', - autoDismiss: true - }); - } catch (e) { - addToast('Something went wrong on server!', { - appearance: 'error', - autoDismiss: true - }); - } - } - }; + const fetchVendorDetails = useCallback(async () => { + const details = await getVendorDetails(id); + if (details) setBasicInfo(details); + }, [getVendorDetails, id]); - const submitVendorApproval = e => { - if (!isVerified) return; - setLoading(true); - let payload = { - status: 'active', - wallet_address: vendor.wallet_address, - vendorId: vendorId - }; - approveVendor(payload) - .then(() => { - changeIsverified(false); - setLoading(false); - togglePasscodeModal(); - addToast('Vendor approved successfully.', { - appearance: 'success', - autoDismiss: true - }); - }) - .catch(() => { - changeIsverified(false); - setLoading(false); - togglePasscodeModal(); - addToast('Invalid vendor wallet address!', { - appearance: 'error', - autoDismiss: true - }); - }); - }; + useEffect(() => { + // console.log('EFFECT!'); + fetchVendorDetails(); + }, [fetchVendorDetails]); - const handleVendorApprove = async e => { - e.preventDefault(); - let swal = await Swal.fire({ - title: 'Are you sure?', - text: `You want to approve this vendor!`, - icon: 'warning', - showCancelButton: true, - confirmButtonColor: '#3085d6', - cancelButtonColor: '#d33', - confirmButtonText: 'Yes' - }); - if (swal.isConfirmed) togglePasscodeModal(); - }; - - useEffect(loadVendorDetails, []); - useEffect(submitVendorApproval, [isVerified]); - - const vendor_status = vendor && vendor.agencies ? vendor.agencies[0].status : 'new'; + console.log('BASIC==>', basicInfo); return ( <> - setPasscodeModal(e)}> - +

Vendors

+ + + Vendors + + Detail + - + - -
-
-
- user -
-
-
{vendor ? vendor.name : ''}
-

Current balance: {vendorBalance || 0}

-
-
- {vendor_status !== 'new' ? ( - - - - - ) : loading ? ( - - ) : ( - - )} -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Status - -
Phone - -
Email - -
Government ID - -
Wallet Address - -
Address - -
-
+
+ + Vendor Detail + -
- user -
-
- + + +
+ user +
+

Susma shahi thakuri

+
Name
+
+
+ + + + +
+
-
- - - + - - Transaction history - {transactionHistory.length > 0 && ( - {transactionHistory.length} transactions found. - )} - - - - - - - - - - - - {transactionHistory && transactionHistory.length ? ( - transactionHistory.map((tx, index) => { - return ( - - - - - - - - ); - }) - ) : ( - - - - )} - -
FromToValueTypeBlockchain Tx
{tx.from || ''}{tx.to || '-'}{tx.value || '-'} - {(tx.tag === 'sent' && tx.to === appSettings.agency.contracts.rahat_admin - ? 'redeem' - : tx.tag) || '-'} - - - Verify - -
No data available.
-
+
+ Token + + +

Balance

+
Token Status
+ + +

50,000

+
Total Redeemed
+ +
+
+ + + + ); -} +}; + +export default Index; diff --git a/src/modules/vendor/detail/transactionHistory.js b/src/modules/vendor/detail/transactionHistory.js new file mode 100644 index 00000000..6c4912fa --- /dev/null +++ b/src/modules/vendor/detail/transactionHistory.js @@ -0,0 +1,59 @@ +import React from 'react'; +import { Card, CardBody, CardTitle, CustomInput, Table, Row, Col } from 'reactstrap'; + +const TransactionHistory = () => { + return ( +
+ {/*
*/} + +
+ + + Transaction History + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FromToDateValueTypeTX
xxxxxxxxxxxx123xxxxxxxxxxxx45623-05-2020200,000Receivedxxxxxxxxxxx45
+
+
+ {/*
*/} + +
+
+ ); +}; + +export default TransactionHistory; diff --git a/src/modules/vendor/detail/vendorDetail.js b/src/modules/vendor/detail/vendorDetail.js new file mode 100644 index 00000000..597c789a --- /dev/null +++ b/src/modules/vendor/detail/vendorDetail.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { Card, CardTitle, Col, Row } from 'reactstrap'; +import '../../../assets/css/project.css'; +import displayPic from '../../../assets/images/users/user_avatar.svg'; + +export default function VendorDetail(props) { + const { name } = props; + return ( +
+ +
+ + Vendor Detail + + + + +
+ user +
+

{name || ''}

+
Name
+
+
+ + + + +
+
+
+
+ ); +} diff --git a/src/modules/vendor/detail/vendorInfo.js b/src/modules/vendor/detail/vendorInfo.js new file mode 100644 index 00000000..830da5a6 --- /dev/null +++ b/src/modules/vendor/detail/vendorInfo.js @@ -0,0 +1,89 @@ +import React from 'react'; +import { Card, CardTitle, Col, Row } from 'reactstrap'; +import '../../../assets/css/project.css'; +import image from '../../../assets/images/ID.jpg'; +import sign from '../../../assets/images/sign.png'; +import { History } from '../../../utils/History'; + +export default function VendorInfo(props) { + const { information } = props; + + console.log('information', information); + const handleEditClick = () => History.push(`/edit-vendor/${information.id}`); + + return ( +
+ +
+
+ + More Information + +
+ +
+
+ + +
+

Susma cold store

+
Shop name
+
+ +
+

{information.email}

+
Email
+
+
+

Bachelors in computer science

+
Education
+
+
+

NIC Bank LTD

+
Bank name
+
+
+

120975345678

+
Bank account number
+
+ + +
+

Female

+
Gender
+
+ +
+

{information.phone}

+
Phone number
+
+
+

{information.address}

+
Address
+
+
+

1043325

+
PAN number
+
+
+

Kathmandu

+
Bank branch
+
+ + + certificate + signature + +
+
+
+
+ ); +} diff --git a/src/modules/vendor/edit/edit.js b/src/modules/vendor/edit/edit.js new file mode 100644 index 00000000..3b058412 --- /dev/null +++ b/src/modules/vendor/edit/edit.js @@ -0,0 +1,213 @@ +import React, { useState, useEffect, useContext } from 'react'; +import { + Breadcrumb, + BreadcrumbItem, + Card, + CardBody, + Row, + Col, + Form, + FormGroup, + Label, + Input, + Button +} from 'reactstrap'; +import { useToasts } from 'react-toast-notifications'; +import { VendorContext } from '../../../contexts/VendorContext'; +import { History } from '../../../utils/History'; +import { TOAST } from '../../../constants'; +import WalletUnlock from '../../../modules/global/walletUnlock'; + +const Add = () => { + const { addToast } = useToasts(); + const { addVendor } = useContext(VendorContext); + + const [passcodeModal, setPasscodeModal] = useState(false); + + const [formData, setFormData] = useState({ + name: '', + // shop_name: '', + phone: '', + email: '', + address: '', + // education: '', + // pan_number: '', + governmentID: '', + wallet_address: '', + bank_branch: '', + bank_name: '', + account_number: '' + }); + const [selectedGender, setSelectedGender] = useState(''); + + const handleInputChange = e => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleGenderChange = e => { + setSelectedGender(e.target.value); + }; + + const handleFormSubmit = e => { + e.preventDefault(); + const payload = { ...formData }; + if (selectedGender) payload.gender = selectedGender; + console.log('vendor', payload); + addVendor(payload) + .then(() => { + addToast('Vendor added successfully', TOAST.SUCCESS); + History.push('/vendors'); + }) + .catch(err => { + addToast(err.message, TOAST.ERROR); + }); + }; + + const handleCancelClick = () => History.push('/users'); + + const saveUserDetails = () => {}; + + useEffect(saveUserDetails); + + return ( +
+ setPasscodeModal(e)}> +

Vendor

+ + + Vendor + + Edit + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + +
+ +
+ +
+ + + +
+ +
+ + + {/* {loading ? ( + + ) : ( */} +
+ + +
+ {/* )} */} +
+
+
+
+ +
+
+ ); +}; + +export default Add; diff --git a/src/modules/vendor/edit/index.js b/src/modules/vendor/edit/index.js new file mode 100644 index 00000000..85ab3470 --- /dev/null +++ b/src/modules/vendor/edit/index.js @@ -0,0 +1,14 @@ +import React from 'react'; + +import { VendorContextProvider } from '../../../contexts/VendorContext'; +import EditVendor from './edit'; + +export default function Index() { + return ( + <> + + + + + ); +} diff --git a/src/routes/Router.js b/src/routes/Router.js index d7d5031f..a07e36d9 100644 --- a/src/routes/Router.js +++ b/src/routes/Router.js @@ -38,6 +38,8 @@ const UserDetails = lazy(() => import('../modules/user/edit')); // Vendor const Vendor = lazy(() => import('../modules/vendor')); const VendorDetails = lazy(() => import('../modules/vendor/detail/index')); +const AddVendor = lazy(() => import('../modules/vendor/add')); +const EditVendor = lazy(() => import('../modules/vendor/edit')); // ------------------------------Beneficiary UI------------------------------------ const BeneficiaryDetail = lazy(() => import('../views/beneficiaries/detail')); @@ -166,6 +168,16 @@ let AppRoutes = [ name: 'Vendor', component: VendorDetails }, + { + path: '/add-vendor', + name: 'AddVendor', + component: AddVendor + }, + { + path: '/edit-vendor/:id', + name: 'Vendor', + component: EditVendor + }, { path: '/vendors', name: 'Vendors', diff --git a/yarn.lock b/yarn.lock index 77230cd0..524b4a1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8963,7 +8963,7 @@ node-releases@^1.1.52, node-releases@^1.1.60: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084" integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA== -node-sass@^4.14.1: +node-sass@4.14.1: version "4.14.1" resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.14.1.tgz#99c87ec2efb7047ed638fb4c9db7f3a42e2217b5" integrity sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g== From 5f9dedf6d3b7687496e2426b500a4f3fbcffbe41 Mon Sep 17 00:00:00 2001 From: rojanbade Date: Thu, 19 Aug 2021 12:49:21 +0545 Subject: [PATCH 31/59] useEffect issue resolved --- src/contexts/VendorContext.js | 7 +++---- src/modules/vendor/detail/detail.js | 2 +- src/utils/index.js | 10 ++++++++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/contexts/VendorContext.js b/src/contexts/VendorContext.js index e91eec70..8ca4b128 100644 --- a/src/contexts/VendorContext.js +++ b/src/contexts/VendorContext.js @@ -1,4 +1,4 @@ -import React, { createContext, useReducer, useContext } from 'react'; +import React, { createContext, useReducer, useContext, useCallback } from 'react'; import vendorReduce from '../reducers/vendorReducer'; import * as Service from '../services/vendor'; import * as AidService from '../services/aid'; @@ -68,11 +68,10 @@ export const VendorContextProvider = ({ children }) => { dispatch({ type: ACTION.SET_VENDOR, data: b }); } - async function getVendorDetails(id) { + const getVendorDetails = useCallback(async id => { const data = await Service.get(id); - setVendor(data); return data; - } + }, []); function addVendor(payload) { // const formData = new FormData(e.target); diff --git a/src/modules/vendor/detail/detail.js b/src/modules/vendor/detail/detail.js index f183a253..e6b37f93 100644 --- a/src/modules/vendor/detail/detail.js +++ b/src/modules/vendor/detail/detail.js @@ -23,7 +23,7 @@ const Index = ({ params }) => { }, [getVendorDetails, id]); useEffect(() => { - // console.log('EFFECT!'); + console.log('EFFECT!'); fetchVendorDetails(); }, [fetchVendorDetails]); diff --git a/src/utils/index.js b/src/utils/index.js index 158e0b50..b7cd8904 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -2,3 +2,13 @@ export const formatWord = word => { if (!word) return '-'; return word.replace(/_/g, ' '); }; + +export const blobToBase64 = blob => { + const reader = new FileReader(); + reader.readAsDataURL(blob); + return new Promise(resolve => { + reader.onloadend = () => { + resolve(reader.result); + }; + }); +}; From e260ef1f0202c712d7a03a3f2c0c7feae35acdec Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Thu, 19 Aug 2021 14:05:14 +0545 Subject: [PATCH 32/59] display project manager and serial number in project list --- src/modules/aid/list/index.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/modules/aid/list/index.js b/src/modules/aid/list/index.js index 8aeaa865..5609ee2d 100644 --- a/src/modules/aid/list/index.js +++ b/src/modules/aid/list/index.js @@ -145,6 +145,7 @@ const List = () => { + @@ -155,12 +156,15 @@ const List = () => { {aids.length ? ( - aids.map(d => { + aids.map((d, i) => { return ( + - - + + { const _status = e === true ? PROJECT_STATUS.ACTIVE : PROJECT_STATUS.SUSPENDED; @@ -37,6 +47,7 @@ export default function DetailsCard(props) { /> ) : ( {projectDetails && ( {projectDetails && } - + diff --git a/src/modules/aid/detail/pieChart.js b/src/modules/aid/detail/pieChart.js index a78be27a..5c52eb3c 100644 --- a/src/modules/aid/detail/pieChart.js +++ b/src/modules/aid/detail/pieChart.js @@ -1,8 +1,9 @@ import React from 'react'; import { Card, CardBody, CardTitle } from 'reactstrap'; import { Pie } from 'react-chartjs-2'; +import Loading from '../../global/Loading'; -export default function Chart({ available_tokens, total_tokens }) { +export default function Chart({ available_tokens, total_tokens, fetching }) { const pieData = { labels: ['Available Tokens', 'Used Tokens'], datasets: [ @@ -19,20 +20,24 @@ export default function Chart({ available_tokens, total_tokens }) { Token Detail
- + ) : ( + + }} + /> + )}
diff --git a/src/modules/global/DetailsCard.js b/src/modules/global/DetailsCard.js index c2dd2cea..11b2e5dd 100644 --- a/src/modules/global/DetailsCard.js +++ b/src/modules/global/DetailsCard.js @@ -4,9 +4,11 @@ import BootstrapSwitchButton from 'bootstrap-switch-button-react'; import '../../assets/css/project.css'; import { PROJECT_STATUS } from '../../constants'; +import Loading from '../global/Loading'; export default function DetailsCard(props) { const { + fetching, title, button_name, name, @@ -64,7 +66,8 @@ export default function DetailsCard(props) {
{name || 'No Label'}
-

{total_value || '0'}

+ {fetching ? :

{total_value || '0'}

} +
{total || 'No Label'}
From 9047075b984adcbe1eb5603fd74f9deac0f90f90 Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Mon, 23 Aug 2021 13:09:36 +0545 Subject: [PATCH 35/59] display serial number in list --- src/modules/aid/list/index.js | 2 +- src/modules/beneficary/list.js | 4 +++- src/modules/institution/list.js | 2 ++ src/modules/mobilizer/list.js | 7 ++++--- src/modules/user/list/index.js | 4 +++- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/modules/aid/list/index.js b/src/modules/aid/list/index.js index 5609ee2d..fffe4591 100644 --- a/src/modules/aid/list/index.js +++ b/src/modules/aid/list/index.js @@ -159,7 +159,7 @@ const List = () => { aids.map((d, i) => { return (
- + + certificatecertificate { const { addToast } = useToasts(); @@ -115,7 +104,6 @@ const Edit = ({ vendorId }) => { const loadBeneficiaryDetails = useCallback(async () => { const d = await getVendorDetails(vendorId); - console.log('data vendor', d); const { projects, name, @@ -169,18 +157,12 @@ const Edit = ({ vendorId }) => { loadBeneficiaryDetails(); }, [loadBeneficiaryDetails, loadProjects]); - console.log('data', formData); - return (
setPasscodeModal(e)}>

Vendor

- - - Vendor - - Edit - + +
diff --git a/src/modules/vendor/list.js b/src/modules/vendor/list.js index 129ff8e6..259815f9 100644 --- a/src/modules/vendor/list.js +++ b/src/modules/vendor/list.js @@ -135,6 +135,7 @@ const Vendor = () => {
S.N. Name Location Project Manager
{i + 1} {d.name}KavreShrawan Khadka{d.location || '-'} + {d.project_manager ? `${d.project_manager.name.first} ${d.project_manager.name.last}` : '-'} + {moment(d.created_at).format('MMM Do YYYY')} {d.status.toUpperCase()} From c49ab9ac54f2152e1f3caab723b7ba3d640176b4 Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Fri, 20 Aug 2021 07:55:24 +0545 Subject: [PATCH 33/59] print single beneficiary --- src/modules/beneficary/detail/detail.js | 77 ++++++++++++++++++++++--- src/modules/global/DetailsCard.js | 13 ++++- src/utils/printSingleBeneficiary.js | 34 +++++++++++ 3 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 src/utils/printSingleBeneficiary.js diff --git a/src/modules/beneficary/detail/detail.js b/src/modules/beneficary/detail/detail.js index 51c12bf0..430ae8ef 100644 --- a/src/modules/beneficary/detail/detail.js +++ b/src/modules/beneficary/detail/detail.js @@ -1,6 +1,7 @@ import React, { useContext, useCallback, useEffect, useState } from 'react'; import { Row, Col, FormGroup, Label, InputGroup, Input } from 'reactstrap'; import { useToasts } from 'react-toast-notifications'; +import QRCode from 'qrcode'; import Balance from '../../ui_components/balance'; import DetailsCard from '../../global/DetailsCard'; @@ -15,6 +16,7 @@ import { TOAST } from '../../../constants'; import { AppContext } from '../../../contexts/AppSettingsContext'; import { BeneficiaryContext } from '../../../contexts/BeneficiaryContext'; import { History } from '../../../utils/History'; +import { htmlResponse } from '../../../utils/printSingleBeneficiary'; const BenefDetails = ({ params }) => { const { id } = params; @@ -31,14 +33,19 @@ const BenefDetails = ({ params }) => { const [currentBalance, setCurrentBalance] = useState(''); const [inputTokens, setInputTokens] = useState(''); const [projectOptions, setProjectOptions] = useState([]); + const [assignTokenAmount, setAssignTokenAmount] = useState(''); const [fetching, setFetching] = useState(false); const [passcodeModal, setPasscodeModal] = useState(false); const [projectModal, setProjectModal] = useState(false); + const [assignTokenModal, setAssignTokenModal] = useState(false); + const [availableBalance, setAvailableBalance] = useState(''); const [showAlert, setShowAlert] = useState(false); const [selectedProject, setSelectedProject] = useState(''); + const toggleAssignTokenModal = () => setAssignTokenModal(!assignTokenModal); + const toggleProjectModal = () => { // If opening modal, reset fields if (!projectModal) { @@ -55,6 +62,30 @@ const BenefDetails = ({ params }) => { setPasscodeModal(!passcodeModal); }, [passcodeModal]); + const handleAssignTokenChange = e => setAssignTokenAmount(e.target.value); + + const handleTokenInputSubmit = e => { + e.preventDefault(); + const { name, address, govt_id, phone } = basicInfo; + const payload = { name, address, govt_id, phone }; + generateQrAndPrint(payload); + }; + + const generateQrAndPrint = async payload => { + toggleAssignTokenModal(); + const imgUrl = await QRCode.toDataURL(`phone:+977${payload.phone}?amount=${assignTokenAmount || null}`); + const html = await htmlResponse(payload, imgUrl); + setAssignTokenAmount(''); + let newWindow = window.open('', 'Print QR', 'fullscreen=yes'), + document = newWindow.document.open(); + document.write(html); + document.close(); + setTimeout(function () { + newWindow.print(); + newWindow.close(); + }, 250); + }; + const handleProjectChange = async d => { try { setSelectedProject(d.value); @@ -110,12 +141,17 @@ const BenefDetails = ({ params }) => { const fetchCurrentBalance = useCallback( async phone => { - const parsed_phone = parseInt(phone); - const { rahat } = appSettings.agency.contracts; - setFetching(true); - const balance = await getBeneficiaryBalance(parsed_phone, rahat); - setCurrentBalance(balance); - setFetching(false); + try { + const parsed_phone = parseInt(phone); + const { rahat } = appSettings.agency.contracts; + setFetching(true); + const balance = await getBeneficiaryBalance(parsed_phone, rahat); + setCurrentBalance(balance); + setFetching(false); + } catch (err) { + setCurrentBalance('0'); + setFetching(false); + } }, [appSettings.agency.contracts, getBeneficiaryBalance] ); @@ -149,7 +185,13 @@ const BenefDetails = ({ params }) => { return ( <> - + { + {/* Assign token modal */} + + + + + + + +

Beneficiary

{i + 1}{(pagination.currentPage - 1) * pagination.limit + i + 1} {d.name} {d.location || '-'} diff --git a/src/modules/beneficary/list.js b/src/modules/beneficary/list.js index ba672dfb..e1e31d6f 100644 --- a/src/modules/beneficary/list.js +++ b/src/modules/beneficary/list.js @@ -175,6 +175,7 @@ const Beneficiary = () => { + @@ -184,9 +185,10 @@ const Beneficiary = () => { {list.length ? ( - list.map(d => { + list.map((d, i) => { return ( + @@ -227,10 +191,10 @@ const EditProject = ({ match }) => { /> )} - + {/* - + */} { axios - .get(`${API.PROJECTS}/${aidId}/beneficiary?${qs.stringify(query)}`, { + .get(`${API.PROJECTS}/${aidId}/vendors?${qs.stringify(query)}`, { headers: { access_token: access_token } }) .then(res => { From 0f968e90a921393c34ecf887187f7b48eaacdfb8 Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Tue, 24 Aug 2021 10:07:59 +0545 Subject: [PATCH 38/59] fetch vendor balance and cleanup --- src/contexts/VendorContext.js | 4 +- .../beneficary/detail/beneficiaryInfo.js | 10 +- src/modules/vendor/detail/detail.js | 61 ++++++++---- .../vendor/detail/transactionHistory.js | 96 +++++++++---------- src/modules/vendor/detail/vendorInfo.js | 17 +++- src/modules/vendor/edit/edit.js | 26 +---- src/modules/vendor/list.js | 2 + 7 files changed, 121 insertions(+), 95 deletions(-) diff --git a/src/contexts/VendorContext.js b/src/contexts/VendorContext.js index b9b5d2b5..98482cdf 100644 --- a/src/contexts/VendorContext.js +++ b/src/contexts/VendorContext.js @@ -21,9 +21,9 @@ export const VendorContextProvider = ({ children }) => { const [state, dispatch] = useReducer(vendorReduce, initialState); const { wallet, appSettings, changeIsverified } = useContext(AppContext); - async function getVendorBalance(contract_addr, wallet_address) { + const getVendorBalance = useCallback((contract_addr, wallet_address) => { return Service.getVendorBalance(contract_addr, wallet_address); - } + }, []); const listAid = useCallback(async () => { const d = await AidService.listAid({ limit: APP_CONSTANTS.FETCH_LIMIT }); diff --git a/src/modules/beneficary/detail/beneficiaryInfo.js b/src/modules/beneficary/detail/beneficiaryInfo.js index 5462d3cf..8e41c352 100644 --- a/src/modules/beneficary/detail/beneficiaryInfo.js +++ b/src/modules/beneficary/detail/beneficiaryInfo.js @@ -2,7 +2,7 @@ import React from 'react'; import { Card, CardTitle, Col, Row } from 'reactstrap'; import '../../../assets/css/project.css'; -import image from '../../../assets/images/ID.jpg'; +import IdImgPlaceholder from '../../../assets/images/id-icon-1.png'; import { formatWord } from '../../../utils'; import { History } from '../../../utils/History'; @@ -70,7 +70,13 @@ export default function BeneficiaryInfo({ basicInfo, extras }) {

{extras && extras.profession ? extras.profession : '-'}

Profession
- certificate + certificate diff --git a/src/modules/vendor/detail/detail.js b/src/modules/vendor/detail/detail.js index add02021..c53a46bd 100644 --- a/src/modules/vendor/detail/detail.js +++ b/src/modules/vendor/detail/detail.js @@ -1,33 +1,61 @@ import React, { useContext, useCallback, useEffect, useState } from 'react'; -import { Breadcrumb, BreadcrumbItem, Row, Col, Card, CardTitle } from 'reactstrap'; +import { Row, Col, Card, CardTitle } from 'reactstrap'; import VendorInfo from './vendorInfo'; import ProjectInvovled from '../../ui_components/projects'; import TransactionHistory from './transactionHistory'; import { VendorContext } from '../../../contexts/VendorContext'; +import { AppContext } from '../../../contexts/AppSettingsContext'; import displayPic from '../../../assets/images/users/user_avatar.svg'; +import Loading from '../../global/Loading'; +import BreadCrumb from '../../ui_components/breadcrumb'; const Index = ({ params }) => { const { id } = params; - const { getVendorDetails, getVendorTransactions } = useContext(VendorContext); + const { getVendorDetails, getVendorTransactions, getVendorBalance } = useContext(VendorContext); + const { appSettings } = useContext(AppContext); + const [basicInfo, setBasicInfo] = useState({}); const [projectList, setProjectList] = useState([]); const [transactionList, setTransactionList] = useState([]); + const [fetchingBlockchain, setFetchingBlockChain] = useState(false); + const [fetchingBalance, setFetchingBalance] = useState(false); + const [vendorBalance, setVendorBalance] = useState('0'); + + const fetchVendorBalance = useCallback( + async wallet_address => { + setFetchingBalance(true); + const { token } = appSettings.agency.contracts; + const balance = await getVendorBalance(token, wallet_address); + setVendorBalance(balance); + setFetchingBalance(false); + }, + [appSettings, getVendorBalance] + ); + const fetchVendorDetails = useCallback(async () => { const details = await getVendorDetails(id); - if (details) setBasicInfo(details); + if (details) { + setBasicInfo(details); + await fetchVendorBalance(details.wallet_address); + } if (details.projects && details.projects.length) { const projects = details.projects.map(d => { return { id: d._id, name: d.name }; }); setProjectList(projects); } - }, [getVendorDetails, id]); + }, [fetchVendorBalance, getVendorDetails, id]); const fetchVendorTransactions = useCallback(async () => { - const transactions = await getVendorTransactions(id); - console.log('transaction alsdjflkajsdlfj', transactions); - if (transactions) setTransactionList(transactions); + try { + setFetchingBlockChain(true); + const transactions = await getVendorTransactions(id); + if (transactions) setTransactionList(transactions); + setFetchingBlockChain(false); + } catch (err) { + setFetchingBlockChain(false); + } }, [getVendorTransactions, id]); useEffect(() => { @@ -35,20 +63,14 @@ const Index = ({ params }) => { }, [fetchVendorDetails]); useEffect(() => { + console.log('VD Effect...'); fetchVendorTransactions(); }, [fetchVendorTransactions]); - console.log('BASIC==>', basicInfo); - return ( <>

Vendors

- - - Vendors - - Detail - +
@@ -86,11 +108,12 @@ const Index = ({ params }) => { Token -

Balance

-
Token Status
+ {fetchingBalance ? :

{vendorBalance}

} + +
Total Balance
-

50,000

+

0

Total Redeemed
@@ -101,7 +124,7 @@ const Index = ({ params }) => { - + ); }; diff --git a/src/modules/vendor/detail/transactionHistory.js b/src/modules/vendor/detail/transactionHistory.js index 2544aba2..f7e5c239 100644 --- a/src/modules/vendor/detail/transactionHistory.js +++ b/src/modules/vendor/detail/transactionHistory.js @@ -1,12 +1,11 @@ import React from 'react'; -import { Card, CardTitle, CustomInput, Table, Row, Col } from 'reactstrap'; -import Loading from '../../global/Loading'; +import { Card, CardTitle, Table, Row, Col } from 'reactstrap'; +import GrowSpinner from '../../global/GrowSpinner'; const EXPLORER_URL = process.env.REACT_APP_BLOCKCHAIN_EXPLORER; const TransactionHistory = props => { - const { transactions } = props; - console.log('tx list', transactions); + const { transactions, fetching } = props; return (
@@ -15,52 +14,53 @@ const TransactionHistory = props => {
Transaction History - - - - - - - + -
S.N. Name Phone Address
{(pagination.currentPage - 1) * pagination.limit + i + 1}
diff --git a/src/modules/institution/list.js b/src/modules/institution/list.js index 86af0072..6f4195cc 100644 --- a/src/modules/institution/list.js +++ b/src/modules/institution/list.js @@ -144,6 +144,7 @@ const Institution = () => { + @@ -154,6 +155,7 @@ const Institution = () => { {institution.length ? ( institution.map((e, i) => ( + +
-

Susma cold store

+

{information.shop_name || '-'}

Shop name
-

{information.email}

+

{information.email || '-'}

Email
-

Bachelors in computer science

-
Education
+

{information.wallet_address || '-'}

+
Wallet address
-

NIC Bank LTD

+

{information.bank_name || '-'}

Bank name
-

120975345678

+

{information.bank_account || '-'}

Bank account number
-

Female

+

{information.gender || '-'}

Gender
-

{information.phone}

+

{information.phone || '-'}

Phone number
-

{information.address}

+

{information.address || '-'}

Address
-

1043325

+

{information.pan_number || '-'}

PAN number
-

Kathmandu

+

{information.bank_branch || '-'}

Bank branch
- certificate - signature + certificate + {/* signature */} + {/* signature */} diff --git a/src/modules/vendor/edit/edit.js b/src/modules/vendor/edit/edit.js index 3b058412..2410f130 100644 --- a/src/modules/vendor/edit/edit.js +++ b/src/modules/vendor/edit/edit.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useContext } from 'react'; +import React, { useState, useEffect, useContext, useCallback } from 'react'; import { Breadcrumb, BreadcrumbItem, @@ -17,45 +17,85 @@ import { VendorContext } from '../../../contexts/VendorContext'; import { History } from '../../../utils/History'; import { TOAST } from '../../../constants'; import WalletUnlock from '../../../modules/global/walletUnlock'; +import AvatarIcon from '../../../assets/images/download.png'; +import { blobToBase64 } from '../../../utils'; +import SelectWrapper from '../../global/SelectWrapper'; -const Add = () => { +const Edit = ({ vendorId }) => { const { addToast } = useToasts(); - const { addVendor } = useContext(VendorContext); + const { listAid, updateVendor, getVendorDetails } = useContext(VendorContext); const [passcodeModal, setPasscodeModal] = useState(false); const [formData, setFormData] = useState({ name: '', - // shop_name: '', + shop_name: '', phone: '', email: '', address: '', - // education: '', - // pan_number: '', - governmentID: '', + govt_id: '', + pan_number: '', wallet_address: '', bank_branch: '', bank_name: '', - account_number: '' + bank_account: '' + }); + const [extras, setExtras] = useState({ + identity_photo: '', + signature_photo: '', + mou_file: '' }); const [selectedGender, setSelectedGender] = useState(''); + const [selectedProjects, setSelectedProjects] = useState(''); + const [projectList, setProjectList] = useState([]); + const [profileUpload, setProfileUpload] = useState(''); + const [existingProjects, setExistingProjects] = useState([]); const handleInputChange = e => { setFormData({ ...formData, [e.target.name]: e.target.value }); }; + const handleCancelClick = () => History.push('/vendors'); const handleGenderChange = e => { setSelectedGender(e.target.value); }; + const handleProjectChange = data => { + const values = data.map(d => d.value); + setSelectedProjects(values.toString()); + }; + + async function handleProfileUpload(e) { + const convertImage = await blobToBase64(e.target.files[0]); + setProfileUpload(convertImage); + } + + async function handleIdentityUpload(e) { + const convertImage = await blobToBase64(e.target.files[0]); + setExtras({ ...extras, identity_photo: convertImage }); + } + + async function handleSignatureUpload(e) { + const convertImage = await blobToBase64(e.target.files[0]); + setExtras({ ...extras, signature_photo: convertImage }); + } + + async function handleMouUpload(e) { + const convertImage = await blobToBase64(e.target.files[0]); + setExtras({ ...extras, mou_file: convertImage }); + } + const handleFormSubmit = e => { e.preventDefault(); - const payload = { ...formData }; + if (!selectedProjects.length) return addToast('Please select project', TOAST.ERROR); + + const payload = { ...formData, extra_files: { ...extras } }; + payload.projects = selectedProjects; + if (profileUpload) payload.photo = profileUpload; if (selectedGender) payload.gender = selectedGender; - console.log('vendor', payload); - addVendor(payload) + updateVendor(vendorId, payload) .then(() => { - addToast('Vendor added successfully', TOAST.SUCCESS); + addToast('Vendor updated successfully', TOAST.SUCCESS); History.push('/vendors'); }) .catch(err => { @@ -63,11 +103,73 @@ const Add = () => { }); }; - const handleCancelClick = () => History.push('/users'); + const createProjectSelectOptions = projects => { + const select_options = projects.map(p => { + return { + label: p.name, + value: p._id + }; + }); + return select_options; + }; + + const loadBeneficiaryDetails = useCallback(async () => { + const d = await getVendorDetails(vendorId); + console.log('data vendor', d); + const { + projects, + name, + phone, + email, + address, + wallet_address, + shop_name, + pan_number, + bank_name, + bank_branch, + bank_account, + photo, + govt_id + } = d; + if (projects && projects.length) { + const project_ids = projects.map(p => p._id); + setSelectedProjects(project_ids.toString()); + const select_options = createProjectSelectOptions(projects); + setExistingProjects(select_options); + } - const saveUserDetails = () => {}; + setFormData({ + name, + phone, + email, + address, + wallet_address, + shop_name, + pan_number, + bank_name, + bank_branch, + bank_account, + photo, + govt_id + }); + const { gender } = d; + if (gender !== 'U') setSelectedGender(gender); + }, [vendorId, getVendorDetails]); - useEffect(saveUserDetails); + const loadProjects = useCallback(async () => { + const projects = await listAid(); + if (projects && projects.data.length) { + const select_options = createProjectSelectOptions(projects.data); + setProjectList(select_options); + } + }, [listAid]); + + useEffect(() => { + loadProjects(); + loadBeneficiaryDetails(); + }, [loadBeneficiaryDetails, loadProjects]); + + console.log('data', formData); return (
@@ -84,9 +186,69 @@ const Add = () => { + +
+ + +
+ {profileUpload ? ( + Profile + ) : ( + Profile + )} + +
+ + + + + + + + + + + + +
+ +
+ + - - + + {existingProjects.length > 0 && ( + + )} + + {existingProjects.length < 1 && ( + + )} @@ -98,21 +260,6 @@ const Add = () => { required /> - - - - - - -
- -
@@ -121,11 +268,12 @@ const Add = () => { - + - - - + + + + {/* */} @@ -140,11 +288,11 @@ const Add = () => { - + @@ -154,7 +302,14 @@ const Add = () => {
- +
@@ -164,23 +319,104 @@ const Add = () => {
- +

- +
- - +
- +
+ + + + +
+ {extras.identity_photo ? ( + Profile + ) : ( + Profile + )} + +
+ + + + +
+ {extras.signature_photo ? ( + Profile + ) : ( + Profile + )} + +
+ + + + +
+ {extras.mou_file ? ( + Profile + ) : ( + Profile + )} + +
+ + {/* {loading ? ( @@ -210,4 +446,4 @@ const Add = () => { ); }; -export default Add; +export default Edit; diff --git a/src/modules/vendor/edit/index.js b/src/modules/vendor/edit/index.js index 85ab3470..bcff9c77 100644 --- a/src/modules/vendor/edit/index.js +++ b/src/modules/vendor/edit/index.js @@ -3,11 +3,12 @@ import React from 'react'; import { VendorContextProvider } from '../../../contexts/VendorContext'; import EditVendor from './edit'; -export default function Index() { +export default function Index({ match }) { + const { id } = match.params; return ( <> - + ); diff --git a/src/modules/vendor/list.js b/src/modules/vendor/list.js index 8cf0fb65..129ff8e6 100644 --- a/src/modules/vendor/list.js +++ b/src/modules/vendor/list.js @@ -2,6 +2,7 @@ import React, { useState, useEffect, useContext } from 'react'; import { VendorContext } from '../../contexts/VendorContext'; import { useToasts } from 'react-toast-notifications'; import { Link } from 'react-router-dom'; +import { History } from '../../utils/History'; import { Card, @@ -85,6 +86,7 @@ const Vendor = () => { let _start = (current_page - 1) * pagination.limit; return fetchList({ start: _start, limit: pagination.limit }); }; + const handleAddClick = () => History.push('/add-vendor'); return (
@@ -122,7 +124,7 @@ const Vendor = () => {
-
@@ -157,8 +159,8 @@ const Vendor = () => { diff --git a/src/services/vendor.js b/src/services/vendor.js index d464dbc4..ae4b9f9d 100644 --- a/src/services/vendor.js +++ b/src/services/vendor.js @@ -1,134 +1,146 @@ -import axios from "axios"; +import axios from 'axios'; -import API from "../constants/api"; -import { getUserToken } from "../utils/sessionManager"; -import CONTRACT from "../constants/contracts"; -import { getContractByProvider } from "../blockchain/abi"; +import API from '../constants/api'; +import { getUserToken } from '../utils/sessionManager'; +import CONTRACT from '../constants/contracts'; +import { getContractByProvider } from '../blockchain/abi'; const access_token = getUserToken(); const faucet_auth_token = process.env.REACT_APP_BLOCKCHAIN_FAUCET_AUTH_TOKEN; -const mapTestContract = (contract) => ({ - addVendor: contract.addVendor, - balanceOf: contract.balanceOf, +const mapTestContract = contract => ({ + addVendor: contract.addVendor, + balanceOf: contract.balanceOf }); export async function getVendorBalance(contract_address, wallet_addr) { - const contract = await getContractByProvider(contract_address, CONTRACT.AIDTOKEN); - const myContract = mapTestContract(contract); - const data = await myContract.balanceOf(wallet_addr); - if (!data) return "Vendor not found!"; - return data.toNumber(); + const contract = await getContractByProvider(contract_address, CONTRACT.AIDTOKEN); + const myContract = mapTestContract(contract); + const data = await myContract.balanceOf(wallet_addr); + if (!data) return 'Vendor not found!'; + return data.toNumber(); } -export async function approveVendor(wallet, payload,contract_address) { - try{ - const contract = await getContractByProvider(contract_address, CONTRACT.RAHAT); - const signerContract = contract.connect(wallet); - const myContract = mapTestContract(signerContract); - const data = await myContract.addVendor(payload.wallet_address); - if (!data) return "Vendor approve failed!"; - const res = await changeVendorStaus(payload.vendorId, payload.status); - getEth({address:payload.wallet_address}); - return res; - } - catch(e){ - console.log(e); - throw Error(e); - } +export async function approveVendor(wallet, payload, contract_address) { + try { + const contract = await getContractByProvider(contract_address, CONTRACT.RAHAT); + const signerContract = contract.connect(wallet); + const myContract = mapTestContract(signerContract); + const data = await myContract.addVendor(payload.wallet_address); + if (!data) return 'Vendor approve failed!'; + const res = await changeVendorStaus(payload.vendorId, payload.status); + getEth({ address: payload.wallet_address }); + return res; + } catch (e) { + console.log(e); + throw Error(e); + } } export async function changeVendorStaus(vendorId, status) { - return axios.patch( - `${API.VENDORS}/${vendorId}/status/`, - { status: status }, - { - headers: { access_token: access_token }, - } - ); + return axios.patch( + `${API.VENDORS}/${vendorId}/status/`, + { status: status }, + { + headers: { access_token: access_token } + } + ); } export async function list(params) { - const res = await axios({ - url: API.VENDORS, - method: "get", - headers: { - access_token, - }, - params, - }); - return res.data; + const res = await axios({ + url: API.VENDORS, + method: 'get', + headers: { + access_token + }, + params + }); + return res.data; } export async function get(id) { - const res = await axios({ - url: API.VENDORS + "/" + id, - method: "get", - headers: { - access_token, - }, - }); - return res.data; + const res = await axios({ + url: API.VENDORS + '/' + id, + method: 'get', + headers: { + access_token + } + }); + return res.data; } export async function vendorTransactions(vendorId) { - const res = await axios({ - url: `${API.VENDORS}/${vendorId}/transactions`, - method: "get", - headers: { access_token }, - }); - return res.data; + const res = await axios({ + url: `${API.VENDORS}/${vendorId}/transactions`, + method: 'get', + headers: { access_token } + }); + return res.data; } export async function listByAid(aid, params) { - const res = await axios({ - url: API.VENDORS + `/aid/${aid}/vendor`, - method: "get", - headers: { - access_token, - }, - params, - }); - return res.data; + const res = await axios({ + url: API.VENDORS + `/aid/${aid}/vendor`, + method: 'get', + headers: { + access_token + }, + params + }); + return res.data; } export function add(payload) { - return new Promise((resolve, reject) => { - axios - .post(`${API.VENDORS}`, payload, { - headers: { access_token: access_token }, - }) - .then((res) => { - if (res.statusText === "OK") { - resolve(res.data); - } - reject(res.data); - }) - .catch((err) => { - reject(err); - }); - }); + return new Promise((resolve, reject) => { + axios + .post(`${API.VENDORS}`, payload, { + headers: { access_token: access_token } + }) + .then(res => { + if (res.statusText === 'OK') { + resolve(res.data); + } + reject(res.data); + }) + .catch(err => { + reject(err); + }); + }); +} + +export async function updateVendor(id, body) { + const res = await axios({ + url: `${API.VENDORS}/${id}`, + method: 'put', + headers: { + access_token + }, + data: body + }); + + return res.data; } export async function approve({ vendorId }) { - const res = await axios({ - url: API.VENDORS + `/approve`, - method: "post", - headers: { - access_token, - }, - data: { vendorId }, - }); - - return res.data; + const res = await axios({ + url: API.VENDORS + `/approve`, + method: 'post', + headers: { + access_token + }, + data: { vendorId } + }); + + return res.data; } export async function getEth({ address }) { - const res = await axios({ - url: API.FAUCET, - method: "post", - data: { address,token:faucet_auth_token }, - }); + const res = await axios({ + url: API.FAUCET, + method: 'post', + data: { address, token: faucet_auth_token } + }); - return res.data; + return res.data; } From 5104aa48107306bf4dafddd4d106f2b7c26d9758 Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Tue, 24 Aug 2021 09:00:51 +0545 Subject: [PATCH 37/59] list vendors by project --- src/contexts/AidContext.js | 69 ++++++++++-------------- src/modules/aid/detail/tab/index.js | 3 +- src/modules/aid/detail/tab/vendorList.js | 42 ++++++++------- src/modules/aid/edit/index.js | 46 ++-------------- src/routes/Router.js | 27 +++++----- src/services/aid.js | 2 +- 6 files changed, 74 insertions(+), 115 deletions(-) diff --git a/src/contexts/AidContext.js b/src/contexts/AidContext.js index f5d9e5f0..622d9881 100644 --- a/src/contexts/AidContext.js +++ b/src/contexts/AidContext.js @@ -16,6 +16,14 @@ const initialState = { currentPage: 1, totalPages: 0 }, + vendors_list: [], + vendor_pagination: { + total: 0, + limit: 10, + start: 0, + currentPage: 1, + totalPages: 0 + }, total_tokens: 0, available_tokens: 0, aid_details: null, @@ -23,9 +31,10 @@ const initialState = { }; export const AidContext = createContext(initialState); + export const AidContextProvider = ({ children }) => { const [state, dispatch] = useReducer(aidReduce, initialState); - const { appSettings, changeIsverified } = useContext(AppContext); + const { changeIsverified } = useContext(AppContext); function getAidDetails(projectId) { return Service.getAidDetails(projectId); @@ -47,30 +56,12 @@ export const AidContextProvider = ({ children }) => { [changeIsverified] ); - // async function addProjectBudget(aidId, supplyToken) { - // // const wallet = await Wallet.loadWallet('123123'); - // const { rahat: rahatContractAddr, rahat_admin } = appSettings.agency.contracts; - // let d = await Service.addProjectBudget(wallet, aidId, supplyToken, rahat_admin); - // changeIsverified(false); - // let balance = await Service.loadAidBalance(aidId, rahatContractAddr); - // if (balance) { - // dispatch({ type: ACTION.GET_BALANCE, res: balance }); - // return d; - // } - // } - async function changeProjectStatus(aidId, status) { let res = await Service.changeProjectStatus(aidId, status); dispatch({ type: ACTION.GET_AID_SUCCESS, res }); return res; } - // async function getProjectCapital(aidId, contract_addr) { - // let res = await Service.getProjectCapital(aidId, contract_addr); - // dispatch({ type: ACTION.SET_TOTAL_TOKENS, res }); - // return res; - // } - const getProjectCapital = useCallback(async (aidId, rahat_admin_contract) => { let res = await Service.getProjectCapital(aidId, rahat_admin_contract); dispatch({ type: ACTION.SET_TOTAL_TOKENS, res }); @@ -86,16 +77,6 @@ export const AidContextProvider = ({ children }) => { return _available; }, []); - // async function getAidBalance(aidId) { - // const { rahat } = appSettings.agency.contracts; - // let _available = await Service.loadAidBalance(aidId, rahat); - // dispatch({ - // type: ACTION.SET_AVAILABLE_TOKENS, - // res: _available - // }); - // return _available; - // } - function setLoading() { dispatch({ type: ACTION.SET_LOADING }); } @@ -104,18 +85,24 @@ export const AidContextProvider = ({ children }) => { dispatch({ type: ACTION.RESET_LOADING }); } - function vendorsByAid(aidId, params) { - return new Promise((resolve, reject) => { - Service.vendorsByAid(aidId, params) - .then(res => { - dispatch({ type: ACTION.VENDORS_LIST_SUCCESS, res }); - resolve(res); - }) - .catch(err => { - reject(err); - }); - }); - } + const vendorsByAid = useCallback(async (aidId, params) => { + const res = await Service.vendorsByAid(aidId, params); + dispatch({ type: ACTION.VENDORS_LIST_SUCCESS, res }); + return res; + }, []); + + // function vendorsByAid(aidId, params) { + // return new Promise((resolve, reject) => { + // Service.vendorsByAid(aidId, params) + // .then(res => { + // dispatch({ type: ACTION.VENDORS_LIST_SUCCESS, res }); + // resolve(res); + // }) + // .catch(err => { + // reject(err); + // }); + // }); + // } const beneficiaryByAid = useCallback((aidId, params) => { return new Promise((resolve, reject) => { diff --git a/src/modules/aid/detail/tab/index.js b/src/modules/aid/detail/tab/index.js index 43c4c70d..14f9f642 100644 --- a/src/modules/aid/detail/tab/index.js +++ b/src/modules/aid/detail/tab/index.js @@ -20,7 +20,6 @@ const Tabs = ({ projectId }) => { }, [beneficiaryByAid, projectId]); useEffect(() => { - console.log('Effect...'); fetchData(); }, [fetchData]); @@ -61,7 +60,7 @@ const Tabs = ({ projectId }) => { - + diff --git a/src/modules/aid/detail/tab/vendorList.js b/src/modules/aid/detail/tab/vendorList.js index 4c03d20d..3b297a56 100644 --- a/src/modules/aid/detail/tab/vendorList.js +++ b/src/modules/aid/detail/tab/vendorList.js @@ -1,17 +1,23 @@ -import React, { useContext } from 'react'; +import React, { useContext, useEffect, useCallback } from 'react'; import { Pagination, PaginationItem, PaginationLink, Table } from 'reactstrap'; import { AidContext } from '../../../../contexts/AidContext'; -const List = () => { - const { pagination } = useContext(AidContext); +const List = ({ projectId }) => { + const { vendor_pagination, vendors_list, vendorsByAid } = useContext(AidContext); const handlePagination = current_page => { - let _start = (current_page - 1) * pagination.limit; + let _start = (current_page - 1) * vendor_pagination.limit; console.log({ _start }); }; - const vendors = []; + const fetchVendorsByAId = useCallback(async () => { + await vendorsByAid(projectId); + }, [projectId, vendorsByAid]); + + useEffect(() => { + fetchVendorsByAId(); + }, [fetchVendorsByAId]); return ( <> @@ -21,20 +27,20 @@ const List = () => { - - + + - {vendors.length ? ( - vendors.map(d => { + {vendors_list.length > 0 ? ( + vendors_list.map(d => { return ( - - - - - + + + + + ); }) @@ -47,7 +53,7 @@ const List = () => {
S.N. Name Phone Address
{(pagination.currentPage - 1) * pagination.limit + i + 1}
{e.name}
diff --git a/src/modules/mobilizer/list.js b/src/modules/mobilizer/list.js index e733307f..4989cdcd 100644 --- a/src/modules/mobilizer/list.js +++ b/src/modules/mobilizer/list.js @@ -40,9 +40,9 @@ const Mobilizer = () => { const toggle = () => setModel(!model); const fetchList = query => { - console.log("LSIISHD") + console.log('LSIISHD'); let params = { ...pagination, ...query }; - console.log({params}); + console.log({ params }); listMobilizer(params) .then() .catch(() => { @@ -145,6 +145,7 @@ const Mobilizer = () => { {list.length ? ( list.map((e, i) => (
{(pagination.currentPage - 1) * pagination.limit + i + 1}
@@ -289,7 +290,7 @@ const Mobilizer = () => {
-
+
diff --git a/src/modules/user/list/index.js b/src/modules/user/list/index.js index 8b75ee72..2f18e664 100644 --- a/src/modules/user/list/index.js +++ b/src/modules/user/list/index.js @@ -77,6 +77,7 @@ const List = () => { + @@ -86,9 +87,10 @@ const List = () => { {users.length ? ( - users.map(d => { + users.map((d, i) => { return ( + From e6dec5d60617188cc1814a49b0a99d670dc39e0e Mon Sep 17 00:00:00 2001 From: rojanbade Date: Mon, 23 Aug 2021 17:19:05 +0545 Subject: [PATCH 36/59] Api integration --- src/assets/images/download.png | Bin 0 -> 6653 bytes src/contexts/VendorContext.js | 19 +- src/modules/vendor/add/add.js | 253 +++++++++++--- src/modules/vendor/detail/detail.js | 37 +- .../vendor/detail/transactionHistory.js | 39 ++- src/modules/vendor/detail/vendorInfo.js | 60 +++- src/modules/vendor/edit/edit.js | 330 +++++++++++++++--- src/modules/vendor/edit/index.js | 5 +- src/modules/vendor/list.js | 8 +- src/services/vendor.js | 208 +++++------ 10 files changed, 716 insertions(+), 243 deletions(-) create mode 100644 src/assets/images/download.png diff --git a/src/assets/images/download.png b/src/assets/images/download.png new file mode 100644 index 0000000000000000000000000000000000000000..b33f0641666143d0d7413333fbc3a3c7203fd80a GIT binary patch literal 6653 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 E4>ys__5c6? literal 0 HcmV?d00001 diff --git a/src/contexts/VendorContext.js b/src/contexts/VendorContext.js index 8ca4b128..b9b5d2b5 100644 --- a/src/contexts/VendorContext.js +++ b/src/contexts/VendorContext.js @@ -4,6 +4,7 @@ import * as Service from '../services/vendor'; import * as AidService from '../services/aid'; import ACTION from '../actions/vendor'; import { AppContext } from './AppSettingsContext'; +import { APP_CONSTANTS } from '../constants'; const initialState = { list: [], @@ -24,10 +25,11 @@ export const VendorContextProvider = ({ children }) => { return Service.getVendorBalance(contract_addr, wallet_address); } - async function listAid() { - const d = await AidService.listAid({ start: 0, limit: 20 }); + const listAid = useCallback(async () => { + const d = await AidService.listAid({ limit: APP_CONSTANTS.FETCH_LIMIT }); dispatch({ type: ACTION.LIST_AID, data: { aids: d.data } }); - } + return d; + }, []); function setAid(aid) { dispatch({ type: ACTION.SET_AID, data: aid }); @@ -95,6 +97,10 @@ export const VendorContextProvider = ({ children }) => { }); } + const updateVendor = (id, payload) => { + return Service.updateVendor(id, payload); + }; + function setLoading() { dispatch({ type: ACTION.SET_LOADING }); } @@ -114,8 +120,8 @@ export const VendorContextProvider = ({ children }) => { } } - async function getVendorTransactions(vendorId) { - let res = await Service.vendorTransactions(vendorId); + const getVendorTransactions = useCallback(async id => { + let res = await Service.vendorTransactions(id); if (res) { dispatch({ type: ACTION.VENDOR_TX, @@ -123,7 +129,7 @@ export const VendorContextProvider = ({ children }) => { }); return res; } - } + }, []); return ( { setAid, clear, addVendor, + updateVendor, setVendor, setLoading, resetLoading, diff --git a/src/modules/vendor/add/add.js b/src/modules/vendor/add/add.js index a9e69dc0..235a9b40 100644 --- a/src/modules/vendor/add/add.js +++ b/src/modules/vendor/add/add.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useContext } from 'react'; +import React, { useState, useEffect, useContext, useCallback } from 'react'; import { Breadcrumb, BreadcrumbItem, @@ -17,28 +17,36 @@ import { VendorContext } from '../../../contexts/VendorContext'; import { History } from '../../../utils/History'; import { TOAST } from '../../../constants'; import WalletUnlock from '../../../modules/global/walletUnlock'; +import SelectWrapper from '../../global/SelectWrapper'; +import { blobToBase64 } from '../../../utils'; +import AvatarIcon from '../../../assets/images/download.png'; const Add = () => { const { addToast } = useToasts(); - const { addVendor } = useContext(VendorContext); - + const { listAid, addVendor } = useContext(VendorContext); const [passcodeModal, setPasscodeModal] = useState(false); - const [formData, setFormData] = useState({ name: '', - // shop_name: '', + shop_name: '', phone: '', email: '', address: '', - // education: '', - // pan_number: '', - governmentID: '', + govt_id: '', + pan_number: '', wallet_address: '', bank_branch: '', bank_name: '', - account_number: '' + bank_account: '' + }); + const [extras, setExtras] = useState({ + identity_photo: '', + signature_photo: '', + mou_file: '' }); const [selectedGender, setSelectedGender] = useState(''); + const [selectedProjects, setSelectedProjects] = useState(''); + const [projectList, setProjectList] = useState([]); + const [profileUpload, setProfileUpload] = useState(''); const handleInputChange = e => { setFormData({ ...formData, [e.target.name]: e.target.value }); @@ -48,11 +56,38 @@ const Add = () => { setSelectedGender(e.target.value); }; + const handleProjectChange = data => { + const values = data.map(d => d.value); + setSelectedProjects(values.toString()); + }; + + async function handleProfileUpload(e) { + const convertImage = await blobToBase64(e.target.files[0]); + setProfileUpload(convertImage); + } + + async function handleIdentityUpload(e) { + const convertImage = await blobToBase64(e.target.files[0]); + setExtras({ ...extras, identity_photo: convertImage }); + } + + async function handleSignatureUpload(e) { + const convertImage = await blobToBase64(e.target.files[0]); + setExtras({ ...extras, signature_photo: convertImage }); + } + + async function handleMouUpload(e) { + const convertImage = await blobToBase64(e.target.files[0]); + setExtras({ ...extras, mou_file: convertImage }); + } const handleFormSubmit = e => { e.preventDefault(); - const payload = { ...formData }; + if (!selectedProjects.length) return addToast('Please select project', TOAST.ERROR); + + const payload = { ...formData, extra_files: { ...extras } }; + payload.projects = selectedProjects; + if (profileUpload) payload.photo = profileUpload; if (selectedGender) payload.gender = selectedGender; - console.log('vendor', payload); addVendor(payload) .then(() => { addToast('Vendor added successfully', TOAST.SUCCESS); @@ -65,9 +100,24 @@ const Add = () => { const handleCancelClick = () => History.push('/users'); - const saveUserDetails = () => {}; + const loadProjects = useCallback(async () => { + const projects = await listAid(); + if (projects && projects.data.length) { + const select_options = projects.data.map(p => { + return { + label: p.name, + value: p._id + }; + }); + setProjectList(select_options); + } + }, [listAid]); + + useEffect(() => { + loadProjects(); + }, [loadProjects]); - useEffect(saveUserDetails); + // console.log('project list', projects); return (
@@ -84,9 +134,56 @@ const Add = () => {
+ +
+ + +
+ {profileUpload ? ( + Profile + ) : ( + Profile + )} + +
+ + + + + + + + + + + + +
+ +
+ + - - + + @@ -98,21 +195,6 @@ const Add = () => { required /> - - - - - - -
- -
@@ -123,9 +205,10 @@ const Add = () => { - - - + + + + {/* */} @@ -140,11 +223,11 @@ const Add = () => { - + @@ -154,7 +237,13 @@ const Add = () => {
- +
@@ -164,23 +253,101 @@ const Add = () => {
- +

- +
- - +
- +
+ + + + +
+ {extras.identity_photo ? ( + Profile + ) : ( + Profile + )} + +
+ + + + +
+ {extras.signature_photo ? ( + Profile + ) : ( + Profile + )} + +
+ + + + +
+ {extras.mou_file ? ( + Profile + ) : ( + Profile + )} + +
+ + {/* {loading ? ( diff --git a/src/modules/vendor/detail/detail.js b/src/modules/vendor/detail/detail.js index e6b37f93..add02021 100644 --- a/src/modules/vendor/detail/detail.js +++ b/src/modules/vendor/detail/detail.js @@ -1,32 +1,43 @@ import React, { useContext, useCallback, useEffect, useState } from 'react'; import { Breadcrumb, BreadcrumbItem, Row, Col, Card, CardTitle } from 'reactstrap'; -// import TotalCard from '../../totalCard'; -import Balance from '../../ui_components/balance'; import VendorInfo from './vendorInfo'; import ProjectInvovled from '../../ui_components/projects'; import TransactionHistory from './transactionHistory'; import { VendorContext } from '../../../contexts/VendorContext'; import displayPic from '../../../assets/images/users/user_avatar.svg'; -const projects = [ - { id: '0', name: 'Sindhupalchowk relief' }, - { id: '1', name: 'Flood relief distribution' } -]; const Index = ({ params }) => { const { id } = params; - const { getVendorDetails } = useContext(VendorContext); + const { getVendorDetails, getVendorTransactions } = useContext(VendorContext); const [basicInfo, setBasicInfo] = useState({}); + const [projectList, setProjectList] = useState([]); + const [transactionList, setTransactionList] = useState([]); const fetchVendorDetails = useCallback(async () => { const details = await getVendorDetails(id); if (details) setBasicInfo(details); + if (details.projects && details.projects.length) { + const projects = details.projects.map(d => { + return { id: d._id, name: d.name }; + }); + setProjectList(projects); + } }, [getVendorDetails, id]); + const fetchVendorTransactions = useCallback(async () => { + const transactions = await getVendorTransactions(id); + console.log('transaction alsdjflkajsdlfj', transactions); + if (transactions) setTransactionList(transactions); + }, [getVendorTransactions, id]); + useEffect(() => { - console.log('EFFECT!'); fetchVendorDetails(); }, [fetchVendorDetails]); + useEffect(() => { + fetchVendorTransactions(); + }, [fetchVendorTransactions]); + console.log('BASIC==>', basicInfo); return ( @@ -47,16 +58,16 @@ const Index = ({ params }) => { - +
user
-

Susma shahi thakuri

+

{basicInfo.name}

Name
- + - + - - - - - - - - + {transactions && + transactions.map(tx => { + return ( + + + + + + + + + ); + })}
S.N. Name Email Phone
{(pagination.current_page - 1) * pagination.limit + i + 1} {`${d.name.first} ${d.name.last}`} {d.email} {d.phone || '-'}
From ToDateBlock number Value Type TX
xxxxxxxxxxxx123xxxxxxxxxxxx45623-05-2020200,000Receivedxxxxxxxxxxx45
{tx.from}{tx.to}{tx.blockNumber}{tx.value}{tx.tag} + + Verify + +
- {/* */}
diff --git a/src/modules/vendor/detail/vendorInfo.js b/src/modules/vendor/detail/vendorInfo.js index 830da5a6..314b4ffe 100644 --- a/src/modules/vendor/detail/vendorInfo.js +++ b/src/modules/vendor/detail/vendorInfo.js @@ -2,15 +2,12 @@ import React from 'react'; import { Card, CardTitle, Col, Row } from 'reactstrap'; import '../../../assets/css/project.css'; import image from '../../../assets/images/ID.jpg'; -import sign from '../../../assets/images/sign.png'; import { History } from '../../../utils/History'; export default function VendorInfo(props) { const { information } = props; - - console.log('information', information); - const handleEditClick = () => History.push(`/edit-vendor/${information.id}`); - + const { id } = props.information; + const handleEditClick = () => History.push(`/edit-vendor/${id}`); return (
@@ -33,53 +30,80 @@ export default function VendorInfo(props) {
{e.phone} {e.address} - - Details + +
Name Address Phone numberBalanceTotal Token RedeemedEmailShop
XYZKavre986745321215000050000{d.name}{d.address || '-'}{d.phone}{d.email}{d.shop_name || '-'}
- {pagination.totalPages > 1 ? ( + {vendor_pagination.totalPages > 1 ? ( { handlePagination(1)} /> - {[...Array(pagination.totalPages)].map((p, i) => ( + {[...Array(vendor_pagination.totalPages)].map((p, i) => ( handlePagination(i + 1)} > {i + 1} ))} - handlePagination(pagination.totalPages)} /> + handlePagination(vendor_pagination.totalPages)} /> ) : ( diff --git a/src/modules/aid/edit/index.js b/src/modules/aid/edit/index.js index 28aaba59..43cf5b3f 100644 --- a/src/modules/aid/edit/index.js +++ b/src/modules/aid/edit/index.js @@ -1,18 +1,6 @@ import React, { useState, useEffect, useContext, useCallback } from 'react'; import { useToasts } from 'react-toast-notifications'; -import { - Breadcrumb, - BreadcrumbItem, - Card, - CardBody, - Row, - Col, - Form, - FormGroup, - Label, - Input, - Button -} from 'reactstrap'; +import { Card, CardBody, Row, Col, Form, FormGroup, Label, Input, Button } from 'reactstrap'; import { TOAST, APP_CONSTANTS, ROLES } from '../../../constants'; import { AidContext } from '../../../contexts/AidContext'; @@ -21,6 +9,7 @@ import { UserContext } from '../../../contexts/UserContext'; import { History } from '../../../utils/History'; import SelectWrapper from '../../global/SelectWrapper'; import GrowSpinner from '../../../modules/global/GrowSpinner'; +import BreadCrumb from '../../ui_components/breadcrumb'; const EditProject = ({ match }) => { const { id } = match.params; @@ -35,7 +24,6 @@ const EditProject = ({ match }) => { const [financialInstitutions, setFinancialInstitutions] = useState([]); const [projectManagers, setProjectManagers] = useState([]); - const [benefUploadFile, setBenefUploadFile] = useState(''); const [formData, setFormData] = useState({ name: '', @@ -49,10 +37,6 @@ const EditProject = ({ match }) => { setFormData({ ...formData, [e.target.name]: e.target.value }); }; - const handleFileChange = e => { - setBenefUploadFile(e.target.files[0]); - }; - const handleInstitutionChange = data => { const institution_values = data.map(d => d.value); setSelectedInstitutions(institution_values); @@ -65,8 +49,6 @@ const EditProject = ({ match }) => { const handleFormSubmit = e => { e.preventDefault(); if (!selectedManager) return addToast('Please select project manager', TOAST.ERROR); - console.log(benefUploadFile); - // const form_payload = createFormData(formData); if (selectedInstitutions.length) formData.financial_institutions = selectedInstitutions.toString(); formData.project_manager = selectedManager; setLoading(true); @@ -82,17 +64,6 @@ const EditProject = ({ match }) => { }); }; - // const createFormData = payload => { - // const form_data = new FormData(); - // for (let property in payload) { - // form_data.append(property, payload[property]); - // } - // if (selectedInstitutions.length) form_data.append('financial_institutions', selectedInstitutions.toString()); - // if (benefUploadFile) form_data.append('file', benefUploadFile); - // form_data.append('project_manager', selectedManager); - // return form_data; - // }; - const handleCancelClick = () => History.push('/projects'); const setProjectDetails = useCallback( @@ -152,17 +123,10 @@ const EditProject = ({ match }) => { setProjectManagers(populate_managers); }; - console.log('=======>', existingInstitutions); - return (

Projects

- - - Projects - - Edit - +
- - - - - - - - - - - - {transactions && - transactions.map(tx => { - return ( - - - - - - - - - ); - })} - -
FromToBlock numberValueTypeTX
{tx.from}{tx.to}{tx.blockNumber}{tx.value}{tx.tag} - - Verify - -
+ {fetching ? ( + + ) : ( + + + + + + + + + + + + + {transactions.length > 0 ? ( + transactions.map(tx => { + return ( + + + + + + + + + ); + }) + ) : ( + + + + )} + +
FromToBlock numberValueTypeTX
{tx.from}{tx.to}{tx.blockNumber}{tx.value}{tx.tag} + + Verify + +
No transaction available
+ )} diff --git a/src/modules/vendor/detail/vendorInfo.js b/src/modules/vendor/detail/vendorInfo.js index 314b4ffe..fc50f426 100644 --- a/src/modules/vendor/detail/vendorInfo.js +++ b/src/modules/vendor/detail/vendorInfo.js @@ -1,9 +1,11 @@ import React from 'react'; import { Card, CardTitle, Col, Row } from 'reactstrap'; import '../../../assets/css/project.css'; -import image from '../../../assets/images/ID.jpg'; +import image from '../../../assets/images/id-icon-1.png'; import { History } from '../../../utils/History'; +const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY; + export default function VendorInfo(props) { const { information } = props; const { id } = props.information; @@ -75,10 +77,21 @@ export default function VendorInfo(props) {
+ @@ -145,6 +146,7 @@ const Vendor = () => { {list.length ? ( list.map((e, i) => ( + - + {loading ? ( + + ) : vendorStatus === 'active' ? ( + + ) : ( + + )} diff --git a/src/views/vendors/detail/transactionHistory.js b/src/views/vendors/detail/transactionHistory.js index 6c4912fa..61a3ea00 100644 --- a/src/views/vendors/detail/transactionHistory.js +++ b/src/views/vendors/detail/transactionHistory.js @@ -1,5 +1,5 @@ import React from 'react'; -import { Card, CardBody, CardTitle, CustomInput, Table, Row, Col } from 'reactstrap'; +import { Card, CardTitle, CustomInput, Table, Row, Col } from 'reactstrap'; const TransactionHistory = () => { return ( diff --git a/src/views/vendors/detail/vendorInfo.js b/src/views/vendors/detail/vendorInfo.js index 2656d4b1..f084bba9 100644 --- a/src/views/vendors/detail/vendorInfo.js +++ b/src/views/vendors/detail/vendorInfo.js @@ -3,7 +3,6 @@ import { Card, CardTitle, Col, Row } from 'reactstrap'; import '../../project.css'; import image from '../../../assets/images/ID.jpg'; import sign from '../../../assets/images/sign.png'; -import MOU from '../../../assets/images/MOU.png'; export default function VendorInfo() { return ( From d5f378bebed491cf86a2b296e47ec9f4b56faab1 Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Wed, 25 Aug 2021 07:22:31 +0545 Subject: [PATCH 40/59] fix project pagination --- src/modules/aid/list/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/aid/list/index.js b/src/modules/aid/list/index.js index fffe4591..ade61b9a 100644 --- a/src/modules/aid/list/index.js +++ b/src/modules/aid/list/index.js @@ -67,7 +67,7 @@ const List = () => { const handlePagination = current_page => { let _start = (current_page - 1) * pagination.limit; - return loadAidList({ start: _start, limit: pagination.limit }); + return listAid({ start: _start, limit: pagination.limit }); }; const loadAidList = () => { From 4fd6636ca018967d8b56d8f219c967977dbcb448 Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Wed, 25 Aug 2021 07:24:35 +0545 Subject: [PATCH 41/59] Revert "fix project pagination" This reverts commit d5f378bebed491cf86a2b296e47ec9f4b56faab1. --- src/modules/aid/list/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/aid/list/index.js b/src/modules/aid/list/index.js index ade61b9a..fffe4591 100644 --- a/src/modules/aid/list/index.js +++ b/src/modules/aid/list/index.js @@ -67,7 +67,7 @@ const List = () => { const handlePagination = current_page => { let _start = (current_page - 1) * pagination.limit; - return listAid({ start: _start, limit: pagination.limit }); + return loadAidList({ start: _start, limit: pagination.limit }); }; const loadAidList = () => { From 0e59f863caa5b943529fad400afff273a0f833b4 Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Wed, 25 Aug 2021 07:26:01 +0545 Subject: [PATCH 42/59] fix project pagination --- src/modules/aid/list/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/aid/list/index.js b/src/modules/aid/list/index.js index fffe4591..ade61b9a 100644 --- a/src/modules/aid/list/index.js +++ b/src/modules/aid/list/index.js @@ -67,7 +67,7 @@ const List = () => { const handlePagination = current_page => { let _start = (current_page - 1) * pagination.limit; - return loadAidList({ start: _start, limit: pagination.limit }); + return listAid({ start: _start, limit: pagination.limit }); }; const loadAidList = () => { From e1bd7c2f0fc18c41da916007e7d721f36b835c99 Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Wed, 25 Aug 2021 08:24:38 +0545 Subject: [PATCH 43/59] beneficiary photo and govt id upload --- src/modules/beneficary/add/add.js | 56 +++++++++++++ .../beneficary/detail/beneficiaryInfo.js | 4 +- src/modules/beneficary/detail/detail.js | 3 +- src/modules/beneficary/edit/edit.js | 81 ++++++++++++++++++- src/modules/global/DetailsCard.js | 21 ++++- 5 files changed, 160 insertions(+), 5 deletions(-) diff --git a/src/modules/beneficary/add/add.js b/src/modules/beneficary/add/add.js index 7db06d6b..68f8f08c 100644 --- a/src/modules/beneficary/add/add.js +++ b/src/modules/beneficary/add/add.js @@ -7,6 +7,8 @@ import BreadCrumb from '../../ui_components/breadcrumb'; import { GROUPS, TOAST } from '../../../constants'; import { BeneficiaryContext } from '../../../contexts/BeneficiaryContext'; import SelectWrapper from '../../global/SelectWrapper'; +import UploadPlaceholder from '../../../assets/images/download.png'; +import { blobToBase64 } from '../../../utils'; const Add = () => { const { addToast } = useToasts(); @@ -36,6 +38,20 @@ const Add = () => { const [selectedGroup, setSelectedGroup] = useState(''); const [selectedProjects, setSelectedProjects] = useState(''); + const [profilePic, setProfilePic] = useState(''); + const [govtId, setGovtId] = useState(''); + + const handleProfileUpload = async e => { + const file = e.target.files[0]; + const base64Url = await blobToBase64(file); + setProfilePic(base64Url); + }; + const handleGovtIdUpload = async e => { + const file = e.target.files[0]; + const base64Url = await blobToBase64(file); + setGovtId(base64Url); + }; + const handleInputChange = e => { setFormData({ ...formData, [e.target.name]: e.target.value }); }; @@ -52,6 +68,8 @@ const Add = () => { const payload = { ...formData, extras: { ...extras } }; payload.projects = selectedProjects; if (selectedGender) payload.gender = selectedGender; + if (profilePic) payload.photo = profilePic; + if (govtId) payload.govt_id_image = govtId; addBeneficiary(payload) .then(() => { addToast('Beneficiary added successfully', TOAST.SUCCESS); @@ -100,6 +118,44 @@ const Add = () => { + + + + +
+ {profilePic ? ( + Profile + ) : ( + Profile + )} + +
+ + + + +
+ {govtId ? ( + Govt ID + ) : ( + Govt ID + )} + +
+ + History.push(`/edit-beneficiary/${basicInfo._id}`); @@ -71,7 +73,7 @@ export default function BeneficiaryInfo({ basicInfo, extras }) {
Profession
certificate { button_name="Generate QR Code" name="Name" name_value={basicInfo.name ? basicInfo.name : ''} + imgUrl={basicInfo.photo ? basicInfo.photo : ''} total="Total Issued" - total_value="1,500" + total_value="-" />
diff --git a/src/modules/beneficary/edit/edit.js b/src/modules/beneficary/edit/edit.js index c4442ece..e282c11f 100644 --- a/src/modules/beneficary/edit/edit.js +++ b/src/modules/beneficary/edit/edit.js @@ -7,6 +7,10 @@ import BreadCrumb from '../../ui_components/breadcrumb'; import { GROUPS, TOAST } from '../../../constants'; import { BeneficiaryContext } from '../../../contexts/BeneficiaryContext'; import SelectWrapper from '../../global/SelectWrapper'; +import { blobToBase64 } from '../../../utils'; +import UploadPlaceholder from '../../../assets/images/download.png'; + +const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY; const Edit = ({ beneficiaryId }) => { const { addToast } = useToasts(); @@ -37,6 +41,23 @@ const Edit = ({ beneficiaryId }) => { const [selectedGroup, setSelectedGroup] = useState(''); const [selectedProjects, setSelectedProjects] = useState(''); + const [profilePic, setProfilePic] = useState(''); + const [govtId, setGovtId] = useState(''); + + const [existingPhoto, setExistingPhoto] = useState(''); + const [existingGovtImage, setExistingGovtImage] = useState(''); + + const handleProfileUpload = async e => { + const file = e.target.files[0]; + const base64Url = await blobToBase64(file); + setProfilePic(base64Url); + }; + const handleGovtIdUpload = async e => { + const file = e.target.files[0]; + const base64Url = await blobToBase64(file); + setGovtId(base64Url); + }; + const handleInputChange = e => { setFormData({ ...formData, [e.target.name]: e.target.value }); }; @@ -53,6 +74,8 @@ const Edit = ({ beneficiaryId }) => { const payload = { ...formData, extras: { ...extras } }; payload.projects = selectedProjects; if (selectedGender) payload.gender = selectedGender; + if (profilePic) payload.photo = profilePic; + if (govtId) payload.govt_id_image = govtId; updateBeneficiary(beneficiaryId, payload) .then(() => { addToast('Beneficiary updated successfully', TOAST.SUCCESS); @@ -86,7 +109,9 @@ const Edit = ({ beneficiaryId }) => { const loadBeneficiaryDetails = useCallback(async () => { const d = await getBeneficiaryDetails(beneficiaryId); - const { extras, projects, name, phone, email, address, address_temporary, govt_id } = d; + const { photo, govt_id_image, extras, projects, name, phone, email, address, address_temporary, govt_id } = d; + if (photo) setExistingPhoto(photo); + if (govt_id_image) setExistingGovtImage(govt_id_image); if (projects && projects.length) { const project_ids = projects.map(p => p._id); setSelectedProjects(project_ids.toString()); @@ -124,6 +149,60 @@ const Edit = ({ beneficiaryId }) => { + + + + +
+ {existingPhoto ? ( + Profile + ) : profilePic ? ( + Profile + ) : ( + Profile + )} + +
+ + + + +
+ {existingGovtImage ? ( + Govt ID + ) : govtId ? ( + Govt ID + ) : ( + Govt ID + )} + +
+ + {existingProjects.length > 0 && ( diff --git a/src/modules/global/DetailsCard.js b/src/modules/global/DetailsCard.js index 11b2e5dd..404bc197 100644 --- a/src/modules/global/DetailsCard.js +++ b/src/modules/global/DetailsCard.js @@ -5,6 +5,9 @@ import BootstrapSwitchButton from 'bootstrap-switch-button-react'; import '../../assets/css/project.css'; import { PROJECT_STATUS } from '../../constants'; import Loading from '../global/Loading'; +import displayPic from '../../assets/images/users/user_avatar.svg'; + +const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY; export default function DetailsCard(props) { const { @@ -12,6 +15,7 @@ export default function DetailsCard(props) { title, button_name, name, + imgUrl, name_value, total, total_value, @@ -62,8 +66,21 @@ export default function DetailsCard(props) { -

{name_value || '0'}

-
{name || 'No Label'}
+ {/*

{name_value || '0'}

+
{name || 'No Label'}
*/} + +
+ user +
+

{name_value || '-'}

+
{name || 'No Label'}
+
+
{fetching ? :

{total_value || '0'}

} From ef441f835e73fd64a1f8349a0b44d6833653c5a6 Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Wed, 25 Aug 2021 19:52:57 +0545 Subject: [PATCH 44/59] beneficiary photo and govt id add update --- src/modules/beneficary/add/add.js | 38 +++++++------ .../beneficary/detail/beneficiaryInfo.js | 2 +- src/modules/beneficary/edit/edit.js | 55 +++++++++++-------- src/modules/global/DetailsCard.js | 2 +- 4 files changed, 55 insertions(+), 42 deletions(-) diff --git a/src/modules/beneficary/add/add.js b/src/modules/beneficary/add/add.js index 68f8f08c..1d565d6a 100644 --- a/src/modules/beneficary/add/add.js +++ b/src/modules/beneficary/add/add.js @@ -32,6 +32,7 @@ const Add = () => { child: '' }); + const [loading, setLoading] = useState(false); const [projectList, setProjectList] = useState([]); const [selectedGender, setSelectedGender] = useState(''); @@ -70,12 +71,15 @@ const Add = () => { if (selectedGender) payload.gender = selectedGender; if (profilePic) payload.photo = profilePic; if (govtId) payload.govt_id_image = govtId; + setLoading(true); addBeneficiary(payload) .then(() => { + setLoading(false); addToast('Beneficiary added successfully', TOAST.SUCCESS); History.push('/beneficiaries'); }) .catch(err => { + setLoading(false); addToast(err.message, TOAST.ERROR); }); }; @@ -319,23 +323,25 @@ const Add = () => { - {/* {loading ? ( - - ) : ( */} -
- - -
- {/* )} */} + ) : ( +
+ + +
+ )}
diff --git a/src/modules/beneficary/detail/beneficiaryInfo.js b/src/modules/beneficary/detail/beneficiaryInfo.js index 63dd1dca..e921cd8e 100644 --- a/src/modules/beneficary/detail/beneficiaryInfo.js +++ b/src/modules/beneficary/detail/beneficiaryInfo.js @@ -73,7 +73,7 @@ export default function BeneficiaryInfo({ basicInfo, extras }) {
Profession
certificate { const [existingPhoto, setExistingPhoto] = useState(''); const [existingGovtImage, setExistingGovtImage] = useState(''); + const [loading, setLoading] = useState(false); const handleProfileUpload = async e => { const file = e.target.files[0]; @@ -76,12 +77,16 @@ const Edit = ({ beneficiaryId }) => { if (selectedGender) payload.gender = selectedGender; if (profilePic) payload.photo = profilePic; if (govtId) payload.govt_id_image = govtId; + setLoading(true); updateBeneficiary(beneficiaryId, payload) .then(() => { + setLoading(false); + addToast('Beneficiary updated successfully', TOAST.SUCCESS); History.push('/beneficiaries'); }) .catch(err => { + setLoading(false); addToast(err.message, TOAST.ERROR); }); }; @@ -154,17 +159,17 @@ const Edit = ({ beneficiaryId }) => {
- {existingPhoto ? ( + {profilePic ? ( Profile - ) : profilePic ? ( + ) : existingPhoto ? ( Profile {
- {existingGovtImage ? ( + {govtId ? ( Govt ID - ) : govtId ? ( + ) : existingGovtImage ? ( Govt ID { - {/* {loading ? ( - - ) : ( */} -
- - -
- {/* )} */} + ) : ( +
+ + +
+ )}
diff --git a/src/modules/global/DetailsCard.js b/src/modules/global/DetailsCard.js index 404bc197..283cb74a 100644 --- a/src/modules/global/DetailsCard.js +++ b/src/modules/global/DetailsCard.js @@ -71,7 +71,7 @@ export default function DetailsCard(props) {
user Date: Wed, 25 Aug 2021 21:34:14 +0545 Subject: [PATCH 45/59] add and update vendor image and files --- src/modules/vendor/add/add.js | 134 ++++++++------------- src/modules/vendor/detail/detail.js | 13 ++- src/modules/vendor/detail/vendorInfo.js | 31 ++--- src/modules/vendor/edit/edit.js | 148 ++++++++++++++++-------- 4 files changed, 173 insertions(+), 153 deletions(-) diff --git a/src/modules/vendor/add/add.js b/src/modules/vendor/add/add.js index 235a9b40..94d0a0eb 100644 --- a/src/modules/vendor/add/add.js +++ b/src/modules/vendor/add/add.js @@ -1,17 +1,5 @@ import React, { useState, useEffect, useContext, useCallback } from 'react'; -import { - Breadcrumb, - BreadcrumbItem, - Card, - CardBody, - Row, - Col, - Form, - FormGroup, - Label, - Input, - Button -} from 'reactstrap'; +import { Card, CardBody, Row, Col, Form, FormGroup, Label, Input, Button } from 'reactstrap'; import { useToasts } from 'react-toast-notifications'; import { VendorContext } from '../../../contexts/VendorContext'; import { History } from '../../../utils/History'; @@ -20,10 +8,12 @@ import WalletUnlock from '../../../modules/global/walletUnlock'; import SelectWrapper from '../../global/SelectWrapper'; import { blobToBase64 } from '../../../utils'; import AvatarIcon from '../../../assets/images/download.png'; +import BreadCrumb from '../../ui_components/breadcrumb'; const Add = () => { const { addToast } = useToasts(); const { listAid, addVendor } = useContext(VendorContext); + const [passcodeModal, setPasscodeModal] = useState(false); const [formData, setFormData] = useState({ name: '', @@ -38,11 +28,14 @@ const Add = () => { bank_name: '', bank_account: '' }); + const [extras, setExtras] = useState({ - identity_photo: '', signature_photo: '', mou_file: '' }); + const [loading, setLoading] = useState(false); + + const [govtIdImg, setGovtIdImg] = useState(''); const [selectedGender, setSelectedGender] = useState(''); const [selectedProjects, setSelectedProjects] = useState(''); const [projectList, setProjectList] = useState([]); @@ -62,23 +55,23 @@ const Add = () => { }; async function handleProfileUpload(e) { - const convertImage = await blobToBase64(e.target.files[0]); - setProfileUpload(convertImage); + const base64Url = await blobToBase64(e.target.files[0]); + setProfileUpload(base64Url); } - async function handleIdentityUpload(e) { - const convertImage = await blobToBase64(e.target.files[0]); - setExtras({ ...extras, identity_photo: convertImage }); + async function handleGovtIdImage(e) { + const base64Url = await blobToBase64(e.target.files[0]); + setGovtIdImg(base64Url); } async function handleSignatureUpload(e) { - const convertImage = await blobToBase64(e.target.files[0]); - setExtras({ ...extras, signature_photo: convertImage }); + const base64Url = await blobToBase64(e.target.files[0]); + setExtras({ ...extras, signature_photo: base64Url }); } async function handleMouUpload(e) { - const convertImage = await blobToBase64(e.target.files[0]); - setExtras({ ...extras, mou_file: convertImage }); + const base64Url = await blobToBase64(e.target.files[0]); + setExtras({ ...extras, mou_file: base64Url }); } const handleFormSubmit = e => { e.preventDefault(); @@ -88,12 +81,16 @@ const Add = () => { payload.projects = selectedProjects; if (profileUpload) payload.photo = profileUpload; if (selectedGender) payload.gender = selectedGender; + if (govtIdImg) payload.govt_id_image = govtIdImg; + setLoading(true); addVendor(payload) .then(() => { + setLoading(false); addToast('Vendor added successfully', TOAST.SUCCESS); History.push('/vendors'); }) .catch(err => { + setLoading(false); addToast(err.message, TOAST.ERROR); }); }; @@ -123,12 +120,7 @@ const Add = () => {
setPasscodeModal(e)}>

Vendor

- - - Vendor - - Add - +
@@ -197,7 +189,7 @@ const Add = () => { - + @@ -224,26 +216,14 @@ const Add = () => { - +
- +
@@ -253,48 +233,30 @@ const Add = () => {
- +

- +

- +

- {extras.identity_photo ? ( + {govtIdImg ? ( Profile { ) : ( Profile )} - +
@@ -312,7 +274,7 @@ const Add = () => {
{extras.signature_photo ? ( Profile { - {/* {loading ? ( - - ) : ( */} -
- - -
- {/* )} */} + ) : ( +
+ + +
+ )}
diff --git a/src/modules/vendor/detail/detail.js b/src/modules/vendor/detail/detail.js index 843957b5..242e556e 100644 --- a/src/modules/vendor/detail/detail.js +++ b/src/modules/vendor/detail/detail.js @@ -14,6 +14,8 @@ import PasscodeModal from '../../global/PasscodeModal'; import { TOAST } from '../../../constants'; import { History } from '../../../utils/History'; +const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY; + const Index = ({ params }) => { const { addToast } = useToasts(); const { id } = params; @@ -124,7 +126,16 @@ const Index = ({ params }) => {
- user + user

{basicInfo.name}

Name
diff --git a/src/modules/vendor/detail/vendorInfo.js b/src/modules/vendor/detail/vendorInfo.js index fc50f426..11692034 100644 --- a/src/modules/vendor/detail/vendorInfo.js +++ b/src/modules/vendor/detail/vendorInfo.js @@ -79,44 +79,35 @@ export default function VendorInfo(props) {
certificatecertificate - {/* signature */} - {/* signature */} + /> diff --git a/src/modules/vendor/edit/edit.js b/src/modules/vendor/edit/edit.js index 6e7154d4..7da6d53d 100644 --- a/src/modules/vendor/edit/edit.js +++ b/src/modules/vendor/edit/edit.js @@ -10,11 +10,14 @@ import { blobToBase64 } from '../../../utils'; import SelectWrapper from '../../global/SelectWrapper'; import BreadCrumb from '../../ui_components/breadcrumb'; +const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY; + const Edit = ({ vendorId }) => { const { addToast } = useToasts(); const { listAid, updateVendor, getVendorDetails } = useContext(VendorContext); const [passcodeModal, setPasscodeModal] = useState(false); + const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ name: '', @@ -30,16 +33,22 @@ const Edit = ({ vendorId }) => { bank_account: '' }); const [extras, setExtras] = useState({ - identity_photo: '', signature_photo: '', mou_file: '' }); + const [govtIdentity, setGovtIdentity] = useState(''); + const [selectedGender, setSelectedGender] = useState(''); const [selectedProjects, setSelectedProjects] = useState(''); const [projectList, setProjectList] = useState([]); const [profileUpload, setProfileUpload] = useState(''); const [existingProjects, setExistingProjects] = useState([]); + const [existingProfilePhoto, setExistingProfilePhoto] = useState(''); + const [existingIdentity, setExistingIdentity] = useState(''); + const [existingSignature, setExistingSignature] = useState(''); + const [existingMou, setExistingMou] = useState(''); + const handleInputChange = e => { setFormData({ ...formData, [e.target.name]: e.target.value }); }; @@ -55,39 +64,48 @@ const Edit = ({ vendorId }) => { }; async function handleProfileUpload(e) { - const convertImage = await blobToBase64(e.target.files[0]); - setProfileUpload(convertImage); + const base64Url = await blobToBase64(e.target.files[0]); + setProfileUpload(base64Url); } - async function handleIdentityUpload(e) { - const convertImage = await blobToBase64(e.target.files[0]); - setExtras({ ...extras, identity_photo: convertImage }); + async function handleGovtIdentity(e) { + const base64Url = await blobToBase64(e.target.files[0]); + setGovtIdentity(base64Url); } async function handleSignatureUpload(e) { - const convertImage = await blobToBase64(e.target.files[0]); - setExtras({ ...extras, signature_photo: convertImage }); + const base64Url = await blobToBase64(e.target.files[0]); + setExtras({ ...extras, signature_photo: base64Url }); } async function handleMouUpload(e) { - const convertImage = await blobToBase64(e.target.files[0]); - setExtras({ ...extras, mou_file: convertImage }); + const base64Url = await blobToBase64(e.target.files[0]); + setExtras({ ...extras, mou_file: base64Url }); } const handleFormSubmit = e => { e.preventDefault(); if (!selectedProjects.length) return addToast('Please select project', TOAST.ERROR); + let extra_files = {}; - const payload = { ...formData, extra_files: { ...extras } }; + const payload = { ...formData }; payload.projects = selectedProjects; if (profileUpload) payload.photo = profileUpload; if (selectedGender) payload.gender = selectedGender; + if (govtIdentity) payload.govt_id_image = govtIdentity; + if (extras.signature_photo) extra_files.signature_photo = extras.signature_photo; + if (extras.mou_file) extra_files.mou_file = extras.mou_file; + payload.extra_files = extra_files; + + setLoading(true); updateVendor(vendorId, payload) .then(() => { + setLoading(false); addToast('Vendor updated successfully', TOAST.SUCCESS); History.push('/vendors'); }) .catch(err => { + setLoading(false); addToast(err.message, TOAST.ERROR); }); }; @@ -117,8 +135,21 @@ const Edit = ({ vendorId }) => { bank_branch, bank_account, photo, - govt_id + govt_id, + extra_files, + govt_id_image } = d; + + if (photo && photo.length) setExistingProfilePhoto(photo[0]); + if (govt_id_image) setExistingIdentity(govt_id_image); + console.log('==========>', extra_files); + + if (extra_files) { + const { signature_photo, mou_file } = extra_files; + if (signature_photo) setExistingSignature(signature_photo); + if (mou_file) setExistingMou(mou_file); + } + if (projects && projects.length) { const project_ids = projects.map(p => p._id); setSelectedProjects(project_ids.toString()); @@ -137,12 +168,11 @@ const Edit = ({ vendorId }) => { bank_name, bank_branch, bank_account, - photo, govt_id }); const { gender } = d; if (gender !== 'U') setSelectedGender(gender); - }, [vendorId, getVendorDetails]); + }, [getVendorDetails, vendorId]); const loadProjects = useCallback(async () => { const projects = await listAid(); @@ -181,10 +211,18 @@ const Edit = ({ vendorId }) => { height="200px" style={{ borderRadius: '10px', marginBottom: '10px' }} /> + ) : existingProfilePhoto ? ( + Profile ) : ( Profile )} - + @@ -244,7 +282,7 @@ const Edit = ({ vendorId }) => { - + @@ -271,13 +309,7 @@ const Edit = ({ vendorId }) => { - + @@ -290,7 +322,6 @@ const Edit = ({ vendorId }) => { value={formData.pan_number} className="form-field" onChange={handleInputChange} - required /> @@ -307,7 +338,6 @@ const Edit = ({ vendorId }) => { value={formData.bank_name} className="form-field" onChange={handleInputChange} - required /> @@ -321,7 +351,6 @@ const Edit = ({ vendorId }) => { value={formData.bank_branch} className="form-field" onChange={handleInputChange} - required /> @@ -335,7 +364,6 @@ const Edit = ({ vendorId }) => { value={formData.bank_account} className="form-field" onChange={handleInputChange} - required /> @@ -343,9 +371,17 @@ const Edit = ({ vendorId }) => {
- {extras.identity_photo ? ( + {govtIdentity ? ( Profile + ) : existingIdentity ? ( + Profile { ) : ( Profile )} - +
@@ -369,6 +405,14 @@ const Edit = ({ vendorId }) => { height="200px" style={{ borderRadius: '10px', marginBottom: '10px' }} /> + ) : existingSignature ? ( + Profile ) : ( Profile )} @@ -392,32 +436,42 @@ const Edit = ({ vendorId }) => { height="200px" style={{ borderRadius: '10px', marginBottom: '10px' }} /> + ) : existingMou ? ( + Profile ) : ( Profile )} - + - {/* {loading ? ( - - ) : ( */} -
- - -
- {/* )} */} + ) : ( +
+ + +
+ )}
From 8f7249f822fbea8808a89d41fa84aeb3f66e59f7 Mon Sep 17 00:00:00 2001 From: Manjik Shrestha Date: Thu, 26 Aug 2021 09:29:16 +0545 Subject: [PATCH 46/59] minor fixes --- src/modules/beneficary/detail/detail.js | 2 +- src/services/beneficiary.js | 1 + src/services/mobilizer.js | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/modules/beneficary/detail/detail.js b/src/modules/beneficary/detail/detail.js index 430ae8ef..3454857c 100644 --- a/src/modules/beneficary/detail/detail.js +++ b/src/modules/beneficary/detail/detail.js @@ -261,7 +261,7 @@ const BenefDetails = ({ params }) => { name="Name" name_value={basicInfo.name ? basicInfo.name : ''} total="Total Issued" - total_value="1,500" + total_value={currentBalance} /> diff --git a/src/services/beneficiary.js b/src/services/beneficiary.js index 0939344f..46c5a5a0 100644 --- a/src/services/beneficiary.js +++ b/src/services/beneficiary.js @@ -54,6 +54,7 @@ export async function listByAid(aid, params) { } export async function addBeneficiary(body) { + if (!body.wallet_address) body.wallet_address = body.phone; const res = await axios({ url: API.BENEFICARIES, method: 'post', diff --git a/src/services/mobilizer.js b/src/services/mobilizer.js index c742c935..eab95bb3 100644 --- a/src/services/mobilizer.js +++ b/src/services/mobilizer.js @@ -26,8 +26,9 @@ export async function approveMobilizer(wallet, payload,contract_address) { const contract = await getContractByProvider(contract_address, CONTRACT.RAHAT); const signerContract = contract.connect(wallet); const myContract = mapTestContract(signerContract); - const data = await myContract.addAdmin(payload.wallet_address); - if (!data) return "Mobilizer approve failed!"; + const tx = await myContract.addAdmin(payload.wallet_address); + let minedTx = await tx.wait(); + if (!minedTx) return "Mobilizer approve failed!"; const res = await approveMobilizerToProject(payload.wallet_address, payload.projectId); getEth({address:payload.wallet_address}); return res; From 181f2bdbb46250747e115773d8e0feaa679386b7 Mon Sep 17 00:00:00 2001 From: rojanbade Date: Thu, 26 Aug 2021 15:03:26 +0545 Subject: [PATCH 47/59] pin code ui updated --- src/assets/css/custom.css | 2 +- src/modules/global/PasscodeModal.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/assets/css/custom.css b/src/assets/css/custom.css index 6fce95d7..80828775 100644 --- a/src/assets/css/custom.css +++ b/src/assets/css/custom.css @@ -188,7 +188,7 @@ a { background-color: #2b7ec1; } .input-pin { - background-color: black; + background-color: #2b7ec1 ; color: white; width: 100px; height: 100px; diff --git a/src/modules/global/PasscodeModal.js b/src/modules/global/PasscodeModal.js index 1987919f..6dca1edc 100644 --- a/src/modules/global/PasscodeModal.js +++ b/src/modules/global/PasscodeModal.js @@ -63,7 +63,7 @@ export default function PasscodeModal(props) { centered class="modal-backdrop fade in" > -
+ {/* {children || 'No child elements supplied.'} */} {/* Close */} @@ -91,7 +91,7 @@ export default function PasscodeModal(props) { "{value}" */} -
+
{loadingMessage ? loadingMessage : ''}
From 3ba8545b95a02e9507005479db21eecadc67c05a Mon Sep 17 00:00:00 2001 From: Manjik Shrestha Date: Thu, 26 Aug 2021 17:47:54 +0545 Subject: [PATCH 48/59] open in new window --- src/modules/aid/detail/projectInfo.js | 38 +++++++++- src/modules/aid/detail/qrGenerator/html.js | 76 ++++++++++++++++++++ src/modules/aid/detail/qrGenerator/index.js | 76 ++++++++++++++++++++ src/modules/aid/detail/qrGenerator/style.css | 41 +++++++++++ 4 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 src/modules/aid/detail/qrGenerator/html.js create mode 100644 src/modules/aid/detail/qrGenerator/index.js create mode 100644 src/modules/aid/detail/qrGenerator/style.css diff --git a/src/modules/aid/detail/projectInfo.js b/src/modules/aid/detail/projectInfo.js index 3987607f..65c439f6 100644 --- a/src/modules/aid/detail/projectInfo.js +++ b/src/modules/aid/detail/projectInfo.js @@ -1,15 +1,37 @@ import React from 'react'; import moment from 'moment'; import { Card, CardTitle, Col, Row } from 'reactstrap'; +import ReactDOM from 'react-dom'; import { History } from '../../../utils/History'; import '../../../assets/css/project.css'; +import qrGenerator from './qrGenerator'; export default function ProjectInfo({ projectDetails }) { const { _id, social_mobilizer, project_manager, location, description, created_at } = projectDetails; const handleEditClick = () => History.push(`/edit-project/${_id}`); + const printQr = async(data) =>{ + console.log("generating") + ReactDOM.createPortal(qrGenerator, document.getElementById('root')) +const qrs= await qrGenerator({min:1,max:3,projectVersion:1,amount:10}) + +// console.log({qrs}); +//var doc = new jsPDF(); + + +var newWindow = window.open("", "newWindow", "width=800, height=600"); +console.log({newWindow}) +if(newWindow) newWindow.document.write(qrs); +// document.write(qrs); +// document.close(); +// setTimeout(function () { +// newWindow.print(); +// newWindow.close(); +// }, 250); + } + return (
@@ -21,14 +43,26 @@ export default function ProjectInfo({ projectDetails }) {
- + + + diff --git a/src/modules/aid/detail/qrGenerator/html.js b/src/modules/aid/detail/qrGenerator/html.js new file mode 100644 index 00000000..5e865750 --- /dev/null +++ b/src/modules/aid/detail/qrGenerator/html.js @@ -0,0 +1,76 @@ +import './style.css'; +import React from 'react'; +import ReactDOMServer from 'react-dom/server'; +import QRCode from 'qrcode.react'; +import RahatLogo from '../../../../assets/images/main_logo.png'; + +function QRGenerator(props ) { + //send props as {min: 1, max: 5, amount: null} + const dataList = []; + + function getQrData(projectVersion, amount, serialNumber) { + if (projectVersion > 999) + throw Error('project version should be less than 1000'); + if (serialNumber > 999) + throw Error('cannot generate more than 1000 qr codes'); + const id = `555${String(projectVersion).padStart(3, '0')}${String( + serialNumber + ).padStart(4, '0')}`; + console.log(id); + return { qrData: `phone:${id}?amount=${amount}`, id }; + } +console.log({props}) + + if (props && props.min && props.max > props.min) { + + try { + for (let i = props.min; i <= props.max; i++) { + const { qrData, id } = getQrData( + props.projectVersion, + props.amount, + i + ); + console.log({ qrData, id }); + dataList.push({ text: qrData, id }); + } + } catch (error) { + alert(error); + } + } + + + return ReactDOMServer.renderToString( + + + + + + +
+ {dataList.map((d, i) => { + console.log({d}); + return ( +
+ + +
+

{d.id}

+
+
+ ); + })} +
+ + + ); +} + +export default QRGenerator; diff --git a/src/modules/aid/detail/qrGenerator/index.js b/src/modules/aid/detail/qrGenerator/index.js new file mode 100644 index 00000000..5e865750 --- /dev/null +++ b/src/modules/aid/detail/qrGenerator/index.js @@ -0,0 +1,76 @@ +import './style.css'; +import React from 'react'; +import ReactDOMServer from 'react-dom/server'; +import QRCode from 'qrcode.react'; +import RahatLogo from '../../../../assets/images/main_logo.png'; + +function QRGenerator(props ) { + //send props as {min: 1, max: 5, amount: null} + const dataList = []; + + function getQrData(projectVersion, amount, serialNumber) { + if (projectVersion > 999) + throw Error('project version should be less than 1000'); + if (serialNumber > 999) + throw Error('cannot generate more than 1000 qr codes'); + const id = `555${String(projectVersion).padStart(3, '0')}${String( + serialNumber + ).padStart(4, '0')}`; + console.log(id); + return { qrData: `phone:${id}?amount=${amount}`, id }; + } +console.log({props}) + + if (props && props.min && props.max > props.min) { + + try { + for (let i = props.min; i <= props.max; i++) { + const { qrData, id } = getQrData( + props.projectVersion, + props.amount, + i + ); + console.log({ qrData, id }); + dataList.push({ text: qrData, id }); + } + } catch (error) { + alert(error); + } + } + + + return ReactDOMServer.renderToString( + + + + + + +
+ {dataList.map((d, i) => { + console.log({d}); + return ( +
+ + +
+

{d.id}

+
+
+ ); + })} +
+ + + ); +} + +export default QRGenerator; diff --git a/src/modules/aid/detail/qrGenerator/style.css b/src/modules/aid/detail/qrGenerator/style.css new file mode 100644 index 00000000..ccb05ae0 --- /dev/null +++ b/src/modules/aid/detail/qrGenerator/style.css @@ -0,0 +1,41 @@ +.App { + text-align: center; +} +.printQr { + text-align: center; + margin-top: 5em; + margin-bottom: 10em; +} +.wrapper { + display: flex; + flex-wrap: wrap; + align-content: space-between; + vertical-align: middle; +} +.itemWrapper { + flex: 1; + padding: 25px; + justify-content: center; + align-items: center; +} +@page { + size: A4; + margin: 70pt 60pt 70pt; +} +@media print { + html, + body { + width: 210mm; + min-height: 297mm; + height: 297mm; + padding: 20px; + } + .no-printme { + display: none; + } + .printme { + width: initial; + min-height: initial; + page-break-after: always; + } +} From b78b2d35f542ad4ff9683cb4a0bb97563d09bd15 Mon Sep 17 00:00:00 2001 From: Manjik Shrestha Date: Wed, 1 Sep 2021 12:35:08 +0545 Subject: [PATCH 49/59] fix beneficiary search by project --- src/contexts/BeneficiaryContext.js | 16 ++++++---------- src/modules/beneficary/list.js | 6 ++++-- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/contexts/BeneficiaryContext.js b/src/contexts/BeneficiaryContext.js index 43dc11b1..16e1c96b 100644 --- a/src/contexts/BeneficiaryContext.js +++ b/src/contexts/BeneficiaryContext.js @@ -38,15 +38,11 @@ export const BeneficiaryContextProvider = ({ children }) => { return Service.getBeneficiaryBalance(phone, contract_address); }, []); - const listAid = useCallback(() => { - return AidService.listAid({ limit: APP_CONSTANTS.FETCH_LIMIT }); - }, []); - - // async function listAid() { - // const d = await AidService.listAid({ start: 0, limit: 50 }); - // dispatch({ type: ACTION.LIST_AID, data: { projectList: d.data } }); - // return d; - // } + async function listProject() { + const d = await AidService.listAid({ start: 0, limit: 50 }); + dispatch({ type: ACTION.LIST_AID, data: { projectList: d.data } }); + return d; + } function setAid(aid) { dispatch({ type: ACTION.SET_AID, data: aid }); @@ -127,7 +123,7 @@ export const BeneficiaryContextProvider = ({ children }) => { beneficiary_detail: state.beneficiary, clear, setAid, - listAid, + listProject, issueTokens, addBeneficiary, updateBeneficiary, diff --git a/src/modules/beneficary/list.js b/src/modules/beneficary/list.js index e1e31d6f..c9222cd2 100644 --- a/src/modules/beneficary/list.js +++ b/src/modules/beneficary/list.js @@ -30,7 +30,8 @@ const Beneficiary = () => { }); const [selectedProject, setSelectedProject] = useState(''); - const { listBeneficiary, list, pagination, listAid, projectList } = useContext(BeneficiaryContext); + + const { listBeneficiary, list, pagination, listProject, projectList } = useContext(BeneficiaryContext); const handleFilterChange = e => { let { value } = e.target; @@ -83,8 +84,9 @@ const Beneficiary = () => { }); }; + const fetchProjectList = () => { - listAid() + listProject() .then() .catch(() => { addToast('Something went wrong!', { From 5922572e98c37ea823bc02dfe776e2cd699c486f Mon Sep 17 00:00:00 2001 From: Manjik Shrestha Date: Thu, 2 Sep 2021 08:49:13 +0545 Subject: [PATCH 50/59] use react-to-print to print component --- package.json | 1 + src/modules/aid/detail/projectInfo.js | 81 +++++++------ src/modules/aid/detail/qrGenerator/index.js | 122 +++++++++----------- src/modules/beneficary/detail/detail.js | 1 + yarn.lock | 7 ++ 5 files changed, 106 insertions(+), 106 deletions(-) diff --git a/package.json b/package.json index 2510668b..8864f8f5 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "react-select": "^2.2.0", "react-stepzilla": "^6.0.2", "react-table-v6": "^6.8.6", + "react-to-print": "^2.13.0", "react-toast-notifications": "^2.4.0", "reactstrap": "^8.4.1", "redux-devtools-extension": "^2.13.8", diff --git a/src/modules/aid/detail/projectInfo.js b/src/modules/aid/detail/projectInfo.js index 65c439f6..ade62840 100644 --- a/src/modules/aid/detail/projectInfo.js +++ b/src/modules/aid/detail/projectInfo.js @@ -1,39 +1,46 @@ -import React from 'react'; +import React, { useRef } from 'react'; import moment from 'moment'; import { Card, CardTitle, Col, Row } from 'reactstrap'; import ReactDOM from 'react-dom'; import { History } from '../../../utils/History'; import '../../../assets/css/project.css'; -import qrGenerator from './qrGenerator'; +import QRGenerator from './qrGenerator'; +import { useReactToPrint } from 'react-to-print'; export default function ProjectInfo({ projectDetails }) { const { _id, social_mobilizer, project_manager, location, description, created_at } = projectDetails; const handleEditClick = () => History.push(`/edit-project/${_id}`); + const qrComponentRef = useRef(); + const handlePreQrPrint = useReactToPrint({ + content: () => qrComponentRef.current + }); - const printQr = async(data) =>{ - console.log("generating") - ReactDOM.createPortal(qrGenerator, document.getElementById('root')) -const qrs= await qrGenerator({min:1,max:3,projectVersion:1,amount:10}) + const printQr = async data => { + console.log('generating'); + //ReactDOM.createPortal(qrGenerator, document.getElementById('root')) + //const qrs= await qrGenerator({min:1,max:3,projectVersion:1,amount:10}) -// console.log({qrs}); -//var doc = new jsPDF(); + // console.log({ qrs }); + //var doc = new jsPDF(); + const printElement = document.createElement('iframe'); -var newWindow = window.open("", "newWindow", "width=800, height=600"); -console.log({newWindow}) -if(newWindow) newWindow.document.write(qrs); -// document.write(qrs); -// document.close(); -// setTimeout(function () { -// newWindow.print(); -// newWindow.close(); -// }, 250); - } + // let newWindow = window.open('', 'Print QR', 'fullscreen=yes'), + // document = newWindow.document.open(); + // document.write("data"); + //newWindow.document.body.innerHTML (qrs); + // document.close(); + // setTimeout(function () { + // newWindow.print(); + // newWindow.close(); + // }, 250); + }; return (
+ {0 && }
@@ -43,26 +50,24 @@ if(newWindow) newWindow.document.write(qrs);
- -
- - -
- +
+ + +
diff --git a/src/modules/aid/detail/qrGenerator/index.js b/src/modules/aid/detail/qrGenerator/index.js index 5e865750..3124f478 100644 --- a/src/modules/aid/detail/qrGenerator/index.js +++ b/src/modules/aid/detail/qrGenerator/index.js @@ -1,76 +1,62 @@ import './style.css'; import React from 'react'; import ReactDOMServer from 'react-dom/server'; +import { useEffect, useState } from 'react'; import QRCode from 'qrcode.react'; import RahatLogo from '../../../../assets/images/main_logo.png'; -function QRGenerator(props ) { - //send props as {min: 1, max: 5, amount: null} - const dataList = []; - - function getQrData(projectVersion, amount, serialNumber) { - if (projectVersion > 999) - throw Error('project version should be less than 1000'); - if (serialNumber > 999) - throw Error('cannot generate more than 1000 qr codes'); - const id = `555${String(projectVersion).padStart(3, '0')}${String( - serialNumber - ).padStart(4, '0')}`; - console.log(id); - return { qrData: `phone:${id}?amount=${amount}`, id }; - } -console.log({props}) - - if (props && props.min && props.max > props.min) { - - try { - for (let i = props.min; i <= props.max; i++) { - const { qrData, id } = getQrData( - props.projectVersion, - props.amount, - i - ); - console.log({ qrData, id }); - dataList.push({ text: qrData, id }); - } - } catch (error) { - alert(error); - } - } - - - return ReactDOMServer.renderToString( - - - - - - -
- {dataList.map((d, i) => { - console.log({d}); - return ( -
- - -
-

{d.id}

-
-
- ); - })} -
- - - ); -} +const QRGenerator = React.forwardRef(({ props }, ref) => { + //send props as {min: 1, max: 5, amount: null} + const [data, setData] = useState([]); + + function getQrData(projectVersion, amount, serialNumber) { + if (projectVersion > 999) throw Error('project version should be less than 1000'); + if (serialNumber > 999) throw Error('cannot generate more than 1000 qr codes'); + const id = `555${String(projectVersion).padStart(3, '0')}${String(serialNumber).padStart(4, '0')}`; + console.log(id); + return { qrData: `phone:${id}?amount=${amount}`, id }; + } + + useEffect(() => { + console.log(props); + if (props && props.min && props.max > props.min) { + const dataList = []; + try { + for (let i = props.min; i <= props.max; i++) { + const { qrData, id } = getQrData(props.projectVersion, props.amount, i); + console.log({ qrData, id }); + dataList.push({ text: qrData, id }); + } + setData(dataList); + } catch (error) { + alert(error); + } + } + }, [props]); + + return ( +
+ {data.map((d, i) => { + return ( +
+ + +
+

{d.id}

+
+
+ ); + })} +
+ ); +}); export default QRGenerator; diff --git a/src/modules/beneficary/detail/detail.js b/src/modules/beneficary/detail/detail.js index 52977051..46a771a6 100644 --- a/src/modules/beneficary/detail/detail.js +++ b/src/modules/beneficary/detail/detail.js @@ -78,6 +78,7 @@ const BenefDetails = ({ params }) => { setAssignTokenAmount(''); let newWindow = window.open('', 'Print QR', 'fullscreen=yes'), document = newWindow.document.open(); + console.log({newWindow}); document.write(html); document.close(); setTimeout(function () { diff --git a/yarn.lock b/yarn.lock index 524b4a1c..69749f18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10921,6 +10921,13 @@ react-table-v6@^6.8.6: dependencies: classnames "^2.2.5" +react-to-print@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/react-to-print/-/react-to-print-2.13.0.tgz#f2422a5665e08a9186eb9f7a2fad6ad1efd9f3a0" + integrity sha512-JMX+HrMtBXWDh2ohPT2IeBkaGY4QpFeloXTXBA7hBK7dXJQei/UXMvQqbZHb0rqJwzAHltZ2zXevuSK/ReKI5w== + dependencies: + prop-types "^15.7.2" + react-toast-notifications@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/react-toast-notifications/-/react-toast-notifications-2.4.0.tgz#6213730bd1fe158fc01aeef200687ea94c5c5b24" From cfd8768f0da9cb1dc6be061c9a576c99a8d248bf Mon Sep 17 00:00:00 2001 From: rojanbade Date: Fri, 3 Sep 2021 11:01:53 +0545 Subject: [PATCH 51/59] Vendor and mobilizer ui update --- src/contexts/MobilizerContext.js | 313 +++++++++--------- src/contexts/VendorContext.js | 10 - .../beneficary/detail/beneficiaryInfo.js | 16 +- src/modules/global/PasscodeModal.js | 6 +- src/modules/mobilizer/add/add.js | 175 ++++++++++ src/modules/mobilizer/add/index.js | 13 + src/modules/mobilizer/detail/detail.js | 216 ++++++++---- src/modules/mobilizer/detail/mobilizerInfo.js | 79 +++++ src/modules/mobilizer/edit/edit.js | 232 +++++++++++++ src/modules/mobilizer/edit/index.js | 14 + src/modules/mobilizer/list.js | 16 +- src/modules/vendor/add/add.js | 2 +- src/modules/vendor/detail/detail.js | 1 + src/modules/vendor/detail/vendorInfo.js | 4 +- src/modules/vendor/edit/edit.js | 7 +- src/modules/vendor/edit/index.js | 1 - src/modules/vendor/list.js | 26 +- src/routes/Router.js | 13 +- src/services/mobilizer.js | 227 +++++++------ 19 files changed, 1004 insertions(+), 367 deletions(-) create mode 100644 src/modules/mobilizer/add/add.js create mode 100644 src/modules/mobilizer/add/index.js create mode 100644 src/modules/mobilizer/detail/mobilizerInfo.js create mode 100644 src/modules/mobilizer/edit/edit.js create mode 100644 src/modules/mobilizer/edit/index.js diff --git a/src/contexts/MobilizerContext.js b/src/contexts/MobilizerContext.js index 45a5abb9..04078280 100644 --- a/src/contexts/MobilizerContext.js +++ b/src/contexts/MobilizerContext.js @@ -1,167 +1,160 @@ -import React, { createContext, useReducer,useContext } from "react"; -import mobilizerReduce from "../reducers/mobilizerReducer"; -import * as Service from "../services/mobilizer"; -import * as AidService from "../services/aid"; -import ACTION from "../actions/mobilizer"; -import {AppContext} from './AppSettingsContext'; +import React, { createContext, useReducer, useContext } from 'react'; +import mobilizerReduce from '../reducers/mobilizerReducer'; +import * as Service from '../services/mobilizer'; +import * as AidService from '../services/aid'; +import ACTION from '../actions/mobilizer'; +import { AppContext } from './AppSettingsContext'; const initialState = { - list: [], - pagination: { limit: 10, start: 0, total: 0, currentPage: 1, totalPages: 0 }, - aid: "", - aids: [], - mobilizer: {}, - loading: false, - transactionHistory: [], + list: [], + pagination: { limit: 10, start: 0, total: 0, currentPage: 1, totalPages: 0 }, + aid: '', + aids: [], + mobilizer: {}, + loading: false, + transactionHistory: [] }; export const MobilizerContext = createContext(initialState); export const MobilizerContextProvider = ({ children }) => { - const [state, dispatch] = useReducer(mobilizerReduce, initialState); - const {wallet,appSettings,changeIsverified} = useContext(AppContext); - - async function getMobilizerBalance(contract_addr, wallet_address) { - return Service.getMobilizerBalance(contract_addr, wallet_address); - } - - - async function getAvailableBalance(proejctId, rahatAdminContractAddr) { - const { rahat:rahatContractAddr } = appSettings.agency.contracts; - return AidService.loadAidBalance(proejctId, rahatContractAddr); - } - - async function listAid() { - const d = await AidService.listAid({ start: 0, limit: 20 }); - dispatch({ type: ACTION.LIST_AID, data: { aids: d.data } }); - return d; - } - - function setAid(aid) { - dispatch({ type: ACTION.SET_AID, data: aid }); - } - - function clear() { - dispatch({ - type: ACTION.LIST, - data: { - limit: 10, - start: 0, - total: 0, - data: [], - page: 0, - name: "", - phone: "", - }, - }); - } - - async function approveMobilizer( payload) { - const { rahat:rahatContractAddr } = appSettings.agency.contracts; - const res = await Service.approveMobilizer(wallet,payload,rahatContractAddr); - changeIsverified(false); - if (res) { - setMobilizer(res.data); - return res.data; - } - } - - async function changeMobilizerStatus(mobilizerId, status) { - const res = await Service.changeMobilizerStaus(mobilizerId, status); - setMobilizer(res.data); - return res.data; - } - - function setMobilizer(b) { - dispatch({ type: ACTION.SET_MOBILIZER, data: b }); - } - - async function getMobilizerDetails(id) { - const data = await Service.get(id); - setMobilizer(data); - return data; - } - - function addMobilizer(e) { - e.preventDefault(); - const formData = new FormData(e.target); - let payload = { - name: formData.get("name"), - phone: formData.get("phone"), - wallet_address: formData.get("ethaddress"), - email: formData.get("email"), - address: formData.get("address"), - govt_id: formData.get("govt_id"), - organization:formData.get("organization") - }; - - return new Promise((resolve, reject) => { - Service.add(payload) - .then((res) => { - resolve(res); - }) - .catch((err) => { - reject(err); - }); - }); - } - - function setLoading() { - dispatch({ type: ACTION.SET_LOADING }); - } - - function resetLoading() { - dispatch({ type: ACTION.RESET_LOADING }); - } - - async function listMobilizer(params) { - let res = await Service.list(params); - if (res) { - dispatch({ - type: ACTION.LIST, - data: res, - }); - return res; - } - } - - async function getMobilizerTransactions(mobilizerId) { - let res = await Service.mobilizerTransactions(mobilizerId); - if (res) { - dispatch({ - type: ACTION.MOBILIZER_TX, - data: res, - }); - return res; - } - } - - return ( - - {children} - - ); + const [state, dispatch] = useReducer(mobilizerReduce, initialState); + const { wallet, appSettings, changeIsverified } = useContext(AppContext); + + async function getMobilizerBalance(contract_addr, wallet_address) { + return Service.getMobilizerBalance(contract_addr, wallet_address); + } + + async function getAvailableBalance(proejctId, rahatAdminContractAddr) { + const { rahat: rahatContractAddr } = appSettings.agency.contracts; + return AidService.loadAidBalance(proejctId, rahatContractAddr); + } + + async function listAid() { + const d = await AidService.listAid({ start: 0, limit: 20 }); + dispatch({ type: ACTION.LIST_AID, data: { aids: d.data } }); + return d; + } + + function setAid(aid) { + dispatch({ type: ACTION.SET_AID, data: aid }); + } + + function clear() { + dispatch({ + type: ACTION.LIST, + data: { + limit: 10, + start: 0, + total: 0, + data: [], + page: 0, + name: '', + phone: '' + } + }); + } + + async function approveMobilizer(payload) { + const { rahat: rahatContractAddr } = appSettings.agency.contracts; + const res = await Service.approveMobilizer(wallet, payload, rahatContractAddr); + changeIsverified(false); + if (res) { + setMobilizer(res.data); + return res.data; + } + } + + async function changeMobilizerStatus(mobilizerId, status) { + const res = await Service.changeMobilizerStaus(mobilizerId, status); + setMobilizer(res.data); + return res.data; + } + + function setMobilizer(b) { + dispatch({ type: ACTION.SET_MOBILIZER, data: b }); + } + + async function getMobilizerDetails(id) { + const data = await Service.get(id); + setMobilizer(data); + return data; + } + + function addMobilizer(payload) { + return new Promise((resolve, reject) => { + Service.add(payload) + .then(res => { + resolve(res); + }) + .catch(err => { + reject(err); + }); + }); + } + + const updateMobilizer = (id, payload) => { + console.log(' mob', payload); + return Service.updateMobilizer(id, payload); + }; + + function setLoading() { + dispatch({ type: ACTION.SET_LOADING }); + } + + function resetLoading() { + dispatch({ type: ACTION.RESET_LOADING }); + } + + async function listMobilizer(params) { + let res = await Service.list(params); + if (res) { + dispatch({ + type: ACTION.LIST, + data: res + }); + return res; + } + } + + async function getMobilizerTransactions(mobilizerId) { + let res = await Service.mobilizerTransactions(mobilizerId); + if (res) { + dispatch({ + type: ACTION.MOBILIZER_TX, + data: res + }); + return res; + } + } + + return ( + + {children} + + ); }; diff --git a/src/contexts/VendorContext.js b/src/contexts/VendorContext.js index d2c0b2b9..1954d73c 100644 --- a/src/contexts/VendorContext.js +++ b/src/contexts/VendorContext.js @@ -89,16 +89,6 @@ export const VendorContextProvider = ({ children }) => { }, []); function addVendor(payload) { - // const formData = new FormData(e.target); - // let payload = { - // name: formData.get("name"), - // phone: formData.get("phone"), - // wallet_address: formData.get("ethaddress"), - // email: formData.get("email"), - // address: formData.get("address"), - // govt_id: formData.get("govt_id"), - // }; - return new Promise((resolve, reject) => { Service.add(payload) .then(res => { diff --git a/src/modules/beneficary/detail/beneficiaryInfo.js b/src/modules/beneficary/detail/beneficiaryInfo.js index e921cd8e..865b6699 100644 --- a/src/modules/beneficary/detail/beneficiaryInfo.js +++ b/src/modules/beneficary/detail/beneficiaryInfo.js @@ -36,6 +36,10 @@ export default function BeneficiaryInfo({ basicInfo, extras }) {

{basicInfo.address || '-'}

Address
+
+

{extras && extras.education ? extras.education : '-'}

+
Education
+

{basicInfo.govt_id || '-'}

Government ID number
@@ -51,8 +55,12 @@ export default function BeneficiaryInfo({ basicInfo, extras }) {
-

{extras && extras.education ? extras.education : '-'}

-
Education
+

{basicInfo.phone || '-'}

+
Phone
+
+
+

{extras && extras.profession ? extras.profession : '-'}

+
Profession

{basicInfo.gender || '-'}

@@ -68,10 +76,6 @@ export default function BeneficiaryInfo({ basicInfo, extras }) {
-
-

{extras && extras.profession ? extras.profession : '-'}

-
Profession
-
certificateClose */}
-

+

{title || 'Verify Passcode'}   @@ -78,6 +78,10 @@ export default function PasscodeModal(props) {

+
+ Enter you passcode from RUMSAN WALLET to proceed transaction +
+
diff --git a/src/modules/mobilizer/add/add.js b/src/modules/mobilizer/add/add.js new file mode 100644 index 00000000..7b3c0018 --- /dev/null +++ b/src/modules/mobilizer/add/add.js @@ -0,0 +1,175 @@ +import React, { useState, useContext } from 'react'; +import { Card, CardBody, Row, Col, Form, FormGroup, Label, Input, Button } from 'reactstrap'; +import { useToasts } from 'react-toast-notifications'; +import { MobilizerContext } from '../../../contexts/MobilizerContext'; +import { History } from '../../../utils/History'; +import { TOAST } from '../../../constants'; +import WalletUnlock from '../../../modules/global/walletUnlock'; +import { blobToBase64 } from '../../../utils'; +import AvatarIcon from '../../../assets/images/download.png'; +import BreadCrumb from '../../ui_components/breadcrumb'; + +const Add = () => { + const { addToast } = useToasts(); + const { addMobilizer } = useContext(MobilizerContext); + const [passcodeModal, setPasscodeModal] = useState(false); + const [formData, setFormData] = useState({ + name: '', + phone: '', + email: '', + address: '', + organization: '', + wallet_address: '' + }); + const [loading, setLoading] = useState(false); + const [govtIdImg, setGovtIdImg] = useState(''); + const [profileUpload, setProfileUpload] = useState(''); + + const handleInputChange = e => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + async function handleProfileUpload(e) { + const base64Url = await blobToBase64(e.target.files[0]); + setProfileUpload(base64Url); + } + + async function handleGovtIdImage(e) { + const base64Url = await blobToBase64(e.target.files[0]); + setGovtIdImg(base64Url); + } + const handleFormSubmit = e => { + e.preventDefault(); + const payload = { ...formData }; + if (profileUpload) payload.photo = profileUpload; + if (govtIdImg) payload.govt_id_image = govtIdImg; + setLoading(true); + addMobilizer(payload) + .then(() => { + setLoading(false); + addToast('Mobilizer added successfully', TOAST.SUCCESS); + History.push('/mobilizers'); + }) + .catch(err => { + setLoading(false); + addToast(err.message, TOAST.ERROR); + }); + }; + + const handleCancelClick = () => History.push('/users'); + + return ( +
+ setPasscodeModal(e)}> +

Mobilizers

+ + +
+ + + + + + + +
+ {profileUpload ? ( + Profile + ) : ( + Profile + )} + +
+ +
+ + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ {govtIdImg ? ( + Profile + ) : ( + Profile + )} + +
+ + + {loading ? ( + + ) : ( +
+ + +
+ )} +
+ + + + + + + ); +}; + +export default Add; diff --git a/src/modules/mobilizer/add/index.js b/src/modules/mobilizer/add/index.js new file mode 100644 index 00000000..fa188267 --- /dev/null +++ b/src/modules/mobilizer/add/index.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { MobilizerContextProvider } from '../../../contexts/MobilizerContext'; +import AddMobilizer from './add'; + +const index = () => { + return ( + + + + ); +}; + +export default index; diff --git a/src/modules/mobilizer/detail/detail.js b/src/modules/mobilizer/detail/detail.js index cafc7810..c5ddced5 100644 --- a/src/modules/mobilizer/detail/detail.js +++ b/src/modules/mobilizer/detail/detail.js @@ -2,22 +2,36 @@ import React, { useContext, useEffect, useState } from 'react'; import { useToasts } from 'react-toast-notifications'; import Select from 'react-select'; import { Link } from 'react-router-dom'; +import Loading from '../../global/Loading'; -import { Card, - CardBody, Row, Col, Input, ButtonGroup, Button, Table, CardSubtitle, CardTitle,Label,FormGroup, - InputGroup, +import { + Card, + CardBody, + Row, + Col, + // Input, + // ButtonGroup, + Button, + Table, + CardSubtitle, + CardTitle, + Label, + FormGroup, Modal, ModalBody, ModalHeader, - ModalFooter } from 'reactstrap'; -import Swal from 'sweetalert2'; + ModalFooter +} from 'reactstrap'; +// import Swal from 'sweetalert2'; import { MobilizerContext } from '../../../contexts/MobilizerContext'; import { AppContext } from '../../../contexts/AppSettingsContext'; import profilePhoto from '../../../assets/images/users/user_avatar.svg'; -import IdPhoto from '../../../assets/images/id-icon-1.png'; -import UnlockWallet from '../../global/walletUnlock'; -import Loading from '../../global/Loading'; +// import IdPhoto from '../../../assets/images/id-icon-1.png'; +// import UnlockWallet from '../../global/walletUnlock'; +import PasscodeModal from '../../global/PasscodeModal'; +import MobilizerInfo from './mobilizerInfo'; +import BreadCrumb from '../../ui_components/breadcrumb'; const EXPLORER_URL = process.env.REACT_APP_BLOCKCHAIN_EXPLORER; const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY; @@ -29,12 +43,12 @@ export default function DetailsForm(props) { mobilizer, getMobilizerDetails, approveMobilizer, - changeMobilizerStatus, + // changeMobilizerStatus, getMobilizerBalance, getMobilizerTransactions, transactionHistory, getAvailableBalance, - listAid, + listAid } = useContext(MobilizerContext); const { appSettings, isVerified, changeIsverified } = useContext(AppContext); const [mobilizerBalance, setMobilizerBalance] = useState(''); @@ -43,11 +57,11 @@ export default function DetailsForm(props) { const togglePasscodeModal = () => setPasscodeModal(!passcodeModal); const [modal, setModal] = useState(false); const [projectOptions, setProjectOptions] = useState([]); - const [inputTokens, setInputTokens] = useState(null); + // const [inputTokens, setInputTokens] = useState(null); const [selectedProject, setSelectedProject] = useState(''); const [availableBalance, setAvailableBalance] = useState(null); const [showAlert, setShowAlert] = useState(false); - + const [fetchingBalance, setFetchingBalance] = useState(false); const loadMobilizerDetails = () => { getMobilizerDetails(mobilizerId) @@ -66,9 +80,12 @@ export default function DetailsForm(props) { const getBalance = async wallet => { if (appSettings && appSettings.agency) { try { + setFetchingBalance(true); + const { token } = appSettings.agency.contracts; let d = await getMobilizerBalance(token, wallet); setMobilizerBalance(d); + setFetchingBalance(false); } catch { addToast('Invalid Mobilizer wallet address!', { appearance: 'error', @@ -78,36 +95,35 @@ export default function DetailsForm(props) { } }; - const handleChangeStatus = async status => { - let swal = await Swal.fire({ - title: 'Are you sure?', - text: `Mobilizer will be marked as ${status}`, - icon: 'warning', - showCancelButton: true, - confirmButtonColor: '#3085d6', - cancelButtonColor: '#d33', - confirmButtonText: 'Yes' - }); - if (swal.isConfirmed) { - try { - await changeMobilizerStatus(mobilizerId, status); - addToast(`Mobilizer marked as ${status}`, { - appearance: 'success', - autoDismiss: true - }); - } catch (e) { - addToast('Something went wrong on server!', { - appearance: 'error', - autoDismiss: true - }); - } - } - }; - - const handleTokenChange = e => { - setInputTokens(e.target.value); - }; - + // const handleChangeStatus = async status => { + // let swal = await Swal.fire({ + // title: 'Are you sure?', + // text: `Mobilizer will be marked as ${status}`, + // icon: 'warning', + // showCancelButton: true, + // confirmButtonColor: '#3085d6', + // cancelButtonColor: '#d33', + // confirmButtonText: 'Yes' + // }); + // if (swal.isConfirmed) { + // try { + // await changeMobilizerStatus(mobilizerId, status); + // addToast(`Mobilizer marked as ${status}`, { + // appearance: 'success', + // autoDismiss: true + // }); + // } catch (e) { + // addToast('Something went wrong on server!', { + // appearance: 'error', + // autoDismiss: true + // }); + // } + // } + // }; + + // const handleTokenChange = e => { + // setInputTokens(e.target.value); + // }; const handleSelectProject = async e => { try { @@ -163,12 +179,11 @@ export default function DetailsForm(props) { }; const resetTokenIssueForm = () => { - setInputTokens(null); + // setInputTokens(null); setAvailableBalance(''); setShowAlert(false); }; - const handleMobilizerApprove = async e => { e.preventDefault(); // let swal = await Swal.fire({ @@ -180,12 +195,11 @@ export default function DetailsForm(props) { // cancelButtonColor: '#d33', // confirmButtonText: 'Yes' // }); - // if (swal.isConfirmed) + // if (swal.isConfirmed) toggleModal(); togglePasscodeModal(); }; - const fetchProjectList = () => { listAid() .then(d => { @@ -223,10 +237,12 @@ export default function DetailsForm(props) { return ( <> - setPasscodeModal(e)}> - + +

Mobilizers

+ + {/* setPasscodeModal(e)}> */} Issue Token @@ -241,7 +257,6 @@ export default function DetailsForm(props) { placeholder="--Select Project--" />
- {showAlert && availableBalance > 0 ? ( @@ -262,24 +277,97 @@ export default function DetailsForm(props) { - - - <> - - - - - + <> + + +
- - +
+ +
+ + Mobilizer Detail + + + +
+
+ user +
+

{mobilizer ? mobilizer.name : ''}

+
Name
+
+
+ +
+ {loading ? ( + + ) : mobilizer_status === 'active' ? ( + + ) : ( + + )} + + + + + + + +
+ Token + +
+ {fetchingBalance ? :

{mobilizerBalance || 0}

} + +
Total Balance
+ +
+

0

+
Total Redeemed
+ + + + + + + + + {/*
@@ -428,7 +516,7 @@ export default function DetailsForm(props) { - + */} diff --git a/src/modules/mobilizer/detail/mobilizerInfo.js b/src/modules/mobilizer/detail/mobilizerInfo.js new file mode 100644 index 00000000..4849329a --- /dev/null +++ b/src/modules/mobilizer/detail/mobilizerInfo.js @@ -0,0 +1,79 @@ +import React from 'react'; +import { Card, CardTitle, Col, Row } from 'reactstrap'; +import '../../../assets/css/project.css'; +import image from '../../../assets/images/id-icon-1.png'; +import { History } from '../../../utils/History'; + +const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY; + +export default function MobilizerInfo(props) { + const { information } = props; + const { id } = props.information; + const handleEditClick = () => History.push(`/edit-mobilizer/${id}`); + return ( +
+ +
+
+ + More Information + +
+ +
+
+ +
+
+

{information.email || '-'}

+
Email
+
+
+

{information.bank_name || '-'}

+
Government ID
+
+
+

{information.wallet_address || '-'}

+
Wallet address
+
+ +
+
+

{information.phone || '-'}

+
Phone number
+
+
+

{information.address || '-'}

+
Address
+
+
+

{information.organization || '-'}

+
Organization
+
+ +
+ certificate + + + + + + ); +} diff --git a/src/modules/mobilizer/edit/edit.js b/src/modules/mobilizer/edit/edit.js new file mode 100644 index 00000000..a3a9c6b2 --- /dev/null +++ b/src/modules/mobilizer/edit/edit.js @@ -0,0 +1,232 @@ +import React, { useState, useEffect, useContext, useCallback } from 'react'; +import { Card, CardBody, Row, Col, Form, FormGroup, Label, Input, Button } from 'reactstrap'; +import { useToasts } from 'react-toast-notifications'; +import { MobilizerContext } from '../../../contexts/MobilizerContext'; +import { History } from '../../../utils/History'; +import { TOAST } from '../../../constants'; +import WalletUnlock from '../../../modules/global/walletUnlock'; +import AvatarIcon from '../../../assets/images/download.png'; +import { blobToBase64 } from '../../../utils'; +import BreadCrumb from '../../ui_components/breadcrumb'; + +const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY; + +const Edit = ({ mobilizerId }) => { + const { addToast } = useToasts(); + const { updateMobilizer, getMobilizerDetails } = useContext(MobilizerContext); + const [passcodeModal, setPasscodeModal] = useState(false); + const [loading, setLoading] = useState(false); + const [formData, setFormData] = useState({ + name: '', + phone: '', + email: '', + address: '', + wallet_address: '', + organization: '' + }); + + const [govtIdentity, setGovtIdentity] = useState(''); + const [profileUpload, setProfileUpload] = useState(''); + const [existingProfilePhoto, setExistingProfilePhoto] = useState(''); + const [existingIdentity, setExistingIdentity] = useState(''); + + const handleInputChange = e => { + console.log('e', e.target.value); + console.log('e', e.target.name); + const value = { ...formData, [e.target.name]: e.target.value }; + console.log('value', value); + + setFormData({ name: e.target.value }); + }; + const handleCancelClick = () => History.push('/mobilizers'); + + async function handleProfileUpload(e) { + const base64Url = await blobToBase64(e.target.files[0]); + setProfileUpload(base64Url); + } + + async function handleGovtIdentity(e) { + const base64Url = await blobToBase64(e.target.files[0]); + setGovtIdentity(base64Url); + } + + const handleFormSubmit = e => { + e.preventDefault(); + console.log('form data', formData); + const payload = { ...formData }; + if (profileUpload) payload.photo = profileUpload; + if (govtIdentity) payload.govt_id_image = govtIdentity; + + setLoading(true); + updateMobilizer(mobilizerId, payload) + .then(() => { + setLoading(false); + addToast('Mobilizer updated successfully', TOAST.SUCCESS); + History.push('/mobilizers'); + }) + .catch(err => { + setLoading(false); + addToast(err.message, TOAST.ERROR); + }); + }; + + const loadMobilizersDetails = useCallback(async () => { + const d = await getMobilizerDetails(mobilizerId); + const { name, phone, email, address, wallet_address, organization, photo, govt_id_image } = d; + + if (photo && photo.length) setExistingProfilePhoto(photo[0]); + if (govt_id_image) setExistingIdentity(govt_id_image); + + setFormData({ + name, + phone, + email, + address, + wallet_address, + organization + }); + }, [getMobilizerDetails, mobilizerId]); + + useEffect(() => { + loadMobilizersDetails(); + }, [loadMobilizersDetails]); + + return ( +
+ setPasscodeModal(e)}> +

Mobilizers

+ + + +
+ + +
+ +
+ + +
+ {profileUpload ? ( + Profile + ) : existingProfilePhoto ? ( + Profile + ) : ( + Profile + )} + +
+ +
+ + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ {govtIdentity ? ( + Profile + ) : existingIdentity ? ( + Profile + ) : ( + Profile + )} + +
+ + + {loading ? ( + + ) : ( +
+ + +
+ )} +
+ + + + + + + ); +}; + +export default Edit; diff --git a/src/modules/mobilizer/edit/index.js b/src/modules/mobilizer/edit/index.js new file mode 100644 index 00000000..01be37b5 --- /dev/null +++ b/src/modules/mobilizer/edit/index.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { MobilizerContextProvider } from '../../../contexts/MobilizerContext'; +import EditVendor from './edit'; + +export default function Index({ match }) { + const { id } = match.params; + return ( + <> + + + + + ); +} diff --git a/src/modules/mobilizer/list.js b/src/modules/mobilizer/list.js index 4989cdcd..820c2c8e 100644 --- a/src/modules/mobilizer/list.js +++ b/src/modules/mobilizer/list.js @@ -2,6 +2,7 @@ import React, { useState, useEffect, useContext } from 'react'; import { MobilizerContext } from '../../contexts/MobilizerContext'; import { useToasts } from 'react-toast-notifications'; import { Link } from 'react-router-dom'; +import { History } from '../../utils/History'; import { Card, @@ -40,9 +41,7 @@ const Mobilizer = () => { const toggle = () => setModel(!model); const fetchList = query => { - console.log('LSIISHD'); let params = { ...pagination, ...query }; - console.log({ params }); listMobilizer(params) .then() .catch(() => { @@ -88,6 +87,8 @@ const Mobilizer = () => { return fetchList({ start: _start, limit: pagination.limit }); }; + const handleAddClick = () => History.push('/add-mobilizers'); + return (
@@ -108,7 +109,7 @@ const Mobilizer = () => { name="customSelect" defaultValue="" onChange={handleFilterChange} - style={{ width: 'auto' }} + style={{ width: 'auto', marginRight: '5px' }} > @@ -124,7 +125,7 @@ const Mobilizer = () => {
-
@@ -135,6 +136,7 @@ const Mobilizer = () => {
S.N. Name Phone Address
{(pagination.currentPage - 1) * pagination.limit + i + 1}
From f2867c60a8a8ef461dcaac5202ac25d45b32fb31 Mon Sep 17 00:00:00 2001 From: Binod Chaudhary Date: Tue, 24 Aug 2021 12:39:57 +0545 Subject: [PATCH 39/59] vendor approve and cleanups --- src/contexts/VendorContext.js | 31 +++++-- src/modules/authentication/Wallet.js | 2 +- src/modules/vendor/detail/detail.js | 83 ++++++++++++++++--- .../vendors/detail/transactionHistory.js | 2 +- src/views/vendors/detail/vendorInfo.js | 1 - 5 files changed, 97 insertions(+), 22 deletions(-) diff --git a/src/contexts/VendorContext.js b/src/contexts/VendorContext.js index 98482cdf..d2c0b2b9 100644 --- a/src/contexts/VendorContext.js +++ b/src/contexts/VendorContext.js @@ -50,15 +50,28 @@ export const VendorContextProvider = ({ children }) => { }); } - async function approveVendor(payload) { - const { rahat: rahatContractAddr } = appSettings.agency.contracts; - const res = await Service.approveVendor(wallet, payload, rahatContractAddr); - changeIsverified(false); - if (res) { - setVendor(res.data); - return res.data; - } - } + const approveVendor = useCallback( + async payload => { + const { rahat: rahatContractAddr } = appSettings.agency.contracts; + const res = await Service.approveVendor(wallet, payload, rahatContractAddr); + changeIsverified(false); + if (res) { + setVendor(res.data); + return res.data; + } + }, + [appSettings.agency.contracts, changeIsverified, wallet] + ); + + // async function approveVendor(payload) { + // const { rahat: rahatContractAddr } = appSettings.agency.contracts; + // const res = await Service.approveVendor(wallet, payload, rahatContractAddr); + // changeIsverified(false); + // if (res) { + // setVendor(res.data); + // return res.data; + // } + // } async function changeVendorStatus(vendorId, status) { const res = await Service.changeVendorStaus(vendorId, status); diff --git a/src/modules/authentication/Wallet.js b/src/modules/authentication/Wallet.js index 178c0ec6..a500739f 100644 --- a/src/modules/authentication/Wallet.js +++ b/src/modules/authentication/Wallet.js @@ -109,7 +109,7 @@ const Wallet = () => { }, QR_REFRESH_TIME); return () => clearTimeout(timer); - }, [generateQR, tempIdentity.privateKey, refreshCounter]); + }, [generateQR, addToast, tempIdentity.privateKey, refreshCounter]); const handleRefreshQrCode = () => setRefreshCounter(refreshCounter + 1); diff --git a/src/modules/vendor/detail/detail.js b/src/modules/vendor/detail/detail.js index c53a46bd..843957b5 100644 --- a/src/modules/vendor/detail/detail.js +++ b/src/modules/vendor/detail/detail.js @@ -1,5 +1,7 @@ import React, { useContext, useCallback, useEffect, useState } from 'react'; import { Row, Col, Card, CardTitle } from 'reactstrap'; +import { useToasts } from 'react-toast-notifications'; + import VendorInfo from './vendorInfo'; import ProjectInvovled from '../../ui_components/projects'; import TransactionHistory from './transactionHistory'; @@ -8,19 +10,51 @@ import { AppContext } from '../../../contexts/AppSettingsContext'; import displayPic from '../../../assets/images/users/user_avatar.svg'; import Loading from '../../global/Loading'; import BreadCrumb from '../../ui_components/breadcrumb'; +import PasscodeModal from '../../global/PasscodeModal'; +import { TOAST } from '../../../constants'; +import { History } from '../../../utils/History'; const Index = ({ params }) => { + const { addToast } = useToasts(); const { id } = params; - const { getVendorDetails, getVendorTransactions, getVendorBalance } = useContext(VendorContext); - const { appSettings } = useContext(AppContext); + + const { getVendorDetails, getVendorTransactions, getVendorBalance, approveVendor } = useContext(VendorContext); + const { isVerified, wallet, appSettings } = useContext(AppContext); const [basicInfo, setBasicInfo] = useState({}); const [projectList, setProjectList] = useState([]); const [transactionList, setTransactionList] = useState([]); + const [loading, setLoading] = useState(false); const [fetchingBlockchain, setFetchingBlockChain] = useState(false); const [fetchingBalance, setFetchingBalance] = useState(false); const [vendorBalance, setVendorBalance] = useState('0'); + const [passcodeModal, setPasscodeModal] = useState(false); + const [vendorStatus, setVendorStatus] = useState(''); + + const togglePasscodeModal = () => setPasscodeModal(!passcodeModal); + + const handleApproveVendor = useCallback(async () => { + setPasscodeModal(false); + const { wallet_address } = basicInfo; + try { + const payload = { + status: 'active', + wallet_address: wallet_address, + vendorId: id + }; + setLoading(true); + const approved = await approveVendor(payload); + if (approved) { + setLoading(false); + addToast('Vendor approved successfully', TOAST.SUCCESS); + History.push('/vendors'); + } + } catch (err) { + setLoading(false); + addToast(err.message, TOAST.ERROR); + } + }, [addToast, approveVendor, basicInfo, id]); const fetchVendorBalance = useCallback( async wallet_address => { @@ -36,6 +70,7 @@ const Index = ({ params }) => { const fetchVendorDetails = useCallback(async () => { const details = await getVendorDetails(id); if (details) { + setVendorStatus(details.agencies[0].status); setBasicInfo(details); await fetchVendorBalance(details.wallet_address); } @@ -63,12 +98,19 @@ const Index = ({ params }) => { }, [fetchVendorDetails]); useEffect(() => { - console.log('VD Effect...'); fetchVendorTransactions(); }, [fetchVendorTransactions]); + useEffect(() => { + if (isVerified && wallet) { + handleApproveVendor(); + } + }, [handleApproveVendor, isVerified, wallet]); + return ( <> + +

Vendors

@@ -90,13 +132,34 @@ const Index = ({ params }) => {
+ @@ -159,9 +161,9 @@ const Mobilizer = () => { - diff --git a/src/modules/vendor/add/add.js b/src/modules/vendor/add/add.js index 94d0a0eb..c9bd942c 100644 --- a/src/modules/vendor/add/add.js +++ b/src/modules/vendor/add/add.js @@ -270,7 +270,7 @@ const Add = () => { - +
{extras.signature_photo ? ( { alt="user" className="rounded-circle" width="45" + height="45" />

{basicInfo.name}

diff --git a/src/modules/vendor/detail/vendorInfo.js b/src/modules/vendor/detail/vendorInfo.js index 11692034..6f9f9276 100644 --- a/src/modules/vendor/detail/vendorInfo.js +++ b/src/modules/vendor/detail/vendorInfo.js @@ -79,7 +79,9 @@ export default function VendorInfo(props) {
certificate { return select_options; }; - const loadBeneficiaryDetails = useCallback(async () => { + const loadVendorsDetails = useCallback(async () => { const d = await getVendorDetails(vendorId); const { projects, @@ -184,8 +184,8 @@ const Edit = ({ vendorId }) => { useEffect(() => { loadProjects(); - loadBeneficiaryDetails(); - }, [loadBeneficiaryDetails, loadProjects]); + loadVendorsDetails(); + }, [loadVendorsDetails, loadProjects]); return (
@@ -277,6 +277,7 @@ const Edit = ({ vendorId }) => { value={formData.wallet_address} name="wallet_address" onChange={handleInputChange} + disabled required /> diff --git a/src/modules/vendor/edit/index.js b/src/modules/vendor/edit/index.js index bcff9c77..282c14cc 100644 --- a/src/modules/vendor/edit/index.js +++ b/src/modules/vendor/edit/index.js @@ -1,5 +1,4 @@ import React from 'react'; - import { VendorContextProvider } from '../../../contexts/VendorContext'; import EditVendor from './edit'; diff --git a/src/modules/vendor/list.js b/src/modules/vendor/list.js index 259815f9..b36b85a0 100644 --- a/src/modules/vendor/list.js +++ b/src/modules/vendor/list.js @@ -3,6 +3,7 @@ import { VendorContext } from '../../contexts/VendorContext'; import { useToasts } from 'react-toast-notifications'; import { Link } from 'react-router-dom'; import { History } from '../../utils/History'; +import moment from 'moment'; import { Card, @@ -94,21 +95,33 @@ const Vendor = () => { -
Vendors - + Vendors +
+ + + + + @@ -137,7 +150,9 @@ const Vendor = () => {
+ + @@ -158,11 +173,14 @@ const Vendor = () => { + + + diff --git a/src/routes/Router.js b/src/routes/Router.js index dcc118cd..aabc81e6 100644 --- a/src/routes/Router.js +++ b/src/routes/Router.js @@ -22,6 +22,8 @@ const Onboard = lazy(() => import('../modules/onboard')); // Mobilizer const Mobilizer = lazy(() => import('../modules/mobilizer')); const MobilizerDetails = lazy(() => import('../modules/mobilizer/detail/index')); +const EditMobilizer = lazy(() => import('../modules/mobilizer/edit')); +const AddMobilizer = lazy(() => import('../modules/mobilizer/add')); // Project const AidList = lazy(() => import('../modules/aid/list')); @@ -175,7 +177,6 @@ let AppRoutes = [ component: Vendor, showInSidebar: true }, - { path: '/mobilizers', name: 'Mobilizers', @@ -183,6 +184,16 @@ let AppRoutes = [ component: Mobilizer, showInSidebar: true }, + { + path: '/add-mobilizers', + name: 'AddMobilizer', + component: AddMobilizer + }, + { + path: '/edit-mobilizer/:id', + name: 'Mobilizer', + component: EditMobilizer + }, { path: '/add_user', name: 'Users', diff --git a/src/services/mobilizer.js b/src/services/mobilizer.js index c742c935..6df5cd18 100644 --- a/src/services/mobilizer.js +++ b/src/services/mobilizer.js @@ -1,146 +1,157 @@ -import axios from "axios"; +import axios from 'axios'; -import API from "../constants/api"; -import { getUserToken } from "../utils/sessionManager"; -import CONTRACT from "../constants/contracts"; -import { getContractByProvider } from "../blockchain/abi"; +import API from '../constants/api'; +import { getUserToken } from '../utils/sessionManager'; +import CONTRACT from '../constants/contracts'; +import { getContractByProvider } from '../blockchain/abi'; const access_token = getUserToken(); const faucet_auth_token = process.env.REACT_APP_BLOCKCHAIN_FAUCET_AUTH_TOKEN; -const mapTestContract = (contract) => ({ - addAdmin: contract.addAdmin, - balanceOf: contract.balanceOf, +const mapTestContract = contract => ({ + addAdmin: contract.addAdmin, + balanceOf: contract.balanceOf }); export async function getMobilizerBalance(contract_address, wallet_addr) { - const contract = await getContractByProvider(contract_address, CONTRACT.AIDTOKEN); - const myContract = mapTestContract(contract); - const data = await myContract.balanceOf(wallet_addr); - if (!data) return "Mobilizer not found!"; - return data.toNumber(); + const contract = await getContractByProvider(contract_address, CONTRACT.AIDTOKEN); + const myContract = mapTestContract(contract); + const data = await myContract.balanceOf(wallet_addr); + if (!data) return 'Mobilizer not found!'; + return data.toNumber(); } -export async function approveMobilizer(wallet, payload,contract_address) { - try{ - const contract = await getContractByProvider(contract_address, CONTRACT.RAHAT); - const signerContract = contract.connect(wallet); - const myContract = mapTestContract(signerContract); - const data = await myContract.addAdmin(payload.wallet_address); - if (!data) return "Mobilizer approve failed!"; - const res = await approveMobilizerToProject(payload.wallet_address, payload.projectId); - getEth({address:payload.wallet_address}); - return res; - } - catch(e){ - console.log(e); - throw Error(e); - } +export async function approveMobilizer(wallet, payload, contract_address) { + try { + const contract = await getContractByProvider(contract_address, CONTRACT.RAHAT); + const signerContract = contract.connect(wallet); + const myContract = mapTestContract(signerContract); + const data = await myContract.addAdmin(payload.wallet_address); + if (!data) return 'Mobilizer approve failed!'; + const res = await approveMobilizerToProject(payload.wallet_address, payload.projectId); + getEth({ address: payload.wallet_address }); + return res; + } catch (e) { + console.log(e); + throw Error(e); + } } export async function changeMobilizerStaus(mobilizerId, status) { - return axios.patch( - `${API.MOBILIZERS}/${mobilizerId}/status/`, - { status: status }, - { - headers: { access_token: access_token }, - } - ); + return axios.patch( + `${API.MOBILIZERS}/${mobilizerId}/status/`, + { status: status }, + { + headers: { access_token: access_token } + } + ); } - -export async function approveMobilizerToProject(wallet_address,projectId){ - return axios.patch( - `${API.MOBILIZERS}/approve/`, - { wallet_address,projectId }, - { - headers: { access_token: access_token }, - } - ); +export async function approveMobilizerToProject(wallet_address, projectId) { + return axios.patch( + `${API.MOBILIZERS}/approve/`, + { wallet_address, projectId }, + { + headers: { access_token: access_token } + } + ); } export async function list(params) { - console.log("MOBILIXERS") - const res = await axios({ - url: API.MOBILIZERS, - method: "get", - headers: { - access_token, - }, - params, - }); - return res.data; + console.log('MOBILIXERS'); + const res = await axios({ + url: API.MOBILIZERS, + method: 'get', + headers: { + access_token + }, + params + }); + return res.data; } export async function get(id) { - const res = await axios({ - url: API.MOBILIZERS + "/" + id, - method: "get", - headers: { - access_token, - }, - }); - return res.data; + const res = await axios({ + url: API.MOBILIZERS + '/' + id, + method: 'get', + headers: { + access_token + } + }); + return res.data; } export async function mobilizerTransactions(mobilizerId) { - const res = await axios({ - url: `${API.MOBILIZERS}/${mobilizerId}/transactions`, - method: "get", - headers: { access_token }, - }); - return res.data; + const res = await axios({ + url: `${API.MOBILIZERS}/${mobilizerId}/transactions`, + method: 'get', + headers: { access_token } + }); + return res.data; } export async function listByAid(aid, params) { - const res = await axios({ - url: API.MOBILIZERS + `/aid/${aid}/mobilizer`, - method: "get", - headers: { - access_token, - }, - params, - }); - return res.data; + const res = await axios({ + url: API.MOBILIZERS + `/aid/${aid}/mobilizer`, + method: 'get', + headers: { + access_token + }, + params + }); + return res.data; } export function add(payload) { - return new Promise((resolve, reject) => { - axios - .post(`${API.MOBILIZERS}`, payload, { - headers: { access_token: access_token }, - }) - .then((res) => { - if (res.statusText === "OK") { - resolve(res.data); - } - reject(res.data); - }) - .catch((err) => { - reject(err); - }); - }); + return new Promise((resolve, reject) => { + axios + .post(`${API.MOBILIZERS}`, payload, { + headers: { access_token: access_token } + }) + .then(res => { + if (res.statusText === 'OK') { + resolve(res.data); + } + reject(res.data); + }) + .catch(err => { + reject(err); + }); + }); +} + +export async function updateMobilizer(id, body) { + const res = await axios({ + url: `${API.MOBILIZERS}/${id}`, + method: 'put', + headers: { + access_token + }, + data: body + }); + + return res.data; } export async function approve({ mobilizerId }) { - const res = await axios({ - url: API.MOBILIZERS + `/approve`, - method: "post", - headers: { - access_token, - }, - data: { mobilizerId }, - }); - - return res.data; + const res = await axios({ + url: API.MOBILIZERS + `/approve`, + method: 'post', + headers: { + access_token + }, + data: { mobilizerId } + }); + + return res.data; } export async function getEth({ address }) { - const res = await axios({ - url: API.FAUCET, - method: "post", - data: { address,token:faucet_auth_token }, - }); + const res = await axios({ + url: API.FAUCET, + method: 'post', + data: { address, token: faucet_auth_token } + }); - return res.data; + return res.data; } From 4b5a06c04f1b5d101cdc6a1d547234dc3ef6c526 Mon Sep 17 00:00:00 2001 From: rojanbade Date: Fri, 3 Sep 2021 16:38:06 +0545 Subject: [PATCH 52/59] formdata issue resolved --- src/contexts/MobilizerContext.js | 6 +++--- src/modules/mobilizer/edit/edit.js | 17 +++-------------- src/modules/vendor/list.js | 9 +++++++-- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/contexts/MobilizerContext.js b/src/contexts/MobilizerContext.js index 04078280..e4f82b09 100644 --- a/src/contexts/MobilizerContext.js +++ b/src/contexts/MobilizerContext.js @@ -1,4 +1,4 @@ -import React, { createContext, useReducer, useContext } from 'react'; +import React, { createContext, useReducer, useContext, useCallback } from 'react'; import mobilizerReduce from '../reducers/mobilizerReducer'; import * as Service from '../services/mobilizer'; import * as AidService from '../services/aid'; @@ -74,11 +74,11 @@ export const MobilizerContextProvider = ({ children }) => { dispatch({ type: ACTION.SET_MOBILIZER, data: b }); } - async function getMobilizerDetails(id) { + const getMobilizerDetails = useCallback(async id => { const data = await Service.get(id); setMobilizer(data); return data; - } + }, []); function addMobilizer(payload) { return new Promise((resolve, reject) => { diff --git a/src/modules/mobilizer/edit/edit.js b/src/modules/mobilizer/edit/edit.js index a3a9c6b2..69ef6425 100644 --- a/src/modules/mobilizer/edit/edit.js +++ b/src/modules/mobilizer/edit/edit.js @@ -31,12 +31,7 @@ const Edit = ({ mobilizerId }) => { const [existingIdentity, setExistingIdentity] = useState(''); const handleInputChange = e => { - console.log('e', e.target.value); - console.log('e', e.target.name); - const value = { ...formData, [e.target.name]: e.target.value }; - console.log('value', value); - - setFormData({ name: e.target.value }); + setFormData({ ...formData, [e.target.name]: e.target.value }); }; const handleCancelClick = () => History.push('/mobilizers'); @@ -76,7 +71,6 @@ const Edit = ({ mobilizerId }) => { if (photo && photo.length) setExistingProfilePhoto(photo[0]); if (govt_id_image) setExistingIdentity(govt_id_image); - setFormData({ name, phone, @@ -132,13 +126,7 @@ const Edit = ({ mobilizerId }) => { - + @@ -171,6 +159,7 @@ const Edit = ({ mobilizerId }) => { value={formData.wallet_address} name="wallet_address" onChange={handleInputChange} + disabled /> diff --git a/src/modules/vendor/list.js b/src/modules/vendor/list.js index b36b85a0..0a930443 100644 --- a/src/modules/vendor/list.js +++ b/src/modules/vendor/list.js @@ -42,7 +42,7 @@ const Vendor = () => { const toggle = () => setModel(!model); const fetchList = query => { - let params = { ...pagination, ...query }; + let params = { start: pagination.start, limit: pagination.limit, ...query }; listVendor(params) .then() .catch(() => { @@ -72,6 +72,11 @@ const Vendor = () => { fetchList({ start: 0, limit: pagination.limit }); }; + const handleStatusChange = e => { + let { value } = e.target; + fetchList({ start: 0, limit: pagination.limit, status: value }); + }; + const handleSearchInputChange = e => { const { value } = e.target; if (filter.searchBy === searchOptions.PHONE) { @@ -108,7 +113,7 @@ const Vendor = () => { id="exampleCustomSelect" name="customSelect" defaultValue="" - // onChange={handleFilterChange} + onChange={handleStatusChange} style={{ width: 'auto', marginRight: '5px' }} > From f0b1d465c4afc4b4b840f9abef139032063239b9 Mon Sep 17 00:00:00 2001 From: rojanbade Date: Mon, 13 Sep 2021 17:58:57 +0545 Subject: [PATCH 53/59] Signup page UI --- src/contexts/BeneficiaryContext.js | 1 - src/modules/app.js | 72 +++++++++++----------- src/modules/authentication/SignUp.js | 85 ++++++++++++++++++++++++++ src/modules/authentication/Wallet.js | 6 +- src/modules/authentication/wallet.css | 5 ++ src/modules/beneficary/add/add.js | 4 +- src/modules/global/LiteModal.js | 49 +++++++++------ src/modules/mobilizer/detail/detail.js | 3 +- src/routes/Authroutes.js | 23 ++++--- 9 files changed, 177 insertions(+), 71 deletions(-) create mode 100644 src/modules/authentication/SignUp.js diff --git a/src/contexts/BeneficiaryContext.js b/src/contexts/BeneficiaryContext.js index 16e1c96b..80504fab 100644 --- a/src/contexts/BeneficiaryContext.js +++ b/src/contexts/BeneficiaryContext.js @@ -4,7 +4,6 @@ import * as Service from '../services/beneficiary'; import * as AidService from '../services/aid'; import ACTION from '../actions/beneficiary'; import { AppContext } from './AppSettingsContext'; -import { APP_CONSTANTS } from '../constants'; const initialState = { list: [], diff --git a/src/modules/app.js b/src/modules/app.js index bc953c31..140634a9 100644 --- a/src/modules/app.js +++ b/src/modules/app.js @@ -1,44 +1,40 @@ -import React from "react"; -import indexRoutes from "../routes"; -import { Router, Route, Switch } from "react-router-dom"; -import { ToastProvider } from "react-toast-notifications"; +import React from 'react'; +import indexRoutes from '../routes'; +import { Router, Route, Switch } from 'react-router-dom'; +import { ToastProvider } from 'react-toast-notifications'; -import { History } from "../utils/History"; -import { PrivateRoute } from "../routes/PrivateRoutes"; -import { AppContextProvider } from "../contexts/AppSettingsContext"; -import { UserContextProvider } from "../contexts/UserContext"; -import { AidContextProvider } from "../contexts/AidContext"; +import { History } from '../utils/History'; +import { PrivateRoute } from '../routes/PrivateRoutes'; +import { AppContextProvider } from '../contexts/AppSettingsContext'; +import { UserContextProvider } from '../contexts/UserContext'; +import { AidContextProvider } from '../contexts/AidContext'; -import AgencyRegistration from "../modules/agency/register"; -import AuthWallet from "../modules/authentication/Wallet"; -import PassportControl from "../modules/passport"; +import AgencyRegistration from '../modules/agency/register'; +import AuthWallet from '../modules/authentication/Wallet'; +import PassportControl from '../modules/passport'; +import SignUp from '../modules/authentication/SignUp'; const App = () => { - return ( - - - - - - - - - - {indexRoutes.map((prop, key) => { - return ( - - ); - })} - - - - - - - ); + return ( + + + + + + + + + + + {indexRoutes.map((prop, key) => { + return ; + })} + + + + + + + ); }; export default App; diff --git a/src/modules/authentication/SignUp.js b/src/modules/authentication/SignUp.js new file mode 100644 index 00000000..a7365cb9 --- /dev/null +++ b/src/modules/authentication/SignUp.js @@ -0,0 +1,85 @@ +import React, { useState } from 'react'; +import { Form, Row, Col, FormGroup, Label, Input, Button } from 'reactstrap'; +import Logo from '../../assets/images/rahat-logo-blue.png'; +// import { TOAST } from '../../constants'; +// import { useToasts } from 'react-toast-notifications'; + +export default function SignUp() { + // const { addToast } = useToasts(); + const [formData, setFormData] = useState({ + name: '', + email: '', + phone: '' + }); + const [loading, setLoading] = useState(false); + + const handleInputChange = e => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleFormSubmit = e => { + e.preventDefault(); + + // const payload = { ...formData }; + + setLoading(true); + // addBeneficiary(payload) + // .then(() => { + // setLoading(false); + // addToast('Account created successfully', TOAST.SUCCESS); + // History.push('/auth/wallet'); + // }) + // .catch(err => { + // setLoading(false); + // addToast(err.message, TOAST.ERROR); + // }); + }; + return ( + <> + + +
+ rahat logo +
+

+ Supporting vulnerable communities with a simple and efficient relief distribution platform. +

+
+
+ +
+
+

Rahat Agency App

+

Create an account

+
+
+ + + + + + + + + + + + + {loading ? ( + + ) : ( + + )} + +
+
+

Copyright © 2021 Rumsan Group of Companies | All Rights Reserved.

+ + + + ); +} diff --git a/src/modules/authentication/Wallet.js b/src/modules/authentication/Wallet.js index a500739f..61625286 100644 --- a/src/modules/authentication/Wallet.js +++ b/src/modules/authentication/Wallet.js @@ -8,6 +8,7 @@ import DataService from '../../services/db'; import './wallet.css'; import { useToasts } from 'react-toast-notifications'; import { TOAST } from '../../constants'; +import { Link } from 'react-router-dom'; const API_SERVER = process.env.REACT_APP_API_SERVER; const WSS_SERVER = API_SERVER.replace('http', 'ws'); @@ -128,7 +129,10 @@ const Wallet = () => {

- Haven’t registered? Sign up now + Haven’t registered?{' '} + + Sign up +

Rahat Agency App

diff --git a/src/modules/authentication/wallet.css b/src/modules/authentication/wallet.css index 5bc6ddab..ce3d922f 100644 --- a/src/modules/authentication/wallet.css +++ b/src/modules/authentication/wallet.css @@ -45,6 +45,11 @@ color: #fff; font-size: 14px; } +.text-subheader { + color: #fff; + font-size: 16px; + text-align: left; +} .text-tutorial { color: #3f9eeb; font-size: 14px; diff --git a/src/modules/beneficary/add/add.js b/src/modules/beneficary/add/add.js index 1d565d6a..addf2ef3 100644 --- a/src/modules/beneficary/add/add.js +++ b/src/modules/beneficary/add/add.js @@ -179,13 +179,13 @@ const Add = () => {
- + - + diff --git a/src/modules/global/LiteModal.js b/src/modules/global/LiteModal.js index e6f432e9..e5e5ab8c 100644 --- a/src/modules/global/LiteModal.js +++ b/src/modules/global/LiteModal.js @@ -1,28 +1,37 @@ -import React, { useState } from 'react'; -import { Form, Modal, ModalBody,ModalHeader } from 'reactstrap'; +import React from // ,{ useState } +'react'; +import { Form, Modal, ModalBody, ModalHeader } from 'reactstrap'; import PropTypes from 'prop-types'; -import useDigitInput from 'react-digit-input'; +// import useDigitInput from 'react-digit-input'; export default function CustomModal(props) { - const { children,toggle, title, loadingMessage, open, size, handleSubmit } = props; - const [value, onChange] = useState(''); - const digits = useDigitInput({ - acceptedCharacters: /^[0-9]$/, - length: 6, - value, - onChange - }); + const { + children, + // toggle, + title, + loadingMessage, + open, + size, + handleSubmit + } = props; + // const [value, onChange] = useState(''); + // const digits = useDigitInput({ + // acceptedCharacters: /^[0-9]$/, + // length: 6, + // value, + // onChange + // }); return ( <> - -
- {title || 'Modal Title'} - {children || 'No child elements supplied.'} -
- {loadingMessage ? loadingMessage : ''} -
- -
+ +
+ {title || 'Modal Title'} + {children || 'No child elements supplied.'} +
+ {loadingMessage ? loadingMessage : ''} +
+ +
); } diff --git a/src/modules/mobilizer/detail/detail.js b/src/modules/mobilizer/detail/detail.js index c5ddced5..c7c67420 100644 --- a/src/modules/mobilizer/detail/detail.js +++ b/src/modules/mobilizer/detail/detail.js @@ -334,7 +334,8 @@ export default function DetailsForm(props) { ) : (
- rahat logo + rahat logo +

Supporting vulnerable communities with a simple and efficient relief distribution platform. @@ -51,8 +91,12 @@ export default function SignUp() {

Rahat Agency App

Create an account

-
+
+ + + + diff --git a/src/modules/authentication/Wallet.js b/src/modules/authentication/Wallet.js index 61625286..9211c074 100644 --- a/src/modules/authentication/Wallet.js +++ b/src/modules/authentication/Wallet.js @@ -77,7 +77,12 @@ const Wallet = () => { ws.current.onmessage = async e => { const data = JSON.parse(e.data); if (data.action === 'unauthorized') { - return addToast('User not authorized!', TOAST.ERROR); + addToast('User not authorized! please Signup', TOAST.WARNING); + window.location.replace(`/sign_up?wallet_address=${data.publicKey}`); + } + if (data.action === 'account-locked') { + addToast('User not activated! ', TOAST.WARNING); + window.location.replace(`/approval?wallet_address=${data.publicKey}`); } if (data.data && data.data.token) { const { id, token } = data.data; diff --git a/src/modules/mobilizer/list.js b/src/modules/mobilizer/list.js index 820c2c8e..aae0a99a 100644 --- a/src/modules/mobilizer/list.js +++ b/src/modules/mobilizer/list.js @@ -41,7 +41,7 @@ const Mobilizer = () => { const toggle = () => setModel(!model); const fetchList = query => { - let params = { ...pagination, ...query }; + let params = { start:pagination.start,limit:pagination.limit, ...query }; listMobilizer(params) .then() .catch(() => { diff --git a/src/modules/user/edit/index.js b/src/modules/user/edit/index.js index c1c39fb7..2f988963 100644 --- a/src/modules/user/edit/index.js +++ b/src/modules/user/edit/index.js @@ -1,6 +1,6 @@ import React, { useCallback, useState, useContext, useEffect } from 'react'; import { useToasts } from 'react-toast-notifications'; -import { Card, CardBody, CardTitle, Row, Col, Form, FormGroup, Label, Input, Button } from 'reactstrap'; +import { Card, CardBody, CardTitle,CardSubtitle, Row, Col, Form, FormGroup, Label, Input, Button } from 'reactstrap'; import { TOAST, ROLES } from '../../../constants'; import { UserContext } from '../../../contexts/UserContext'; @@ -90,11 +90,11 @@ const UserDetails = props => { }; const sanitizeAndSetRoles = roles => { + if(!roles) return; let data = roles.map(d => { return { label: d, value: d }; }); - if (data.length > 1) data = data.filter(f => f.value === ROLES.ADMIN); - + //if (data.length > 1) data = data.filter(f => f.value === ROLES.ADMIN); setExsitingRoles(data); }; @@ -214,23 +214,29 @@ const UserDetails = props => { User Roles + {existingRoles.length > 0 ?( + existingRoles.map((roles)=>{ + return {roles.label} + }) + ):( + '' + )}
+ - {existingRoles.length > 0 ? ( + - ) : ( - '' - )} + diff --git a/src/services/users.js b/src/services/users.js index d1876fd5..96c7cdce 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -42,6 +42,25 @@ export function verifyToken(token) { }); } +export function signUp(payload) { + return new Promise((resolve, reject) => { + axios + .post(`${API.USERS}/register`,payload) + .then(res => { + if (res) { + resolve({ sucess: true, status: 200 }); + } + resolve({ + success: false, + status: 500 + }); + }) + .catch(err => { + reject(err.response.data); + }); + }); +} + export function loginUsingMetamask(payload) { return new Promise((resolve, reject) => { axios From b0ef781ea5a7e8b0172d531caf2e51c385528f42 Mon Sep 17 00:00:00 2001 From: Manjik Shrestha Date: Tue, 14 Sep 2021 04:32:27 +0545 Subject: [PATCH 55/59] user wait for approval --- src/modules/authentication/Approval.js | 40 ++++++++++++++++++++++++++ src/routes/Authroutes.js | 7 +++++ 2 files changed, 47 insertions(+) create mode 100644 src/modules/authentication/Approval.js diff --git a/src/modules/authentication/Approval.js b/src/modules/authentication/Approval.js new file mode 100644 index 00000000..4991d3f0 --- /dev/null +++ b/src/modules/authentication/Approval.js @@ -0,0 +1,40 @@ +import React, { useState } from 'react'; + +import { Row, Col} from 'reactstrap'; +import Logo from '../../assets/images/rahat-logo-blue.png'; +import qs from 'query-string'; + + + +export default function SignUp(props) { + const search = qs.parse(props.location.search); + + const[wallet,setWallet]=useState(search.wallet_address) + return ( + <> + + +
+ rahat logo + +
+

+ Supporting vulnerable communities with a simple and efficient relief distribution platform. +

+
+
+ +
+
+

Rahat Agency App

+

Account not Activated

+ {wallet &&

Wallet: {wallet}

} + +

Please contact hello@esatya.io to activate your account

+
+

Copyright © 2021 Rumsan Group of Companies | All Rights Reserved.

+ + + + ); +} diff --git a/src/routes/Authroutes.js b/src/routes/Authroutes.js index a854e942..cd903b8c 100644 --- a/src/routes/Authroutes.js +++ b/src/routes/Authroutes.js @@ -1,6 +1,8 @@ import { lazy } from 'react'; const WalletLogin = lazy(() => import('../modules/authentication/Wallet')); const SignUp = lazy(() => import('../modules/authentication/SignUp')); +const Approval = lazy(() => import('../modules/authentication/Approval')); + var AuthRoutes = [ { @@ -13,6 +15,11 @@ var AuthRoutes = [ path: '/sign_up', name: 'SignUp', component: SignUp + }, + { + path: '/approval', + name: 'Approval', + component: Approval } ]; export default AuthRoutes; From 2f4f491fba8ab1215e366cf5509be85400a2035a Mon Sep 17 00:00:00 2001 From: rojanbade Date: Fri, 24 Sep 2021 10:52:54 +0545 Subject: [PATCH 56/59] Issue resolved and transaction chart added --- package.json | 2 + src/contexts/BeneficiaryContext.js | 24 +---- src/modules/beneficary/add/add.js | 6 +- src/modules/beneficary/edit/edit.js | 11 ++- src/modules/dashboard/Dashboard.js | 4 +- src/modules/global/PasscodeModal.js | 2 +- src/modules/mobilizer/detail/detail.js | 10 ++ src/modules/mobilizer/detail/mobilizerInfo.js | 2 +- src/modules/ui_components/chart/index.js | 95 +++++++++++++++++++ src/modules/vendor/detail/vendorInfo.js | 10 +- yarn.lock | 76 ++++++++++++++- 11 files changed, 206 insertions(+), 36 deletions(-) create mode 100644 src/modules/ui_components/chart/index.js diff --git a/package.json b/package.json index 2510668b..11c6edd2 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ }, "dependencies": { "add": "^2.0.6", + "apexcharts": "^3.28.3", "axios": "^0.21.1", "bootstrap-switch-button-react": "^1.2.0", "chart.js": "^2.9.3", @@ -52,6 +53,7 @@ "qrcode.react": "^1.0.0", "query-string": "^6.13.1", "react": "^16.13.1", + "react-apexcharts": "1.3.7", "react-chartjs-2": "^2.10.0", "react-digit-input": "^2.1.0", "react-dom": "^16.13.1", diff --git a/src/contexts/BeneficiaryContext.js b/src/contexts/BeneficiaryContext.js index 80504fab..88041265 100644 --- a/src/contexts/BeneficiaryContext.js +++ b/src/contexts/BeneficiaryContext.js @@ -74,23 +74,6 @@ export const BeneficiaryContextProvider = ({ children }) => { return Service.updateBeneficiary(id, payload); }; - // const addBeneficiary = async event => { - // event.preventDefault(); - // const formData = new FormData(event.target); - - // let payload = { - // name: formData.get('name'), - // phone: formData.get('phone'), - // govt_id: formData.get('govt_id'), - // email: formData.get('email'), - // address: formData.get('address'), - // wallet_address: formData.get('wallet_address'), - // project_id: formData.get('aid') - // }; - // let d = await Service.addBeneficiary(payload); - // return d; - // }; - async function listBeneficiary(params) { let res = await Service.listBeneficiary(params); if (res) { @@ -107,8 +90,9 @@ export const BeneficiaryContextProvider = ({ children }) => { for (let b of beneficiaries) await Service.addBeneficiary(b); }; - const getBeneficiaryDetails = useCallback(id => { - return Service.get(id); + const getBeneficiaryDetails = useCallback(async id => { + const data = await Service.get(id); + return data; }, []); return ( @@ -119,7 +103,7 @@ export const BeneficiaryContextProvider = ({ children }) => { list: state.list, pagination: state.pagination, tokenBalance: state.tokenBalance, - beneficiary_detail: state.beneficiary, + beneficiary: state.beneficiary, clear, setAid, listProject, diff --git a/src/modules/beneficary/add/add.js b/src/modules/beneficary/add/add.js index addf2ef3..42c32f10 100644 --- a/src/modules/beneficary/add/add.js +++ b/src/modules/beneficary/add/add.js @@ -12,7 +12,7 @@ import { blobToBase64 } from '../../../utils'; const Add = () => { const { addToast } = useToasts(); - const { listAid, addBeneficiary } = useContext(BeneficiaryContext); + const { listProject, addBeneficiary } = useContext(BeneficiaryContext); const [formData, setFormData] = useState({ name: '', @@ -96,7 +96,7 @@ const Add = () => { const handleCancelClick = () => History.push('/beneficiaries'); const loadProjects = useCallback(async () => { - const projects = await listAid(); + const projects = await listProject(); if (projects && projects.data.length) { const select_options = projects.data.map(p => { return { @@ -106,7 +106,7 @@ const Add = () => { }); setProjectList(select_options); } - }, [listAid]); + }, [listProject]); useEffect(() => { loadProjects(); diff --git a/src/modules/beneficary/edit/edit.js b/src/modules/beneficary/edit/edit.js index a57aff2f..a7957c44 100644 --- a/src/modules/beneficary/edit/edit.js +++ b/src/modules/beneficary/edit/edit.js @@ -14,7 +14,7 @@ const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY; const Edit = ({ beneficiaryId }) => { const { addToast } = useToasts(); - const { listAid, updateBeneficiary, getBeneficiaryDetails } = useContext(BeneficiaryContext); + const { listProject, updateBeneficiary, getBeneficiaryDetails } = useContext(BeneficiaryContext); const [formData, setFormData] = useState({ name: '', @@ -60,6 +60,7 @@ const Edit = ({ beneficiaryId }) => { }; const handleInputChange = e => { + console.log('event', e.target.name, e.target.value); setFormData({ ...formData, [e.target.name]: e.target.value }); }; @@ -125,19 +126,19 @@ const Edit = ({ beneficiaryId }) => { } setExtras(extras); if (extras.group) setSelectedGroup(extras.group); - delete d.extras; + // delete d.extras; setFormData({ name, phone, email, address, address_temporary, govt_id }); const { gender } = d; if (gender !== 'U') setSelectedGender(gender); - }, [beneficiaryId, getBeneficiaryDetails]); + }, [getBeneficiaryDetails, beneficiaryId]); const loadProjects = useCallback(async () => { - const projects = await listAid(); + const projects = await listProject(); if (projects && projects.data.length) { const select_options = createProjectSelectOptions(projects.data); setProjectList(select_options); } - }, [listAid]); + }, [listProject]); useEffect(() => { loadProjects(); diff --git a/src/modules/dashboard/Dashboard.js b/src/modules/dashboard/Dashboard.js index ba388340..c0909da6 100644 --- a/src/modules/dashboard/Dashboard.js +++ b/src/modules/dashboard/Dashboard.js @@ -1,13 +1,12 @@ import React, { useContext, useState, useEffect } from 'react'; import { Row, Col } from 'reactstrap'; import { useToasts } from 'react-toast-notifications'; - import TokenByProject from './tokens_by_project'; import BeneficiaryByProject from './beneficiary_by_project'; import { StatsCard } from '../ui_components/cards'; import { TOAST } from '../../constants'; - import { UserContext } from '../../contexts/UserContext'; +import TransactionChart from '../ui_components/chart'; const Dashboard = () => { const { addToast } = useToasts(); @@ -127,6 +126,7 @@ const Dashboard = () => { /> + ); }; diff --git a/src/modules/global/PasscodeModal.js b/src/modules/global/PasscodeModal.js index 0e42e544..e8d72406 100644 --- a/src/modules/global/PasscodeModal.js +++ b/src/modules/global/PasscodeModal.js @@ -32,7 +32,7 @@ export default function PasscodeModal(props) { changeIsverified(true); setLoadingMessage('Wallet verified!'); } else { - setLoadingMessage('Verifying wallet, please wallet...'); + setLoadingMessage('Verifying wallet, please wait...'); const w = await Wallet.loadWallet(input_value); setWalletPasscode(input_value); changeIsverified(true); diff --git a/src/modules/mobilizer/detail/detail.js b/src/modules/mobilizer/detail/detail.js index c7c67420..4f7697d3 100644 --- a/src/modules/mobilizer/detail/detail.js +++ b/src/modules/mobilizer/detail/detail.js @@ -3,6 +3,7 @@ import { useToasts } from 'react-toast-notifications'; import Select from 'react-select'; import { Link } from 'react-router-dom'; import Loading from '../../global/Loading'; +import ProjectInvovled from '../../ui_components/projects'; import { Card, @@ -62,12 +63,20 @@ export default function DetailsForm(props) { const [availableBalance, setAvailableBalance] = useState(null); const [showAlert, setShowAlert] = useState(false); const [fetchingBalance, setFetchingBalance] = useState(false); + const [projectList, setProjectList] = useState([]); const loadMobilizerDetails = () => { getMobilizerDetails(mobilizerId) .then(d => { getMobilizerTransactions(mobilizerId); getBalance(d.wallet_address); + + if (d.projects && d.projects.length) { + const projects = d.projects.map(d => { + return { id: d._id, name: d.name }; + }); + setProjectList(projects); + } }) .catch(() => { addToast('Something went wrong on server!', { @@ -367,6 +376,7 @@ export default function DetailsForm(props) { + {/*
diff --git a/src/modules/mobilizer/detail/mobilizerInfo.js b/src/modules/mobilizer/detail/mobilizerInfo.js index 4849329a..fff200c2 100644 --- a/src/modules/mobilizer/detail/mobilizerInfo.js +++ b/src/modules/mobilizer/detail/mobilizerInfo.js @@ -65,7 +65,7 @@ export default function MobilizerInfo(props) { ? `${IPFS_GATEWAY}/ipfs/${information.govt_id_image}` : image } - alt="certificate" + alt="goverment_id" width="90%" height="130px" className="card-data" diff --git a/src/modules/ui_components/chart/index.js b/src/modules/ui_components/chart/index.js new file mode 100644 index 00000000..0aa09f01 --- /dev/null +++ b/src/modules/ui_components/chart/index.js @@ -0,0 +1,95 @@ +import React from 'react'; +import { Card, CardTitle } from 'reactstrap'; +import '../../../assets/css/project.css'; +import Chart from 'react-apexcharts'; +// import Loading from '../../global/Loading'; + +//Line chart +const optionssalesummary = { + chart: { + id: 'basic-bar', + type: 'area', + toolbar: { + show: false + } + }, + + dataLabels: { + enabled: false + }, + stroke: { + curve: 'smooth', + width: 2 + }, + colors: ['#4fc3f7', '#7460ee'], + legend: { + show: false + }, + markers: { + size: 3 + }, + xaxis: { + categories: [1, 2, 3, 4, 5, 6, 7, 8, 9], + labels: { + show: true, + style: { + colors: ['#99abb4', '#99abb4', '#99abb4', '#99abb4', '#99abb4', '#99abb4', '#99abb4', '#99abb4'], + fontSize: '12px', + fontFamily: "'Nunito Sans', sans-serif" + } + } + }, + yaxis: { + labels: { + show: true, + style: { + colors: ['#99abb4', '#99abb4', '#99abb4', '#99abb4', '#99abb4', '#99abb4', '#99abb4', '#99abb4'], + fontSize: '12px', + fontFamily: "'Nunito Sans', sans-serif" + } + } + }, + grid: { + borderColor: 'rgba(0,0,0,0.1)', + xaxis: { + lines: { + show: true + } + }, + yaxis: { + lines: { + show: true + } + } + }, + tooltip: { + theme: 'dark' + } +}; +const seriessalessummry = [ + { + name: 'Site A view', + data: [0, 5, 6, 8, 25, 9, 8, 24] + }, + { + name: 'Site B view', + data: [0, 3, 1, 2, 8, 1, 5, 1] + } +]; + +export default function TransactionChart() { + return ( +
+ +
+ Transactions +
+
+ +
+
+
+
+
+ ); +} diff --git a/src/modules/vendor/detail/vendorInfo.js b/src/modules/vendor/detail/vendorInfo.js index 6f9f9276..de4eb5fc 100644 --- a/src/modules/vendor/detail/vendorInfo.js +++ b/src/modules/vendor/detail/vendorInfo.js @@ -10,6 +10,8 @@ export default function VendorInfo(props) { const { information } = props; const { id } = props.information; const handleEditClick = () => History.push(`/edit-vendor/${id}`); + + console.log(information.extra_files && information.extra_files.signature_photo); return (
@@ -83,29 +85,31 @@ export default function VendorInfo(props) { ? `${IPFS_GATEWAY}/ipfs/${information.extra_files.identity_photo}` : image } - alt="certificate" + alt="identity_photo" width="90%" height="130px" className="card-data" /> +
certificate +
certificate=2.3.x" + +svg.filter.js@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/svg.filter.js/-/svg.filter.js-2.0.2.tgz#91008e151389dd9230779fcbe6e2c9a362d1c203" + integrity sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM= + dependencies: + svg.js "^2.2.5" + +svg.js@>=2.3.x, svg.js@^2.0.1, svg.js@^2.2.5, svg.js@^2.4.0, svg.js@^2.6.5: + version "2.7.1" + resolved "https://registry.yarnpkg.com/svg.js/-/svg.js-2.7.1.tgz#eb977ed4737001eab859949b4a398ee1bb79948d" + integrity sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA== + +svg.pathmorphing.js@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz#c25718a1cc7c36e852ecabc380e758ac09bb2b65" + integrity sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww== + dependencies: + svg.js "^2.4.0" + +svg.resize.js@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/svg.resize.js/-/svg.resize.js-1.4.3.tgz#885abd248e0cd205b36b973c4b578b9a36f23332" + integrity sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw== + dependencies: + svg.js "^2.6.5" + svg.select.js "^2.1.2" + +svg.select.js@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-2.1.2.tgz#e41ce13b1acff43a7441f9f8be87a2319c87be73" + integrity sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ== + dependencies: + svg.js "^2.2.5" + +svg.select.js@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-3.0.1.tgz#a4198e359f3825739226415f82176a90ea5cc917" + integrity sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw== + dependencies: + svg.js "^2.6.5" + svgo@^1.0.0, svgo@^1.2.2: version "1.3.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" From 4a44dab49fa33f52d8cac288b96b0690e5ecd380 Mon Sep 17 00:00:00 2001 From: Manjik Shrestha Date: Fri, 24 Sep 2021 10:59:17 +0545 Subject: [PATCH 57/59] code cleanup --- src/modules/authentication/SignUp.js | 33 ++++++++-------------------- src/modules/authentication/Wallet.js | 4 ++-- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/src/modules/authentication/SignUp.js b/src/modules/authentication/SignUp.js index 688e8ca0..e8a82c15 100644 --- a/src/modules/authentication/SignUp.js +++ b/src/modules/authentication/SignUp.js @@ -10,6 +10,7 @@ import { UserContext } from '../../contexts/UserContext'; import * as Service from '../../services/appSettings'; import { useToasts } from 'react-toast-notifications'; import { TOAST } from '../../constants'; +import { History } from '../../utils/History'; export default function SignUp(props) { @@ -22,7 +23,7 @@ export default function SignUp(props) { wallet_address: search.wallet_address, agency:'', }); - const {appSettings} = useContext(AppContext); + const {appSettings,getAppSettings} = useContext(AppContext); const {signUp} = useContext(UserContext); const [loading, setLoading] = useState(false); @@ -30,28 +31,19 @@ export default function SignUp(props) { setFormData({ ...formData, [e.target.name]: e.target.value }); }; - function getSettings() { - return new Promise((resolve, reject) => { - Service.getSettings() - .then(res => { - resolve(res); - }) - .catch(err => reject(err)); - }); - } const handleFormSubmit = async e => { e.preventDefault(); try{ + console.log(appSettings); setLoading(true) - const res =await getSettings(); - const payload = { ...formData ,agency:res.agency.id}; + const payload = { ...formData ,agency:appSettings.agency.id}; const user = await signUp(payload); if(user.sucess){ setLoading(false); } addToast('Successfully Registered', TOAST.SUCCESS); - window.location.replace('/auth/login'); + History.push('/auth/login'); } catch(e){ addToast(e.error, TOAST.ERROR); @@ -59,19 +51,12 @@ export default function SignUp(props) { } }; - // const loadAppSettings = () => { - // getAppSettings().then(); - // }; - -// const checkWallet = ()=>{ -// console.log("checkoing"); -// return 'asdas'; -// } + const loadAppSettings = () => { + getAppSettings().then() + }; -// useEffect(()=>{ -// console.log("WRKIN"); -// },[]) +useEffect(loadAppSettings,[]); return ( <> diff --git a/src/modules/authentication/Wallet.js b/src/modules/authentication/Wallet.js index 9211c074..d71081a7 100644 --- a/src/modules/authentication/Wallet.js +++ b/src/modules/authentication/Wallet.js @@ -133,12 +133,12 @@ const Wallet = () => {
-

+ {/*

Haven’t registered?{' '} Sign up -

+

*/}

Rahat Agency App

From b3a187d23e7a3933b6398c7935a2dd51deb9e68c Mon Sep 17 00:00:00 2001 From: Manjik Shrestha Date: Sun, 26 Sep 2021 22:55:23 +0545 Subject: [PATCH 58/59] pre qr-code generate modal --- src/modules/aid/detail/projectInfo.js | 96 ++++++++++++++------ src/modules/aid/detail/qrGenerator/html.js | 76 ---------------- src/modules/aid/detail/qrGenerator/index.js | 14 +-- src/modules/aid/detail/qrGenerator/style.css | 7 +- 4 files changed, 80 insertions(+), 113 deletions(-) delete mode 100644 src/modules/aid/detail/qrGenerator/html.js diff --git a/src/modules/aid/detail/projectInfo.js b/src/modules/aid/detail/projectInfo.js index ade62840..63365873 100644 --- a/src/modules/aid/detail/projectInfo.js +++ b/src/modules/aid/detail/projectInfo.js @@ -1,46 +1,88 @@ -import React, { useRef } from 'react'; +import React, { useRef ,useState} from 'react'; import moment from 'moment'; -import { Card, CardTitle, Col, Row } from 'reactstrap'; +import { Card, CardTitle, Col, Row ,FormGroup, InputGroup, Input,Label } from 'reactstrap'; import ReactDOM from 'react-dom'; import { History } from '../../../utils/History'; import '../../../assets/css/project.css'; import QRGenerator from './qrGenerator'; -import { useReactToPrint } from 'react-to-print'; +import ReactToPrint from 'react-to-print'; +import ModalWrapper from '../../global/CustomModal'; + export default function ProjectInfo({ projectDetails }) { - const { _id, social_mobilizer, project_manager, location, description, created_at } = projectDetails; + const { _id, social_mobilizer, project_manager, location, description, created_at ,serial_index} = projectDetails; const handleEditClick = () => History.push(`/edit-project/${_id}`); - const qrComponentRef = useRef(); - const handlePreQrPrint = useReactToPrint({ - content: () => qrComponentRef.current - }); - const printQr = async data => { - console.log('generating'); - //ReactDOM.createPortal(qrGenerator, document.getElementById('root')) - //const qrs= await qrGenerator({min:1,max:3,projectVersion:1,amount:10}) + const [qrGenModal, setQrGenModal] = useState(false); + const [qrGenData,setQrGenData] = useState({ min: 0, max: 0, projectVersion: 0, amount:null }); + const [qrGenLoading,setQrGenLoading] = useState(false); + + const toggleQrGen = () => { + setQrGenModal(!qrGenModal); + setQrGenData({min: 0, max: 0, projectVersion: serial_index, amount:null}) + } + const toggleQrGenLoading = () => setQrGenLoading(!qrGenLoading); + + const qrComponentRef = useRef(); + const printRef = useRef(); + const handleQrGenSubmit = (e) => { + e.preventDefault(); + printRef.current.handleClick(); + } - // console.log({ qrs }); - //var doc = new jsPDF(); + // useReactToPrint({ + // content: () => qrComponentRef.current + // }); + const handleQrGenData = e =>{ + console.log(e.target.name); + if (e.target.name==='max'&&e.target.value > 100) return; - const printElement = document.createElement('iframe'); + setQrGenData({ ...qrGenData, [e.target.name]: e.target.value || null }); + } - // let newWindow = window.open('', 'Print QR', 'fullscreen=yes'), - // document = newWindow.document.open(); - // document.write("data"); - //newWindow.document.body.innerHTML (qrs); - // document.close(); - // setTimeout(function () { - // newWindow.print(); - // newWindow.close(); - // }, 250); - }; return (
- {0 && } +
+ + } + content={() => qrComponentRef.current} + ref={printRef} + /> +
+ + + + + + + + + +
@@ -52,7 +94,7 @@ export default function ProjectInfo({ projectDetails }) {
+
+

{ moment(extras.created_at).format('MMM Do YYYY, hh:mm A') || '-'}

+
Registration Date
+
diff --git a/src/modules/beneficary/list.js b/src/modules/beneficary/list.js index c9222cd2..ec2f68ae 100644 --- a/src/modules/beneficary/list.js +++ b/src/modules/beneficary/list.js @@ -18,6 +18,8 @@ import { } from 'reactstrap'; import displayPic from '../../assets/images/users/user_avatar.svg'; import { History } from '../../utils/History'; +import moment from 'moment'; + const searchOptions = { PHONE: 'phone', NAME: 'name', PROJECT: 'project' }; @@ -181,7 +183,7 @@ const Beneficiary = () => {
- + @@ -204,7 +206,7 @@ const Beneficiary = () => { - + + {list.length ? ( list.map((e, i) => ( + +
diff --git a/src/modules/vendor/list.js b/src/modules/vendor/list.js index 0a930443..b807a2f6 100644 --- a/src/modules/vendor/list.js +++ b/src/modules/vendor/list.js @@ -157,8 +157,8 @@ const Vendor = () => {
- + @@ -180,9 +180,9 @@ const Vendor = () => { - +
S.N. Name Phone Address{e.phone} {e.address} - - Details + + +
S.N. NameStatus PhoneCreated Date Address Action
{e.agencies[0].status} {e.phone}{moment(e.created_at).format('MMM Do YYYY')}{e.address} - +
Name Phone AddressGovt. IDRegistration Date Action
{d.phone} {d.address}{d.govt_id ? d.govt_id : '-'}{moment(d.created_at).format('MMM Do YYYY, hh:mm A')} diff --git a/src/modules/mobilizer/detail/mobilizerInfo.js b/src/modules/mobilizer/detail/mobilizerInfo.js index fff200c2..b8100604 100644 --- a/src/modules/mobilizer/detail/mobilizerInfo.js +++ b/src/modules/mobilizer/detail/mobilizerInfo.js @@ -3,7 +3,7 @@ import { Card, CardTitle, Col, Row } from 'reactstrap'; import '../../../assets/css/project.css'; import image from '../../../assets/images/id-icon-1.png'; import { History } from '../../../utils/History'; - +import moment from 'moment' const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY; export default function MobilizerInfo(props) { @@ -70,6 +70,10 @@ export default function MobilizerInfo(props) { height="130px" className="card-data" /> +
+

{ moment(information.created_at).format('MMM Do YYYY, hh:mm A') || '-'}

+
Registration Date
+
diff --git a/src/modules/mobilizer/list.js b/src/modules/mobilizer/list.js index aae0a99a..ba670d37 100644 --- a/src/modules/mobilizer/list.js +++ b/src/modules/mobilizer/list.js @@ -3,6 +3,8 @@ import { MobilizerContext } from '../../contexts/MobilizerContext'; import { useToasts } from 'react-toast-notifications'; import { Link } from 'react-router-dom'; import { History } from '../../utils/History'; +import moment from 'moment'; + import { Card, @@ -140,12 +142,14 @@ const Mobilizer = () => {
Name Phone AddressRegistration Date Action
{(pagination.currentPage - 1) * pagination.limit + i + 1} @@ -161,6 +165,7 @@ const Mobilizer = () => { {e.phone} {e.address}{moment(e.created_at).format('MMM Do YYYY, hh:mm A')} diff --git a/src/modules/vendor/detail/vendorInfo.js b/src/modules/vendor/detail/vendorInfo.js index de4eb5fc..a4deaaaa 100644 --- a/src/modules/vendor/detail/vendorInfo.js +++ b/src/modules/vendor/detail/vendorInfo.js @@ -3,7 +3,7 @@ import { Card, CardTitle, Col, Row } from 'reactstrap'; import '../../../assets/css/project.css'; import image from '../../../assets/images/id-icon-1.png'; import { History } from '../../../utils/History'; - +import moment from 'moment' const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY; export default function VendorInfo(props) { @@ -54,6 +54,10 @@ export default function VendorInfo(props) {

{information.bank_account || '-'}

Bank account number
+
+

{ moment(information.created_at).format('MMM Do YYYY, hh:mm A') || '-'}

+
Registration Date
+
Name Status PhoneCreated Date AddressRegistration Date Action
{e.agencies[0].status} {e.phone}{moment(e.created_at).format('MMM Do YYYY')} {e.address}{moment(e.created_at).format('MMM Do YYYY, hh:mm A')} diff --git a/src/services/mobilizer.js b/src/services/mobilizer.js index 72744ae3..c6bc75a1 100644 --- a/src/services/mobilizer.js +++ b/src/services/mobilizer.js @@ -59,7 +59,6 @@ export async function approveMobilizerToProject(wallet_address, projectId) { } export async function list(params) { - console.log('MOBILIXERS'); const res = await axios({ url: API.MOBILIZERS, method: 'get',