Merge remote-tracking branch 'origin/customers'

This commit is contained in:
Ahmed Bouhuolia
2020-06-21 19:51:18 +02:00
17 changed files with 1360 additions and 69 deletions

View File

@@ -6,15 +6,36 @@ import DashboardInsider from 'components/Dashboard/DashboardInsider';
import CustomerForm from 'containers/Customers/CustomerForm';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withCustomersActions from './withCustomersActions';
import { compose } from 'utils';
function Customer({}) {
function Customer({
// #withDashboardActions
changePageTitle,
formik,
//#withCustomersActions
requestFetchCustomers,
}) {
const { id } = useParams();
const history = useHistory();
const fetchCustomers = useQuery('customers-list', () =>
requestFetchCustomers({}),
);
const fetchCustomerDatails =useQuery(id && ['customer-detail',id],()=>requestFetchCustomers())
return (
<DashboardInsider name={'customer-form'}>
<CustomerForm />
<DashboardInsider
// formik={formik}
loading={ fetchCustomerDatails.isFetching || fetchCustomers.isFetching}
name={'customer-form'}
>
<CustomerForm customerId={id} />
</DashboardInsider>
);
}
export default Customer;
export default compose(withDashboardActions, withCustomersActions)(Customer);

View File

@@ -0,0 +1,132 @@
import React, { useCallback, useMemo, useState } from 'react';
import {
NavbarGroup,
NavbarDivider,
Button,
Classes,
Intent,
Popover,
Position,
PopoverInteractionKind,
} from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import classNames from 'classnames';
import { connect } from 'react-redux';
import { useHistory } from 'react-router-dom';
import Icon from 'components/Icon';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import FilterDropdown from 'components/FilterDropdown';
import { If } from 'components';
import withResourceDetail from 'containers/Resources/withResourceDetails';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import addCustomersTableQueries from 'containers/Customers/withCustomersActions';
import { compose } from 'utils';
import withCustomersActions from 'containers/Customers/withCustomersActions';
const CustomerActionsBar = ({
// #withResourceDetail
resourceFields,
//#withCustomersActions
addCustomersTableQueries,
// #ownProps
selectedRows = [],
onFilterChanged,
onBulkDelete,
}) => {
const [filterCount, setFilterCount] = useState(0);
const history = useHistory();
const { formatMessage } = useIntl();
const onClickNewCustomer = useCallback(() => {
history.push('/customers/new');
}, [history]);
const filterDropdown = FilterDropdown({
fields: resourceFields,
onFilterChange: (filterConditions) => {
setFilterCount(filterConditions.length || 0);
addCustomersTableQueries({
filter_roles: filterConditions || '',
});
onFilterChanged && onFilterChanged(filterConditions);
},
});
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
selectedRows,
]);
const handleBulkDelete = useCallback(() => {
onBulkDelete && onBulkDelete(selectedRows.map((r) => r.id));
}, [onBulkDelete, selectedRows]);
return (
<DashboardActionsBar>
<NavbarGroup>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'plus'} />}
text={<T id={'new_customer'} />}
onClick={onClickNewCustomer}
/>
<NavbarDivider />
<Popover
content={filterDropdown}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={
filterCount <= 0 ? (
<T id={'filter'} />
) : (
`${filterCount} ${formatMessage({ id: 'filters_applied' })}`
)
}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<If condition={hasSelectedRows}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="trash-16" iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleBulkDelete}
/>
</If>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-import-16" iconSize={16} />}
text={<T id={'import'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
</NavbarGroup>
</DashboardActionsBar>
);
};
const mapStateToProps = (state, props) => ({
resourceName: 'customers',
});
const withCustomersActionsBar = connect(mapStateToProps);
export default compose(
withCustomersActionsBar,
withResourceDetail(({ resourceFields }) => ({
resourceFields,
})),
withCustomersActions,
)(CustomerActionsBar);

View File

