From e20c912a3d7410acea50472610fe84b46af9834b Mon Sep 17 00:00:00 2001 From: elforjani3 Date: Sun, 14 Jun 2020 13:27:36 +0200 Subject: [PATCH] WIP/customers --- client/src/config/sidebarMenu.js | 69 ++++--- client/src/containers/Customers/Customer.js | 20 ++ .../src/containers/Customers/CustomerForm.js | 175 ++++++++++++++++++ .../src/containers/Customers/RadioCustomer.js | 22 +++ .../Customers/withCustomerDetail.js | 8 + .../src/containers/Customers/withCustomers.js | 18 ++ .../Customers/withCustomersActions.js | 23 +++ client/src/lang/en/index.js | 13 ++ client/src/routes/dashboard.js | 26 +++ .../src/store/customers/customers.actions.js | 93 ++++++++++ .../src/store/customers/customers.reducer.js | 45 +++++ .../store/customers/customers.selectors.js | 10 + client/src/store/customers/customers.type.js | 8 + client/src/store/reducers.js | 5 +- client/src/store/types.js | 2 + client/src/style/App.scss | 25 ++- client/src/style/pages/customer.scss | 24 +++ 17 files changed, 544 insertions(+), 42 deletions(-) create mode 100644 client/src/containers/Customers/Customer.js create mode 100644 client/src/containers/Customers/CustomerForm.js create mode 100644 client/src/containers/Customers/RadioCustomer.js create mode 100644 client/src/containers/Customers/withCustomerDetail.js create mode 100644 client/src/containers/Customers/withCustomers.js create mode 100644 client/src/containers/Customers/withCustomersActions.js create mode 100644 client/src/store/customers/customers.actions.js create mode 100644 client/src/store/customers/customers.reducer.js create mode 100644 client/src/store/customers/customers.selectors.js create mode 100644 client/src/store/customers/customers.type.js create mode 100644 client/src/style/pages/customer.scss diff --git a/client/src/config/sidebarMenu.js b/client/src/config/sidebarMenu.js index 7ae10e79e..338df7efc 100644 --- a/client/src/config/sidebarMenu.js +++ b/client/src/config/sidebarMenu.js @@ -1,5 +1,5 @@ -import React from 'react' -import { FormattedMessage as T} from 'react-intl'; +import React from 'react'; +import { FormattedMessage as T } from 'react-intl'; export default [ { @@ -8,9 +8,9 @@ export default [ { icon: 'homepage', iconSize: 20, - text: , + text: , disabled: false, - href: '/homepage', + href: '/homepage', }, { divider: true, @@ -18,18 +18,18 @@ export default [ { icon: 'homepage', iconSize: 20, - text: , + text: , children: [ { - text: , + text: , href: '/items', }, { - text: , + text: , href: '/items/new', }, { - text: , + text: , href: '/items/categories', }, ], @@ -40,22 +40,22 @@ export default [ { icon: 'balance-scale', iconSize: 20, - text: , + text: , children: [ { - text: , + text: , href: '/accounts', }, { - text: , + text: , href: '/manual-journals', }, { - text: , + text: , href: '/make-journal-entry', }, { - text: , + text: , href: '/ExchangeRates', }, ], @@ -63,19 +63,19 @@ export default [ { icon: 'university', iconSize: 20, - text: , + text: , children: [], }, { icon: 'shopping-cart', iconSize: 20, - text: , + text: , children: [], }, { icon: 'balance-scale', iconSize: 20, - text: , + text: , children: [ { icon: 'cut', @@ -88,54 +88,69 @@ export default [ { icon: 'analytics', iconSize: 18, - text: , + text: , children: [ { - text: , + text: , href: '/balance-sheet', }, { - text: , + text: , href: '/trial-balance-sheet', }, { - text: , + text: , href: '/journal-sheet', }, { - text: , + text: , href: '/general-ledger', }, { - text: , + text: , href: '/profit-loss-sheet', }, ], }, { - text: , + text: , icon: 'receipt', iconSize: 18, children: [ { - text: , + text: , href: '/expenses', }, { - text: , + text: , href: '/expenses/new', }, ], }, + { + text: , + icon: 'receipt', + iconSize: 18, + children: [ + { + text: , + // href: '/', + }, + { + text: , + href: '/customers/new', + }, + ], + }, { divider: true, }, { - text: , + text: , href: '/preferences', }, { - text: , + text: , href: '/auditing/list', }, ]; diff --git a/client/src/containers/Customers/Customer.js b/client/src/containers/Customers/Customer.js new file mode 100644 index 000000000..021d3b33e --- /dev/null +++ b/client/src/containers/Customers/Customer.js @@ -0,0 +1,20 @@ +import React, { useCallback } from 'react'; +import { useParams, useHistory } from 'react-router-dom'; +import { useQuery } from 'react-query'; + +import DashboardInsider from 'components/Dashboard/DashboardInsider'; +import CustomerForm from 'containers/Customers/CustomerForm'; + +import withDashboardActions from 'containers/Dashboard/withDashboardActions'; + +import { compose } from 'utils'; + +function Customer({}) { + return ( + + + + ); +} + +export default Customer; diff --git a/client/src/containers/Customers/CustomerForm.js b/client/src/containers/Customers/CustomerForm.js new file mode 100644 index 000000000..4da06f613 --- /dev/null +++ b/client/src/containers/Customers/CustomerForm.js @@ -0,0 +1,175 @@ +import React, { useState, useMemo, useCallback, useEffect } from 'react'; +import * as Yup from 'yup'; +import { useFormik } from 'formik'; +import { + FormGroup, + MenuItem, + Intent, + InputGroup, + Button, + Classes, + Checkbox, + RadioGroup, + Radio, +} from '@blueprintjs/core'; +import { Row, Col } from 'react-grid-system'; +import { FormattedMessage as T, useIntl } from 'react-intl'; +import { queryCache, useQuery } from 'react-query'; +import { useHistory } from 'react-router-dom'; +import { pick } from 'lodash'; +import classNames from 'classnames'; + +import AppToaster from 'components/AppToaster'; +import ErrorMessage from 'components/ErrorMessage'; +import Icon from 'components/Icon'; +import MoneyInputGroup from 'components/MoneyInputGroup'; +import Dragzone from 'components/Dragzone'; + +import withDashboardActions from 'containers/Dashboard/withDashboardActions'; +import withCustomerDetail from './withCustomerDetail'; +import withCustomersActions from './withCustomersActions'; +import RadioCustomer from './RadioCustomer'; + +import { compose, handleStringChange } from 'utils'; +import withCustomers from './withCustomers'; + +function CustomerForm({ + // #withDashboardActions + changePageTitle, + + customers, + //#withCustomersActions + requestSubmitCustomer, + requestFetchCustomers, +}) { + const { formatMessage } = useIntl(); + + const validationSchema = Yup.object().shape({ + customer_type: Yup.string() + .required() + .trim() + .label(formatMessage({ id: 'customer_type_' })), + first_name: Yup.string().trim(), + last_name: Yup.string().trim(), + company_name: Yup.string().trim(), + display_name: Yup.string() + .trim() + .required() + .label(formatMessage({ id: 'display_name_' })), + email: Yup.string().email(), + work_phone: Yup.string(), + }); + + useEffect(() => { + changePageTitle(formatMessage({ id: 'new_customer' })); + }, [changePageTitle, formatMessage]); + //business + const initialValues = useMemo( + () => ({ + customer_type: 'business', + first_name: '', + last_name: '', + company_name: '', + display_name: '', + // email: '', + work_phone: '', + }), + [], + ); + const { + getFieldProps, + setFieldValue, + values, + touched, + errors, + handleSubmit, + isSubmitting, + } = useFormik({ + enableReinitialize: true, + validationSchema: validationSchema, + initialValues: { + ...initialValues, + }, + + onSubmit: (values, { setSubmitting, resetForm, setErrors }) => { + requestSubmitCustomer(values) + .then((response) => { + AppToaster.show({ + message: formatMessage({ + id: 'the_customer_has_been_successfully_created', + }), + intent: Intent.SUCCESS, + }); + }) + .catch((errors) => { + setSubmitting(false); + }); + }, + }); + + const requiredSpan = useMemo(() => *, []); + const handleCustomerTypeCahange = useCallback( + (value) => { + setFieldValue('customer_type', value); + }, + [setFieldValue], + ); + + console.log(customers, 'ER'); + + const fetch = useQuery('customers-table', (key) => requestFetchCustomers()); + + return ( +
+
+
+ + } + className={'form-group--name'} + intent={ + errors.display_name && touched.display_name && Intent.DANGER + } + inline={true} + helperText={ + + } + > + + +
+ + +
+
+ ); +} + +export default compose( + withCustomerDetail, + withCustomers(({ customers }) => ({ + customers, + })), + withDashboardActions, + withCustomersActions, +)(CustomerForm); diff --git a/client/src/containers/Customers/RadioCustomer.js b/client/src/containers/Customers/RadioCustomer.js new file mode 100644 index 000000000..601491d92 --- /dev/null +++ b/client/src/containers/Customers/RadioCustomer.js @@ -0,0 +1,22 @@ +import React from 'react'; +import { handleStringChange } from 'utils'; +import { useIntl } from 'react-intl'; +import { RadioGroup, Radio } from '@blueprintjs/core'; + +export default function RadioCustomer(props) { + const { onChange, ...rest } = props; + const { formatMessage } = useIntl(); + return ( + { + onChange && onChange(value); + })} + {...rest} + > + + + + ); +} diff --git a/client/src/containers/Customers/withCustomerDetail.js b/client/src/containers/Customers/withCustomerDetail.js new file mode 100644 index 000000000..755c2fc1a --- /dev/null +++ b/client/src/containers/Customers/withCustomerDetail.js @@ -0,0 +1,8 @@ +import { connect } from 'react-redux'; +import { getCustomerById } from 'store/customers/customers.reducer'; + +const mapStateToProps = (state, props) => ({ + customerDetail: getCustomerById(state, props.customerId), +}); + +export default connect(mapStateToProps); diff --git a/client/src/containers/Customers/withCustomers.js b/client/src/containers/Customers/withCustomers.js new file mode 100644 index 000000000..7bc0b4fb4 --- /dev/null +++ b/client/src/containers/Customers/withCustomers.js @@ -0,0 +1,18 @@ +import { connect } from 'react-redux'; +import { getCustomersItems } from 'store/customers/customers.selectors'; +import { getResourceViews } from 'store/customViews/customViews.selectors'; + +export default (mapState) => { + const mapStateToProps = (state, props) => { + + const mapped = { + customersViews: getResourceViews(state, 'customers'), + customers: getCustomersItems(state, state.customers.currentViewId), + customersLoading: state.customers.loading, + customerErrors: state.customers.errors, + }; + return mapState ? mapState(mapped, state, props) : mapped; + }; + + return connect(mapStateToProps); +}; diff --git a/client/src/containers/Customers/withCustomersActions.js b/client/src/containers/Customers/withCustomersActions.js new file mode 100644 index 000000000..8b88d885d --- /dev/null +++ b/client/src/containers/Customers/withCustomersActions.js @@ -0,0 +1,23 @@ +import { connect } from 'react-redux'; +import { + fetchCustomers, + submitCustomer, + editCustomer, +} from 'store/customers/customers.actions'; +import t from 'store/types'; + +export const mapDispatchToProps = (dispatch) => ({ + requestFetchCustomers: (query) => dispatch(fetchCustomers({ query })), + // requestDeleteCustomer: (id) => dispatch(deleteCustomer({ id })), + // requestDeleteBulkCustomers:(ids)=>dispatch(deleteBulkCustomers({ids})), + requestSubmitCustomer: (form) => dispatch(submitCustomer({ form })), + // requestEditCustomer: (id, form) => dispatch(editCustomer({ id, form })), + + addCustomersTableQueries: (queries) => + dispatch({ + type: t.CUSTOMERS_TABLE_QUERIES_ADD, + queries, + }), +}); + +export default connect(null, mapDispatchToProps); diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js index 7dcd00767..e9b9d7c65 100644 --- a/client/src/lang/en/index.js +++ b/client/src/lang/en/index.js @@ -417,4 +417,17 @@ export default { quick_new: 'Quick new', help: 'Help', organization_id: 'Orgnization ID', + + customers: 'Customers', + new_customers:'New Customers', + customer_type_: 'Customer type', + display_name_: 'Display name', + new_customer: 'New Customer', + customer_type: 'Customer Type', + business: 'business', + individual: 'Individual', + display_name:'Display Name', + + the_customer_has_been_successfully_created: + 'The customer has been successfully created.', }; diff --git a/client/src/routes/dashboard.js b/client/src/routes/dashboard.js index 2d2f0f9c3..6da6e57f9 100644 --- a/client/src/routes/dashboard.js +++ b/client/src/routes/dashboard.js @@ -141,4 +141,30 @@ export default [ }), breadcrumb: 'Exchange Rates', }, + + { + path: `/customers/:id/edit`, + component: LazyLoader({ + // loader: () => import(), + }), + breadcrumb: 'Edit Customer', + }, + { + path: `/customers/new`, + component: LazyLoader({ + loader: () => import('containers/Customers/Customer'), + }), + breadcrumb: 'New Customer', + }, + + // Customers + { + path: `/customers`, + component: LazyLoader({ + // loader: () => import(''), + }), + breadcrumb: 'Customers', + }, + + ]; diff --git a/client/src/store/customers/customers.actions.js b/client/src/store/customers/customers.actions.js new file mode 100644 index 000000000..e719ad2ae --- /dev/null +++ b/client/src/store/customers/customers.actions.js @@ -0,0 +1,93 @@ +import ApiService from 'services/ApiService'; +import t from 'store/types'; + +export const submitCustomer = ({ form }) => { + return (dispatch) => + new Promise((resolve, reject) => { + dispatch({ + type: t.SET_DASHBOARD_REQUEST_LOADING, + }); + + ApiService.post('customers', form) + .then((response) => { + dispatch({ + type: t.SET_DASHBOARD_REQUEST_COMPLETED, + }); + resolve(response); + }) + .catch((error) => { + const { response } = error; + const { data } = response; + dispatch({ + type: t.SET_DASHBOARD_REQUEST_COMPLETED, + }); + reject(data?.errors); + }); + }); +}; + +export const editCustomer = ({ form, id }) => { + return (dispatch) => + new Promise((resolve, reject) => { + dispatch({ + type: t.SET_DASHBOARD_REQUEST_LOADING, + }); + + ApiService.post(`customers/${id}`, form) + .then((response) => { + dispatch({ + type: t.SET_DASHBOARD_REQUEST_COMPLETED, + }); + resolve(response); + }) + .catch((error) => { + const { response } = error; + const { data } = response; + dispatch({ + type: t.SET_DASHBOARD_REQUEST_COMPLETED, + }); + reject(data?.errors); + }); + }); +}; + +export const fetchCustomers = ({ query }) => { + return (dispatch, getState) => + new Promise((resolve, reject) => { + const pageQuery = getState().items.tableQuery; + dispatch({ + type: t.ITEMS_TABLE_LOADING, + payload: { loading: true }, + }); + dispatch({ + type: t.SET_DASHBOARD_REQUEST_LOADING, + }); + ApiService.get(`customers`, { params: { ...pageQuery, ...query } }) + .then((response) => { + dispatch({ + type: t.CUSTOMER_SET, + customers: response.data.customers.results, + }); + dispatch({ + type: t.CUSTOMERS_PAGE_SET, + customers: response.data.customers.results, + customViewId: response.data.customers.customViewId, + paginationMeta: response.data.customers.pagination, + }); + dispatch({ + type: t.CUSTOMERS_TABLE_LOADING, + payload: { loading: false }, + }); + dispatch({ + type: t.SET_DASHBOARD_REQUEST_COMPLETED, + }); + resolve(response); + }) + .catch((error) => { + dispatch({ + type: t.SET_DASHBOARD_REQUEST_COMPLETED, + }); + reject(error); + }); + }); +}; diff --git a/client/src/store/customers/customers.reducer.js b/client/src/store/customers/customers.reducer.js new file mode 100644 index 000000000..a660fe31d --- /dev/null +++ b/client/src/store/customers/customers.reducer.js @@ -0,0 +1,45 @@ +import t from 'store/types'; +import { createReducer } from '@reduxjs/toolkit'; +import { createTableQueryReducers } from 'store/queryReducers'; + +const initialState = { + items: {}, + views: {}, + loading: false, + currentViewId: -1, + errors: [], +}; + +const customersReducer = createReducer(initialState, { + [t.CUSTOMER_SET]: (state, action) => { + const _customers = {}; + + action.customers.forEach((customer) => { + _customers[customer.id] = customer; + }); + state.items = { + ...state.items, + ..._customers, + }; + }, + [t.CUSTOMERS_PAGE_SET]: (state, action) => { + const viewId = action.customViewId || -1; + const view = state.views[viewId] || {}; + + state.views[viewId] = { + ...view, + ids: action.customers.map((i) => i.id), + }; + }, + [t.CUSTOMER_DELETE]: (state, action) => { + if (typeof state.items[action.id] !== 'undefined') { + delete state.items[action.id]; + } + }, +}); + +export default createTableQueryReducers('customers', customersReducer); + +export const getCustomerById = (state, id) => { + return state.customers[id]; +}; diff --git a/client/src/store/customers/customers.selectors.js b/client/src/store/customers/customers.selectors.js new file mode 100644 index 000000000..f337de723 --- /dev/null +++ b/client/src/store/customers/customers.selectors.js @@ -0,0 +1,10 @@ +import { pickItemsFromIds } from 'store/selectors'; + +export const getCustomersItems = (state, viewId) => { + const customersView = state.customers.views[viewId || -1]; + const customersItems = state.customers.items; + + return typeof customersView === 'object' + ? pickItemsFromIds(customersItems, customersView.ids) || [] + : []; +}; diff --git a/client/src/store/customers/customers.type.js b/client/src/store/customers/customers.type.js new file mode 100644 index 000000000..c63620434 --- /dev/null +++ b/client/src/store/customers/customers.type.js @@ -0,0 +1,8 @@ +export default { + CUSTOMERS_ITEMS_SET: 'CUSTOMERS_ITEMS_SET', + CUSTOMER_SET: 'CUSTOMER_SET', + CUSTOMERS_PAGE_SET: 'CUSTOMERS_PAGE_SET', + CUSTOMERS_TABLE_LOADING: 'CUSTOMERS_TABLE_LOADING', + CUSTOMERS_TABLE_QUERIES_ADD: 'CUSTOMERS_TABLE_QUERIES_ADD', + CUSTOMER_DELETE:'CUSTOMER_DELETE' +}; diff --git a/client/src/store/reducers.js b/client/src/store/reducers.js index f8851e793..cd401f3db 100644 --- a/client/src/store/reducers.js +++ b/client/src/store/reducers.js @@ -15,9 +15,9 @@ import itemCategories from './itemCategories/itemsCategory.reducer'; import settings from './settings/settings.reducer'; import manualJournals from './manualJournals/manualJournals.reducers'; import globalSearch from './search/search.reducer'; -import exchangeRates from './ExchangeRate/exchange.reducer' +import exchangeRates from './ExchangeRate/exchange.reducer'; import globalErrors from './globalErrors/globalErrors.reducer'; - +import customers from './customers/customers.reducer'; export default combineReducers({ authentication, @@ -37,4 +37,5 @@ export default combineReducers({ globalSearch, exchangeRates, globalErrors, + customers, }); diff --git a/client/src/store/types.js b/client/src/store/types.js index 65209b814..25af78fbb 100644 --- a/client/src/store/types.js +++ b/client/src/store/types.js @@ -16,6 +16,7 @@ import settings from './settings/settings.type'; import search from './search/search.type'; import register from './registers/register.type'; import exchangeRate from './ExchangeRate/exchange.type'; +import customer from './customers/customers.type'; export default { ...authentication, @@ -36,4 +37,5 @@ export default { ...search, ...register, ...exchangeRate, + ...customer, }; diff --git a/client/src/style/App.scss b/client/src/style/App.scss index f5bcf80e8..b416c8698 100644 --- a/client/src/style/App.scss +++ b/client/src/style/App.scss @@ -12,8 +12,8 @@ $menu-item-color-active: $light-gray3; $breadcrumbs-collapsed-icon: url("data:image/svg+xml,"); $sidebar-zindex: 15; -$pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, Icons16, sans-serif; - +$pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, + Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, Icons16, sans-serif; // Blueprint framework. @import '@blueprintjs/core/src/blueprint.scss'; @@ -48,16 +48,16 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, @import 'pages/items'; @import 'pages/items-categories'; @import 'pages/invite-form.scss'; -@import "pages/currency"; -@import "pages/invite-user.scss"; +@import 'pages/currency'; +@import 'pages/invite-user.scss'; @import 'pages/exchange-rate.scss'; - +@import 'pages/customer.scss'; + // Views @import 'views/filter-dropdown'; @import 'views/sidebar'; - -.App{ +.App { min-width: 960px; } @@ -75,12 +75,11 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, } } -.bigcapital--alt{ - - svg{ +.bigcapital--alt { + svg { path, .path-13, - .path-1{ + .path-1 { fill: #fff; } } @@ -90,6 +89,6 @@ body.authentication { background-color: #fcfdff; } -.bp3-toast{ +.bp3-toast { box-shadow: none; -} \ No newline at end of file +} diff --git a/client/src/style/pages/customer.scss b/client/src/style/pages/customer.scss new file mode 100644 index 000000000..378ab63cc --- /dev/null +++ b/client/src/style/pages/customer.scss @@ -0,0 +1,24 @@ +.customer-form { + padding: 22px; + width: 100%; + + // padding-bottom: 90px; + + .bp3-form-group { + // margin: 25px 20px 20px; + + .bp3-label { + min-width: 100px; + } + .bp3-form-content { + width: 45%; + } + } + + &__primary-section { + background-color: #fafafa; + padding: 40px 22px 22px; + margin: -22px -22px 22px; + background-color: #fafafa; + } +}