diff --git a/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactions.js b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactions.js
new file mode 100644
index 000000000..74ed07d60
--- /dev/null
+++ b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactions.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 VendorsTransactionsHeader from './VendorsTransactionsHeader';
+import VendorsTransactionsActionsBar from './VendorsTransactionsActionsBar';
+import VendorsTransactionsTable from './VendorsTransactionsTable';
+
+import withVendorsTransactionsActions from './withVendorsTransactionsActions';
+import withSettings from 'containers/Settings/withSettings';
+
+import { VendorsTransactionsProvider } from './VendorsTransactionsProvider';
+import { VendorsTransactionsLoadingBar } from './components';
+
+import { compose } from 'utils';
+
+/**
+ * Vendors transactions.
+ */
+function VendorsTransactions({
+ // #withPreferences
+ organizationName,
+
+ //#withVendorsTransactionsActions
+ toggleVendorsTransactionsFilterDrawer,
+}) {
+ // 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(
+ () => () => {
+ toggleVendorsTransactionsFilterDrawer(false);
+ },
+ [toggleVendorsTransactionsFilterDrawer],
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+export default compose(
+ withSettings(({ organizationSettings }) => ({
+ organizationName: organizationSettings.name,
+ })),
+ withVendorsTransactionsActions,
+)(VendorsTransactions);
diff --git a/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsActionsBar.js b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsActionsBar.js
new file mode 100644
index 000000000..92b4a2dfc
--- /dev/null
+++ b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsActionsBar.js
@@ -0,0 +1,134 @@
+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 { useVendorsTranscationsContext } from './VendorsTransactionsProvider';
+import withVendorsTransaction from './withVendorsTransaction';
+import withVendorsTransactionsActions from './withVendorsTransactionsActions';
+
+import { compose, saveInvoke } from 'utils';
+
+/**
+ * vendors transcations actions bar.
+ */
+function VendorsTransactionsActionsBar({
+ // #ownProps
+ numberFormat,
+ onNumberFormatSubmit,
+
+ //#withVendorsTransaction
+ isFilterDrawerOpen,
+
+ //#withVendorsTransactionsActions
+ toggleVendorsTransactionsFilterDrawer,
+}) {
+ const {
+ isVendorsTransactionsLoading,
+ refetch,
+ } = useVendorsTranscationsContext();
+
+ // Handle filter toggle click.
+ const handleFilterToggleClick = () => {
+ toggleVendorsTransactionsFilterDrawer();
+ };
+
+ // Handle recalculate the report button.
+ const handleRecalcReport = () => {
+ refetch();
+ };
+
+ // 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(
+ withVendorsTransaction(({ vendorsTransactionsDrawerFilter }) => ({
+ isFilterDrawerOpen: vendorsTransactionsDrawerFilter,
+ })),
+ withVendorsTransactionsActions,
+)(VendorsTransactionsActionsBar);
diff --git a/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsHeader.js b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsHeader.js
new file mode 100644
index 000000000..297f9487a
--- /dev/null
+++ b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsHeader.js
@@ -0,0 +1,100 @@
+import React from 'react';
+import * as Yup from 'yup';
+import moment from 'moment';
+import { Formik, Form } from 'formik';
+import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
+import { FormattedMessage as T, useIntl } from 'react-intl';
+
+import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
+import VendorsTransactionsHeaderGeneralPanel from './VendorsTransactionsHeaderGeneralPanel';
+
+import withVendorsTransaction from './withVendorsTransaction';
+import withVendorsTransactionsActions from './withVendorsTransactionsActions';
+
+import { compose } from 'utils';
+
+/**
+ * Vendors transactions header.
+ */
+
+function VendorsTransactionsHeader({
+ // #ownProps
+ onSubmitFilter,
+ pageFilter,
+
+ //#withVendorsTransaction
+ isFilterDrawerOpen,
+
+ //#withVendorsTransactionsActions
+ toggleVendorsTransactionsFilterDrawer: 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(
+ withVendorsTransactionsActions,
+ withVendorsTransaction(({ vendorsTransactionsDrawerFilter }) => ({
+ isFilterDrawerOpen: vendorsTransactionsDrawerFilter,
+ })),
+)(VendorsTransactionsHeader);
diff --git a/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsHeaderGeneralPanel.js b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsHeaderGeneralPanel.js
new file mode 100644
index 000000000..49c1beea2
--- /dev/null
+++ b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsHeaderGeneralPanel.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
+
+/**
+ * Vendors transactions header - General panel.
+ */
+export default function VendorsTransactionsHeaderGeneralPanel() {
+ return (
+
+
+
+ );
+}
diff --git a/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsProvider.js b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsProvider.js
new file mode 100644
index 000000000..c024e43a1
--- /dev/null
+++ b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsProvider.js
@@ -0,0 +1,37 @@
+import React, { createContext, useContext, useMemo } from 'react';
+import FinancialReportPage from '../FinancialReportPage';
+import { useVendorsTranscationsReport } from 'hooks/query';
+
+
+const VendorsTransactionsContext = createContext();
+
+/**
+ * Vendors transcations provider.
+ */
+function VendorsTransactionsProvider({ filter, ...props }) {
+ const {
+ data: vendorsTransactions,
+ isFetching: isVendorsTransactionFetching,
+ isLoading: isVendorsTransactionsLoading,
+ refetch,
+ } = useVendorsTranscationsReport();
+
+ const provider = {
+ vendorsTransactions,
+ isVendorsTransactionsLoading,
+ isVendorsTransactionFetching,
+ refetch,
+ filter,
+ };
+
+ return (
+
+
+
+ );
+}
+
+const useVendorsTranscationsContext = () =>
+ useContext(VendorsTransactionsContext);
+
+export { VendorsTransactionsProvider, useVendorsTranscationsContext };
diff --git a/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsTable.js b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsTable.js
new file mode 100644
index 000000000..b554f3aae
--- /dev/null
+++ b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsTable.js
@@ -0,0 +1,60 @@
+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 { useVendorsTransactionsColumns } from './components';
+import { useVendorsTranscationsContext } from './VendorsTransactionsProvider';
+
+import { defaultExpanderReducer, getColumnWidth } from 'utils';
+
+/**
+ * Vendors transcations table.
+ */
+
+export default function VendorsTransactionsTable({
+ // #ownProps
+ companyName,
+}) {
+ const { formatMessage } = useIntl();
+
+ const {
+ vendorsTransactions: { tableRows },
+ isVendorsTransactionsLoading,
+ filter,
+ } = useVendorsTranscationsContext();
+
+ const columns = useVendorsTransactionsColumns();
+
+ const expandedRows = useMemo(() => defaultExpanderReducer(tableRows, 5), [
+ tableRows,
+ ]);
+
+ const rowClassNames = (row) => {
+ return [`row-type--${row.original.rowTypes}`];
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/client/src/containers/FinancialStatements/VendorsTransactions/components.js b/client/src/containers/FinancialStatements/VendorsTransactions/components.js
new file mode 100644
index 000000000..5c97cf4cd
--- /dev/null
+++ b/client/src/containers/FinancialStatements/VendorsTransactions/components.js
@@ -0,0 +1,99 @@
+import React from 'react';
+import { formatMessage } from 'services/intl';
+import { If } from 'components';
+import { useVendorsTranscationsContext } from './VendorsTransactionsProvider';
+import FinancialLoadingBar from '../FinancialLoadingBar';
+import { defaultExpanderReducer, getColumnWidth, getForceWidth } from 'utils';
+import { CellTextSpan } from 'components/Datatable/Cells';
+
+/**
+ * Retrieve vendors transcations columns.
+ */
+export const useVendorsTransactionsColumns = () => {
+ const {
+ vendorsTransactions: { tableRows },
+ } = useVendorsTranscationsContext();
+
+ return React.useMemo(
+ () => [
+ {
+ Header: formatMessage({ id: 'vendor_name' }),
+ accessor: ({ cells }) => {
+ return (
+
+ {cells[0].value}
+
+ );
+ },
+ className: 'vendor_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',
+ textOverview: true,
+ width: 180,
+ },
+ {
+ Header: formatMessage({ id: 'transaction_type' }),
+ accessor: 'cells[3].value',
+ textOverview: true,
+ width: 180,
+ },
+ {
+ 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],
+ );
+};
+
+/**
+ * vendors transcations loading bar.
+ */
+export function VendorsTransactionsLoadingBar() {
+ const { isVendorsTransactionsLoading } = useVendorsTranscationsContext();
+ return (
+
+
+
+ );
+}
diff --git a/client/src/containers/FinancialStatements/VendorsTransactions/withVendorsTransaction.js b/client/src/containers/FinancialStatements/VendorsTransactions/withVendorsTransaction.js
new file mode 100644
index 000000000..1331f8842
--- /dev/null
+++ b/client/src/containers/FinancialStatements/VendorsTransactions/withVendorsTransaction.js
@@ -0,0 +1,15 @@
+import { connect } from 'react-redux';
+import { getVendorsTransactionsFilterDrawer } from 'store/financialStatement/financialStatements.selectors';
+
+export default (mapState) => {
+ const mapStateToProps = (state, props) => {
+ const mapped = {
+ vendorsTransactionsDrawerFilter: getVendorsTransactionsFilterDrawer(
+ state,
+ props,
+ ),
+ };
+ return mapState ? mapState(mapped, state, props) : mapped;
+ };
+ return connect(mapStateToProps);
+};
diff --git a/client/src/containers/FinancialStatements/VendorsTransactions/withVendorsTransactionsActions.js b/client/src/containers/FinancialStatements/VendorsTransactions/withVendorsTransactionsActions.js
new file mode 100644
index 000000000..7239c6f01
--- /dev/null
+++ b/client/src/containers/FinancialStatements/VendorsTransactions/withVendorsTransactionsActions.js
@@ -0,0 +1,9 @@
+import { connect } from 'react-redux';
+import { toggleVendorsTransactionsFilterDrawer } from 'store/financialStatement/financialStatements.actions';
+
+const mapActionsToProps = (dispatch) => ({
+ toggleVendorsTransactionsFilterDrawer: (toggle) =>
+ dispatch(toggleVendorsTransactionsFilterDrawer(toggle)),
+});
+
+export default connect(null, mapActionsToProps);
diff --git a/client/src/style/pages/FinancialStatements/ContactsTransactions.scss b/client/src/style/pages/FinancialStatements/ContactsTransactions.scss
new file mode 100644
index 000000000..aa8591eba
--- /dev/null
+++ b/client/src/style/pages/FinancialStatements/ContactsTransactions.scss
@@ -0,0 +1,75 @@
+.financial-sheet {
+ &--customer-transactions,
+ &--vendor-transactions {
+ .financial-sheet__table {
+ .tbody,
+ .thead {
+ .tr .td,
+ .tr .th {
+ &.credit,
+ &.debit,
+ &.running_balance {
+ text-align: right;
+ }
+ }
+ }
+ .tbody {
+ .tr .td {
+ padding-top: 0.2rem;
+ padding-bottom: 0.2rem;
+ border-top-color: transparent;
+ border-bottom-color: transparent;
+
+ &.customer_name,
+ &.vendor_name {
+ > div {
+ display: flex;
+ }
+ span.force-width {
+ position: relative;
+ }
+ }
+ }
+ .tr:not(.no-results) .td {
+ // border-left: 1px solid #ececec;
+ }
+
+ .tr.row-type {
+ &--CUSTOMER,
+ &--VENDOR {
+ .td {
+ &.customer_name,
+ &.vendor_name {
+ font-weight: 500;
+ }
+ &.name {
+ border-left-color: transparent;
+ }
+ }
+ &:not(:first-child).is-expanded .td {
+ border-top: 1px solid #ddd;
+ }
+ }
+ &--OPENING_BALANCE,
+ // &--TRANSACTION,
+ &--CLOSING_BALANCE {
+ font-weight: 500;
+ }
+ &--CUSTOMER:last-child {
+ .td {
+ border-bottom: 1px solid #ddd;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+.financial-statement--transactions {
+ .financial-header-drawer {
+ .bp3-drawer {
+ max-height: 350px;
+ }
+ }
+}