Fix : Customers

This commit is contained in:
elforjani3
2020-11-10 13:31:54 +02:00
parent 027c1af841
commit eef1c3c7e1
15 changed files with 393 additions and 190 deletions

View File

@@ -2,22 +2,25 @@ import React, { useCallback } from 'react';
import { useParams, useHistory } from 'react-router-dom'; import { useParams, useHistory } from 'react-router-dom';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import CustomerForm from 'containers/Customers/CustomerForm'; import CustomerForm from 'containers/Customers/CustomerForm';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withCustomersActions from './withCustomersActions'; import withCustomersActions from './withCustomersActions';
import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions';
import { compose } from 'utils'; import { compose } from 'utils';
function Customer({ function Customer({
// #withDashboardActions // // #withDashboardActions
changePageTitle, // changePageTitle,
formik, // formik,
//#withCustomersActions //#withCustomersActions
requestFetchCustomers, requestFetchCustomers,
requestFetchCustomer, requestFetchCustomer,
// #wihtCurrenciesActions
requestFetchCurrencies,
}) { }) {
const { id } = useParams(); const { id } = useParams();
const history = useHistory(); const history = useHistory();
@@ -32,7 +35,11 @@ function Customer({
(key, customerId) => requestFetchCustomer(customerId), (key, customerId) => requestFetchCustomer(customerId),
{ enabled: id && id }, { enabled: id && id },
); );
// Handle fetch Currencies data table
const fetchCurrencies = useQuery('currencies', () =>
requestFetchCurrencies(),
);
const handleFormSubmit = useCallback( const handleFormSubmit = useCallback(
(payload) => { (payload) => {
payload.redirect && history.push('/customers'); payload.redirect && history.push('/customers');
@@ -46,7 +53,11 @@ function Customer({
return ( return (
<DashboardInsider <DashboardInsider
loading={fetchCustomer.isFetching || fetchCustomers.isFetching} loading={
fetchCustomer.isFetching ||
fetchCustomers.isFetching ||
fetchCurrencies.isFetching
}
name={'customer-form'} name={'customer-form'}
> >
<CustomerForm <CustomerForm
@@ -58,4 +69,4 @@ function Customer({
); );
} }
export default compose(withDashboardActions, withCustomersActions)(Customer); export default compose(withCustomersActions, withCurrenciesActions)(Customer);

View File

@@ -18,7 +18,7 @@ const CustomerBillingAddress = ({
<h4> <h4>
<T id={'billing_address'} /> <T id={'billing_address'} />
</h4> </h4>
{/*------------ Billing Address country -----------*/}
<FormGroup <FormGroup
className={'form-group--journal-number'} className={'form-group--journal-number'}
intent={ intent={
@@ -44,7 +44,7 @@ const CustomerBillingAddress = ({
{...getFieldProps('billing_address_country')} {...getFieldProps('billing_address_country')}
/> />
</FormGroup> </FormGroup>
{/*------------ Billing Address 1 -----------*/}
<FormGroup <FormGroup
label={<T id={'address_line_1'} />} label={<T id={'address_line_1'} />}
className={'form-group--address_line_1'} className={'form-group--address_line_1'}
@@ -67,7 +67,7 @@ const CustomerBillingAddress = ({
{...getFieldProps('billing_address_1')} {...getFieldProps('billing_address_1')}
/> />
</FormGroup> </FormGroup>
{/*------------ Billing Address 2 -----------*/}
<FormGroup <FormGroup
label={<T id={'address_line_2'} />} label={<T id={'address_line_2'} />}
className={'form-group--journal-number'} className={'form-group--journal-number'}
@@ -90,7 +90,7 @@ const CustomerBillingAddress = ({
{...getFieldProps('billing_address_2')} {...getFieldProps('billing_address_2')}
/> />
</FormGroup> </FormGroup>
{/*------------ Billing Address city -----------*/}
<FormGroup <FormGroup
label={<T id={'city_town'} />} label={<T id={'city_town'} />}
className={'form-group--journal-number'} className={'form-group--journal-number'}
@@ -116,7 +116,7 @@ const CustomerBillingAddress = ({
{...getFieldProps('billing_address_city')} {...getFieldProps('billing_address_city')}
/> />
</FormGroup> </FormGroup>
{/*------------ Billing Address state -----------*/}
<FormGroup <FormGroup
label={<T id={'state'} />} label={<T id={'state'} />}
className={'form-group--journal-number'} className={'form-group--journal-number'}
@@ -142,47 +142,47 @@ const CustomerBillingAddress = ({
{...getFieldProps('billing_address_state')} {...getFieldProps('billing_address_state')}
/> />
</FormGroup> </FormGroup>
{/*------------ Billing Address postcode -----------*/}
<FormGroup <FormGroup
label={<T id={'zip_code'} />} label={<T id={'zip_code'} />}
intent={ intent={
errors.billing_address_zipcode && errors.billing_address_postcode &&
touched.billing_address_zipcode && touched.billing_address_postcode &&
Intent.DANGER Intent.DANGER
} }
inline={true} inline={true}
helperText={ helperText={
<ErrorMessage <ErrorMessage
name="billing_address_zipcode" name="billing_address_postcode"
{...{ errors, touched }} {...{ errors, touched }}
/> />
} }
> >
<InputGroup <InputGroup
intent={ intent={
errors.billing_address_zipcode && errors.billing_address_postcode &&
touched.billing_address_zipcode && touched.billing_address_postcode &&
Intent.DANGER Intent.DANGER
} }
{...getFieldProps('billing_address_zipcode')} {...getFieldProps('billing_address_postcode')}
/> />
</FormGroup> </FormGroup>
{/*------------ Billing Address phone -----------*/}
<FormGroup <FormGroup
label={<T id={'phone'} />} label={<T id={'phone'} />}
intent={ intent={
errors.shipping_phone && touched.shipping_phone && Intent.DANGER errors.billing_address_phone && touched.billing_address_phone && Intent.DANGER
} }
inline={true} inline={true}
helperText={ helperText={
<ErrorMessage name="shipping_phone" {...{ errors, touched }} /> <ErrorMessage name="billing_address_phone" {...{ errors, touched }} />
} }
> >
<InputGroup <InputGroup
intent={ intent={
errors.shipping_phone && touched.shipping_phone && Intent.DANGER errors.billing_address_phone && touched.billing_address_phone && Intent.DANGER
} }
{...getFieldProps('shipping_phone')} {...getFieldProps('billing_address_phone')}
/> />
</FormGroup> </FormGroup>
</Col> </Col>
@@ -191,7 +191,7 @@ const CustomerBillingAddress = ({
<h4> <h4>
<T id={'shipping_address'} /> <T id={'shipping_address'} />
</h4> </h4>
{/*------------ Shipping Address country -----------*/}
<FormGroup <FormGroup
label={<T id={'country'} />} label={<T id={'country'} />}
className={'form-group--journal-number'} className={'form-group--journal-number'}
@@ -217,7 +217,7 @@ const CustomerBillingAddress = ({
{...getFieldProps('shipping_address_country')} {...getFieldProps('shipping_address_country')}
/> />
</FormGroup> </FormGroup>
{/*------------ Shipping Address 1 -----------*/}
<FormGroup <FormGroup
label={<T id={'address_line_1'} />} label={<T id={'address_line_1'} />}
className={'form-group--journal-number'} className={'form-group--journal-number'}
@@ -240,7 +240,7 @@ const CustomerBillingAddress = ({
{...getFieldProps('billing_address_1')} {...getFieldProps('billing_address_1')}
/> />
</FormGroup> </FormGroup>
{/*------------ Shipping Address 2 -----------*/}
<FormGroup <FormGroup
label={<T id={'address_line_2'} />} label={<T id={'address_line_2'} />}
className={'form-group--journal-number'} className={'form-group--journal-number'}
@@ -263,7 +263,7 @@ const CustomerBillingAddress = ({
{...getFieldProps('billing_address_2')} {...getFieldProps('billing_address_2')}
/> />
</FormGroup> </FormGroup>
{/*------------ Shipping Address city -----------*/}
<FormGroup <FormGroup
label={<T id={'city_town'} />} label={<T id={'city_town'} />}
className={'form-group--journal-number'} className={'form-group--journal-number'}
@@ -289,7 +289,7 @@ const CustomerBillingAddress = ({
{...getFieldProps('shipping_address_city')} {...getFieldProps('shipping_address_city')}
/> />
</FormGroup> </FormGroup>
{/*------------ Shipping Address state -----------*/}
<FormGroup <FormGroup
label={<T id={'state'} />} label={<T id={'state'} />}
className={'form-group--journal-number'} className={'form-group--journal-number'}
@@ -315,47 +315,47 @@ const CustomerBillingAddress = ({
{...getFieldProps('shipping_address_state')} {...getFieldProps('shipping_address_state')}
/> />
</FormGroup> </FormGroup>
{/*------------ Shipping Address postcode -----------*/}
<FormGroup <FormGroup
label={<T id={'zip_code'} />} label={<T id={'zip_code'} />}
intent={ intent={
errors.shipping_address_zipcode && errors.shipping_address_postcode &&
touched.shipping_address_zipcode && touched.shipping_address_postcode &&
Intent.DANGER Intent.DANGER
} }
inline={true} inline={true}
helperText={ helperText={
<ErrorMessage <ErrorMessage
name="shipping_address_zipcode" name="shipping_address_postcode"
{...{ errors, touched }} {...{ errors, touched }}
/> />
} }
> >
<InputGroup <InputGroup
intent={ intent={
errors.shipping_address_zipcode && errors.shipping_address_postcode &&
touched.shipping_address_zipcode && touched.shipping_address_postcode &&
Intent.DANGER Intent.DANGER
} }
{...getFieldProps('shipping_address_zipcode')} {...getFieldProps('shipping_address_postcode')}
/> />
</FormGroup> </FormGroup>
{/*------------ Shipping Address phone -----------*/}
<FormGroup <FormGroup
label={<T id={'phone'} />} label={<T id={'phone'} />}
intent={ intent={
errors.shipping_phone && touched.shipping_phone && Intent.DANGER errors.shipping_address_phone && touched.shipping_address_phone && Intent.DANGER
} }
inline={true} inline={true}
helperText={ helperText={
<ErrorMessage name="shipping_phone" {...{ errors, touched }} /> <ErrorMessage name="shipping_address_phone" {...{ errors, touched }} />
} }
> >
<InputGroup <InputGroup
intent={ intent={
errors.shipping_phone && touched.shipping_phone && Intent.DANGER errors.shipping_address_phone && touched.shipping_address_phone && Intent.DANGER
} }
{...getFieldProps('shipping_phone')} {...getFieldProps('shipping_address_phone')}
/> />
</FormGroup> </FormGroup>
</Col> </Col>

View File

@@ -1,4 +1,4 @@
import React, { useCallback } from 'react'; import React, { useCallback, useState } from 'react';
import moment from 'moment'; import moment from 'moment';
import classNames from 'classnames'; import classNames from 'classnames';
import { FormGroup, Intent, Position, Classes } from '@blueprintjs/core'; import { FormGroup, Intent, Position, Classes } from '@blueprintjs/core';
@@ -6,52 +6,86 @@ import { DateInput } from '@blueprintjs/datetime';
import { import {
ErrorMessage, ErrorMessage,
MoneyInputGroup, MoneyInputGroup,
CurrenciesSelectList, CurrencySelectList,
Row, Row,
Col, Col,
} from 'components'; } from 'components';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import { momentFormatter, tansformDateValue } from 'utils';
export default function CustomerFinancialPanel({ import withCurrencies from 'containers/Currencies/withCurrencies';
import { compose, momentFormatter, tansformDateValue } from 'utils';
function CustomerFinancialPanel({
setFieldValue, setFieldValue,
errors, errors,
touched, touched,
values, values,
// #withCurrencies
currenciesList,
customerId,
}) { }) {
const [selectedItems, setSelectedItems] = useState();
const handleDateChange = useCallback( const handleDateChange = useCallback(
(date) => { (date) => {
const formatted = moment(date).format('YYYY-MM-DD'); const formatted = moment(date).format('YYYY-MM-DD');
setFieldValue('payment_date', formatted); setFieldValue('opening_balance_at', formatted);
}, },
[setFieldValue], [setFieldValue],
); );
const handleMoneyInputChange = useCallback(
(e, value) => {
setFieldValue('opening_balance', value);
},
[setFieldValue],
);
const onItemsSelect = useCallback(
(filedName) => {
return (filed) => {
setSelectedItems({
...selectedItems,
[filedName]: filed,
});
setFieldValue(filedName, filed.currency_code);
};
},
[setFieldValue, selectedItems],
);
return ( return (
<div className={'tab-panel--financial'}> <div className={'tab-panel--financial'}>
<Row> <Row>
<Col xs={6}> <Col xs={6}>
{/*------------ Opening balance at -----------*/}
<FormGroup <FormGroup
label={<T id={'opening_balance_at'} />} label={<T id={'opening_balance_at'} />}
className={classNames('form-group--select-list', Classes.FILL)} className={classNames('form-group--select-list', Classes.FILL)}
intent={ intent={
errors.opening_balance_date && errors.opening_balance_at &&
touched.opening_balance_date && touched.opening_balance_at &&
Intent.DANGER Intent.DANGER
} }
inline={true} inline={true}
helperText={ helperText={
<ErrorMessage name="payment_date" {...{ errors, touched }} /> <ErrorMessage
name="opening_balance_at"
{...{ errors, touched }}
/>
} }
> >
<DateInput <DateInput
{...momentFormatter('YYYY/MM/DD')} {...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(values.payment_date)} value={tansformDateValue(values.opening_balance_at)}
onChange={handleDateChange} onChange={handleDateChange}
popoverProps={{ position: Position.BOTTOM, minimal: true }} popoverProps={{ position: Position.BOTTOM, minimal: true }}
disabled={customerId}
/> />
</FormGroup> </FormGroup>
{/*------------ Opening balance -----------*/}
<FormGroup <FormGroup
label={<T id={'opening_balance'} />} label={<T id={'opening_balance'} />}
className={classNames('form-group--opening-balance', Classes.FILL)} className={classNames('form-group--opening-balance', Classes.FILL)}
@@ -63,12 +97,14 @@ export default function CustomerFinancialPanel({
<MoneyInputGroup <MoneyInputGroup
value={values.opening_balance} value={values.opening_balance}
prefix={'$'} prefix={'$'}
onChange={handleMoneyInputChange}
inputGroupProps={{ inputGroupProps={{
fill: true, fill: true,
}} }}
disabled={customerId}
/> />
</FormGroup> </FormGroup>
{/*------------ Currency -----------*/}
<FormGroup <FormGroup
label={<T id={'currency'} />} label={<T id={'currency'} />}
className={classNames( className={classNames(
@@ -78,10 +114,20 @@ export default function CustomerFinancialPanel({
)} )}
inline={true} inline={true}
> >
<CurrenciesSelectList /> {/* <CurrenciesSelectList /> */}
<CurrencySelectList
currenciesList={currenciesList}
selectedCurrencyCode={values.currency_code}
onCurrencySelected={onItemsSelect('currency_code')}
disabled={customerId}
/>
</FormGroup> </FormGroup>
</Col> </Col>
</Row> </Row>
</div> </div>
); );
} }
export default compose(
withCurrencies(({ currenciesList }) => ({ currenciesList })),
)(CustomerFinancialPanel);

View File

@@ -15,8 +15,8 @@ export default function CustomerFloatingActions({
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}> <div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
<Button <Button
intent={Intent.PRIMARY}
disabled={isSubmitting} disabled={isSubmitting}
intent={Intent.PRIMARY}
type="submit" type="submit"
onClick={() => { onClick={() => {
onSubmitClick({ publish: true, redirect: true }); onSubmitClick({ publish: true, redirect: true });

View File

@@ -1,6 +1,7 @@
import React, { useState, useMemo, useCallback, useEffect } from 'react'; import React, { useState, useMemo, useCallback, useEffect } from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import moment from 'moment';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
@@ -24,6 +25,9 @@ import useMedia from 'hooks/useMedia';
import { compose } from 'utils'; import { compose } from 'utils';
/**
* Customer form.
*/
function CustomerForm({ function CustomerForm({
// #withDashboardActions // #withDashboardActions
changePageTitle, changePageTitle,
@@ -36,7 +40,6 @@ function CustomerForm({
// #withCustomersActions // #withCustomersActions
requestSubmitCustomer, requestSubmitCustomer,
requestFetchCustomers,
requestEditCustomer, requestEditCustomer,
// #withMediaActions // #withMediaActions
@@ -67,6 +70,7 @@ function CustomerForm({
.required() .required()
.trim() .trim()
.label(formatMessage({ id: 'customer_type_' })), .label(formatMessage({ id: 'customer_type_' })),
salutation: Yup.string().trim(),
first_name: Yup.string().trim(), first_name: Yup.string().trim(),
last_name: Yup.string().trim(), last_name: Yup.string().trim(),
company_name: Yup.string().trim(), company_name: Yup.string().trim(),
@@ -74,48 +78,71 @@ function CustomerForm({
.trim() .trim()
.required() .required()
.label(formatMessage({ id: 'display_name_' })), .label(formatMessage({ id: 'display_name_' })),
email: Yup.string().email(), email: Yup.string().email(),
work_phone: Yup.number(), work_phone: Yup.number(),
personal_phone: Yup.number(),
website: Yup.string().url(),
active: Yup.boolean(), active: Yup.boolean(),
note: Yup.string().trim(),
billing_address_city: Yup.string().trim(),
billing_address_country: Yup.string().trim(), billing_address_country: Yup.string().trim(),
billing_address_email: Yup.string().email(), billing_address_1: Yup.string().trim(),
billing_address_zipcode: Yup.number().nullable(), billing_address_2: Yup.string().trim(),
billing_address_phone: Yup.number(), billing_address_city: Yup.string().trim(),
billing_address_state: Yup.string().trim(), billing_address_state: Yup.string().trim(),
billing_address_postcode: Yup.number().nullable(),
billing_address_phone: Yup.number(),
shipping_address_city: Yup.string().trim(),
shipping_address_country: Yup.string().trim(), shipping_address_country: Yup.string().trim(),
shipping_address_email: Yup.string().email(), shipping_address_1: Yup.string().trim(),
shipping_address_zipcode: Yup.number().nullable(), shipping_address_2: Yup.string().trim(),
shipping_address_phone: Yup.number(), shipping_address_city: Yup.string().trim(),
shipping_address_state: Yup.string().trim(), shipping_address_state: Yup.string().trim(),
shipping_address_postcode: Yup.number().nullable(),
shipping_address_phone: Yup.number(),
opening_balance: Yup.number(),
currency_code: Yup.string(),
opening_balance_at: Yup.date(),
}); });
const defaultInitialValues = useMemo( const defaultInitialValues = useMemo(
() => ({ () => ({
customer_type: 'business', customer_type: 'business',
salutation: '',
first_name: '', first_name: '',
last_name: '', last_name: '',
company_name: '', company_name: '',
display_name: '', display_name: '',
email: '', email: '',
work_phone: '', work_phone: '',
personal_phone: '',
website: '',
note: '',
active: true, active: true,
billing_address_city: '',
billing_address_country: '', billing_address_country: '',
billing_address_zipcode: null, billing_address_1: '',
billing_address_phone: '', billing_address_2: '',
billing_address_city: '',
billing_address_state: '', billing_address_state: '',
billing_address_postcode: null,
billing_address_phone: '',
shipping_address_city: '',
shipping_address_country: '', shipping_address_country: '',
shipping_address_zipcode: null, shipping_address_1: '',
shipping_address_phone: '', shipping_address_2: '',
shipping_address_city: '',
shipping_address_state: '', shipping_address_state: '',
shipping_address_postcode: null,
shipping_address_phone: '',
opening_balance: '',
currency_code: '',
opening_balance_at: moment(new Date()).format('YYYY-MM-DD'),
}), }),
[], [],
); );
@@ -147,7 +174,10 @@ function CustomerForm({
: changePageTitle(formatMessage({ id: 'new_customer' })); : changePageTitle(formatMessage({ id: 'new_customer' }));
}, [changePageTitle, customer, formatMessage]); }, [changePageTitle, customer, formatMessage]);
const handleFormSubmit = (values, { setSubmitting, resetForm, setErrors }) => { const handleFormSubmit = (
values,
{ setSubmitting, resetForm, setErrors },
) => {
const formValues = { ...values, status: payload.publish }; const formValues = { ...values, status: payload.publish };
if (customer && customer.id) { if (customer && customer.id) {
requestEditCustomer(customer.id, formValues) requestEditCustomer(customer.id, formValues)
@@ -189,7 +219,8 @@ function CustomerForm({
errors, errors,
values, values,
touched, touched,
handleSubmit isSubmitting,
handleSubmit,
} = useFormik({ } = useFormik({
enableReinitialize: true, enableReinitialize: true,
validationSchema: validationSchema, validationSchema: validationSchema,
@@ -199,7 +230,6 @@ function CustomerForm({
onSubmit: handleFormSubmit, onSubmit: handleFormSubmit,
}); });
const initialAttachmentFiles = useMemo(() => { const initialAttachmentFiles = useMemo(() => {
return customer && customer.media return customer && customer.media
? customer.media.map((attach) => ({ ? customer.media.map((attach) => ({
@@ -209,6 +239,7 @@ function CustomerForm({
})) }))
: []; : [];
}, []); }, []);
const handleDropFiles = useCallback((_files) => { const handleDropFiles = useCallback((_files) => {
setFiles(_files.filter((file) => file.uploaded === false)); setFiles(_files.filter((file) => file.uploaded === false));
}, []); }, []);
@@ -269,16 +300,19 @@ function CustomerForm({
getFieldProps={getFieldProps} getFieldProps={getFieldProps}
errors={errors} errors={errors}
values={values} values={values}
touched={touched} /> touched={touched}
customerId={customer}
/>
</div> </div>
<CustomerFloatingActions
onSubmitClick={handleSubmitClick}
customer={customer}
onCancelClick={handleCancelClick}
customerId={null}
/>
</form> </form>
<CustomerFloatingActions
isSubmitting={isSubmitting}
onSubmitClick={handleSubmitClick}
// customer={customer}
onCancelClick={handleCancelClick}
customerId={customer}
/>
</div> </div>
); );
} }

View File

@@ -31,12 +31,8 @@ export default function CustomerFormAfterPrimarySection({
/> />
</FormGroup> </FormGroup>
{/*------------ Customer email -----------*/} {/*------------ Phone number -----------*/}
<FormGroup <FormGroup
intent={errors.work_phone && touched.work_phone && Intent.DANGER}
helperText={
<ErrorMessage name={'work_phone'} {...{ errors, touched }} />
}
className={'form-group--phone-number'} className={'form-group--phone-number'}
label={<T id={'phone_number'} />} label={<T id={'phone_number'} />}
inline={true} inline={true}
@@ -49,8 +45,8 @@ export default function CustomerFormAfterPrimarySection({
/> />
<InputGroup <InputGroup
intent={errors.work_phone && touched.work_phone && Intent.DANGER} intent={errors.personal_phone && touched.personal_phone && Intent.DANGER}
{...getFieldProps('work_phone')} {...getFieldProps('personal_phone')}
placeholder={'Mobile'} placeholder={'Mobile'}
/> />
</ControlGroup> </ControlGroup>

View File

@@ -32,15 +32,20 @@ export default function CustomerFormPrimarySection({
); );
// Handle salutation field select. // Handle salutation field select.
const handleSalutationSelect = useCallback((salutation) => { const handleSalutationSelect = useCallback(
setFieldValue('salutation', salutation.label); (salutation) => {
}, [setFieldValue]); setFieldValue('salutation', salutation.label);
},
[setFieldValue],
);
// Handle display name field select. // Handle display name field select.
const handleDisplayNameSelect = useCallback((displayName) => { const handleDisplayNameSelect = useCallback(
setFieldValue('display_name', displayName.label); (displayName) => {
}, [setFieldValue]); setFieldValue('display_name', displayName.label);
},
[setFieldValue],
);
return ( return (
<div className={'customer-form__primary-section-content'}> <div className={'customer-form__primary-section-content'}>
{/**-----------Customer type. -----------*/} {/**-----------Customer type. -----------*/}
@@ -58,6 +63,7 @@ export default function CustomerFormPrimarySection({
<ControlGroup> <ControlGroup>
<SalutationList <SalutationList
onItemSelect={handleSalutationSelect} onItemSelect={handleSalutationSelect}
selectedItem={values.salutation}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
className={classNames( className={classNames(
CLASSES.FORM_GROUP_LIST_SELECT, CLASSES.FORM_GROUP_LIST_SELECT,
@@ -119,6 +125,7 @@ export default function CustomerFormPrimarySection({
company={values.company_name} company={values.company_name}
salutation={values.salutation} salutation={values.salutation}
onItemSelect={handleDisplayNameSelect} onItemSelect={handleDisplayNameSelect}
// selectedItem={values.display_name}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> />
</FormGroup> </FormGroup>

View File

@@ -10,14 +10,12 @@ import {
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import DataTable from 'components/DataTable';
import Icon from 'components/Icon';
import { Money } from 'components';
import { useUpdateEffect } from 'hooks'; import { useUpdateEffect } from 'hooks';
import LoadingIndicator from 'components/LoadingIndicator'; import LoadingIndicator from 'components/LoadingIndicator';
import withCustomers from './withCustomers'; import { DataTable, Icon, Money } from 'components';
import withCustomers from './withCustomers';
import { compose, firstLettersArgs, saveInvoke } from 'utils'; import { compose, firstLettersArgs, saveInvoke } from 'utils';
const AvatarCell = (row) => { const AvatarCell = (row) => {
@@ -25,13 +23,13 @@ const AvatarCell = (row) => {
}; };
const CustomerTable = ({ const CustomerTable = ({
loading,
//#withCustomers //#withCustomers
customers, customers,
customersLoading, customersLoading,
customerPagination,
//#props //#OwnProps
loading,
onEditCustomer, onEditCustomer,
onDeleteCustomer, onDeleteCustomer,
onFetchData, onFetchData,
@@ -80,18 +78,21 @@ const CustomerTable = ({
); );
// Renders actions table cell. // Renders actions table cell.
const renderActionsCell = useMemo(() => ({ cell }) => ( const renderActionsCell = useMemo(
<Popover () => ({ cell }) => (
content={renderContextMenu({ <Popover
customer: cell.row.original, content={renderContextMenu({
onEditCustomer, customer: cell.row.original,
onDeleteCustomer, onEditCustomer,
})} onDeleteCustomer,
position={Position.RIGHT_BOTTOM} })}
> position={Position.RIGHT_BOTTOM}
<Button icon={<Icon icon="more-h-16" iconSize={16} />} /> >
</Popover> <Button icon={<Icon icon="more-h-16" iconSize={16} />} />
), [onDeleteCustomer, onEditCustomer, renderContextMenu]); </Popover>
),
[onDeleteCustomer, onEditCustomer, renderContextMenu],
);
const columns = useMemo( const columns = useMemo(
() => [ () => [
@@ -186,14 +187,19 @@ const CustomerTable = ({
loading={customersLoading && !initialMount} loading={customersLoading && !initialMount}
spinnerProps={{ size: 30 }} spinnerProps={{ size: 30 }}
rowContextMenu={rowContextMenu} rowContextMenu={rowContextMenu}
pagination={true}
pagesCount={customerPagination.pagesCount}
initialPageSize={customerPagination.pageSize}
initialPageIndex={customerPagination.page - 1}
/> />
</LoadingIndicator> </LoadingIndicator>
); );
}; };
export default compose( export default compose(
withCustomers(({ customers, customersLoading }) => ({ withCustomers(({ customers, customersLoading, customerPagination }) => ({
customers, customers,
customersLoading, customersLoading,
customerPagination,
})), })),
)(CustomerTable); )(CustomerTable);

View File

@@ -16,8 +16,10 @@ import CustomersTable from 'containers/Customers/CustomerTable';
import CustomerActionsBar from 'containers/Customers/CustomerActionsBar'; import CustomerActionsBar from 'containers/Customers/CustomerActionsBar';
import CustomersViewsTabs from 'containers/Customers/CustomersViewsTabs'; import CustomersViewsTabs from 'containers/Customers/CustomersViewsTabs';
import withCustomers from 'containers/Customers/withCustomers';
import withCustomersActions from 'containers/Customers/withCustomersActions'; import withCustomersActions from 'containers/Customers/withCustomersActions';
import withResourceActions from 'containers/Resources/withResourcesActions'; import withResourceActions from 'containers/Resources/withResourcesActions';
import withViewsActions from 'containers/Views/withViewsActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import { compose } from 'utils'; import { compose } from 'utils';
@@ -30,7 +32,10 @@ function CustomersList({
requestFetchResourceViews, requestFetchResourceViews,
requestFetchResourceFields, requestFetchResourceFields,
//#withCustomersActions // #withCustomers
customersTableQuery,
// #withCustomersActions
requestFetchCustomers, requestFetchCustomers,
requestDeleteCustomer, requestDeleteCustomer,
requestDeleteBulkCustomers, requestDeleteBulkCustomers,
@@ -57,9 +62,10 @@ function CustomersList({
// ]); // ]);
// }); // });
const fetchCustomers = useQuery('customers-table', () => { const fetchCustomers = useQuery(
requestFetchCustomers({}); ['customers-table', customersTableQuery],
}); () => requestFetchCustomers(),
);
const handleEditCustomer = useCallback( const handleEditCustomer = useCallback(
(cusomter) => { (cusomter) => {
@@ -67,6 +73,7 @@ function CustomersList({
}, },
[history], [history],
); );
// Handle click delete customer. // Handle click delete customer.
const handleDeleteCustomer = useCallback( const handleDeleteCustomer = useCallback(
(customer) => { (customer) => {
@@ -169,26 +176,29 @@ function CustomersList({
}, [requestDeleteBulkCustomers, bulkDelete, formatMessage]); }, [requestDeleteBulkCustomers, bulkDelete, formatMessage]);
return ( return (
<DashboardInsider <DashboardInsider name={'customers-list'}>
loading={fetchCustomers.isFetching}
name={'customers-list'}
>
<CustomerActionsBar <CustomerActionsBar
selectedRows={selectedRows} selectedRows={selectedRows}
onFilterChanged={handleFilterChanged} onFilterChanged={handleFilterChanged}
onBulkDelete={handleBulkDelete} onBulkDelete={handleBulkDelete}
/> />
<CustomersViewsTabs />
<DashboardPageContent> <DashboardPageContent>
<CustomersTable <Switch>
loading={tableLoading} <Route
onDeleteCustomer={handleDeleteCustomer} exact={true}
onEditCustomer={handleEditCustomer} path={['/customers/:custom_view_id/custom_view', '/customers']}
onfetchData={handleFetchData} >
onSelectedRowsChange={handleSelectedRowsChange} <CustomersViewsTabs />
/> <CustomersTable
loading={fetchCustomers.isFetching}
onDeleteCustomer={handleDeleteCustomer}
onEditCustomer={handleEditCustomer}
onfetchData={handleFetchData}
onSelectedRowsChange={handleSelectedRowsChange}
/>
</Route>
</Switch>
<Alert <Alert
cancelButtonText={<T id={'cancel'} />} cancelButtonText={<T id={'cancel'} />}
@@ -230,6 +240,7 @@ function CustomersList({
export default compose( export default compose(
withResourceActions, withResourceActions,
withDashboardActions,
withCustomersActions, withCustomersActions,
withDashboardActions,
withCustomers(({ customersTableQuery }) => ({ customersTableQuery })),
)(CustomersList); )(CustomersList);

View File

@@ -12,9 +12,11 @@ export default function CustomersTabs({
errors, errors,
values, values,
touched, touched,
customerId,
}) { }) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const [customer] = useState(customerId);
return ( return (
<div> <div>
<Tabs <Tabs
@@ -32,6 +34,7 @@ export default function CustomersTabs({
errors={errors} errors={errors}
setFieldValue={setFieldValue} setFieldValue={setFieldValue}
touched={touched} touched={touched}
customerId={customer}
/> />
} }
/> />

View File

@@ -1,17 +1,27 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { getCustomersItems, getCustomersListFactory } from 'store/customers/customers.selectors';
import { getResourceViews } from 'store/customViews/customViews.selectors'; import { getResourceViews } from 'store/customViews/customViews.selectors';
import {
getCustomerCurrentPageFactory,
getCustomerPaginationMetaFactory,
getCustomerTableQueryFactory,
} from 'store/customers/customers.selectors';
export default (mapState) => { export default (mapState) => {
const getCustomersList = getCustomersListFactory(); const getCustomersList = getCustomerCurrentPageFactory();
const getCustomerPaginationMeta = getCustomerPaginationMetaFactory();
const getCustomerTableQuery = getCustomerTableQueryFactory();
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
const query = getCustomerTableQuery(state, props);
const mapped = { const mapped = {
customers: getCustomersList(state, props, query),
customersViews: getResourceViews(state, props, 'customers'), customersViews: getResourceViews(state, props, 'customers'),
customersItems: getCustomersList(state, props), customersTableQuery: query,
customers: getCustomersItems(state, state.customers.currentViewId), customerPagination: getCustomerPaginationMeta(state, props, query),
customersLoading: state.customers.loading, customersLoading: state.customers.loading,
customerErrors: state.customers.errors, customersItems: state.customers.items,
// customerErrors: state.customers.errors,
}; };
return mapState ? mapState(mapped, state, props) : mapped; return mapState ? mapState(mapped, state, props) : mapped;
}; };

View File

@@ -1,14 +1,9 @@
import { resolve } from 'p-progress';
import ApiService from 'services/ApiService'; import ApiService from 'services/ApiService';
import t from 'store/types'; import t from 'store/types';
export const submitCustomer = ({ form }) => { export const submitCustomer = ({ form }) => {
return (dispatch) => return (dispatch) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.post('customers', form) ApiService.post('customers', form)
.then((response) => { .then((response) => {
resolve(response); resolve(response);
@@ -25,10 +20,6 @@ export const submitCustomer = ({ form }) => {
export const editCustomer = ({ form, id }) => { export const editCustomer = ({ form, id }) => {
return (dispatch) => return (dispatch) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.post(`customers/${id}`, form) ApiService.post(`customers/${id}`, form)
.then((response) => { .then((response) => {
resolve(response); resolve(response);
@@ -45,7 +36,8 @@ export const editCustomer = ({ form, id }) => {
export const fetchCustomers = ({ query }) => { export const fetchCustomers = ({ query }) => {
return (dispatch, getState) => return (dispatch, getState) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const pageQuery = getState().items.tableQuery; const pageQuery = getState().customers.tableQuery;
dispatch({ dispatch({
type: t.CUSTOMERS_TABLE_LOADING, type: t.CUSTOMERS_TABLE_LOADING,
payload: { loading: true }, payload: { loading: true },
@@ -53,14 +45,25 @@ export const fetchCustomers = ({ query }) => {
ApiService.get(`customers`, { params: { ...pageQuery, ...query } }) ApiService.get(`customers`, { params: { ...pageQuery, ...query } })
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: t.CUSTOMERS_ITEMS_SET, type: t.CUSTOMERS_PAGE_SET,
customers: response.data.customers, payload: {
customers: response.data.customers,
customViewId: response.data.customViewId || -1,
paginationMeta: response.data.pagination,
},
}); });
dispatch({ dispatch({
type: t.CUSTOMERS_PAGE_SET, type: t.CUSTOMERS_ITEMS_SET,
customers: response.data.customers, payload: {
customViewId: response.data.customers?.viewMeta?.customViewId || -1, customers: response.data.customers,
paginationMeta: response.data.pagination, },
});
dispatch({
type: t.CUSTOMERS_PAGINATION_SET,
payload: {
pagination: response.data.pagination,
customViewId: response.data.customViewId || -1,
},
}); });
dispatch({ dispatch({
type: t.CUSTOMERS_TABLE_LOADING, type: t.CUSTOMERS_TABLE_LOADING,
@@ -83,7 +86,7 @@ export const fetchCustomer = ({ id }) => {
type: t.CUSTOMER_SET, type: t.CUSTOMER_SET,
payload: { payload: {
id, id,
customer: response.data.contact, customer: response.data.customer,
}, },
}); });
resolve(response); resolve(response);
@@ -99,7 +102,10 @@ export const deleteCustomer = ({ id }) => {
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
ApiService.delete(`customers/${id}`) ApiService.delete(`customers/${id}`)
.then((response) => { .then((response) => {
dispatch({ type: t.CUSTOMER_DELETE, id }); dispatch({
type: t.CUSTOMER_DELETE,
payload: { id },
});
resolve(response); resolve(response);
}) })
.catch((error) => { .catch((error) => {

View File

@@ -1,4 +1,5 @@
import t from 'store/types'; import t from 'store/types';
import { snakeCase } from 'lodash';
import { createReducer } from '@reduxjs/toolkit'; import { createReducer } from '@reduxjs/toolkit';
import { createTableQueryReducers } from 'store/queryReducers'; import { createTableQueryReducers } from 'store/queryReducers';
@@ -7,14 +8,26 @@ const initialState = {
views: {}, views: {},
loading: false, loading: false,
currentViewId: -1, currentViewId: -1,
tableQuery: {
page_size: 5,
page: 1,
},
errors: [], errors: [],
}; };
const customersReducer = createReducer(initialState, { const customersReducer = createReducer(initialState, {
[t.CUSTOMER_SET]: (state, action) => {
const { id, customer } = action.payload;
const _customers = state.items[id] || {};
state.items[id] = { ..._customers, ...customer };
},
[t.CUSTOMERS_ITEMS_SET]: (state, action) => { [t.CUSTOMERS_ITEMS_SET]: (state, action) => {
const { customers } = action.payload;
const _customers = {}; const _customers = {};
action.customers.forEach((customer) => { customers.forEach((customer) => {
_customers[customer.id] = customer; _customers[customer.id] = customer;
}); });
state.items = { state.items = {
@@ -22,24 +35,59 @@ const customersReducer = createReducer(initialState, {
..._customers, ..._customers,
}; };
}, },
[t.CUSTOMERS_PAGE_SET]: (state, action) => { [t.CUSTOMERS_PAGE_SET]: (state, action) => {
const viewId = action.customViewId || -1; const { customViewId, customers, paginationMeta } = action.payload;
const viewId = customViewId || -1;
const view = state.views[viewId] || {}; const view = state.views[viewId] || {};
state.views[viewId] = { state.views[viewId] = {
...view, ...view,
ids: action.customers.map((i) => i.id), pages: {
...(state.views?.[viewId]?.pages || {}),
[paginationMeta.page]: {
ids: customers.map((i) => i.id),
},
},
}; };
}, },
[t.CUSTOMER_DELETE]: (state, action) => { [t.CUSTOMER_DELETE]: (state, action) => {
if (typeof state.items[action.id] !== 'undefined') { const { id } = action.payload;
if (typeof state.items[id] !== 'undefined') {
delete state.items[action.id]; delete state.items[action.id];
} }
}, },
[t.CUSTOMERS_TABLE_LOADING]: (state, action) => { [t.CUSTOMERS_TABLE_LOADING]: (state, action) => {
const { loading } = action.payload; const { loading } = action.payload;
state.loading = !!loading; state.loading = loading;
}, },
[t.CUSTOMERS_PAGINATION_SET]: (state, action) => {
const { pagination, customViewId } = action.payload;
const mapped = {
pageSize: parseInt(pagination.page_size, 10),
page: parseInt(pagination.page, 10),
total: parseInt(pagination.total, 10),
};
const paginationMeta = {
...mapped,
pagesCount: Math.ceil(mapped.total / mapped.pageSize),
pageIndex: Math.max(mapped.page - 1, 0),
};
state.views = {
...state.views,
[customViewId]: {
...(state.views?.[customViewId] || {}),
paginationMeta,
},
};
},
[t.CUSTOMERS_BULK_DELETE]: (state, action) => { [t.CUSTOMERS_BULK_DELETE]: (state, action) => {
const { ids } = action.payload; const { ids } = action.payload;
const items = { ...state.items }; const items = { ...state.items };
@@ -51,10 +99,6 @@ const customersReducer = createReducer(initialState, {
}); });
state.items = items; state.items = items;
}, },
[t.CUSTOMER_SET]: (state, action) => {
const { id, customer } = action.payload;
state.items[id] = { ...customer };
},
}); });
export default createTableQueryReducers('customers', customersReducer); export default createTableQueryReducers('customers', customersReducer);

View File

@@ -1,26 +1,54 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { pickItemsFromIds } from 'store/selectors'; import { pickItemsFromIds, paginationLocationQuery } from 'store/selectors';
const customersViewsSelector = state => state.customers.views; const customerTableQuery = (state) => state.customers.tableQuery;
const customersItemsSelector = state => state.customers.items;
const customersCurrentViewSelector = state => state.customers.currentViewId;
export const getCustomersItems = createSelector( const customersByIdSelector = (state, props) => {
customersViewsSelector, return state.customers.items[props.customerId];
customersItemsSelector, };
customersCurrentViewSelector,
(customersViews, customersItems, currentViewId) => {
const customersView = customersViews[currentViewId || -1];
return (typeof customersView === 'object') const customersPaginationSelector = (state, props) => {
? pickItemsFromIds(customersItems, customersView.ids) || [] const viewId = state.customers.currentViewId;
: []; return state.customers.views?.[viewId];
}, };
);
const customerPageSelector = (state, props, query) => {
const viewId = state.customers.currentViewId;
return state.customers.views?.[viewId]?.pages?.[query.page];
};
const customersItemsSelector = (state) => state.customers.items;
export const getCustomerTableQueryFactory = () =>
createSelector(
paginationLocationQuery,
customerTableQuery,
(locationQuery, tableQuery) => {
return {
...locationQuery,
...tableQuery,
};
},
);
export const getCustomerCurrentPageFactory = () =>
createSelector(
customerPageSelector,
customersItemsSelector,
(customerPage, customersItems) => {
return typeof customerPage === 'object'
? pickItemsFromIds(customersItems, customerPage.ids) || []
: [];
},
);
export const getCustomersByIdFactory = () =>
createSelector(customersByIdSelector, (customer) => {
return customer;
});
export const getCustomerPaginationMetaFactory = () =>
createSelector(customersPaginationSelector, (customerPage) => {
return customerPage?.paginationMeta || {};
});
export const getCustomersListFactory = () => createSelector(
customersItemsSelector,
(customersItems) => {
return Object.values(customersItems);
}
);

View File

@@ -4,6 +4,7 @@ export default {
CUSTOMERS_PAGE_SET: 'CUSTOMERS_PAGE_SET', CUSTOMERS_PAGE_SET: 'CUSTOMERS_PAGE_SET',
CUSTOMERS_TABLE_LOADING: 'CUSTOMERS_TABLE_LOADING', CUSTOMERS_TABLE_LOADING: 'CUSTOMERS_TABLE_LOADING',
CUSTOMERS_TABLE_QUERIES_ADD: 'CUSTOMERS_TABLE_QUERIES_ADD', CUSTOMERS_TABLE_QUERIES_ADD: 'CUSTOMERS_TABLE_QUERIES_ADD',
CUSTOMER_DELETE:'CUSTOMER_DELETE', CUSTOMER_DELETE: 'CUSTOMER_DELETE',
CUSTOMERS_BULK_DELETE:'CUSTOMERS_BULK_DELETE' CUSTOMERS_BULK_DELETE: 'CUSTOMERS_BULK_DELETE',
CUSTOMERS_PAGINATION_SET: 'CUSTOMERS_PAGINATION_SET',
}; };