diff --git a/client/src/config/sidebarMenu.js b/client/src/config/sidebarMenu.js index 338df7efc..72ace0b7b 100644 --- a/client/src/config/sidebarMenu.js +++ b/client/src/config/sidebarMenu.js @@ -134,7 +134,7 @@ export default [ children: [ { text: , - // href: '/', + href: '/customers', }, { text: , diff --git a/client/src/containers/Customers/Customer.js b/client/src/containers/Customers/Customer.js index 021d3b33e..e4a8e237b 100644 --- a/client/src/containers/Customers/Customer.js +++ b/client/src/containers/Customers/Customer.js @@ -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 ( - - + + ); } -export default Customer; +export default compose(withDashboardActions, withCustomersActions)(Customer); diff --git a/client/src/containers/Customers/CustomerActionsBar.js b/client/src/containers/Customers/CustomerActionsBar.js new file mode 100644 index 000000000..8dae4374b --- /dev/null +++ b/client/src/containers/Customers/CustomerActionsBar.js @@ -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 ( + + + } + text={} + onClick={onClickNewCustomer} + /> + + + + ) : ( + `${filterCount} ${formatMessage({ id: 'filters_applied' })}` + ) + } + icon={} + /> + + + + } + text={} + intent={Intent.DANGER} + onClick={handleBulkDelete} + /> + + + } + text={} + /> + } + text={} + /> + + + ); +}; + +const mapStateToProps = (state, props) => ({ + resourceName: 'customers', +}); + +const withCustomersActionsBar = connect(mapStateToProps); + +export default compose( + withCustomersActionsBar, + withResourceDetail(({ resourceFields }) => ({ + resourceFields, + })), + withCustomersActions, +)(CustomerActionsBar); diff --git a/client/src/containers/Customers/CustomerAddressTabs.js b/client/src/containers/Customers/CustomerAddressTabs.js new file mode 100644 index 000000000..6aeeacfb1 --- /dev/null +++ b/client/src/containers/Customers/CustomerAddressTabs.js @@ -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 ( + + + + + + + + } + className={'form-group--journal-number'} + intent={ + errors.billing_address_country && + touched.billing_address_country && + Intent.DANGER + } + inline={true} + helperText={ + + } + > + + + + } + className={'form-group--journal-number'} + intent={ + errors.billing_address_city && + touched.billing_address_city && + Intent.DANGER + } + inline={true} + helperText={ + + } + > + + + + } + className={'form-group--journal-number'} + intent={ + errors.billing_address_state && + touched.billing_address_state && + Intent.DANGER + } + inline={true} + helperText={ + + } + > + + + + } + intent={ + errors.billing_address_zipcode && + touched.billing_address_zipcode && + Intent.DANGER + } + inline={true} + helperText={ + + } + > + + + + + + + + + } + className={'form-group--journal-number'} + intent={ + errors.shipping_address_country && + touched.shipping_address_country && + Intent.DANGER + } + inline={true} + helperText={ + + } + > + + + + } + className={'form-group--journal-number'} + intent={ + errors.shipping_address_city && + touched.shipping_address_city && + Intent.DANGER + } + inline={true} + helperText={ + + } + > + + + + } + className={'form-group--journal-number'} + intent={ + errors.shipping_address_state && + touched.shipping_address_state && + Intent.DANGER + } + inline={true} + helperText={ + + } + > + + + + } + intent={ + errors.shipping_address_zipcode && + touched.shipping_address_zipcode && + Intent.DANGER + } + inline={true} + helperText={ + + } + > + + + + + + ); +}; + +export default CustomerBillingAddress; diff --git a/client/src/containers/Customers/CustomerAttachmentTabs.js b/client/src/containers/Customers/CustomerAttachmentTabs.js new file mode 100644 index 000000000..1f07e51aa --- /dev/null +++ b/client/src/containers/Customers/CustomerAttachmentTabs.js @@ -0,0 +1,23 @@ +import React, { + useMemo, + useState, + useEffect, + useRef, + useCallback, +} from 'react'; +import Dragzone from 'components/Dragzone'; + +function CustomerAttachmentTabs() { + return ( + + + + ); +} + +export default CustomerAttachmentTabs; diff --git a/client/src/containers/Customers/CustomerForm.js b/client/src/containers/Customers/CustomerForm.js index 4da06f613..4de5b6b47 100644 --- a/client/src/containers/Customers/CustomerForm.js +++ b/client/src/containers/Customers/CustomerForm.js @@ -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(() => *, []); 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 ( - + + + + + } + inline={true} + intent={ + formik.errors.first_name && + formik.touched.first_name && + Intent.DANGER + } + helperText={ + + } + // className={'form-group--contact-name'} + > + + + + + + + } + // className={'form-group--contact-name'} + > + + + + + {/* Company Name */} + } + className={'form-group--company_name'} + labelInfo={requiredSpan} + intent={ + formik.errors.company_name && + formik.touched.company_name && + Intent.DANGER + } + inline={true} + helperText={ + + } + > + + + {/* 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({ > + + {/* Email */} + + } + intent={ + formik.errors.email && formik.touched.email && Intent.DANGER + } + helperText={ + + } + className={'form-group--email'} + inline={true} + > + + + + + {/* Active checkbox */} + + } + defaultChecked={formik.values.active} + {...formik.getFieldProps('active')} + /> + + + + } + intent={ + formik.errors.work_phone && + formik.touched.work_phone && + Intent.DANGER + } + helperText={ + + } + className={'form-group--phone-number'} + inline={true} + > + + + + diff --git a/client/src/containers/Customers/CustomerNotTabs.js b/client/src/containers/Customers/CustomerNotTabs.js new file mode 100644 index 000000000..554ef9c39 --- /dev/null +++ b/client/src/containers/Customers/CustomerNotTabs.js @@ -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 ( + + } + // className={'form-group--description'} + intent={errors.note && touched.note && Intent.DANGER} + helperText={} + inline={true} + > + + + + ); +}; + +export default CustomerBillingAddress; diff --git a/client/src/containers/Customers/CustomerTable.js b/client/src/containers/Customers/CustomerTable.js new file mode 100644 index 000000000..16a885895 --- /dev/null +++ b/client/src/containers/Customers/CustomerTable.js @@ -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) => ( + + } /> + + } + onClick={handleEditCustomer(customer)} + /> + } + onClick={handleDeleteCustomer(customer)} + /> + + )); + + 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 }) => ( + + } /> + + ), + 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 ( + + + + ); +}; + +export default compose( + withCustomers(({ customers, customersLoading }) => ({ + customers, + customersLoading, + })), +)(CustomerTable); diff --git a/client/src/containers/Customers/CustomersList.js b/client/src/containers/Customers/CustomersList.js new file mode 100644 index 000000000..7c2098c3c --- /dev/null +++ b/client/src/containers/Customers/CustomersList.js @@ -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 ( + + + + + + } + confirmButtonText={} + icon="trash" + intent={Intent.DANGER} + isOpen={deleteCustomer} + onCancel={handleCancelDeleteCustomer} + onConfirm={handleConfirmDeleteCustomer} + > + + + + + + } + confirmButtonText={`${formatMessage({ + id: 'delete', + })} (${selectedRowsCount})`} + icon="trash" + intent={Intent.DANGER} + isOpen={bulkDelete} + onCancel={handleCancelBulkDelete} + onConfirm={handleConfirmBulkDelete} + > + + + + + + + ); +} + +export default compose( + withResourceActions, + withDashboardActions, + withCustomersActions, +)(CustomersList); diff --git a/client/src/containers/Customers/CustomersTabs.js b/client/src/containers/Customers/CustomersTabs.js new file mode 100644 index 000000000..aea897e07 --- /dev/null +++ b/client/src/containers/Customers/CustomersTabs.js @@ -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 ( + + + + } + /> + } + /> + } + /> + + + ); +} + +export default CustomersTabs; diff --git a/client/src/containers/Customers/withCustomersActions.js b/client/src/containers/Customers/withCustomersActions.js index 8b88d885d..66900f4f8 100644 --- a/client/src/containers/Customers/withCustomersActions.js +++ b/client/src/containers/Customers/withCustomersActions.js @@ -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({ diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js index e9b9d7c65..b69012315 100644 --- a/client/src/lang/en/index.js +++ b/client/src/lang/en/index.js @@ -419,15 +419,43 @@ export default { organization_id: 'Orgnization ID', customers: 'Customers', - new_customers:'New Customers', + new_customers: 'New Customers', customer_type_: 'Customer type', display_name_: 'Display name', new_customer: 'New Customer', customer_type: 'Customer Type', business: 'business', individual: 'Individual', - display_name:'Display Name', + 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?", }; diff --git a/client/src/routes/dashboard.js b/client/src/routes/dashboard.js index 6da6e57f9..2015eb11a 100644 --- a/client/src/routes/dashboard.js +++ b/client/src/routes/dashboard.js @@ -145,10 +145,11 @@ export default [ { path: `/customers/:id/edit`, component: LazyLoader({ - // loader: () => import(), + loader: () => import('containers/Customers/Customer'), }), breadcrumb: 'Edit Customer', }, + { path: `/customers/new`, component: LazyLoader({ @@ -161,10 +162,8 @@ export default [ { path: `/customers`, component: LazyLoader({ - // loader: () => import(''), + loader: () => import('containers/Customers/CustomersList'), }), breadcrumb: 'Customers', }, - - ]; diff --git a/client/src/store/customers/customers.actions.js b/client/src/store/customers/customers.actions.js index e719ad2ae..ab705bc92 100644 --- a/client/src/store/customers/customers.actions.js +++ b/client/src/store/customers/customers.actions.js @@ -68,6 +68,7 @@ export const fetchCustomers = ({ query }) => { type: t.CUSTOMER_SET, customers: response.data.customers.results, }); + dispatch({ type: t.CUSTOMERS_PAGE_SET, 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 || []); + }); + }); +}; + diff --git a/client/src/store/customers/customers.reducer.js b/client/src/store/customers/customers.reducer.js index a660fe31d..86d198974 100644 --- a/client/src/store/customers/customers.reducer.js +++ b/client/src/store/customers/customers.reducer.js @@ -36,10 +36,14 @@ const customersReducer = createReducer(initialState, { delete state.items[action.id]; } }, + [t.CUSTOMERS_TABLE_LOADING]: (state, action) => { + const { loading } = action.payload; + state.loading = !!loading; + }, }); export default createTableQueryReducers('customers', customersReducer); export const getCustomerById = (state, id) => { - return state.customers[id]; + return state.customers.items[id]; }; diff --git a/client/src/store/customers/customers.selectors.js b/client/src/store/customers/customers.selectors.js index f337de723..94fe8ad42 100644 --- a/client/src/store/customers/customers.selectors.js +++ b/client/src/store/customers/customers.selectors.js @@ -1,6 +1,6 @@ import { pickItemsFromIds } from 'store/selectors'; - export const getCustomersItems = (state, viewId) => { + const customersView = state.customers.views[viewId || -1]; const customersItems = state.customers.items; diff --git a/client/src/style/pages/customer.scss b/client/src/style/pages/customer.scss index 378ab63cc..ef434fe92 100644 --- a/client/src/style/pages/customer.scss +++ b/client/src/style/pages/customer.scss @@ -1,24 +1,105 @@ .customer-form { - padding: 22px; + padding: 25px; + padding-bottom: 90px; width: 100%; - - // padding-bottom: 90px; + margin-bottom: 30px; .bp3-form-group { - // margin: 25px 20px 20px; - .bp3-label { - min-width: 100px; + width: 130px; } .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 { background-color: #fafafa; padding: 40px 22px 22px; margin: -22px -22px 22px; 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; + // } }
+ +