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 { useQuery } from 'react-query';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import CustomerForm from 'containers/Customers/CustomerForm';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withCustomersActions from './withCustomersActions';
import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions';
import { compose } from 'utils';
function Customer({
// #withDashboardActions
changePageTitle,
// // #withDashboardActions
// changePageTitle,
formik,
// formik,
//#withCustomersActions
requestFetchCustomers,
requestFetchCustomer,
// #wihtCurrenciesActions
requestFetchCurrencies,
}) {
const { id } = useParams();
const history = useHistory();
@@ -32,7 +35,11 @@ function Customer({
(key, customerId) => requestFetchCustomer(customerId),
{ enabled: id && id },
);
// Handle fetch Currencies data table
const fetchCurrencies = useQuery('currencies', () =>
requestFetchCurrencies(),
);
const handleFormSubmit = useCallback(
(payload) => {
payload.redirect && history.push('/customers');
@@ -46,7 +53,11 @@ function Customer({
return (
<DashboardInsider
loading={fetchCustomer.isFetching || fetchCustomers.isFetching}
loading={
fetchCustomer.isFetching ||
fetchCustomers.isFetching ||
fetchCurrencies.isFetching
}
name={'customer-form'}
>
<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>
<T id={'billing_address'} />
</h4>
{/*------------ Billing Address country -----------*/}
<FormGroup
className={'form-group--journal-number'}
intent={
@@ -44,7 +44,7 @@ const CustomerBillingAddress = ({
{...getFieldProps('billing_address_country')}
/>
</FormGroup>
{/*------------ Billing Address 1 -----------*/}
<FormGroup
label={<T id={'address_line_1'} />}
className={'form-group--address_line_1'}
@@ -67,7 +67,7 @@ const CustomerBillingAddress = ({
{...getFieldProps('billing_address_1')}
/>
</FormGroup>
{/*------------ Billing Address 2 -----------*/}
<FormGroup
label={<T id={'address_line_2'} />}
className={'form-group--journal-number'}
@@ -90,7 +90,7 @@ const CustomerBillingAddress = ({
{...getFieldProps('billing_address_2')}
/>
</FormGroup>
{/*------------ Billing Address city -----------*/}
<FormGroup
label={<T id={'city_town'} />}
className={'form-group--journal-number'}
@@ -116,7 +116,7 @@ const CustomerBillingAddress = ({
{...getFieldProps('billing_address_city')}
/>
</FormGroup>
{/*------------ Billing Address state -----------*/}
<FormGroup
label={<T id={'state'} />}
className={'form-group--journal-number'}
@@ -142,47 +142,47 @@ const CustomerBillingAddress = ({
{...getFieldProps('billing_address_state')}
/>
</FormGroup>
{/*------------ Billing Address postcode -----------*/}
<FormGroup
label={<T id={'zip_code'} />}
intent={
errors.billing_address_zipcode &&
touched.billing_address_zipcode &&
errors.billing_address_postcode &&
touched.billing_address_postcode &&
Intent.DANGER
}
inline={true}
helperText={
<ErrorMessage
name="billing_address_zipcode"
name="billing_address_postcode"
{...{ errors, touched }}
/>
}
>
<InputGroup
intent={
errors.billing_address_zipcode &&
touched.billing_address_zipcode &&
errors.billing_address_postcode &&
touched.billing_address_postcode &&
Intent.DANGER
}
{...getFieldProps('billing_address_zipcode')}
{...getFieldProps('billing_address_postcode')}
/>
</FormGroup>
{/*------------ Billing Address phone -----------*/}
<FormGroup
label={<T id={'phone'} />}
intent={
errors.shipping_phone && touched.shipping_phone && Intent.DANGER
errors.billing_address_phone && touched.billing_address_phone && Intent.DANGER
}
inline={true}
helperText={
<ErrorMessage name="shipping_phone" {...{ errors, touched }} />
<ErrorMessage name="billing_address_phone" {...{ errors, touched }} />
}
>
<InputGroup
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>
</Col>
@@ -191,7 +191,7 @@ const CustomerBillingAddress = ({
<h4>
<T id={'shipping_address'} />
</h4>
{/*------------ Shipping Address country -----------*/}
<FormGroup
label={<T id={'country'} />}
className={'form-group--journal-number'}
@@ -217,7 +217,7 @@ const CustomerBillingAddress = ({
{...getFieldProps('shipping_address_country')}
/>
</FormGroup>
{/*------------ Shipping Address 1 -----------*/}
<FormGroup
label={<T id={'address_line_1'} />}
className={'form-group--journal-number'}
@@ -240,7 +240,7 @@ const CustomerBillingAddress = ({
{...getFieldProps('billing_address_1')}
/>
</FormGroup>
{/*------------ Shipping Address 2 -----------*/}
<FormGroup
label={<T id={'address_line_2'} />}
className={'form-group--journal-number'}
@@ -263,7 +263,7 @@ const CustomerBillingAddress = ({
{...getFieldProps('billing_address_2')}
/>
</FormGroup>
{/*------------ Shipping Address city -----------*/}
<FormGroup
label={<T id={'city_town'} />}
className={'form-group--journal-number'}
@@ -289,7 +289,7 @@ const CustomerBillingAddress = ({
{...getFieldProps('shipping_address_city')}
/>
</FormGroup>
{/*------------ Shipping Address state -----------*/}
<FormGroup
label={<T id={'state'} />}
className={'form-group--journal-number'}
@@ -315,47 +315,47 @@ const CustomerBillingAddress = ({
{...getFieldProps('shipping_address_state')}
/>
</FormGroup>
{/*------------ Shipping Address postcode -----------*/}
<FormGroup
label={<T id={'zip_code'} />}
intent={
errors.shipping_address_zipcode &&
touched.shipping_address_zipcode &&
errors.shipping_address_postcode &&
touched.shipping_address_postcode &&
Intent.DANGER
}
inline={true}
helperText={
<ErrorMessage
name="shipping_address_zipcode"
name="shipping_address_postcode"
{...{ errors, touched }}
/>
}
>
<InputGroup
intent={
errors.shipping_address_zipcode &&
touched.shipping_address_zipcode &&
errors.shipping_address_postcode &&
touched.shipping_address_postcode &&
Intent.DANGER
}
{...getFieldProps('shipping_address_zipcode')}
{...getFieldProps('shipping_address_postcode')}
/>
</FormGroup>
{/*------------ Shipping Address phone -----------*/}
<FormGroup
label={<T id={'phone'} />}
intent={
errors.shipping_phone && touched.shipping_phone && Intent.DANGER
errors.shipping_address_phone && touched.shipping_address_phone && Intent.DANGER
}
inline={true}
helperText={
<ErrorMessage name="shipping_phone" {...{ errors, touched }} />
<ErrorMessage name="shipping_address_phone" {...{ errors, touched }} />
}
>
<InputGroup
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>
</Col>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
import t from 'store/types';
import { snakeCase } from 'lodash';
import { createReducer } from '@reduxjs/toolkit';
import { createTableQueryReducers } from 'store/queryReducers';
@@ -7,14 +8,26 @@ const initialState = {
views: {},
loading: false,
currentViewId: -1,
tableQuery: {
page_size: 5,
page: 1,
},
errors: [],
};
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) => {
const { customers } = action.payload;
const _customers = {};
action.customers.forEach((customer) => {
customers.forEach((customer) => {
_customers[customer.id] = customer;
});
state.items = {
@@ -22,24 +35,59 @@ const customersReducer = createReducer(initialState, {
..._customers,
};
},
[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] || {};
state.views[viewId] = {
...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) => {
if (typeof state.items[action.id] !== 'undefined') {
const { id } = action.payload;
if (typeof state.items[id] !== 'undefined') {
delete state.items[action.id];
}
},
[t.CUSTOMERS_TABLE_LOADING]: (state, action) => {
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) => {
const { ids } = action.payload;
const items = { ...state.items };
@@ -51,10 +99,6 @@ const customersReducer = createReducer(initialState, {
});
state.items = items;
},
[t.CUSTOMER_SET]: (state, action) => {
const { id, customer } = action.payload;
state.items[id] = { ...customer };
},
});
export default createTableQueryReducers('customers', customersReducer);

View File

@@ -1,26 +1,54 @@
import { createSelector } from 'reselect';
import { pickItemsFromIds } from 'store/selectors';
import { pickItemsFromIds, paginationLocationQuery } from 'store/selectors';
const customersViewsSelector = state => state.customers.views;
const customersItemsSelector = state => state.customers.items;
const customersCurrentViewSelector = state => state.customers.currentViewId;
const customerTableQuery = (state) => state.customers.tableQuery;
export const getCustomersItems = createSelector(
customersViewsSelector,
customersItemsSelector,
customersCurrentViewSelector,
(customersViews, customersItems, currentViewId) => {
const customersView = customersViews[currentViewId || -1];
const customersByIdSelector = (state, props) => {
return state.customers.items[props.customerId];
};
return (typeof customersView === 'object')
? pickItemsFromIds(customersItems, customersView.ids) || []
: [];
},
);
const customersPaginationSelector = (state, props) => {
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_TABLE_LOADING: 'CUSTOMERS_TABLE_LOADING',
CUSTOMERS_TABLE_QUERIES_ADD: 'CUSTOMERS_TABLE_QUERIES_ADD',
CUSTOMER_DELETE:'CUSTOMER_DELETE',
CUSTOMERS_BULK_DELETE:'CUSTOMERS_BULK_DELETE'
CUSTOMER_DELETE: 'CUSTOMER_DELETE',
CUSTOMERS_BULK_DELETE: 'CUSTOMERS_BULK_DELETE',
CUSTOMERS_PAGINATION_SET: 'CUSTOMERS_PAGINATION_SET',
};