From 93d16cf35cc753a87c6a1b2ecb166e4db6122dc9 Mon Sep 17 00:00:00 2001 From: vchikalkin Date: Thu, 11 Apr 2024 00:53:31 +0300 Subject: [PATCH] merge branch feature/cache-manager-admin --- apps/api/package.json | 1 + apps/api/src/proxy/proxy.controller.ts | 68 +++++++++++ apps/web/Components/Admin/Cache/Query.tsx | 77 +++++++++++++ apps/web/Components/Admin/Cache/QueryList.tsx | 25 +++++ .../Components/Admin/Cache/ReloadButton.tsx | 24 ++++ apps/web/Components/Admin/Cache/index.tsx | 58 ++++++++++ apps/web/Components/Admin/Cache/lib/hooks.tsx | 30 +++++ apps/web/Components/Admin/Cache/lib/utils.tsx | 23 ++++ apps/web/Components/Admin/Layout.tsx | 17 +++ apps/web/Components/Admin/index.ts | 2 + .../web/Components/Calculation/Form/index.jsx | 6 +- .../Page.jsx => Calculation/Layout.jsx} | 6 +- .../Output/PaymentsTable/config.ts | 0 .../Output/PaymentsTable/index.jsx | 0 .../Output/Results/config.ts | 0 .../Output/Results/index.jsx | 2 +- .../{ => Calculation}/Output/Validation.jsx | 6 +- .../{ => Calculation}/Output/index.jsx | 4 +- .../Components/Calculation/Settings/index.jsx | 2 +- apps/web/Components/Calculation/index.js | 2 - apps/web/Components/Calculation/index.ts | 4 + apps/web/Components/Layout/Auth.jsx | 8 +- apps/web/Components/Layout/Header.jsx | 3 +- apps/web/Components/Layout/Logo.jsx | 17 +-- apps/web/Components/Layout/Navigation.tsx | 48 ++++++++ apps/web/Components/Layout/index.jsx | 21 +++- apps/web/api/cache/query.ts | 42 +++++++ apps/web/api/cache/types.ts | 3 + apps/web/config/schema/env.js | 6 +- apps/web/config/schema/runtime-config.js | 4 + apps/web/config/urls.ts | 12 ++ apps/web/config/users.js | 1 + apps/web/constants/page.js | 3 + apps/web/constants/urls.js | 4 + apps/web/next.config.js | 27 +++++ apps/web/package.json | 1 + apps/web/pages/_app.jsx | 2 +- apps/web/pages/_document.jsx | 4 +- apps/web/pages/admin/cache.jsx | 69 ++++++++++++ apps/web/pages/index.jsx | 106 +++++++----------- apps/web/pages/unlimited.jsx | 66 ++++++++--- apps/web/public/site.webmanifest | 4 +- apps/web/stores/index.js | 5 +- apps/web/utils/page.tsx | 7 ++ apps/web/utils/user.ts | 51 +++++++++ packages/shared/.eslintrc.js | 10 ++ packages/shared/package.json | 13 +++ packages/shared/tsconfig.json | 6 + packages/shared/types/cache.ts | 4 + packages/ui/elements/index.ts | 2 + pnpm-lock.yaml | 24 +++- 51 files changed, 799 insertions(+), 131 deletions(-) create mode 100644 apps/web/Components/Admin/Cache/Query.tsx create mode 100644 apps/web/Components/Admin/Cache/QueryList.tsx create mode 100644 apps/web/Components/Admin/Cache/ReloadButton.tsx create mode 100644 apps/web/Components/Admin/Cache/index.tsx create mode 100644 apps/web/Components/Admin/Cache/lib/hooks.tsx create mode 100644 apps/web/Components/Admin/Cache/lib/utils.tsx create mode 100644 apps/web/Components/Admin/Layout.tsx create mode 100644 apps/web/Components/Admin/index.ts rename apps/web/Components/{Layout/Page.jsx => Calculation/Layout.jsx} (79%) rename apps/web/Components/{ => Calculation}/Output/PaymentsTable/config.ts (100%) rename apps/web/Components/{ => Calculation}/Output/PaymentsTable/index.jsx (100%) rename apps/web/Components/{ => Calculation}/Output/Results/config.ts (100%) rename apps/web/Components/{ => Calculation}/Output/Results/index.jsx (96%) rename apps/web/Components/{ => Calculation}/Output/Validation.jsx (94%) rename apps/web/Components/{ => Calculation}/Output/index.jsx (96%) delete mode 100644 apps/web/Components/Calculation/index.js create mode 100644 apps/web/Components/Calculation/index.ts create mode 100644 apps/web/Components/Layout/Navigation.tsx create mode 100644 apps/web/api/cache/query.ts create mode 100644 apps/web/api/cache/types.ts create mode 100644 apps/web/constants/page.js create mode 100644 apps/web/pages/admin/cache.jsx create mode 100644 apps/web/utils/page.tsx create mode 100644 apps/web/utils/user.ts create mode 100644 packages/shared/.eslintrc.js create mode 100644 packages/shared/package.json create mode 100644 packages/shared/tsconfig.json create mode 100644 packages/shared/types/cache.ts diff --git a/apps/api/package.json b/apps/api/package.json index a8e0dec..ab89ff1 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -45,6 +45,7 @@ "fastify": "^4.26.1", "jest": "^29.5.0", "prettier": "^3.0.0", + "shared": "workspace:*", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "29.1.1", diff --git a/apps/api/src/proxy/proxy.controller.ts b/apps/api/src/proxy/proxy.controller.ts index 49e11c6..d95b3fc 100644 --- a/apps/api/src/proxy/proxy.controller.ts +++ b/apps/api/src/proxy/proxy.controller.ts @@ -4,14 +4,18 @@ import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { All, Controller, + Delete, + Get, HttpException, HttpStatus, Inject, + Query, Req, Res, } from '@nestjs/common'; import type { Cache } from 'cache-manager'; import { FastifyReply, FastifyRequest } from 'fastify'; +import type { QueryItem } from 'shared/types/cache'; import { env } from 'src/config/env'; type RedisStore = Omit & { @@ -23,6 +27,7 @@ export class ProxyController { constructor( @Inject(CACHE_MANAGER) private readonly cacheManager: RedisStore, ) {} + @All('/graphql') public async graphql(@Req() req: FastifyRequest, @Res() reply: FastifyReply) { const { operationName, query, variables } = req.body as GQLRequest; @@ -56,4 +61,67 @@ export class ProxyController { return reply.send(data); } + + @Get('/get-queries') + public async getQueriesList(@Res() reply: FastifyReply) { + const res = await this.getAllQueries(); + + return reply.send(res); + } + + private async getAllQueries() { + const list = await this.cacheManager.store.keys('*'); + + return (Object.keys(queryTTL) as Array).reduce( + (acc, queryName) => { + const queries = list.filter((x) => x.split(' ').at(0) === queryName); + if (queries.length) { + const ttl = queryTTL[queryName]; + acc[queryName] = { queries, ttl }; + } + + return acc; + }, + {} as Record, + ); + } + + @Delete('/delete-query') + public async deleteQuery( + @Query('queryKey') queryKey: string, + @Res() reply: FastifyReply, + ) { + try { + await this.cacheManager.del(queryKey); + + return reply.send('ok'); + } catch (error) { + throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @Delete('/reset') + public async reset(@Res() reply: FastifyReply) { + try { + await this.cacheManager.reset(); + + return reply.send('ok'); + } catch (error) { + throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @Get('/get-query') + public async getQueryValue( + @Query('queryKey') queryKey: string, + @Res() reply: FastifyReply, + ) { + try { + const value = await this.cacheManager.get(queryKey); + + return reply.send(value); + } catch (error) { + throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } } diff --git a/apps/web/Components/Admin/Cache/Query.tsx b/apps/web/Components/Admin/Cache/Query.tsx new file mode 100644 index 0000000..19b1501 --- /dev/null +++ b/apps/web/Components/Admin/Cache/Query.tsx @@ -0,0 +1,77 @@ +import * as cacheApi from '@/api/cache/query'; +import { min } from '@/styles/mq'; +import { useQuery } from '@tanstack/react-query'; +import { memo, useState } from 'react'; +import styled from 'styled-components'; +import { Button, Collapse } from 'ui/elements'; +import { Flex } from 'ui/grid'; + +type QueryProps = { + readonly onDeleteQuery: () => Promise; + readonly queryKey: string; +}; + +const StyledPre = styled.pre` + max-height: 300px; + overflow-y: auto; + + ${min('desktop')} { + max-height: 800px; + } +`; + +export const Query = memo(({ onDeleteQuery, queryKey }: QueryProps) => { + const { data, refetch } = useQuery({ + enabled: false, + queryFn: ({ signal }) => signal && cacheApi.getQueryValue(queryKey, { signal }), + queryKey: ['admin', 'cache', 'query', queryKey], + refetchOnWindowFocus: false, + }); + + const [activeKey, setActiveKey] = useState(undefined); + const [deletePending, setDeletePending] = useState(false); + + const content = ( + <> + {JSON.stringify(data, null, 2)} + + + + + ); + + return ( + { + if (activeKey) { + setActiveKey(undefined); + } else { + setActiveKey(queryKey); + + refetch(); + } + }} + /> + ); +}); diff --git a/apps/web/Components/Admin/Cache/QueryList.tsx b/apps/web/Components/Admin/Cache/QueryList.tsx new file mode 100644 index 0000000..0035db4 --- /dev/null +++ b/apps/web/Components/Admin/Cache/QueryList.tsx @@ -0,0 +1,25 @@ +import { Query } from './Query'; +import * as cacheApi from '@/api/cache/query'; +import { memo, useMemo, useState } from 'react'; +import type { QueryItem } from 'shared/types/cache'; + +type QueryListProps = QueryItem; + +export const QueryList = memo(({ queries }: QueryListProps) => { + const [deletedQueries, setDeletedQueries] = useState([]); + + const activeQueries = useMemo( + () => queries.filter((queryKey) => !deletedQueries.includes(queryKey)), + [deletedQueries, queries] + ); + + function handleDeleteQuery(queryKey: string) { + return cacheApi + .deleteQuery(queryKey) + .then(() => setDeletedQueries([...deletedQueries, queryKey])); + } + + return activeQueries.map((queryKey) => ( + handleDeleteQuery(queryKey)} /> + )); +}); diff --git a/apps/web/Components/Admin/Cache/ReloadButton.tsx b/apps/web/Components/Admin/Cache/ReloadButton.tsx new file mode 100644 index 0000000..6ee039a --- /dev/null +++ b/apps/web/Components/Admin/Cache/ReloadButton.tsx @@ -0,0 +1,24 @@ +import { useState } from 'react'; +import { Button } from 'ui/elements'; +import { ReloadOutlined } from 'ui/elements/icons'; + +export function ReloadButton({ onClick }: { readonly onClick: () => Promise }) { + const [pending, setPending] = useState(false); + + return ( + + ); +} diff --git a/apps/web/Components/Admin/Cache/index.tsx b/apps/web/Components/Admin/Cache/index.tsx new file mode 100644 index 0000000..e344a54 --- /dev/null +++ b/apps/web/Components/Admin/Cache/index.tsx @@ -0,0 +1,58 @@ +import Background from '../../Layout/Background'; +import { useFilteredQueries } from './lib/hooks'; +import { QueryList } from './QueryList'; +import { ReloadButton } from './ReloadButton'; +import { min } from '@/styles/mq'; +import styled from 'styled-components'; +import { Collapse, Divider, Input } from 'ui/elements'; + +const Wrapper = styled(Background)` + padding: 4px 6px; + width: 100vw; + + ${min('tablet')} { + min-height: 790px; + } + + ${min('laptop')} { + padding: 4px 18px 10px; + width: 1280px; + } +`; + +const Flex = styled.div` + display: flex; + margin-bottom: 16px; + justify-content: space-between; + gap: 10px; +`; + +export function Cache() { + const { filteredQueries, refetch, setFilterString } = useFilteredQueries(); + + if (!filteredQueries) { + return
Загрузка...
; + } + + return ( + + Управление кэшем + + setFilterString(e.target.value)} + /> + + + ({ + children: , + key: queryGroupName, + label: queryGroupName, + }))} + /> + + ); +} diff --git a/apps/web/Components/Admin/Cache/lib/hooks.tsx b/apps/web/Components/Admin/Cache/lib/hooks.tsx new file mode 100644 index 0000000..7db2f6c --- /dev/null +++ b/apps/web/Components/Admin/Cache/lib/hooks.tsx @@ -0,0 +1,30 @@ +import { filterQueries } from './utils'; +import * as cacheApi from '@/api/cache/query'; +import type { ResponseQueries } from '@/api/cache/types'; +import { useQuery } from '@tanstack/react-query'; +import { useEffect, useState } from 'react'; +import { useDebounce } from 'use-debounce'; + +export function useFilteredQueries() { + const { data: queries, refetch } = useQuery({ + enabled: false, + queryFn: ({ signal }) => signal && cacheApi.getQueries({ signal }), + queryKey: ['admin', 'cache', 'queries'], + refetchOnWindowFocus: false, + }); + + const [filteredQueries, setFilteredQueries] = useState(queries); + const [filterString, setFilterString] = useState(''); + const [debouncedFilterString] = useDebounce(filterString, 350); + + useEffect(() => { + if (!debouncedFilterString) { + setFilteredQueries(queries); + } + if (queries && debouncedFilterString) { + setFilteredQueries(filterQueries(queries, debouncedFilterString)); + } + }, [debouncedFilterString, queries]); + + return { filteredQueries, queries, refetch, setFilterString }; +} diff --git a/apps/web/Components/Admin/Cache/lib/utils.tsx b/apps/web/Components/Admin/Cache/lib/utils.tsx new file mode 100644 index 0000000..d337cab --- /dev/null +++ b/apps/web/Components/Admin/Cache/lib/utils.tsx @@ -0,0 +1,23 @@ +import type { ResponseQueries } from '@/api/cache/types'; + +export function filterQueries(queriesObj: ResponseQueries, searchStr: string): ResponseQueries { + const filteredObj: ResponseQueries = {}; + + for (const key in queriesObj) { + if (key.includes(searchStr)) { + filteredObj[key] = queriesObj[key]; + } else { + const queries: string[] = []; + queriesObj[key].queries.forEach((queryKey) => { + if (queryKey.toLowerCase().includes(searchStr.toLowerCase())) { + queries.push(queryKey); + } + }); + if (queries.length) { + filteredObj[key] = { ...queriesObj[key], queries }; + } + } + } + + return filteredObj; +} diff --git a/apps/web/Components/Admin/Layout.tsx b/apps/web/Components/Admin/Layout.tsx new file mode 100644 index 0000000..7858dfe --- /dev/null +++ b/apps/web/Components/Admin/Layout.tsx @@ -0,0 +1,17 @@ +import { min } from '@/styles/mq'; +import type { PropsWithChildren } from 'react'; +import styled from 'styled-components'; + +const Flex = styled.div` + display: flex; + flex-direction: column; + + ${min('laptop')} { + flex-direction: row; + justify-content: center; + } +`; + +export function Layout({ children }: PropsWithChildren) { + return {children}; +} diff --git a/apps/web/Components/Admin/index.ts b/apps/web/Components/Admin/index.ts new file mode 100644 index 0000000..b99ea0e --- /dev/null +++ b/apps/web/Components/Admin/index.ts @@ -0,0 +1,2 @@ +export * from './Cache'; +export * from './Layout'; diff --git a/apps/web/Components/Calculation/Form/index.jsx b/apps/web/Components/Calculation/Form/index.jsx index 5448eee..2fd28b9 100644 --- a/apps/web/Components/Calculation/Form/index.jsx +++ b/apps/web/Components/Calculation/Form/index.jsx @@ -44,13 +44,13 @@ const ComponentWrapper = styled.div` } `; -function Form({ prune }) { +export function Form({ prune }) { return ( {formTabs .filter((tab) => !prune?.includes(tab.id)) - .map(({ id, title, Component }) => ( + .map(({ Component, id, title }) => ( @@ -61,5 +61,3 @@ function Form({ prune }) { ); } - -export default Form; diff --git a/apps/web/Components/Layout/Page.jsx b/apps/web/Components/Calculation/Layout.jsx similarity index 79% rename from apps/web/Components/Layout/Page.jsx rename to apps/web/Components/Calculation/Layout.jsx index 33f641e..c86e2fb 100644 --- a/apps/web/Components/Layout/Page.jsx +++ b/apps/web/Components/Calculation/Layout.jsx @@ -2,7 +2,7 @@ import { min } from '@/styles/mq'; import styled from 'styled-components'; import { Box } from 'ui/grid'; -export const Grid = styled(Box)` +export const Layout = styled(Box)` display: flex; flex-direction: column; gap: 10px; @@ -17,8 +17,4 @@ export const Grid = styled(Box)` grid-template-columns: 2fr 1fr 1.5fr; /* margin: 8px 5%; */ } - - ${min('desktop-xl')} { - margin: 8px 10% !important; - } `; diff --git a/apps/web/Components/Output/PaymentsTable/config.ts b/apps/web/Components/Calculation/Output/PaymentsTable/config.ts similarity index 100% rename from apps/web/Components/Output/PaymentsTable/config.ts rename to apps/web/Components/Calculation/Output/PaymentsTable/config.ts diff --git a/apps/web/Components/Output/PaymentsTable/index.jsx b/apps/web/Components/Calculation/Output/PaymentsTable/index.jsx similarity index 100% rename from apps/web/Components/Output/PaymentsTable/index.jsx rename to apps/web/Components/Calculation/Output/PaymentsTable/index.jsx diff --git a/apps/web/Components/Output/Results/config.ts b/apps/web/Components/Calculation/Output/Results/config.ts similarity index 100% rename from apps/web/Components/Output/Results/config.ts rename to apps/web/Components/Calculation/Output/Results/config.ts diff --git a/apps/web/Components/Output/Results/index.jsx b/apps/web/Components/Calculation/Output/Results/index.jsx similarity index 96% rename from apps/web/Components/Output/Results/index.jsx rename to apps/web/Components/Calculation/Output/Results/index.jsx index 7487274..b9af6c6 100644 --- a/apps/web/Components/Output/Results/index.jsx +++ b/apps/web/Components/Calculation/Output/Results/index.jsx @@ -22,7 +22,7 @@ const Wrapper = styled.div` `; const Results = observer(() => { - const { $results, $process } = useStore(); + const { $process, $results } = useStore(); const resultsValues = toJS($results.values); // eslint-disable-next-line no-negated-condition diff --git a/apps/web/Components/Output/Validation.jsx b/apps/web/Components/Calculation/Output/Validation.jsx similarity index 94% rename from apps/web/Components/Output/Validation.jsx rename to apps/web/Components/Calculation/Output/Validation.jsx index e404ded..239559f 100644 --- a/apps/web/Components/Output/Validation.jsx +++ b/apps/web/Components/Calculation/Output/Validation.jsx @@ -41,7 +41,7 @@ function getElementsErrors({ $calculation, $process }) { }); } -function getPaymentsTableErrors({ $tables, $process }) { +function getPaymentsTableErrors({ $process, $tables }) { const { payments } = $tables; const errors = payments.validation.getErrors(); const title = payments.validation.params.err_title; @@ -58,7 +58,7 @@ function getPaymentsTableErrors({ $tables, $process }) { )); } -function getInsuranceTableErrors({ $tables, $process }) { +function getInsuranceTableErrors({ $process, $tables }) { const { insurance } = $tables; const errors = insurance.validation.getErrors(); const title = insurance.validation.params.err_title; @@ -75,7 +75,7 @@ function getInsuranceTableErrors({ $tables, $process }) { )); } -function getFingapTableErrors({ $tables, $process }) { +function getFingapTableErrors({ $process, $tables }) { const { fingap } = $tables; const errors = fingap.validation.getErrors(); const title = fingap.validation.params.err_title; diff --git a/apps/web/Components/Output/index.jsx b/apps/web/Components/Calculation/Output/index.jsx similarity index 96% rename from apps/web/Components/Output/index.jsx rename to apps/web/Components/Calculation/Output/index.jsx index c39734d..7b80501 100644 --- a/apps/web/Components/Output/index.jsx +++ b/apps/web/Components/Calculation/Output/index.jsx @@ -42,7 +42,7 @@ const Wrapper = styled(Background)` } `; -const Output = observer(() => { +export const Output = observer(() => { const { $results } = useStore(); const [activeKey, setActiveKey] = useState(undefined); const { hasErrors } = useErrors(); @@ -69,5 +69,3 @@ const Output = observer(() => { ); }); - -export default Output; diff --git a/apps/web/Components/Calculation/Settings/index.jsx b/apps/web/Components/Calculation/Settings/index.jsx index 7c140bd..82d8948 100644 --- a/apps/web/Components/Calculation/Settings/index.jsx +++ b/apps/web/Components/Calculation/Settings/index.jsx @@ -17,7 +17,7 @@ const Wrapper = styled(Background)` } `; -export default function Settings() { +export function Settings() { const { $process } = useStore(); const mainRows = $process.has('Unlimited') diff --git a/apps/web/Components/Calculation/index.js b/apps/web/Components/Calculation/index.js deleted file mode 100644 index 591091d..0000000 --- a/apps/web/Components/Calculation/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default as Form } from './Form'; -export { default as Settings } from './Settings'; diff --git a/apps/web/Components/Calculation/index.ts b/apps/web/Components/Calculation/index.ts new file mode 100644 index 0000000..8b8b747 --- /dev/null +++ b/apps/web/Components/Calculation/index.ts @@ -0,0 +1,4 @@ +export * from './Form'; +export * from './Layout'; +export * from './Output'; +export * from './Settings'; diff --git a/apps/web/Components/Layout/Auth.jsx b/apps/web/Components/Layout/Auth.jsx index 45c17a3..98b632b 100644 --- a/apps/web/Components/Layout/Auth.jsx +++ b/apps/web/Components/Layout/Auth.jsx @@ -45,13 +45,7 @@ const Logout = styled.a` function Auth() { return ( - + Выход diff --git a/apps/web/Components/Layout/Header.jsx b/apps/web/Components/Layout/Header.jsx index b122daa..126d243 100644 --- a/apps/web/Components/Layout/Header.jsx +++ b/apps/web/Components/Layout/Header.jsx @@ -7,6 +7,7 @@ import { Flex } from 'ui/grid'; const HeaderContent = styled(Flex)` flex-direction: row; justify-content: space-between; + padding: 14px 12px; background: linear-gradient( 90deg, @@ -15,9 +16,7 @@ const HeaderContent = styled(Flex)` var(--color-tertiarty) 100% ); - padding: 14px 12px; ${min('laptop')} { - padding: 14px 12px; padding-left: 20px; } `; diff --git a/apps/web/Components/Layout/Logo.jsx b/apps/web/Components/Layout/Logo.jsx index 9488349..abb5479 100644 --- a/apps/web/Components/Layout/Logo.jsx +++ b/apps/web/Components/Layout/Logo.jsx @@ -5,6 +5,7 @@ import getColors from '@/styles/colors'; import { min } from '@/styles/mq'; import { observer } from 'mobx-react-lite'; import Image from 'next/image'; +import Link from 'next/link'; import logo from 'public/assets/images/logo-primary.svg'; import styled from 'styled-components'; import { Tag } from 'ui/elements'; @@ -33,8 +34,9 @@ const LogoText = styled.h3` const TagWrapper = styled.div` font-family: 'Montserrat'; font-weight: 500; - * { - font-size: 0.7rem; + + ${min('tablet')} { + margin: 0 5px; } `; @@ -42,12 +44,11 @@ const { COLOR_PRIMARY } = getColors(); const UnlimitedTag = observer(() => { const { $process } = useStore(); + if ($process.has('Unlimited')) { return ( - - без ограничений - + без ограничений ); } @@ -59,9 +60,11 @@ function Logo() { return ( - logo + + logo + - + Лизинговый Калькулятор diff --git a/apps/web/Components/Layout/Navigation.tsx b/apps/web/Components/Layout/Navigation.tsx new file mode 100644 index 0000000..a1c5375 --- /dev/null +++ b/apps/web/Components/Layout/Navigation.tsx @@ -0,0 +1,48 @@ +import Background from './Background'; +import type { MenuProps } from 'antd'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { Menu } from 'ui/elements'; +import { AppstoreOutlined, SettingOutlined } from 'ui/elements/icons'; + +const items: MenuProps['items'] = [ + { + children: [ + { + // icon: , + key: '/', + label: Главная, + }, + { + // icon: , + key: '/unlimited', + label: Без ограничений, + }, + ], + icon: , + key: 'home', + label: 'Приложение', + }, + { + children: [ + { + // icon: , + key: '/admin/cache', + label: Управление кэшем, + }, + ], + icon: , + key: 'admin', + label: 'Панель управления', + }, +]; + +export function AppNavigation() { + const { pathname } = useRouter(); + + return ( + + + + ); +} diff --git a/apps/web/Components/Layout/index.jsx b/apps/web/Components/Layout/index.jsx index f433777..86873dc 100644 --- a/apps/web/Components/Layout/index.jsx +++ b/apps/web/Components/Layout/index.jsx @@ -1,11 +1,22 @@ import Header from './Header'; -import { Flex } from 'ui/grid'; +import { AppNavigation } from './Navigation'; +import { min } from '@/styles/mq'; +import styled from 'styled-components'; -export default function Layout({ children }) { +const Main = styled.main` + margin: 8px 0; + + ${min('desktop-xl')} { + margin: 8px 10%; + } +`; + +export default function Layout({ children, user }) { return ( - + <>
-
{children}
- + {user?.admin ? : false} +
{children}
+ ); } diff --git a/apps/web/api/cache/query.ts b/apps/web/api/cache/query.ts new file mode 100644 index 0000000..070f59f --- /dev/null +++ b/apps/web/api/cache/query.ts @@ -0,0 +1,42 @@ +import type { ResponseQueries } from './types'; +import getUrls from '@/config/urls'; +import { withHandleError } from '@/utils/axios'; +import axios from 'axios'; + +const { + URL_CACHE_GET_QUERIES, + URL_CACHE_DELETE_QUERY, + URL_CACHE_RESET_QUERIES, + URL_CACHE_GET_QUERY, +} = getUrls(); + +export function getQueries({ signal }: { signal: AbortSignal }) { + return withHandleError(axios.get(URL_CACHE_GET_QUERIES, { signal })).then( + ({ data }) => data + ); +} + +export function deleteQuery(queryKey: string) { + return withHandleError( + axios.delete(URL_CACHE_DELETE_QUERY, { + params: { + queryKey, + }, + }) + ).then(({ data }) => data); +} + +export function reset() { + return withHandleError(axios.delete(URL_CACHE_RESET_QUERIES)).then(({ data }) => data); +} + +export function getQueryValue(queryKey: string, { signal }: { signal: AbortSignal }) { + return withHandleError( + axios.get(URL_CACHE_GET_QUERY, { + params: { + queryKey, + }, + signal, + }) + ).then(({ data }) => data); +} diff --git a/apps/web/api/cache/types.ts b/apps/web/api/cache/types.ts new file mode 100644 index 0000000..688b61e --- /dev/null +++ b/apps/web/api/cache/types.ts @@ -0,0 +1,3 @@ +import type { QueryItem } from 'shared/types/cache'; + +export type ResponseQueries = Record; diff --git a/apps/web/config/schema/env.js b/apps/web/config/schema/env.js index 58db832..59ccf2a 100644 --- a/apps/web/config/schema/env.js +++ b/apps/web/config/schema/env.js @@ -7,12 +7,16 @@ const envSchema = z.object({ SENTRY_DSN: z.string(), SENTRY_ENVIRONMENT: z.string(), URL_1C_TRANSTAX_DIRECT: z.string(), + URL_CACHE_DELETE_QUERY_DIRECT: z.string().default('http://api:3001/proxy/delete-query'), + URL_CACHE_GET_QUERIES_DIRECT: z.string().default('http://api:3001/proxy/get-queries'), + URL_CACHE_GET_QUERY_DIRECT: z.string().default('http://api:3001/proxy/get-query'), + URL_CACHE_RESET_QUERIES_DIRECT: z.string().default('http://api:3001/proxy/reset'), URL_CORE_CALCULATE_DIRECT: z.string(), URL_CORE_FINGAP_DIRECT: z.string(), URL_CRM_CREATEKP_DIRECT: z.string(), URL_CRM_DOWNLOADKP_BASE: z.string(), URL_CRM_GRAPHQL_DIRECT: z.string(), - URL_CRM_GRAPHQL_PROXY: z.string(), + URL_CRM_GRAPHQL_PROXY: z.string().default('http://api:3001/proxy/graphql'), URL_ELT_KASKO_DIRECT: z.string(), URL_ELT_OSAGO_DIRECT: z.string(), URL_GET_USER_DIRECT: z.string(), diff --git a/apps/web/config/schema/runtime-config.js b/apps/web/config/schema/runtime-config.js index cc6d691..ed5c1eb 100644 --- a/apps/web/config/schema/runtime-config.js +++ b/apps/web/config/schema/runtime-config.js @@ -13,6 +13,10 @@ const serverRuntimeConfigSchema = envSchema.pick({ SENTRY_DSN: true, SENTRY_ENVIRONMENT: true, URL_1C_TRANSTAX_DIRECT: true, + URL_CACHE_DELETE_QUERY_DIRECT: true, + URL_CACHE_GET_QUERIES_DIRECT: true, + URL_CACHE_GET_QUERY_DIRECT: true, + URL_CACHE_RESET_QUERIES_DIRECT: true, URL_CORE_CALCULATE_DIRECT: true, URL_CORE_FINGAP_DIRECT: true, URL_CRM_CREATEKP_DIRECT: true, diff --git a/apps/web/config/urls.ts b/apps/web/config/urls.ts index 37f9a6c..f06c419 100644 --- a/apps/web/config/urls.ts +++ b/apps/web/config/urls.ts @@ -22,6 +22,10 @@ function getUrls() { URL_ELT_KASKO_DIRECT, URL_ELT_OSAGO_DIRECT, URL_CRM_GRAPHQL_PROXY, + URL_CACHE_GET_QUERIES_DIRECT, + URL_CACHE_DELETE_QUERY_DIRECT, + URL_CACHE_RESET_QUERIES_DIRECT, + URL_CACHE_GET_QUERY_DIRECT, } = serverRuntimeConfigSchema.parse(serverRuntimeConfig); return { @@ -29,6 +33,10 @@ function getUrls() { PORT, SENTRY_DSN, URL_1C_TRANSTAX: URL_1C_TRANSTAX_DIRECT, + URL_CACHE_DELETE_QUERY: URL_CACHE_DELETE_QUERY_DIRECT, + URL_CACHE_GET_QUERIES: URL_CACHE_GET_QUERIES_DIRECT, + URL_CACHE_GET_QUERY: URL_CACHE_GET_QUERY_DIRECT, + URL_CACHE_RESET_QUERIES: URL_CACHE_RESET_QUERIES_DIRECT, URL_CORE_CALCULATE: URL_CORE_CALCULATE_DIRECT, URL_CORE_FINGAP: URL_CORE_FINGAP_DIRECT, URL_CRM_CREATEKP: URL_CRM_CREATEKP_DIRECT, @@ -44,6 +52,10 @@ function getUrls() { BASE_PATH, SENTRY_DSN, URL_1C_TRANSTAX: withBasePath(urls.URL_1C_TRANSTAX_PROXY), + URL_CACHE_DELETE_QUERY: withBasePath(urls.URL_CACHE_DELETE_QUERY_PROXY), + URL_CACHE_GET_QUERIES: withBasePath(urls.URL_CACHE_GET_QUERIES_PROXY), + URL_CACHE_GET_QUERY: withBasePath(urls.URL_CACHE_GET_QUERY_PROXY), + URL_CACHE_RESET_QUERIES: withBasePath(urls.URL_CACHE_RESET_QUERIES_PROXY), URL_CORE_CALCULATE: withBasePath(urls.URL_CORE_CALCULATE_PROXY), URL_CORE_FINGAP: withBasePath(urls.URL_CORE_FINGAP_PROXY), URL_CRM_CREATEKP: withBasePath(urls.URL_CRM_CREATEKP_PROXY), diff --git a/apps/web/config/users.js b/apps/web/config/users.js index b681d9d..97d821d 100644 --- a/apps/web/config/users.js +++ b/apps/web/config/users.js @@ -1,3 +1,4 @@ export const unlimitedRoles = ['Калькулятор без ограничений']; export const defaultRoles = ['Лизинговый калькулятор', 'МПЛ', 'Управляющий подразделением']; +export const adminRoles = ['Калькулятор без ограничений', 'Системный администратор']; export const usersSuper = ['akalinina', 'vchikalkin']; diff --git a/apps/web/constants/page.js b/apps/web/constants/page.js new file mode 100644 index 0000000..622a4a7 --- /dev/null +++ b/apps/web/constants/page.js @@ -0,0 +1,3 @@ +export const PAGE_TITLE = 'Лизинговый калькулятор - Эволюция'; +export const PAGE_DESCRIPTION = + 'Лизинговый калькулятор - Эволюция - Расчет лизинговых платежей - Создание КП'; diff --git a/apps/web/constants/urls.js b/apps/web/constants/urls.js index 280ac3b..e20cd01 100644 --- a/apps/web/constants/urls.js +++ b/apps/web/constants/urls.js @@ -1,5 +1,9 @@ module.exports = { URL_1C_TRANSTAX_PROXY: '/api/1c/transtax', + URL_CACHE_DELETE_QUERY_PROXY: '/api/admin/cache/delete', + URL_CACHE_GET_QUERIES_PROXY: '/api/admin/cache/get-queries', + URL_CACHE_GET_QUERY_PROXY: '/api/admin/cache/get-query', + URL_CACHE_RESET_QUERIES_PROXY: '/api/admin/cache/reset', URL_CORE_CALCULATE_PROXY: '/api/core/calculate', URL_CORE_FINGAP_PROXY: '/api/core/fingap', URL_CRM_CREATEKP_PROXY: '/api/crm/create-kp', diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 692cd63..ca3cbdc 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -36,6 +36,17 @@ module.exports = withSentryConfig( output: 'standalone', publicRuntimeConfig: publicRuntimeConfigSchema.parse(env), reactStrictMode: true, + + async redirects() { + return [ + { + source: '/admin', + destination: '/admin/cache', + permanent: true, + }, + ]; + }, + async rewrites() { return [ { @@ -66,6 +77,22 @@ module.exports = withSentryConfig( destination: env.URL_ELT_OSAGO_DIRECT, source: urls.URL_ELT_OSAGO_PROXY, }, + { + destination: env.URL_CACHE_GET_QUERIES_DIRECT, + source: urls.URL_CACHE_GET_QUERIES_PROXY, + }, + { + destination: env.URL_CACHE_DELETE_QUERY_DIRECT + '/:path*', + source: urls.URL_CACHE_DELETE_QUERY_PROXY + '/:path*', + }, + { + destination: env.URL_CACHE_RESET_QUERIES_DIRECT, + source: urls.URL_CACHE_RESET_QUERIES_PROXY, + }, + { + destination: env.URL_CACHE_GET_QUERY_DIRECT, + source: urls.URL_CACHE_GET_QUERY_PROXY, + }, ...favicons.map((fileName) => buildFaviconRewrite(`/${fileName}`)), ]; }, diff --git a/apps/web/package.json b/apps/web/package.json index aa5518b..bff01d0 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -58,6 +58,7 @@ "jest": "^29.4.3", "jest-environment-jsdom": "^29.3.1", "msw": "^1.1.0", + "shared": "workspace:*", "ts-jest": "^29.0.5", "typescript": "^5.3.3" }, diff --git a/apps/web/pages/_app.jsx b/apps/web/pages/_app.jsx index 6cb442a..2c4de9f 100644 --- a/apps/web/pages/_app.jsx +++ b/apps/web/pages/_app.jsx @@ -52,7 +52,7 @@ function App({ Component, pageProps }) { }} > - + diff --git a/apps/web/pages/_document.jsx b/apps/web/pages/_document.jsx index b7bf6d4..4e2f9f9 100644 --- a/apps/web/pages/_document.jsx +++ b/apps/web/pages/_document.jsx @@ -1,6 +1,8 @@ +/* eslint-disable react/no-danger */ /* eslint-disable @typescript-eslint/explicit-member-accessibility */ import { metaFavicon } from '@/config/meta'; import { withBasePath } from '@/config/urls'; +import { PAGE_DESCRIPTION } from '@/constants/page'; import Document, { Head, Html, Main, NextScript } from 'next/document'; import { ServerStyleSheet } from 'styled-components'; import { createCache, doExtraStyle, StyleProvider } from 'ui/tools'; @@ -43,7 +45,7 @@ export default class MyDocument extends Document { - + {metaFavicon} diff --git a/apps/web/pages/admin/cache.jsx b/apps/web/pages/admin/cache.jsx new file mode 100644 index 0000000..919fdb6 --- /dev/null +++ b/apps/web/pages/admin/cache.jsx @@ -0,0 +1,69 @@ +import { getQueries } from '@/api/cache/query'; +import initializeApollo from '@/apollo/client'; +import * as Admin from '@/Components/Admin'; +import { Error } from '@/Components/Common/Error'; +import { getPageTitle } from '@/utils/page'; +import { makeGetUserType } from '@/utils/user'; +import { dehydrate, QueryClient } from '@tanstack/react-query'; +import Head from 'next/head'; + +function Content() { + return ( + + + {getPageTitle('Управление кэшем')} + + + + ); +} + +export default function Page(props) { + if (props.statusCode !== 200) return ; + + return ; +} + +/** @type {import('next').GetServerSideProps} */ +export async function getServerSideProps({ req }) { + const { cookie = '' } = req.headers; + + const queryClient = new QueryClient(); + const apolloClient = initializeApollo(); + const getUserType = makeGetUserType({ apolloClient, queryClient }); + + try { + const user = await getUserType({ cookie }); + + if (!user.admin) { + return { + props: { + initialQueryState: dehydrate(queryClient), + statusCode: 403, + }, + }; + } + + await queryClient.prefetchQuery(['admin', 'cache', 'queries'], ({ signal }) => + getQueries({ signal }) + ); + + return { + props: { + calculation: {}, + initialApolloState: apolloClient.cache.extract(), + initialQueryState: dehydrate(queryClient), + statusCode: 200, + user, + }, + }; + } catch (error) { + return { + props: { + error: JSON.stringify(error), + initialQueryState: dehydrate(queryClient), + statusCode: 500, + }, + }; + } +} diff --git a/apps/web/pages/index.jsx b/apps/web/pages/index.jsx index 6fdefbf..737b129 100644 --- a/apps/web/pages/index.jsx +++ b/apps/web/pages/index.jsx @@ -1,12 +1,9 @@ -import { getUser } from '@/api/user/query'; import initializeApollo from '@/apollo/client'; import * as Calculation from '@/Components/Calculation'; import { Error } from '@/Components/Common/Error'; -import { Grid } from '@/Components/Layout/Page'; -import Output from '@/Components/Output'; -import { defaultRoles } from '@/config/users'; -import * as CRMTypes from '@/graphql/crm.types'; import * as hooks from '@/process/hooks'; +import { getPageTitle } from '@/utils/page'; +import { makeGetUserType } from '@/utils/user'; import { dehydrate, QueryClient } from '@tanstack/react-query'; import Head from 'next/head'; @@ -17,80 +14,59 @@ function Content() { hooks.useReactions(); return ( - + - Лизинговый калькулятор - Эволюция + {getPageTitle()} - - + + ); } -export default function Home(props) { +export default function Page(props) { if (props.statusCode !== 200) return ; return ; } -export const makeGetServerSideProps = ({ roles }) => - /** @type {import('next').GetServerSideProps} */ - ( - async function ({ req }) { - const { cookie = '' } = req.headers; +/** @type {import('next').GetServerSideProps} */ +export async function getServerSideProps({ req }) { + const { cookie = '' } = req.headers; - const queryClient = new QueryClient(); + const queryClient = new QueryClient(); + const apolloClient = initializeApollo(); + const getUserType = makeGetUserType({ apolloClient, queryClient }); - const user = await queryClient.fetchQuery(['user'], ({ signal }) => - getUser({ - headers: { - cookie, - }, - signal, - }) - ); + try { + const user = await getUserType({ cookie }); - const apolloClient = initializeApollo(); - - try { - const { - data: { systemuser }, - } = await apolloClient.query({ - fetchPolicy: 'network-only', - query: CRMTypes.GetSystemUserDocument, - variables: { - domainname: user.domainName, - }, - }); - - if (!systemuser?.roles?.some((x) => x?.name && roles.includes(x.name))) { - return { - props: { - initialQueryState: dehydrate(queryClient), - statusCode: 403, - }, - }; - } - - return { - props: { - calculation: {}, - initialApolloState: apolloClient.cache.extract(), - initialQueryState: dehydrate(queryClient), - statusCode: 200, - }, - }; - } catch (error) { - return { - props: { - error: JSON.stringify(error), - initialQueryState: dehydrate(queryClient), - statusCode: 500, - }, - }; - } + if (!user.default) { + return { + props: { + initialQueryState: dehydrate(queryClient), + statusCode: 403, + }, + }; } - ); -export const getServerSideProps = makeGetServerSideProps({ roles: defaultRoles }); + return { + props: { + calculation: {}, + initialApolloState: apolloClient.cache.extract(), + initialQueryState: dehydrate(queryClient), + statusCode: 200, + user, + }, + }; + } catch (error) { + return { + props: { + error: JSON.stringify(error), + initialQueryState: dehydrate(queryClient), + statusCode: 500, + }, + }; + } +} diff --git a/apps/web/pages/unlimited.jsx b/apps/web/pages/unlimited.jsx index e6bc64d..7b7fc0e 100644 --- a/apps/web/pages/unlimited.jsx +++ b/apps/web/pages/unlimited.jsx @@ -1,17 +1,13 @@ -import { makeGetServerSideProps } from '.'; +import initializeApollo from '@/apollo/client'; import * as Calculation from '@/Components/Calculation'; import { Error } from '@/Components/Common/Error'; -import { Grid } from '@/Components/Layout/Page'; -import Output from '@/Components/Output'; -import { unlimitedRoles } from '@/config/users'; import * as hooks from '@/process/hooks'; -import { useStore } from '@/stores/hooks'; +import { getPageTitle } from '@/utils/page'; +import { makeGetUserType } from '@/utils/user'; +import { dehydrate, QueryClient } from '@tanstack/react-query'; import Head from 'next/head'; function Content() { - const store = useStore(); - store.$process.add('Unlimited'); - hooks.useSentryScope(); hooks.useMainData(); hooks.useGetUsers(); @@ -19,24 +15,60 @@ function Content() { hooks.useReactions(); return ( - + - Лизинговый калькулятор без ограничений - Эволюция - + {getPageTitle('Без ограничений')} - - + + ); } -export default function Unlimited(props) { +export default function Page(props) { if (props.statusCode !== 200) return ; return ; } -export const getServerSideProps = makeGetServerSideProps({ - roles: unlimitedRoles, -}); +/** @type {import('next').GetServerSideProps} */ +export async function getServerSideProps({ req }) { + const { cookie = '' } = req.headers; + + const queryClient = new QueryClient(); + const apolloClient = initializeApollo(); + const getUserType = makeGetUserType({ apolloClient, queryClient }); + + try { + const user = await getUserType({ cookie }); + + if (!user.unlimited) { + return { + props: { + initialQueryState: dehydrate(queryClient), + statusCode: 403, + }, + }; + } + + return { + props: { + calculation: {}, + initialApolloState: apolloClient.cache.extract(), + initialQueryState: dehydrate(queryClient), + mode: 'unlimited', + statusCode: 200, + user, + }, + }; + } catch (error) { + return { + props: { + error: JSON.stringify(error), + initialQueryState: dehydrate(queryClient), + statusCode: 500, + }, + }; + } +} diff --git a/apps/web/public/site.webmanifest b/apps/web/public/site.webmanifest index a08938e..4b5886a 100644 --- a/apps/web/public/site.webmanifest +++ b/apps/web/public/site.webmanifest @@ -1,6 +1,6 @@ { - "name": "Лизинговый калькулятор | Эволюция", - "short_name": "Лизинговый калькулятор | Эволюция", + "name": "Лизинговый калькулятор - Эволюция", + "short_name": "Калькулятор", "start_url": "/", "icons": [ { diff --git a/apps/web/stores/index.js b/apps/web/stores/index.js index 047bebe..0594192 100644 --- a/apps/web/stores/index.js +++ b/apps/web/stores/index.js @@ -10,7 +10,7 @@ export function initializeStore(initialData) { const _store = store ?? new RootStore(); if (initialData) { - const { calculation, tables } = initialData; + const { calculation, tables, mode } = initialData; if (calculation?.values) _store.$calculation.$values.hydrate(calculation.values); if (calculation?.statuses) _store.$calculation.$status.hydrate(calculation.statuses); @@ -23,6 +23,9 @@ export function initializeStore(initialData) { values: tables.insurance.values, }); } + + _store.$process.clear(); + if (mode === 'unlimited') _store.$process.add('Unlimited'); } if (isServer()) return _store; diff --git a/apps/web/utils/page.tsx b/apps/web/utils/page.tsx new file mode 100644 index 0000000..a6ce041 --- /dev/null +++ b/apps/web/utils/page.tsx @@ -0,0 +1,7 @@ +import { PAGE_TITLE } from '@/constants/page'; + +export function getPageTitle(title?: string) { + if (!title) return PAGE_TITLE; + + return `${title} - ${PAGE_TITLE}`; +} diff --git a/apps/web/utils/user.ts b/apps/web/utils/user.ts new file mode 100644 index 0000000..ceaa552 --- /dev/null +++ b/apps/web/utils/user.ts @@ -0,0 +1,51 @@ +import { getUser } from '@/api/user/query'; +import { adminRoles, defaultRoles, unlimitedRoles } from '@/config/users'; +import * as CRMTypes from '@/graphql/crm.types'; +import type { ApolloClient, NormalizedCache } from '@apollo/client'; +import type { QueryClient } from '@tanstack/react-query'; +import { sift } from 'radash'; + +type GetUserTypeProps = { + cookie: string; +}; + +type UserType = { + admin: boolean; + default: boolean; + unlimited: boolean; +}; + +type MakeGetUserTypeProps = { + apolloClient: ApolloClient; + queryClient: QueryClient; +}; +export function makeGetUserType({ apolloClient, queryClient }: MakeGetUserTypeProps) { + return async function ({ cookie }: GetUserTypeProps): Promise { + const user = await queryClient.fetchQuery(['user'], ({ signal }) => + getUser({ + headers: { + cookie, + }, + signal, + }) + ); + + const { + data: { systemuser }, + } = await apolloClient.query({ + fetchPolicy: 'network-only', + query: CRMTypes.GetSystemUserDocument, + variables: { + domainname: user.domainName, + }, + }); + + const roles = systemuser?.roles ? sift(systemuser?.roles)?.map((x) => x?.name) : []; + + return { + admin: adminRoles.some((x) => roles?.includes(x)), + default: defaultRoles.some((x) => roles?.includes(x)), + unlimited: unlimitedRoles.some((x) => roles?.includes(x)), + }; + }; +} diff --git a/packages/shared/.eslintrc.js b/packages/shared/.eslintrc.js new file mode 100644 index 0000000..ee7b070 --- /dev/null +++ b/packages/shared/.eslintrc.js @@ -0,0 +1,10 @@ +const { createConfig } = require('@vchikalkin/eslint-config-awesome'); + +module.exports = createConfig('typescript', { + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: __dirname, + }, + ignorePatterns: ['*.config.js', '.eslintrc.js'], + root: true, +}); diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100644 index 0000000..57c8583 --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,13 @@ +{ + "name": "shared", + "version": "0.0.0", + "license": "MIT", + "scripts": { + "lint": "TIMING=1 eslint \"**/*.ts*\"" + }, + "devDependencies": { + "@vchikalkin/eslint-config-awesome": "^1.1.6", + "eslint": "^8.52.0", + "tsconfig": "workspace:*" + } +} diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json new file mode 100644 index 0000000..10fc0c0 --- /dev/null +++ b/packages/shared/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "tsconfig/common.json", + "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"], + "exclude": ["dist", "build", "node_modules"], + "compilerOptions": { "outDir": "./build", "lib": ["DOM", "ES2020"] } +} diff --git a/packages/shared/types/cache.ts b/packages/shared/types/cache.ts new file mode 100644 index 0000000..6ab0f62 --- /dev/null +++ b/packages/shared/types/cache.ts @@ -0,0 +1,4 @@ +export type QueryItem = { + queries: string[]; + ttl: number | false; +}; diff --git a/packages/ui/elements/index.ts b/packages/ui/elements/index.ts index 1071de9..9b089bb 100644 --- a/packages/ui/elements/index.ts +++ b/packages/ui/elements/index.ts @@ -11,9 +11,11 @@ export * from './Text'; export { Alert, Badge, + Collapse, Divider, Form, InputNumber, + Menu, message, notification, Result, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef0a4e1..bfc3003 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,6 +96,9 @@ importers: prettier: specifier: ^3.0.0 version: 3.2.5 + shared: + specifier: workspace:* + version: link:../../packages/shared source-map-support: specifier: ^0.5.21 version: 0.5.21 @@ -250,6 +253,9 @@ importers: msw: specifier: ^1.1.0 version: 1.3.2(typescript@5.3.3) + shared: + specifier: workspace:* + version: link:../../packages/shared ts-jest: specifier: ^29.0.5 version: 29.1.1(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.3.3) @@ -257,6 +263,18 @@ importers: specifier: ^5.3.3 version: 5.3.3 + packages/shared: + devDependencies: + '@vchikalkin/eslint-config-awesome': + specifier: ^1.1.6 + version: 1.1.6(@babel/eslint-plugin@7.23.5)(@babel/plugin-syntax-flow@7.23.3)(@babel/plugin-transform-react-jsx@7.23.4)(eslint-plugin-import@2.29.1)(eslint@8.57.0)(graphql@16.8.1)(prettier@3.2.5)(typescript@5.3.3) + eslint: + specifier: ^8.52.0 + version: 8.57.0 + tsconfig: + specifier: workspace:* + version: link:../tsconfig + packages/tools: devDependencies: '@vchikalkin/eslint-config-awesome': @@ -6907,7 +6925,7 @@ packages: '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.3.3) '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.3.3) eslint: 8.57.0 - jest: 29.7.0(@types/node@20.11.20)(ts-node@10.9.2) + jest: 29.7.0(@types/node@18.19.18) transitivePeerDependencies: - supports-color - typescript @@ -8151,7 +8169,7 @@ packages: '@graphql-tools/json-file-loader': 7.4.18(graphql@16.8.1) '@graphql-tools/load': 7.8.14(graphql@16.8.1) '@graphql-tools/merge': 8.4.2(graphql@16.8.1) - '@graphql-tools/url-loader': 7.17.18(@types/node@18.19.18)(graphql@16.8.1) + '@graphql-tools/url-loader': 7.17.18(@types/node@20.11.20)(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) cosmiconfig: 8.0.0 graphql: 16.8.1 @@ -13175,7 +13193,7 @@ packages: '@babel/core': 7.23.9 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.11.20)(ts-node@10.9.2) + jest: 29.7.0(@types/node@18.19.18) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2