fix: accounts suggest field

This commit is contained in:
Ahmed Bouhuolia
2025-12-21 16:03:15 +02:00
parent b22328cff9
commit 31f5cbf335
22 changed files with 1189 additions and 2224 deletions

View File

@@ -96,7 +96,7 @@ export function AccountsMultiSelect({
};
return (
<FMultiSelect<AccountSelect>
<FMultiSelect
{...rest}
items={filteredAccounts}
valueAccessor={'id'}

View File

@@ -1,23 +1,34 @@
// @ts-nocheck
import React, { useCallback, useMemo } from 'react';
import * as R from 'ramda';
import React, { useCallback, ComponentType } from 'react';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import { MenuItem } from '@blueprintjs/core';
import { CLASSES } from '@/constants/classes';
import { ItemRenderer, ItemPredicate } from '@blueprintjs/select';
import { DialogsName } from '@/constants/dialogs';
import {
FSuggest,
MenuItemNestedText,
FormattedMessage as T,
} from '@/components';
import { nestedArrayToflatten, filterAccountsByQuery } from '@/utils';
import { FSuggest, Suggest, FormattedMessage as T } from '@/components';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import { usePreprocessingAccounts } from './_hooks';
// Account interface
interface Account {
id: number;
name: string;
code: string;
account_level?: number;
account_type?: string;
account_parent_type?: string;
account_root_type?: string;
account_normal?: string;
}
// Types for renderers and predicates
type AccountItemRenderer = ItemRenderer<Account>;
type AccountItemPredicate = ItemPredicate<Account>;
// Create new account renderer.
const createNewItemRenderer = (query, active, handleClick) => {
const createNewItemRenderer = (
query: string,
active: boolean,
handleClick: (event: React.MouseEvent<HTMLElement>) => void,
): React.ReactElement => {
return (
<MenuItem
icon="add"
@@ -29,12 +40,17 @@ const createNewItemRenderer = (query, active, handleClick) => {
};
// Create new item from the given query string.
const createNewItemFromQuery = (name) => {
const createNewItemFromQuery = (name: string): Partial<Account> => {
return { name };
};
// Filters accounts items.
const filterAccountsPredicater = (query, account, _index, exactMatch) => {
const filterAccountsPredicater: AccountItemPredicate = (
query: string,
account: Account,
_index?: number,
exactMatch?: boolean,
): boolean => {
const normalizedTitle = account.name.toLowerCase();
const normalizedQuery = query.toLowerCase();
@@ -45,78 +61,139 @@ const filterAccountsPredicater = (query, account, _index, exactMatch) => {
}
};
/**
* Accounts suggest field.
*/
function AccountsSuggestFieldRoot({
// #withDialogActions
openDialog,
// #ownProps
accounts,
defaultSelectText = intl.formatMessage({ id: 'select_account' }),
filterByParentTypes = [],
filterByTypes = [],
filterByNormal,
filterByRootTypes = [],
allowCreate,
...suggestProps
}) {
const flattenAccounts = useMemo(
() => nestedArrayToflatten(accounts),
[accounts],
);
const filteredAccounts = useMemo(
() =>
filterAccountsByQuery(flattenAccounts, {
filterByParentTypes,
filterByTypes,
filterByNormal,
filterByRootTypes,
}),
[
flattenAccounts,
filterByParentTypes,
filterByTypes,
filterByNormal,
filterByRootTypes,
],
);
const handleCreateItemSelect = useCallback(
(item) => {
if (!item.id) {
openDialog(DialogsName.AccountForm);
}
},
[openDialog],
);
// Maybe inject new item props to select component.
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
const maybeCreateNewItemFromQuery = allowCreate
? createNewItemFromQuery
: null;
// Account item renderer for Suggest (non-Formik)
const accountItemRenderer: AccountItemRenderer = (
item: Account,
{ handleClick, modifiers },
): React.ReactElement | null => {
if (!modifiers.matchesPredicate) {
return null;
}
return (
<FSuggest
items={filteredAccounts}
itemPredicate={filterAccountsPredicater}
onCreateItemSelect={handleCreateItemSelect}
valueAccessor="id"
textAccessor="name"
labelAccessor="code"
inputProps={{ placeholder: defaultSelectText }}
resetOnClose
popoverProps={{ minimal: true, boundary: 'window' }}
createNewItemRenderer={maybeCreateNewItemRenderer}
createNewItemFromQuery={maybeCreateNewItemFromQuery}
{...suggestProps}
<MenuItem
active={modifiers.active}
disabled={modifiers.disabled}
label={item.code}
key={item.id}
text={item.name}
onClick={handleClick}
/>
);
};
// Input value renderer for Suggest (non-Formik)
const inputValueRenderer = (item: Account | null): string => {
if (item) {
return item.name || '';
}
return '';
};
// Props specific to the HOC (excluding component's own props)
interface AccountsSuggestFieldOwnProps {
// #withDialogActions
openDialog: (name: string, payload?: any) => void;
// #ownProps
items: Account[];
defaultSelectText?: string;
filterByParentTypes?: string[];
filterByTypes?: string[];
filterByNormal?: string;
filterByRootTypes?: string[];
allowCreate?: boolean;
}
export const AccountsSuggestField = R.compose(withDialogActions)(
AccountsSuggestFieldRoot,
// Props that the HOC provides to the wrapped component (should be omitted from external props)
type ProvidedSuggestProps =
| 'items'
| 'itemPredicate'
| 'onCreateItemSelect'
| 'valueAccessor'
| 'textAccessor'
| 'labelAccessor'
| 'resetOnClose'
| 'createNewItemRenderer'
| 'createNewItemFromQuery';
// Utility type to extract props from a component
type ComponentProps<C> = C extends ComponentType<infer P> ? P : never;
/**
* HOC for Accounts Suggest Field logic.
* Returns a component that accepts the wrapped component's props minus the ones provided by the HOC.
*/
function withAccountsSuggestFieldLogic<C extends ComponentType<any>>(
Component: C,
): ComponentType<
AccountsSuggestFieldOwnProps & Omit<ComponentProps<C>, ProvidedSuggestProps>
> {
return function AccountsSuggestFieldLogic({
// #withDialogActions
openDialog,
// #ownProps
items,
defaultSelectText = intl.formatMessage({ id: 'select_account' }),
filterByParentTypes = [],
filterByTypes = [],
filterByNormal,
filterByRootTypes = [],
allowCreate,
// SuggestProps - props that will be passed to Suggest/FSuggest
...suggestProps
}: AccountsSuggestFieldOwnProps &
Omit<ComponentProps<C>, ProvidedSuggestProps>) {
const filteredAccounts = usePreprocessingAccounts(items, {
filterByParentTypes,
filterByTypes,
filterByNormal: filterByNormal ? [filterByNormal] : [],
filterByRootTypes,
});
const handleCreateItemSelect = useCallback(
(item: Account | Partial<Account>) => {
if (!('id' in item) || !item.id) {
openDialog(DialogsName.AccountForm);
}
},
[openDialog],
);
// Maybe inject new item props to select component.
const maybeCreateNewItemRenderer = allowCreate
? createNewItemRenderer
: undefined;
const maybeCreateNewItemFromQuery = allowCreate
? createNewItemFromQuery
: undefined;
// Build the SuggestProps to pass to the component
const processedSuggestProps = {
items: filteredAccounts,
itemPredicate: filterAccountsPredicater,
onCreateItemSelect: handleCreateItemSelect,
valueAccessor: 'id' as const,
textAccessor: 'name' as const,
labelAccessor: 'code' as const,
inputProps: { placeholder: defaultSelectText },
resetOnClose: true,
popoverProps: { minimal: true, boundary: 'window' as const },
createNewItemRenderer: maybeCreateNewItemRenderer,
createNewItemFromQuery: maybeCreateNewItemFromQuery,
...suggestProps,
} as ComponentProps<C>;
return <Component {...processedSuggestProps} />;
};
}
const AccountsSuggestFieldWithLogic = withAccountsSuggestFieldLogic(Suggest);
const FAccountsSuggestFieldWithLogic = withAccountsSuggestFieldLogic(FSuggest);
export const AccountsSuggestField = withDialogActions(
AccountsSuggestFieldWithLogic,
);
export const FAccountsSuggestField = withDialogActions(
FAccountsSuggestFieldWithLogic,
);

View File

@@ -58,9 +58,9 @@ export default function AccountCellRenderer({
{...formGroupProps}
>
<AccountsSuggestField
accounts={accounts}
onAccountSelected={handleAccountSelected}
selectedAccountId={initialValue}
items={accounts}
onItemSelect={handleAccountSelected}
selectedValue={initialValue}
filterByRootTypes={filterAccountsByRootTypes}
filterByTypes={filterAccountsByTypes}
inputProps={{

View File

@@ -10,7 +10,16 @@ import {
TextArea,
HTMLSelect,
} from '@blueprintjs-formik/core';
import { MultiSelect, SuggestField } from '@blueprintjs-formik/select';
import {
MultiSelect,
Suggest,
Select,
FormikMultiSelect,
FormikSuggest,
withFormikMultiSelect,
withFormikSuggest,
withFormikSelect,
} from '@blueprintjs-formik/select';
import { DateInput, TimezoneSelect } from '@blueprintjs-formik/datetime';
import { FSelect } from './Select';
@@ -22,11 +31,17 @@ export {
RadioGroup as FRadioGroup,
Switch as FSwitch,
FSelect,
MultiSelect as FMultiSelect,
FormikMultiSelect as FMultiSelect,
EditableText as FEditableText,
SuggestField as FSuggest,
FormikSuggest as FSuggest,
TextArea as FTextArea,
DateInput as FDateInput,
HTMLSelect as FHTMLSelect,
TimezoneSelect as FTimezoneSelect,
Suggest,
MultiSelect,
Select,
withFormikSelect,
withFormikMultiSelect,
withFormikSuggest,
};

View File

@@ -1,7 +1,7 @@
// @ts-nocheck
import React from 'react';
import { Button } from '@blueprintjs/core';
import { Select } from '@blueprintjs-formik/select';
import { FormikSelect } from '@blueprintjs-formik/select';
import styled from 'styled-components';
import clsx from 'classnames';
@@ -14,7 +14,7 @@ export function FSelect({ ...props }) {
className={clsx({ 'is-selected': !!text }, props.className)}
/>
);
return <Select input={input} fill={true} {...props} />;
return <FormikSelect input={input} fill={true} {...props} />;
}
export const SelectButton = styled(Button)`