From bb41fe7ce59f3a76ce3ec71bc57ccdf47052f0ee Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Mon, 8 Mar 2021 17:20:02 +0200 Subject: [PATCH] feat(expenses): filter expenses accounts. --- client/src/components/AccountsSelectList.js | 48 +++++++------ client/src/components/AccountsSuggestField.js | 39 +++++------ .../DataTableCells/AccountsListFieldCell.js | 8 ++- client/src/components/utils.js | 46 ++++++++++++ .../ExpenseForm/ExpenseFormEntriesTable.js | 2 + .../Expenses/ExpenseForm/components.js | 2 +- client/src/style/pages/Expense/List.scss | 20 +++--- client/src/utils.js | 70 ++++++++----------- 8 files changed, 136 insertions(+), 99 deletions(-) create mode 100644 client/src/components/utils.js diff --git a/client/src/components/AccountsSelectList.js b/client/src/components/AccountsSelectList.js index 27abc0b6d..7b173dff2 100644 --- a/client/src/components/AccountsSelectList.js +++ b/client/src/components/AccountsSelectList.js @@ -3,7 +3,7 @@ import { MenuItem, Button } from '@blueprintjs/core'; import { Select } from '@blueprintjs/select'; import { FormattedMessage as T } from 'react-intl'; import classNames from 'classnames'; -import { isEmpty } from 'lodash'; +import { filterAccountsByQuery } from './utils'; import { CLASSES } from 'common/classes'; export default function AccountsSelectList({ @@ -14,32 +14,30 @@ export default function AccountsSelectList({ onAccountSelected, disabled = false, popoverFill = false, - filterByParentTypes = [], - filterByTypes = [], + + filterByParentTypes, + filterByTypes, filterByNormal, - buttonProps = {} + filterByRootTypes, + + buttonProps = {}, }) { // Filters accounts based on filter props. const filteredAccounts = useMemo(() => { - let filteredAccounts = [...accounts]; - - if (!isEmpty(filterByParentTypes)) { - filteredAccounts = filteredAccounts.filter( - (account) => filterByParentTypes.indexOf(account.account_parent_type) !== -1, - ); - } - if (!isEmpty(filterByTypes)) { - filteredAccounts = filteredAccounts.filter( - (account) => filterByTypes.indexOf(account.account_type) !== -1, - ); - } - if (!isEmpty(filterByNormal)) { - filteredAccounts = filteredAccounts.filter( - (account) => filterByTypes.indexOf(account.account_normal) === filterByNormal, - ); - } + let filteredAccounts = filterAccountsByQuery(accounts, { + filterByRootTypes, + filterByParentTypes, + filterByTypes, + filterByNormal, + }); return filteredAccounts; - }, [accounts, filterByParentTypes, filterByTypes, filterByNormal]); + }, [ + accounts, + filterByRootTypes, + filterByParentTypes, + filterByTypes, + filterByNormal, + ]); // Find initial account object to set it as default account in initial render. const initialAccount = useMemo( @@ -103,7 +101,11 @@ export default function AccountsSelectList({ noResults={} />} itemRenderer={accountItem} itemPredicate={filterAccountsPredicater} - popoverProps={{ minimal: true, usePortal: !popoverFill, inline: popoverFill }} + popoverProps={{ + minimal: true, + usePortal: !popoverFill, + inline: popoverFill, + }} filterable={true} onItemSelect={onAccountSelect} disabled={disabled} diff --git a/client/src/components/AccountsSuggestField.js b/client/src/components/AccountsSuggestField.js index a47cb0703..bdd1742a5 100644 --- a/client/src/components/AccountsSuggestField.js +++ b/client/src/components/AccountsSuggestField.js @@ -1,48 +1,47 @@ import React, { useState, useCallback, useEffect, useMemo } from 'react'; import { MenuItem } from '@blueprintjs/core'; import { Suggest } from '@blueprintjs/select'; -import { isEmpty } from 'lodash'; import classNames from 'classnames'; import { CLASSES } from 'common/classes'; import { FormattedMessage as T } from 'react-intl'; +import { filterAccountsByQuery } from './utils'; +/** + * Accounts suggest field. + */ export default function AccountsSuggestField({ accounts, initialAccountId, selectedAccountId, defaultSelectText = 'Select account', + popoverFill = false, onAccountSelected, filterByParentTypes = [], filterByTypes = [], filterByNormal, - popoverFill = false, + filterByRootTypes = [], ...suggestProps }) { // Filters accounts based on filter props. const filteredAccounts = useMemo(() => { - let filteredAccounts = [...accounts]; - - if (!isEmpty(filterByParentTypes)) { - filteredAccounts = filteredAccounts.filter( - (account) => filterByParentTypes.indexOf(account.account_parent_type) !== -1, - ); - } - if (!isEmpty(filterByTypes)) { - filteredAccounts = filteredAccounts.filter( - (account) => filterByTypes.indexOf(account.account_type) !== -1, - ); - } - if (!isEmpty(filterByNormal)) { - filteredAccounts = filteredAccounts.filter( - (account) => filterByTypes.indexOf(account.account_normal) === filterByNormal, - ); - } + let filteredAccounts = filterAccountsByQuery(accounts, { + filterByRootTypes, + filterByParentTypes, + filterByTypes, + filterByNormal, + }); return filteredAccounts; - }, [accounts, filterByParentTypes, filterByTypes, filterByNormal]); + }, [ + accounts, + filterByRootTypes, + filterByParentTypes, + filterByTypes, + filterByNormal, + ]); // Find initial account object to set it as default account in initial render. const initialAccount = useMemo( diff --git a/client/src/components/DataTableCells/AccountsListFieldCell.js b/client/src/components/DataTableCells/AccountsListFieldCell.js index 58fac50c1..724cf2753 100644 --- a/client/src/components/DataTableCells/AccountsListFieldCell.js +++ b/client/src/components/DataTableCells/AccountsListFieldCell.js @@ -7,12 +7,14 @@ import AccountsSuggestField from 'components/AccountsSuggestField'; // import AccountsSelectList from 'components/AccountsSelectList'; import { FormGroup, Classes, Intent } from '@blueprintjs/core'; -// Account cell renderer. +/** + * Account cell renderer. + */ export default function AccountCellRenderer({ column: { id, accountsDataProp, - filterAccountsByRootType, + filterAccountsByRootTypes, filterAccountsByTypes, }, row: { index, original }, @@ -55,7 +57,7 @@ export default function AccountCellRenderer({ accounts={accounts} onAccountSelected={handleAccountSelected} selectedAccountId={initialValue} - filterByRootTypes={filterAccountsByRootType} + filterByRootTypes={filterAccountsByRootTypes} filterByTypes={filterAccountsByTypes} inputProps={{ inputRef: (ref) => (accountRef.current = ref), diff --git a/client/src/components/utils.js b/client/src/components/utils.js new file mode 100644 index 000000000..fd8129ca3 --- /dev/null +++ b/client/src/components/utils.js @@ -0,0 +1,46 @@ +import { castArray, isEmpty, includes, pickBy } from 'lodash'; + +/** + * Filters the given accounts of the given query. + * @param {*} accounts + * @param {*} queryProps + * @returns {*} + */ +export const filterAccountsByQuery = (accounts, queryProps) => { + const defaultQuery = { + filterByParentTypes: [], + filterByTypes: [], + filterByNormal: [], + filterByRootTypes: [], + ...pickBy(queryProps, v => v !== undefined), + }; + const query = { + filterByParentTypes: castArray(defaultQuery.filterByParentTypes), + filterByTypes: castArray(defaultQuery.filterByTypes), + filterByNormal: castArray(defaultQuery.filterByNormal), + filterByRootTypes: castArray(defaultQuery.filterByRootTypes), + }; + let filteredAccounts = [...accounts]; + + if (!isEmpty(query.filterByParentTypes)) { + filteredAccounts = filteredAccounts.filter((account) => + includes(query.filterByParentTypes, account.account_parent_type), + ); + } + if (!isEmpty(query.filterByTypes)) { + filteredAccounts = filteredAccounts.filter((account) => + includes(query.filterByTypes, account.account_type), + ); + } + if (!isEmpty(query.filterByNormal)) { + filteredAccounts = filteredAccounts.filter((account) => + includes(query.filterByTypes, account.account_normal), + ); + } + if (!isEmpty(query.filterByRootTypes)) { + filteredAccounts = filteredAccounts.filter((account) => + includes(query.filterByRootTypes, account.account_root_type), + ); + } + return filteredAccounts; +}; diff --git a/client/src/containers/Expenses/ExpenseForm/ExpenseFormEntriesTable.js b/client/src/containers/Expenses/ExpenseForm/ExpenseFormEntriesTable.js index 2cce9d4c3..1aa3ecaf0 100644 --- a/client/src/containers/Expenses/ExpenseForm/ExpenseFormEntriesTable.js +++ b/client/src/containers/Expenses/ExpenseForm/ExpenseFormEntriesTable.js @@ -32,7 +32,9 @@ export default function ExpenseFormEntriesTable({ const handleUpdateData = useCallback( (rowIndex, columnId, value) => { const newRows = compose( + // Update auto-adding new line. updateAutoAddNewLine(defaultEntry, ['expense_account_id']), + // Update the row value of the given row index and column id. updateTableRow(rowIndex, columnId, value), )(entries); diff --git a/client/src/containers/Expenses/ExpenseForm/components.js b/client/src/containers/Expenses/ExpenseForm/components.js index 6992c6992..adeac0c71 100644 --- a/client/src/containers/Expenses/ExpenseForm/components.js +++ b/client/src/containers/Expenses/ExpenseForm/components.js @@ -89,7 +89,7 @@ export function useExpenseFormTableColumns() { className: 'expense_account_id', disableSortBy: true, width: 40, - filterAccountsByRootType: ['expense'], + filterAccountsByRootTypes: ['expense'], }, { Header: formatMessage({ id: 'amount_currency' }, { currency: 'USD' }), diff --git a/client/src/style/pages/Expense/List.scss b/client/src/style/pages/Expense/List.scss index 1f2ed83f5..9cb9b0c1e 100644 --- a/client/src/style/pages/Expense/List.scss +++ b/client/src/style/pages/Expense/List.scss @@ -1,20 +1,16 @@ - // Blueprint framework. // @import '@blueprintjs/core/src/blueprint.scss'; .dashboard__insider--expenses { - .bigcapital-datatable { - .table { - .tbody { - - - .td.total_amount{ - span{ - font-weight: 500; - } - } + .bigcapital-datatable { + .table { + .tbody { + .td.amount { + span { + font-weight: 600; + } } } } } - \ No newline at end of file +} \ No newline at end of file diff --git a/client/src/utils.js b/client/src/utils.js index 0d57c3e59..80f54cbc8 100644 --- a/client/src/utils.js +++ b/client/src/utils.js @@ -1,11 +1,11 @@ import moment from 'moment'; -import _ from 'lodash'; +import _, { castArray } from 'lodash'; import { Intent } from '@blueprintjs/core'; import Currency from 'js-money/lib/currency'; import accounting from 'accounting'; import deepMapKeys from 'deep-map-keys'; import { createSelectorCreator, defaultMemoize } from 'reselect'; -import { isEqual } from 'lodash'; +import { isEqual } from 'lodash'; export function removeEmptyFromObject(obj) { obj = Object.assign({}, obj); @@ -92,7 +92,6 @@ export const compose = (...funcs) => (arg) => arg, ); - export const getObjectDiff = (a, b) => { return _.reduce( a, @@ -158,9 +157,9 @@ export function formattedAmount(cents, currency, props) { symbol, precision: 0, format: { - pos : "%s%v", - neg : "%s%v", - zero: parsedProps.noZero ? "" : '%s%v' + pos: '%s%v', + neg: '%s%v', + zero: parsedProps.noZero ? '' : '%s%v', }, }; @@ -352,7 +351,7 @@ export function treeToList( return tree.reduce((acc, o) => { depth += 1; - if (o[idFieldKey] && nodeFilter(o, depth)) { + if (o[idFieldKey] && nodeFilter(o, depth)) { acc.push(nodeMapper(o, depth)); } if (o[childrenFieldKey]) { @@ -384,7 +383,7 @@ export function defaultToTransform( } export function isBlank(value) { - return _.isEmpty(value) && !_.isNumber(value) || _.isNaN(value); + return (_.isEmpty(value) && !_.isNumber(value)) || _.isNaN(value); } export const getColumnWidth = ( @@ -403,15 +402,12 @@ export const getColumnWidth = ( return result; }; -export const getForceWidth = ( - text, - magicSpacing = 14, -) => { +export const getForceWidth = (text, magicSpacing = 14) => { const textLength = text.length; - const result = textLength * magicSpacing + const result = textLength * magicSpacing; return result; -} +}; export const toSafeNumber = (number) => { return _.toNumber(_.defaultTo(number, 0)); @@ -434,23 +430,22 @@ export function flatObject(obj) { const path = []; // current path function dig(obj) { - if (obj !== Object(obj)) - /*is primitive, end of path*/ - return flatObject[path.join('.')] = obj; /*<- value*/ - - //no? so this is an object with keys. go deeper on each key down - for (let key in obj) { - path.push(key); - dig(obj[key]); - path.pop(); - } + if (obj !== Object(obj)) + /*is primitive, end of path*/ + return (flatObject[path.join('.')] = obj); /*<- value*/ + + //no? so this is an object with keys. go deeper on each key down + for (let key in obj) { + path.push(key); + dig(obj[key]); + path.pop(); + } } dig(obj); return flatObject; } - export function randomNumber(min, max) { if (min > max) { let temp = max; @@ -464,7 +459,6 @@ export function randomNumber(min, max) { } } - export function transformResponse(response) { return transformToCamelCase(response); } @@ -479,7 +473,6 @@ export function transactionNumber(prefix, number) { codes.push(number); } return codes.join('-'); - } export function safeCallback(callback, ...args) { @@ -488,7 +481,7 @@ export function safeCallback(callback, ...args) { export const createDeepEqualSelector = createSelectorCreator( defaultMemoize, - isEqual + isEqual, ); /** @@ -499,8 +492,8 @@ export const isTableEmptyStatus = ({ data, pagination, filterMeta }) => { _.isEmpty(data), _.isEmpty(filterMeta.view), pagination.page === 1, - ].every(cond => cond === true) -} + ].every((cond) => cond === true); +}; /** * Transformes the pagination meta to table props. @@ -545,7 +538,7 @@ export function globalTableStateToTable(globalState) { */ export function transformPagination(pagination) { const transformed = transformResponse(pagination); - + return { ...transformed, pagesCount: getPagesCountFromPaginationMeta(transformed), @@ -559,23 +552,19 @@ export function removeRowsByIndex(rows, rowIndex) { return newRows; } - export function safeSumBy(entries, getter) { return _.chain(entries) - .map(row => toSafeNumber(_.get(row, getter))) + .map((row) => toSafeNumber(_.get(row, getter))) .sum() .value(); } - - export const fullAmountPaymentEntries = (entries) => { return entries.map((item) => ({ ...item, payment_amount: item.due_amount, })); -} - +}; export const amountPaymentEntries = (amount, entries) => { let total = amount; @@ -609,6 +598,7 @@ export const updateMinEntriesLines = (min, defaultEntry) => (lines) => { const diffLines = Math.max(min - lines.length, 0); return [...lines, ...repeatValue(defaultEntry, diffLines)]; } + return lines; }; export const updateRemoveLineByIndex = (rowIndex) => (entries) => { @@ -622,8 +612,8 @@ export const updateTableRow = (rowIndex, columnId, value) => (old) => { return { ...old[rowIndex], [columnId]: value, - } + }; } - return row - }) + return row; + }); }; \ No newline at end of file