feat: customer form styling.

feat: customers list.
This commit is contained in:
Ahmed Bouhuolia
2020-11-08 21:44:20 +02:00
parent 982420c8e5
commit 9241f8b8a5
17 changed files with 305 additions and 282 deletions

View File

@@ -12,11 +12,7 @@ const CustomerBillingAddress = ({
getFieldProps,
}) => {
return (
<div
className={
'customer-form__tabs-section customer-form__tabs-section--address'
}
>
<div className={'tab-panel--address'}>
<Row>
<Col xs={6}>
<h4>

View File

@@ -28,7 +28,7 @@ export default function CustomerFinancialPanel({
);
return (
<div className={'customer-form__tabs-section customer-form__tabs-section--financial'}>
<div className={'tab-panel--financial'}>
<Row>
<Col xs={6}>
<FormGroup

View File

@@ -143,10 +143,46 @@ function CustomerForm({
useEffect(() => {
customer && customer.id
? changePageTitle(formatMessage({ id: 'edit_customer_details' }))
? changePageTitle(formatMessage({ id: 'edit_customer' }))
: changePageTitle(formatMessage({ id: 'new_customer' }));
}, [changePageTitle, customer, formatMessage]);
const handleFormSubmit = (values, { setSubmitting, resetForm, setErrors }) => {
const formValues = { ...values, status: payload.publish };
if (customer && customer.id) {
requestEditCustomer(customer.id, formValues)
.then((response) => {
AppToaster.show({
message: formatMessage({
id: 'the_item_customer_has_been_successfully_edited',
}),
intent: Intent.SUCCESS,
});
setSubmitting(false);
resetForm();
saveInvokeSubmit({ action: 'update', ...payload });
})
.catch((errors) => {
setSubmitting(false);
});
} else {
requestSubmitCustomer(formValues)
.then((response) => {
AppToaster.show({
message: formatMessage({
id: 'the_customer_has_been_successfully_created',
}),
intent: Intent.SUCCESS,
});
setSubmitting(false);
saveInvokeSubmit({ action: 'new', ...payload });
})
.catch((errors) => {
setSubmitting(false);
});
}
};
const {
setFieldValue,
getFieldProps,
@@ -160,42 +196,7 @@ function CustomerForm({
initialValues: {
...initialValues,
},
onSubmit: (values, { setSubmitting, resetForm, setErrors }) => {
const formValues = { ...values, status: payload.publish };
if (customer && customer.id) {
requestEditCustomer(customer.id, formValues)
.then((response) => {
AppToaster.show({
message: formatMessage({
id: 'the_item_customer_has_been_successfully_edited',
}),
intent: Intent.SUCCESS,
});
setSubmitting(false);
resetForm();
saveInvokeSubmit({ action: 'update', ...payload });
})
.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');
setSubmitting(false);
saveInvokeSubmit({ action: 'new', ...payload });
})
.catch((errors) => {
setSubmitting(false);
});
}
},
onSubmit: handleFormSubmit,
});

View File

@@ -32,13 +32,14 @@ export default function CustomerFormPrimarySection({
);
// Handle salutation field select.
const handleSalutationSelect = (salutation) => {
const handleSalutationSelect = useCallback((salutation) => {
setFieldValue('salutation', salutation.label);
};
}, [setFieldValue]);
// Handle display name field select.
const handleDisplayNameSelect = (displayName) => {
const handleDisplayNameSelect = useCallback((displayName) => {
setFieldValue('display_name', displayName.label);
};
}, [setFieldValue]);
return (
<div className={'customer-form__primary-section-content'}>
@@ -116,6 +117,7 @@ export default function CustomerFormPrimarySection({
firstName={values.first_name}
lastName={values.last_name}
company={values.company_name}
salutation={values.salutation}
onItemSelect={handleDisplayNameSelect}
popoverProps={{ minimal: true }}
/>

View File

@@ -7,28 +7,21 @@ import ErrorMessage from 'components/ErrorMessage';
export default function CustomerNotePanel({ errors, touched, getFieldProps }) {
return (
<div
className={
'customer-form__tabs-section customer-form__tabs-section--note'
}
>
<Row>
<Col xs={6}>
<FormGroup
label={<T id={'note'} />}
className={classNames('form-group--select-list', Classes.FILL)}
intent={errors.note && touched.note && Intent.DANGER}
helperText={
<ErrorMessage name="payment_date" {...{ errors, touched }} />
}
>
<TextArea
intent={errors.note && touched.note && Intent.DANGER}
{...getFieldProps('note')}
/>
</FormGroup>
</Col>
</Row>
<div className={'tab-panel--note'}>
<FormGroup
label={<T id={'note'} />}
className={classNames('form-group--note', Classes.FILL)}
intent={errors.note && touched.note && Intent.DANGER}
helperText={
<ErrorMessage name="payment_date" {...{ errors, touched }} />
}
>
<TextArea
intent={errors.note && touched.note && Intent.DANGER}
{...getFieldProps('note')}
/>
</FormGroup>
</div>
);
}

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import React, { useState, useCallback, useMemo } from 'react';
import {
Button,
Popover,
@@ -6,17 +6,23 @@ import {
MenuItem,
MenuDivider,
Position,
Intent,
} 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 { compose } from 'utils';
import { compose, firstLettersArgs, saveInvoke } from 'utils';
const AvatarCell = (row) => {
return <span className="avatar">{firstLettersArgs(row.display_name)}</span>;
};
const CustomerTable = ({
loading,
@@ -32,7 +38,6 @@ const CustomerTable = ({
onSelectedRowsChange,
}) => {
const { formatMessage } = useIntl();
const [initialMount, setInitialMount] = useState(false);
useUpdateEffect(() => {
@@ -41,36 +46,64 @@ const CustomerTable = ({
}
}, [customersLoading, setInitialMount]);
const handleEditCustomer = useCallback(
(customer) => () => {
onEditCustomer && onEditCustomer(customer);
// Customers actions list.
const renderContextMenu = useMemo(
() => ({ customer, onEditCustomer, onDeleteCustomer }) => {
const handleEditCustomer = () => {
saveInvoke(onEditCustomer, customer);
};
const handleDeleteCustomer = () => {
saveInvoke(onDeleteCustomer, customer);
};
return (
<Menu>
<MenuItem
icon={<Icon icon="reader-18" />}
text={formatMessage({ id: 'view_details' })}
/>
<MenuDivider />
<MenuItem
icon={<Icon icon="pen-18" />}
text={formatMessage({ id: 'edit_customer' })}
onClick={handleEditCustomer}
/>
<MenuItem
icon={<Icon icon="trash-16" iconSize={16} />}
text={formatMessage({ id: 'delete_customer' })}
intent={Intent.DANGER}
onClick={handleDeleteCustomer}
/>
</Menu>
);
},
[onEditCustomer],
[formatMessage],
);
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>
));
// 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 columns = useMemo(
() => [
{
id: 'avatar',
Header: '',
accessor: AvatarCell,
className: 'avatar',
width: 50,
disableResizing: true,
disableSortBy: true,
},
{
id: 'display_name',
Header: formatMessage({ id: 'display_name' }),
@@ -95,26 +128,20 @@ const CustomerTable = ({
{
id: 'receivable_balance',
Header: formatMessage({ id: 'receivable_balance' }),
// accessor: '',
accessor: (r) => <Money amount={r.closing_balance} currency={'USD'} />,
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>
),
Cell: renderActionsCell,
className: 'actions',
width: 50,
width: 70,
disableResizing: true,
disableSortBy: true,
},
],
[actionMenuList, formatMessage],
[formatMessage, renderActionsCell],
);
const selectionColumn = useMemo(
@@ -138,6 +165,13 @@ const CustomerTable = ({
[onSelectedRowsChange],
);
const rowContextMenu = (cell) =>
renderContextMenu({
customer: cell.row.original,
onEditCustomer,
onDeleteCustomer,
});
return (
<LoadingIndicator loading={loading} mount={false}>
<DataTable
@@ -146,11 +180,12 @@ const CustomerTable = ({
data={customers}
selectionColumn={selectionColumn}
onFetchData={handleFetchDate}
expandable={true}
treeGraph={true}
expandable={false}
treeGraph={false}
onSelectedRowsChange={handleSelectedRowsChange}
loading={customersLoading && !initialMount}
spinnerProps={{ size: 30 }}
rowContextMenu={rowContextMenu}
/>
</LoadingIndicator>
);

View File

@@ -135,11 +135,10 @@ function CustomersList({
filter_roles: filterConditions || '',
});
},
[fetchCustomers],
[addCustomersTableQueries],
);
// Handle Customers bulk delete button click.,
const handleBulkDelete = useCallback(
(customersIds) => {
setBulkDelete(customersIds);
@@ -184,7 +183,7 @@ function CustomersList({
<DashboardPageContent>
<CustomersTable
loadong={tableLoading}
loading={tableLoading}
onDeleteCustomer={handleDeleteCustomer}
onEditCustomer={handleEditCustomer}
onfetchData={handleFetchData}