BIG-59: Refactoring mutli-select component.

This commit is contained in:
a.bouhuolia
2021-09-13 16:49:43 +02:00
parent 315b27ffe5
commit 5a3f42682b
17 changed files with 249 additions and 299 deletions

View File

@@ -1,76 +1,30 @@
import React, { useMemo, useCallback, useState } from 'react';
import { omit } from 'lodash';
import { MenuItem, Button } from '@blueprintjs/core';
import MultiSelect from 'components/MultiSelect';
import { FormattedMessage as T } from 'components';
export default function AccountsMultiSelect({ accounts, onAccountSelected }) {
const [selectedAccounts, setSelectedAccounts] = useState({});
const isAccountSelect = useCallback(
(accountId) => {
return 'undefined' !== typeof selectedAccounts[accountId];
},
[selectedAccounts],
);
// Account item of select accounts field.
const accountItem = useCallback(
(item, { handleClick, modifiers, query }) => {
return (
<MenuItem
icon={isAccountSelect(item.id) ? 'tick' : 'blank'}
text={item.name}
label={item.code}
key={item.id}
onClick={handleClick}
/>
);
},
[isAccountSelect],
);
const countSelectedAccounts = useMemo(
() => Object.values(selectedAccounts).length,
[selectedAccounts],
);
const onAccountSelect = useCallback(
(account) => {
const selected = {
...(!isAccountSelect(account.id)
? {
...selectedAccounts,
[account.id]: true,
}
: {
...omit(selectedAccounts, [account.id]),
}),
};
setSelectedAccounts({ ...selected });
onAccountSelected && onAccountSelected(selected);
},
[setSelectedAccounts, selectedAccounts, isAccountSelect, onAccountSelected],
);
import React from 'react';
import { MenuItem } from '@blueprintjs/core';
import { MultiSelect } from './MultiSelectTaggable';
export default function AccountsMultiSelect({ ...multiSelectProps }) {
return (
<MultiSelect
items={accounts}
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
itemRenderer={accountItem}
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}
/>
);
}}
popoverProps={{ minimal: true }}
filterable={true}
onItemSelect={onAccountSelect}
>
<Button
text={
countSelectedAccounts === 0 ? (
<T id={'all_accounts'} />
) : (
`(${countSelectedAccounts}) Selected accounts`
)
}
/>
</MultiSelect>
fill={true}
tagRenderer={(item) => item.name}
resetOnSelect={true}
{...multiSelectProps}
/>
);
}

View File

