Pull request #1457: story(TEAMMSBMOB-20013): альтернативные сценарии

Merge in MCB_FE/mcb-platform-monorepo from story/TEAMMSBMOB-20013 to develop

* commit '5c3bf72f251581dc630c4c6d8c2fad2102195ade':
  feat(TEAMMSBMOB-20013): правки
  feat(TEAMMSBMOB-20013): альтернативные сценарии
  feat(TEAMMSBMOB-20013): изм поведение модалки выбора организации, поиска
  feat(TEAMMSBMOB-20013): Изменение поиска контрагента
This commit is contained in:
Дмитрий Слабухин
2025-11-24 16:07:42 +03:00
11 changed files with 99 additions and 34 deletions
@@ -1,3 +1,3 @@
const symbolsForExcept = ['.', ',', 'e'];
const symbolsForExcept = ['.', ',', 'e', '-', '+', 'E'];
export { symbolsForExcept };
@@ -6,7 +6,7 @@ import { Text } from '@fractal-ui/styling';
import { Dropdown } from '@msb/fractal-ui-composites';
import type { InputSize } from '@msb/fractal-ui-composites/src/input/types';
import { Flex, MEDIA, useMediaQuery } from '@msb/shared';
import { useFormState, useForm } from 'react-final-form';
import { useFormState } from 'react-final-form';
import { LOCALIZATION, symbolsForExcept } from '../constants';
import * as S from './PartnerSearch.styled';
import { Option } from './ui';
@@ -56,21 +56,15 @@ const PartnerSearch = ({ partnerName, handleName, showInputFieldName, innField,
ogrnField,
});
const { change } = useForm();
const description = useMemo(() => {
if (other[innField] && other[ogrnField]) return `ИНН: ${other[innField]}, ОГРН: ${other[ogrnField]}`;
if (isMobile) return LOCALIZATION.ORGANIZATION_SEARCH_MOBILE;
}, [innField, isMobile, ogrnField, other]);
const handleSearch = useCallback(
e => {
onInput();
change(partnerName, e.currentTarget.value);
},
[change, onInput, partnerName]
);
const handleSearch = useCallback(() => {
onInput();
}, [onInput]);
const ref = useRef<HTMLInputElement>(null);
@@ -113,6 +107,8 @@ const PartnerSearch = ({ partnerName, handleName, showInputFieldName, innField,
<Option
key={option.value}
dependentName={partnerName}
innName={innField}
ogrnName={ogrnField}
option={option}
onClick={onDropdownClick({ value: option.value, inn: option.inn, ogrn: option.ogrn })}
/>
@@ -6,6 +6,8 @@ import * as S from './Option.styled';
interface Props {
dependentName: string;
innName: string;
ogrnName: string;
option: {
value: string;
label: string;
@@ -15,18 +17,23 @@ interface Props {
onClick(): void;
}
const Option = ({ onClick, dependentName, option }: Props) => {
const Option = ({ onClick, dependentName, option, innName, ogrnName }: Props) => {
const { values } = useFormState();
const value = useMemo(() => values[dependentName], [dependentName, values]);
const inn = useMemo(() => values[innName], [innName, values]);
const ogrn = useMemo(() => values[ogrnName], [ogrnName, values]);
const notBoldedLabel = useMemo(() => (option.label === value ? option.label : option.label.replace(value, '')), [option.label, value]);
const notBoldedInn = useMemo(() => (option.inn === value ? option.inn : trimFromStart(option.inn, value)), [option.inn, value]);
const notBoldedOgrn = useMemo(() => (option.ogrn === value ? option.ogrn : trimFromStart(option.ogrn, value)), [option.ogrn, value]);
const selected = value === option.value && option.inn === inn && option.ogrn === ogrn;
return (
<S.DropdownItemStyled selected={value === option.value} onClick={onClick}>
<S.DropdownItemStyled selected={selected} onClick={onClick}>
<Text.P1>
<strong>{!option.inn.startsWith(value) && !option.ogrn.startsWith(value) && option.label !== value && value}</strong>
{notBoldedLabel}
@@ -117,9 +117,24 @@ const usePartnerSearch = ({ organizations, partnerName, showInputFieldName, hand
[batch, change, handleName, showInputFieldName]
);
const onBlurNameField = useCallback(() => {
setIsOpen(false);
}, []);
const onBlurNameField = useCallback(
e => {
setIsOpen(false);
if (
!options.some(opt => opt.value.startsWith(e.event.target.value)) ||
!options.some(opt => opt.inn.startsWith(e.event.target.value)) ||
!options.some(opt => opt.ogrn.startsWith(e.event.target.value))
) {
batch(() => {
change(innField, undefined);
change(ogrnField, undefined);
change(partnerName, undefined);
});
}
},
[batch, change, innField, ogrnField, options, partnerName]
);
const onInput = useCallback(() => {
setIsOpen(true);
@@ -2,4 +2,8 @@ const LOCALIZATION = {
SUBSCRIBE: 'Подписка активна',
};
export { LOCALIZATION };
const MODAL_LOCALIZATION = {
SELECT: 'Выбрать',
};
export { LOCALIZATION, MODAL_LOCALIZATION };
@@ -1,9 +1,11 @@
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Icon } from '@fractal-ui/library';
import type { ModalButtonProps } from '@fractal-ui/overlays';
import { Modal, Drawer } from '@fractal-ui/overlays';
import { Dropdown } from '@msb/fractal-ui-composites';
import { MEDIA, useMediaQuery } from '@msb/shared';
import { useForm, useFormState } from 'react-final-form';
import { MODAL_LOCALIZATION } from '../../constants';
import { Option } from './Option';
import * as S from './Organizations.styled';
import { useOrganizations } from '@/shared/model';
@@ -21,6 +23,14 @@ const Organizations = ({ textBefore, fieldName, title, onChange }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { change } = useForm();
const { values } = useFormState();
const [organizationId, setId] = useState<string>();
useEffect(() => {
if (!organizationId && values[fieldName]) {
setId(values[fieldName]);
}
}, [organizationId, values, fieldName]);
const { organizationOptions, activeOrganizations } = useOrganizations();
const organization = useMemo(
@@ -33,11 +43,26 @@ const Organizations = ({ textBefore, fieldName, title, onChange }: Props) => {
const onOptionClick = useCallback(
(orgId: string) => () => {
change(fieldName, orgId);
onChange?.(orgId);
handleClose();
setId(orgId);
},
[change, fieldName, handleClose, onChange]
[]
);
const actions: ModalButtonProps[] = useMemo(
() => [
{
text: MODAL_LOCALIZATION.SELECT,
onClick: () => {
change(fieldName, organizationId);
onChange?.(organizationId!);
handleClose();
},
dataAction: 'select',
variant: 'primary',
shape: 'default',
},
],
[change, fieldName, handleClose, onChange, organizationId]
);
return (
@@ -51,28 +76,28 @@ const Organizations = ({ textBefore, fieldName, title, onChange }: Props) => {
)}
</S.Subtitle>
{isTablet || isMobile ? (
<Drawer header={title} isOpen={isOpen} onClose={handleClose}>
<Drawer isFixedActions actions={actions} header={title} isOpen={isOpen} onClose={handleClose}>
<Dropdown isEmbedded dataName="organizations">
{organizationOptions.map(org => (
<Option
key={org.id}
hasActiveSub={activeOrganizations.some(finedOrg => finedOrg.id === org.id)}
org={org}
selected={org.id === values[fieldName]}
selected={org.id === organizationId}
onClick={onOptionClick(org.id)}
/>
))}
</Dropdown>
</Drawer>
) : (
<Modal header={title} isOpen={isOpen} onClose={handleClose}>
<Modal actions={actions} header={title} isOpen={isOpen} onClose={handleClose}>
<Dropdown dataName="organizations">
{organizationOptions.map(org => (
<Option
key={org.id}
hasActiveSub={activeOrganizations.some(finedOrg => finedOrg.id === org.id)}
org={org}
selected={org.id === values[fieldName]}
selected={org.id === organizationId}
onClick={onOptionClick(org.id)}
/>
))}
@@ -20,7 +20,8 @@ const reportMetadataParamsFromFields = (values: FormProps): ReportParams => {
offset: 0,
},
sort: {
direction: SORT_DIRECTION.ASC,
// Несоответствие типов сортировки с ЭКО
direction: SORT_DIRECTION.ASC.toUpperCase() as SORT_DIRECTION,
field: 'reportNumber',
},
},
@@ -8,7 +8,7 @@ const mapDataToRequestParams = (values: FormValues): GenerateParams => {
orgId: organizations,
};
if (organizationsSearch) {
if (organizationsSearch && inn && ogrn) {
requestParams.partnerName = organizationsSearch;
requestParams.inn = inn;
requestParams.ogrn = ogrn;
@@ -1,7 +1,7 @@
import { useCallback, useState } from 'react';
import type { ReportItem } from '@msb/http';
import { queryClient } from '@msb/http';
import { useDialog } from '@msb/shared';
import { useDialog, useRedirect } from '@msb/shared';
import type { AxiosError } from 'axios';
import { useForm, useFormState } from 'react-final-form';
import { MODAL_EMPTY_LOCALIZATION, MODAL_ERROR_LOCALIZATION } from '../../constants';
@@ -24,6 +24,8 @@ const useActions = ({ clearForm }: UseActionsProps) => {
const { setReport } = useReportContext();
const { showDialog } = useDialog();
const goBack = useRedirect('b');
const handleCheck = useCallback(async () => {
try {
setIsLoading(true);
@@ -53,6 +55,7 @@ const useActions = ({ clearForm }: UseActionsProps) => {
okButtonText: MODAL_EMPTY_LOCALIZATION.BUTTON,
hideCancelButton: true,
type: 'warning',
onClose: () => queryClient.invalidateQueries([QUERY_KEY_REPORT_INFINITE]),
});
break;
default:
@@ -62,14 +65,15 @@ const useActions = ({ clearForm }: UseActionsProps) => {
cancelButtonText: MODAL_ERROR_LOCALIZATION.CANCEL,
message: MODAL_ERROR_LOCALIZATION.DESCRIPTION,
onOk: handleCheck,
onCancel: goBack,
type: 'error',
});
}).catch(() => queryClient.invalidateQueries([QUERY_KEY_REPORT_INFINITE]));
break;
}
} finally {
setIsLoading(false);
}
}, [errors, values, clearForm, setReport, restart, initialValues, showDialog]);
}, [errors, values, clearForm, setReport, restart, initialValues, showDialog, goBack]);
return { handleCheck, isLoading };
};
@@ -74,7 +74,6 @@ const SearchPartnerContainer = ({ clearForm, ReportCard }: Props) => {
</S.PartnerBlockForm>
</>
)}
subscription={{ values: true }}
validate={validate}
onSubmit={emptyFn}
/>
@@ -16,7 +16,7 @@ const useValidate = () => {
return useCallback(
async (values: FormValues) => {
const { organizations, organizationsSearch, showInputField, handleInput } = values;
const { organizations, organizationsSearch, showInputField, handleInput, inn, ogrn } = values;
const clientAuthorities = userAuthorities?.data.clientAuthorities || {};
@@ -51,12 +51,26 @@ const useValidate = () => {
};
}
if ((organizationsSearch as string)?.length > MAX_LENGTH) {
if (organizationsSearch?.length && organizationsSearch.length > MAX_LENGTH) {
errors[FIELDS.ORGANIZATIONS_SEARCH] = {
error: ERROR_LOCALIZATION[FIELDS.ORGANIZATIONS_SEARCH].MAX_LENGTH,
};
}
// Для проверки, что именно кликнули в dropdown по организации
if (!showInputField && !inn) {
errors[FIELDS.INN] = {
error: 'required',
};
}
// Для проверки, что именно кликнули в dropdown по организации
if (!showInputField && !ogrn) {
errors[FIELDS.OGRN] = {
error: 'required',
};
}
if (showInputField && !handleInput) {
errors[FIELDS.HANDLE_INPUT] = {
error: ERROR_LOCALIZATION[FIELDS.HANDLE_INPUT].REQUIRED,