浏览代码

feat: 首页模拟展示

xyh 2 年之前
父节点
当前提交
9419cc89a1

+ 0 - 6
cypress/e2e/login.cy.ts

@@ -19,10 +19,4 @@ describe('登录页面', function() {
 
     cy.url().should('contain', '/login');
   });
-
-  it.only('菜单', function() {
-    cy.getTestId('menu').children().should('have.length', 4);
-
-    cy.getTestId('menu').children().first().should('include.text', '首页');
-  });
 });

+ 4 - 2
cypress/e2e/storage.cy.ts

@@ -13,13 +13,15 @@ import {
   validateExport,
   exportIntercept,
   intoMenu,
+  dictionaryIntercept,
 } from './utils';
 
-describe.skip('库位管理', function() {
+describe('库位管理', function() {
   beforeEach(function() {
     loginIntercept();
     menuIntercept();
     loginSetup();
+    dictionaryIntercept();
     intoMenu('仓库管理', '库位管理');
   });
 
@@ -57,7 +59,7 @@ describe.skip('库位管理', function() {
         MODAL_NAME = 'storage_modal',
         LABEL = '库位';
 
-  it('表格', function() {
+  it.only('表格', function() {
     validateTableList(TABLE_NAME);
 
     const validate = validateTableSearch(TABLE_NAME);

+ 1 - 0
packages/app/package.json

@@ -28,6 +28,7 @@
     "react-hook-form": "^7.43.0",
     "react-modal": "^3.16.1",
     "react-router-dom": "^6.8.0",
+    "recharts": "^2.4.3",
     "use-context-selector": "^1.4.1",
     "yup": "^0.32.11",
     "zustand": "^4.3.2"

二进制
packages/app/src/assets/images/iconLogo.png


二进制
packages/app/src/assets/images/login/background.webp


文件差异内容过多而无法显示
+ 0 - 7
packages/app/src/assets/images/logo.svg


+ 1 - 0
packages/app/src/models/response/stream.ts

@@ -19,6 +19,7 @@ export type WarehousingListData = {
    * 生产日期
    */
   producDate: string;
+  /** 入库时间 */
   scrq: string;
   /**
    * 序列号

+ 5 - 2
packages/app/src/pages/home/index.tsx

@@ -4,10 +4,11 @@ import {Layout} from 'antd';
 import logo from '@assets/images/logo.png';
 import Menu from './menu';
 import User from './user';
-import {Outlet} from 'react-router-dom';
+import {Link, Outlet} from 'react-router-dom';
 import {Auth, ErrorBoundary, Footer, Jurisdiction} from '@components';
 import {useMenu} from './hooks';
 import HomeSkeleton from './Skeleton';
+import {HOME_PATH} from '@routes';
 
 const Home: FC = function() {
   const menus = useMenu();
@@ -15,7 +16,9 @@ const Home: FC = function() {
   return (
     <Layout className='container'>
       <Layout.Header className={css.header}>
-        <img src={logo} alt='logo' className={css.logo} />
+        <Link to={HOME_PATH}>
+          <img src={logo} alt='logo' className={css.logo} />
+        </Link>
         <User />
       </Layout.Header>
 

+ 5 - 0
packages/app/src/pages/home/menu/hooks.tsx

@@ -1,3 +1,4 @@
+import {HOME_PATH} from '@routes';
 import {menuStore} from '@stores';
 import {ParseMenuType} from '@utils';
 import {useEffect, useState} from 'react';
@@ -35,5 +36,9 @@ export function useOpenKey(menus: ParseMenuType[]) {
     if (route.id !== current[0]) setCurrent([route.id]);
   }, [current, pages, pathname]);
 
+  useEffect(function() {
+    pathname === HOME_PATH && setOpenKey([]);
+  }, [pathname]);
+
   return [{openKeys, current}, {onOpenChange, onClick}] as const;
 }

+ 12 - 2
packages/app/src/pages/login/index.module.css

@@ -3,8 +3,11 @@
   flex-direction: column;
   align-items: center;
   justify-content: center;
-  background: #eee;
-  background-image: url('@assets/images/login/background.jpg');
+  background-color: #eee;
+  background-image: url('@assets/images/login/background.webp');
+  background-repeat: no-repeat;
+  background-position: bottom center;
+  background-size: contain;
 }
 
 .card {
@@ -30,3 +33,10 @@
   position: fixed;
   bottom: 16px;
 }
+
+.logo {
+  position: fixed;
+  top: 24px;
+  left: 24px;
+  width: 60px;
+}

+ 2 - 2
packages/app/src/pages/login/index.tsx

@@ -5,18 +5,18 @@ import {Card} from 'antd';
 import Lottie from 'lottie-react';
 import LoginData from '@assets/json/loginData.json';
 import LoginInfo from './info';
-import {Footer} from '@components';
+import logo from '@assets/images/iconLogo.png';
 
 const Login: FC = function() {
   return (
     <main className={cla(['container', css.main])}>
+      <img src={logo} className={css.logo} />
       <Card bordered={false} className={css.card}>
         <div className={css.info}>
           <Lottie animationData={LoginData} className={css.lottie} />
           <LoginInfo />
         </div>
       </Card>
-      <Footer className={css.footer} color='#eee' />
     </main>
   );
 };

+ 1 - 1
packages/app/src/pages/login/info/index.tsx

@@ -10,7 +10,7 @@ const LoginInfo: FC = function() {
 
   return (
     <div className={css.loginInfo}>
-      <h1>仓储物流管理系统</h1>
+      <h1>仓储物流管理平台</h1>
 
       <form className={css.loginForm} onSubmit={onSubmit} data-testid='login_form'>
         <Controller

+ 24 - 0
packages/app/src/pages/main/group/Item.tsx

@@ -0,0 +1,24 @@
+import {ChildrenFC} from '@utils';
+import css from './index.module.css';
+import {Card, Col} from 'antd';
+import classNames from 'classnames';
+
+type Props = {
+  title: string,
+  content: string,
+};
+
+const GroupItem: ChildrenFC<Props> = function({children, title, content}) {
+  return (
+    <Col span={6}>
+      <Card title={title}>
+        <p className={css.cardContent}>{content}</p>
+        <div className={classNames([css.cardExpand, 'main-card-item-expand'])}>
+          {children}
+        </div>
+      </Card>
+    </Col>
+  );
+};
+
+export default GroupItem;

+ 49 - 0
packages/app/src/pages/main/group/hooks.ts

@@ -0,0 +1,49 @@
+import {useEventListener, useThrottleFn} from 'ahooks';
+import {useEffect, useState} from 'react';
+
+export function useCardRect() {
+  const [rect, setRect] = useState({width: 0, height: 0});
+
+  const {run, cancel} = useThrottleFn(
+    function() {
+      const cardElement = document.querySelector('.main-card-item-expand');
+      if (cardElement) {
+        const {width, height} = cardElement.getBoundingClientRect();
+
+        setRect({width, height});
+      }
+    },
+    {
+      wait: 300,
+      leading: false,
+      trailing: true,
+    },
+  );
+
+  useEventListener('resize', run);
+
+  useEffect(
+    function() {
+      run();
+
+      return cancel;
+    },
+    [cancel, run],
+  );
+
+  return rect;
+}
+
+export function useMockAnimate() {
+  const [width, setWidth] = useState(0);
+
+  useEffect(function() {
+    const timer = setTimeout(function() {
+      setWidth(50);
+    }, 500);
+
+    return () => clearTimeout(timer);
+  }, []);
+
+  return width;
+}

+ 40 - 0
packages/app/src/pages/main/group/index.module.css

@@ -0,0 +1,40 @@
+.process {
+  width: 100%;
+  height: 12px;
+  overflow: hidden;
+  background-color: #eee;
+  border-radius: 6px;
+}
+
+.process-inner {
+  display: block;
+  height: 100%;
+  background-image: linear-gradient(to right, #baffff, #1fc5c5);
+  border-radius: 6px;
+  transition: width 500ms linear;
+}
+
+.card-title {
+  font-size: 14px;
+  font-weight: normal;
+  color: rgb(0 0 0 / 55%);
+}
+
+.card-content {
+  height: 30px;
+  margin-top: 12px;
+  overflow: hidden;
+  font-size: 30px;
+  line-height: 30px;
+  color: black;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.card-expand {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  height: 70px;
+  margin-top: 24px;
+}

+ 90 - 0
packages/app/src/pages/main/group/index.tsx

@@ -0,0 +1,90 @@
+import {FC} from 'react';
+import GroupItem from './Item';
+import {Row} from 'antd';
+import {AreaChart, Area, BarChart, Bar, ResponsiveContainer} from 'recharts';
+import {useCardRect, useMockAnimate} from './hooks';
+import css from './index.module.css';
+
+const areaData = [
+  {
+    name: 'Page A',
+    value: 4000,
+  },
+  {
+    name: 'Page B',
+    value: 3000,
+  },
+  {
+    name: 'Page C',
+    value: 2000,
+  },
+  {
+    name: 'Page D',
+    value: 2780,
+  },
+];
+
+const lineData = [
+  {
+    name: 'Page A',
+    value: 4000,
+  },
+  {
+    name: 'Page B',
+    value: 3000,
+  },
+  {
+    name: 'Page C',
+    value: 2000,
+  },
+  {
+    name: 'Page D',
+    value: 2780,
+  },
+  {
+    name: 'Page e',
+    value: 2280,
+  },
+  {
+    name: 'Page f',
+    value: 2280,
+  },
+];
+
+const Group: FC = function() {
+  const rect = useCardRect();
+  const processWidth = useMockAnimate();
+
+  return (
+    <Row gutter={12}>
+      <GroupItem title='今日预计到货数量(单)' content='20'>
+        <ResponsiveContainer>
+          <AreaChart data={areaData}>
+            <Area type='monotone' dataKey='value' stroke='#00a6ca' fill='#66e3ff' />
+          </AreaChart>
+        </ResponsiveContainer>
+      </GroupItem>
+      <GroupItem title='今日到货率' content='50%'>
+        <div className={css.process}>
+          <span className={css.processInner} style={{width: `${processWidth}%`}} />
+        </div>
+      </GroupItem>
+      <GroupItem title='原料入库数量(台)' content='2000'>
+        <ResponsiveContainer>
+          <BarChart {...rect} data={lineData}>
+            <Bar type='monotone' dataKey='value' fill='#6395F9' />
+          </BarChart>
+        </ResponsiveContainer>
+      </GroupItem>
+      <GroupItem title='原料出库数量(台)' content='100'>
+        <ResponsiveContainer>
+          <BarChart {...rect} data={lineData}>
+            <Bar type='monotone' dataKey='value' fill='#c93756' />
+          </BarChart>
+        </ResponsiveContainer>
+      </GroupItem>
+    </Row>
+  );
+};
+
+export default Group;

+ 0 - 3
packages/app/src/pages/main/index.module.css

@@ -1,3 +0,0 @@
-.main {
-  padding: var(--content-padding);
-}

+ 7 - 16
packages/app/src/pages/main/index.tsx

@@ -1,23 +1,14 @@
-import css from './index.module.css';
-import {Card} from 'antd';
 import {FC} from 'react';
+import Group from './group';
+import Stream from './stream';
+import Other from './other';
 
 const Main: FC = function() {
   return (
-    <section className={css.main}>
-      <Card>
-        <section
-          style={{
-            width: '100%',
-            height: '500px',
-            display: 'flex',
-            alignItems: 'center',
-            justifyContent: 'center',
-          }}
-        >
-          <h1 style={{fontSize: '40px'}}>仓储物流管理系统</h1>
-        </section>
-      </Card>
+    <section className='content-main'>
+      <Group />
+      <Stream />
+      <Other />
     </section>
   );
 };

+ 10 - 0
packages/app/src/pages/main/other/index.module.css

@@ -0,0 +1,10 @@
+.card {
+  & :global(.ant-card-body) {
+    width: 100%;
+    height: 400px;
+  }
+}
+
+.card-group {
+  margin-top: 24px;
+}

+ 78 - 0
packages/app/src/pages/main/other/index.tsx

@@ -0,0 +1,78 @@
+import {Card, Col, Row} from 'antd';
+import css from './index.module.css';
+import {FC} from 'react';
+import {ResponsiveContainer, XAxis, YAxis, Tooltip, Legend, AreaChart, Area} from 'recharts';
+
+const data = [
+  {
+    name: '03-06',
+    data: 290,
+    value: 590,
+  },
+  {
+    name: '03-07',
+    data: 268,
+    value: 868,
+  },
+  {
+    name: '03-08',
+    data: 397,
+    value: 1397,
+  },
+  {
+    name: '03-09',
+    data: 2280,
+    value: 1480,
+  },
+  {
+    name: '03-10',
+    data: 120,
+    value: 1520,
+  },
+  {
+    name: '03-11',
+    data: 2400,
+    value: 1400,
+  },
+  {
+    name: '03-12',
+    data: 1500,
+    value: 1200,
+  },
+  {
+    name: '03-13',
+    data: 1900,
+    value: 100,
+  },
+];
+
+const Other: FC = function() {
+  return (
+    <Row gutter={12} className={css.cardGroup}>
+      <Col span={24}>
+        <Card title='产成品出入库统计' className={css.card}>
+          <ResponsiveContainer>
+            <AreaChart data={data}>
+              <XAxis dataKey='name' />
+              <YAxis />
+              <Legend
+                formatter={function(name: string) {
+                  return name === 'value' ? '入库' : '出库';
+                }}
+              />
+              <Tooltip
+                formatter={function(value: string, name: string) {
+                  return [value, name === 'value' ? '入库' : '出库'];
+                }}
+              />
+              <Area type='monotone' dataKey='value' stroke='#8884d8' fill='#EAEAF8' />
+              <Area type='monotone' dataKey='data' stroke='#82ca9d' fill='#E8F5ED' />
+            </AreaChart>
+          </ResponsiveContainer>
+        </Card>
+      </Col>
+    </Row>
+  );
+};
+
+export default Other;

+ 42 - 0
packages/app/src/pages/main/stream/index.module.css

@@ -0,0 +1,42 @@
+.card {
+  & :global(.ant-card-body) {
+    width: 100%;
+    height: 400px;
+  }
+}
+
+.card-group {
+  margin-top: 24px;
+}
+
+.list {
+  height: 100%;
+  overflow: auto;
+
+  & li {
+    display: flex;
+    justify-content: space-between;
+    height: 30px;
+    font-size: 14px;
+    line-height: 30px;
+
+    & span:nth-child(1) {
+      flex: 1;
+      padding-right: 10px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
+}
+
+.list-wrapper {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+
+  & :global(.ant-card-body) {
+    width: 100%;
+    height: 400px;
+  }
+}

+ 131 - 0
packages/app/src/pages/main/stream/index.tsx

@@ -0,0 +1,131 @@
+import {Card, Col, Row} from 'antd';
+import {FC} from 'react';
+import {ResponsiveContainer, Bar, XAxis, YAxis, BarChart, Tooltip, Legend} from 'recharts';
+import css from './index.module.css';
+
+const data = [
+  {
+    name: '03-06',
+    value: 590,
+    data: 100,
+  },
+  {
+    name: '03-07',
+    value: 868,
+    data: 200,
+  },
+  {
+    name: '03-08',
+    value: 1397,
+    data: 500,
+  },
+  {
+    name: '03-09',
+    value: 1480,
+    data: 700,
+  },
+  {
+    name: '03-10',
+    value: 1520,
+    data: 300,
+  },
+  {
+    name: '03-11',
+    value: 1400,
+    data: 100,
+  },
+  {
+    name: '03-12',
+    value: 1200,
+    data: 0,
+  },
+  {
+    name: '03-13',
+    value: 100,
+    data: 1200,
+  },
+];
+
+const StreamGroup: FC = function() {
+  return (
+    <Row className={css.cardGroup} gutter={12}>
+      <Col span={12}>
+        <Card title='原材料出入库统计' className={css.card}>
+          <ResponsiveContainer>
+            <BarChart data={data}>
+              <XAxis dataKey='name' />
+              <YAxis />
+              <Legend
+                formatter={function(name: string) {
+                  return name === 'value' ? '入库' : '出库';
+                }}
+              />
+              <Tooltip
+                formatter={function(value: string, name: string) {
+                  return [value, name === 'value' ? '入库' : '出库'];
+                }}
+                itemSorter={payload => payload.name === 'value' ? 1 : -1}
+              />
+              <Bar stackId='stream' dataKey='value' fill='#00a6ca' />
+              <Bar stackId='stream' dataKey='data' fill='#f47983' />
+            </BarChart>
+          </ResponsiveContainer>
+        </Card>
+      </Col>
+      <Col span={6}>
+        <Card title='原材料出库记录' className={css.listWrapper}>
+          <ul className={css.list}>
+            <li>
+              <span>HDI123123231</span>
+              <span>2020/12/22</span>
+            </li>
+            <li>
+              <span>HDI123123231</span>
+              <span>2020/12/22</span>
+            </li>
+            <li>
+              <span>HDI123123231</span>
+              <span>2020/12/22</span>
+            </li>
+            <li>
+              <span>HDI123123231</span>
+              <span>2020/12/22</span>
+            </li>
+            <li>
+              <span>HDI123123231</span>
+              <span>2020/12/22</span>
+            </li>
+          </ul>
+        </Card>
+      </Col>
+      <Col span={6}>
+        <Card title='原材料入库记录' className={css.listWrapper}>
+          <ul className={css.list}>
+            <li>
+              <span>HDI123123231</span>
+              <span>2020/12/22</span>
+            </li>
+            <li>
+              <span>HDI123123231</span>
+              <span>2020/12/22</span>
+            </li>
+            <li>
+              <span>HDI123123231</span>
+              <span>2020/12/22</span>
+            </li>
+            <li>
+              <span>HDI123123231</span>
+              <span>2020/12/22</span>
+            </li>
+            <li>
+              <span>HDI123123231</span>
+              <span>2020/12/22</span>
+            </li>
+          </ul>
+        </Card>
+      </Col>
+    </Row>
+  );
+};
+
+export default StreamGroup;

+ 1 - 1
packages/app/src/pages/raw-in-stream/filter/index.tsx

@@ -7,7 +7,7 @@ import {useExport, useSearch} from './hooks';
 const Filter: FC = function() {
   const [{dates, start, end}, onDatesChange] = useRangeDate();
   const [code, onCodeChange] = useState('');
-  const [isSearching, onSearch] = useSearch(start, end, code);
+  const [isSearching, onSearch] = useSearch(code, start, end);
   const [isExporting, onExport] = useExport();
 
   return (

+ 94 - 2
packages/app/src/pages/raw-in-stream/table/index.tsx

@@ -1,10 +1,102 @@
 import {FC} from 'react';
 import {useList} from './hooks';
+import {WarehousingListData} from '@models';
+import {ColumnsType} from 'antd/es/table';
+import {Table} from '@components';
+import {pageContext, searchContext} from '../context';
+import {Card} from 'antd';
+
+const columns: ColumnsType<WarehousingListData> = [
+  {
+    title: '物料编号',
+    dataIndex: 'wllbCode',
+    key: 'wllbCode',
+    width: 200,
+  },
+  {
+    title: '供应商名称',
+    dataIndex: 'supplierName',
+    key: 'supplierName',
+    width: 180,
+  },
+  {
+    title: '连翻号',
+    dataIndex: 'serial',
+    key: 'serial',
+    width: 200,
+  },
+  {
+    title: '生产日期',
+    dataIndex: 'producDate',
+    key: 'producDate',
+    width: 120,
+  },
+  {
+    title: '生产批次',
+    dataIndex: 'producDate',
+    key: 'producDate',
+    width: 120,
+  },
+  {
+    title: '容量',
+    dataIndex: 'capacity',
+    key: 'capacity',
+    width: 100,
+  },
+  {
+    title: '序列号',
+    dataIndex: 'seq',
+    key: 'seq',
+    width: 200,
+  },
+  {
+    title: '类型',
+    dataIndex: 'type',
+    key: 'type',
+    width: 130,
+  },
+  {
+    title: '用户名称',
+    dataIndex: 'userName',
+    key: 'userName',
+    width: 150,
+  },
+  {
+    title: '部门',
+    dataIndex: 'departmentName',
+    key: 'departmentName',
+    width: 150,
+  },
+  {
+    title: '库位名称',
+    dataIndex: 'storageLocationName',
+    key: 'storageLocationName',
+    width: 150,
+  },
+  {
+    title: '入库日期',
+    dataIndex: 'scrq',
+    key: 'scrq',
+    width: 200,
+  },
+];
 
 const TableList: FC = function() {
-  useList();
+  const [{data, count}] = useList();
 
-  return <></>;
+  return (
+    <Card className='table-wrapper'>
+      <Table
+        scrollX={1900}
+        data={data}
+        count={count}
+        pageContext={pageContext}
+        searchContext={searchContext}
+        columns={columns}
+        data-testid='raw_in_stream_table'
+      />
+    </Card>
+  );
 };
 
 export default TableList;

+ 237 - 5
pnpm-lock.yaml

@@ -76,6 +76,7 @@ importers:
       react-hook-form: ^7.43.0
       react-modal: ^3.16.1
       react-router-dom: ^6.8.0
+      recharts: ^2.4.3
       use-context-selector: ^1.4.1
       yup: ^0.32.11
       zustand: ^4.3.2
@@ -98,6 +99,7 @@ importers:
       react-hook-form: 7.43.0_react@18.2.0
       react-modal: 3.16.1_biqbaboplfbrettd7655fr4n2y
       react-router-dom: 6.8.0_biqbaboplfbrettd7655fr4n2y
+      recharts: 2.4.3_biqbaboplfbrettd7655fr4n2y
       use-context-selector: 1.4.1_biqbaboplfbrettd7655fr4n2y
       yup: 0.32.11
       zustand: 4.3.2_immer@9.0.19+react@18.2.0
@@ -2722,6 +2724,48 @@ packages:
       '@types/node': 18.11.18
     dev: true
 
+  /@types/d3-array/3.0.4:
+    resolution: {integrity: sha512-nwvEkG9vYOc0Ic7G7kwgviY4AQlTfYGIZ0fqB7CQHXGyYM6nO7kJh5EguSNA3jfh4rq7Sb7eMVq8isuvg2/miQ==}
+    dev: false
+
+  /@types/d3-color/3.1.0:
+    resolution: {integrity: sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==}
+    dev: false
+
+  /@types/d3-ease/3.0.0:
+    resolution: {integrity: sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==}
+    dev: false
+
+  /@types/d3-interpolate/3.0.1:
+    resolution: {integrity: sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==}
+    dependencies:
+      '@types/d3-color': 3.1.0
+    dev: false
+
+  /@types/d3-path/3.0.0:
+    resolution: {integrity: sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==}
+    dev: false
+
+  /@types/d3-scale/4.0.3:
+    resolution: {integrity: sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==}
+    dependencies:
+      '@types/d3-time': 3.0.0
+    dev: false
+
+  /@types/d3-shape/3.1.1:
+    resolution: {integrity: sha512-6Uh86YFF7LGg4PQkuO2oG6EMBRLuW9cbavUW46zkIO5kuS2PfTqo2o9SkgtQzguBHbLgNnU90UNsITpsX1My+A==}
+    dependencies:
+      '@types/d3-path': 3.0.0
+    dev: false
+
+  /@types/d3-time/3.0.0:
+    resolution: {integrity: sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==}
+    dev: false
+
+  /@types/d3-timer/3.0.0:
+    resolution: {integrity: sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==}
+    dev: false
+
   /@types/eslint-scope/3.7.4:
     resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==}
     dependencies:
@@ -4495,6 +4539,10 @@ packages:
       source-map: 0.6.1
     dev: true
 
+  /css-unit-converter/1.1.2:
+    resolution: {integrity: sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==}
+    dev: false
+
   /css-what/6.1.0:
     resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
     engines: {node: '>= 6'}
@@ -4661,6 +4709,77 @@ packages:
       yauzl: 2.10.0
     dev: false
 
+  /d3-array/3.2.2:
+    resolution: {integrity: sha512-yEEyEAbDrF8C6Ob2myOBLjwBLck1Z89jMGFee0oPsn95GqjerpaOA4ch+vc2l0FNFFwMD5N7OCSEN5eAlsUbgQ==}
+    engines: {node: '>=12'}
+    dependencies:
+      internmap: 2.0.3
+    dev: false
+
+  /d3-color/3.1.0:
+    resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+    engines: {node: '>=12'}
+    dev: false
+
+  /d3-ease/3.0.1:
+    resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+    engines: {node: '>=12'}
+    dev: false
+
+  /d3-format/3.1.0:
+    resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
+    engines: {node: '>=12'}
+    dev: false
+
+  /d3-interpolate/3.0.1:
+    resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+    engines: {node: '>=12'}
+    dependencies:
+      d3-color: 3.1.0
+    dev: false
+
+  /d3-path/3.1.0:
+    resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
+    engines: {node: '>=12'}
+    dev: false
+
+  /d3-scale/4.0.2:
+    resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
+    engines: {node: '>=12'}
+    dependencies:
+      d3-array: 3.2.2
+      d3-format: 3.1.0
+      d3-interpolate: 3.0.1
+      d3-time: 3.1.0
+      d3-time-format: 4.1.0
+    dev: false
+
+  /d3-shape/3.2.0:
+    resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
+    engines: {node: '>=12'}
+    dependencies:
+      d3-path: 3.1.0
+    dev: false
+
+  /d3-time-format/4.1.0:
+    resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
+    engines: {node: '>=12'}
+    dependencies:
+      d3-time: 3.1.0
+    dev: false
+
+  /d3-time/3.1.0:
+    resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
+    engines: {node: '>=12'}
+    dependencies:
+      d3-array: 3.2.2
+    dev: false
+
+  /d3-timer/3.0.1:
+    resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+    engines: {node: '>=12'}
+    dev: false
+
   /dashdash/1.14.1:
     resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==}
     engines: {node: '>=0.10'}
@@ -4753,6 +4872,10 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /decimal.js-light/2.5.1:
+    resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
+    dev: false
+
   /decimal.js/10.4.3:
     resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
     dev: true
@@ -4905,6 +5028,12 @@ packages:
       utila: 0.4.0
     dev: true
 
+  /dom-helpers/3.4.0:
+    resolution: {integrity: sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==}
+    dependencies:
+      '@babel/runtime': 7.20.13
+    dev: false
+
   /dom-serializer/1.4.1:
     resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
     dependencies:
@@ -5057,7 +5186,7 @@ packages:
       is-shared-array-buffer: 1.0.2
       is-string: 1.0.7
       is-weakref: 1.0.2
-      object-inspect: 1.12.2
+      object-inspect: 1.12.3
       object-keys: 1.1.1
       object.assign: 4.1.4
       regexp.prototype.flags: 1.4.3
@@ -5488,7 +5617,6 @@ packages:
 
   /eventemitter3/4.0.7:
     resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
-    dev: true
 
   /events/3.3.0:
     resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
@@ -5617,6 +5745,10 @@ packages:
   /fast-deep-equal/3.1.3:
     resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
 
+  /fast-equals/4.0.3:
+    resolution: {integrity: sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==}
+    dev: false
+
   /fast-glob/3.2.12:
     resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
     engines: {node: '>=8.6.0'}
@@ -6372,6 +6504,11 @@ packages:
       side-channel: 1.0.4
     dev: true
 
+  /internmap/2.0.3:
+    resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
+    engines: {node: '>=12'}
+    dev: false
+
   /interpret/2.2.0:
     resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==}
     engines: {node: '>= 0.10'}
@@ -7784,8 +7921,8 @@ packages:
     resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
     engines: {node: '>=0.10.0'}
 
-  /object-inspect/1.12.2:
-    resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==}
+  /object-inspect/1.12.3:
+    resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
 
   /object-is/1.1.5:
     resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==}
@@ -8929,6 +9066,10 @@ packages:
       postcss-selector-parser: 6.0.11
     dev: true
 
+  /postcss-value-parser/3.3.1:
+    resolution: {integrity: sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==}
+    dev: false
+
   /postcss-value-parser/4.2.0:
     resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
     dev: true
@@ -9717,6 +9858,17 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /react-resize-detector/7.1.2_biqbaboplfbrettd7655fr4n2y:
+    resolution: {integrity: sha512-zXnPJ2m8+6oq9Nn8zsep/orts9vQv3elrpA+R8XTcW7DVVUJ9vwDwMXaBtykAYjMnkCIaOoK9vObyR7ZgFNlOw==}
+    peerDependencies:
+      react: ^16.0.0 || ^17.0.0 || ^18.0.0
+      react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0
+    dependencies:
+      lodash: 4.17.21
+      react: 18.2.0
+      react-dom: 18.2.0_react@18.2.0
+    dev: false
+
   /react-router-dom/6.8.0_biqbaboplfbrettd7655fr4n2y:
     resolution: {integrity: sha512-hQouduSTywGJndE86CXJ2h7YEy4HYC6C/uh19etM+79FfQ6cFFFHnHyDlzO4Pq0eBUI96E4qVE5yUjA00yJZGQ==}
     engines: {node: '>=14'}
@@ -9740,6 +9892,33 @@ packages:
       react: 18.2.0
     dev: false
 
+  /react-smooth/2.0.2_biqbaboplfbrettd7655fr4n2y:
+    resolution: {integrity: sha512-pgqSp1q8rAGtF1bXQE0m3CHGLNfZZh5oA5o1tsPLXRHnKtkujMIJ8Ws5nO1mTySZf1c4vgwlEk+pHi3Ln6eYLw==}
+    peerDependencies:
+      prop-types: ^15.6.0
+      react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
+      react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
+    dependencies:
+      fast-equals: 4.0.3
+      react: 18.2.0
+      react-dom: 18.2.0_react@18.2.0
+      react-transition-group: 2.9.0_biqbaboplfbrettd7655fr4n2y
+    dev: false
+
+  /react-transition-group/2.9.0_biqbaboplfbrettd7655fr4n2y:
+    resolution: {integrity: sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==}
+    peerDependencies:
+      react: '>=15.0.0'
+      react-dom: '>=15.0.0'
+    dependencies:
+      dom-helpers: 3.4.0
+      loose-envify: 1.4.0
+      prop-types: 15.8.1
+      react: 18.2.0
+      react-dom: 18.2.0_react@18.2.0
+      react-lifecycles-compat: 3.0.4
+    dev: false
+
   /react/18.2.0:
     resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
     engines: {node: '>=0.10.0'}
@@ -9793,6 +9972,33 @@ packages:
       picomatch: 2.3.1
     dev: true
 
+  /recharts-scale/0.4.5:
+    resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==}
+    dependencies:
+      decimal.js-light: 2.5.1
+    dev: false
+
+  /recharts/2.4.3_biqbaboplfbrettd7655fr4n2y:
+    resolution: {integrity: sha512-/hkRHTQShEOKDYd2OlKLIvGA0X9v/XVO/mNeRoDHg0lgFRL2KbGzeqVnStI3mMfORUZ6Hak4JbQ+uDiin1Foqg==}
+    engines: {node: '>=12'}
+    peerDependencies:
+      prop-types: ^15.6.0
+      react: ^16.0.0 || ^17.0.0 || ^18.0.0
+      react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0
+    dependencies:
+      classnames: 2.3.2
+      eventemitter3: 4.0.7
+      lodash: 4.17.21
+      react: 18.2.0
+      react-dom: 18.2.0_react@18.2.0
+      react-is: 16.13.1
+      react-resize-detector: 7.1.2_biqbaboplfbrettd7655fr4n2y
+      react-smooth: 2.0.2_biqbaboplfbrettd7655fr4n2y
+      recharts-scale: 0.4.5
+      reduce-css-calc: 2.1.8
+      victory-vendor: 36.6.8
+    dev: false
+
   /rechoir/0.7.1:
     resolution: {integrity: sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==}
     engines: {node: '>= 0.10'}
@@ -9808,6 +10014,13 @@ packages:
       strip-indent: 3.0.0
     dev: true
 
+  /reduce-css-calc/2.1.8:
+    resolution: {integrity: sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==}
+    dependencies:
+      css-unit-converter: 1.1.2
+      postcss-value-parser: 3.3.1
+    dev: false
+
   /regenerate-unicode-properties/10.1.0:
     resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==}
     engines: {node: '>=4'}
@@ -10160,7 +10373,7 @@ packages:
     dependencies:
       call-bind: 1.0.2
       get-intrinsic: 1.1.3
-      object-inspect: 1.12.2
+      object-inspect: 1.12.3
 
   /signal-exit/3.0.7:
     resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
@@ -11050,6 +11263,25 @@ packages:
       extsprintf: 1.3.0
     dev: false
 
+  /victory-vendor/36.6.8:
+    resolution: {integrity: sha512-H3kyQ+2zgjMPvbPqAl7Vwm2FD5dU7/4bCTQakFQnpIsfDljeOMDojRsrmJfwh4oAlNnWhpAf+mbAoLh8u7dwyQ==}
+    dependencies:
+      '@types/d3-array': 3.0.4
+      '@types/d3-ease': 3.0.0
+      '@types/d3-interpolate': 3.0.1
+      '@types/d3-scale': 4.0.3
+      '@types/d3-shape': 3.1.1
+      '@types/d3-time': 3.0.0
+      '@types/d3-timer': 3.0.0
+      d3-array: 3.2.2
+      d3-ease: 3.0.1
+      d3-interpolate: 3.0.1
+      d3-scale: 4.0.2
+      d3-shape: 3.2.0
+      d3-time: 3.1.0
+      d3-timer: 3.0.1
+    dev: false
+
   /w3c-xmlserializer/4.0.0:
     resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==}
     engines: {node: '>=14'}