@@ -0,0 +1,246 @@
import React, { useState, useMemo, useCallback, useEffect } from 'react';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import {
FormGroup,
MenuItem,
Intent,
InputGroup,
HTMLSelect,
Button,
Classes,
} from '@blueprintjs/core';
import { Row, Col } from 'react-grid-system';
import { FormattedMessage as T, useIntl } from 'react-intl';
import ErrorMessage from 'components/ErrorMessage';
const CustomerBillingAddress = ({
formik: { errors, touched, setFieldValue, getFieldProps },
}) => {
return (
<div className={'customer-form'}>
<Row gutterWidth={16} className={'customer-form__tabs-section'}>
<Col width={404}>
<h4>
<T id={'billing_address'} />
</h4>
<FormGroup
label={<T id={'country'} />}
className={'form-group--journal-number'}
intent={
errors.billing_address_country &&
touched.billing_address_country &&
Intent.DANGER
}
inline={true}
helperText={
<ErrorMessage
name="billing_address_country"
{...{ errors, touched }}
/>
}
>
<InputGroup
intent={
errors.billing_address_country &&
touched.billing_address_country &&
Intent.DANGER
}
{...getFieldProps('billing_address_country')}
/>
</FormGroup>
<FormGroup
label={<T id={'city_town'} />}
className={'form-group--journal-number'}
intent={
errors.billing_address_city &&
touched.billing_address_city &&
Intent.DANGER
}
inline={true}
helperText={
<ErrorMessage
name="billing_address_city"
{...{ errors, touched }}
/>
}
>
<InputGroup
intent={
errors.billing_address_city &&
touched.billing_address_city &&
Intent.DANGER
}
{...getFieldProps('billing_address_city')}
/>
</FormGroup>
<FormGroup
label={<T id={'state'} />}
className={'form-group--journal-number'}
intent={
errors.billing_address_state &&
touched.billing_address_state &&
Intent.DANGER
}
inline={true}
helperText={
<ErrorMessage
name="billing_address_state"
{...{ errors, touched }}
/>
}
>
<InputGroup
intent={
errors.billing_address_state &&
touched.billing_address_state &&
Intent.DANGER
}
{...getFieldProps('billing_address_state')}
/>
</FormGroup>
<FormGroup
label={<T id={'zip_code'} />}
intent={
errors.billing_address_zipcode &&
touched.billing_address_zipcode &&
Intent.DANGER
}
inline={true}
helperText={
<ErrorMessage
name="billing_address_zipcode"
{...{ errors, touched }}
/>
}
>
<InputGroup
intent={
errors.billing_address_zipcode &&
touched.billing_address_zipcode &&
Intent.DANGER
}
{...getFieldProps('billing_address_zipcode')}
/>
</FormGroup>
</Col>
<Col width={404}>
<h4>
<T id={'shipping_address'} />
</h4>
<FormGroup
label={<T id={'country'} />}
className={'form-group--journal-number'}
intent={
errors.shipping_address_country &&
touched.shipping_address_country &&
Intent.DANGER
}
inline={true}
helperText={
<ErrorMessage
name="shipping_address_country"
{...{ errors, touched }}
/>
}
>
<InputGroup
intent={
errors.shipping_address_country &&
touched.shipping_address_country &&
Intent.DANGER
}
{...getFieldProps('shipping_address_country')}
/>
</FormGroup>
<FormGroup
label={<T id={'city_town'} />}
className={'form-group--journal-number'}
intent={
errors.shipping_address_city &&
touched.shipping_address_city &&
Intent.DANGER
}
inline={true}
helperText={
<ErrorMessage
name="shipping_address_city"
{...{ errors, touched }}
/>
}
>
<InputGroup
intent={
errors.shipping_address_city &&
touched.shipping_address_city &&
Intent.DANGER
}
{...getFieldProps('shipping_address_city')}
/>
</FormGroup>
<FormGroup
label={<T id={'state'} />}
className={'form-group--journal-number'}
intent={
errors.shipping_address_state &&
touched.shipping_address_state &&
Intent.DANGER
}
inline={true}
helperText={
<ErrorMessage
name="shipping_address_state"
{...{ errors, touched }}
/>
}
>
<InputGroup
intent={
errors.shipping_address_state &&
touched.shipping_address_state &&
Intent.DANGER
}
{...getFieldProps('shipping_address_state')}
/>
</FormGroup>
<FormGroup
label={<T id={'zip_code'} />}
intent={
errors.shipping_address_zipcode &&
touched.shipping_address_zipcode &&
Intent.DANGER
}
inline={true}
helperText={
<ErrorMessage
name="shipping_address_zipcode"
{...{ errors, touched }}
/>
}
>
<InputGroup
intent={
errors.shipping_address_zipcode &&
touched.shipping_address_zipcode &&
Intent.DANGER
}
{...getFieldProps('shipping_address_zipcode')}
/>
</FormGroup>
</Col>
</Row>
</div>
);
};
export default CustomerBillingAddress;

