Проблема:
Не работает поиск по фильтрам
Иногда вводим в фильтр один ИНН но подтягивается два разных Инн

___________________________________________
src/pages/TinSettings/TinSettings.tsx редактировано под хранение в стор

export const TinSettings = () => {
  const dispatch = useAppDispatch();
  const isAdmin = useSelector(isAdminSelector);
  const isLoading = useSelector(getIsPending(getTinSettings));
  const error = useSelector(getError(getTinSettings));
  const currencyPairs = useSelector(CurrencyPairsSelector);
  const content = useSelector(TinSettingDataSelector);
  const searchString = useSelector(tinSearchStringSelector);
  const pageInfo = useSelector(PageInfoSelector);
  const sort = useSelector(tinSortStringSelector);
  const { tin, counterpartyName, currencyPair, active } = searchString;
  
  const setSort = (key: string) => {
    let newSort: typeof sort = null;
    if (!sort || sort.key !== key) {
      newSort = { key, value: 'asc' };
    } else if (sort?.value === 'asc' && sort.key === key) {
      newSort = { key, value: 'desc' };
    }
    dispatch(setSortString(newSort));
  };

 const { isEditing }= useSelector (selectActiveForm)

  const onMoreClick = useCallback(() => {
    dispatch(changePageNumber({}));
  }, []);

  const getData = useDebounced(() => dispatch(getTinSettings()), 500);

  useEffect(() => {
    getData();
  }, [sort, searchString, pageInfo.size, pageInfo.number]);

  useEffect(() => {
    dispatch(getCurrencyPairs());
    dispatch(getTinSettings());
  }, []);

  const onDelete = useCallback(async (id: string) => {
    await dispatch(deleteTinSetting([id]));
  }, []);
};

const renderSortIcon = (key: string) => {
if (!sort) {
return null;
}
if (key == sort.key) {
return sort.value === 'asc'? <Icon.ArrowUpSmall /> : <Icon.ArrowDownSmall />;
}
return null;
};

const currencyOptions = useMemo (() => currencyPairs.map((pair) => ({ label: pair,
value: pair })), [currencyPairs]);

const handleFilter = (key: string, value?: string) => {
dispatch(setSearchString({...searchString, [key]: value}));
};

const resetFilter = () => {
dispatch(setSearchString({}));
};

if (!content.length && isLoading) {
return <Loader />;
}

