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,8 +12,6 @@ import {
Checkbox,
Spinner,
ContextMenu,
Menu,
MenuItem,
} from '@blueprintjs/core';
import classnames from 'classnames';
import { FixedSizeList } from 'react-window';

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}

View File

@@ -501,7 +501,6 @@ export default {
billing_address: 'Billing Address',
shipping_address: 'Shipping Address',
customers_list: 'Customers List',
edit_customer_details: 'Edit Customer Details',
receivable_balance: 'Receivable balance',
the_customer_has_been_successfully_created:
'The customer has been successfully created.',
@@ -804,4 +803,6 @@ export default {
' Changing full amount will change all credits and payment were applied, Is this okay?',
address_line_1: 'Address line 1',
address_line_2: 'Address line 2',
website: 'Website',
notes: 'Notes',
};

View File

@@ -47,7 +47,7 @@ export const fetchCustomers = ({ query }) => {
new Promise((resolve, reject) => {
const pageQuery = getState().items.tableQuery;
dispatch({
type: t.ITEMS_TABLE_LOADING,
type: t.CUSTOMERS_TABLE_LOADING,
payload: { loading: true },
});
ApiService.get(`customers`, { params: { ...pageQuery, ...query } })

View File

@@ -13,37 +13,36 @@
overflow: hidden;
}
#{$self}__header{
.bp3-form-group{
max-width: 500px;
.bp3-form-group{
max-width: 500px;
&.bp3-inline{
.bp3-label{
min-width: 150px;
}
.bp3-form-content{
width: 100%;
}
}
.bp3-form-content{
width: 100%;
}
}
.form-group--contact_name{
max-width: 100%;
.form-group--contact_name{
max-width: 600px;
.bp3-control-group > *{
flex-shrink: unset;
.bp3-control-group > *{
flex-shrink: unset;
&:not(:last-child) {
padding-right: 10px;
}
&:not(:last-child) {
padding-right: 10px;
}
&.input-group--salutation-list{
width: 25%;
}
&.input-group--first-name,
&.input-group--last-name{
width: 37%;
}
&.input-group--salutation-list{
width: 25%;
}
&.input-group--first-name,
&.input-group--last-name{
width: 37%;
}
}
}
@@ -76,29 +75,52 @@
margin-top: 20px;
max-width: 1000px;
.bp3-form-group{
max-width: 440px;
.bp3-label{
min-width: 145px;
}
.bp3-form-content{
width: 100%;
}
textarea.bp3-input{
max-width: 100%;
width: 100%;
min-height: 50px;
}
}
h4{
font-weight: 500;
color: #888;
margin-bottom: 1.2rem;
font-size: 14px;
}
// Tab panels.
.tab-panel{
&--address{
.bp3-form-group{
max-width: 440px;
&.bp3-inline{
.bp3-label{
min-width: 145px;
}
}
.bp3-form-content{
width: 100%;
}
textarea.bp3-input{
max-width: 100%;
width: 100%;
min-height: 50px;
}
}
}
&--note{
.form-group--note{
.bp3-form-group{
max-width: 600px;
}
textarea{
width: 100%;
min-height: 100px;
}
}
}
}
.dropzone-container{
max-width: 600px;
}
}
.bp3-tabs{
@@ -136,115 +158,24 @@
}
}
.dashboard__insider--customers-list{
.customer-form{
.bigcapital-datatable{
.avatar.td{
&__primary-section{
background-color: #fafafa;
padding: 40px 22px 5px;
margin: -20px -20px 26px;
&-content{
width: 600px;
.avatar{
height: 30px;
width: 30px;
display: inline-block;
background: #f3e2f6;
border-radius: 50%;
line-height: 30px;
text-align: center;
font-weight: 400;
font-size: 14px;
color: #93639a;
}
}
}
&__after-primary-section{
&-content{
width: 600px;
}
}
}
// .customer-form {
// padding: 25px;
// padding-bottom: 90px;
// width: 100%;
// margin-bottom: 30px;
// .form-group--customer-type {
// .bp3-label {
// position: relative;
// display: inline-block;
// margin: 0 50px 30px 0px;
// }
// }
// // .form-group--contact-name {
// // .bp3-input-group .bp3-input {
// // position: relative;
// // // display: none;
// // width: 50%;
// // }
// // // .row {
// // // width: fit-content;
// // // }
// // // .#{$ns}-form-content{
// // // width: 350px;
// // // }
// // }
// h1 {
// font-size: 14px;
// margin-bottom: 20px;
// }
// &__primary-section {
//
// }
// &__tabs-section {
// position: relative;
// h4 {
// margin: 0;
// font-weight: 500;
// margin-bottom: 20px;
// font-size: 14px;
// color: #828282;
// }
// > div:first-of-type {
// padding-right: 40px !important;
// }
// > div ~ div {
// padding-left: 40px !important;
// }
// }
// .dropzone-container {
// align-self: end;
// }
// .dropzone {
// width: 300px;
// height: 100px;
// margin-right: 20px;
// }
// }
// .form-group--contact-name {
// .bp3-form-group.bp3-inline label.bp3-label {
// line-height: 30px;
// display: inline-block;
// margin: 0 45px 0 0;
// width: 200px;
// }
// .bp3-input-group .bp3-input {
// position: relative;
// // display: none;
// width: 100%;
// // margin-left: 30px;
// }
// // .row {
// // width: fit-content;
// // }
// // .#{$ns}-form-content{
// // width: 350px;
// // }
// }