diff --git a/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactions.js b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactions.js
new file mode 100644
index 000000000..17a72ae3b
--- /dev/null
+++ b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactions.js
@@ -0,0 +1,88 @@
+import React, { useEffect, useState } from 'react';
+import moment from 'moment';
+import 'style/pages/FinancialStatements/ContactsTransactions.scss';
+
+import { FinancialStatement } from 'components';
+import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
+
+import CustomersTransactionsHeader from './CustomersTransactionsHeader';
+import CustomersTransactionsTable from './CustomersTransactionsTable';
+import CustomersTranscationsActionsBar from './CustomersTranscationsActionsBar';
+
+import withCustomersTransactionsActions from './withCustomersTransactionsActions';
+import withSettings from 'containers/Settings/withSettings';
+import { CustomersTranscationsLoadingBar } from './components';
+import { CustomersTranscationsProvider } from './CustomersTranscationsProvider';
+
+import { compose } from 'utils';
+
+/**
+ * Customers transactions.
+ */
+function CustomersTransactions({
+ // #withPreferences
+ organizationName,
+
+ //#withCustomersTransactionsActions
+ toggleCustomersTransactionsFilterDrawer,
+}) {
+ // filter
+ const [filter, setFilter] = useState({
+ fromDate: moment().startOf('year').format('YYYY-MM-DD'),
+ toDate: moment().endOf('year').format('YYYY-MM-DD'),
+ });
+
+ const handleFilterSubmit = (filter) => {
+ const _filter = {
+ ...filter,
+ fromDate: moment(filter.fromDate).format('YYYY-MM-DD'),
+ toDate: moment(filter.toDate).format('YYYY-MM-DD'),
+ };
+ setFilter({ ..._filter });
+ };
+
+ // Handle number format submit.
+ const handleNumberFormatSubmit = (values) => {
+ setFilter({
+ ...filter,
+ numberFormat: values,
+ });
+ };
+
+ useEffect(
+ () => () => {
+ toggleCustomersTransactionsFilterDrawer(false);
+ },
+ [toggleCustomersTransactionsFilterDrawer],
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+export default compose(
+ withSettings(({ organizationSettings }) => ({
+ organizationName: organizationSettings.name,
+ })),
+ withCustomersTransactionsActions,
+)(CustomersTransactions);
diff --git a/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsHeader.js b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsHeader.js
new file mode 100644
index 000000000..f766d9efe
--- /dev/null
+++ b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsHeader.js
@@ -0,0 +1,100 @@
+import React from 'react';
+import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
+import { FormattedMessage as T, useIntl } from 'react-intl';
+import moment from 'moment';
+import * as Yup from 'yup';
+import { Formik, Form } from 'formik';
+
+import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
+import CustomersTransactionsHeaderGeneralPanel from './CustomersTransactionsHeaderGeneralPanel';
+
+import withCustomersTransactions from './withCustomersTransactions';
+import withCustomersTransactionsActions from './withCustomersTransactionsActions';
+
+import { compose } from 'utils';
+
+/**
+ * Customers transactions header.
+ */
+function CustomersTransactionsHeader({
+ // #ownProps
+ onSubmitFilter,
+ pageFilter,
+
+ //#withCustomersTransactions
+ isFilterDrawerOpen,
+
+ //#withCustomersTransactionsActions
+ toggleCustomersTransactionsFilterDrawer: toggleFilterDrawer,
+}) {
+ const { formatMessage } = useIntl();
+
+ // Filter form initial values.
+ const initialValues = {
+ ...pageFilter,
+ fromDate: moment(pageFilter.fromDate).toDate(),
+ toDate: moment(pageFilter.toDate).toDate(),
+ };
+
+ // Validation schema.
+ const validationSchema = Yup.object().shape({
+ fromDate: Yup.date()
+ .required()
+ .label(formatMessage({ id: 'fromDate' })),
+ toDate: Yup.date()
+ .min(Yup.ref('fromDate'))
+ .required()
+ .label(formatMessage({ id: 'toDate' })),
+ });
+
+ // Handle form submit.
+ const handleSubmit = (values, { setSubmitting }) => {
+ onSubmitFilter(values);
+ toggleFilterDrawer(false);
+ setSubmitting(false);
+ };
+
+ // Handle drawer close action.
+ const handleDrawerClose = () => {
+ toggleFilterDrawer(false);
+ };
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default compose(
+ withCustomersTransactions(({ customersTransactionsDrawerFilter }) => ({
+ isFilterDrawerOpen: customersTransactionsDrawerFilter,
+ })),
+ withCustomersTransactionsActions,
+)(CustomersTransactionsHeader);
diff --git a/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsHeaderGeneralPanel.js b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsHeaderGeneralPanel.js
new file mode 100644
index 000000000..903a8b449
--- /dev/null
+++ b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsHeaderGeneralPanel.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
+
+/**
+ * Customers transactions header - General panel.
+ */
+export default function CustomersTransactionsHeaderGeneralPanel() {
+ return (
+
+
+
+ );
+}
diff --git a/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsTable.js b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsTable.js
new file mode 100644
index 000000000..972bde7d2
--- /dev/null
+++ b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsTable.js
@@ -0,0 +1,59 @@
+import React, { useMemo, useCallback } from 'react';
+import { useIntl } from 'react-intl';
+import classNames from 'classnames';
+
+import FinancialSheet from 'components/FinancialSheet';
+import DataTable from 'components/DataTable';
+import { useCustomersTranscationsColumns } from './components';
+import { useCustomersTranscationsContext } from './CustomersTranscationsProvider';
+
+import { defaultExpanderReducer, getColumnWidth } from 'utils';
+
+/**
+ * Customers transcations table.
+ */
+export default function CustomersTransactionsTable({
+ // #ownProps
+ companyName,
+}) {
+ const { formatMessage } = useIntl();
+
+ const {
+ customersTransactions: { tableRows },
+ isCustomersTransactionsLoading,
+ filter,
+ } = useCustomersTranscationsContext();
+
+ const columns = useCustomersTranscationsColumns();
+
+ const expandedRows = useMemo(() => defaultExpanderReducer(tableRows, 4), [
+ tableRows,
+ ]);
+
+ const rowClassNames = (row) => {
+ return [`row-type--${row.original.rowTypes}`];
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTranscationsActionsBar.js b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTranscationsActionsBar.js
new file mode 100644
index 000000000..53954b6c7
--- /dev/null
+++ b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTranscationsActionsBar.js
@@ -0,0 +1,135 @@
+import React from 'react';
+import {
+ NavbarGroup,
+ Button,
+ Classes,
+ NavbarDivider,
+ Popover,
+ PopoverInteractionKind,
+ Position,
+} from '@blueprintjs/core';
+import { FormattedMessage as T } from 'react-intl';
+import classNames from 'classnames';
+
+import Icon from 'components/Icon';
+import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
+import NumberFormatDropdown from 'components/NumberFormatDropdown';
+
+import { useCustomersTranscationsContext } from './CustomersTranscationsProvider';
+import withCustomersTransactions from './withCustomersTransactions';
+import withCustomersTransactionsActions from './withCustomersTransactionsActions';
+
+import { compose, saveInvoke } from 'utils';
+
+/**
+ * Customers transcations actions bar.
+ */
+function CustomersTranscationsActionsBar({
+ // #ownProps
+ numberFormat,
+ onNumberFormatSubmit,
+
+ //#withCustomersTransactions
+ isFilterDrawerOpen,
+
+ //#withCustomersTransactionsActions
+ toggleCustomersTransactionsFilterDrawer,
+}) {
+ const {
+ isCustomersTransactionsLoading,
+ CustomersTransactionsRefetch,
+ } = useCustomersTranscationsContext();
+
+ // Handle filter toggle click.
+ const handleFilterToggleClick = () => {
+ toggleCustomersTransactionsFilterDrawer();
+ };
+
+ // Handle recalculate the report button.
+ const handleRecalcReport = () => {
+ CustomersTransactionsRefetch();
+ };
+
+ // Handle number format form submit.
+ const handleNumberFormatSubmit = (values) => {
+ saveInvoke(onNumberFormatSubmit, values);
+ };
+
+ return (
+
+
+ }
+ onClick={handleRecalcReport}
+ icon={}
+ />
+
+ }
+ text={
+ isFilterDrawerOpen ? (
+
+ ) : (
+
+ )
+ }
+ onClick={handleFilterToggleClick}
+ active={isFilterDrawerOpen}
+ />
+
+
+ }
+ minimal={true}
+ interactionKind={PopoverInteractionKind.CLICK}
+ position={Position.BOTTOM_LEFT}
+ >
+ }
+ icon={}
+ />
+
+
+
+ }
+ icon={}
+ />
+
+
+
+
+ }
+ text={}
+ />
+ }
+ text={}
+ />
+
+
+ );
+}
+
+export default compose(
+ withCustomersTransactions(({ customersTransactionsDrawerFilter }) => ({
+ isFilterDrawerOpen: customersTransactionsDrawerFilter,
+ })),
+ withCustomersTransactionsActions,
+)(CustomersTranscationsActionsBar);
diff --git a/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTranscationsProvider.js b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTranscationsProvider.js
new file mode 100644
index 000000000..af6d3cdf1
--- /dev/null
+++ b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTranscationsProvider.js
@@ -0,0 +1,36 @@
+import React, { createContext, useContext, useMemo } from 'react';
+import FinancialReportPage from '../FinancialReportPage';
+import { useCustomersTranscationsReport } from 'hooks/query';
+
+const CustomersTranscationsContext = createContext();
+
+/**
+ * Customers transcations provider.
+ */
+function CustomersTranscationsProvider({ filter, ...props }) {
+ // fetches the customers transcations.
+ const {
+ data: customersTransactions,
+ isFetching: isCustomersTransactionsFetching,
+ isLoading: isCustomersTransactionsLoading,
+ refetch: CustomersTransactionsRefetch,
+ } = useCustomersTranscationsReport(filter, { keepPreviousData: true });
+
+ const provider = {
+ customersTransactions,
+ isCustomersTransactionsFetching,
+ isCustomersTransactionsLoading,
+ CustomersTransactionsRefetch,
+ filter,
+ };
+
+ return (
+
+
+
+ );
+}
+const useCustomersTranscationsContext = () =>
+ useContext(CustomersTranscationsContext);
+
+export { CustomersTranscationsProvider, useCustomersTranscationsContext };
diff --git a/client/src/containers/FinancialStatements/CustomersTransactions/components.js b/client/src/containers/FinancialStatements/CustomersTransactions/components.js
new file mode 100644
index 000000000..12f4c4b72
--- /dev/null
+++ b/client/src/containers/FinancialStatements/CustomersTransactions/components.js
@@ -0,0 +1,101 @@
+import React from 'react';
+import { formatMessage } from 'services/intl';
+import { If } from 'components';
+import { useCustomersTranscationsContext } from './CustomersTranscationsProvider';
+import FinancialLoadingBar from '../FinancialLoadingBar';
+import { getForceWidth, defaultExpanderReducer, getColumnWidth } from 'utils';
+import { CellTextSpan } from 'components/Datatable/Cells';
+
+/**
+ * Retrieve customers transcations columns.
+ */
+export const useCustomersTranscationsColumns = () => {
+ const {
+ customersTransactions: { tableRows },
+ isCustomersTransactionsLoading,
+ } = useCustomersTranscationsContext();
+
+ return React.useMemo(
+ () => [
+ {
+ Header: formatMessage({ id: 'customer_name' }),
+ accessor: ({ cells }) => {
+ return (
+
+ {cells[0].value}
+
+ );
+ },
+ className: 'customer_name',
+ textOverview: true,
+ // width: 240,
+ },
+ {
+ Header: formatMessage({ id: 'account_name' }),
+ accessor: 'cells[1].value',
+ className: 'name',
+ textOverview: true,
+ width: 180,
+ },
+ {
+ Header: formatMessage({ id: 'reference_type' }),
+ accessor: 'cells[2].value',
+ width: 180,
+ textOverview: true,
+ },
+ {
+ Header: formatMessage({ id: 'transaction_type' }),
+ accessor: 'cells[3].value',
+ width: 180,
+ textOverview: true,
+ },
+ {
+ Header: formatMessage({ id: 'credit' }),
+ accessor: 'cells[4].value',
+ className: 'credit',
+ textOverview: true,
+ width: getColumnWidth(tableRows, 'credit', {
+ minWidth: 140,
+ magicSpacing: 10,
+ }),
+ },
+ {
+ Header: formatMessage({ id: 'debit' }),
+ accessor: 'cells[5].value',
+ className: 'debit',
+ textOverview: true,
+ width: getColumnWidth(tableRows, 'debit', {
+ minWidth: 140,
+ magicSpacing: 10,
+ }),
+ },
+ {
+ Header: formatMessage({ id: 'running_balance' }),
+ accessor: 'cells[6].value',
+ className: 'running_balance',
+ textOverview: true,
+ width: getColumnWidth(tableRows, 'running_balance', {
+ minWidth: 140,
+ magicSpacing: 10,
+ }),
+ },
+ ],
+ [tableRows, formatMessage],
+ );
+};
+
+/**
+ * customers transcations loading bar.
+ */
+export function CustomersTranscationsLoadingBar() {
+ const { isCustomersTransactionsLoading } = useCustomersTranscationsContext();
+
+ return (
+
+
+
+ );
+}
diff --git a/client/src/containers/FinancialStatements/CustomersTransactions/withCustomersTransactions.js b/client/src/containers/FinancialStatements/CustomersTransactions/withCustomersTransactions.js
new file mode 100644
index 000000000..9fb9f0ecc
--- /dev/null
+++ b/client/src/containers/FinancialStatements/CustomersTransactions/withCustomersTransactions.js
@@ -0,0 +1,15 @@
+import { connect } from 'react-redux';
+import { getCustomersTransactionsFilterDrawer } from 'store/financialStatement/financialStatements.selectors';
+
+export default (mapState) => {
+ const mapStateToProps = (state, props) => {
+ const mapped = {
+ customersTransactionsDrawerFilter: getCustomersTransactionsFilterDrawer(
+ state,
+ props,
+ ),
+ };
+ return mapState ? mapState(mapped, state, props) : mapped;
+ };
+ return connect(mapStateToProps);
+};
diff --git a/client/src/containers/FinancialStatements/CustomersTransactions/withCustomersTransactionsActions.js b/client/src/containers/FinancialStatements/CustomersTransactions/withCustomersTransactionsActions.js
new file mode 100644
index 000000000..ac3ce137f
--- /dev/null
+++ b/client/src/containers/FinancialStatements/CustomersTransactions/withCustomersTransactionsActions.js
@@ -0,0 +1,10 @@
+import { connect } from 'react-redux';
+import { toggleCustomersTransactionsFilterDrawer } from 'store/financialStatement/financialStatements.actions';
+
+
+const mapActionsToProps = (dispatch) => ({
+ toggleCustomersTransactionsFilterDrawer: (toggle) =>
+ dispatch(toggleCustomersTransactionsFilterDrawer(toggle)),
+});
+
+export default connect(null, mapActionsToProps);