View File

@@ -0,0 +1,23 @@
import React, {
useMemo,
useState,
useEffect,
useRef,
useCallback,
} from 'react';
import Dragzone from 'components/Dragzone';
function CustomerAttachmentTabs() {
return (
<div>
<Dragzone
initialFiles={[]}
onDrop={null}
onDeleteFile={[]}
hint={'Attachments: Maxiumum size: 20MB'}
/>
</div>
);
}
export default CustomerAttachmentTabs;

View File

@@ -9,8 +9,6 @@ import {
Button,
Classes,
Checkbox,
RadioGroup,
Radio,
} from '@blueprintjs/core';
import { Row, Col } from 'react-grid-system';
import { FormattedMessage as T, useIntl } from 'react-intl';
@@ -21,28 +19,51 @@ 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 CustomersTabs from 'containers/Customers/CustomersTabs';
import RadioCustomer from 'containers/Customers/RadioCustomer';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withCustomerDetail from './withCustomerDetail';
import withCustomersActions from './withCustomersActions';
import RadioCustomer from './RadioCustomer';
import withCustomerDetail from 'containers/Customers/withCustomerDetail';
import withCustomersActions from 'containers/Customers/withCustomersActions';
import withMediaActions from 'containers/Media/withMediaActions';
import withCustomers from 'containers/Customers//withCustomers';
import useMedia from 'hooks/useMedia';
import { compose, handleStringChange } from 'utils';
import withCustomers from './withCustomers';
import { compose } from 'utils';
function CustomerForm({
// #withDashboardActions
changePageTitle,
//#withCustomers
customers,
//#withCustomerDetail
customerDetail,
//#withCustomersActions
requestSubmitCustomer,
requestFetchCustomers,
requestEditCustomer,
// #withMediaActions
requestSubmitMedia,
requestDeleteMedia,
}) {
const { formatMessage } = useIntl();
const history = useHistory();
const {
setFiles,
saveMedia,
deletedFiles,
setDeletedFiles,
deleteMedia,
} = useMedia({
saveCallback: requestSubmitMedia,
deleteCallback: requestDeleteMedia,
});
const validationSchema = Yup.object().shape({
customer_type: Yup.string()
@@ -51,40 +72,79 @@ function CustomerForm({
.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(),
work_phone: Yup.number(),
active: Yup.boolean(),
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_state: Yup.string().trim(),
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_state: Yup.string().trim(),
});
useEffect(() => {
changePageTitle(formatMessage({ id: 'new_customer' }));
}, [changePageTitle, formatMessage]);
//business
const initialValues = useMemo(
const defaultInitialValues = useMemo(
() => ({
customer_type: 'business',
first_name: '',
last_name: '',
company_name: '',
display_name: '',
// email: '',
email: '',
work_phone: '',
active: true,
billing_address_city: '',
billing_address_country: '',
billing_address_zipcode: null,
billing_address_phone: '',
billing_address_state: '',
shipping_address_city: '',
shipping_address_country: '',
shipping_address_zipcode: null,
shipping_address_phone: '',
shipping_address_state: '',
}),
[],
);
const {
getFieldProps,
setFieldValue,
values,
touched,
errors,
handleSubmit,
isSubmitting,
} = useFormik({
const initialValues = useMemo(
() => ({
...(customerDetail
? {
...pick(customerDetail, Object.keys(defaultInitialValues)),
}
: {
...defaultInitialValues,
}),
}),
[customerDetail, defaultInitialValues],
);
useEffect(() => {
customerDetail && customerDetail.id
? changePageTitle(formatMessage({ id: 'edit_customer_details' }))
: changePageTitle(formatMessage({ id: 'new_customer' }));
}, [changePageTitle, customerDetail, formatMessage]);
const formik = useFormik({
enableReinitialize: true,
validationSchema: validationSchema,
initialValues: {
@@ -92,46 +152,176 @@ function CustomerForm({
},
onSubmit: (values, { setSubmitting, resetForm, setErrors }) => {
requestSubmitCustomer(values)
.then((response) => {
AppToaster.show({
message: formatMessage({
id: 'the_customer_has_been_successfully_created',
}),
intent: Intent.SUCCESS,
const formValues = { ...values };
if (customerDetail && customerDetail.id) {
requestEditCustomer(customerDetail.id, formValues)
.then((response) => {
AppToaster.show({
message: formatMessage({
id: 'the_item_customer_has_been_successfully_edited',
}),
intent: Intent.SUCCESS,
});
setSubmitting(false);
history.push('/customers');
resetForm();
})
.catch((errors) => {
setSubmitting(false);
});
})
.catch((errors) => {
setSubmitting(false);
});
} else {
requestSubmitCustomer(formValues)
.then((response) => {
AppToaster.show({
message: formatMessage({
id: 'the_customer_has_been_successfully_created',
}),
intent: Intent.SUCCESS,
});
history.push('/customers');
})
.catch((errors) => {
setSubmitting(false);
});
}
},
});
const requiredSpan = useMemo(() => <span class="required">*</span>, []);
const handleCustomerTypeCahange = useCallback(
(value) => {
setFieldValue('customer_type', value);
formik.setFieldValue('customer_type', value);
},
[setFieldValue],
[formik.setFieldValue],
);
console.log(customers, 'ER');
console.log(formik.values, 'ER');
const { errors, touched, getFieldProps, values, isSubmitting } = useMemo(
() => formik,
[formik],
);
const fetch = useQuery('customers-table', (key) => requestFetchCustomers());
const initialAttachmentFiles = useMemo(() => {
return customerDetail && customerDetail.media
? customerDetail.media.map((attach) => ({
preview: attach.attachment_file,
upload: true,
metadata: { ...attach },
}))
: [];
}, []);
const handleDropFiles = useCallback((_files) => {
setFiles(_files.filter((file) => file.uploaded === false));
}, []);
const handleDeleteFile = useCallback(
(_deletedFiles) => {
_deletedFiles.forEach((deletedFile) => {
if (deletedFile.uploaded && deletedFile.metadata.id) {
setDeletedFiles([...deletedFiles, deletedFile.metadata.id]);
}
});
},
[setDeletedFiles, deletedFiles],
);
const handleCancelClickBtn = () => {
history.goBack();
};
return (
<div className={'customer-form'}>
<form onSubmit={handleSubmit}>
<form onSubmit={formik.handleSubmit}>
<div className={'customer-form__primary-section'}>
<RadioCustomer
selectedValue={values.customer_type}
selectedValue={formik.values.customer_type}
onChange={handleCustomerTypeCahange}
className={'form-group--customer-type'}
/>
<Row>
<Col md={3.5} className={'form-group--contact-name'}>
<FormGroup
label={<T id={'contact_name'} />}
inline={true}
intent={
formik.errors.first_name &&
formik.touched.first_name &&
Intent.DANGER
}
helperText={
<ErrorMessage name={'first_name'} {...{ errors, touched }} />
}
// className={'form-group--contact-name'}
>
<InputGroup
placeholder={'First Name'}
intent={
formik.errors.first_name &&
formik.touched.first_name &&
Intent.DANGER
}
{...formik.getFieldProps('first_name')}
/>
</FormGroup>
</Col>
<Col md={2}>
<FormGroup
inline={true}
intent={
formik.errors.last_name &&
formik.touched.last_name &&
Intent.DANGER
}
helperText={
<ErrorMessage name={'last_name'} {...{ errors, touched }} />
}
// className={'form-group--contact-name'}
>
<InputGroup
placeholder={'Last Name'}
intent={
formik.errors.last_name &&
formik.touched.last_name &&
Intent.DANGER
}
{...formik.getFieldProps('last_name')}
/>
</FormGroup>
</Col>
</Row>
{/* Company Name */}
<FormGroup
label={<T id={'company_name'} />}
className={'form-group--company_name'}
labelInfo={requiredSpan}
intent={
formik.errors.company_name &&
formik.touched.company_name &&
Intent.DANGER
}
inline={true}
helperText={
<ErrorMessage {...{ errors, touched }} name={'company_name'} />
}
>
<InputGroup
intent={
formik.errors.company_name &&
formik.touched.company_name &&
Intent.DANGER
}
{...formik.getFieldProps('company_name')}
/>
</FormGroup>
{/* Display Name */}
<FormGroup
label={<T id={'display_name'} />}
className={'form-group--name'}
intent={
errors.display_name && touched.display_name && Intent.DANGER
formik.errors.display_name &&
formik.touched.display_name &&
Intent.DANGER
}
inline={true}
helperText={
@@ -140,23 +330,96 @@ function CustomerForm({
>
<InputGroup
intent={
errors.display_name && touched.display_name && Intent.DANGER
formik.errors.display_name &&
formik.touched.display_name &&
Intent.DANGER
}
{...getFieldProps('display_name')}
{...formik.getFieldProps('display_name')}
/>
</FormGroup>
</div>
<Row>
{/* Email */}
<Col md={6}>
<FormGroup
label={<T id={'email'} />}
intent={
formik.errors.email && formik.touched.email && Intent.DANGER
}
helperText={
<ErrorMessage name={'email'} {...{ errors, touched }} />
}
className={'form-group--email'}
inline={true}
>
<InputGroup
intent={
formik.errors.email && formik.touched.email && Intent.DANGER
}
{...formik.getFieldProps('email')}
/>
</FormGroup>
</Col>
{/* Active checkbox */}
<FormGroup label={' '} inline={true} className={'form-group--active'}>
<Checkbox
inline={true}
label={<T id={'active'} />}
defaultChecked={formik.values.active}
{...formik.getFieldProps('active')}
/>
</FormGroup>
</Row>
<FormGroup
label={<T id={'phone_number'} />}
intent={
formik.errors.work_phone &&
formik.touched.work_phone &&
Intent.DANGER
}
helperText={
<ErrorMessage name={'work_phone'} {...{ errors, touched }} />
}
className={'form-group--phone-number'}
inline={true}
>
<InputGroup
intent={
formik.errors.work_phone &&
formik.touched.work_phone &&
Intent.DANGER
}
{...formik.getFieldProps('work_phone')}
/>
</FormGroup>
<CustomersTabs formik={formik} />
<div class="form__floating-footer">
<Button intent={Intent.PRIMARY} disabled={isSubmitting} type="submit">
<T id={'save'} />
{customerDetail && customerDetail.id ? (
<T id={'edit'} />
) : (
<T id={'save'} />
)}
</Button>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
className={'ml1'}
name={'save_and_new'}
>
<T id={'save_new'} />
</Button>
<Button className={'ml1'} disabled={isSubmitting}>
<T id={'save_as_draft'} />
</Button>
<Button className={'ml1'}>
<Button className={'ml1'} onClick={handleCancelClickBtn}>
<T id={'close'} />
</Button>
</div>

View File

@@ -0,0 +1,36 @@
import React from 'react';
import {
Button,
Classes,
FormGroup,
InputGroup,
Intent,
TextArea,
MenuItem,
} from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import ErrorMessage from 'components/ErrorMessage';
const CustomerBillingAddress = ({
formik: { errors, touched, setFieldValue, getFieldProps },
}) => {
return (
<div>
<FormGroup
label={<T id={'note'} />}
// className={'form-group--description'}
intent={errors.note && touched.note && Intent.DANGER}
helperText={<ErrorMessage name="note" {...{ errors, touched }} />}
inline={true}
>
<TextArea
growVertically={true}
large={true}
{...getFieldProps('note')}
/>
</FormGroup>
</div>
);
};
export default CustomerBillingAddress;

View File

@@ -0,0 +1,164 @@
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import {
Button,
Popover,
Menu,
MenuItem,
MenuDivider,
Position,
} from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import DataTable from 'components/DataTable';
import Icon from 'components/Icon';
import { useUpdateEffect } from 'hooks';
import LoadingIndicator from 'components/LoadingIndicator';
import withCustomers from './withCustomers';
import { compose } from 'utils';
const CustomerTable = ({
loading,
//#withCustomers
customers,
customersLoading,
//#props
onEditCustomer,
onDeleteCustomer,
onFetchData,
onSelectedRowsChange,
}) => {
const { formatMessage } = useIntl();
const [initialMount, setInitialMount] = useState(false);
useUpdateEffect(() => {
if (!customersLoading) {
setInitialMount(true);
}
}, [customersLoading, setInitialMount]);
const handleEditCustomer = useCallback(
(customer) => () => {
onEditCustomer && onEditCustomer(customer);
},
[onEditCustomer],
);
const handleDeleteCustomer = useCallback(
(customer) => () => {
onDeleteCustomer && onDeleteCustomer(customer);
},
[onDeleteCustomer],
);
const actionMenuList = useCallback((customer) => (
<Menu>
<MenuItem text={<T id={'view_details'} />} />
<MenuDivider />
<MenuItem
text={<T id={'edit_customer'} />}
onClick={handleEditCustomer(customer)}
/>
<MenuItem
text={<T id={'delete_customer'} />}
onClick={handleDeleteCustomer(customer)}
/>
</Menu>
));
const columns = useMemo(
() => [
{
id: 'display_name',
Header: formatMessage({ id: 'display_name' }),
accessor: 'display_name',
className: 'display_name',
width: 150,
},
{
id: 'company_name',
Header: formatMessage({ id: 'company_name' }),
accessor: 'company_name',
className: 'company_name',
width: 150,
},
{
id: 'phone_number',
Header: formatMessage({ id: 'phone_number' }),
accessor: 'work_phone',
className: 'phone_number',
width: 100,
},
{
id: 'receivable_balance',
Header: formatMessage({ id: 'receivable_balance' }),
// accessor: '',
className: 'receivable_balance',
width: 100,
},
{
id: 'actions',
Cell: ({ cell }) => (
<Popover
content={actionMenuList(cell.row.original)}
position={Position.RIGHT_BOTTOM}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
),
className: 'actions',
width: 50,
},
],
[actionMenuList, formatMessage],
);
const selectionColumn = useMemo(
() => ({
minWidth: 42,
width: 42,
maxWidth: 42,
}),
[],
);
const handleFetchDate = useCallback((...args) => {
onFetchData && onFetchData(...args);
});
const handleSelectedRowsChange = useCallback(
(selectedRows) => {
onSelectedRowsChange &&
onSelectedRowsChange(selectedRows.map((s) => s.original));
},
[onSelectedRowsChange],
);
return (
<LoadingIndicator loading={loading} mount={false}>
<DataTable
noInitialFetch={true}
columns={columns}
data={customers}
selectionColumn={selectionColumn}
onFetchData={handleFetchDate}
expandable={true}
treeGraph={true}
onSelectedRowsChange={handleSelectedRowsChange}
loading={customersLoading && !initialMount}
spinnerProps={{ size: 30 }}
/>
</LoadingIndicator>
);
};
export default compose(
withCustomers(({ customers, customersLoading }) => ({
customers,
customersLoading,
})),
)(CustomerTable);

View File

@@ -0,0 +1,232 @@
import React, { useEffect, useCallback, useState, useMemo } from 'react';
import { Route, Switch, useHistory } from 'react-router-dom';
import { Intent, Alert } from '@blueprintjs/core';
import { useQuery } from 'react-query';
import {
FormattedMessage as T,
FormattedHTMLMessage,
useIntl,
} from 'react-intl';
import AppToaster from 'components/AppToaster';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import CustomersTable from 'containers/Customers/CustomerTable';
import CustomerActionsBar from 'containers/Customers/CustomerActionsBar';
import withCustomersActions from 'containers/Customers/withCustomersActions';
import withResourceActions from 'containers/Resources/withResourcesActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import { compose } from 'utils';
function CustomersList({
// #withDashboardActions
changePageTitle,
// #withResourceActions
requestFetchResourceViews,
requestFetchResourceFields,
//#withCustomersActions
requestFetchCustomers,
requestDeleteCustomer,
requestDeleteBulkCustomers,
addCustomersTableQueries,
}) {
const [deleteCustomer, setDeleteCustomer] = useState(false);
const [selectedRows, setSelectedRows] = useState([]);
const [tableLoading, setTableLoading] = useState(false);
const [bulkDelete, setBulkDelete] = useState(false);
const { formatMessage } = useIntl();
const history = useHistory();
useEffect(() => {
changePageTitle(formatMessage({ id: 'customers_list' }));
}, [changePageTitle]);
// Fetch customers resource views and fields.
// const fetchHook = useQuery('resource-customers', () => {
// return Promise.all([
// requestFetchResourceViews('customers'),
// requestFetchResourceFields('customers'),
// ]);
// });
const fetchCustomers = useQuery('customers-table', () => {
requestFetchCustomers({});
});
const handleEditCustomer = useCallback(
(cusomter) => {
history.push(`/customers/${cusomter.id}/edit`);
},
[history],
);
// Handle click delete customer.
const handleDeleteCustomer = useCallback(
(customer) => {
setDeleteCustomer(customer);
},
[setDeleteCustomer],
);
// Handle cancel delete the customer.
const handleCancelDeleteCustomer = useCallback(() => {
setDeleteCustomer(false);
}, [setDeleteCustomer]);
// handle confirm delete customer.
const handleConfirmDeleteCustomer = useCallback(() => {
requestDeleteCustomer(deleteCustomer.id).then(() => {
AppToaster.show({
message: formatMessage({
id: 'the_customer_has_been_successfully_deleted',
}),
intent: Intent.SUCCESS,
});
setDeleteCustomer(false);
});
}, [requestDeleteCustomer, deleteCustomer, formatMessage]);
// Handle fetch data table.
const handleFetchData = useCallback(
({ pageIndex, pageSize, sortBy }) => {
addCustomersTableQueries({
...(sortBy.length > 0
? {
column_sort_order: sortBy[0].id,
sort_order: sortBy[0].desc ? 'desc' : 'asc',
}
: {}),
});
fetchCustomers.refetch();
},
[fetchCustomers, addCustomersTableQueries],
);
// Handle selected rows change.
const handleSelectedRowsChange = useCallback(
(customer) => {
setSelectedRows(customer);
},
[setSelectedRows],
);
useEffect(() => {
if (tableLoading && !fetchCustomers.isFetching) {
setTableLoading(false);
}
}, [tableLoading, fetchCustomers.isFetching]);
// Calculates the data table selected rows count.
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [
selectedRows,
]);
// Handle filter change to re-fetch the items.
const handleFilterChanged = useCallback(
(filterConditions) => {
addCustomersTableQueries({
filter_roles: filterConditions || '',
});
},
[fetchCustomers],
);
// Handle items bulk delete button click.,
const handleBulkDelete = useCallback(
(itemsIds) => {
setBulkDelete(itemsIds);
},
[setBulkDelete],
);
// Handle cancel accounts bulk delete.
const handleCancelBulkDelete = useCallback(() => {
setBulkDelete(false);
}, []);
// Handle confirm items bulk delete.
const handleConfirmBulkDelete = useCallback(() => {
requestDeleteBulkCustomers(bulkDelete)
.then(() => {
setBulkDelete(false);
AppToaster.show({
message: formatMessage({
id: 'the_customers_has_been_successfully_deleted',
}),
intent: Intent.SUCCESS,
});
})
.catch((errors) => {
setBulkDelete(false);
});
}, [requestDeleteBulkCustomers, bulkDelete, formatMessage]);
return (
<DashboardInsider
loading={fetchCustomers.isFetching}
name={'customers-list'}
>
<CustomerActionsBar
selectedRows={selectedRows}
onFilterChanged={handleFilterChanged}
onBulkDelete={handleBulkDelete}
/>
<DashboardPageContent>
<CustomersTable
loadong={tableLoading}
onDeleteCustomer={handleDeleteCustomer}
onEditCustomer={handleEditCustomer}
onfetchData={handleFetchData}
onSelectedRowsChange={handleSelectedRowsChange}
/>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon="trash"
intent={Intent.DANGER}
isOpen={deleteCustomer}
onCancel={handleCancelDeleteCustomer}
onConfirm={handleConfirmDeleteCustomer}
>
<p>
<FormattedHTMLMessage
id={'once_delete_this_customer_you_will_able_to_restore_it'}
/>
</p>
</Alert>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={`${formatMessage({
id: 'delete',
})} (${selectedRowsCount})`}
icon="trash"
intent={Intent.DANGER}
isOpen={bulkDelete}
onCancel={handleCancelBulkDelete}
onConfirm={handleConfirmBulkDelete}
>
<p>
<T
id={'once_delete_these_customers_you_will_not_able_restore_them'}
/>
</p>
</Alert>
</DashboardPageContent>
</DashboardInsider>
);
}
export default compose(
withResourceActions,
withDashboardActions,
withCustomersActions,
)(CustomersList);

View File

@@ -0,0 +1,41 @@
import React, { useState, useCallback } from 'react';
import { Tabs, Tab } from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import CustomerAddressTabs from './CustomerAddressTabs';
import CustomerNotTabs from './CustomerNotTabs';
import CustomerAttachmentTabs from './CustomerAttachmentTabs';
function CustomersTabs({ formik }) {
const [animate, setAnimate] = useState(true);
const { formatMessage } = useIntl();
const handleChangeTabs = useCallback(() => {}, []);
return (
<div>
<Tabs animate={animate} id={'customer-tabs'} large={true}>
<Tab
id={'other'}
title={formatMessage({ id: 'other' })}
panel={'Other'}
/>
<Tab
id={'address'}
title={formatMessage({ id: 'address' })}
panel={<CustomerAddressTabs formik={formik} />}
/>
<Tab
id={'attachement'}
title={formatMessage({ id: 'attachement' })}
panel={<CustomerAttachmentTabs />}
/>
<Tab
id={'note'}
title={formatMessage({ id: 'note' })}
panel={<CustomerNotTabs formik={formik} />}
/>
</Tabs>
</div>
);
}
export default CustomersTabs;

View File

@@ -3,15 +3,16 @@ import {
fetchCustomers,
submitCustomer,
editCustomer,
deleteCustomer,
} from 'store/customers/customers.actions';
import t from 'store/types';
export const mapDispatchToProps = (dispatch) => ({
requestFetchCustomers: (query) => dispatch(fetchCustomers({ query })),
// requestDeleteCustomer: (id) => dispatch(deleteCustomer({ id })),
requestDeleteCustomer: (id) => dispatch(deleteCustomer({ id })),
// requestDeleteBulkCustomers:(ids)=>dispatch(deleteBulkCustomers({ids})),
requestSubmitCustomer: (form) => dispatch(submitCustomer({ form })),
// requestEditCustomer: (id, form) => dispatch(editCustomer({ id, form })),
requestEditCustomer: (id, form) => dispatch(editCustomer({ id, form })),
addCustomersTableQueries: (queries) =>
dispatch({