feat:(TEAMMSBMOB-18327) - виджет популярных шаблонов
This commit is contained in:
@@ -39,8 +39,9 @@ const PATHS_DEPOSITS = {
|
||||
const PATHS_PAYMENTS = {
|
||||
HOME: PATHS.PAYMENTS,
|
||||
PAYMENT_ORDER: `${PATHS.PAYMENTS}/payment-order`,
|
||||
EDIT_OR_DETAILS: `${PATHS.PAYMENTS}/:id`,
|
||||
TEMPLATES: `${PATHS.PAYMENTS}/templates`,
|
||||
CREATE_TEMPLATE: `${PATHS.PAYMENTS}/templates/new`,
|
||||
EDIT_OR_DETAILS: `${PATHS.PAYMENTS}/:id`,
|
||||
} as const;
|
||||
|
||||
const STATEMENTS_AND_INQUIRIES_PATHS = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { CreateTemplatePage } from '@/pages/CreateTemplatePage';
|
||||
import { OrderDetailsPage } from '@/pages/OrderDetailsPage';
|
||||
import { PaymentOrderPage } from '@/pages/PaymentOrderPage';
|
||||
import { PaymentsMainPage } from '@/pages/PaymentsMainPage';
|
||||
@@ -9,7 +10,8 @@ const AppRouter = () => (
|
||||
<Switch>
|
||||
<Route exact component={PaymentsMainPage} path={PATHS.HOME.PATH} />
|
||||
<Route component={PaymentOrderPage} path={PATHS.PAYMENT_ORDER.PATH} />
|
||||
<Route component={TemplatesPage.Page} path={PATHS.TEMPLATES.PATH} />
|
||||
<Route exact component={TemplatesPage.Page} path={PATHS.TEMPLATES.PATH} />
|
||||
<Route component={CreateTemplatePage.Page} path={PATHS.CREATE_TEMPLATE.PATH} />
|
||||
<Route exact component={OrderDetailsPage.Page} path={PATHS.EDIT_OR_DETAILS.PATH} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './localization';
|
||||
@@ -0,0 +1,5 @@
|
||||
const LOCALIZATION = {
|
||||
TITLE: 'Создание шаблона',
|
||||
};
|
||||
|
||||
export { LOCALIZATION };
|
||||
@@ -0,0 +1 @@
|
||||
export * from './ui';
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { ReactElement } from 'react';
|
||||
import { PageLayoutWithSections } from '@msb/shared';
|
||||
import { LOCALIZATION } from '../constants';
|
||||
import { PageHeader, PageLayout } from '@/shared/ui';
|
||||
|
||||
namespace CreateTemplatePage {
|
||||
export const Page = (): ReactElement => (
|
||||
<PageLayoutWithSections>
|
||||
<PageLayout>
|
||||
<PageHeader.Element title={LOCALIZATION.TITLE} />
|
||||
</PageLayout>
|
||||
</PageLayoutWithSections>
|
||||
);
|
||||
}
|
||||
|
||||
export { CreateTemplatePage };
|
||||
@@ -0,0 +1 @@
|
||||
export * from './CreateTemplatePage';
|
||||
@@ -30,6 +30,7 @@ import { useRublePaymentClient } from '@/entities/payments';
|
||||
import { CreatePaymentButton } from '@/features/CreatePaymentButton';
|
||||
import { PAYMENTS_YM_EVENTS, PATHS } from '@/shared/constants';
|
||||
import { AsideInformerList } from '@/widgets/AsideInformerList';
|
||||
import { FrequentlyUsedTemplates } from '@/widgets/FrequentlyUsedTemplates';
|
||||
import { PaymentsListContent } from '@/widgets/PaymentsListContent';
|
||||
|
||||
const PaymentsMainPage = (): ReactElement => {
|
||||
@@ -110,6 +111,10 @@ const PaymentsMainPage = (): ReactElement => {
|
||||
}
|
||||
};
|
||||
|
||||
const organizationId = FrequentlyUsedTemplates.useAllowWidgetShow()
|
||||
? FrequentlyUsedTemplates.getOrganizationId(userAuthorities)
|
||||
: undefined;
|
||||
|
||||
if (isLoading) {
|
||||
return <PaymentsListSkeleton />;
|
||||
}
|
||||
@@ -156,7 +161,12 @@ const PaymentsMainPage = (): ReactElement => {
|
||||
return (
|
||||
<PageLayoutWithSections
|
||||
asideInMobile
|
||||
aside={<AsideInformerList informers={INFORMERS_LIST_ITEMS} />}
|
||||
aside={
|
||||
<>
|
||||
<AsideInformerList informers={INFORMERS_LIST_ITEMS} />
|
||||
{organizationId && <FrequentlyUsedTemplates.Element organizationId={organizationId} />}
|
||||
</>
|
||||
}
|
||||
header={
|
||||
<S.HeaderWrapper>
|
||||
<Title.H1>{LOCALIZATION.PAYMENTS_TITLE}</Title.H1>
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { type ReactElement, useMemo } from 'react';
|
||||
import { AUTHORITIES, checkOrganizationsHavePermission, PageLayoutWithSections, SystemResponseStatus, useAppContext } from '@msb/shared';
|
||||
import {
|
||||
AUTHORITIES,
|
||||
checkOrganizationsHavePermission,
|
||||
PageLayoutWithSections,
|
||||
SystemResponseStatus,
|
||||
useAppContext,
|
||||
useRedirect,
|
||||
} from '@msb/shared';
|
||||
import { TemplatesList } from '../../../features';
|
||||
import { LOCALIZATION } from '../constants';
|
||||
import { PATHS } from '@/shared/constants';
|
||||
import { PageHeader, PageLayout } from '@/shared/ui';
|
||||
|
||||
namespace TemplatesPage {
|
||||
@@ -14,11 +22,12 @@ namespace TemplatesPage {
|
||||
|
||||
return organizations.length > 0 ? organizations[0] : '';
|
||||
}, [userAuthorities]);
|
||||
const createTemplateNavigation = useRedirect(PATHS.CREATE_TEMPLATE.PATH);
|
||||
|
||||
return (
|
||||
<PageLayoutWithSections>
|
||||
<PageLayout>
|
||||
<PageHeader.Element buttonTitle={LOCALIZATION.CREATE} title={LOCALIZATION.TITLE} />
|
||||
<PageHeader.Element buttonAction={createTemplateNavigation} buttonTitle={LOCALIZATION.CREATE} title={LOCALIZATION.TITLE} />
|
||||
{organizationId === '' ? (
|
||||
<SystemResponseStatus
|
||||
description={LOCALIZATION.ERROR.DESCRIPTION}
|
||||
@@ -46,30 +55,3 @@ namespace TemplatesPage {
|
||||
}
|
||||
|
||||
export { TemplatesPage };
|
||||
|
||||
/*
|
||||
{!isLoading && model ? (
|
||||
<Flex column mb="7">
|
||||
<PageHeader.Element
|
||||
badge={{ title: model.presentation.badge, type: model.presentation.badgeType }}
|
||||
subtitle={model.presentation.subtitle}
|
||||
title={LOCALIZATION.TITLE}
|
||||
/>
|
||||
<DescriptionList dataName="payment-card-view">
|
||||
{model.presentation.records.map(record =>
|
||||
record.value ? (
|
||||
<DescriptionRow key={record.name} label={record.title} labelWidth="30%" name={record.name}>
|
||||
{record.value}
|
||||
</DescriptionRow>
|
||||
) : (
|
||||
<Flex row mb="3" mt="6">
|
||||
<Title.H5 key={record.name}>{record.title}</Title.H5>
|
||||
</Flex>
|
||||
)
|
||||
)}
|
||||
</DescriptionList>
|
||||
</Flex>
|
||||
) : (
|
||||
<Flex column mb="7" />
|
||||
)}
|
||||
* */
|
||||
|
||||
@@ -5,6 +5,7 @@ const PATHS = {
|
||||
PAYMENT_ORDER: { PATH: PATHS_PAYMENTS.PAYMENT_ORDER, TITLE: 'Платёж юридическому или физическому лицу' },
|
||||
EDIT_OR_DETAILS: { PATH: PATHS_PAYMENTS.EDIT_OR_DETAILS, TITLE: '' },
|
||||
TEMPLATES: { PATH: PATHS_PAYMENTS.TEMPLATES, TITLE: '' },
|
||||
CREATE_TEMPLATE: { PATH: PATHS_PAYMENTS.CREATE_TEMPLATE, TITLE: '' },
|
||||
} as const;
|
||||
|
||||
export { PATHS };
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './localization';
|
||||
@@ -0,0 +1,14 @@
|
||||
const LOCALIZATION = {
|
||||
TITLE: 'Шаблоны',
|
||||
BUTTON: 'Показать все',
|
||||
ERROR: {
|
||||
TEXT: 'Данные не загрузились',
|
||||
BUTTON: 'Обновить',
|
||||
},
|
||||
EMPTY: {
|
||||
TEXT: 'Создавайте шаблоны для быстрого заполнения платежей',
|
||||
BUTTON: 'Создать',
|
||||
},
|
||||
};
|
||||
|
||||
export { LOCALIZATION };
|
||||
@@ -0,0 +1 @@
|
||||
export * from './ui';
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const webkitBox = '-webkit-box';
|
||||
const fontSizeTitle = 18; // '18px'
|
||||
const lineHeightTitle = 1.33; // '24px'
|
||||
const lineClamp = 2;
|
||||
|
||||
const Container = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 20px;
|
||||
grid-template-rows: 1fr;
|
||||
gap: 4px 16px;
|
||||
position: relative;
|
||||
grid-template-areas: 'title icon';
|
||||
`;
|
||||
|
||||
const IconWrapper = styled.div(() => ({
|
||||
gridArea: 'icon',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'right',
|
||||
justifyItems: 'right',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
verticalAlign: 'top',
|
||||
}));
|
||||
|
||||
const TitleWrapper = styled.div(() => ({
|
||||
gridArea: 'title',
|
||||
'& div': {
|
||||
display: webkitBox,
|
||||
maxHeight: `${fontSizeTitle * lineHeightTitle * lineClamp}px`,
|
||||
margin: '0 auto',
|
||||
fontSize: fontSizeTitle,
|
||||
lineHeight: lineHeightTitle,
|
||||
'-webkit-line-clamp': String(lineClamp),
|
||||
'-webkit-box-orient': 'vertical',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
},
|
||||
}));
|
||||
|
||||
export { Container, IconWrapper, TitleWrapper };
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
import { type ReactElement, useMemo } from 'react';
|
||||
import { Button, ButtonLink, Skeleton } from '@fractal-ui/core';
|
||||
import { Icon } from '@fractal-ui/library';
|
||||
import { Title, Text } from '@fractal-ui/styling';
|
||||
import { type AuthoritiesResponseDto, FEATURE_TOGGLE_NAMES, usePaymentTemplate } from '@msb/http';
|
||||
import { AUTHORITIES, checkOrganizationsHavePermission, Flex, range, useAppContext, useFeatureToggles, useRedirect } from '@msb/shared';
|
||||
import { LOCALIZATION } from '../constants';
|
||||
import * as S from './FrequentlyUsedTemplates.styles';
|
||||
import { PATHS } from '@/shared/constants';
|
||||
|
||||
namespace FrequentlyUsedTemplates {
|
||||
export interface Props {
|
||||
organizationId: string;
|
||||
}
|
||||
|
||||
export const getOrganizationId = (userAuthorities?: AuthoritiesResponseDto): string | undefined => {
|
||||
const organizations = checkOrganizationsHavePermission(userAuthorities?.data.clientAuthorities || {}, [
|
||||
AUTHORITIES.PAYMENT.TEMPLATE_VIEW,
|
||||
]);
|
||||
|
||||
return organizations.length > 0 ? organizations[0] : undefined;
|
||||
};
|
||||
|
||||
export const useAllowWidgetShow = (): boolean => {
|
||||
const { userAuthorities } = useAppContext();
|
||||
const { isEnabled: isTemplatesEnabled } = useFeatureToggles(FEATURE_TOGGLE_NAMES.PAYMENT_TEMPLATES);
|
||||
const organizationId = useMemo(() => getOrganizationId(userAuthorities), [userAuthorities]);
|
||||
|
||||
return isTemplatesEnabled && organizationId !== undefined && organizationId.length > 0;
|
||||
};
|
||||
|
||||
const loadingSkeletonHeight = 8;
|
||||
const maxRecordsCount = 3;
|
||||
|
||||
export const Element = ({ organizationId }: Props): ReactElement => {
|
||||
const { data, isLoading, isError, refetch } = usePaymentTemplate(organizationId, 0, 3, false);
|
||||
const records = useMemo(
|
||||
() =>
|
||||
data && Array.isArray(data.pages)
|
||||
? data?.pages
|
||||
.reduce((accumulator, item) => accumulator.concat(item), [])
|
||||
.sort((a, b) => new Date(b.usedAt ?? 0).getTime() - new Date(a.usedAt ?? 0).getTime())
|
||||
.filter((record, index) => index < maxRecordsCount)
|
||||
: [],
|
||||
[data]
|
||||
);
|
||||
const createTemplate = useRedirect(PATHS.CREATE_TEMPLATE.PATH);
|
||||
const listTemplate = useRedirect(PATHS.TEMPLATES.PATH);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
column
|
||||
backgroundColor="bg.primary"
|
||||
borderRadius="16px"
|
||||
boxShadow="0 0 16px 0 rgba(78, 88, 134, 0.04)"
|
||||
gap={4}
|
||||
padding="4"
|
||||
width="100%"
|
||||
>
|
||||
<Flex column gap={4} mb="4">
|
||||
<Title.H4>{LOCALIZATION.TITLE}</Title.H4>
|
||||
{isLoading &&
|
||||
[...range(0, 2)].map(index => (
|
||||
<Skeleton key={index} dataName="freq-template-record" height={loadingSkeletonHeight} variant="card" />
|
||||
))}
|
||||
{!isLoading && isError && <Text.P2 color="text.secondary">{LOCALIZATION.ERROR.TEXT}</Text.P2>}
|
||||
{!isError && !isLoading && records.length === 0 && <Text.P2 color="text.secondary">{LOCALIZATION.EMPTY.TEXT}</Text.P2>}
|
||||
{!isError &&
|
||||
!isLoading &&
|
||||
records.length > 0 &&
|
||||
records.map(record => (
|
||||
<ButtonLink key={record.id} align="left" dataAction="navigate-to-template">
|
||||
<S.Container>
|
||||
<S.TitleWrapper>
|
||||
<Text.P1>{record.templateName}</Text.P1>
|
||||
</S.TitleWrapper>
|
||||
{!record.accountExists && (
|
||||
<S.IconWrapper>
|
||||
<Icon color="text.warning" name="AttentionFilled" />
|
||||
</S.IconWrapper>
|
||||
)}
|
||||
</S.Container>
|
||||
</ButtonLink>
|
||||
))}
|
||||
</Flex>
|
||||
{(isError || !isLoading) && (
|
||||
<Button
|
||||
dataAction="refetch-templates"
|
||||
shape="default"
|
||||
size="M"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
if (isError) {
|
||||
refetch();
|
||||
} else if (records.length === 0) {
|
||||
createTemplate();
|
||||
} else {
|
||||
listTemplate();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* eslint-disable-next-line no-nested-ternary */}
|
||||
{isError ? LOCALIZATION.ERROR.BUTTON : records.length === 0 ? LOCALIZATION.EMPTY.BUTTON : LOCALIZATION.BUTTON}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export { FrequentlyUsedTemplates };
|
||||
@@ -0,0 +1 @@
|
||||
export * from './FrequentlyUsedTemplates';
|
||||
Reference in New Issue
Block a user