Pull request #2467: Release 1.10.0

Merge in MCB_FE/mcb-platform-monorepo from release-1.10.0 to master

* commit 'b0e7c45c453739767b7cbbc2bca5b4a87d4f6de7': (141 commits)
  chore(app): 1.10.0
  feat(TEAMMSBMOB-22759): изменение цвета текста
  feat(TEAMMSBMOB-22759): рекламная капания 5
  fix(TEAMMSBMOB-21989): Исправление отображения кнопки подписать
  fix(TEAMMSBMOB-21989): исправление заголовка
  feat(TEAMMSBMOB-22193): фикс бэк контроля кпп 3-го лица
  feat(TEAMMSBMOB-22812): фикс модального окна об успешном создании заявки нэп
  bugfix(TEAMMSBMOB-10267): убрал иконку валюты из чипсов + сброс фильтров после перехода со страницы "мои продукты"
  feat(TEAMMSBMOB-22193): фикс кпп и Кода там.органа
  fix(TEAMMSBMOB-21989): Исправление привилегии
  fix(TEAMMSBMOB-21989): Исправление логики поиска организации
  feat(TEAMMSBMOB-22193): откад логики очередй бэк контролей
  feat(TEAMMSBMOB-21989): Добавление заголовка к запросам
  fix(TEAMMSBMOB-22841): removed bad logic
  fix(TEAMMSBMOB-22836): change expired info
  fix(TEAMMSBMOB-22840): added option template
  bugfix(TEAMMSBMOB-10264): фикс багов по форме расширеных фильтров
  fix(TEAMMSBMOB-22838): фикс выбора улучшенного предложения
  fix(TEAMMSBMOB-22842): фикс бага года в платежах
  fix(TEAMMSBMOB-22609): Возврат UI выпадающего меню
  ...
