mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 05:10:31 +00:00
refactor(webapp): Accounts Select and MultiSelect components
This commit is contained in:
@@ -1,74 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { FMultiSelect } from '../Forms';
|
||||
import classNames from 'classnames';
|
||||
import { Classes } from '@blueprintjs/popover2';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} query
|
||||
* @param {*} account
|
||||
* @param {*} _index
|
||||
* @param {*} exactMatch
|
||||
* @returns
|
||||
*/
|
||||
const accountItemPredicate = (query, account, _index, exactMatch) => {
|
||||
const normalizedTitle = account.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${account.code}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} account
|
||||
* @param {*} param1
|
||||
* @returns
|
||||
*/
|
||||
const accountItemRenderer = (
|
||||
account,
|
||||
{ handleClick, modifiers, query },
|
||||
{ isSelected },
|
||||
) => {
|
||||
return (
|
||||
<MenuItem
|
||||
icon={isSelected ? 'tick' : 'blank'}
|
||||
text={account.name}
|
||||
label={account.code}
|
||||
key={account.id}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const accountSelectProps = {
|
||||
itemPredicate: accountItemPredicate,
|
||||
itemRenderer: accountItemRenderer,
|
||||
valueAccessor: (item) => item.id,
|
||||
labelAccessor: (item) => item.code,
|
||||
tagRenderer: (item) => item.name,
|
||||
};
|
||||
|
||||
/**
|
||||
* branches mulit select.
|
||||
* @param {*} param0
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function AccountMultiSelect({ accounts, ...rest }) {
|
||||
return (
|
||||
<FMultiSelect
|
||||
items={accounts}
|
||||
popoverProps={{
|
||||
minimal: true,
|
||||
}}
|
||||
{...accountSelectProps}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,31 +1,79 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { MultiSelect } from '../MultiSelectTaggable';
|
||||
import { FMultiSelect } from '../Forms';
|
||||
import { accountPredicate } from './_components';
|
||||
import { filterAccountsByQuery, nestedArrayToflatten } from '@/utils';
|
||||
import { MenuItemNestedText } from '../Menu';
|
||||
|
||||
export function AccountsMultiSelect({ ...multiSelectProps }) {
|
||||
/**
|
||||
* Default account item renderer of the list.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
const accountRenderer = (
|
||||
item,
|
||||
{ handleClick, modifiers, query },
|
||||
{ isSelected },
|
||||
) => {
|
||||
if (!modifiers.matchesPredicate) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<MultiSelect
|
||||
itemRenderer={(
|
||||
item,
|
||||
{ active, selected, handleClick, modifiers, query },
|
||||
) => {
|
||||
return (
|
||||
<MenuItem
|
||||
active={active}
|
||||
icon={selected ? 'tick' : 'blank'}
|
||||
text={item.name}
|
||||
label={item.code}
|
||||
key={item.id}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
<MenuItem
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
text={<MenuItemNestedText level={item.account_level} text={item.name} />}
|
||||
key={item.id}
|
||||
onClick={handleClick}
|
||||
icon={isSelected ? 'tick' : 'blank'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Accounts multi-select field binded with Formik form.
|
||||
* @param {*} param0
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function AccountsMultiSelect({
|
||||
items,
|
||||
|
||||
filterByRootTypes,
|
||||
filterByParentTypes,
|
||||
filterByTypes,
|
||||
filterByNormal,
|
||||
|
||||
...rest
|
||||
}) {
|
||||
// Filters accounts based on the given filter props.
|
||||
const filteredAccounts = useMemo(() => {
|
||||
const flattenAccounts = nestedArrayToflatten(items);
|
||||
|
||||
return filterAccountsByQuery(flattenAccounts, {
|
||||
filterByRootTypes,
|
||||
filterByParentTypes,
|
||||
filterByTypes,
|
||||
filterByNormal,
|
||||
});
|
||||
}, [
|
||||
items,
|
||||
filterByRootTypes,
|
||||
filterByParentTypes,
|
||||
filterByTypes,
|
||||
filterByNormal,
|
||||
]);
|
||||
|
||||
return (
|
||||
<FMultiSelect
|
||||
items={filteredAccounts}
|
||||
valueAccessor={'id'}
|
||||
textAccessor={'name'}
|
||||
labelAccessor={'code'}
|
||||
tagAccessor={'name'}
|
||||
popoverProps={{ minimal: true }}
|
||||
fill={true}
|
||||
tagRenderer={(item) => item.name}
|
||||
resetOnSelect={true}
|
||||
{...multiSelectProps}
|
||||
itemPredicate={accountPredicate}
|
||||
itemRenderer={accountRenderer}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
105
packages/webapp/src/components/Accounts/AccountsSelect.tsx
Normal file
105
packages/webapp/src/components/Accounts/AccountsSelect.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
// @ts-nocheck
|
||||
import React, { useMemo } from 'react';
|
||||
import * as R from 'ramda';
|
||||
import intl from 'react-intl-universal';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import {
|
||||
MenuItemNestedText,
|
||||
FormattedMessage as T,
|
||||
FSelect,
|
||||
} from '@/components';
|
||||
import { filterAccountsByQuery, nestedArrayToflatten } from '@/utils';
|
||||
import { accountPredicate } from './_components';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
|
||||
// Create new account renderer.
|
||||
const createNewItemRenderer = (query, active, handleClick) => {
|
||||
return (
|
||||
<MenuItem
|
||||
icon="add"
|
||||
text={intl.get('list.create', { value: `"${query}"` })}
|
||||
active={active}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// Create new item from the given query string.
|
||||
const createNewItemFromQuery = (name) => {
|
||||
return {
|
||||
name,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Default account item renderer.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
const accountRenderer = (item, { handleClick, modifiers, query }) => {
|
||||
if (!modifiers.matchesPredicate) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<MenuItem
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
label={item.code}
|
||||
key={item.id}
|
||||
text={<MenuItemNestedText level={item.account_level} text={item.name} />}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Accounts select field binded with Formik form.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function AccountsSelectRoot({
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #ownProps
|
||||
items,
|
||||
|
||||
filterByParentTypes,
|
||||
filterByTypes,
|
||||
filterByNormal,
|
||||
filterByRootTypes,
|
||||
|
||||
...restProps
|
||||
}) {
|
||||
// Filters accounts based on filter props.
|
||||
const filteredAccounts = useMemo(() => {
|
||||
const flattenAccounts = nestedArrayToflatten(items);
|
||||
|
||||
const filteredAccounts = filterAccountsByQuery(flattenAccounts, {
|
||||
filterByRootTypes,
|
||||
filterByParentTypes,
|
||||
filterByTypes,
|
||||
filterByNormal,
|
||||
});
|
||||
return filteredAccounts;
|
||||
}, [
|
||||
items,
|
||||
filterByRootTypes,
|
||||
filterByParentTypes,
|
||||
filterByTypes,
|
||||
filterByNormal,
|
||||
]);
|
||||
|
||||
return (
|
||||
<FSelect
|
||||
items={filteredAccounts}
|
||||
textAccessor={'name'}
|
||||
labelAccessor={'code'}
|
||||
valueAccessor={'id'}
|
||||
popoverProps={{ minimal: true, usePortal: true, inline: false }}
|
||||
itemPredicate={accountPredicate}
|
||||
itemRenderer={accountRenderer}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const AccountsSelect = R.compose(withDialogActions)(AccountsSelectRoot);
|
||||
@@ -1,177 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import classNames from 'classnames';
|
||||
import { MenuItem, Button } from '@blueprintjs/core';
|
||||
import { Select } from '@blueprintjs/select';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { MenuItemNestedText, FormattedMessage as T } from '@/components';
|
||||
import { nestedArrayToflatten, filterAccountsByQuery } from '@/utils';
|
||||
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
|
||||
// Create new account renderer.
|
||||
const createNewItemRenderer = (query, active, handleClick) => {
|
||||
return (
|
||||
<MenuItem
|
||||
icon="add"
|
||||
text={intl.get('list.create', { value: `"${query}"` })}
|
||||
active={active}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// Create new item from the given query string.
|
||||
const createNewItemFromQuery = (name) => {
|
||||
return {
|
||||
name,
|
||||
};
|
||||
};
|
||||
|
||||
// Filters accounts items.
|
||||
const filterAccountsPredicater = (query, account, _index, exactMatch) => {
|
||||
const normalizedTitle = account.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Accounts select list.
|
||||
*/
|
||||
function AccountsSelectListRoot({
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #ownProps
|
||||
accounts,
|
||||
initialAccountId,
|
||||
selectedAccountId,
|
||||
defaultSelectText = 'Select account',
|
||||
onAccountSelected,
|
||||
disabled = false,
|
||||
popoverFill = false,
|
||||
|
||||
filterByParentTypes,
|
||||
filterByTypes,
|
||||
filterByNormal,
|
||||
filterByRootTypes,
|
||||
|
||||
allowCreate,
|
||||
|
||||
buttonProps = {},
|
||||
}) {
|
||||
const flattenAccounts = useMemo(
|
||||
() => nestedArrayToflatten(accounts),
|
||||
[accounts],
|
||||
);
|
||||
|
||||
// Filters accounts based on filter props.
|
||||
const filteredAccounts = useMemo(() => {
|
||||
let filteredAccounts = filterAccountsByQuery(flattenAccounts, {
|
||||
filterByRootTypes,
|
||||
filterByParentTypes,
|
||||
filterByTypes,
|
||||
filterByNormal,
|
||||
});
|
||||
return filteredAccounts;
|
||||
}, [
|
||||
flattenAccounts,
|
||||
filterByRootTypes,
|
||||
filterByParentTypes,
|
||||
filterByTypes,
|
||||
filterByNormal,
|
||||
]);
|
||||
|
||||
// Find initial account object to set it as default account in initial render.
|
||||
const initialAccount = useMemo(
|
||||
() => filteredAccounts.find((a) => a.id === initialAccountId),
|
||||
[initialAccountId, filteredAccounts],
|
||||
);
|
||||
|
||||
// Select account item.
|
||||
const [selectedAccount, setSelectedAccount] = useState(
|
||||
initialAccount || null,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof selectedAccountId !== 'undefined') {
|
||||
const account = selectedAccountId
|
||||
? filteredAccounts.find((a) => a.id === selectedAccountId)
|
||||
: null;
|
||||
setSelectedAccount(account);
|
||||
}
|
||||
}, [selectedAccountId, filteredAccounts, setSelectedAccount]);
|
||||
|
||||
// Account item of select accounts field.
|
||||
const accountItem = useCallback((item, { handleClick, modifiers, query }) => {
|
||||
return (
|
||||
<MenuItem
|
||||
text={<MenuItemNestedText level={item.level} text={item.name} />}
|
||||
label={item.code}
|
||||
key={item.id}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
}, []);
|
||||
|
||||
// Handle the account item select.
|
||||
const handleAccountSelect = useCallback(
|
||||
(account) => {
|
||||
if (account.id) {
|
||||
setSelectedAccount({ ...account });
|
||||
onAccountSelected && onAccountSelected(account);
|
||||
} else {
|
||||
openDialog(DialogsName.AccountForm);
|
||||
}
|
||||
},
|
||||
[setSelectedAccount, onAccountSelected, openDialog],
|
||||
);
|
||||
|
||||
// Maybe inject new item props to select component.
|
||||
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||
const maybeCreateNewItemFromQuery = allowCreate
|
||||
? createNewItemFromQuery
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Select
|
||||
items={filteredAccounts}
|
||||
noResults={<MenuItem disabled={true} text={<T id={'no_accounts'} />} />}
|
||||
itemRenderer={accountItem}
|
||||
itemPredicate={filterAccountsPredicater}
|
||||
popoverProps={{
|
||||
minimal: true,
|
||||
usePortal: !popoverFill,
|
||||
inline: popoverFill,
|
||||
}}
|
||||
filterable={true}
|
||||
onItemSelect={handleAccountSelect}
|
||||
disabled={disabled}
|
||||
className={classNames('form-group--select-list', {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
})}
|
||||
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||
>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
text={selectedAccount ? selectedAccount.name : defaultSelectText}
|
||||
{...buttonProps}
|
||||
/>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
export const AccountsSelectList = R.compose(withDialogActions)(
|
||||
AccountsSelectListRoot,
|
||||
);
|
||||
@@ -1,49 +1,15 @@
|
||||
// @ts-nocheck
|
||||
import React, { useCallback } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { ListSelect } from '@/components';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
|
||||
export function AccountsTypesSelect({
|
||||
accountsTypes,
|
||||
selectedTypeId,
|
||||
defaultSelectText = 'Select account type',
|
||||
onTypeSelected,
|
||||
disabled = false,
|
||||
popoverFill = false,
|
||||
...restProps
|
||||
}) {
|
||||
// Filters accounts types items.
|
||||
const filterAccountTypeItems = (query, accountType, _index, exactMatch) => {
|
||||
const normalizedTitle = accountType.label.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return normalizedTitle.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle item selected.
|
||||
const handleItemSelected = (accountType) => {
|
||||
onTypeSelected && onTypeSelected(accountType);
|
||||
};
|
||||
import React from 'react';
|
||||
import { FSelect } from '@/components/Forms';
|
||||
|
||||
export function AccountsTypesSelect({ ...props }) {
|
||||
return (
|
||||
<ListSelect
|
||||
items={accountsTypes}
|
||||
selectedItemProp={'key'}
|
||||
selectedItem={selectedTypeId}
|
||||
textProp={'label'}
|
||||
defaultText={defaultSelectText}
|
||||
onItemSelect={handleItemSelected}
|
||||
itemPredicate={filterAccountTypeItems}
|
||||
disabled={disabled}
|
||||
className={classNames('form-group--select-list', {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
})}
|
||||
{...restProps}
|
||||
<FSelect
|
||||
valueAccessor={'key'}
|
||||
labelAccessor={'label'}
|
||||
textAccessor={'label'}
|
||||
placeholder={'Select an account...'}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
16
packages/webapp/src/components/Accounts/_components.tsx
Normal file
16
packages/webapp/src/components/Accounts/_components.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { MenuItemNestedText } from '../Menu';
|
||||
|
||||
|
||||
// Filters accounts items.
|
||||
export const accountPredicate = (query, account, _index, exactMatch) => {
|
||||
const normalizedTitle = account.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './AccountMultiSelect';
|
||||
export * from './AccountsSelect';
|
||||
export * from './AccountsMultiSelect';
|
||||
export * from './AccountsSelectList';
|
||||
export * from './AccountsSuggestField';
|
||||
export * from './AccountsTypesSelect';
|
||||
|
||||
@@ -45,13 +45,6 @@ const currencyItemRenderer = (currency, { handleClick, modifiers, query }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const currencySelectProps = {
|
||||
itemPredicate: currencyItemPredicate,
|
||||
itemRenderer: currencyItemRenderer,
|
||||
valueAccessor: 'currency_code',
|
||||
labelAccessor: 'currency_code',
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} currencies
|
||||
@@ -60,7 +53,10 @@ const currencySelectProps = {
|
||||
export function CurrencySelect({ currencies, ...rest }) {
|
||||
return (
|
||||
<FSelect
|
||||
{...currencySelectProps}
|
||||
itemPredicate={currencyItemPredicate}
|
||||
itemRenderer={currencyItemRenderer}
|
||||
valueAccessor={'currency_code'}
|
||||
labelAccessor={'currency_code'}
|
||||
{...rest}
|
||||
items={currencies}
|
||||
input={CurrnecySelectButton}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import {
|
||||
FormGroup,
|
||||
InputGroup,
|
||||
@@ -9,9 +10,23 @@ import {
|
||||
EditableText,
|
||||
TextArea,
|
||||
} from '@blueprintjs-formik/core';
|
||||
import { Button } from '@blueprintjs/core';
|
||||
import { Select, MultiSelect } from '@blueprintjs-formik/select';
|
||||
import { DateInput } from '@blueprintjs-formik/datetime';
|
||||
|
||||
function FSelect({ ...props }) {
|
||||
const input = ({ activeItem, text, label, value }) => {
|
||||
return (
|
||||
<Button
|
||||
text={text || props.placeholder || 'Select an item ...'}
|
||||
rightIcon="double-caret-vertical"
|
||||
{...props.buttonProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return <Select input={input} {...props} />;
|
||||
}
|
||||
|
||||
export {
|
||||
FormGroup as FFormGroup,
|
||||
InputGroup as FInputGroup,
|
||||
@@ -19,7 +34,7 @@ export {
|
||||
Checkbox as FCheckbox,
|
||||
RadioGroup as FRadioGroup,
|
||||
Switch as FSwitch,
|
||||
Select as FSelect,
|
||||
FSelect,
|
||||
MultiSelect as FMultiSelect,
|
||||
EditableText as FEditableText,
|
||||
TextArea as FTextArea,
|
||||
|
||||
Reference in New Issue
Block a user