From 77d826e6d468fc9e606d2aa34214b48781227a20 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Sat, 29 Jan 2022 20:46:41 +0200 Subject: [PATCH] feat(BalanceSheet|ProfitLoss): comparions feature. --- src/components/Datatable/TableHeader.js | 2 +- src/components/FinancialSheet.js | 2 - .../BalanceSheet/BalanceSheet.js | 1 - .../BalanceSheet/BalanceSheetTable.js | 97 +++-- .../BalanceSheet/components.js | 12 +- .../FinancialStatements/BalanceSheet/utils.js | 324 +++++++++++++--- .../ProfitLossSheet/ProfitLossSheet.js | 6 +- .../ProfitLossSheetHeaderComparisonPanel.js | 18 +- .../ProfitLossSheet/ProfitLossSheetTable.js | 121 +++--- .../ProfitLossSheet/hooks.js | 14 + .../ProfitLossSheet/utils.js | 365 ++++++++++++++++++ src/hooks/query/financialReports.js | 22 +- src/style/App.scss | 3 + src/style/components/DataTable/DataTable.scss | 6 +- .../FinancialStatements/BalanceSheet.scss | 47 --- .../FinancialStatements/FinancialSheet.scss | 26 -- src/utils/index.js | 12 + 17 files changed, 795 insertions(+), 283 deletions(-) create mode 100644 src/containers/FinancialStatements/ProfitLossSheet/hooks.js create mode 100644 src/containers/FinancialStatements/ProfitLossSheet/utils.js delete mode 100644 src/style/pages/FinancialStatements/BalanceSheet.scss delete mode 100644 src/style/pages/FinancialStatements/FinancialSheet.scss diff --git a/src/components/Datatable/TableHeader.js b/src/components/Datatable/TableHeader.js index b1916bb49..de8405ff7 100644 --- a/src/components/Datatable/TableHeader.js +++ b/src/components/Datatable/TableHeader.js @@ -15,7 +15,7 @@ function TableHeaderCell({ column, index }) {
diff --git a/src/components/FinancialSheet.js b/src/components/FinancialSheet.js index 49d10231d..389272c4c 100644 --- a/src/components/FinancialSheet.js +++ b/src/components/FinancialSheet.js @@ -4,8 +4,6 @@ import classnames from 'classnames'; import { FormattedMessage as T } from 'components'; import intl from 'react-intl-universal'; -import 'style/pages/FinancialStatements/FinancialSheet.scss'; - import { If, LoadingIndicator, MODIFIER } from 'components'; export default function FinancialSheet({ diff --git a/src/containers/FinancialStatements/BalanceSheet/BalanceSheet.js b/src/containers/FinancialStatements/BalanceSheet/BalanceSheet.js index 861a10cb7..84e91a083 100644 --- a/src/containers/FinancialStatements/BalanceSheet/BalanceSheet.js +++ b/src/containers/FinancialStatements/BalanceSheet/BalanceSheet.js @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react'; import moment from 'moment'; -import 'style/pages/FinancialStatements/BalanceSheet.scss'; import { BalanceSheetAlerts, BalanceSheetLoadingBar } from './components'; import { FinancialStatement } from 'components'; diff --git a/src/containers/FinancialStatements/BalanceSheet/BalanceSheetTable.js b/src/containers/FinancialStatements/BalanceSheet/BalanceSheetTable.js index 69cc36e45..242b29d8d 100644 --- a/src/containers/FinancialStatements/BalanceSheet/BalanceSheetTable.js +++ b/src/containers/FinancialStatements/BalanceSheet/BalanceSheetTable.js @@ -1,14 +1,13 @@ -import React, { useMemo, useCallback } from 'react'; +import React from 'react'; +import styled from 'styled-components'; import intl from 'react-intl-universal'; -import classNames from 'classnames'; import FinancialSheet from 'components/FinancialSheet'; import DataTable from 'components/DataTable'; -import { CellTextSpan } from 'components/Datatable/Cells'; import { useBalanceSheetContext } from './BalanceSheetProvider'; -import { defaultExpanderReducer, getColumnWidth } from 'utils'; - +import { defaultExpanderReducer, tableRowTypesToClassnames } from 'utils'; +import { TableStyle } from 'common'; import { useBalanceSheetColumns } from './components'; /** @@ -20,54 +19,17 @@ export default function BalanceSheetTable({ }) { // Balance sheet context. const { - balanceSheet: { tableRows, columns, query }, - isLoading, + balanceSheet: { table, query }, } = useBalanceSheetContext(); + // Retrieve the database columns. const tableColumns = useBalanceSheetColumns(); - // const tableColumns = useMemo( - // () => [ - // { - // Header: intl.get('account_name'), - // accessor: (row) => (row.code ? `${row.name} - ${row.code}` : row.name), - // className: 'account_name', - // textOverview: true, - // width: 240, - // }, - // ...(query.display_columns_type === 'total' - // ? [ - // { - // Header: intl.get('total'), - // accessor: 'total.formatted_amount', - // Cell: CellTextSpan, - // className: 'total', - // width: 140, - // }, - // ] - // : []), - // ...(query.display_columns_type === 'date_periods' - // ? columns.map((column, index) => ({ - // id: `date_period_${index}`, - // Header: column, - // Cell: CellTextSpan, - // accessor: `total_periods[${index}].formatted_amount`, - // className: classNames('total-period', `total-periods-${index}`), - // width: getColumnWidth( - // tableRows, - // `total_periods.${index}.formatted_amount`, - // { minWidth: 100 }, - // ), - // })) - // : []), - // ], - // [query, columns, tableRows], - // ); - - // Calculates the default expanded rows of balance sheet table. - // const expandedRows = useMemo(() => { - // return defaultExpanderReducer(tableRows, 4); - // }, [tableRows]); + // Retrieve default expanded rows of balance sheet. + const expandedRows = React.useMemo( + () => defaultExpanderReducer(table?.rows || [], 3), + [table], + ); return ( - ); } + +const BalanceSheetDataTable = styled(DataTable)` + .table { + .tbody .tr { + .td { + border-bottom: 0; + padding-top: 0.36rem; + padding-bottom: 0.36rem; + } + &.is-expanded { + .td:not(.name) .cell-inner { + opacity: 0; + } + } + &.row_type--TOTAL { + .td { + font-weight: 500; + border-top: 1px solid #bbb; + } + } + + &:last-of-type .td{ + border-bottom: 1px solid #bbb; + } + } + } +`; diff --git a/src/containers/FinancialStatements/BalanceSheet/components.js b/src/containers/FinancialStatements/BalanceSheet/components.js index fcd22362c..a5110d4f6 100644 --- a/src/containers/FinancialStatements/BalanceSheet/components.js +++ b/src/containers/FinancialStatements/BalanceSheet/components.js @@ -18,15 +18,14 @@ export function BalanceSheetAlerts() { refetchBalanceSheet(); }; // Can't display any error if the report is loading. - if (isLoading) { - return null; - } + if (isLoading) return null; return (
{' '} + @@ -52,12 +51,13 @@ export function BalanceSheetLoadingBar() { * Retrieve balance sheet columns. */ export const useBalanceSheetColumns = () => { + // Balance sheet context. const { - balanceSheet: { columns, tableRows }, + balanceSheet: { table }, } = useBalanceSheetContext(); return React.useMemo( - () => dynamicColumns(columns, tableRows), - [columns, tableRows], + () => dynamicColumns(table?.columns || [], table?.rows || []), + [table], ); }; diff --git a/src/containers/FinancialStatements/BalanceSheet/utils.js b/src/containers/FinancialStatements/BalanceSheet/utils.js index 1a4367589..d22ee2aec 100644 --- a/src/containers/FinancialStatements/BalanceSheet/utils.js +++ b/src/containers/FinancialStatements/BalanceSheet/utils.js @@ -1,10 +1,19 @@ import * as Yup from 'yup'; import * as R from 'ramda'; +import { isEmpty } from 'lodash'; import intl from 'react-intl-universal'; import moment from 'moment'; import { CellTextSpan } from 'components/Datatable/Cells'; import { getColumnWidth } from 'utils'; +const getTableCellValueAccessor = (index) => `cells[${index}].value`; + +const Align = { Left: 'left', Right: 'right', Center: 'center' }; + +/** + * + * @returns + */ export const getBalanceSheetHeaderDefaultValues = () => { return { basic: 'cash', @@ -15,6 +24,10 @@ export const getBalanceSheetHeaderDefaultValues = () => { }; }; +/** + * + * @returns + */ export const getBalanceSheetHeaderValidationSchema = () => Yup.object().shape({ dateRange: Yup.string().optional(), @@ -30,80 +43,303 @@ export const getBalanceSheetHeaderValidationSchema = () => /** * Account name column mapper. */ -const accountNameMapper = (data, index, column) => { +const accountNameMapper = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + return { - id: column.key, key: column.key, Header: column.label, - accessor: `cells[${index}].value`, + accessor, className: column.key, textOverview: true, - Cell: CellTextSpan, - width: getColumnWidth(data, `cells[${index}].value`, { - magicSpacing: 10, - minWidth: 240, - }), + width: getColumnWidth(data, accessor, { magicSpacing: 10, minWidth: 240 }), + }; +}); + +/** + * Assoc columns to total column. + */ +const assocColumnsToTotalColumn = R.curry((data, column, columnAccessor) => { + const columns = totalColumnsComposer(data, column); + + return R.assoc('columns', columns, columnAccessor); +}); + +/** + * Detarmines whether the given column has children columns. + * @returns {boolean} + */ +const isColumnHasColumns = (column) => !isEmpty(column.children); + +/** + * + * @param {*} data + * @param {*} column + * @returns + */ +const dateRangeSoloColumnAttrs = (data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + accessor, + width: getColumnWidth(data, accessor, { magicSpacing: 10, minWidth: 100 }), }; }; /** * Date range columns mapper. */ -const dateRangeMapper = (data, index, column) => ({ - id: column.key, - Header: column.label, - key: column.key, - accessor: `cells[${index}].value`, - width: getColumnWidth(data, `cells.${index}.value`, { - magicSpacing: 10, - minWidth: 100, - }), - className: `date-period ${column.key}`, - disableSortBy: true, - textOverview: true, +const dateRangeMapper = R.curry((data, column) => { + const isDateColumnHasColumns = isColumnHasColumns(column); + + const columnAccessor = { + Header: column.label, + key: column.key, + disableSortBy: true, + textOverview: true, + align: Align.Center, + }; + return R.compose( + R.when( + R.always(isDateColumnHasColumns), + assocColumnsToTotalColumn(data, column), + ), + R.when( + R.always(!isDateColumnHasColumns), + R.mergeLeft(dateRangeSoloColumnAttrs(data, column)), + ), + )(columnAccessor); }); /** * Total column mapper. */ -const totalMapper = (data, index, column) => { - return { - id: column.key, +const totalMapper = R.curry((data, column) => { + const hasChildren = !isEmpty(column.children); + const accessor = getTableCellValueAccessor(column.cell_index); + + const columnAccessor = { key: column.key, Header: column.label, - accessor: `cells[${index}].value`, - className: 'total', + accessor, textOverview: true, Cell: CellTextSpan, - width: getColumnWidth(data, `cells[${index}].value`, { - magicSpacing: 10, - minWidth: 200, - }), + width: getColumnWidth(data, accessor, { magicSpacing: 10, minWidth: 200 }), disableSortBy: true, + align: hasChildren ? 'center' : 'right', }; -}; + return R.compose( + R.when(R.always(hasChildren), assocColumnsToTotalColumn(data, column)), + )(columnAccessor); +}); + +/** + * `Percentage of column` column accessor. + */ +const percentageOfColumnAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getColumnWidth(data, accessor, { magicSpacing: 10, minWidth: 100 }), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * `Percentage of row` column accessor. + */ +const percentageOfRowAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getColumnWidth(data, accessor, { magicSpacing: 10, minWidth: 100 }), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * Previous year column accessor. + */ +const previousYearAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getColumnWidth(data, accessor, { magicSpacing: 10, minWidth: 100 }), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * Pervious year change column accessor. + */ +const previousYearChangeAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getColumnWidth(data, accessor, { magicSpacing: 10, minWidth: 100 }), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * Previous year percentage column accessor. + */ +const previousYearPercentageAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getColumnWidth(data, accessor, { magicSpacing: 10, minWidth: 100 }), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * Previous period column accessor. + */ +const previousPeriodAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getColumnWidth(data, accessor, { magicSpacing: 10, minWidth: 100 }), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * Previous period change column accessor. + */ +const previousPeriodChangeAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getColumnWidth(data, accessor, { magicSpacing: 10, minWidth: 100 }), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * Previous period percentage column accessor. + */ +const previousPeriodPercentageAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getColumnWidth(data, accessor, { magicSpacing: 10, minWidth: 100 }), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * + * @param {*} column + * @param {*} index + * @returns + */ +const totalColumnsMapper = R.curry((data, column) => { + return R.compose( + R.when(R.pathEq(['key'], 'total'), totalMapper(data)), + // Percetage of column/row. + R.when( + R.pathEq(['key'], 'percentage_of_column'), + percentageOfColumnAccessor(data), + ), + R.when( + R.pathEq(['key'], 'percentage_of_row'), + percentageOfRowAccessor(data), + ), + // Previous year. + R.when(R.pathEq(['key'], 'previous_year'), previousYearAccessor(data)), + R.when( + R.pathEq(['key'], 'previous_year_change'), + previousYearChangeAccessor(data), + ), + R.when( + R.pathEq(['key'], 'previous_year_percentage'), + previousYearPercentageAccessor(data), + ), + // Pervious period. + R.when(R.pathEq(['key'], 'previous_period'), previousPeriodAccessor(data)), + R.when( + R.pathEq(['key'], 'previous_period_change'), + previousPeriodChangeAccessor(data), + ), + R.when( + R.pathEq(['key'], 'previous_period_percentage'), + previousPeriodPercentageAccessor(data), + ), + )(column); +}); + +/** + * Total sub-columns composer. + */ +const totalColumnsComposer = R.curry((data, column) => { + return R.map(totalColumnsMapper(data), column.children); +}); /** * Detarmines the given string starts with `date-range` string. */ const isMatchesDateRange = (r) => R.match(/^date-range/g, r).length > 0; +/** + * Dynamic column mapper. + */ +const dynamicColumnMapper = R.curry((data, column) => { + const indexTotalMapper = totalMapper(data); + const indexAccountNameMapper = accountNameMapper(data); + const indexDatePeriodMapper = dateRangeMapper(data); + + return R.compose( + R.when(R.pathSatisfies(isMatchesDateRange, ['key']), indexDatePeriodMapper), + R.when(R.pathEq(['key'], 'name'), indexAccountNameMapper), + R.when(R.pathEq(['key'], 'total'), indexTotalMapper), + )(column); +}); + /** * Cash flow dynamic columns. */ export const dynamicColumns = (columns, data) => { - const mapper = (column, index) => { - return R.compose( - R.when( - R.pathSatisfies(isMatchesDateRange, ['key']), - R.curry(dateRangeMapper)(data, index), - ), - R.when( - R.pathEq(['key'], 'name'), - R.curry(accountNameMapper)(data, index), - ), - R.when(R.pathEq(['key'], 'total'), R.curry(totalMapper)(data, index)), - )(column); - }; - return columns.map(mapper); + return R.map(dynamicColumnMapper(data), columns); }; diff --git a/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheet.js b/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheet.js index c7c264b43..67244362f 100644 --- a/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheet.js +++ b/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheet.js @@ -51,7 +51,6 @@ function ProfitLossSheet({ numberFormat, }); }; - // Hide the filter drawer once the page unmount. React.useEffect( () => () => { @@ -66,8 +65,8 @@ function ProfitLossSheet({ numberFormat={filter.numberFormat} onNumberFormatSubmit={handleNumberFormatSubmit} /> - - + {/* */} + {/* */}
@@ -75,7 +74,6 @@ function ProfitLossSheet({ pageFilter={filter} onSubmitFilter={handleSubmitFilter} /> -
diff --git a/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetHeaderComparisonPanel.js b/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetHeaderComparisonPanel.js index 98a2f63a1..b911cccc0 100644 --- a/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetHeaderComparisonPanel.js +++ b/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetHeaderComparisonPanel.js @@ -28,7 +28,7 @@ export default function ProfitLossSheetHeaderComparisonPanel() { - + {({ field }) => ( }> - + {({ field }) => ( }> {/**----------- Previous Period (PP) -----------*/} - + {({ field }) => ( }> - + {({ field }) => ( }> - + {({ field }) => ( }> {/**----------- % of Column -----------*/} - + {({ field }) => ( }> {/**----------- % of Row -----------*/} - + {({ field }) => ( }> {/**----------- % of Expense -----------*/} - + {({ field }) => ( }> {/**----------- % of Income -----------*/} - + {({ field }) => ( }> [ - { - Header: intl.get('account'), - accessor: (row) => (row.code ? `${row.name} - ${row.code}` : row.name), - className: 'name', - textOverview: true, - width: 240, - }, - ...(query.display_columns_type === 'total' - ? [ - { - Header: intl.get('total'), - Cell: CellTextSpan, - accessor: 'total.formatted_amount', - className: 'total', - width: 140, - }, - ] - : []), - ...(query.display_columns_type === 'date_periods' - ? columns.map((column, index) => ({ - id: `date_period_${index}`, - Header: column, - Cell: CellTextSpan, - accessor: `total_periods[${index}].formatted_amount`, - width: getColumnWidth( - tableRows, - `total_periods.${index}.formatted_amount`, - { minWidth: 100 }, - ), - className: 'total-period', - })) - : []), - ], - [ - query.display_columns_type, - tableRows, - columns, - ], - ); + // Retrieves the profit/loss table columns. + const tableColumns = useProfitLossSheetColumns(); // Retrieve default expanded rows of balance sheet. - const expandedRows = useMemo( - () => defaultExpanderReducer(tableRows, 3), - [tableRows], + const expandedRows = React.useMemo( + () => defaultExpanderReducer(table?.rows || [], 3), + [table], ); - // Retrieve conditional datatable row classnames. - const rowClassNames = useCallback((row) => { - const { original } = row; - const rowTypes = Array.isArray(original.rowTypes) ? original.rowTypes : []; - - return { - ...rowTypes.reduce((acc, rowType) => { - acc[`row_type--${rowType}`] = rowType; - return acc; - }, {}), - }; - }, []); - return ( } - fromDate={query.from_date} - toDate={query.to_date} + // fromDate={query.from_date} + // toDate={query.to_date} name="profit-loss-sheet" loading={isLoading} - basis={query.basis} + // basis={query.basis} > - ); } + +const ProfitLossDataTable = styled(DataTable)` + .table { + .tbody .tr { + .td { + border-bottom: 0; + padding-top: 0.36rem; + padding-bottom: 0.36rem; + } + &.is-expanded { + .td:not(.name) .cell-inner { + opacity: 0; + } + } + &.row_type--TOTAL { + .td { + font-weight: 500; + border-top: 1px solid #bbb; + } + } + &:last-of-type .td{ + border-bottom: 1px solid #bbb; + } + } + } +`; diff --git a/src/containers/FinancialStatements/ProfitLossSheet/hooks.js b/src/containers/FinancialStatements/ProfitLossSheet/hooks.js new file mode 100644 index 000000000..6f89ce860 --- /dev/null +++ b/src/containers/FinancialStatements/ProfitLossSheet/hooks.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { dynamicColumns } from './utils'; +import { useProfitLossSheetContext } from './ProfitLossProvider'; + +export const useProfitLossSheetColumns = () => { + const { + profitLossSheet: { table }, + } = useProfitLossSheetContext(); + + return React.useMemo( + () => dynamicColumns(table.columns || [], table.rows || []), + [table], + ); +}; diff --git a/src/containers/FinancialStatements/ProfitLossSheet/utils.js b/src/containers/FinancialStatements/ProfitLossSheet/utils.js new file mode 100644 index 000000000..b0d42cc5d --- /dev/null +++ b/src/containers/FinancialStatements/ProfitLossSheet/utils.js @@ -0,0 +1,365 @@ +import * as R from 'ramda'; +import { isEmpty } from 'lodash'; +import { CellTextSpan } from 'components/Datatable/Cells'; +import { getColumnWidth } from 'utils'; + +const Align = { Left: 'left', Right: 'right', Center: 'center' }; +const getTableCellValueAccessor = (index) => `cells[${index}].value`; + +const getReportColWidth = (data, accessor) => { + return getColumnWidth(data, accessor, { magicSpacing: 10, minWidth: 100 }); +}; + +const isNodeHasChildren = (node) => !isEmpty(node.children); + +/** + * `Percentage of income` column accessor. + */ +const percentageOfIncomeAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getReportColWidth(data, accessor), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * `Percentage of expense` column accessor. + */ +const percentageOfExpenseAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getReportColWidth(data, accessor), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * `Percentage of column` column accessor. + */ +const percentageOfColumnAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getReportColWidth(data, accessor), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * `Percentage of row` column accessor. + */ +const percentageOfRowAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getReportColWidth(data, accessor), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * Previous year column accessor. + */ +const previousYearAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getReportColWidth(data, accessor), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * Pervious year change column accessor. + */ +const previousYearChangeAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getReportColWidth(data, accessor), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * Previous year percentage column accessor. + */ +const previousYearPercentageAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getReportColWidth(data, accessor), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * Previous period column accessor. + */ +const previousPeriodAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getReportColWidth(data, accessor), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * Previous period change column accessor. + */ +const previousPeriodChangeAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getReportColWidth(data, accessor), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * Previous period percentage column accessor. + */ +const previousPeriodPercentageAccessor = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + Header: column.label, + key: column.key, + accessor, + width: getReportColWidth(data, accessor), + align: Align.Right, + disableSortBy: true, + textOverview: true, + }; +}); + +/** + * + * @param {*} column + * @param {*} index + * @returns + */ +const totalColumnsMapper = R.curry((data, column) => { + return R.compose( + R.when(R.pathEq(['key'], 'total'), totalColumn(data)), + // Percetage of column/row. + R.when( + R.pathEq(['key'], 'percentage_column'), + percentageOfColumnAccessor(data), + ), + R.when( + R.pathEq(['key'], 'percentage_row'), + percentageOfRowAccessor(data), + ), + R.when( + R.pathEq(['key'], 'percentage_income'), + percentageOfIncomeAccessor(data), + ), + R.when( + R.pathEq(['key'], 'percentage_expenses'), + percentageOfExpenseAccessor(data), + ), + // Previous year. + R.when(R.pathEq(['key'], 'previous_year'), previousYearAccessor(data)), + R.when( + R.pathEq(['key'], 'previous_year_change'), + previousYearChangeAccessor(data), + ), + R.when( + R.pathEq(['key'], 'previous_year_percentage'), + previousYearPercentageAccessor(data), + ), + // Pervious period. + R.when(R.pathEq(['key'], 'previous_period'), previousPeriodAccessor(data)), + R.when( + R.pathEq(['key'], 'previous_period_change'), + previousPeriodChangeAccessor(data), + ), + R.when( + R.pathEq(['key'], 'previous_period_percentage'), + previousPeriodPercentageAccessor(data), + ), + )(column); +}); + +/** + * Total sub-columns composer. + */ +const totalColumnsComposer = R.curry((data, column) => { + return R.map(totalColumnsMapper(data), column.children); +}); + +/** + * Assoc columns to total column. + */ +const assocColumnsToTotalColumn = R.curry((data, column, columnAccessor) => { + const columns = totalColumnsComposer(data, column); + + return R.assoc('columns', columns, columnAccessor); +}); + +/** + * + */ +const totalColumn = R.curry((data, column) => { + const hasChildren = isNodeHasChildren(column); + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + key: column.key, + Header: column.label, + accessor, + textOverview: true, + Cell: CellTextSpan, + width: getColumnWidth(data, accessor, { magicSpacing: 10, minWidth: 200 }), + disableSortBy: true, + align: hasChildren ? 'center' : 'right', + }; +}); + +/** + * + */ +const totalColumnCompose = R.curry((data, column) => { + const hasChildren = isNodeHasChildren(column); + + return R.compose( + R.when(R.always(hasChildren), assocColumnsToTotalColumn(data, column)), + totalColumn(data), + )(column); +}); + +/** + * Account name column mapper. + */ +const accountNameColumn = R.curry((data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + key: column.key, + Header: column.label, + accessor, + className: column.key, + textOverview: true, + width: getReportColWidth(data, accessor), + }; +}); + + +/** + * + * @param {*} data + * @param {*} column + * @returns + */ + const dateRangeSoloColumnAttrs = (data, column) => { + const accessor = getTableCellValueAccessor(column.cell_index); + + return { + accessor, + width: getReportColWidth(data, accessor), + }; +}; + +const dateRangeColumn = R.curry((data, column) => { + const isDateColumnHasColumns = isNodeHasChildren(column); + + const columnAccessor = { + Header: column.label, + key: column.key, + disableSortBy: true, + textOverview: true, + align: Align.Center, + }; + return R.compose( + R.when( + R.always(isDateColumnHasColumns), + assocColumnsToTotalColumn(data, column), + ), + R.when( + R.always(!isDateColumnHasColumns), + R.mergeLeft(dateRangeSoloColumnAttrs(data, column)), + ), + )(columnAccessor); +}) + +/** + * Detarmines the given string starts with `date-range` string. + */ + const isMatchesDateRange = (r) => R.match(/^date-range/g, r).length > 0; + +/** + * + * @param {} data + * @param {} column + */ +const dynamicColumnMapper = R.curry((data, column) => { + const indexTotalColumn = totalColumnCompose(data); + const indexAccountNameColumn = accountNameColumn(data); + const indexDatePeriodMapper = dateRangeColumn(data); + + return R.compose( + R.when(R.pathSatisfies(isMatchesDateRange, ['key']), indexDatePeriodMapper), + R.when(R.pathEq(['key'], 'name'), indexAccountNameColumn), + R.when(R.pathEq(['key'], 'total'), indexTotalColumn), + )(column); +}); + +/** + * + * @param {*} columns + * @param {*} data + * @returns + */ +export const dynamicColumns = (columns, data) => { + return R.map(dynamicColumnMapper(data), columns); +}; diff --git a/src/hooks/query/financialReports.js b/src/hooks/query/financialReports.js index 12ac238bd..dce611916 100644 --- a/src/hooks/query/financialReports.js +++ b/src/hooks/query/financialReports.js @@ -29,16 +29,10 @@ export function useBalanceSheet(query, props) { }, { select: (res) => ({ - columns: res.data.table.columns, - tableRows: res.data.table.rows, - // query: res.data.query, - // meta: res.data.meta, + table: res.data.table }), defaultData: { - tableRows: [], - columns: [], - // query: {}, - // meta: {}, + table: {}, }, ...props, }, @@ -68,6 +62,7 @@ export function useTrialBalanceSheet(query, props) { method: 'get', url: '/financial_statements/trial_balance_sheet', params: query, + }, { select: (res) => ({ @@ -94,17 +89,16 @@ export function useProfitLossSheet(query, props) { method: 'get', url: '/financial_statements/profit_loss_sheet', params: query, + headers: { + Accept: 'application/json+table', + }, }, { select: (res) => ({ - tableRows: profitLossSheetReducer(res.data.data), - ...res.data, + table: res.data.table }), defaultData: { - data: {}, - tableRows: [], - columns: [], - query: {}, + table: {}, }, ...props, }, diff --git a/src/style/App.scss b/src/style/App.scss index 55b604c00..f4a0e9186 100644 --- a/src/style/App.scss +++ b/src/style/App.scss @@ -285,6 +285,9 @@ html[lang^="ar"] { .align-right { text-align: right; } +.align-center{ + text-align: center; +} .font-bold { font-weight: 600; diff --git a/src/style/components/DataTable/DataTable.scss b/src/style/components/DataTable/DataTable.scss index 1258e6160..a2c396075 100644 --- a/src/style/components/DataTable/DataTable.scss +++ b/src/style/components/DataTable/DataTable.scss @@ -363,11 +363,15 @@ .table-constrant, .table--constrant { .table { + .thead{ + .tr:first-of-type{ + border-top: 1px solid #000000; + } + } .thead .th { background: transparent; color: #222222; border-bottom: 1px solid #000000; - border-top: 1px solid #000000; padding: 0.5rem; } diff --git a/src/style/pages/FinancialStatements/BalanceSheet.scss b/src/style/pages/FinancialStatements/BalanceSheet.scss deleted file mode 100644 index eea6edd57..000000000 --- a/src/style/pages/FinancialStatements/BalanceSheet.scss +++ /dev/null @@ -1,47 +0,0 @@ - -.financial-sheet{ - - &--balance-sheet{ - .financial-sheet__table{ - - .thead, - .tbody{ - .tr .td.name ~ .td, - .tr .th.name ~ .th{ - text-align: right; - } - } - .tbody{ - .tr .td{ - border-bottom: 0; - padding-top: 0.4rem; - padding-bottom: 0.4rem; - } - .tr.row_type--total-row .td{ - border-top: 1px solid #BBB; - } - .tr.row_type--total-row.row_type--assets .td, - .tr.row_type--total-row.row_type--liabilities_equity .td{ - border-bottom: 3px double #333; - } - .tr.row_type--total-row{ - .total.td, - .account_name.td, - .total-period.td{ - font-weight: 600; - color: #333; - } - } - - .tr.is-expanded{ - .td.total, - .td.total-period{ - .cell-text{ - display: none; - } - } - } - } - } - } -} \ No newline at end of file diff --git a/src/style/pages/FinancialStatements/FinancialSheet.scss b/src/style/pages/FinancialStatements/FinancialSheet.scss deleted file mode 100644 index 5c37bfdd3..000000000 --- a/src/style/pages/FinancialStatements/FinancialSheet.scss +++ /dev/null @@ -1,26 +0,0 @@ - -.bigcapital-datatable{ - - &--financial-report{ - .table { - .tbody{ - - .tr.no-results { - .td{ - border-bottom: 1px solid #DDD; - } - } - } - .thead{ - .tr .th{ - background-color: #fff; - border-top: 1px solid #666; - border-bottom: 1px solid #666; - - padding: 8px 0.4rem; - color: #222; - } - } - } - } -} diff --git a/src/utils/index.js b/src/utils/index.js index f91986577..945a41c09 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -900,3 +900,15 @@ export function ignoreEventFromSelectors(event, selectors) { .map((selector) => event.target.closest(selector)) .some((element) => !!element); } + + + +export const tableRowTypesToClassnames = ({ original }) => { + const rowTypes = _.castArray(original.row_types); + + return rowTypes.reduce((acc, rowType) => { + acc[`row_type--${rowType}`] = rowType; + + return acc; + }, {}); +}; \ No newline at end of file