This commit is contained in:
Егор Онуфрийчук
2026-01-30 18:09:05 +03:00
573 changed files with 14097 additions and 1636 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "@msb/ib-module",
"version": "1.9.2",
"version": "1.10.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@msb/ib-module",
"version": "1.9.2",
"version": "1.10.0",
"workspaces": [
"packages/*",
"services/*"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@msb/ib-module",
"version": "1.9.2",
"version": "1.10.0",
"files": [
"msb-host",
"msb-main-page",
@@ -10,7 +10,7 @@ const Weeks: React.FC = () => {
const { weekDays } = useContext(CalendarContext);
return (
<WeekContainer data-testid="calendarWeek" mx={{ XS: 'calendar.mobileHeaderPx', S: '-2px' }} px={{ XS: 'calendar.XS', S: '0' }}>
<WeekContainer data-testid="calendarWeek" mx={{ XS: 'calendar.mobileHeaderPx', S: '20px' }} px={{ XS: 'calendar.XS', S: '0' }}>
{weekDays.map(weekDay => (
<Text.P3
key={weekDay}
@@ -255,7 +255,6 @@ export const WeekContainer = styled.div<SpaceProps>(
display: 'grid',
gridTemplateColumns: 'repeat(7,1fr)',
justifyContent: 'start',
width: '250px',
marginBottom: calendarM,
userSelect: 'none',
borderColor: 'control.secondary.grey.bg',
@@ -1,6 +1,6 @@
import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { Theme } from '@emotion/react';
import { BottomSheet, PopupContainer } from '@fractal-ui/overlays';
import { BottomSheet, Drawer, PopupContainer } from '@fractal-ui/overlays';
import { BreakPoint, Responsive, useBreakpoints, Wrapper } from '@fractal-ui/styling';
import type { StyledComponentProps } from '@fractal-ui/styling';
import Dropdown from '../../dropdown';
@@ -83,6 +83,7 @@ const withOptions = <T extends BaseWithOptionsProps>(WrappedComponent: React.For
onBeforeClose,
onBeforeOpen,
notFoundButtonVariant = 'primary',
showDrawerForBreakPoint = false,
...rest
},
ref
@@ -96,7 +97,7 @@ const withOptions = <T extends BaseWithOptionsProps>(WrappedComponent: React.For
const [isOpened, setIsOpened] = useState<boolean>(false);
const [searchInputValue, setSearchInputValue] = useState<number | string | undefined>();
const { XS } = useBreakpoints();
const { XS, S } = useBreakpoints();
const isShowButtonPositionTop = Boolean(onInnerButtonClick) && innerButtonText && innerButtonPosition === 'top';
@@ -341,7 +342,7 @@ const withOptions = <T extends BaseWithOptionsProps>(WrappedComponent: React.For
innerButtonIcon={innerButtonIcon}
innerButtonPosition={innerButtonPosition}
innerButtonText={innerButtonText}
isEmbedded={XS}
isEmbedded={showDrawerForBreakPoint ? S : XS}
isInnerButtonFixed={isInnerButtonFixed}
notFoundButtonText={notFoundButtonText}
notFoundButtonVariant={notFoundButtonVariant}
@@ -407,17 +408,35 @@ const withOptions = <T extends BaseWithOptionsProps>(WrappedComponent: React.For
<WrappedComponent {...(wrappedProps as T)} ref={mergeRefs(ref, inputRef)} />
<Responsive>
<BreakPoint>
<PopupContainer
closeOnEscape
anchorEl={containerRef}
isOpen={isOpened}
onAfterClose={onAfterCloseDropdown}
onBeforeClose={onBeforeClose}
onBeforeOpen={onBeforeOpen}
onClose={handleClose}
>
{dropdown}
</PopupContainer>
{showDrawerForBreakPoint ? (
<Drawer
alwaysMaxHeight={alwaysMaxHeight}
// bottomSheetRef={bottomSheetRef}
// disableFooterPadding={isShowButtonPositionBottom}
footer={footer}
header={header || rest.label}
isOpen={isOpened}
unscrollableContent={unscrollableContent}
onAfterClose={onAfterCloseDropdown}
onBeforeClose={onBeforeClose}
onBeforeOpen={onBeforeOpen}
onClose={handleClose}
>
{dropdown}
</Drawer>
) : (
<PopupContainer
closeOnEscape
anchorEl={containerRef}
isOpen={isOpened}
onAfterClose={onAfterCloseDropdown}
onBeforeClose={onBeforeClose}
onBeforeOpen={onBeforeOpen}
onClose={handleClose}
>
{dropdown}
</PopupContainer>
)}
</BreakPoint>
<BreakPoint at="XS">
<BottomSheet
@@ -160,6 +160,13 @@ export interface WithOptionsProps<T = Record<string, unknown>>
* @default false
*/
hasFocusOnBottomSheetSearch?: boolean;
/**
* Параметр отвечающий за то, какой вид выбора опций будет у селектора в дефолтном BreakPoint.
* Необходим, т.к. сетка брекпоинтов фрактала отличается от наших размеров.
*
* @default false
*/
showDrawerForBreakPoint?: boolean;
}
/** Контекст хока withOptions. */
@@ -8,6 +8,8 @@ export const getBusinessCardsGetPage = async (
): Promise<BusinessCardsGetPageResponseDto> => {
const response = await network.client.post<BusinessCardsGetPageResponseDto>(BUSINESS_CARDS_GET_PAGE_ENDPOINT, data, {
headers: { Organizationid: organizationId },
// На eco-test2 запрос проходит почни минуту (49.88с)
timeout: 60_000,
});
return response.data;
+2
View File
@@ -40,12 +40,14 @@ enum FEATURE_TOGGLE_NAMES {
LOCAL_QUALIFIED_SIGN_IBMSB = 'localQualifiedSignIBMSB',
IMPORT_PAYMENTS = 'ImportPaymentsIBMSB',
FEA_OPERATION_DAY = 'vedOperationDayInformIBMSB',
CURRENCY_TRANSFER_LIST = 'currencyTransferIBMSB',
MAIN_PAGE_DYNAMIC_BANNERS = 'mainpageDinamicBannersIBMSB',
SENTRY = 'monitoringIBMSB',
YM_USER_ID = 'metricaSetIdIBMSB',
STAR_RATING = 'starRatingIBMSB',
DEPOSIT_FORM = 'depositFormIBMSB',
DEPOSIT_ADVANCED_FILTER = 'depositFilterIBMSB',
VED_CALLBACK_IMSB = 'vedCallbackIBMSB',
}
export { FEATURE_TOGGLE_NAMES, type FeatureToggleData, type FeatureToggleItem, type FeatureToggleResponse };
@@ -240,6 +240,8 @@ interface OtherInfo {
filialAbsCode?: string;
/** Адрес филиала в АБС Ф1. */
filialAbsAddress?: string;
/** Наименование подразделения. */
filialName?: string;
}
interface InquiryRequestDto {
@@ -0,0 +1,12 @@
import { network } from '@msb/http';
import { PAYMENTS_CLIENT_I18N_RU_RU } from '../../endpoints';
const fetchPaymentsI18n = async (): Promise<Record<string, string>> => {
const response = await network.client.get<Record<string, string>>(PAYMENTS_CLIENT_I18N_RU_RU);
return response.data;
};
export { fetchPaymentsI18n };
@@ -0,0 +1,2 @@
export * from './fetchPaymentsI18n';
export * from './queryKeys';
@@ -0,0 +1,5 @@
const QUERY_KEY_PAYMENTS_I18N = 'payments-i18n';
export { QUERY_KEY_PAYMENTS_I18N };
@@ -17,3 +17,4 @@ export * from './fetchEditTemplate';
export * from './sendPayments';
export * from './fetchSignatories';
export * from './fetchSignatures';
export * from './i18n';
@@ -0,0 +1,5 @@
const PAYMENTS_CLIENT_I18N_RU_RU = '/ruble-payment-client/i18n/ru_Ru' as const;
export { PAYMENTS_CLIENT_I18N_RU_RU };
@@ -0,0 +1,3 @@
export * from './constants';
@@ -22,3 +22,4 @@ export * from './fetchEditTemplate';
export * from './sendPayments';
export * from './fetchSignatories';
export * from './fetchSignatures';
export * from './i18n';
@@ -0,0 +1,3 @@
export * from './usePaymentsI18n';
@@ -0,0 +1,22 @@
import { useQuery } from '@msb/http';
import { fetchPaymentsI18n, QUERY_KEY_PAYMENTS_I18N } from '../../api';
const usePaymentsI18n = () => {
const { data, error, isLoading, refetch } = useQuery<Record<string, string>, Error | undefined>({
queryKey: [QUERY_KEY_PAYMENTS_I18N],
queryFn: fetchPaymentsI18n,
refetchOnMount: true,
staleTime: 0,
});
return {
paymentsI18nData: data,
paymentsI18nError: error,
isPaymentsI18nLoading: isLoading,
refetchPaymentsI18n: refetch,
};
};
export { usePaymentsI18n };
@@ -11,3 +11,4 @@ export * from './paymentImport';
export * from './fileImport';
export * from './fetchSignatores';
export * from './fetchSignatures';
export * from './i18n';
@@ -1,4 +1,5 @@
import { type FilterResponseDto, GET_INVESTMENT_DOCUMENT_FILTERS, network } from '@msb/http';
import type { FilterByStatusResponseDto, FilterResponseDto } from '@msb/http';
import { GET_INVESTMENT_DOCUMENT_FILTERS, network, POST_INVESTMENT_DOCUMENT_CATEGORIES_BY_FILTER } from '@msb/http';
const fetchFilters = async (): Promise<FilterResponseDto> => {
const response = await network.client.get<FilterResponseDto>(GET_INVESTMENT_DOCUMENT_FILTERS);
@@ -6,4 +7,10 @@ const fetchFilters = async (): Promise<FilterResponseDto> => {
return response.data;
};
export { fetchFilters };
const fetchFiltersByStatus = async (): Promise<FilterByStatusResponseDto> => {
const response = await network.client.post<FilterByStatusResponseDto>(POST_INVESTMENT_DOCUMENT_CATEGORIES_BY_FILTER);
return response.data;
};
export { fetchFilters, fetchFiltersByStatus };
@@ -1,3 +1,5 @@
const QUERY_KEY_FILTERS = 'DOCUMENTS_FILTERS';
export { QUERY_KEY_FILTERS };
const QUERY_KEY_FILTERS_BY_STATUS = 'INVESTMENT_DOCUMENT_FILTERS_BY_STATUS';
export { QUERY_KEY_FILTERS, QUERY_KEY_FILTERS_BY_STATUS };
@@ -0,0 +1,12 @@
const statusFilters = [
{ label: 'Черновик', statuses: ['CAT_NEW', 'CAT_DRAFT', 'CAT_QUOTED'], code: 'DRAFT' },
{ label: 'Требует подтверждения', statuses: ['CAT_NEW', 'CAT_QUOTED'], code: 'QUOTED' },
{ label: 'Закрыт досрочно', statuses: ['CAT_WITHDRAWN'], code: 'WITHDRAWN' },
{ label: 'Закрыт', statuses: ['CAT_EXECUTED'], code: 'EXECUTED' },
{ label: 'Нарушен', statuses: ['CAT_VIOLATED'], code: 'VIOLATED' },
{ label: 'Отменён', statuses: ['CAT_CANCELED', 'CAT_VISA_REJECTED'], code: 'CANCELED' },
{ label: 'Открыт', statuses: ['CAT_DONE'], code: 'ACTIVE' },
{ label: 'В обработке', statuses: ['CAT_DELIVERED', 'CAT_WAITING_FOR_VISA', 'CAT_CONFIRMED'], code: 'CONFIRMED' },
];
export { statusFilters };
@@ -0,0 +1 @@
export * from './constants';
@@ -1,3 +1,5 @@
const GET_INVESTMENT_DOCUMENT_FILTERS = '/treasury-deals-client/investment-document/filters' as const;
export { GET_INVESTMENT_DOCUMENT_FILTERS };
const POST_INVESTMENT_DOCUMENT_CATEGORIES_BY_FILTER = '/treasury-deals-client/investment-document/categories-by-filter' as const;
export { GET_INVESTMENT_DOCUMENT_FILTERS, POST_INVESTMENT_DOCUMENT_CATEGORIES_BY_FILTER };
@@ -15,4 +15,13 @@ interface FilterResponseDto {
traceId: string;
}
export type { FilterResponseDto, DataFilters, ErrorResponse };
interface FilterByStatusDto {
code: string;
title: string;
}
interface FilterByStatusResponseDto {
data: FilterByStatusDto[];
}
export type { FilterResponseDto, DataFilters, ErrorResponse, FilterByStatusResponseDto };
@@ -26,7 +26,7 @@ interface TreasuryDealsCurrenciesResponseDto {
interface CurrenciesRequestBody {
data: {
clientWebDealingId?: string;
clientWebDealingId: string;
};
}
@@ -1,3 +1,4 @@
export * from './endpoints';
export * from './model';
export * from './api';
export * from './constants';
@@ -1,5 +1,5 @@
export * from './useProducts';
export { useFetchedFilters } from './useFilters';
export { useFetchedFilters, useFiltersByStatus } from './useFilters';
export { useUserInfoUnion } from './useUserInfoUnion';
export { useProductsMainPage } from './useProductsMainPage';
export { useValidate } from './useValidate';
@@ -9,4 +9,4 @@ export { useWorkCalendar } from './workCalendar';
export * from './showcase';
export * from './calculator';
export { useRubAccountNew } from './useRubAccountNew';
export { useAccountById } from './useAccountById';
export { useAccountById } from './useAccountById';
@@ -1,5 +1,5 @@
import { useQuery, type FilterResponseDto } from '@msb/http';
import { fetchFilters, QUERY_KEY_FILTERS } from '../../api';
import type { FilterByStatusResponseDto, FilterResponseDto } from '@msb/http';
import { useQuery, QUERY_KEY_FILTERS_BY_STATUS, fetchFilters, QUERY_KEY_FILTERS, fetchFiltersByStatus } from '@msb/http';
const useFetchedFilters = () => {
const { data: fetchedFilters, isLoading: isLoadingFilters } = useQuery<FilterResponseDto, Error | undefined>({
@@ -10,4 +10,13 @@ const useFetchedFilters = () => {
return { fetchedFilters, isLoadingFilters };
};
export { useFetchedFilters };
const useFiltersByStatus = () => {
const { data: filtersByStatus, isLoading: isLoadingFiltersByStatus } = useQuery<FilterByStatusResponseDto, Error | undefined>({
queryKey: [QUERY_KEY_FILTERS_BY_STATUS],
queryFn: fetchFiltersByStatus,
});
return { filtersByStatus, isLoadingFiltersByStatus };
};
export { useFetchedFilters, useFiltersByStatus };
@@ -3,7 +3,14 @@ type TreasuryDealsFilter = 'fastFilter' | 'organization' | 'product';
interface AdvancedFilterState {
amountFrom: string;
amountTo: string;
termFrom: string;
termTo: string;
dateBeginStart: string;
dateBeginEnd: string;
dateEndStart: string;
dateEndEnd: string;
currency: string[];
status: string[];
}
type TreasuryDealsFilters = Record<TreasuryDealsFilter, string[]>;
@@ -1,6 +1,6 @@
import { useMemo, useState } from 'react';
import type { AdvancedFilterState, ProductsResponseDto } from '@msb/http';
import { QUERY_KEY_FETCH_PRODUCTS_SINGLE_TYPE, useInfiniteQuery } from '@msb/http';
import { QUERY_KEY_FETCH_PRODUCTS_SINGLE_TYPE, useInfiniteQuery, statusFilters } from '@msb/http';
import {
fetchProducts,
QUERY_KEY_FETCH_PRODUCTS,
@@ -54,6 +54,35 @@ const useProducts = (filters: TreasuryDealsFilters, advancedFilters?: AdvancedFi
});
}
return {
params: {
filter,
paging: { offset: 0, limit: LIMIT },
},
};
}, [filters, fetchedFilters]);
const advancedResultFilters = useMemo(() => {
const filter: Record<string, any> = {};
if (filters.organization.length > 0) {
filter.clientId = { in: filters.organization };
}
if (filters.product.length > 0) {
filter.docType = { in: filters.product };
}
if (filters.fastFilter.length > 0 && fetchedFilters) {
filters.fastFilter.forEach(code => {
const fetched = fetchedFilters.data.find(f => f.code === code);
if (fetched) {
Object.assign(filter, fetched.filter);
}
});
}
if (advancedFilters) {
if (advancedFilters.amountFrom) {
filter.amount = { ge: advancedFilters.amountFrom };
@@ -63,9 +92,39 @@ const useProducts = (filters: TreasuryDealsFilters, advancedFilters?: AdvancedFi
filter.amount = { ...filter.amount, le: advancedFilters.amountTo };
}
if (advancedFilters.termFrom) {
filter.period = { ge: Number(advancedFilters.termFrom) };
}
if (advancedFilters.termTo) {
filter.period = { ...filter.period, le: Number(advancedFilters.termTo) };
}
if (advancedFilters?.currency?.length > 0) {
filter.currency = { in: advancedFilters.currency };
}
if (advancedFilters?.status?.length > 0) {
const statuses = advancedFilters.status.flatMap(code => statusFilters.find(el => el.code === code)?.statuses || []);
filter.statusCategory = { in: statuses };
}
if (advancedFilters?.dateBeginStart) {
filter.dateBegin = { ge: advancedFilters.dateBeginStart };
}
if (advancedFilters?.dateBeginEnd) {
filter.dateBegin = { ...filter.dateBegin, le: advancedFilters.dateBeginEnd };
}
if (advancedFilters?.dateEndStart) {
filter.dateEnd = { ge: advancedFilters.dateEndStart };
}
if (advancedFilters?.dateEndEnd) {
filter.dateEnd = { ...filter.dateEnd, le: advancedFilters.dateEndEnd };
}
}
return {
@@ -74,9 +133,10 @@ const useProducts = (filters: TreasuryDealsFilters, advancedFilters?: AdvancedFi
paging: { offset: 0, limit: LIMIT },
},
};
}, [filters, fetchedFilters, advancedFilters]);
}, [advancedFilters, fetchedFilters, filters]);
const filtersKey = useMemo(() => JSON.stringify({ ...filters, ...(advancedFilters ?? {}) }), [advancedFilters, filters]);
const filtersKey = useMemo(() => JSON.stringify({ ...filters }), [filters]);
const advancedFiltersKey = useMemo(() => JSON.stringify({ ...filters, ...(advancedFilters ?? {}) }), [advancedFilters, filters]);
// =========================== ALL products data fetching and parsing ========================= //
const {
@@ -86,13 +146,13 @@ const useProducts = (filters: TreasuryDealsFilters, advancedFilters?: AdvancedFi
hasNextPage: hasAllProductsDataNextPage,
fetchNextPage: allProductsDataFetchNextPage,
} = useInfiniteQuery<ProductsResponseDto, Error | undefined>(
[QUERY_KEY_FETCH_PRODUCTS, filtersKey],
[QUERY_KEY_FETCH_PRODUCTS, advancedFiltersKey],
({
pageParam = {
offset: 0,
limit: LIMIT,
},
}) => fetchProducts({ ...resultFilters, params: { ...resultFilters.params, paging: pageParam } }),
}) => fetchProducts({ ...advancedResultFilters, params: { ...advancedResultFilters.params, paging: pageParam } }),
{
cacheTime: 0,
getNextPageParam: getNextPageParamCommon,
@@ -68,7 +68,7 @@ const BUSINESS_CARDS_ACCOUNTS_MOCK = {
},
{
clientId: 'c060253d-5195-4ee4-8380-37f3f9b1f4db',
accountId: '7i98i769-fjjf-9ij1-g7f6-15jf42g473gf',
accountId: '7a98a769-faaf-9ad1-a7a6-15af42a473af',
number: '40702810670000005678',
type: 'Расчетный',
typeContrAccount: '1',
@@ -77,6 +77,127 @@ const BUSINESS_CARDS_ACCOUNTS_MOCK = {
serviceBranchCode: '000',
serviceOfficeCode: '001-001-099-009',
},
{
clientId: 'c060253d-5195-4ee4-8380-37f3f9b1f4db',
accountId: '8b09b87a-abbb-0be2-b8b7-26ba53b584b0',
number: '40702810770000006789',
type: 'Расчетный',
typeContrAccount: '2',
currencyCode: 'RUB',
restAmount: 1_200_000.50,
serviceBranchCode: '000',
serviceOfficeCode: '001-001-099-009',
},
{
clientId: 'c060253d-5195-4ee4-8380-37f3f9b1f4db',
accountId: '9c1ac98b-bccc-1cf3-c9c8-37cb64c695c1',
number: '40702810870000007890',
type: 'Расчетный',
typeContrAccount: '1',
currencyCode: 'RUB',
restAmount: 4_500_000.75,
serviceBranchCode: '000',
serviceOfficeCode: '001-001-099-009',
},
{
clientId: 'c060253d-5195-4ee4-8380-37f3f9b1f4db',
accountId: 'ad2bda9c-cddd-2da4-dada-48dc75d7a6d2',
number: '40702810970000008901',
type: 'Расчетный',
typeContrAccount: '2',
currencyCode: 'RUB',
restAmount: 850_000.25,
serviceBranchCode: '000',
serviceOfficeCode: '001-001-099-009',
},
{
clientId: 'c060253d-5195-4ee4-8380-37f3f9b1f4db',
accountId: 'be3cebad-deee-3eb5-ebeb-59ed86e8b7e3',
number: '40702811070000009012',
type: 'Расчетный',
typeContrAccount: '1',
currencyCode: 'RUB',
restAmount: 2_750_000.00,
serviceBranchCode: '000',
serviceOfficeCode: '001-001-099-009',
},
{
clientId: 'c060253d-5195-4ee4-8380-37f3f9b1f4db',
accountId: 'cf4dfcbe-efff-4fc6-fcfc-6afe97f9c8f4',
number: '40702811170000000123',
type: 'Расчетный',
typeContrAccount: '2',
currencyCode: 'RUB',
restAmount: 1_950_000.50,
serviceBranchCode: '000',
serviceOfficeCode: '001-001-099-009',
},
{
clientId: 'c060253d-5195-4ee4-8380-37f3f9b1f4db',
accountId: 'd05e0dcf-f000-50d7-0d0d-7b0fa80ad905',
number: '40702811270000001234',
type: 'Расчетный',
typeContrAccount: '1',
currencyCode: 'RUB',
restAmount: 5_200_000.75,
serviceBranchCode: '000',
serviceOfficeCode: '001-001-099-009',
},
{
clientId: 'c060253d-5195-4ee4-8380-37f3f9b1f4db',
accountId: 'e16f1ed0-0111-61e8-1e1e-8c10b91bea16',
number: '40702811370000002345',
type: 'Расчетный',
typeContrAccount: '2',
currencyCode: 'RUB',
restAmount: 650_000.25,
serviceBranchCode: '000',
serviceOfficeCode: '001-001-099-009',
},
{
clientId: 'c060253d-5195-4ee4-8380-37f3f9b1f4db',
accountId: 'f27a2fe1-1222-72f9-2f2f-9d21ca2cfb27',
number: '40702811470000003456',
type: 'Расчетный',
typeContrAccount: '1',
currencyCode: 'RUB',
restAmount: 3_850_000.00,
serviceBranchCode: '000',
serviceOfficeCode: '001-001-099-009',
},
{
clientId: 'c060253d-5195-4ee4-8380-37f3f9b1f4db',
accountId: '038b3af2-2333-83a0-3a3a-ae32db3d0c38',
number: '40702811570000004567',
type: 'Расчетный',
typeContrAccount: '2',
currencyCode: 'RUB',
restAmount: 1_350_000.50,
serviceBranchCode: '000',
serviceOfficeCode: '001-001-099-009',
},
{
clientId: 'c060253d-5195-4ee4-8380-37f3f9b1f4db',
accountId: '149c4ba3-3444-94b1-4b4b-bf43ec4e1d49',
number: '40702811670000005678',
type: 'Расчетный',
typeContrAccount: '1',
currencyCode: 'RUB',
restAmount: 6_100_000.75,
serviceBranchCode: '000',
serviceOfficeCode: '001-001-099-009',
},
{
clientId: 'c060253d-5195-4ee4-8380-37f3f9b1f4db',
accountId: '25ad5cb4-4555-a5c2-5c5c-c054fd5f2e5a',
number: '40702811770000006789',
type: 'Расчетный',
typeContrAccount: '2',
currencyCode: 'RUB',
restAmount: 950_000.25,
serviceBranchCode: '000',
serviceOfficeCode: '001-001-099-009',
},
],
};
@@ -9,8 +9,17 @@ const isAccessTest = false;
const getBusinessCardByIdHandler = rest.get<never, never, BusinessCardsGetByIdResponseDto>(
BUSINESS_CARDS_GET_BY_ID_ENDPOINT,
async (req, res, ctx) => {
const clientId = req.url.searchParams.get('clientId');
let result = BUSINESS_CARDS_GET_BY_ID_MOCK;
if (!clientId) {
// Бэк фактически возвращает без поля "data".
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return res(ctx.delay(1000), ctx.status(400), ctx.json({}));
}
if (isIntegrationError || isAccessTest) {
// Бэк фактически возвращает без поля "data".
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -20,9 +29,15 @@ const getBusinessCardByIdHandler = rest.get<never, never, BusinessCardsGetByIdRe
if (result?.error) {
result.error.code = isAccessTest ? '403' : '0';
}
return res(
ctx.delay(1000),
ctx.status(403),
ctx.json({ error: { code: '403', message: 'Ошибка' } } as BusinessCardsGetByIdResponseDto)
);
}
return res(ctx.delay(1000), ctx.status(200), ctx.json(result));
return res(ctx.delay(1000), ctx.status(isAccessTest ? 403 : 200), ctx.json(result));
}
);
@@ -297,7 +297,7 @@ const BUSINESS_CARDS_GET_BY_ID_MOCK: BusinessCardsGetByIdResponseDto = {
const BUSINESS_CARDS_ERROR = {
error: {
code: '0',
code: '403',
description: 'description',
logLevel: 'ERROR',
message: 'message',
+8
View File
@@ -74,6 +74,14 @@ const FEATURE_TOGGLE_MOCK: FeatureToggleResponse = {
featureCode: FEATURE_TOGGLE_NAMES.DEPOSIT_ADVANCED_FILTER,
isEnabled: true,
},
{
featureCode: FEATURE_TOGGLE_NAMES.VED_CALLBACK_IMSB,
isEnabled: true,
},
{
featureCode: FEATURE_TOGGLE_NAMES.CURRENCY_TRANSFER_LIST,
isEnabled: true,
},
{
featureCode: FEATURE_TOGGLE_NAMES.STAR_RATING,
isEnabled: true,
+2
View File
@@ -24,6 +24,7 @@ import {
getPaymentCopyHandlers,
getPageWithIndicatorsHandlers,
getSuggestionPageHandlers,
paymentsI18nHandlers,
} from './payments-client';
import { fileImportHandlers } from './payments-client/fileImport';
import { paymentImportHandlers } from './payments-client/paymentImport';
@@ -119,6 +120,7 @@ const handlers = [
...userNotificationsHandler,
...getPageWithIndicatorsHandlers,
...getSuggestionPageHandlers,
...paymentsI18nHandlers,
...editTreasuryDealsHandlers,
...retryTreasuryDealsHandlers,
...fetchTreasuryDealsAccountsMnoHandlers,
+1
View File
@@ -800,6 +800,7 @@ const GET_INQUIRY_MOCK: InquiryRequestDto = {
filialAbsCode: '099/1009',
filialAbsAddress: 'ул Тольятти, д. 33А',
receptionEmailAgreement: false,
filialName: 'Ф-Л БАНКА ГПБ (АО) "ЗАПАДНО-СИБИРСКИЙ"',
},
inquiryVersion: 2,
};
@@ -0,0 +1,11 @@
import { PAYMENTS_CLIENT_I18N_RU_RU } from '@msb/http';
import { rest } from 'msw';
import { PAYMENTS_CLIENT_I18N_MOCK } from './mocks';
const paymentsI18nHandler = rest.get<Record<string, string>>(PAYMENTS_CLIENT_I18N_RU_RU, (_, res, ctx) =>
res(ctx.delay(100), ctx.json(PAYMENTS_CLIENT_I18N_MOCK))
);
export { paymentsI18nHandler };
@@ -0,0 +1,10 @@
import { paymentsI18nHandler } from './i18n';
export * from './mocks';
export * from './i18n';
const paymentsI18nHandlers = [paymentsI18nHandler];
export { paymentsI18nHandlers };
@@ -0,0 +1,16 @@
const PAYMENTS_CLIENT_I18N_MOCK: Record<string, string> = {
'cpm.field.empty': 'Реквизит «{fieldName}» является обязательным для заполнения, не может быть пустым',
'cpm.field.string.empty': 'Поле «{fieldName}» обязательно для заполнения',
'cpm.field.paymentAmount.paymentAmount.positive': 'Сумма платежа не может быть нулевой',
'cpm.field.paymentAmount.vatCalculationMethod.not.selected': 'Не выбран способ расчета НДС',
'cpm.field.paymentInfo.paymentPurpose.existNds': 'В назначении платежа не обнаружено упоминание об НДС',
'cpm.field.payerDetails.innKio.length': 'Длина ИНН должна быть 10 или 12 символов, КИО - 5 символов',
'field.empty': 'Реквизит «{field}» является обязательным для заполнения и не может быть пустым.',
'paymentAmount.vatCalculationMethod': 'Способ расчета НДС',
'paymentInfo.paymentPurpose': 'Назначение платежа',
'sbp.bankClient.merchantAddress': 'Адрес Точки продаж',
};
export { PAYMENTS_CLIENT_I18N_MOCK };
+1
View File
@@ -12,4 +12,5 @@ export * from './getPaymentCopy';
export * from './paymentImport';
export * from './fileImport';
export * from './sendPayments';
export * from './i18n';
export * from './types';
+1 -1
View File
@@ -1163,7 +1163,7 @@ const STATEMENTS_REQUESTS_HISTORY_MOCK: StatementRequest[] = [
const STATEMENT_RELEVANCE_STATUS_MOCK = {
data: {
status: STATEMENT_RELEVANCE_STATUS.ACTUAL,
status: STATEMENT_RELEVANCE_STATUS.OUTDATED,
},
};
@@ -1,9 +1,15 @@
import { type FilterResponseDto, GET_INVESTMENT_DOCUMENT_FILTERS } from '@msb/http/treasury-deals-client/endpoints';
import { type FilterResponseDto, GET_INVESTMENT_DOCUMENT_FILTERS, POST_INVESTMENT_DOCUMENT_CATEGORIES_BY_FILTER } from '@msb/http';
import type { FilterByStatusResponseDto } from '@msb/http/treasury-deals-client/endpoints';
import { rest } from 'msw';
import { FILTERS_MOCK } from './mocks';
import { FILTER_BY_STATUS_MOCK, FILTERS_MOCK } from './mocks';
const GET_FILTERS_HANDLER = rest.get<never, never, FilterResponseDto>(GET_INVESTMENT_DOCUMENT_FILTERS, (_, res, ctx) =>
res(ctx.json(FILTERS_MOCK))
);
export { GET_FILTERS_HANDLER };
const GET_FILTERS_BY_STATUS_HANDLER = rest.post<never, never, FilterByStatusResponseDto>(
POST_INVESTMENT_DOCUMENT_CATEGORIES_BY_FILTER,
(_, res, ctx) => res(ctx.json(FILTER_BY_STATUS_MOCK))
);
export { GET_FILTERS_HANDLER, GET_FILTERS_BY_STATUS_HANDLER };
@@ -1,5 +1,5 @@
import { GET_FILTERS_HANDLER } from './filters';
import { GET_FILTERS_HANDLER, GET_FILTERS_BY_STATUS_HANDLER } from './filters';
const filterHandlers = [GET_FILTERS_HANDLER];
const filterHandlers = [GET_FILTERS_HANDLER, GET_FILTERS_BY_STATUS_HANDLER];
export { filterHandlers };
@@ -1,5 +1,5 @@
/* eslint-disable sonarjs/no-duplicate-string */
import type { FilterResponseDto } from '@msb/http/treasury-deals-client/endpoints';
import type { FilterByStatusResponseDto, FilterResponseDto } from '@msb/http/treasury-deals-client/endpoints';
const FILTERS_MOCK: FilterResponseDto = {
data: [
@@ -112,4 +112,53 @@ const FILTERS_MOCK: FilterResponseDto = {
traceId: 'string',
};
export { FILTERS_MOCK };
const FILTER_BY_STATUS_MOCK: FilterByStatusResponseDto = {
data: [
{
code: 'CAT_DRAFT',
title: 'Черновик',
},
{
code: 'CAT_NEW',
title: 'Ожидание подтверждения',
},
{
code: 'CAT_WAITING_FOR_VISA',
title: 'Ожидает согласования спец. депозитария',
},
{
code: 'CAT_VISA_REJECTED',
title: 'Отклонена спец. депозитарием',
},
{
code: 'CAT_DELIVERED',
title: 'Подтверждено Клиентом',
},
{
code: 'CAT_CANCELED',
title: 'Отменена',
},
{
code: 'CAT_CONFIRMED',
title: 'Сделка подтверждена',
},
{
code: 'CAT_DONE',
title: 'Заключена',
},
{
code: 'CAT_WITHDRAWN',
title: 'Уведомление на досрочное истребование',
},
{
code: 'CAT_VIOLATED',
title: 'Нарушена',
},
{
code: 'CAT_EXECUTED',
title: 'Завершена',
},
],
};
export { FILTERS_MOCK, FILTER_BY_STATUS_MOCK };
@@ -31,14 +31,9 @@ const USER_AUTHORITIES_MOCK: AuthoritiesResponseDto = {
'SWITCHING_TO_AB_PAYMENTS',
'SWITCHING_TO_LK_UVED',
'TREASURY_AGREEMENT_REQUEST_DEPOSIT_CREATE',
'INQUIRY_REQUEST.VIEW',
'INQUIRY.COPY_DOC',
'TREASURY_COMMON',
'TREASURY_GENERAL_AGREEMENTS_COMMON',
'INQUIRY_REQUEST.CREATE',
'INQUIRY_REQUEST.UPDATE',
'INQUIRY_REQUEST.SIGN',
'INQUIRY.EXPORT_DOC',
'INQUIRY_REQUEST.CREATE_CLIENT_CLIENT',
'CLIENT_HOME_PAGE_ACCOUNT_VIEW',
'TREASURY_ACCOUNTS_COMMON',
'TREASURY_DEPOSIT_DEALS_COMMON',
@@ -56,6 +51,10 @@ const USER_AUTHORITIES_MOCK: AuthoritiesResponseDto = {
clientAuthorities: {
'3fa85f64-5717-4562-b3fc-2c963f66afa2': ['ECO_PARTNER_CHECK_CLIENT.VIEW_USE_SERVICE_PAGE'],
'64b3578f-eaa2-4367-90f2-98965b589262': [
'INQUIRY.CLIENT.LIST',
'PRODUCT_OFFER.CLIENT.CONSULTATION_REQUEST',
'INQUIRY.CLIENT.CREATE',
'ECO_PARTNER_CHECK_CLIENT.VIEW_MANAGE_SERVICE_PAGE',
'CURRENCY_TRANSFER_CLIENT_SCROLLER_VIEW',
'TAD.CLIENT.RQ_VIEW_SC',
'CONV.CLIENT.RQ_VIEW_SC',
@@ -76,7 +75,7 @@ const USER_AUTHORITIES_MOCK: AuthoritiesResponseDto = {
'LKMP.ACCESS.VIEW',
'LKMP.OFFER.VIEW',
'LKUVED.VIEW',
'INQUIRY_REQUEST.VIEW',
'INQUIRY.CLIENT.VIEW',
'LKUVED.ACCESS.VIEW',
'OFFER_ACCEPTANCE.VIEW',
'TREASURY_AGREEMENT_REQUEST_MNO_VIEW',
@@ -90,7 +89,7 @@ const USER_AUTHORITIES_MOCK: AuthoritiesResponseDto = {
'TREASURY_AGREEMENT_REQUEST_DU_MNO_VIEW',
'TREASURY_AGREEMENT_REQUEST_DU_CONVERSION_VIEW',
'DIGITAL_SIGNATURE_TOOL_VIEW',
'INQUIRY_REQUEST.CREATE',
'INQUIRY_REQUEST.CREATE_CLIENT_CLIENT',
'VRKO_CONV',
'VRKO_VP',
'RRKO_PPTS',
@@ -110,15 +109,14 @@ const USER_AUTHORITIES_MOCK: AuthoritiesResponseDto = {
'TMG.ACCESS.VIEW',
'TMG.OFFER.VIEW',
'CONSTITUTION_REGISTRATION_CLIENT.VIEW_LIST',
'ECO_PARTNER_CHECK_CLIENT.VIEW_MANAGE_SERVICE_PAGE',
'ECO_PARTNER_CHECK_CLIENT.VIEW_USE_SERVICE_PAGE',
'ECO_PARTNER_CHECK_CLIENT.VIEW_CONNECTION_SERVICE_PAGE',
'ECO_PARTNER_CHECK_CLIENT.SUBSCRIPTION_SIGN',
'SBP.CONTRACT.REQUEST.VIEW.LIST',
'INQUIRY_REQUEST.CREATE',
'INQUIRY.CLIENT.CREATE',
'CLIENT_HOME_PAGE_ACCOUNT_VIEW',
'INQUIRY_REQUEST.SIGN',
'INQUIRY.EXPORT_DOC',
'INQUIRY.CLIENT.SIGN',
'INQUIRY.CLIENT.EXPORT_DOC',
'RUBLE_PAYMENT_ORDER.SEND',
'RUBLE_PAYMENT_ORDER.SIGN',
'ACCOUNT_OPENING_REQUEST.CLIENT.CONTRACT_CREATE',
@@ -155,7 +153,7 @@ const USER_AUTHORITIES_MOCK: AuthoritiesResponseDto = {
'LKMP.ACCESS.VIEW',
'LKMP.OFFER.VIEW',
'LKUVED.VIEW',
'INQUIRY_REQUEST.VIEW',
'INQUIRY.CLIENT.VIEW',
'LKUVED.ACCESS.VIEW',
'OFFER_ACCEPTANCE.VIEW',
'TREASURY_AGREEMENT_REQUEST_MNO_VIEW',
@@ -169,9 +167,9 @@ const USER_AUTHORITIES_MOCK: AuthoritiesResponseDto = {
'TREASURY_AGREEMENT_REQUEST_DU_MNO_VIEW',
'TREASURY_AGREEMENT_REQUEST_DU_CONVERSION_VIEW',
'DIGITAL_SIGNATURE_TOOL_VIEW',
'INQUIRY_REQUEST.CREATE',
'INQUIRY_REQUEST.SIGN',
'INQUIRY.EXPORT_DOC',
'INQUIRY.CLIENT.CREATE',
'INQUIRY.CLIENT.SIGN',
'INQUIRY.CLIENT.EXPORT_DOC',
'VRKO_CONV',
'VRKO_VP',
'RRKO_PPTS',
@@ -193,7 +191,7 @@ const USER_AUTHORITIES_MOCK: AuthoritiesResponseDto = {
'CONSTITUTION_REGISTRATION_CLIENT.VIEW_LIST',
'ECO_PARTNER_CHECK_CLIENT.VIEW_MANAGE_SERVICE_PAGE',
'SBP.CONTRACT.REQUEST.VIEW.LIST',
'INQUIRY_REQUEST.CREATE',
'INQUIRY.CLIENT.CREATE',
'CLIENT_HOME_PAGE_ACCOUNT_VIEW',
'BC.CLIENT.LIST.VIEW',
'BANK_CLIENT_GET_USER_ORG',
@@ -205,9 +203,9 @@ const USER_AUTHORITIES_MOCK: AuthoritiesResponseDto = {
'LKMP.ACCESS.VIEW_LIST',
'BANK_CLIENT_GET_USER_ORG',
'TREASURY_MNO_DEALS_COMMON',
'INQUIRY_REQUEST.CREATE',
'INQUIRY_REQUEST.SIGN',
'INQUIRY.EXPORT_DOC',
'INQUIRY.CLIENT.CREATE',
'INQUIRY.CLIENT.SIGN',
'INQUIRY.CLIENT.EXPORT_DOC',
'CLIENT_HOME_PAGE_ACCOUNT_VIEW',
'CLIENT_VIEW_ACCOUNT_DETAILS',
'RUBLE_PAYMENT_ORDER.CREATE',
@@ -227,7 +225,7 @@ const USER_AUTHORITIES_MOCK: AuthoritiesResponseDto = {
'CLIENT_HOME_PAGE_ACCOUNT_VIEW',
'BANK_CLIENT_GET_USER_ORG',
'TREASURY_MNO_DEALS_COMMON',
'INQUIRY_REQUEST.CREATE',
'INQUIRY.CLIENT.CREATE',
'BC.CLIENT.LIST.VIEW',
'BANK_CLIENT_GET_USER_ORG',
],
@@ -238,9 +236,9 @@ const USER_AUTHORITIES_MOCK: AuthoritiesResponseDto = {
'CLIENT_HOME_PAGE_ACCOUNT_VIEW',
'BANK_CLIENT_GET_USER_ORG',
'TREASURY_MNO_DEALS_COMMON',
'INQUIRY_REQUEST.CREATE',
'INQUIRY_REQUEST.SIGN',
'INQUIRY.EXPORT_DOC',
'INQUIRY.CLIENT.CREATE',
'INQUIRY.CLIENT.SIGN',
'INQUIRY.CLIENT.EXPORT_DOC',
'BC.CLIENT.LIST.VIEW',
'BANK_CLIENT_GET_USER_ORG',
],
@@ -251,9 +249,9 @@ const USER_AUTHORITIES_MOCK: AuthoritiesResponseDto = {
'CLIENT_HOME_PAGE_ACCOUNT_VIEW',
'BANK_CLIENT_GET_USER_ORG',
'TREASURY_MNO_DEALS_COMMON',
'INQUIRY_REQUEST.CREATE',
'INQUIRY_REQUEST.SIGN',
'INQUIRY.EXPORT_DOC',
'INQUIRY.CLIENT.CREATE',
'INQUIRY.CLIENT.SIGN',
'INQUIRY.CLIENT.EXPORT_DOC',
'BC.CLIENT.LIST.VIEW',
'BANK_CLIENT_GET_USER_ORG',
],
@@ -264,9 +262,9 @@ const USER_AUTHORITIES_MOCK: AuthoritiesResponseDto = {
'CLIENT_HOME_PAGE_ACCOUNT_VIEW',
'BANK_CLIENT_GET_USER_ORG',
'TREASURY_MNO_DEALS_COMMON',
'INQUIRY_REQUEST.CREATE',
'INQUIRY_REQUEST.SIGN',
'INQUIRY.EXPORT_DOC',
'INQUIRY.CLIENT.CREATE',
'INQUIRY.CLIENT.SIGN',
'INQUIRY.CLIENT.EXPORT_DOC',
'BC.CLIENT.LIST.VIEW',
'BANK_CLIENT_GET_USER_ORG',
],
@@ -22,9 +22,16 @@ const LOCALIZATION = {
BUTTON_GO: 'Перейти',
BUTTON_STAY: 'Остаться',
OPEN_MNO_ACTION: 'Открыть неснижаемый остаток',
FROM: 'От',
TO: 'До',
SELECT_FROM_THE_LIST: 'Выберите из списка',
FIELDS: {
AMOUNT: 'Сумма',
CURRENCY: 'Валюта',
STATUS: 'Статус',
TERM: 'Срок',
DATE_BEGIN: 'Дата открытия',
DATE_END: 'Дата закрытия',
},
BLOCKING: {
DFM_BANNER_MESSAGE:
+17 -1
View File
@@ -36,7 +36,7 @@ const PATHS_DEPOSITS = {
HOME: PATHS.DEPOSITS,
PRODUCTS: `${PATHS.DEPOSITS}/products`,
TREASURY_DEALS: `${PATHS.DEPOSITS}/treasury-deals`,
TREAUSRY_DEALS_UNIVERSAL: `${PATHS.DEPOSITS}/treasury-deals-universal`, //TODO: временное расположение, закроется фиче тогглом
TREAUSRY_DEALS_UNIVERSAL: `${PATHS.DEPOSITS}/treasury-deals-universal`, // TODO: временное расположение, закроется фиче тогглом
CONFIRMATION: `${PATHS.DEPOSITS}/confirmation`,
CANCEL_DOCUMENT: `${PATHS.DEPOSITS}/products/cancel`,
EARLY_REFUND: `${PATHS.DEPOSITS}/products/early-refund`,
@@ -164,6 +164,7 @@ const EXTERNAL_LINKS = {
REGULATIONS_CERTIFICATION_CENTER: 'https://www.gazprombank.ru/corporate/settlement_cash_services/client_bank/#tab_432725',
CRYPTO_PRO: 'https://passport.gbo.gazprombank.ru/help#cm61',
SALARY_PROJECTS: 'https://www.gazprombank.ru/business/salary-project/#first-step',
PAYMENTS_ABROAD: 'https://www.gazprombank.ru/special/msb/ved-payments-1/',
};
const CROSS_SALE_PATHS = {
@@ -181,6 +182,19 @@ const CROSS_BORDER_AB_PAYMENTS = {
AUTH: 'https://app.ab-payments.ru/auth',
};
const MY_DSS_V1_LINKS = {
RU_STORE: 'https://www.rustore.ru/catalog/app/ru.cryptopro.mydss',
APP_GALLERY: 'https://appgallery.huawei.com/#/app/C103988925',
CRYPTOPRO: 'https://cryptopro.ru/products/mydss',
};
const MY_DSS_V2_LINKS = {
RU_STORE: 'https://www.rustore.ru/catalog/app/ru.safetech.mydss.v2',
APP_GALLERY: 'https://appgallery.huawei.com/app/C103534717',
APP_STORE: 'https://apps.apple.com/ru/app/mydss-2-0/id1524467203',
APK: 'https://cdn.gbo.gazprombank.ru/app/android/businesssign.apk',
};
const IB_MODULE_VERSION = process.env.IB_MODULE_VERSION ?? '';
const queryStringVersion = `${IB_MODULE_VERSION ? `?version=${IB_MODULE_VERSION}` : ''}`;
@@ -203,4 +217,6 @@ export {
CROSS_SALE_PATHS,
EXTERNAL_LINKS,
queryStringVersion,
MY_DSS_V1_LINKS,
MY_DSS_V2_LINKS,
};
+1
View File
@@ -8,3 +8,4 @@ export * from './useScrollLock';
export * from './useRedirectByToggle';
export * from './useInfiniteScroll';
export * from './useSearchParams';
export * from './useOrganizationWithAuthorities';
@@ -0,0 +1,17 @@
import { useMemo } from 'react';
import type { Authority } from '@msb/http';
import { useAppContext } from '../context';
import { getOrganizationWithAuthorities } from '../lib';
const useOrganizationWithAuthorities = (authorities: Authority[], isCheckEvery: boolean = true) => {
const { userAuthorities } = useAppContext();
const clientAuthorities = useMemo(() => userAuthorities?.data.clientAuthorities || {}, [userAuthorities?.data.clientAuthorities]);
return useMemo(
() => getOrganizationWithAuthorities(clientAuthorities, authorities, isCheckEvery),
[authorities, clientAuthorities, isCheckEvery]
);
};
export { useOrganizationWithAuthorities };
@@ -77,6 +77,18 @@ function checkIsPermissionsAvailable(
: authorities.some(authority => organizationAuthorities.includes(authority));
}
function getOrganizationWithAuthorities(clientAuthorities: ClientAuthoritiesDto, authorities: Authority[], isCheckEvery: boolean = true) {
const organizationIds = Object.keys(clientAuthorities);
return organizationIds.find(organizationId => {
if (isCheckEvery) {
return authorities.every(authority => clientAuthorities[organizationId].includes(authority));
}
return authorities.some(authority => clientAuthorities[organizationId].includes(authority));
});
}
export {
checkOrganizationsHavePermission,
checkHavePermission,
@@ -84,4 +96,5 @@ export {
checkHavePermissionList,
checkHaveUserPermissionsList,
checkIsPermissionsAvailable,
getOrganizationWithAuthorities,
};
+4 -1
View File
@@ -89,4 +89,7 @@ const formatSize = (byte: number | string, locale: string = 'ru') => {
return `${byteSize} ${SIZE_DESIGNATIONS[locale].B}`;
};
export { formatSize };
const getFormattedBalanceWithoutIcon = (money: number, maximumFractionDigits = 2, minimumFractionDigits = 2) =>
new Intl.NumberFormat('ru-RU', { style: 'decimal', maximumFractionDigits, minimumFractionDigits }).format(money);
export { formatSize, getFormattedBalanceWithoutIcon };
+1
View File
@@ -1,3 +1,4 @@
// Не экспортировать /tests!!
export * from './useMediaQuery';
export * from './pluralize';
export * from './useImageItemsLoader';
+1
View File
@@ -1 +1,2 @@
export * from './customRender';
export * from './mocks';
+3
View File
@@ -0,0 +1,3 @@
export * from './useYaMetrika';
export * from './useMediaQuery';
export * from './useRedirect';
@@ -0,0 +1,47 @@
/**
* Утилиты для создания моков useMediaQuery
* Используется в тестах для мокирования медиа-запросов.
*/
export interface UseMediaQueryMockState {
isMobileValue: boolean;
isTabletValue: boolean;
}
export interface UseMediaQueryMocks {
state: UseMediaQueryMockState;
useMediaQueryMock: jest.Mock<boolean, unknown[]>;
}
const DEFAULT_MEDIA_QUERY_STATE: UseMediaQueryMockState = {
isMobileValue: false,
isTabletValue: false,
};
/**
* Создает мок для useMediaQuery с поддержкой mobile и tablet
* Первый вызов возвращает isMobileValue, второй - isTabletValue.
*/
export function createUseMediaQueryMockWithDeviceTypes(
initialState: UseMediaQueryMockState = DEFAULT_MEDIA_QUERY_STATE
): UseMediaQueryMocks {
const state: UseMediaQueryMockState = {
isMobileValue: initialState.isMobileValue,
isTabletValue: initialState.isTabletValue,
};
const useMediaQueryMock = jest.fn(() => {
const callCount = useMediaQueryMock.mock.calls.length;
if (callCount === 1) {
return state.isMobileValue;
}
return state.isTabletValue;
});
return {
state,
useMediaQueryMock,
};
}
@@ -0,0 +1,22 @@
/**
* Утилиты для создания моков useRedirect
* Используется в тестах для мокирования редиректов.
*/
export interface UseRedirectMocks {
redirectMock: jest.Mock;
useRedirectMock: jest.Mock<jest.Mock, [string?]>;
}
/**
* Создает моки для useRedirect.
*/
export function createUseRedirectMock(): UseRedirectMocks {
const redirectMock = jest.fn();
const useRedirectMock = jest.fn((_path?: string) => redirectMock);
return {
redirectMock,
useRedirectMock,
};
}
@@ -0,0 +1,29 @@
/**
* Утилиты для создания моков useYaMetrika
* Используется в тестах для мокирования Яндекс.Метрики.
*/
export interface UseYaMetrikaMockReturn {
handleReachGoal: jest.Mock;
}
export interface UseYaMetrikaMocks {
handleReachGoalMock: jest.Mock;
useYaMetrikaMock: jest.Mock<UseYaMetrikaMockReturn, unknown[]>;
}
/**
* Создает моки для useYaMetrika.
* @returns Объект с моками handleReachGoal и useYaMetrika.
*/
export function createUseYaMetrikaMock(): UseYaMetrikaMocks {
const handleReachGoalMock = jest.fn();
const useYaMetrikaMock = jest.fn(() => ({
handleReachGoal: handleReachGoalMock,
}));
return {
handleReachGoalMock,
useYaMetrikaMock,
};
}
+17 -12
View File
@@ -3,7 +3,12 @@ import { LongRightIcon } from '@fractal-ui/library';
import { Modal } from '@fractal-ui/overlays';
import { Title, Text } from '@fractal-ui/styling';
import type { MegaBannerModel, SALE_GRADIENTS } from '@msb/shared';
import { MEDIA, TEXT_RATE_KEY, useBestRatesMainPage, useMediaQuery, type BaseModalProps } from '@msb/shared';
import {
MEDIA,
// TEXT_RATE_KEY, useBestRatesMainPage,
useMediaQuery,
type BaseModalProps,
} from '@msb/shared';
import * as S from './MegaBanner.styles';
interface MegaBannerProps extends MegaBannerModel, BaseModalProps {
@@ -17,14 +22,14 @@ const MegaBanner = ({
button,
image,
gradient = 'sale2',
rateText,
dealType,
// rateText,
// dealType,
onConfirm,
onClose,
}: MegaBannerProps) => {
const isMobile = useMediaQuery(MEDIA.mobile);
const { bestRates } = useBestRatesMainPage();
// const { bestRates } = useBestRatesMainPage();
if (!isOpen) {
return null;
@@ -38,13 +43,13 @@ const MegaBanner = ({
onClose?.();
};
const haveRate = dealType && rateText && bestRates && bestRates[dealType];
// const haveRate = dealType && rateText && bestRates && bestRates[dealType];
const finalText = haveRate
? rateText
.split(new RegExp(`(${TEXT_RATE_KEY})`))
.map(textPart => (textPart === TEXT_RATE_KEY ? <b>{bestRates[dealType]}%</b> : textPart))
: text;
// const finalText = haveRate
// ? rateText
// .split(new RegExp(`(${TEXT_RATE_KEY})`))
// .map(textPart => (textPart === TEXT_RATE_KEY ? <b>{bestRates[dealType]}%</b> : textPart))
// : text;
return (
<>
@@ -62,11 +67,11 @@ const MegaBanner = ({
{isMobile ? <Title.H1Bold color="text.primary">{title}</Title.H1Bold> : <Title.H2Bold color="text.primary">{title}</Title.H2Bold>}
{isMobile ? (
<Text.P2 color="text.primary" whiteSpace="pre-wrap">
{finalText}
{text}
</Text.P2>
) : (
<Text.P1 color="text.primary" whiteSpace="inherit">
{finalText}
{text}
</Text.P1>
)}
</S.Content>
@@ -1,7 +1,7 @@
/* eslint-disable react/jsx-no-useless-fragment */
import { useMemo } from 'react';
import { useAppContext } from '../../context';
import { checkHavePermission, checkHaveUserPermissions } from '../../lib';
import { checkHavePermission, checkHaveUserPermissions, checkIsPermissionsAvailable } from '../../lib';
import type { PermissionItem } from '../../model';
import { AUTHORITIES_FIELD } from '../../model';
import { NotAuthorizedError } from '../SystemResponse';
@@ -10,21 +10,26 @@ interface PermissionsProps {
permissionsList: PermissionItem[];
children: React.ReactNode;
ymNotAuthorizedGoal?: React.ReactNode;
organizationId?: string;
}
const Permissions = ({ permissionsList, children, ymNotAuthorizedGoal }: PermissionsProps) => {
const Permissions = ({ permissionsList, children, ymNotAuthorizedGoal, organizationId }: PermissionsProps) => {
const { userAuthorities } = useAppContext();
const hasPermissions = useMemo(
() =>
permissionsList.every(({ permissions, authoritiesField, isCheckEvery }) => {
if (organizationId) {
return checkIsPermissionsAvailable(organizationId, userAuthorities?.data.clientAuthorities || {}, permissions, isCheckEvery);
}
if (authoritiesField === AUTHORITIES_FIELD.CLIENT_AUTHORITIES) {
return checkHavePermission(userAuthorities?.data.clientAuthorities || {}, permissions, isCheckEvery);
}
return checkHaveUserPermissions(userAuthorities?.data.authorities, permissions, isCheckEvery);
}),
[permissionsList, userAuthorities]
[organizationId, permissionsList, userAuthorities]
);
return hasPermissions ? (
@@ -11,9 +11,10 @@ const BannerContainer = styled.div<{ $background: string }>(({ $background }) =>
const Image = styled.img({
position: 'absolute',
right: 0,
right: '-12px',
bottom: 16,
height: 132,
scale: '1.5',
});
const Content = styled.div({
@@ -56,10 +56,10 @@ const PromoBanner: FC<PromoBannerProps> = ({
{bestRates[dealType]?.replace('.', ',')}%
</Title.H1>
)}
{title && <Title.H4 color="text.primary">{title}</Title.H4>}
<Text.P3 color="text.primary">{description}</Text.P3>
{title && <Title.H4 color="bg.primary">{title}</Title.H4>}
<Text.P3 color="bg.primary">{description}</Text.P3>
</S.Title>
<Button dataAction="read-more" size="M" variant="secondary" onClick={navigateTo}>
<Button dataAction="read-more" size="S" variant="white" onClick={navigateTo}>
{buttonText}
</Button>
</S.Content>
@@ -7,16 +7,18 @@ import type { DateFieldWithCalendarContextValues, Option, PERIOD_TYPE } from './
const useDateButton = () => {
const { onChange, options, value, isCalendarShown, setSelectedCalendarDate, setSelectedDate, handleCalendarDateSelect } =
useDateFieldWithCalendarContext();
const selected = useMemo(() => options.find(opt => opt.value === value?.period), [options, value?.period]);
const selectedOption = useMemo(() => {
const found = options.find(opt => opt.value === value?.period);
return found || DEFAULT_SELECTED_OPTION;
}, [options, value?.period]);
const [isDrawerOpened, setIsDrawerOpened] = useState(false);
const [selectedOptionLabel, setSelectedOptionLabel] = useState(selected?.label);
const [selectedOption, setSelectedOption] = useState<Option>(selected ? selected : DEFAULT_SELECTED_OPTION);
const reset = useCallback(() => {
setSelectedCalendarDate(undefined);
setSelectedDate(undefined);
setSelectedOption(DEFAULT_SELECTED_OPTION);
setSelectedOptionLabel('');
onChange();
setIsDrawerOpened(false);
}, [onChange, setSelectedCalendarDate, setSelectedDate]);
@@ -25,12 +27,12 @@ const useDateButton = () => {
(option?: DateFieldWithCalendarContextValues['options'][0]) => {
if (isCalendarShown) {
handleCalendarDateSelect();
setSelectedOption(DEFAULT_SELECTED_OPTION);
setSelectedOptionLabel('');
} else if (option) {
setSelectedOption(option);
setSelectedDate(option?.value as PERIOD_TYPE);
setSelectedOptionLabel(option?.label);
return;
}
if (option) {
setSelectedDate(option.value as PERIOD_TYPE);
const interval = getIntervalByPeriod(option?.value as PERIOD_TYPE);
@@ -41,13 +43,11 @@ const useDateButton = () => {
);
const handleButtonClick = useCallback(() => {
if (!isDrawerOpened) {
setIsDrawerOpened(() => true);
}
}, [isDrawerOpened]);
setIsDrawerOpened(true);
}, []);
const handleClose = useCallback(() => {
setIsDrawerOpened(() => false);
setIsDrawerOpened(false);
}, []);
const contextValues: WithOptionsContextProps<{ label: string; value: number | string }> = useMemo(
@@ -66,8 +66,6 @@ const useDateButton = () => {
reset,
change,
drawer: { open: handleButtonClick, close: handleClose, state: isDrawerOpened },
selectedOptionLabel,
selected,
selectedOption,
contextValues,
};
@@ -30,7 +30,8 @@ const DateButton = ({
}: Props) => {
const { options, handleInnerButtonClick, isCalendarShown, setSelectedCalendarDate, setIsCalendarShown } =
useDateFieldWithCalendarContext();
const { reset, change, drawer, selectedOptionLabel, selectedOption, contextValues } = useDateButton();
const { reset, change, drawer, selectedOption, contextValues } = useDateButton();
const drawerActions: ModalButtonProps[] = useMemo(() => {
let actions: ModalButtonProps[] = [
@@ -50,7 +51,7 @@ const DateButton = ({
{
text: LOCALIZATION.SELECT_LABEL,
shape: 'default',
disabled: isCalendarShown ? !selectedCalendarDate : !selectedOption.value || selectedOption.value === selectedDate,
disabled: !selectedCalendarDate,
dataAction: 'select-drawer-calendar-date',
onClick: () => change(),
},
@@ -59,21 +60,27 @@ const DateButton = ({
}
return actions;
}, [change, isCalendarShown, reset, selectedCalendarDate, selectedDate, selectedOption.value]);
}, [change, isCalendarShown, reset, selectedCalendarDate, selectedDate]);
const bottomSheetRef = useRef<HTMLDivElement | null>(null);
const buttonVariant = selectedDate || drawer.state ? 'primary' : 'secondary';
// Лейбл вычисляется из value.period + options
const labelToShow = selectedOption?.label;
return (
<WithOptionsContext.Provider value={contextValues}>
<Button
isIconRight
dataAction={name}
flex="0 0 auto"
icon={drawer.state || selectedDate ? CrossIcon : DownIcon}
minWidth="fit-content"
shape="default"
size="S"
variant={buttonVariant}
width="fit-content"
onClick={e => {
const tagName =
e.nativeEvent.target && 'tagName' in e.nativeEvent.target ? (e.nativeEvent.target as { tagName: string }).tagName : '';
@@ -85,7 +92,7 @@ const DateButton = ({
}
}}
>
{selectedOptionLabel ? `${LOCALIZATION.REQUEST_DATE}: ${selectedOptionLabel}` : LOCALIZATION.REQUEST_DATE}
{labelToShow ? labelToShow : LOCALIZATION.REQUEST_DATE}
</Button>
<Drawer
hideContentPadding
@@ -53,6 +53,13 @@ const DateFieldWithCalendar = ({
);
const [options, setOptions] = useState(acceptableOptions);
// Если опция изменилась на пустую, то нужно задать отчистить выбранную опцию
useEffect(() => {
if (!other.value) {
selectPeriod(undefined);
}
}, [other.value]);
const setupCustomPeriod = useCallback(() => {
const { customLabel } = getSelectedDateLabel(other.value?.dateInterval || other.defaultValue?.dateInterval);
@@ -36,6 +36,7 @@ const DateSelect = ({
tooltipPosition,
readOnly,
shouldEnableWeekends = false,
width,
}: Props) => {
const { setSelectedCalendarDate, options, setIsCalendarShown, isCalendarShown, handleInnerButtonClick } =
useDateFieldWithCalendarContext();
@@ -65,6 +66,7 @@ const DateSelect = ({
tooltipPosition={tooltipPosition}
value={selectedDate}
warningText={warningText}
width={width}
onChange={handleSelectChange}
onInnerButtonClick={handleInnerButtonClick}
/>
+1
View File
@@ -0,0 +1 @@
export { Select } from './ui';
@@ -0,0 +1,17 @@
import { Select as FractalSelect } from '@msb/fractal-ui-composites';
import type { BaseSelectProps } from '@msb/fractal-ui-composites';
import { MEDIA, useMediaQuery } from '@msb/shared';
/**
* Обёртка над компонентом Select фрактала, необходимая для кастомизации функциональности под наши решения.
*
* ShowDrawerForBreakPoint нужен для отображения Drawer в размере Tablet.
* Отображаем разные FractalSelect, т.к. Брекпоинты фрактала отличаются от наших.
*/
const Select = (props: BaseSelectProps) => {
const isTablet = useMediaQuery(MEDIA.tablet);
return isTablet ? <FractalSelect showDrawerForBreakPoint {...(props as any)} /> : <FractalSelect {...(props as any)} />;
};
export { Select };
@@ -0,0 +1 @@
export { Select } from './Select';
+1
View File
@@ -2,4 +2,5 @@ export { MultiSelect } from './MultiSelect';
export { MultiSelectButton } from './MultiSelectButton';
export { SelectButton } from './SelectButton';
export { SingleSelectButton } from './SingleSelectButton';
export { Select } from './Select';
export * from './DateFieldWithCalendar';
+14 -2
View File
@@ -1,3 +1,4 @@
import type { CSSProperties } from 'react';
import type { ModalButtonProps, StatusModalType } from '@fractal-ui/overlays';
import { Text } from '@fractal-ui/styling';
import type { BaseModalProps } from '../../context';
@@ -32,6 +33,15 @@ interface BaseDialogProps<T = unknown> extends BaseModalProps {
isLoading?: boolean;
/** Признак скрытия иконки "Назад". */
hideBackIcon?: boolean;
/** Устанавливает ширину кнопки.
* @default fit-content
*/
okButtonWidth?: CSSProperties['width'];
/**
* Устанавливает ширину кнопки отмены.
* @default 300px
*/
cancelButtonWidth?: CSSProperties['width'];
}
/** Модальное окно подтверждения действия. */
@@ -53,6 +63,8 @@ const BaseDialog = <T,>({
isLoading,
hideBackIcon = false,
okButtonShape,
okButtonWidth = 'fit-content',
cancelButtonWidth = 300,
}: BaseDialogProps<T>) => {
const handleAccept = () => (onOk ? onOk(data) : onSuccess?.());
@@ -68,7 +80,7 @@ const BaseDialog = <T,>({
text: okButtonText,
shape: okButtonShape,
variant: 'primary',
width: 300,
width: okButtonWidth,
},
{
dataAction: 'cancel',
@@ -77,7 +89,7 @@ const BaseDialog = <T,>({
size: 'L',
text: cancelButtonText,
variant: 'primary',
width: 300,
width: cancelButtonWidth,
},
];
@@ -19,6 +19,7 @@ import { OrganizationBlocker } from './OrganizationBlocker';
import { OrganizationField } from './OrganizationField';
import { ReservedAccountField } from './ReservedAccountField';
import { SignatoryField } from './SignatoryField';
import { Skeletons } from './Skeletons';
import { OPEN_ACCOUNT_GOALS } from '@/shared/constants';
import { getCardDescription, getCardStatusProps } from '@/shared/lib';
import { Aside } from '@/shared/ui/Aside';
@@ -63,20 +64,22 @@ const AccountOrderForm = () => {
<Flex column height="100%" justifyContent="space-between" pt="4">
<Flex column gap={7}>
<OrganizationField />
<AccountTypeField />
<OrganizationBlocker>
<CurrencyField />
<OfficeField />
<CommissionAccountBlocker>
<ContractField />
<ReservedAccountField />
<Flex alignItems="flex-start" gap={5} {...(isMobile ? { column: true } : { row: true })}>
<CommissionAccountField />
<SignatoryField />
</Flex>
<AgreementsFields />
</CommissionAccountBlocker>
</OrganizationBlocker>
<Skeletons>
<AccountTypeField />
<OrganizationBlocker>
<CurrencyField />
<OfficeField />
<CommissionAccountBlocker>
<ContractField />
<ReservedAccountField />
<Flex alignItems="flex-start" gap={5} {...(isMobile ? { column: true } : { row: true })}>
<CommissionAccountField />
<SignatoryField />
</Flex>
<AgreementsFields />
</CommissionAccountBlocker>
</OrganizationBlocker>
</Skeletons>
</Flex>
<CommissionAccountBlocker>
<Actions />
@@ -0,0 +1,28 @@
import type { ReactNode } from 'react';
import { Skeleton } from '@fractal-ui/core';
import { Flex } from '@msb/shared';
import { useOrganizationFieldValidation } from '../model';
interface Props {
children: ReactNode;
}
const Skeletons = ({ children }: Props) => {
const { isValidating } = useOrganizationFieldValidation();
return isValidating ? (
<Flex column gap={7}>
<Skeleton dataName="field1" height="40px" />
<Skeleton dataName="field2" height="40px" />
<Flex row gap={5}>
<Skeleton dataName="field3" height="40px" width="100%" />
<Skeleton dataName="field4" height="40px" width="100%" />
</Flex>
</Flex>
) : (
// eslint-disable-next-line react/jsx-no-useless-fragment
<>{children}</>
);
};
export { Skeletons };
@@ -107,13 +107,19 @@ const useOpenAccountsOrdersTable = () => {
},
[getRowActions]
);
const actionHeaderProps = {
buttonProps: {
dataAction: 'open-card',
},
handler: handleClickOpenAccount,
title: LOCALIZATION.OPEN_ACCOUNT,
};
const actionHeaderProps = useMemo(() => {
if (openRequestsData.length === 0 && !isLoading && !filterIsActive) {
return;
}
return {
buttonProps: {
dataAction: 'open-card',
},
handler: handleClickOpenAccount,
title: LOCALIZATION.OPEN_ACCOUNT,
};
}, [filterIsActive, handleClickOpenAccount, isLoading, openRequestsData.length]);
return {
columns,
@@ -27,7 +27,7 @@ const usePhoto = (props: UsePhotoProps) => {
network.client
.get(link)
.then(response => {
const contentType = response.headers['Content-Type'];
const contentType = response.headers['content-type'];
if (response.status && typeof contentType === 'string' && contentType?.includes('image')) {
setManagerPhoto(link);
@@ -0,0 +1,15 @@
import { createContext, useContext } from 'react';
const SbpLandingContext = createContext<{ setIsSbpContractExist(value: boolean): void } | null>(null);
const useSbpLandingContext = () => {
const context = useContext(SbpLandingContext);
if (context === null) {
throw new Error('Use context with SbpLandingContext provider');
}
return context;
};
export { SbpLandingContext, useSbpLandingContext };
@@ -0,0 +1 @@
export { SbpLandingContext, useSbpLandingContext } from './SbpLandingContext';
@@ -0,0 +1 @@
export * from './localization';
@@ -0,0 +1,6 @@
const LOCALIZATION = {
ACCEPT_SBP_BUTTON: 'Подключить',
OPEN_SBP_DETAILS_BUTTON: 'Подробнее',
};
export { LOCALIZATION };
@@ -0,0 +1 @@
export { GuideList } from './ui';
@@ -0,0 +1 @@
export * from './types';
@@ -0,0 +1,12 @@
interface GuideListItemProps {
title: string;
description: string;
index: number;
isIndexVisible?: boolean;
}
interface GuideListProps {
items: Array<Omit<GuideListItemProps, 'index' | 'isIndexVisible'>>;
}
export type { GuideListItemProps, GuideListProps };
@@ -0,0 +1,29 @@
import styled from '@emotion/styled';
const GuideListLayout = styled.div(({ theme }) => ({
backgroundColor: theme.colors.bg.secondary,
display: 'flex',
flexDirection: 'column',
borderRadius: '20px',
padding: '24px',
width: '100%',
gap: '24px',
}));
const GuideListButtonsLayout = styled.div`
display: flex;
gap: 24px;
align-items: center;
justify-content: start;
margin-top: 16px;
width: 100%;
`;
const ListItemsDivider = styled.div(({ theme }) => ({
borderColor: theme.colors.control.border,
borderBottomWidth: '1px',
borderBottomStyle: 'solid',
width: '100%',
}));
export { GuideListLayout, GuideListButtonsLayout, ListItemsDivider };
@@ -0,0 +1,34 @@
import React from 'react';
import { Button, ButtonLink } from '@fractal-ui/core';
import { LOCALIZATION } from '../constants';
import type { GuideListProps } from '../model';
import * as S from './GuideList.styles';
import { ListItemsDivider } from './GuideList.styles';
import { GuideListItem } from './GuideListItem';
import { useSbpLandingContext } from '@/entities/SbpLandingContext';
const GuideList = ({ items }: GuideListProps) => {
const { setIsSbpContractExist } = useSbpLandingContext();
const onApplyClick = () => {
setIsSbpContractExist(true);
};
return (
<S.GuideListLayout>
{items.map(({ description, title }, index) => (
<>
<GuideListItem key={title} description={description} index={index + 1} title={title} />
{index !== items.length - 1 && <ListItemsDivider key={`${title}-divider`} />}
</>
))}
<S.GuideListButtonsLayout>
<Button dataAction="accept-sbp" onClick={onApplyClick}>
{LOCALIZATION.ACCEPT_SBP_BUTTON}
</Button>
<ButtonLink dataAction="open-sbp-details">{LOCALIZATION.OPEN_SBP_DETAILS_BUTTON}</ButtonLink>
</S.GuideListButtonsLayout>
</S.GuideListLayout>
);
};
export { GuideList };
@@ -0,0 +1,16 @@
import styled from '@emotion/styled';
const GuideListItemLayout = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
min-height: 52px;
`;
const GuideListItemTitle = styled.div`
display: flex;
justify-content: space-between;
`;
export { GuideListItemLayout, GuideListItemTitle };
@@ -0,0 +1,22 @@
import React, { useMemo } from 'react';
import { Title, Text, useTheme } from '@fractal-ui/styling';
import type { GuideListItemProps } from '../model';
import * as S from './GuideListItem.styles';
const GuideListItem = ({ title, description, index, isIndexVisible = true }: GuideListItemProps) => {
const theme = useTheme();
const preparedIndex = useMemo(() => (index < 10 ? index.toString().padStart(2, '0') : index), [index]);
return (
<S.GuideListItemLayout>
<S.GuideListItemTitle>
<Title.H4>{title}</Title.H4>
{isIndexVisible && <Title.H4 color={theme.colors.text.accentBrand}>{preparedIndex}</Title.H4>}
</S.GuideListItemTitle>
<Text.P2 color={theme.colors.text.secondary}>{description}</Text.P2>
</S.GuideListItemLayout>
);
};
export { GuideListItem };
@@ -0,0 +1 @@
export { GuideList } from './GuideList';
@@ -0,0 +1,18 @@
import { SBP_CONTRACT_FIELDS } from '../model';
import { LOCALIZATION } from './localization';
const SBP_CONTRACT_FIELDS_TO_LABELS = {
[SBP_CONTRACT_FIELDS.CONTRACT_TYPE]: LOCALIZATION[SBP_CONTRACT_FIELDS.CONTRACT_TYPE],
[SBP_CONTRACT_FIELDS.INN]: LOCALIZATION[SBP_CONTRACT_FIELDS.INN],
[SBP_CONTRACT_FIELDS.KPP]: LOCALIZATION[SBP_CONTRACT_FIELDS.KPP],
[SBP_CONTRACT_FIELDS.OGRN]: LOCALIZATION[SBP_CONTRACT_FIELDS.OGRN],
[SBP_CONTRACT_FIELDS.CONTRACT_NUMBER]: LOCALIZATION[SBP_CONTRACT_FIELDS.CONTRACT_NUMBER],
[SBP_CONTRACT_FIELDS.LEGAL_ID]: LOCALIZATION[SBP_CONTRACT_FIELDS.LEGAL_ID],
[SBP_CONTRACT_FIELDS.SIGN_DATE]: LOCALIZATION[SBP_CONTRACT_FIELDS.SIGN_DATE],
[SBP_CONTRACT_FIELDS.START_DATE]: LOCALIZATION[SBP_CONTRACT_FIELDS.START_DATE],
[SBP_CONTRACT_FIELDS.RETURN_ACCOUNT]: LOCALIZATION[SBP_CONTRACT_FIELDS.RETURN_ACCOUNT],
[SBP_CONTRACT_FIELDS.COMMISSION_ACCOUNT]: LOCALIZATION[SBP_CONTRACT_FIELDS.COMMISSION_ACCOUNT],
[SBP_CONTRACT_FIELDS.ACCOUNTS]: LOCALIZATION[SBP_CONTRACT_FIELDS.ACCOUNTS],
};
export { SBP_CONTRACT_FIELDS_TO_LABELS };
@@ -0,0 +1,2 @@
export * from './localization';
export * from './mocks';
@@ -0,0 +1,23 @@
import { SBP_CONTRACT_FIELDS } from '../model';
const LOCALIZATION = {
SBP_DETAILS_BUTTON: 'Подробнее о СБП',
ADD_SBP_ACCOUNTS_BUTTON: 'Добавить счета',
ORG_DATA_TITLE: 'Данные организации',
CONTRACT_DATA_TITLE: 'Данные договора',
ACCOUNTS_DATA_TITLE: 'Счета',
ORGANIZATION: 'Организация',
[SBP_CONTRACT_FIELDS.CONTRACT_TYPE]: 'Вид деятельности',
[SBP_CONTRACT_FIELDS.INN]: 'ИНН',
[SBP_CONTRACT_FIELDS.KPP]: 'КПП',
[SBP_CONTRACT_FIELDS.OGRN]: 'ОГРН / ОГРНИП',
[SBP_CONTRACT_FIELDS.CONTRACT_NUMBER]: 'Договор №',
[SBP_CONTRACT_FIELDS.LEGAL_ID]: 'LegalID в СБП',
[SBP_CONTRACT_FIELDS.SIGN_DATE]: 'Дата подписания',
[SBP_CONTRACT_FIELDS.START_DATE]: 'Дата начала действия',
[SBP_CONTRACT_FIELDS.RETURN_ACCOUNT]: 'Счёт для возвратов по СБП',
[SBP_CONTRACT_FIELDS.COMMISSION_ACCOUNT]: 'Счёт для списания комиссии по СБП',
[SBP_CONTRACT_FIELDS.ACCOUNTS]: 'Счета для регистрации в СБП:',
};
export { LOCALIZATION };
@@ -0,0 +1,80 @@
import { SBP_CONTRACT_FIELDS } from '../model';
import { SBP_CONTRACT_FIELDS_TO_LABELS } from './constants';
import type { SbpDescriptionRow } from '@/shared/ui/DescriptionList';
const ORGANIZATION_DATA: SbpDescriptionRow[] = [
{
name: SBP_CONTRACT_FIELDS.CONTRACT_TYPE,
label: SBP_CONTRACT_FIELDS_TO_LABELS[SBP_CONTRACT_FIELDS.CONTRACT_TYPE],
value: 'Водоснабжение, водоотведение, организация',
description: '',
},
{
name: SBP_CONTRACT_FIELDS.INN,
label: SBP_CONTRACT_FIELDS_TO_LABELS[SBP_CONTRACT_FIELDS.INN],
value: '234567890765',
description: '',
},
{
name: SBP_CONTRACT_FIELDS.KPP,
label: SBP_CONTRACT_FIELDS_TO_LABELS[SBP_CONTRACT_FIELDS.KPP],
value: '773643002',
description: '',
},
{
name: SBP_CONTRACT_FIELDS.OGRN,
label: SBP_CONTRACT_FIELDS_TO_LABELS[SBP_CONTRACT_FIELDS.OGRN],
value: '773643002',
description: '',
},
];
const CONTRACT_DATA: SbpDescriptionRow[] = [
{
name: SBP_CONTRACT_FIELDS.CONTRACT_NUMBER,
label: SBP_CONTRACT_FIELDS_TO_LABELS[SBP_CONTRACT_FIELDS.CONTRACT_NUMBER],
value: '9721007182',
description: '',
},
{
name: SBP_CONTRACT_FIELDS.LEGAL_ID,
label: SBP_CONTRACT_FIELDS_TO_LABELS[SBP_CONTRACT_FIELDS.LEGAL_ID],
value: '40702.810.8.21643442469',
description: '',
},
{
name: SBP_CONTRACT_FIELDS.SIGN_DATE,
label: SBP_CONTRACT_FIELDS_TO_LABELS[SBP_CONTRACT_FIELDS.SIGN_DATE],
value: '24.01.2022',
description: '',
},
{
name: SBP_CONTRACT_FIELDS.START_DATE,
label: SBP_CONTRACT_FIELDS_TO_LABELS[SBP_CONTRACT_FIELDS.START_DATE],
value: '24.01.2022',
description: '',
},
];
const ACCOUNTS_DATA: SbpDescriptionRow[] = [
{
name: SBP_CONTRACT_FIELDS.RETURN_ACCOUNT,
label: SBP_CONTRACT_FIELDS_TO_LABELS[SBP_CONTRACT_FIELDS.RETURN_ACCOUNT],
value: '40702.810.8.21644442469',
description: '',
},
{
name: SBP_CONTRACT_FIELDS.COMMISSION_ACCOUNT,
label: SBP_CONTRACT_FIELDS_TO_LABELS[SBP_CONTRACT_FIELDS.COMMISSION_ACCOUNT],
value: '40702.810.8.21644442469',
description: 'Комиссия: 0.4% с транзакции, но не более 1 500 ₽',
},
{
name: SBP_CONTRACT_FIELDS.ACCOUNTS,
label: SBP_CONTRACT_FIELDS_TO_LABELS[SBP_CONTRACT_FIELDS.ACCOUNTS],
value: '40702.810.8.21644442469; 40702.810.8.21644442469; 40702.810.8.21644442469',
description: '',
},
];
export { ORGANIZATION_DATA, CONTRACT_DATA, ACCOUNTS_DATA };
@@ -0,0 +1 @@
export { SbpContracts } from './ui';
@@ -0,0 +1 @@
export * from './types';
@@ -0,0 +1,17 @@
enum SBP_CONTRACT_FIELDS {
ORG_SHORT_NAME = 'shortName',
INN = 'innKio',
OGRN = 'ogrnOgrip',
KPP = 'kpp',
CONTRACT_NUMBER = 'contractNumber',
CONTRACT_TYPE = 'contractType',
LEGAL_ID = 'legalId',
MCC_NAME = 'mccName',
SIGN_DATE = 'signDate',
START_DATE = 'startDate',
RETURN_ACCOUNT = 'returnAccount',
COMMISSION_ACCOUNT = 'commissionAccount',
ACCOUNTS = 'accounts',
}
export { SBP_CONTRACT_FIELDS };
@@ -0,0 +1,21 @@
import styled from '@emotion/styled';
const SbpContractsLayout = styled.div(({ theme }) => ({
backgroundColor: theme.colors.bg.primary,
display: 'flex',
flexDirection: 'column',
borderRadius: '16px',
padding: '0 24px',
width: '100%',
gap: '40px',
}));
const SbpContractsButtonsLayout = styled.div`
display: flex;
align-items: center;
justify-content: end;
gap: 8px;
width: 100%;
`;
export { SbpContractsLayout, SbpContractsButtonsLayout };
@@ -0,0 +1,34 @@
import React from 'react';
import { Select } from '@fractal-ui/composites';
import { Button } from '@fractal-ui/core';
import { ACCOUNTS_DATA, CONTRACT_DATA, LOCALIZATION, ORGANIZATION_DATA } from '../constants';
import * as S from './SbpContracts.styles';
import { useOrganizationsSelect } from '@/shared/lib/hooks/useOrganizationsSelect';
import { SbpDescriptionList } from '@/shared/ui/DescriptionList';
const SbpContracts = () => {
const { onOrganizationChange, organizationsOptions, selectedOrganization } = useOrganizationsSelect();
return (
<S.SbpContractsLayout>
<Select
name="Sbp_contracts_organizations"
options={organizationsOptions}
value={selectedOrganization}
width="50%"
onChange={onOrganizationChange}
/>
<SbpDescriptionList dataName="sbp-org-data" items={ORGANIZATION_DATA} title={LOCALIZATION.ORG_DATA_TITLE} />
<SbpDescriptionList dataName="sbp-contract-data" items={CONTRACT_DATA} title={LOCALIZATION.CONTRACT_DATA_TITLE} />
<SbpDescriptionList dataName="sbp-accounts-data" items={ACCOUNTS_DATA} title={LOCALIZATION.ACCOUNTS_DATA_TITLE} />
<S.SbpContractsButtonsLayout>
<Button dataAction="sbp-details" variant="blue">
{LOCALIZATION.SBP_DETAILS_BUTTON}
</Button>
<Button dataAction="add-accounts">{LOCALIZATION.ADD_SBP_ACCOUNTS_BUTTON}</Button>
</S.SbpContractsButtonsLayout>
</S.SbpContractsLayout>
);
};
export { SbpContracts };
@@ -0,0 +1 @@
export { SbpContracts } from './SbpContracts';
@@ -0,0 +1,102 @@
import { Text } from '@fractal-ui/styling';
import { Cell, CELL_TYPE } from '@fractal-ui/table';
import { documentStatusTypeToBadgeType } from '../lib';
import type { TransactionsHistory } from '../model';
import { SBP_OPERATIONS_HISTORY_FIELDS, TRANSACTIONS_HISTORY_STATUS, TRANSACTIONS_HISTORY_TYPE } from '../model';
import { LOCALIZATION } from './localization';
const TRANSACTION_REQUEST_TYPE_OPTIONS = {
[TRANSACTIONS_HISTORY_TYPE.PAYMENT_BACK]: LOCALIZATION.RETURN_TEXT,
[TRANSACTIONS_HISTORY_TYPE.PAYOUT_FROM_MERCHANT]: LOCALIZATION.TRANSFER_TEXT,
[TRANSACTIONS_HISTORY_TYPE.INCOMING_PAYMENT]: LOCALIZATION.INCOMING_PAYMENT,
};
const TRANSACTION_REQUEST_CLIENT_STATUS_LABEL = {
[TRANSACTIONS_HISTORY_STATUS.IN_PROGRESS]: LOCALIZATION[TRANSACTIONS_HISTORY_STATUS.IN_PROGRESS],
[TRANSACTIONS_HISTORY_STATUS.CANCELLED]: LOCALIZATION[TRANSACTIONS_HISTORY_STATUS.CANCELLED],
[TRANSACTIONS_HISTORY_STATUS.PERFORMED]: LOCALIZATION[TRANSACTIONS_HISTORY_STATUS.PERFORMED],
[TRANSACTIONS_HISTORY_STATUS.DRAFT]: LOCALIZATION[TRANSACTIONS_HISTORY_STATUS.DRAFT],
};
const OPERATIONS_HISTORY_COLUMNS = [
{
id: SBP_OPERATIONS_HISTORY_FIELDS.AMOUNT,
label: LOCALIZATION[SBP_OPERATIONS_HISTORY_FIELDS.AMOUNT],
name: SBP_OPERATIONS_HISTORY_FIELDS.AMOUNT,
headerType: 'string',
Cell({ amount }: TransactionsHistory) {
return <Cell cellName={SBP_OPERATIONS_HISTORY_FIELDS.AMOUNT} cellType={CELL_TYPE.STRING} text={amount?.value} />;
},
hasSort: true,
},
{
id: SBP_OPERATIONS_HISTORY_FIELDS.DOC_DATE,
label: LOCALIZATION[SBP_OPERATIONS_HISTORY_FIELDS.DOC_DATE],
name: SBP_OPERATIONS_HISTORY_FIELDS.DOC_DATE,
headerType: 'string',
Cell({ transactionTime }: TransactionsHistory) {
return (
<Cell
cellName={SBP_OPERATIONS_HISTORY_FIELDS.DOC_DATE}
cellType={CELL_TYPE.STRING}
subText={transactionTime.toDateString()}
text={transactionTime.toDateString()}
/>
);
},
hasSort: true,
defaultCanSort: true,
sortDescFirst: true,
width: 100,
},
{
id: SBP_OPERATIONS_HISTORY_FIELDS.MERCHANT_NAME,
label: LOCALIZATION[SBP_OPERATIONS_HISTORY_FIELDS.MERCHANT_NAME],
name: SBP_OPERATIONS_HISTORY_FIELDS.MERCHANT_NAME,
headerType: 'string',
Cell({ merchant, isB2b }: TransactionsHistory) {
return (
<Cell
cellName={SBP_OPERATIONS_HISTORY_FIELDS.MERCHANT_ID}
cellType={CELL_TYPE.STRING}
subText={isB2b ? merchant.id : ''}
text={merchant?.name}
/>
);
},
hasSort: false,
},
{
id: SBP_OPERATIONS_HISTORY_FIELDS.DOC_TYPE,
label: LOCALIZATION[SBP_OPERATIONS_HISTORY_FIELDS.DOC_TYPE],
name: SBP_OPERATIONS_HISTORY_FIELDS.DOC_TYPE,
headerType: 'string',
Cell({ transactionType }: TransactionsHistory) {
return (
<Cell
cellName={SBP_OPERATIONS_HISTORY_FIELDS.DOC_TYPE}
cellType={CELL_TYPE.STRING}
text={TRANSACTION_REQUEST_TYPE_OPTIONS[transactionType]}
/>
);
},
hasSort: true,
width: 130,
},
{
id: SBP_OPERATIONS_HISTORY_FIELDS.STATUS,
label: LOCALIZATION[SBP_OPERATIONS_HISTORY_FIELDS.STATUS],
name: SBP_OPERATIONS_HISTORY_FIELDS.STATUS,
headerType: 'string',
Cell({ status }: { status: TRANSACTIONS_HISTORY_STATUS }) {
return (
<Cell badgeType={documentStatusTypeToBadgeType(status)} cellName={SBP_OPERATIONS_HISTORY_FIELDS.STATUS} cellType={CELL_TYPE.STATUS}>
<Text.P3Short>{TRANSACTION_REQUEST_CLIENT_STATUS_LABEL[status]}</Text.P3Short>
</Cell>
);
},
hasSort: true,
},
];
export { OPERATIONS_HISTORY_COLUMNS, TRANSACTION_REQUEST_TYPE_OPTIONS };
@@ -0,0 +1,3 @@
export * from './constants';
export * from './mocks';
export * from './localization';
@@ -0,0 +1,38 @@
import { SBP_OPERATIONS_HISTORY_FIELDS } from '../model';
import { TRANSACTION_REQUEST_CLIENT_STATUS } from '@/shared/models';
const LOCALIZATION = {
[TRANSACTION_REQUEST_CLIENT_STATUS.DRAFT]: 'Черновик',
[SBP_OPERATIONS_HISTORY_FIELDS.DOC_TYPE]: 'Операция',
[SBP_OPERATIONS_HISTORY_FIELDS.DOC_DATE]: 'Дата',
[SBP_OPERATIONS_HISTORY_FIELDS.CONTRACTOR]: 'Контрагент',
[SBP_OPERATIONS_HISTORY_FIELDS.CONTRACTOR_BANK]: 'Банк контрагента',
[SBP_OPERATIONS_HISTORY_FIELDS.AMOUNT]: 'Сумма',
[SBP_OPERATIONS_HISTORY_FIELDS.MERCHANT_ID]: 'Мерчант',
[SBP_OPERATIONS_HISTORY_FIELDS.STATUS]: 'Статус',
[SBP_OPERATIONS_HISTORY_FIELDS.MERCHANT_NAME]: 'Наименование мерчанта',
PERFORMED: 'Исполнен',
CANCELLED: 'Отменён',
IN_PROGRESS: 'В обработке',
EXPANDED_ROW_RECIPIENT_ACCOUNT: 'Счёт получателя',
EXPANDED_ROW_SENDER_ACCOUNT: 'Счёт отправителя',
EXPANDED_ROW_CONTRACTOR_NAME: 'Иия контрагента',
EXPANDED_ROW_CONTRACTOR_ADDRESS: 'Адрес контрагента',
EXPANDED_ROW_CONTRACTOR_BANK: 'Банк контрагента',
EXPANDED_ROW_PAYMENT_PURPOSE: 'Назначение платежа',
EXPANDED_ROW_TRANSACTION_DATE: 'Дата входящей операции',
EXPANDED_ROW_TRANSACTION_AMOUNT: 'Сумма входящей операции',
EXPANDED_ROW_TRANSACTION_COMMISSION: 'Комиссия',
EXPANDED_ROW_TRANSACTION_TRANSACTION_ID: 'ID транзакции',
EXPANDED_ROW_TRANSACTION_QR_ID: 'ID QR-кода',
EXPANDED_ROW_TRANSACTION_INCOME_CODE: 'Код вида дохода',
EXPANDED_ROW_TRANSACTION_BIC: 'БИК банка',
EXPANDED_ROW_TRANSACTION_AGENT: 'Транзакция при участии Агента ТСП',
RETURN_TEXT: 'Возврат',
TRANSFER_TEXT: 'Перевод',
INCOMING_PAYMENT: 'Входящий платёж',
INN_TEXT: 'ИНН',
BIC_TEXT: 'БИК',
};
export { LOCALIZATION };

Some files were not shown because too many files have changed in this diff Show More