mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +00:00
Merge remote-tracking branch 'origin/customers'
This commit is contained in:
@@ -67,7 +67,7 @@ export default [
|
|||||||
divider: true,
|
divider: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Financial accounting',
|
text: <T id={'financial_accounting'} />,
|
||||||
label: true,
|
label: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,15 +6,36 @@ import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
|||||||
import CustomerForm from 'containers/Customers/CustomerForm';
|
import CustomerForm from 'containers/Customers/CustomerForm';
|
||||||
|
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
|
import withCustomersActions from './withCustomersActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
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 (
|
return (
|
||||||
<DashboardInsider name={'customer-form'}>
|
<DashboardInsider
|
||||||
<CustomerForm />
|
// formik={formik}
|
||||||
|
loading={ fetchCustomerDatails.isFetching || fetchCustomers.isFetching}
|
||||||
|
name={'customer-form'}
|
||||||
|
>
|
||||||
|
<CustomerForm customerId={id} />
|
||||||
</DashboardInsider>
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Customer;
|
export default compose(withDashboardActions, withCustomersActions)(Customer);
|
||||||
|
|||||||
132
client/src/containers/Customers/CustomerActionsBar.js
Normal file
132
client/src/containers/Customers/CustomerActionsBar.js
Normal 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);
|
||||||
246
client/src/containers/Customers/CustomerAddressTabs.js
Normal file
246
client/src/containers/Customers/CustomerAddressTabs.js
Normal 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;
|
||||||
23
client/src/containers/Customers/CustomerAttachmentTabs.js
Normal file
23
client/src/containers/Customers/CustomerAttachmentTabs.js
Normal 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;
|
||||||
@@ -9,8 +9,6 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Classes,
|
Classes,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
RadioGroup,
|
|
||||||
Radio,
|
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { Row, Col } from 'react-grid-system';
|
import { Row, Col } from 'react-grid-system';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
@@ -21,28 +19,51 @@ import classNames from 'classnames';
|
|||||||
|
|
||||||
import AppToaster from 'components/AppToaster';
|
import AppToaster from 'components/AppToaster';
|
||||||
import ErrorMessage from 'components/ErrorMessage';
|
import ErrorMessage from 'components/ErrorMessage';
|
||||||
import Icon from 'components/Icon';
|
|
||||||
import MoneyInputGroup from 'components/MoneyInputGroup';
|
import CustomersTabs from 'containers/Customers/CustomersTabs';
|
||||||
import Dragzone from 'components/Dragzone';
|
import RadioCustomer from 'containers/Customers/RadioCustomer';
|
||||||
|
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import withCustomerDetail from './withCustomerDetail';
|
import withCustomerDetail from 'containers/Customers/withCustomerDetail';
|
||||||
import withCustomersActions from './withCustomersActions';
|
import withCustomersActions from 'containers/Customers/withCustomersActions';
|
||||||
import RadioCustomer from './RadioCustomer';
|
import withMediaActions from 'containers/Media/withMediaActions';
|
||||||
|
import withCustomers from 'containers/Customers//withCustomers';
|
||||||
|
import useMedia from 'hooks/useMedia';
|
||||||
|
|
||||||
import { compose, handleStringChange } from 'utils';
|
import { compose } from 'utils';
|
||||||
import withCustomers from './withCustomers';
|
|
||||||
|
|
||||||
function CustomerForm({
|
function CustomerForm({
|
||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
|
|
||||||
|
//#withCustomers
|
||||||
customers,
|
customers,
|
||||||
|
|
||||||
|
//#withCustomerDetail
|
||||||
|
customerDetail,
|
||||||
|
|
||||||
//#withCustomersActions
|
//#withCustomersActions
|
||||||
requestSubmitCustomer,
|
requestSubmitCustomer,
|
||||||
requestFetchCustomers,
|
requestFetchCustomers,
|
||||||
|
requestEditCustomer,
|
||||||
|
|
||||||
|
// #withMediaActions
|
||||||
|
requestSubmitMedia,
|
||||||
|
requestDeleteMedia,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const {
|
||||||
|
setFiles,
|
||||||
|
saveMedia,
|
||||||
|
deletedFiles,
|
||||||
|
setDeletedFiles,
|
||||||
|
deleteMedia,
|
||||||
|
} = useMedia({
|
||||||
|
saveCallback: requestSubmitMedia,
|
||||||
|
deleteCallback: requestDeleteMedia,
|
||||||
|
});
|
||||||
|
|
||||||
const validationSchema = Yup.object().shape({
|
const validationSchema = Yup.object().shape({
|
||||||
customer_type: Yup.string()
|
customer_type: Yup.string()
|
||||||
@@ -51,40 +72,79 @@ function CustomerForm({
|
|||||||
.label(formatMessage({ id: 'customer_type_' })),
|
.label(formatMessage({ id: 'customer_type_' })),
|
||||||
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(),
|
||||||
|
|
||||||
display_name: Yup.string()
|
display_name: Yup.string()
|
||||||
.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.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(() => {
|
const defaultInitialValues = useMemo(
|
||||||
changePageTitle(formatMessage({ id: 'new_customer' }));
|
|
||||||
}, [changePageTitle, formatMessage]);
|
|
||||||
//business
|
|
||||||
const initialValues = useMemo(
|
|
||||||
() => ({
|
() => ({
|
||||||
customer_type: 'business',
|
customer_type: 'business',
|
||||||
first_name: '',
|
first_name: '',
|
||||||
last_name: '',
|
last_name: '',
|
||||||
company_name: '',
|
company_name: '',
|
||||||
display_name: '',
|
display_name: '',
|
||||||
// email: '',
|
email: '',
|
||||||
work_phone: '',
|
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,
|
const initialValues = useMemo(
|
||||||
setFieldValue,
|
() => ({
|
||||||
values,
|
...(customerDetail
|
||||||
touched,
|
? {
|
||||||
errors,
|
...pick(customerDetail, Object.keys(defaultInitialValues)),
|
||||||
handleSubmit,
|
}
|
||||||
isSubmitting,
|
: {
|
||||||
} = useFormik({
|
...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,
|
enableReinitialize: true,
|
||||||
validationSchema: validationSchema,
|
validationSchema: validationSchema,
|
||||||
initialValues: {
|
initialValues: {
|
||||||
@@ -92,46 +152,176 @@ function CustomerForm({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onSubmit: (values, { setSubmitting, resetForm, setErrors }) => {
|
onSubmit: (values, { setSubmitting, resetForm, setErrors }) => {
|
||||||
requestSubmitCustomer(values)
|
const formValues = { ...values };
|
||||||
.then((response) => {
|
if (customerDetail && customerDetail.id) {
|
||||||
AppToaster.show({
|
requestEditCustomer(customerDetail.id, formValues)
|
||||||
message: formatMessage({
|
.then((response) => {
|
||||||
id: 'the_customer_has_been_successfully_created',
|
AppToaster.show({
|
||||||
}),
|
message: formatMessage({
|
||||||
intent: Intent.SUCCESS,
|
id: 'the_item_customer_has_been_successfully_edited',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
setSubmitting(false);
|
||||||
|
history.push('/customers');
|
||||||
|
resetForm();
|
||||||
|
})
|
||||||
|
.catch((errors) => {
|
||||||
|
setSubmitting(false);
|
||||||
});
|
});
|
||||||
})
|
} else {
|
||||||
.catch((errors) => {
|
requestSubmitCustomer(formValues)
|
||||||
setSubmitting(false);
|
.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 requiredSpan = useMemo(() => <span class="required">*</span>, []);
|
||||||
const handleCustomerTypeCahange = useCallback(
|
const handleCustomerTypeCahange = useCallback(
|
||||||
(value) => {
|
(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 (
|
return (
|
||||||
<div className={'customer-form'}>
|
<div className={'customer-form'}>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={formik.handleSubmit}>
|
||||||
<div className={'customer-form__primary-section'}>
|
<div className={'customer-form__primary-section'}>
|
||||||
<RadioCustomer
|
<RadioCustomer
|
||||||
selectedValue={values.customer_type}
|
selectedValue={formik.values.customer_type}
|
||||||
onChange={handleCustomerTypeCahange}
|
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
|
<FormGroup
|
||||||
label={<T id={'display_name'} />}
|
label={<T id={'display_name'} />}
|
||||||
className={'form-group--name'}
|
|
||||||
intent={
|
intent={
|
||||||
errors.display_name && touched.display_name && Intent.DANGER
|
formik.errors.display_name &&
|
||||||
|
formik.touched.display_name &&
|
||||||
|
Intent.DANGER
|
||||||
}
|
}
|
||||||
inline={true}
|
inline={true}
|
||||||
helperText={
|
helperText={
|
||||||
@@ -140,23 +330,96 @@ function CustomerForm({
|
|||||||
>
|
>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
intent={
|
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>
|
</FormGroup>
|
||||||
</div>
|
</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">
|
<div class="form__floating-footer">
|
||||||
<Button intent={Intent.PRIMARY} disabled={isSubmitting} type="submit">
|
<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>
|
||||||
|
|
||||||
<Button className={'ml1'} disabled={isSubmitting}>
|
<Button className={'ml1'} disabled={isSubmitting}>
|
||||||
<T id={'save_as_draft'} />
|
<T id={'save_as_draft'} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button className={'ml1'}>
|
<Button className={'ml1'} onClick={handleCancelClickBtn}>
|
||||||
<T id={'close'} />
|
<T id={'close'} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
36
client/src/containers/Customers/CustomerNotTabs.js
Normal file
36
client/src/containers/Customers/CustomerNotTabs.js
Normal 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;
|
||||||
164
client/src/containers/Customers/CustomerTable.js
Normal file
164
client/src/containers/Customers/CustomerTable.js
Normal 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);
|
||||||
232
client/src/containers/Customers/CustomersList.js
Normal file
232
client/src/containers/Customers/CustomersList.js
Normal 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);
|
||||||
41
client/src/containers/Customers/CustomersTabs.js
Normal file
41
client/src/containers/Customers/CustomersTabs.js
Normal 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;
|
||||||
@@ -3,15 +3,16 @@ import {
|
|||||||
fetchCustomers,
|
fetchCustomers,
|
||||||
submitCustomer,
|
submitCustomer,
|
||||||
editCustomer,
|
editCustomer,
|
||||||
|
deleteCustomer,
|
||||||
} from 'store/customers/customers.actions';
|
} from 'store/customers/customers.actions';
|
||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
|
|
||||||
export const mapDispatchToProps = (dispatch) => ({
|
export const mapDispatchToProps = (dispatch) => ({
|
||||||
requestFetchCustomers: (query) => dispatch(fetchCustomers({ query })),
|
requestFetchCustomers: (query) => dispatch(fetchCustomers({ query })),
|
||||||
// requestDeleteCustomer: (id) => dispatch(deleteCustomer({ id })),
|
requestDeleteCustomer: (id) => dispatch(deleteCustomer({ id })),
|
||||||
// requestDeleteBulkCustomers:(ids)=>dispatch(deleteBulkCustomers({ids})),
|
// requestDeleteBulkCustomers:(ids)=>dispatch(deleteBulkCustomers({ids})),
|
||||||
requestSubmitCustomer: (form) => dispatch(submitCustomer({ form })),
|
requestSubmitCustomer: (form) => dispatch(submitCustomer({ form })),
|
||||||
// requestEditCustomer: (id, form) => dispatch(editCustomer({ id, form })),
|
requestEditCustomer: (id, form) => dispatch(editCustomer({ id, form })),
|
||||||
|
|
||||||
addCustomersTableQueries: (queries) =>
|
addCustomersTableQueries: (queries) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ export default {
|
|||||||
as: 'As',
|
as: 'As',
|
||||||
receivable_aging_summary: 'Receivable Aging Summary',
|
receivable_aging_summary: 'Receivable Aging Summary',
|
||||||
customers: 'Customers',
|
customers: 'Customers',
|
||||||
new_customers:'New Customers',
|
new_customers: 'New Customers',
|
||||||
customer_type_: 'Customer type',
|
customer_type_: 'Customer type',
|
||||||
display_name_: 'Display name',
|
display_name_: 'Display name',
|
||||||
new_customer: 'New Customer',
|
new_customer: 'New Customer',
|
||||||
@@ -471,5 +471,35 @@ export default {
|
|||||||
next: 'Next',
|
next: 'Next',
|
||||||
previous: 'Previous',
|
previous: 'Previous',
|
||||||
showing_current_page_to_total: 'Showing {currentPage} to {totalPages} of {total} entries',
|
showing_current_page_to_total: 'Showing {currentPage} to {totalPages} of {total} entries',
|
||||||
new_child_account: 'New Child Account'
|
new_child_account: 'New Child Account',
|
||||||
|
display_name: 'Display Name',
|
||||||
|
contact_name: 'Contact Name',
|
||||||
|
company_name: 'Company Name',
|
||||||
|
other: 'Other',
|
||||||
|
address: 'Address',
|
||||||
|
attachement: 'Attachement',
|
||||||
|
country: 'Country',
|
||||||
|
city_town: 'City/Town',
|
||||||
|
state: 'State',
|
||||||
|
zip_code: 'ZIP/Code',
|
||||||
|
streat: 'Streat',
|
||||||
|
edit_customer: 'Edit Customer',
|
||||||
|
delete_customer: 'Delete Customer',
|
||||||
|
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.',
|
||||||
|
the_customer_has_been_successfully_deleted:
|
||||||
|
'The customer has been successfully deleted.',
|
||||||
|
the_customers_has_been_successfully_deleted:
|
||||||
|
'The customers have been successfully deleted.',
|
||||||
|
the_item_customer_has_been_successfully_edited:
|
||||||
|
'The item customer has been successfully edited.',
|
||||||
|
once_delete_this_customer_you_will_able_to_restore_it: `Once you delete this customer, you won\'t be able to restore it later. Are you sure you want to delete this cusomter?`,
|
||||||
|
once_delete_these_customers_you_will_not_able_restore_them:
|
||||||
|
"Once you delete these customers, you won't be able to retrieve them later. Are you sure you want to delete them?",
|
||||||
|
financial_accounting: 'Financial accounting'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -183,10 +183,11 @@ export default [
|
|||||||
{
|
{
|
||||||
path: `/customers/:id/edit`,
|
path: `/customers/:id/edit`,
|
||||||
component: LazyLoader({
|
component: LazyLoader({
|
||||||
// loader: () => import(),
|
loader: () => import('containers/Customers/Customer'),
|
||||||
}),
|
}),
|
||||||
breadcrumb: 'Edit Customer',
|
breadcrumb: 'Edit Customer',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: `/customers/new`,
|
path: `/customers/new`,
|
||||||
component: LazyLoader({
|
component: LazyLoader({
|
||||||
@@ -199,7 +200,7 @@ export default [
|
|||||||
{
|
{
|
||||||
path: `/customers`,
|
path: `/customers`,
|
||||||
component: LazyLoader({
|
component: LazyLoader({
|
||||||
// loader: () => import(''),
|
loader: () => import('containers/Customers/CustomersList'),
|
||||||
}),
|
}),
|
||||||
breadcrumb: 'Customers',
|
breadcrumb: 'Customers',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export const fetchCustomers = ({ query }) => {
|
|||||||
type: t.CUSTOMER_SET,
|
type: t.CUSTOMER_SET,
|
||||||
customers: response.data.customers.results,
|
customers: response.data.customers.results,
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.CUSTOMERS_PAGE_SET,
|
type: t.CUSTOMERS_PAGE_SET,
|
||||||
customers: response.data.customers.results,
|
customers: response.data.customers.results,
|
||||||
@@ -91,3 +92,18 @@ export const fetchCustomers = ({ query }) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const deleteCustomer = ({ id }) => {
|
||||||
|
return (dispatch) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
ApiService.delete(`customers/${id}`)
|
||||||
|
.then((response) => {
|
||||||
|
dispatch({ type: t.CUSTOMER_DELETE, id });
|
||||||
|
resolve(response);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error.response.data.errors || []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -36,10 +36,14 @@ const customersReducer = createReducer(initialState, {
|
|||||||
delete state.items[action.id];
|
delete state.items[action.id];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
[t.CUSTOMERS_TABLE_LOADING]: (state, action) => {
|
||||||
|
const { loading } = action.payload;
|
||||||
|
state.loading = !!loading;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default createTableQueryReducers('customers', customersReducer);
|
export default createTableQueryReducers('customers', customersReducer);
|
||||||
|
|
||||||
export const getCustomerById = (state, id) => {
|
export const getCustomerById = (state, id) => {
|
||||||
return state.customers[id];
|
return state.customers.items[id];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { pickItemsFromIds } from 'store/selectors';
|
import { pickItemsFromIds } from 'store/selectors';
|
||||||
|
|
||||||
export const getCustomersItems = (state, viewId) => {
|
export const getCustomersItems = (state, viewId) => {
|
||||||
|
|
||||||
const customersView = state.customers.views[viewId || -1];
|
const customersView = state.customers.views[viewId || -1];
|
||||||
const customersItems = state.customers.items;
|
const customersItems = state.customers.items;
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,105 @@
|
|||||||
.customer-form {
|
.customer-form {
|
||||||
padding: 22px;
|
padding: 25px;
|
||||||
|
padding-bottom: 90px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-bottom: 30px;
|
||||||
// padding-bottom: 90px;
|
|
||||||
|
|
||||||
.bp3-form-group {
|
.bp3-form-group {
|
||||||
// margin: 25px 20px 20px;
|
|
||||||
|
|
||||||
.bp3-label {
|
.bp3-label {
|
||||||
min-width: 100px;
|
width: 130px;
|
||||||
}
|
}
|
||||||
.bp3-form-content {
|
.bp3-form-content {
|
||||||
width: 45%;
|
width: 375px;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
min-width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 120px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 {
|
&__primary-section {
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
padding: 40px 22px 22px;
|
padding: 40px 22px 22px;
|
||||||
margin: -22px -22px 22px;
|
margin: -22px -22px 22px;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__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;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user