mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +00:00
feat: accounts list as tree structure.
This commit is contained in:
@@ -40,6 +40,8 @@ const CLASSES = {
|
|||||||
DATATABLE_EMPTY_STATUS_DESC: 'datatable-empty-status__desc',
|
DATATABLE_EMPTY_STATUS_DESC: 'datatable-empty-status__desc',
|
||||||
DATATABLE_EMPTY_STATUS_ACTIONS: 'datatable-empty-status__actions',
|
DATATABLE_EMPTY_STATUS_ACTIONS: 'datatable-empty-status__actions',
|
||||||
|
|
||||||
|
SELECT_LIST_FILL_POPOVER: 'select-list--fill-popover',
|
||||||
|
|
||||||
...Classes,
|
...Classes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Select } from '@blueprintjs/select';
|
|||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
export default function AccountsSelectList({
|
export default function AccountsSelectList({
|
||||||
accounts,
|
accounts,
|
||||||
@@ -12,6 +13,7 @@ export default function AccountsSelectList({
|
|||||||
defaultSelectText = 'Select account',
|
defaultSelectText = 'Select account',
|
||||||
onAccountSelected,
|
onAccountSelected,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
popoverFill = false,
|
||||||
filterByRootTypes = [],
|
filterByRootTypes = [],
|
||||||
filterByTypes = [],
|
filterByTypes = [],
|
||||||
filterByNormal
|
filterByNormal
|
||||||
@@ -61,7 +63,7 @@ export default function AccountsSelectList({
|
|||||||
const accountItem = useCallback((item, { handleClick, modifiers, query }) => {
|
const accountItem = useCallback((item, { handleClick, modifiers, query }) => {
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={item.name}
|
text={item.htmlName || item.name}
|
||||||
label={item.code}
|
label={item.code}
|
||||||
key={item.id}
|
key={item.id}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
@@ -100,11 +102,13 @@ export default function AccountsSelectList({
|
|||||||
noResults={<MenuItem disabled={true} text={<T id={'no_accounts'} />} />}
|
noResults={<MenuItem disabled={true} text={<T id={'no_accounts'} />} />}
|
||||||
itemRenderer={accountItem}
|
itemRenderer={accountItem}
|
||||||
itemPredicate={filterAccountsPredicater}
|
itemPredicate={filterAccountsPredicater}
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true, usePortal: !popoverFill, inline: popoverFill }}
|
||||||
filterable={true}
|
filterable={true}
|
||||||
onItemSelect={onAccountSelect}
|
onItemSelect={onAccountSelect}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={classNames('form-group--select-list')}
|
className={classNames('form-group--select-list', {
|
||||||
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import { repeat } from 'lodash';
|
||||||
import { pickItemsFromIds, getItemById } from 'store/selectors';
|
import { pickItemsFromIds, getItemById } from 'store/selectors';
|
||||||
import { flatToNestedArray } from 'utils';
|
import { flatToNestedArray, treeToList } from 'utils';
|
||||||
|
|
||||||
const accountsViewsSelector = (state) => state.accounts.views;
|
const accountsViewsSelector = (state) => state.accounts.views;
|
||||||
const accountsDataSelector = (state) => state.accounts.items;
|
const accountsDataSelector = (state) => state.accounts.items;
|
||||||
const accountsCurrentViewSelector = (state) => state.accounts.currentViewId;
|
const accountsCurrentViewSelector = (state) => state.accounts.currentViewId;
|
||||||
const accountIdPropSelector = (state, props) => props.accountId;
|
const accountIdPropSelector = (state, props) => props.accountId;
|
||||||
const accountsListSelector = (state) => state.accounts.list;
|
const accountsListSelector = (state) => state.accounts.listTree;
|
||||||
|
|
||||||
export const getAccountsItems = createSelector(
|
export const getAccountsItems = createSelector(
|
||||||
accountsViewsSelector,
|
accountsViewsSelector,
|
||||||
@@ -30,8 +31,23 @@ export const getAccountsListFactory = () =>
|
|||||||
createSelector(
|
createSelector(
|
||||||
accountsListSelector,
|
accountsListSelector,
|
||||||
accountsDataSelector,
|
accountsDataSelector,
|
||||||
(accounts, accountsItems) => {
|
(accountsTree, accountsItems) => {
|
||||||
return pickItemsFromIds(accountsItems, accounts);
|
return treeToList(accountsTree, {
|
||||||
|
idFieldKey: 'id',
|
||||||
|
childrenFieldKey: 'children',
|
||||||
|
nodeMapper: (node, depth) => {
|
||||||
|
const account = accountsItems[node.id];
|
||||||
|
const spaceChar = String.fromCharCode(160);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...account,
|
||||||
|
htmlName: (depth > 1)
|
||||||
|
? (`${repeat(spaceChar, (depth - 1) * 2)}${account.name}`) :
|
||||||
|
account.name,
|
||||||
|
depth,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import {
|
import { Intent } from '@blueprintjs/core';
|
||||||
Intent,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import Currency from 'js-money/lib/currency';
|
import Currency from 'js-money/lib/currency';
|
||||||
import PProgress from 'p-progress';
|
import PProgress from 'p-progress';
|
||||||
import accounting from 'accounting';
|
import accounting from 'accounting';
|
||||||
@@ -270,13 +268,12 @@ export const orderingLinesIndexes = (lines, attribute = 'index') => {
|
|||||||
return lines.map((line, index) => ({ ...line, [attribute]: index + 1 }));
|
return lines.map((line, index) => ({ ...line, [attribute]: index + 1 }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const transformToObject = (arr, key) => {
|
export const transformToObject = (arr, key) => {
|
||||||
return arr.reduce(function(acc, cur, i) {
|
return arr.reduce(function (acc, cur, i) {
|
||||||
acc[key ? cur[key] : i] = cur;
|
acc[key ? cur[key] : i] = cur;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const itemsStartWith = (items, char) => {
|
export const itemsStartWith = (items, char) => {
|
||||||
return items.filter((item) => item.indexOf(char) === 0);
|
return items.filter((item) => item.indexOf(char) === 0);
|
||||||
@@ -284,15 +281,90 @@ export const itemsStartWith = (items, char) => {
|
|||||||
|
|
||||||
export const saveInvoke = (func, ...rest) => {
|
export const saveInvoke = (func, ...rest) => {
|
||||||
return func && func(...rest);
|
return func && func(...rest);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const transformToForm = (obj, emptyInitialValues) => {
|
export const transformToForm = (obj, emptyInitialValues) => {
|
||||||
return _.pickBy(
|
return _.pickBy(
|
||||||
obj,
|
obj,
|
||||||
(val, key) => val !== null && Object.keys(emptyInitialValues).includes(key),
|
(val, key) => val !== null && Object.keys(emptyInitialValues).includes(key),
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export function inputIntent({ error, touched }){
|
export function inputIntent({ error, touched }) {
|
||||||
return error && touched ? Intent.DANGER : '';
|
return error && touched ? Intent.DANGER : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function listToTree(
|
||||||
|
inputList,
|
||||||
|
{
|
||||||
|
idFieldKey = 'id',
|
||||||
|
parentFieldKey = 'parent_account_id',
|
||||||
|
nodeMapper = (node) => ({ ...node }),
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
var map = {},
|
||||||
|
node,
|
||||||
|
roots = [],
|
||||||
|
i;
|
||||||
|
const list = inputList.map((item) => nodeMapper(item));
|
||||||
|
|
||||||
|
for (i = 0; i < list.length; i += 1) {
|
||||||
|
map[list[i][idFieldKey]] = i;
|
||||||
|
list[i].children = [];
|
||||||
|
}
|
||||||
|
for (i = 0; i < list.length; i += 1) {
|
||||||
|
node = list[i];
|
||||||
|
|
||||||
|
if (node[parentFieldKey]) {
|
||||||
|
list[map[node[parentFieldKey]]].children.push(node);
|
||||||
|
} else {
|
||||||
|
roots.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roots;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function treeToList(
|
||||||
|
list,
|
||||||
|
{
|
||||||
|
idFieldKey = 'id',
|
||||||
|
childrenFieldKey = 'children',
|
||||||
|
nodeMapper = (node, depth) => node,
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
let depth = 0;
|
||||||
|
|
||||||
|
const walker = (tree) => {
|
||||||
|
return tree.reduce(function (acc, o) {
|
||||||
|
depth += 1;
|
||||||
|
|
||||||
|
if (o[idFieldKey]) {
|
||||||
|
acc.push(nodeMapper(o, depth));
|
||||||
|
}
|
||||||
|
if (o[childrenFieldKey]) {
|
||||||
|
acc = acc.concat(walker(o.children));
|
||||||
|
}
|
||||||
|
depth -= 1;
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
return walker(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultToTransform(
|
||||||
|
value,
|
||||||
|
defaultOrTransformedValue,
|
||||||
|
defaultValue,
|
||||||
|
) {
|
||||||
|
const _defaultValue =
|
||||||
|
typeof defaultValue === 'undefined'
|
||||||
|
? defaultOrTransformedValue
|
||||||
|
: defaultValue;
|
||||||
|
|
||||||
|
const _transfromedValue =
|
||||||
|
typeof defaultValue === 'undefined' ? value : defaultOrTransformedValue;
|
||||||
|
|
||||||
|
return value == null || value !== value || value === ''
|
||||||
|
? _defaultValue
|
||||||
|
: _transfromedValue;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user