@@ -1,59 +1,11 @@
import React, { useCallback, useState } from 'react';
import { MenuItem, Button } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import MultiSelect from 'components/MultiSelect';
import { FormattedMessage as T } from 'components';
import { safeInvoke } from 'utils';
import React, { useCallback } from 'react';
import { MenuItem } from '@blueprintjs/core';
import { MultiSelect } from '../components/MultiSelectTaggable';
/**
* Contacts multi-select component.
*/
export default function ContactsMultiSelect({
contacts,
defaultText = <T id={'all_customers'} />,
buttonProps,
onContactSelect,
contactsSelected = [],
...multiSelectProps
}) {
const [localSelected, setLocalSelected] = useState([...contactsSelected]);
// Detarmines the given id is selected.
const isItemSelected = useCallback(
(id) => localSelected.some((s) => s === id),
[localSelected],
);
// Contact item renderer.
const contactRenderer = useCallback(
(contact, { handleClick }) => (
<MenuItem
icon={isItemSelected(contact.id) ? 'tick' : 'blank'}
text={contact.display_name}
key={contact.id}
onClick={handleClick}
/>
),
[isItemSelected],
);
// Count selected items.
const countSelected = localSelected.length;
// Handle item selected.
const handleItemSelect = useCallback(
({ id }) => {
const selected = isItemSelected(id)
? localSelected.filter((s) => s !== id)
: [...localSelected, id];
setLocalSelected([...selected]);
safeInvoke(onContactSelect, selected);
},
[setLocalSelected, localSelected, isItemSelected, onContactSelect],
);
export default function ContactsMultiSelect({ ...multiSelectProps }) {
// Filters accounts items.
const filterContactsPredicater = useCallback(
(query, contact, _index, exactMatch) => {
@@ -71,23 +23,20 @@ export default function ContactsMultiSelect({
return (
<MultiSelect
items={contacts}
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
itemRenderer={contactRenderer}
itemRenderer={(contact, { selected, active, handleClick }) => (
<MenuItem
active={active}
icon={selected ? 'tick' : 'blank'}
text={contact.display_name}
key={contact.id}
onClick={handleClick}
/>
)}
popoverProps={{ minimal: true }}
filterable={true}
onItemSelect={handleItemSelect}
fill={true}
itemPredicate={filterContactsPredicater}
tagRenderer={(item) => item.display_name}
{...multiSelectProps}
>
<Button
text={
countSelected === 0
? defaultText
: intl.get('selected_customers', { count: countSelected })
}
{...buttonProps}
/>
</MultiSelect>
/>
);
}

View File

@@ -1,64 +1,11 @@
import React, { useCallback, useState } from 'react';
import { MenuItem, Button, Intent } from '@blueprintjs/core';
import { MultiSelect } from '@blueprintjs/select';
import { FormattedMessage as T } from 'components';
import { safeInvoke } from 'utils';
import React, { useCallback } from 'react';
import { MenuItem } from '@blueprintjs/core';
import { MultiSelect } from '../../components';
/**
* Items multi-select.
*/
export function ItemsMultiSelect({
items,
defaultText = <T id={'All items'} />,
buttonProps,
onTagRenderer,
selectedItems = [],
onItemSelect,
...multiSelectProps
}) {
const [localSelected, setLocalSelected] = useState([...selectedItems]);
// Detarmines the given id is selected.
const isItemSelected = useCallback(
(id) => localSelected.some((s) => s === id),
[localSelected],
);
// Contact item renderer.
const itemRenderer = useCallback(
(item, { handleClick }) => (
<MenuItem
icon={isItemSelected(item.id) ? 'tick' : 'blank'}
text={item.name}
label={item.code}
key={item.id}
onClick={handleClick}
/>
),
[isItemSelected],
);
// Handle item selected.
const handleItemSelect = useCallback(
({ id }) => {
const selected = isItemSelected(id)
? localSelected.filter((s) => s !== id)
: [...localSelected, id];
setLocalSelected([...selected]);
safeInvoke(onItemSelect, selected);
},
[setLocalSelected, localSelected, isItemSelected, onItemSelect],
);
const itemsSelected = () => {
const results = [];
items.map(({ id, name }) => {
return isItemSelected(id) ? results.push(name) : [];
});
return results;
};
export function ItemsMultiSelect({ ...multiSelectProps }) {
// Filters accounts items.
const filterItemsPredicater = useCallback(
(query, item, _index, exactMatch) => {
@@ -74,48 +21,28 @@ export function ItemsMultiSelect({
[],
);
// Count selected items.
const countSelected = itemsSelected().length;
// Clear Button
const clearButton =
countSelected > 0 ? (
<Button
icon="cross"
minimal={true}
// onClick={() => setLocalSelected([])}
/>
) : undefined;
// handle remove tag
const handleTagRemove = (tag) => {
let tagList = localSelected.filter((s) => s !== tag);
setLocalSelected(tagList);
};
return (
<MultiSelect
items={items}
noResults={<MenuItem disabled={true} text={<T id={'No items'} />} />}
itemRenderer={itemRenderer}
popoverProps={{
minimal: true,
usePortal: false,
targetTagName: 'div ',
}}
selectedItems={itemsSelected()}
itemRenderer={(item, { selected, handleClick, active }) => (
<MenuItem
active={active}
icon={selected ? 'tick' : 'blank'}
text={item.name}
label={item.code}
key={item.id}
onClick={handleClick}
/>
)}
popoverProps={{ minimal: true, usePortal: false, targetTagName: 'div ' }}
fill={true}
onItemSelect={handleItemSelect}
itemPredicate={filterItemsPredicater}
tagRenderer={onTagRenderer}
tagInputProps={{
tagProps: { intent: Intent.NONE, minimal: false },
onRemove: handleTagRemove,
// rightElement: clearButton,
}}
tagRenderer={(item) => item.name}
resetOnSelect={true}
// openOnKeyDown={true}
{...multiSelectProps}
/>
);
}
ItemsMultiSelect.defaultProps = {
initialSelectedItems: [],
};

View File

@@ -0,0 +1,81 @@
import React, { useCallback, useState } from 'react';
import { includes } from 'lodash';
import { MenuItem } from '@blueprintjs/core';
import { MultiSelect as MultiSelectBP } from '@blueprintjs/select';
import { FormattedMessage as T } from 'components';
import { safeInvoke } from 'utils';
/**
* Items multi-select.
*/
export function MultiSelect({
items,
initialSelectedItems,
onItemSelect,
...multiSelectProps
}) {
const [localSelected, setLocalSelected] = useState(initialSelectedItems);
// Detarmines whether the given id is selected.
const isItemSelected = useCallback(
(item) => includes(localSelected, item),
[localSelected],
);
// Removes the given item from selected items.
const removeSelectedItem = React.useCallback(
(item) => localSelected.filter((localItem) => localItem !== item),
[localSelected],
);
// Adds the given item to selected items.
const addSelectedItem = React.useCallback(
(item) => [...localSelected, item],
[localSelected],
);
// Handle item selected.
const handleItemSelect = useCallback(
(item) => {
const selected = isItemSelected(item)
? removeSelectedItem(item)
: addSelectedItem(item);
setLocalSelected(selected);
safeInvoke(onItemSelect, selected);
},
[addSelectedItem, removeSelectedItem, isItemSelected, onItemSelect],
);
// handle remove tag
const handleTagRemove = (item) => {
const selected = removeSelectedItem(item);
setLocalSelected(selected);
safeInvoke(onItemSelect, selected);
};
const itemRenderer = (item, props) => {
return multiSelectProps.itemRenderer(item, {
selected: isItemSelected(item),
...props,
});
};
return (
<MultiSelectBP
items={items}
noResults={<MenuItem disabled={true} text={<T id={'No items'} />} />}
tagInputProps={{
onRemove: handleTagRemove,
}}
{...multiSelectProps}
itemRenderer={itemRenderer}
selectedItems={localSelected}
onItemSelect={handleItemSelect}
/>
);
}
MultiSelect.defaultProps = {
initialSelectedItems: [],
};

View File

@@ -77,7 +77,7 @@ export * from './Subscriptions';
export * from './Dashboard';
export * from './Drawer';
export * from './Forms';
export * from './MultiSelectTaggable'
const Hint = FieldHint;
const T = FormattedMessage;

View File

@@ -99,11 +99,10 @@ export default function APAgingSummaryHeaderGeneralContent() {
className={classNames('form-group--select-list', Classes.FILL)}
>
<ContactsMultiSelect
defaultText={<T id={'all_vendors'} />}
contacts={vendors}
contactsSelected={value}
onContactSelect={(contactsIds) => {
setFieldValue('vendorsIds', contactsIds);
items={vendors}
onItemSelect={(vendors) => {
const vendorsIds = vendors.map((customer) => customer.id);
setFieldValue('vendorsIds', vendorsIds);
}}
/>
</FormGroup>

View File

@@ -105,10 +105,12 @@ export default function ARAgingSummaryHeaderGeneralContent() {
className={classNames('form-group--select-list', Classes.FILL)}
>
<ContactsMultiSelect
contacts={customers}
contactsSelected={value}
onContactSelect={(contactsIds) => {
setFieldValue('customersIds', contactsIds);
items={customers}
onItemSelect={(customers) => {
const customersIds = customers.map(
(customer) => customer.id,
);
setFieldValue('customersIds', customersIds);
}}
/>
</FormGroup>

View File

@@ -74,15 +74,15 @@ export default function CustomersBalanceSummaryGeneralPanelContent() {
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'Specific customers'} />}
label={<T id={'specific_customers'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<ContactsMultiSelect
onContactSelect={(contactsIds) => {
setFieldValue('customersIds', contactsIds);
items={customers}
onItemSelect={(contacts) => {
const customersIds = contacts.map(contact => contact.id);
setFieldValue('customersIds', customersIds);
}}
contacts={customers}
contactsSelected={value}
/>
</FormGroup>
)}

View File

@@ -3,8 +3,12 @@ import classNames from 'classnames';
import { Field } from 'formik';
import { Classes, FormGroup } from '@blueprintjs/core';
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import { Row, Col } from 'components';
import { ContactsMultiSelect, FormattedMessage as T } from 'components';
import {
Row,
Col,
ContactsMultiSelect,
FormattedMessage as T,
} from '../../../components';
import {
CustomersTransactionsGeneralPanelProvider,
useCustomersTransactionsGeneralPanelContext,
@@ -40,11 +44,13 @@ function CustomersTransactionsHeaderGeneralPanelContent() {
className={classNames('form-group--select-list', Classes.FILL)}
>
<ContactsMultiSelect
onContactSelect={(contactsIds) => {
setFieldValue('customersIds', contactsIds);
items={customers}
onItemSelect={(customers) => {
const customersIds = customers.map(
(customer) => customer.id,
);
setFieldValue('customersIds', customersIds);
}}
contacts={customers}
contactsSelected={value}
/>
</FormGroup>
)}

View File

@@ -43,7 +43,7 @@ function GLHeaderGeneralPaneContent() {
label={<T id={'specific_accounts'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<AccountsMultiSelect accounts={accounts} />
<AccountsMultiSelect items={accounts} />
</FormGroup>
</Col>
</Row>

View File

@@ -2,10 +2,18 @@ import React from 'react';
import classNames from 'classnames';
import { FormGroup, Classes } from '@blueprintjs/core';
import { Field } from 'formik';
import { Row, Col, FormattedMessage as T } from 'components';
import {
ItemsMultiSelect,
Row,
Col,
FormattedMessage as T,
} from '../../../components';
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import { useInventoryItemDetailsContext } from './InventoryItemDetailsProvider';
import { InventoryItemDetailsHeaderGeneralProvider } from './InventoryItemDetailsHeaderGeneralProvider';
import {
InventoryItemDetailsHeaderGeneralProvider,
useInventoryItemDetailsHeaderGeneralContext,
} from './InventoryItemDetailsHeaderGeneralProvider';
/**
* Inventory item details header - General panel.
@@ -22,7 +30,7 @@ export default function InventoryItemDetailsHeaderGeneralPanel() {
* Inventory item details header - General panel - Content.
*/
function InventoryItemDetailsHeaderGeneralPanelContent() {
const { items } = useInventoryItemDetailsContext();
const { items } = useInventoryItemDetailsHeaderGeneralContext();
return (
<div>
@@ -39,7 +47,15 @@ function InventoryItemDetailsHeaderGeneralPanelContent() {
<FormGroup
label={<T id={'Specific items'} />}
className={classNames('form-group--select-list', Classes.FILL)}
></FormGroup>
>
<ItemsMultiSelect
items={items}
onItemSelect={(items) => {
const itemsIds = items.map((item) => item.id);
setFieldValue('itemsIds', itemsIds);
}}
/>
</FormGroup>
)}
</Field>
</Col>

View File

@@ -3,8 +3,14 @@ import { FastField, Field } from 'formik';
import { DateInput } from '@blueprintjs/datetime';
import { FormGroup, Position, Classes } from '@blueprintjs/core';
import classNames from 'classnames';
import { FormattedMessage as T } from 'components';
import { ItemsMultiSelect, Row, Col, FieldHint } from 'components';
import {
FormattedMessage as T,
ItemsMultiSelect,
Row,
Col,
FieldHint,
} from '../../../components';
import {
momentFormatter,
tansformDateValue,
@@ -64,16 +70,18 @@ function InventoryValuationHeaderGeneralPanelContent() {
<Row>
<Col xs={5}>
<Field name={'itemsIds'}>
{({
form: { setFieldValue },
field: { value },
meta: { error, touched },
}) => (
{({ form: { setFieldValue } }) => (
<FormGroup
label={<T id={'Specific items'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<ItemsMultiSelect
items={items}
onItemSelect={(items) => {
const itemsIds = items.map((item) => item.id);
setFieldValue('itemsIds', itemsIds);
}}
/>
</FormGroup>
)}
</Field>

View File

@@ -1,18 +1,22 @@
import React from 'react';
import { FormGroup, Classes } from '@blueprintjs/core';
import { Field } from 'formik';
import { Row, Col, FormattedMessage as T } from 'components';
import {
Row,
Col,
FormattedMessage as T,
ItemsMultiSelect,
} from '../../../components';
import classNames from 'classnames';
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import { ItemsMultiSelect } from 'components';
import {
PurchasesByItemsGeneralPanelProvider,
usePurchaseByItemsGeneralPanelContext,
} from './PurchasesByItemsGeneralPanelProvider';
/**
*
*
*/
export default function PurchasesByItemsGeneralPanel() {
return (
@@ -35,16 +39,18 @@ function PurchasesByItemsGeneralPanelContent() {
<Row>
<Col xs={4}>
<Field name={'itemsIds'}>
{({
form: { setFieldValue },
field: { value },
meta: { error, touched },
}) => (
{({ form: { setFieldValue } }) => (
<FormGroup
label={<T id={'Specific items'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<ItemsMultiSelect
items={items}
onItemSelect={(items) => {
const itemsIds = items.map((item) => item.id);
setFieldValue('itemsIds', itemsIds);
}}
/>
</FormGroup>
)}
</Field>

View File

@@ -28,13 +28,9 @@ function SalesByItemsHeader({
// #withSalesByItemsActions
toggleSalesByItemsFilterDrawer,
}) {
// Form validation schema.
const validationSchema = Yup.object().shape({
fromDate: Yup.date()
.required()
.label(intl.get('from_date')),
fromDate: Yup.date().required().label(intl.get('from_date')),
toDate: Yup.date()
.min(Yup.ref('fromDate'))
.required()

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { FormGroup, Classes } from '@blueprintjs/core';
import { Field } from 'formik';
import classNames from 'classnames';
import { get } from 'lodash';
import { Row, Col, ItemsMultiSelect, FormattedMessage as T } from 'components';
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
@@ -34,22 +35,17 @@ function SalesByItemsHeaderGeneralPanelContent() {
<Row>
<Col xs={4}>
<Field name={'itemsIds'}>
{({
form: { setFieldValue },
field: { value },
meta: { error, touched },
}) => (
{({ form: { setFieldValue }, field: { value } }) => (
<FormGroup
label={<T id={'Specific items'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<ItemsMultiSelect
items={items}
selectedItems={value}
onItemSelect={(itemsIds) => {
onItemSelect={(items) => {
const itemsIds = items.map((item) => item.id);
setFieldValue('itemsIds', itemsIds);
}}
onTagRenderer={(value) => value}
/>
</FormGroup>
)}

View File

@@ -4,7 +4,13 @@ import { DateInput } from '@blueprintjs/datetime';
import classNames from 'classnames';
import { FormGroup, Position, Classes, Checkbox } from '@blueprintjs/core';
import { Row, Col, FieldHint, FormattedMessage as T } from 'components';
import {
ContactsMultiSelect,
Row,
Col,
FieldHint,
FormattedMessage as T,
} from '../../../components';
import {
momentFormatter,
tansformDateValue,
@@ -68,15 +74,19 @@ export default function VendorsBalanceSummaryHeaderGeneralContent() {
<Row>
<Col xs={4}>
<Field name={'vendorsIds'}>
{({
form: { setFieldValue },
field: { value },
meta: { error, touched },
}) => (
{({ form: { setFieldValue } }) => (
<FormGroup
label={<T id={'specific_vendors'} />}
className={classNames('form-group--select-list', Classes.FILL)}
></FormGroup>
>
<ContactsMultiSelect
items={vendors}
onItemSelect={(contacts) => {
const vendorsIds = contacts.map((contact) => contact.id);
setFieldValue('vendorsIds', vendorsIds);
}}
/>
</FormGroup>
)}
</Field>
</Col>

View File

@@ -9,7 +9,7 @@ import {
Col,
ContactsMultiSelect,
FormattedMessage as T,
} from 'components';
} from '../../../components';
import {
VendorsTransactionsGeneralPanelProvider,
useVendorsTransactionsGeneralPanelContext,
@@ -45,11 +45,11 @@ function VendorsTransactionsHeaderGeneralPanelContent() {
className={classNames('form-group--select-list', Classes.FILL)}
>
<ContactsMultiSelect
onContactSelect={(contactsIds) => {
setFieldValue('vendorsIds', contactsIds);
items={vendors}
onItemSelect={(vendors) => {
const vendorsIds = vendors.map((customer) => customer.id);
setFieldValue('vendorsIds', vendorsIds);
}}
contacts={vendors}
contactsSelected={value}
/>
</FormGroup>
)}