return (
<Flex pt="24px" gridGap={'24px'} flexDirection="column">
<Flex zIndex={20} gridGap="12px">
<Input
value={tin || ''}
width="258px"
size={SIZE_VARIANT.XSMALL}
inlineLabel
labelText="ИНН"
type="number"
allowLeadingZeroes
maxLength={12}
onChange={(value) => handleFilter('tin', value)}
/>
<Input
value={counterpartyName || ''}
width="258px"
size={SIZE_VARIANT.XSMALL]
inlineLabel
labelText="Контрагент"
onChange={(value) => handleFilter('counterpartyName', value)}
/>
<SelectNew
inlineLabel
width="258px"
labelText-"Валютная пара"
type="search-single"
size-{SIZE_VARIANT.XSMALL}
selected={currencyPair || ''}
options={currencyOptions}
onSelect={(value) => {
handleFilter('currencyPair', value as string);
}}
/>
<Switch
label="Только активные"
isActive-{active === 'true'}
onChange={(value) => handleFilter('active', value ? 'true' : undefined)}
isText={false}
/>
<Button
iconRight={Icon.Refresh}
disabled={isEditing}
type="button"
marginLeft="auto"
variation-{BUTTON_VARIATION.GHOST}
onClick={resetFilter}
>
Очистить фильтры
</Button>
{isAdmin && (
<Button
iconRight={Icon.Plus}
disabled={isEditing} 
type="button"
marginLeft="auto"
variation-{BUTTON_VARIATION.GHOST}
onClick={() => {
dispatch(resetFormData ())b
dispatch(startEditing ());
}
>
Добавить запись 
</Button>
)}
</Flex>

<Flex
flexDirection="column"
borderRadius="md"
height="100%"
border="1px solid"
borderColor="table-cell-border--default"
<Header cols={20}>
<Cell col={TEMPLATE_COLS.active} alignSelf-"center">
<Typography
Typography>
</Cell>
variant={TYPOGRAPHY_VARIANT.TABLE_HEADER_MEDIUM)>Активно</
<HeaderCell alignSelf="center" col={TEMPLATE_COLS.validUntil}>
<Flex onClick={() => setSort('validUntil')} justifyContent="space-between"
cursor="pointer" pr="12px">
<Typography variant={TYPOGRAPHY_VARIANT.TABLE_HEADER_MEDIUM}>Действует
до</Typography>
{renderSortIcon('validUntil')}
</Flex>
</HeaderCell>
<HeaderCell alignSelf="center" col={TEMPLATE_COLS.tin}>
<Flex onClick={() => setSort('tin')} justifyContent="space-between"
cursor="pointer" pr="12px">
<Typography
variant={TYPOGRAPHY_VARIANT.TABLE_HEADER_MEDIUM)>ИНН</
Typography>
{renderSortIcon('tin')}
</Flex>
</HeaderCell>
<HeaderCell alignSelf="center" col={TEMPLATE_COLS.counterpartyName}>
<Flex onClick={() => setSort('counterpartyName')}
justifyContent="space-between" cursor="pointer" pr="12px">
<Typography variant={TYPOGRAPHY_VARIANT.TABLE_HEADER_MEDIUM} >Контрагент</
Typography>
{renderSortIcon('counterpartyName')}
</Flex>
</HeaderCell>
<Cell alignSelf="center" col={TEMPLATE_COLS.description}>
<Typography variant={TYPOGRAPHY_VARIANT.TABLE_HEADER_MEDIUM}>Доп.
информация</Typography>
</Cell>
<Cell alignSelf="center" col={TEMPLATE_COLS.currencyPair}>
<Typography variant={TYPOGRAPHY_VARIANT.TABLE_HEADER_MEDIUM} >Валютная
пара</Typography>
</Cell>
<Cell alignSelf="center" col-{TEMPLATE_COLS.minAmount}>
<Typography variant={TYPOGRAPHY_VARIANT.TABLE_HEADER_MEDIUM}>Мин. сумма</
Typography>
</Cell>
<Cell alignSelf="center" col-{TEMPLATE_COLS.maxAmount}>
<Typography
Typography>
</Cell>
variant={TYPOGRAPHY_VARIANT.TABLE_HEADER_MEDIUM}>Макс. сумма</
<Cell col={2}></Cell>
</Header>

{isEditing && <Editing key="new" toggleEditing={() => dispatch (startEditing())}
isNew />}
{content.map((data, index) => (
<TinRow key={data.id} rowData={data} index={index} onDelete={() => onDelete
(data.id)} />
))}
{content.length === 0 && !isEditing && (
)}
<ErrorNotification isPositive={lerror} details={error || 'Данные не
найдены'} />
<Box backgroundColor="table-cell-fill--default" position="sticky" bottom="0">
<LoadMoreButton
/>
handleClickMore={onMoreClick}
currentPage={pageInfo.number}
isLoading={isLoading}
isLast={pageInfo.last}
totalElements={pageInfo.totalElements}
</Box>
</Flex>
</Flex>
);
};

___________________________________________
//src/typings/Platform.ts
export type TEMPORARY_ANY = any; // eslint-disable-line

export interface PlatformState {
[key: string]: TEMPORARY_ANY;
}

export interface MicrofrontendProps {
name: string;
context: () => { [key: string]: unknown };
publish: <S>(state: S) => void;
navigateTo: (appName: string) => void;
subscribe: <T extends keyof PlatformState>(
listener: (value: PlatformState [T], oldValue?: PlatformState [T]) => void,
path?: T
) => VoidFunction | void;
setToast: (arg: { title: string; content: string }) => void;
callModal: (modal: TModalData) => void;
closeModal: () => void;
setError: (modal: TModalData) => void;
handleFinishApp: (callback: () => void) => void;
finishApp: () => void;
onFinishClientSession: () => void;
registerApp: (callback: () => void) => void;
domElementGetter: () => HTMLElement;
};

export type TButton = {
label: string;
callback: () => void;
type: 'primary' | 'secondary';
isInFocus?: boolean;
};

export type TModalData = {
erroriar: string;
title?: string;
content?: string;
onok?: TButton;
onCancel?: TButton;
};

export interface LegalClient {
inn: string;
kpp?: string;
ogrn?: string;
shortName: string;
unifiedId: string;
}
___________________________________________
src/pages/TinSettings/Editing.tsx

type Props =
| { data: TinSettingData; toggleEditing: () => void; isNew?: false }
| { isNew: true; data?: TinSettingData; toggleEditing: () => void };

export const Editing: FC<Props> = ({ data, toggleEditing, isNew }) => {
const dispatch = useAppDispatch();
const currencyPairs = useSelector (CurrencyPairsSelector);
const isLoading = useSelector(getIsPending (changeTinSetting));


const onSubmit = async (values: TinSettingData) => {
const req = await dispatch(
changeTinSetting({
...values,
counterpartyName: values.counterpartyName | undefined,
validUntil: values.validUntil || undefined,
currencyPair: values.currencyPair || undefined,
description: values.description || undefined,
minAmount: values.minAmount?.replace(/\D/g, '') || undefined,
maxAmount: values.maxAmount?.replace(/\D/g, '') || undefined,
globalId: values.globalId || '',
})
);

(req.meta.requestStatus === 'fulfilled') {
dispatch(resetFormData ());
 toggleEditing();
};

const formik = useFormik({
initialValues: isNew ? ({ active: true } as TinSettingData): data,
onSubmit,
validate: (values) => {
const errorobj = {} as Record<keyof TinSettingData, string | undefined>;

if (!values.tin || (values.tin.length !== 10 && values.tin.length !==
12)) {
errorobj.tin = 'Укажите корректный ИНН';
}

if (values.validUntil && Number(values?.validUntil?.length) !== 10) {
errorobj.validUntil = 'Укажите корректную дату';
}

  if (values.validUntil && !isBeforeDate(new Date(), parseDateString(values.validUntil))) {
  }
  errorObj.validUntil = 'Дата не должна быть из прошлого';

  if (values.minAmount && values.maxAmount) {
    if (Number(values.minAmount.replace(/\D/g, '')) > Number(values.maxAmount.replace(/\D/g,''))) {
      errorObj.minAmount = 'Сумма мин. не может быть больше суммы макс.';
    }
  }
  return errorObj;
},
validateOnBlur: true,
enableReinitialize: true,
});

const { values, errors, dirty, touched, isValid, setFieldValue,
handleSubmit } = formik;

useEffect(
() => () => {
  dispatch(resetActionsStatuses());
},
[]
);

useEffect(() => {
  document.onkeydown = (e: KeyboardEvent) => {
    const { ctrlKey, code } = e;

    if (ctrlKey && code === 'Enter') {
e.preventDefault();
handleSubmit();
return;
}
};
return () => {
document.onkeydown = () => {
//
};
document.onkeyup = () => {
//
};
};
}, []);

const currencyOptions = useMemo (
() => { label: '', value: ''}].concat(currencyPairs.map((pair) => ({
label: pair, value: pair }))),
[currencyPairs]
);

useEffect (()=>{
const subscriber = subscribe((client: LegalClient) => {
if (client) {
formik.setValues({
... formik.values,
tin.: client.inn,
counterpartyName: client.shortName,
globalId: client.unifiedId,
})
}
}, 'crmLegalClient.crmLegalClient');
return subscriber
}, []);

const handleClientSearch = () =>{
navigateTo(PLATFORM_APPS.LEGAL_CLIENT_SEARCH)
}

return (
<Row cols={20}>
<Cell col={TEMPLATE_COLS.active) alignSelf="self-start">
<Switch
isText={false}
isActive={values?.active}
onChange={(value: boolean) setFieldValue(`active`. value)
isActive={values?.active}
onChange={(value: boolean) => setFieldValue(`active`, value)}
/>
</Cell>
<Cell col={TEMPLATE_COLS.validUntil} alignSelf="self-start">
<DateInput
size={SIZE_VARIANT.XSMALL}
captionText={errors.validUntil}
hasError={touched.validUntil && !! errors.validUntil}
placeholder="Ввести дату до"
value={values.validUntil}
onChange={(value: string) => setFieldValue(`validUntil`, value)}
/>
</Cell>
<Cell col={TEMPLATE_COLS.tin} alignSelf="self-start">
<Flex gridGap="0" minWidth="120px" alignItems="flex-start"
width="inherit" justifyContent="space-around">
<Input
size={SIZE_VARIANT.XSMALL}
value={formik.values.tin}
captionText={errors.tin}
hasError={touched.tin && !! errors.tin}
type="number"
allowLeadingZeroes
maxLength={12}
onChange={(value: string) => setFieldValue(`tin`, value)}
readOnly
disabled={!!values.globalId}
/>
<Button width="30px" variation={BUTTON_VARIATION.GHOST} onClick=
{handleClientSearch}>
<Icon.Magnifier />
</Button>
</Flex>
</Cell>
<Cell col={TEMPLATE_COLS.counterpartyName} alignSelf="self-start">
<TextArea
size={SIZE_VARIANT.SMALL}
value={values.counterpartyName as string}
onChange={(value) => setFieldValue(`counterpartyName`, value)}
counter={false}
disabled={!!values.globalId}
/>
...
// Открытие формы редактирования 
<Button
disabled={disableSubmit}
icon={Icon.Save}
variation={BUTTON_VARIATION.TERTIARY}
onClick={() => {
handleSubmit();
}}
/>
// Закрытие формы редактирования 
<Button
icon={Icon.Close}
variation={BUTTON_VARIATION.TERTIARY}
onClick={() => {handleClose}
/>
{!isNew && (
<Button
disabled={isLoading}
icon={Icon.Bin}
variation={BUTTON_VARIATION.DANGER_SECONDRAY}
onClick={() => {
handleSubmit();
}}
/>
)}
</ButtonWrapper>
</Cell>
</Row>
);
};

_______________________________________
src/api/Deals.ts

const legalDealApi = api.injectEndpoints({
endpoints: (builder) => ({
getIndividuals: builder.query<PaginatedResponse <Deal()>, undefined>({
query: () => ({
url: `/conversion/v1/deal/list?page=0&size=50&sort=updatedDate, desc&
search=${
DEALS_FILTERS [Deals TabsEnum.CONFIRM]
},${encodeURIComponent(`tradeDate<=${formatDateToString(new Date())}
`)},${encodeURIComponent(
`tradeDate>=${formatDateToString(new Date())}`
)},${encodeURIComponent('subject~INDIVIDUALS%')}`,
}),
}),
getDealForConfirm: builder.query<PaginatedResponse<Deal[]>, string>({
query: (searchString) => ({
url: `/conversion/v1/deal/list?page=0&size=0&sort=updatedDate,desc&
search=${
DEALS_FILTERS[DealsTabsEnum.CONFIRM]
},${encodeURIComponent(`tradeDate<=${formatDateToString(new Date())}
`)},${encodeURIComponent(
`tradeDate>=${formatDateToString(new Date())}`
)},${encodeURIComponent(`baxterGroup==${searchString`)},$
{encodeURIComponent('subject~CORPORATES%')}`,
}),
providesTags: (_res, _err, id) => {{ type: ApiTagTypesEnum.DEALS, id }],
}),
}),
});
export const { useGetIndividualsQuery, useGetDealForConfirmQuery,
useLazyGetDealForConfirmQuery } = legalDealApi;

___________________________________________
src/store/app/selectors.ts

import { createSelector } from '@reduxjs/toolkit';
import { RootState } from '..';
import { AppRolesEnum } from './types';

export const snackMessageSelector = (state: RootState) => state.
appSLiceReducer.snack;
export const rolesSelector = (state: RootState) => new Set(state.
appSLiceReducer.roles);
export const legalClientSelector = (state: RootState) => state.
appSLiceReducer.legalClient;
export const isConversionUserSelector = createSelector(
rolesSelector,
(roles) => roles.has(AppRolesEnum.VIEWER) || roles.has(AppRolesEnum.USER)
);
export const isAdminSelector = createSelector(rolesSelector, (roles) => roles.
has(AppRolesEnum.ADMIN));
___________________________________________
src/store/app/slice.ts

import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { getRoles } from './actions';
import { State } from './types';
import { LegalClient } from '@/typings/Platform';

const initialState: State = {
snack: null,
roles: [],
};
export const appSlice = createSlice({
name: 'app',
initialState,
reducers: {
setMessage: (state, action: PayloadAction<{ message: string; type:
string } | null>) => {
state.snack = action.payload;
},
setLegalClient: (state, action: PayloadAction<LegalClient | null>)
=> {
state.legalclient = action.payload;
},
},
extraReducers: (builder) => {
builder.addCase(getRoles.fulfilled, (state, { payload }) => {
state.roles = payload;
});
},
});
export const { setMessage, setLegalClient } = appSlice.actions;
export default appSlice.reducer;
___________________________________________
src/store/app/types.ts

import { LegalClient } from '@/typings/Platform';

export interface State {
snack: { message: string; type: string } | null;
roles: AppRolesEnum[];
legalClient?: LegalClient | null;
}
__________________________________________
src/store/TinSettings/selector.ts

export const TinSettingDataSelector - (state: RootState) => state.tinSettingSlice.data;
export const CurrencyPairsSelector = (state: RootState) => state.tinSettingSlice.
currencyPairs;
export const tinSearchStringSelector = (state: RootState) => state.tinSettingSlice.
searchString;
export const tinSortStringSelector = (state: RootState) => state.tinSettingSlice.
sortString;
export const PageInfoSelector = (state: RootState) => state.tinSettingSlice.pageInfo;
|export const selectActiveForm = (state: RootState) => state.tinSettingSlice.activeForm;
__________________________________________
src/store/TinSettings/slice.ts

const initialState: State = {
data: [],
searchString: { active: 'true'},
sortString: { key: 'validUntil', value: 'desc' },
currencyPairs: [],
pageInfo: {
},
last: true,
totalElements: 10,
totalPages: 1,
size: 10,
number: 0,
activeForm: {
data: null,
isEditing: false,
},
};

export const tinSettingsSlice - createSlice({
name: 'tinSettings',
initialState,
reducers: {
startEditing: (state, action: PayloadAction<TinSettingData | undefined>) => {
state.activeForm = {
data: action.payload || ({ active: true } as TinSettingData),
isEditing: true,
};
},
cancelEditing: (state) => {
state.activeForm = initialState.activeForm;
},
resetFormData: (state) => {
state.activeForm = initialState.activeForm;

},
changePageNumber: (state, { payload }: PayloadAction<{ number?: number }>) => {
const { number } = payload;
state.pageInfo.number = number ?? state.pageInfo.number + 1;
},
setSearchString: (state, { payload }) => {
state.searchString = payload;
state.pageInfo.number = 0;
},
setSortString: (state, { payload }) => {
state.pageInfo.number = 0;
state.sortString = payload;
},
resetData: (state) => {
state.data = [];
},
extraReducers: (builder) => {
builder.addCase (getCurrencyPairs.fulfilled, (state, { payload }) => {
state.currencyPairs = payload;
});
builder.addCase(deleteTinSetting.fulfilled, (state, { meta }) => {
state.data = state.data.filter((el) => !meta.arg.includes (el.id));
});
builder.addCase(changeTinSetting.fulfilled, (state, { payload, meta }) =>
if (meta.arg.id) {
const index = state.data.findIndex((element) => element.id === meta.arg.id);
state.data[index] = payload[0];
} else {
state.data = payload.concat(state.data);
}
});
builder.addCase(getTinSettings.fulfilled, (state, { payload }) => {
if (payload.pageInfo.number === 0) {
state.data = payload.content;
} else {
state.data = state.data.concat(payload.content);
}
state.pageInfo.totalElements = payload.pageInfo.totalElements;
state.pageInfo.last = payload.pageInfo.last;
});
}
});

export const {
startEditing,
cancelEditing,
resetFormData,
changePageNumber,
setSortString,
setSearchString,
resetData,
} = tinSettingsSlice.actions;
export default tinSettingsSlice.reducer;
___________________________________________
src/store/TinSettings/types.ts

export interface State {
pageInfo: PageInfo;
data: TinSettingData[];
currencyPairs: string[];
searchString: { tin?: string; counterpartyName?: string; currencyPair?: string; active?: string };
sortString: { key: string; value: 'asc' | 'desc' } | null;
activeForm: {
data: TinSettingData | null;
isEditing: boolean;
};
}

export interface TinSettingData {
id: string;
active: boolean;
counterpartyName?: string;
validUntil?: string;
currencyPair?: string;
tin: string;
description?: string;
minAmount?: string;
maxAmount?: string;
globalId: string;
}