Bläddra i källkod

全体的に整備

develop
sosuke.iwabuchi 2 år sedan
förälder
incheckning
057e31c68c
31 ändrade filer med 1509 tillägg och 67 borttagningar
  1. +8
    -0
      package.json
  2. +18
    -38
      public/index.html
  3. +9
    -0
      src/@types/index.ts
  4. +18
    -19
      src/App.tsx
  5. +8
    -0
      src/codes/page.ts
  6. +8
    -0
      src/codes/user.ts
  7. +5
    -0
      src/components/LoadingScreen.tsx
  8. +65
    -0
      src/contexts/DashboardLayoutContext.tsx
  9. +30
    -0
      src/contexts/PageContext.tsx
  10. +31
    -0
      src/contexts/WindowSizeContext.tsx
  11. +16
    -0
      src/hooks/useDashBoard.ts
  12. +44
    -0
      src/hooks/useNavigateCustom.ts
  13. +6
    -0
      src/hooks/usePage.ts
  14. +39
    -0
      src/hooks/useResponsive.ts
  15. +6
    -0
      src/hooks/useWindowSize.ts
  16. +112
    -0
      src/layouts/dashbord/header.tsx
  17. +77
    -0
      src/layouts/dashbord/index.tsx
  18. +230
    -0
      src/layouts/dashbord/navigator.tsx
  19. +31
    -0
      src/layouts/dashbord/tab/ContractTabs.tsx
  20. +17
    -0
      src/layouts/dashbord/tab/index.tsx
  21. +34
    -0
      src/layouts/dashbord/tab/tabutil.tsx
  22. +5
    -0
      src/pages/common/Page404.tsx
  23. +17
    -0
      src/pages/dashboard/contract/detail.tsx
  24. +17
    -0
      src/pages/dashboard/contract/list.tsx
  25. +17
    -0
      src/pages/test/TestA.tsx
  26. +12
    -0
      src/pages/test/TestB.tsx
  27. +57
    -0
      src/routes/index.tsx
  28. +91
    -0
      src/routes/path.ts
  29. +150
    -0
      src/theme/index.tsx
  30. +3
    -2
      tsconfig.json
  31. +328
    -8
      yarn.lock

+ 8
- 0
package.json Visa fil

@@ -3,15 +3,23 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.10.8",
"@emotion/styled": "^11.10.8",
"@mui/icons-material": "^5.11.16",
"@mui/material": "^5.12.2",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0",
"@testing-library/user-event": "^13.2.1",
"@types/jest": "^27.0.1",
"@types/lodash": "^4.14.194",
"@types/node": "^16.7.13",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/react-router-dom": "^5.3.3",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.11.0",
"react-scripts": "5.0.1",
"typescript": "^4.4.2",
"web-vitals": "^2.1.0"


+ 18
- 38
public/index.html Visa fil

@@ -1,43 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.

Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />

You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
<title>SateReceipt</title>
</head>

<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>

</body>

</html>

+ 9
- 0
src/@types/index.ts Visa fil

@@ -0,0 +1,9 @@
import { ReactNode } from "react";

export type HasChildren = {
children: ReactNode;
};

export type Dictionary = {
[key: string]: string;
};

+ 18
- 19
src/App.tsx Visa fil

@@ -1,25 +1,24 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { CssBaseline } from "@mui/material";
import { PageContextProvider } from "contexts/PageContext";
import { WindowSizeContextProvider } from "contexts/WindowSizeContext";
import { BrowserRouter } from "react-router-dom";
import { Routes } from "routes";
import { AppThemeProvider } from "theme";

function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
<>
<AppThemeProvider>
<PageContextProvider>
<WindowSizeContextProvider>
<BrowserRouter>
<CssBaseline />
<Routes />
</BrowserRouter>
</WindowSizeContextProvider>
</PageContextProvider>
</AppThemeProvider>
</>
);
}



+ 8
- 0
src/codes/page.ts Visa fil

@@ -0,0 +1,8 @@
export const PageID = {
NONE: "NONE",

DASHBOARD_CONTRACT_LIST: "DASHBOARD_CONTRACT_LIST",
DASHBOARD_CONTRACT_DETAIL: "DASHBOARD_CONTRACT_DETAIL",
} as const;

export type PageID = (typeof PageID)[keyof typeof PageID];

+ 8
- 0
src/codes/user.ts Visa fil

@@ -0,0 +1,8 @@
export const UserRole = {
NONE: 0,
NORMAL_ADMIN: 100,
CONTRACT_ADMIN: 200,
SUPER_ADMIN: 900,
} as const;

export type UserRole = (typeof UserRole)[keyof typeof UserRole];

+ 5
- 0
src/components/LoadingScreen.tsx Visa fil

@@ -0,0 +1,5 @@
import { Box } from "@mui/material";

export default function LoadingScreen() {
return <Box>Loading...</Box>;
}

+ 65
- 0
src/contexts/DashboardLayoutContext.tsx Visa fil

@@ -0,0 +1,65 @@
import { HasChildren } from "@types";
import { PageID } from "codes/page";
import usePage from "hooks/usePage";
import useWindowSize from "hooks/useWindowSize";
import { ReactNode, createContext, useMemo, useState } from "react";

type ContextProps = {
headerTitle: string;
setHeaderTitle: (title: string) => void;
drawerWidth: number;
innerHeight: number;
innerWidth: number;
contentsWidth: number;
tabs: ReactNode | null;
setTabs: (tabs: ReactNode | null) => void;
pageId: PageID;
setPageId: (tabs: PageID) => void;
};
const contextInit: ContextProps = {
headerTitle: "",
setHeaderTitle: (title: string) => {},
drawerWidth: 0,
innerHeight: 0,
innerWidth: 0,
contentsWidth: 0,
tabs: null,
setTabs: (tabs: ReactNode | null) => {},
pageId: PageID.NONE,
setPageId: (tabs: PageID) => {},
};
export const DashboardLayoutContext = createContext(contextInit);

type Props = HasChildren;
export function DashboardLayoutContextProvider({ children }: Props) {
const drawerWidth = 256;

const [headerTitle, setHeaderTitle] = useState("");
const [tabs, setTabs] = useState<ReactNode | null>(null);

const { width: innerWidth, height: innerHeight } = useWindowSize();
const { pageId, setPageId } = usePage();

const contentsWidth = useMemo(() => {
return innerWidth - drawerWidth;
}, [drawerWidth, innerWidth]);

return (
<DashboardLayoutContext.Provider
value={{
headerTitle,
setHeaderTitle,
drawerWidth,
innerWidth,
innerHeight,
contentsWidth,
tabs,
setTabs,
pageId,
setPageId,
}}
>
{children}
</DashboardLayoutContext.Provider>
);
}

+ 30
- 0
src/contexts/PageContext.tsx Visa fil

@@ -0,0 +1,30 @@
import { HasChildren } from "@types";
import { PageID } from "codes/page";
import useWindowSize from "hooks/useWindowSize";
import { ReactNode, createContext, useMemo, useState } from "react";

type ContextProps = {
pageId: PageID;
setPageId: (pageId: PageID) => void;
};
const contextInit: ContextProps = {
pageId: PageID.NONE,
setPageId: (pageId: PageID) => {},
};
export const PageContext = createContext(contextInit);

type Props = HasChildren;
export function PageContextProvider({ children }: Props) {
const [pageId, setPageId] = useState<PageID>(PageID.NONE);

return (
<PageContext.Provider
value={{
pageId,
setPageId,
}}
>
{children}
</PageContext.Provider>
);
}

+ 31
- 0
src/contexts/WindowSizeContext.tsx Visa fil

@@ -0,0 +1,31 @@
import { HasChildren } from "@types";
import { createContext, useLayoutEffect, useState } from "react";

export const WindowSizeContext = createContext({
width: 0,
height: 0,
});

type Props = HasChildren;
export function WindowSizeContextProvider({ children }: Props) {
const [width, setWidth] = useState(window.innerWidth);
const [height, setHeight] = useState(window.innerHeight);

useLayoutEffect(() => {
const updateSize = () => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
};

window.addEventListener("resize", updateSize);
updateSize();

return () => window.removeEventListener("resize", updateSize);
}, []);

return (
<WindowSizeContext.Provider value={{ width, height }}>
{children}
</WindowSizeContext.Provider>
);
}

+ 16
- 0
src/hooks/useDashBoard.ts Visa fil

@@ -0,0 +1,16 @@
import { useContext, useEffect } from "react";
import { DashboardLayoutContext } from "contexts/DashboardLayoutContext";
import { PageID } from "codes/page";

export default function useDashboard(pageId?: PageID) {
const context = useContext(DashboardLayoutContext);

useEffect(() => {
if (pageId) {
context.setPageId(pageId ?? PageID.NONE);
// console.log("CURRENT", pageId);
}
}, []);

return context;
}

+ 44
- 0
src/hooks/useNavigateCustom.ts Visa fil

@@ -0,0 +1,44 @@
import { useLocation, useNavigate } from "react-router";
import { Dictionary } from "@types";

export default function useNavigateCustom() {
const navigate = useNavigate();
const { pathname, search } = useLocation();

const navigateWhenChanged = (
path: string,
param?: Dictionary | URLSearchParams | string,
context?: any
) => {
const currentUrl = pathname + search;
let newPath = path;

if (typeof param === "string") {
if (!param.startsWith("?")) {
newPath += "?";
}
newPath += param;
} else if (param instanceof URLSearchParams) {
const search = param.toString();
if (search) {
newPath += "?";
newPath += search;
}
} else if (typeof param === "object") {
const urlParam = new URLSearchParams(param);
const search = urlParam.toString();
if (search) {
newPath += "?";
newPath += search;
}
}

if (currentUrl !== newPath) {
if (context) {
console.log("navigate to", newPath, context);
}
navigate(newPath);
}
};
return { navigate, navigateWhenChanged };
}

+ 6
- 0
src/hooks/usePage.ts Visa fil

@@ -0,0 +1,6 @@
import { PageContext } from "contexts/PageContext";
import { useContext } from "react";

export default function usePage() {
return useContext(PageContext);
}

+ 39
- 0
src/hooks/useResponsive.ts Visa fil

@@ -0,0 +1,39 @@
// @mui
import { Breakpoint } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';

// ----------------------------------------------------------------------

type Query = 'up' | 'down' | 'between' | 'only';
type Key = Breakpoint | number;
type Start = Breakpoint | number;
type End = Breakpoint | number;

export default function useResponsive(query: Query, key?: Key, start?: Start, end?: End) {
const theme = useTheme();

const mediaUp = useMediaQuery(theme.breakpoints.up(key as Key));

const mediaDown = useMediaQuery(theme.breakpoints.down(key as Key));

const mediaBetween = useMediaQuery(theme.breakpoints.between(start as Start, end as End));

const mediaOnly = useMediaQuery(theme.breakpoints.only(key as Breakpoint));

if (query === 'up') {
return mediaUp;
}

if (query === 'down') {
return mediaDown;
}

if (query === 'between') {
return mediaBetween;
}

if (query === 'only') {
return mediaOnly;
}
}

+ 6
- 0
src/hooks/useWindowSize.ts Visa fil

@@ -0,0 +1,6 @@
import { useContext } from "react";
import { WindowSizeContext } from "contexts/WindowSizeContext";

export default function useWindowSize() {
return useContext(WindowSizeContext);
}

+ 112
- 0
src/layouts/dashbord/header.tsx Visa fil

@@ -0,0 +1,112 @@
import MenuIcon from "@mui/icons-material/Menu";
import { Box } from "@mui/material";
import AppBar from "@mui/material/AppBar";
import Grid from "@mui/material/Grid";
import IconButton from "@mui/material/IconButton";
import Toolbar from "@mui/material/Toolbar";
import Typography from "@mui/material/Typography";
import useDashboard from "hooks/useDashBoard";
import * as React from "react";

interface HeaderProps {
onDrawerToggle: () => void;
}

export default function Header(props: HeaderProps) {
const { onDrawerToggle } = props;

const { contentsWidth, headerTitle, tabs } = useDashboard();

return (
<React.Fragment>
<AppBar color="primary" position="sticky" elevation={0}>
<Toolbar>
<Grid container spacing={1} alignItems="center">
<Grid sx={{ display: { sm: "none", xs: "block" } }} item>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={onDrawerToggle}
edge="start"
>
<MenuIcon />
</IconButton>
</Grid>
{/* <Grid item xs /> */}
{/* <Grid item>
<Link
href="/"
variant="body2"
sx={{
textDecoration: "none",
color: lightColor,
"&:hover": {
color: "common.white",
},
}}
rel="noopener noreferrer"
target="_blank"
>
Go to docs
</Link>
</Grid> */}
{/* <Grid item>
<Tooltip title="Alerts • No alerts">
<IconButton color="inherit">
<NotificationsIcon />
</IconButton>
</Tooltip>
</Grid> */}
{/* <Grid item>
<IconButton color="inherit" sx={{ p: 0.5 }}>
<Avatar src="/static/images/avatar/1.jpg" alt="My Avatar" />
</IconButton>
</Grid> */}
</Grid>
</Toolbar>
</AppBar>
<AppBar
component="div"
color="primary"
position="static"
elevation={0}
sx={{ zIndex: 0 }}
>
<Toolbar>
<Grid container alignItems="center" spacing={1}>
<Grid item xs>
<Typography color="inherit" variant="h5" component="h1">
{headerTitle}
</Typography>
</Grid>
{/* <Grid item>
<Button
sx={{ borderColor: lightColor }}
variant="outlined"
color="inherit"
size="small"
>
Web setup
</Button>
</Grid>
<Grid item>
<Tooltip title="Help">
<IconButton color="inherit">
<HelpIcon />
</IconButton>
</Tooltip>
</Grid> */}
</Grid>
</Toolbar>
</AppBar>
<AppBar
component="div"
position="static"
elevation={0}
sx={{ zIndex: 0 }}
>
{!!tabs && <Box sx={{ maxWidth: contentsWidth }}>{tabs}</Box>}
</AppBar>
</React.Fragment>
);
}

+ 77
- 0
src/layouts/dashbord/index.tsx Visa fil

@@ -0,0 +1,77 @@
import { Box, Typography, styled } from "@mui/material";
import { Outlet } from "react-router-dom";
import Navigator from "./navigator";
import useResponsive from "hooks/useResponsive";
import { useContext, useEffect, useMemo, useState } from "react";
import Header from "./header";
import useWindowSize from "hooks/useWindowSize";
import {
DashboardLayoutContext,
DashboardLayoutContextProvider,
} from "contexts/DashboardLayoutContext";
import useDashboard from "hooks/useDashBoard";

function Copyright() {
return (
<Typography variant="body2" color="text.secondary" align="center">
{"Copyright ©Satellite-Technologies Co., Ltd."}
{new Date().getFullYear()}. All Rights Reserved.
</Typography>
);
}

function App() {
const [mobileOpen, setMobileOpen] = useState(false);

const isSmUp = useResponsive("up", "sm");

const { drawerWidth, innerHeight, innerWidth, contentsWidth } =
useDashboard();

const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};

useEffect(() => {
console.log({ drawerWidth, innerWidth, contentsWidth });
}, []);

return (
<Box sx={{ display: "flex", minHeight: "100vh" }}>
<Box
component="nav"
sx={{ width: { sm: drawerWidth }, flexShrink: { md: 0 } }}
>
{isSmUp ? null : (
<Navigator
PaperProps={{ style: { width: drawerWidth } }}
variant="temporary"
open={mobileOpen}
onClose={handleDrawerToggle}
/>
)}
<Navigator
PaperProps={{ style: { width: drawerWidth } }}
sx={{ display: { sm: "block", xs: "none" } }}
/>
</Box>
<Box sx={{ flex: 1, display: "flex", flexDirection: "column" }}>
<Header onDrawerToggle={handleDrawerToggle} />
<Box component="main" sx={{ flex: 1, py: 6, px: 4 }}>
<Outlet />
</Box>
<Box component="footer" sx={{ p: 2 }}>
<Copyright />
</Box>
</Box>
</Box>
);
}

export default function DashBoardLayout() {
return (
<DashboardLayoutContextProvider>
<App />
</DashboardLayoutContextProvider>
);
}

+ 230
- 0
src/layouts/dashbord/navigator.tsx Visa fil

@@ -0,0 +1,230 @@
import * as React from "react";
import Divider from "@mui/material/Divider";
import Drawer, { DrawerProps } from "@mui/material/Drawer";
import List from "@mui/material/List";
import Box from "@mui/material/Box";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import HomeIcon from "@mui/icons-material/Home";
import PeopleIcon from "@mui/icons-material/People";
import DnsRoundedIcon from "@mui/icons-material/DnsRounded";
import PermMediaOutlinedIcon from "@mui/icons-material/PhotoSizeSelectActual";
import PublicIcon from "@mui/icons-material/Public";
import SettingsEthernetIcon from "@mui/icons-material/SettingsEthernet";
import SettingsInputComponentIcon from "@mui/icons-material/SettingsInputComponent";
import TimerIcon from "@mui/icons-material/Timer";
import SettingsIcon from "@mui/icons-material/Settings";
import PhonelinkSetupIcon from "@mui/icons-material/PhonelinkSetup";
import { UserRole } from "codes/user";
import { useLocation } from "react-router-dom";
import { PATH, getPath } from "routes/path";
import usePage from "hooks/usePage";
import { PageID } from "codes/page";
import { ExpandLess, ExpandMore } from "@mui/icons-material";
import { Collapse } from "@mui/material";
import useNavigateCustom from "hooks/useNavigateCustom";

type Group = {
label: string;
children: SubGroup[];
};

type SubGroup = {
label: string;
icon: React.ReactNode;
children?: Child[];

// 子要素を持たない場合は下記は必須
id?: PageID;
role?: UserRole;
};

type Child = {
label: string;
id: PageID;
role?: UserRole;
};

const categories: Group[] = [
{
label: "管理",
children: [
{
label: "契約",
icon: <PeopleIcon />,
children: [
{
id: PageID.DASHBOARD_CONTRACT_LIST,
label: "一覧",
},
{
id: PageID.DASHBOARD_CONTRACT_DETAIL,
label: "詳細",
},
],
},
// { id: "Database", icon: <DnsRoundedIcon />, navigate: "contract" },
// { id: "Storage", icon: <PermMediaOutlinedIcon /> },
// { id: "Hosting", icon: <PublicIcon /> },
// { id: "Functions", icon: <SettingsEthernetIcon /> },
// {
// id: "Machine learning",
// icon: <SettingsInputComponentIcon />,
// },
],
},
// {
// id: "Quality",
// children: [
// { id: "Analytics", icon: <SettingsIcon /> },
// { id: "Performance", icon: <TimerIcon /> },
// { id: "Test Lab", icon: <PhonelinkSetupIcon /> },
// ],
// },
];

const item = {
py: "2px",
px: 3,
color: "rgba(255, 255, 255, 0.7)",
"&:hover, &:focus": {
bgcolor: "rgba(255, 255, 255, 0.08)",
},
};

const itemCategory = {
boxShadow: "0 -1px 0 rgb(255,255,255,0.1) inset",
py: 1.5,
px: 3,
};

export default function Navigator(props: DrawerProps) {
const { ...other } = props;

return (
<Drawer variant="permanent" {...other}>
<List disablePadding>
<ListItem
sx={{ ...item, ...itemCategory, fontSize: 22, color: "#fff" }}
>
SateReceipt
</ListItem>
<ListItem sx={{ ...item, ...itemCategory }}>
<ListItemIcon>
<HomeIcon />
</ListItemIcon>
<ListItemText>Project Overview</ListItemText>
</ListItem>

{categories.map((group, index) => {
return <Group {...group} key={index} />;
})}
</List>
</Drawer>
);
}

function Group(group: Group) {
const { label, children } = group;

return (
<Box sx={{ bgcolor: "#101F33" }}>
<ListItem sx={{ py: 2, px: 3 }}>
<ListItemText sx={{ color: "#fff" }}>{label}</ListItemText>
</ListItem>
{children.map((ele, index) => (
<SubGroup {...ele} key={index} />
))}
<Divider sx={{ mt: 2 }} />
</Box>
);
}

function SubGroup({ icon, label, id, children, role }: SubGroup) {
const { pageId } = usePage();
const { navigateWhenChanged } = useNavigateCustom();

const { elements, shouldOpen } = useContents(children ?? []);

const [open, setOpen] = React.useState(false);

React.useEffect(() => {
setOpen(shouldOpen);
}, [shouldOpen]);

// 子要素ありの場合
if (elements && elements.length !== 0) {
const handleClick = () => {
setOpen(!open);
};
return (
<>
<ListItemButton onClick={handleClick} sx={item} selected={false}>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText>{label}</ListItemText>
{open ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{elements}
</List>
</Collapse>
</>
);
}

// 子要素なしの場合
const handleClick = () => {
if (id) {
const path = getPath(id);
navigateWhenChanged(path);
}
};
const selected = id === pageId;
return (
<ListItemButton onClick={handleClick} selected={selected}>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText>{label}</ListItemText>
</ListItemButton>
);
}

function useContents(children: Child[]) {
const { pageId } = usePage();
const { navigateWhenChanged } = useNavigateCustom();

const [shouldOpen, setShouldOpen] = React.useState(false);

const elements = React.useMemo(() => {
setShouldOpen(false);
return children.map(({ label, id }, index) => {
const selected = id === pageId;
if (selected) {
setShouldOpen(true);
}

const handleClick = () => {
const path = getPath(id);
navigateWhenChanged(path);
};

return (
<ListItemButton
selected={selected}
sx={{ ...item, pl: 4 }}
key={index}
onClick={handleClick}
>
<ListItemText primary={label} />
</ListItemButton>
);
});
}, [pageId]);

return {
elements,
shouldOpen,
};
}

+ 31
- 0
src/layouts/dashbord/tab/ContractTabs.tsx Visa fil

@@ -0,0 +1,31 @@
import { Tabs } from "@mui/material";
import { TabProps, useTab } from "./tabutil";
import { PageID } from "codes/page";
import { getPath } from "routes/path";

const tabs: TabProps[] = [
{
label: "一覧",
pageId: PageID.DASHBOARD_CONTRACT_LIST,
},
{
label: "詳細",
pageId: PageID.DASHBOARD_CONTRACT_DETAIL,
},
];

export default function ContractTabs() {
const { elements, getTabIndex } = useTab(tabs);

return (
<Tabs
value={getTabIndex}
textColor="inherit"
// scrollButtons
// allowScrollButtonsMobile
variant="scrollable"
>
{elements}
</Tabs>
);
}

+ 17
- 0
src/layouts/dashbord/tab/index.tsx Visa fil

@@ -0,0 +1,17 @@
import { Tab as MuiTab, TabProps as MuiTabProps } from "@mui/material";
import useNavigateCustom from "hooks/useNavigateCustom";

export type TabProps = {
navigate?: string;
} & MuiTabProps;
export function Tab({ navigate, ...others }: TabProps) {
const { navigateWhenChanged } = useNavigateCustom();

const handleClick = () => {
if (navigate) {
navigateWhenChanged(navigate);
}
};

return <MuiTab onClick={handleClick} {...others} />;
}

+ 34
- 0
src/layouts/dashbord/tab/tabutil.tsx Visa fil

@@ -0,0 +1,34 @@
import { PageID } from "codes/page";
import usePage from "hooks/usePage";
import { useMemo } from "react";
import { Tab } from ".";
import { getPath } from "routes/path";

export type TabProps = {
label: string;
pageId: PageID;
};

export function useTab(tabs: TabProps[]) {
const { pageId } = usePage();

const elements = useMemo(() => {
return tabs.map(({ label, pageId: elementPageId }, index) => {
const path = getPath(elementPageId);
return <Tab {...{ label, navigate: path, key: index }} />;
});
}, [tabs]);

const getTabIndex = useMemo(() => {
return (
tabs.findIndex((tab) => {
return tab.pageId === pageId;
}) ?? 0
);
}, [pageId, tabs]);

return {
elements,
getTabIndex,
};
}

+ 5
- 0
src/pages/common/Page404.tsx Visa fil

@@ -0,0 +1,5 @@
import { Box } from "@mui/material";

export default function TestB() {
return <Box>NotFound.</Box>;
}

+ 17
- 0
src/pages/dashboard/contract/detail.tsx Visa fil

@@ -0,0 +1,17 @@
import { Box } from "@mui/material";
import { PageID } from "codes/page";
import useDashboard from "hooks/useDashBoard";
import ContractTabs from "layouts/dashbord/tab/ContractTabs";
import { useEffect } from "react";

export default function ContractDetail() {
const { setHeaderTitle, setTabs } = useDashboard(
PageID.DASHBOARD_CONTRACT_DETAIL
);

useEffect(() => {
setHeaderTitle("契約者詳細");
setTabs(<ContractTabs />);
}, []);
return <Box>ContractDetail</Box>;
}

+ 17
- 0
src/pages/dashboard/contract/list.tsx Visa fil

@@ -0,0 +1,17 @@
import { Box } from "@mui/material";
import { PageID } from "codes/page";
import useDashboard from "hooks/useDashBoard";
import ContractTabs from "layouts/dashbord/tab/ContractTabs";
import { useEffect } from "react";

export default function ContractList() {
const { setHeaderTitle, setTabs } = useDashboard(
PageID.DASHBOARD_CONTRACT_LIST
);

useEffect(() => {
setHeaderTitle("契約者一覧");
setTabs(<ContractTabs />);
}, []);
return <Box>ContractList</Box>;
}

+ 17
- 0
src/pages/test/TestA.tsx Visa fil

@@ -0,0 +1,17 @@
import { Box } from "@mui/material";
import { PageID } from "codes/page";
import useDashboard from "hooks/useDashBoard";
import ContractTabs from "layouts/dashbord/tab/ContractTabs";
import { useEffect } from "react";

export default function TestA() {
const { setHeaderTitle, setTabs } = useDashboard(
PageID.DASHBOARD_CONTRACT_LIST
);

useEffect(() => {
setHeaderTitle("契約者一覧");
setTabs(<ContractTabs />);
}, []);
return <Box>TestA</Box>;
}

+ 12
- 0
src/pages/test/TestB.tsx Visa fil

@@ -0,0 +1,12 @@
import { Box } from "@mui/material";
import useDashboard from "hooks/useDashBoard";
import { useEffect } from "react";

export default function TestB() {
const { setHeaderTitle } = useDashboard();

useEffect(() => {
setHeaderTitle("契約者詳細");
}, []);
return <Box>TestB</Box>;
}

+ 57
- 0
src/routes/index.tsx Visa fil

@@ -0,0 +1,57 @@
import LoadingScreen from "components/LoadingScreen";
import DashboardLayout from "layouts/dashbord";
import { ElementType, Suspense, lazy } from "react";
import { useLocation, useRoutes } from "react-router-dom";
import { PATH, getRoute } from "./path";
import { PageID } from "codes/page";

const Loadable = (Component: ElementType) => (props: any) => {
return (
<Suspense fallback={<LoadingScreen />}>
<Component {...props} />
</Suspense>
);
};

export function Routes() {
return useRoutes([
{
path: "testa",
element: <TestAPage />,
},
{
path: "testb",
element: <TestBPage />,
},
{
path: "*",
element: <Page404 />,
},
{
path: PATH.dashboard.root,
element: <DashboardLayout />,
children: [
{
path: getRoute(PageID.DASHBOARD_CONTRACT_LIST, PATH.dashboard.root),
element: <ContractList />,
},
{
path: getRoute(PageID.DASHBOARD_CONTRACT_DETAIL, PATH.dashboard.root),
element: <ContractDetail />,
},
],
},
]);
}

const TestAPage = Loadable(lazy(() => import("pages/test/TestA")));
const TestBPage = Loadable(lazy(() => import("pages/test/TestB")));

const ContractList = Loadable(
lazy(() => import("pages/dashboard/contract/list"))
);
const ContractDetail = Loadable(
lazy(() => import("pages/dashboard/contract/detail"))
);

const Page404 = Loadable(lazy(() => import("pages/common/Page404")));

+ 91
- 0
src/routes/path.ts Visa fil

@@ -0,0 +1,91 @@
import { Dictionary } from "@types";
import { PageID } from "codes/page";
import { get, isNumber, isString, replace } from "lodash";

const DASHBOARD = "dashboard";

const PREFIX = {
list: "list",
detail: "detail",
};

export const PATH = {
dashboard: {
root: "dashboard",
contract: "contract",
},
};

const makePath = (paths: string[]): string => {
return "/" + paths.join("/");
};

const makeListPageCallback = (path: string) => {
return (page: number) => {
return [path, "/", PREFIX.list, String(page)].join("/");
};
};
const makeDashboardPath = (paths: string[]): string => {
return makePath([PATH.dashboard.root, ...paths]);
};

export const PATH_DASHBOARD = {
contract: {
list: makeListPageCallback(makeDashboardPath([PATH.dashboard.contract])),
detail: makeDashboardPath([PATH.dashboard.contract, "detail"]),
},
sms: {
list: makePath([DASHBOARD, "sms"]),
},
};

const PATHS = {
[PageID.DASHBOARD_CONTRACT_LIST]: "/dashboard/contract/list/:page",
[PageID.DASHBOARD_CONTRACT_DETAIL]: "/dashboard/contract/detail",
};

export type PathOption = {
page?: number;
query?: Dictionary;
};
export function getPath(pageId: PageID, option?: PathOption) {
let path = getRoute(pageId);

// ページ番号解決
path = replacePathParam(path, "page", option?.page ?? 0);

// その他URLパラメータ変換
if (option?.query !== undefined) {
Object.keys(option.query).forEach((key) => {
const value = get(option.query, key);
if (value === undefined) return;

path = replacePathParam(path, key, value);
});
}

return path;
}

export function getRoute(pageId: PageID, exclude?: string): string {
let path = get(PATHS, pageId);
if (!path) throw new Error("ルート未定義:" + pageId);

if (exclude) {
path = replace(path, "/" + exclude + "/", "");
}

return path;
}

function replacePathParam(
sourceStr: string,
searchStr: string,
replacement: string | number
): string {
return replace(
sourceStr,
":" + searchStr,
isString(replacement) ? replacement : String(replacement)
);
}

+ 150
- 0
src/theme/index.tsx Visa fil

@@ -0,0 +1,150 @@
import { ThemeProvider, createTheme } from "@mui/material";
import { HasChildren } from "@types";

let theme = createTheme({
palette: {
primary: {
light: "#63ccff",
main: "#009be5",
dark: "#006db3",
},
},
typography: {
h5: {
fontWeight: 500,
fontSize: 26,
letterSpacing: 0.5,
},
},
shape: {
borderRadius: 8,
},
components: {
MuiTab: {
defaultProps: {
disableRipple: true,
},
},
},
mixins: {
toolbar: {
minHeight: 48,
},
},
});

theme = {
...theme,
components: {
MuiDrawer: {
styleOverrides: {
paper: {
backgroundColor: "#081627",
},
},
},
MuiButton: {
styleOverrides: {
root: {
textTransform: "none",
},
contained: {
boxShadow: "none",
"&:active": {
boxShadow: "none",
},
},
},
},
MuiTabs: {
styleOverrides: {
root: {
marginLeft: theme.spacing(1),
},
indicator: {
height: 3,
borderTopLeftRadius: 3,
borderTopRightRadius: 3,
backgroundColor: theme.palette.common.white,
},
},
},
MuiTab: {
styleOverrides: {
root: {
textTransform: "none",
margin: "0 16px",
minWidth: 0,
padding: 0,
[theme.breakpoints.up("md")]: {
padding: 0,
minWidth: 0,
},
},
},
},
MuiIconButton: {
styleOverrides: {
root: {
padding: theme.spacing(1),
},
},
},
MuiTooltip: {
styleOverrides: {
tooltip: {
borderRadius: 4,
},
},
},
MuiDivider: {
styleOverrides: {
root: {
backgroundColor: "rgb(255,255,255,0.15)",
},
},
},
MuiListItemButton: {
styleOverrides: {
root: {
"&.Mui-selected": {
color: "#4fc3f7",
},
},
},
},
MuiListItemText: {
styleOverrides: {
primary: {
fontSize: 14,
fontWeight: theme.typography.fontWeightMedium,
},
},
},
MuiListItemIcon: {
styleOverrides: {
root: {
color: "inherit",
minWidth: "auto",
marginRight: theme.spacing(2),
"& svg": {
fontSize: 20,
},
},
},
},
MuiAvatar: {
styleOverrides: {
root: {
width: 32,
height: 32,
},
},
},
},
};

type Props = HasChildren;
export function AppThemeProvider({ children }: Props) {
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
}

+ 3
- 2
tsconfig.json Visa fil

@@ -18,9 +18,10 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
"jsx": "react-jsx",
"baseUrl": "src"
},
"include": [
"src"
]
}
}

+ 328
- 8
yarn.lock Visa fil

@@ -169,7 +169,7 @@
dependencies:
"@babel/types" "^7.21.5"

"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4":
"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz#ac88b2f76093637489e718a90cec6cf8a9b029af"
integrity sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==
@@ -1038,7 +1038,7 @@
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==

"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.20.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.21.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==
@@ -1195,6 +1195,113 @@
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz#2cbcf822bf3764c9658c4d2e568bd0c0cb748016"
integrity sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==

"@emotion/babel-plugin@^11.10.8":
version "11.10.8"
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.8.tgz#bae325c902937665d00684038fd5294223ef9e1d"
integrity sha512-gxNky50AJL3AlkbjvTARiwAqei6/tNUxDZPSKd+3jqWVM3AmdVTTdpjHorR/an/M0VJqdsuq5oGcFH+rjtyujQ==
dependencies:
"@babel/helper-module-imports" "^7.16.7"
"@babel/runtime" "^7.18.3"
"@emotion/hash" "^0.9.0"
"@emotion/memoize" "^0.8.0"
"@emotion/serialize" "^1.1.1"
babel-plugin-macros "^3.1.0"
convert-source-map "^1.5.0"
escape-string-regexp "^4.0.0"
find-root "^1.1.0"
source-map "^0.5.7"
stylis "4.1.4"

"@emotion/cache@^11.10.7", "@emotion/cache@^11.10.8":
version "11.10.8"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.8.tgz#3b39b4761bea0ae2f4f07f0a425eec8b6977c03e"
integrity sha512-5fyqGHi51LU95o7qQ/vD1jyvC4uCY5GcBT+UgP4LHdpO9jPDlXqhrRr9/wCKmfoAvh5G/F7aOh4MwQa+8uEqhA==
dependencies:
"@emotion/memoize" "^0.8.0"
"@emotion/sheet" "^1.2.1"
"@emotion/utils" "^1.2.0"
"@emotion/weak-memoize" "^0.3.0"
stylis "4.1.4"

"@emotion/hash@^0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.0.tgz#c5153d50401ee3c027a57a177bc269b16d889cb7"
integrity sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==

"@emotion/is-prop-valid@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz#7f2d35c97891669f7e276eb71c83376a5dc44c83"
integrity sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==
dependencies:
"@emotion/memoize" "^0.8.0"

"@emotion/memoize@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f"
integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==

"@emotion/react@^11.10.8":
version "11.10.8"
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.8.tgz#02e274ecb45e03ab9d7a8eb9f0f0c064613eaf7b"
integrity sha512-ZfGfiABtJ1P1OXqOBsW08EgCDp5fK6C5I8hUJauc/VcJBGSzqAirMnFslhFWnZJ/w5HxPI36XbvMV0l4KZHl+w==
dependencies:
"@babel/runtime" "^7.18.3"
"@emotion/babel-plugin" "^11.10.8"
"@emotion/cache" "^11.10.8"
"@emotion/serialize" "^1.1.1"
"@emotion/use-insertion-effect-with-fallbacks" "^1.0.0"
"@emotion/utils" "^1.2.0"
"@emotion/weak-memoize" "^0.3.0"
hoist-non-react-statics "^3.3.1"

"@emotion/serialize@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.1.tgz#0595701b1902feded8a96d293b26be3f5c1a5cf0"
integrity sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==
dependencies:
"@emotion/hash" "^0.9.0"
"@emotion/memoize" "^0.8.0"
"@emotion/unitless" "^0.8.0"
"@emotion/utils" "^1.2.0"
csstype "^3.0.2"

"@emotion/sheet@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.1.tgz#0767e0305230e894897cadb6c8df2c51e61a6c2c"
integrity sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==

"@emotion/styled@^11.10.8":
version "11.10.8"
resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.10.8.tgz#a3fd68efd90bd7e8a06b82b95adec643d386fa69"
integrity sha512-gow0lF4Uw/QEdX2REMhI8v6wLOabPKJ+4HKNF0xdJ2DJdznN6fxaXpQOx6sNkyBhSUL558Rmcu1Lq/MYlVo4vw==
dependencies:
"@babel/runtime" "^7.18.3"
"@emotion/babel-plugin" "^11.10.8"
"@emotion/is-prop-valid" "^1.2.0"
"@emotion/serialize" "^1.1.1"
"@emotion/use-insertion-effect-with-fallbacks" "^1.0.0"
"@emotion/utils" "^1.2.0"

"@emotion/unitless@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.0.tgz#a4a36e9cbdc6903737cd20d38033241e1b8833db"
integrity sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==

"@emotion/use-insertion-effect-with-fallbacks@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz#ffadaec35dbb7885bd54de3fa267ab2f860294df"
integrity sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==

"@emotion/utils@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.0.tgz#9716eaccbc6b5ded2ea5a90d65562609aab0f561"
integrity sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==

"@emotion/weak-memoize@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb"
integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==

"@eslint-community/eslint-utils@^4.2.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
@@ -1548,6 +1655,99 @@
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==

"@mui/base@5.0.0-alpha.127":
version "5.0.0-alpha.127"
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.127.tgz#bc61eaf1fd31094c939b6521cfea21643207c3b4"
integrity sha512-FoRQd0IOH9MnfyL5yXssyQRnC4vXI+1bwkU1idr+wNkP1ZfxE+JsThHcfl1dy5azLssVUGTtQFD9edQLdbyJog==
dependencies:
"@babel/runtime" "^7.21.0"
"@emotion/is-prop-valid" "^1.2.0"
"@mui/types" "^7.2.4"
"@mui/utils" "^5.12.0"
"@popperjs/core" "^2.11.7"
clsx "^1.2.1"
prop-types "^15.8.1"
react-is "^18.2.0"

"@mui/core-downloads-tracker@^5.12.2":
version "5.12.2"
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.12.2.tgz#4a0186d25b01d693171366e1c00de0e7c8c35f6a"
integrity sha512-Qn7dy8tql6T0hY6gTFPkpWlnqVVFGu5Z6QzEzUSzzmLZpfAx4kf8sFz0PHiB7gU5yrqcZF9picMx1shpRY/rXw==

"@mui/icons-material@^5.11.16":
version "5.11.16"
resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.11.16.tgz#417fa773c56672e39d6ccfed9ac55591985f0d38"
integrity sha512-oKkx9z9Kwg40NtcIajF9uOXhxiyTZrrm9nmIJ4UjkU2IdHpd4QVLbCc/5hZN/y0C6qzi2Zlxyr9TGddQx2vx2A==
dependencies:
"@babel/runtime" "^7.21.0"

"@mui/material@^5.12.2":
version "5.12.2"
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.12.2.tgz#c3fcc94e523d9e673e2e045dfad04d12ab454a80"
integrity sha512-XOVy6fVC0rI2dEwDq/1s4Te2hewTUe6lznzeVnruyATGkdmM06WnHqkZOoLVIWo9hWwAxpcgTDcAIVpFtt1nrw==
dependencies:
"@babel/runtime" "^7.21.0"
"@mui/base" "5.0.0-alpha.127"
"@mui/core-downloads-tracker" "^5.12.2"
"@mui/system" "^5.12.1"
"@mui/types" "^7.2.4"
"@mui/utils" "^5.12.0"
"@types/react-transition-group" "^4.4.5"
clsx "^1.2.1"
csstype "^3.1.2"
prop-types "^15.8.1"
react-is "^18.2.0"
react-transition-group "^4.4.5"

"@mui/private-theming@^5.12.0":
version "5.12.0"
resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.12.0.tgz#5f1e6fd09b1447c387fdac1eef7f23efca5c6d69"
integrity sha512-w5dwMen1CUm1puAtubqxY9BIzrBxbOThsg2iWMvRJmWyJAPdf3Z583fPXpqeA2lhTW79uH2jajk5Ka4FuGlTPg==
dependencies:
"@babel/runtime" "^7.21.0"
"@mui/utils" "^5.12.0"
prop-types "^15.8.1"

"@mui/styled-engine@^5.12.0":
version "5.12.0"
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.12.0.tgz#44640cad961adcc9413ae32116237cd1c8f7ddb0"
integrity sha512-frh8L7CRnvD0RDmIqEv6jFeKQUIXqW90BaZ6OrxJ2j4kIsiVLu29Gss4SbBvvrWwwatR72sBmC3w1aG4fjp9mQ==
dependencies:
"@babel/runtime" "^7.21.0"
"@emotion/cache" "^11.10.7"
csstype "^3.1.2"
prop-types "^15.8.1"

"@mui/system@^5.12.1":
version "5.12.1"
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.12.1.tgz#8452bc03159f0a6725b96bde1dee1316e308231b"
integrity sha512-Po+sicdV3bbRYXdU29XZaHPZrW7HUYUqU1qCu77GCCEMbahC756YpeyefdIYuPMUg0OdO3gKIUfDISBrkjJL+w==
dependencies:
"@babel/runtime" "^7.21.0"
"@mui/private-theming" "^5.12.0"
"@mui/styled-engine" "^5.12.0"
"@mui/types" "^7.2.4"
"@mui/utils" "^5.12.0"
clsx "^1.2.1"
csstype "^3.1.2"
prop-types "^15.8.1"

"@mui/types@^7.2.4":
version "7.2.4"
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.4.tgz#b6fade19323b754c5c6de679a38f068fd50b9328"
integrity sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==

"@mui/utils@^5.12.0":
version "5.12.0"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.12.0.tgz#284db48b36ac26f3d34076379072c1dc8aed1ad0"
integrity sha512-RmQwgzF72p7Yr4+AAUO6j1v2uzt6wr7SWXn68KBsnfVpdOHyclCzH2lr/Xu6YOw9su4JRtdAIYfJFXsS6Cjkmw==
dependencies:
"@babel/runtime" "^7.21.0"
"@types/prop-types" "^15.7.5"
"@types/react-is" "^16.7.1 || ^17.0.0"
prop-types "^15.8.1"
react-is "^18.2.0"

"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
version "5.1.1-v1"
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
@@ -1591,6 +1791,16 @@
schema-utils "^3.0.0"
source-map "^0.7.3"

"@popperjs/core@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.7.tgz#ccab5c8f7dc557a52ca3288c10075c9ccd37fff7"
integrity sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==

"@remix-run/router@1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.6.0.tgz#45010e1826f4d81a1b2cfaf874f1aac93998cd28"
integrity sha512-N13NRw3T2+6Xi9J//3CGLsK2OqC8NMme3d/YX+nh05K9YHWGcv8DycHJrqGScSP4T75o8IN6nqIMhVFU8ohg8w==

"@rollup/plugin-babel@^5.2.0":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"
@@ -1946,6 +2156,11 @@
dependencies:
"@types/node" "*"

"@types/history@^4.7.11":
version "4.7.11"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==

"@types/html-minifier-terser@^6.0.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
@@ -2003,6 +2218,11 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==

"@types/lodash@^4.14.194":
version "4.14.194"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.194.tgz#b71eb6f7a0ff11bff59fc987134a093029258a76"
integrity sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g==

"@types/mime@*":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
@@ -2033,7 +2253,7 @@
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0"
integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==

"@types/prop-types@*":
"@types/prop-types@*", "@types/prop-types@^15.7.5":
version "15.7.5"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
@@ -2060,6 +2280,37 @@
dependencies:
"@types/react" "*"

"@types/react-is@^16.7.1 || ^17.0.0":
version "17.0.4"
resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-17.0.4.tgz#3cccd02851f7f7a75b21d6e922da26bc7f8f44ad"
integrity sha512-FLzd0K9pnaEvKz4D1vYxK9JmgQPiGk1lu23o1kqGsLeT0iPbRSF7b76+S5T9fD8aRa0B8bY7I/3DebEj+1ysBA==
dependencies:
"@types/react" "^17"

"@types/react-router-dom@^5.3.3":
version "5.3.3"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83"
integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==
dependencies:
"@types/history" "^4.7.11"
"@types/react" "*"
"@types/react-router" "*"

"@types/react-router@*":
version "5.1.20"
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.20.tgz#88eccaa122a82405ef3efbcaaa5dcdd9f021387c"
integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==
dependencies:
"@types/history" "^4.7.11"
"@types/react" "*"

"@types/react-transition-group@^4.4.5":
version "4.4.5"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416"
integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==
dependencies:
"@types/react" "*"

"@types/react@*", "@types/react@^18.0.0":
version "18.2.0"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.0.tgz#15cda145354accfc09a18d2f2305f9fc099ada21"
@@ -2069,6 +2320,15 @@
"@types/scheduler" "*"
csstype "^3.0.2"

"@types/react@^17":
version "17.0.58"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.58.tgz#c8bbc82114e5c29001548ebe8ed6c4ba4d3c9fb0"
integrity sha512-c1GzVY97P0fGxwGxhYq989j4XwlcHQoto6wQISOC2v6wm3h0PORRWJFHlkRjfGsiG3y1609WdQ+J+tKxvrEd6A==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"

"@types/resolve@1.17.1":
version "1.17.1"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
@@ -3109,6 +3369,11 @@ cliui@^7.0.2:
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"

clsx@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==

co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@@ -3251,7 +3516,7 @@ content-type@~1.0.4:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==

convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
@@ -3514,7 +3779,7 @@ cssstyle@^2.3.0:
dependencies:
cssom "~0.3.6"

csstype@^3.0.2:
csstype@^3.0.2, csstype@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
@@ -3721,6 +3986,14 @@ dom-converter@^0.2.0:
dependencies:
utila "~0.4"

dom-helpers@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
dependencies:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"

dom-serializer@0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
@@ -4457,6 +4730,11 @@ find-cache-dir@^3.3.1:
make-dir "^3.0.2"
pkg-dir "^4.1.0"

find-root@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==

find-up@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
@@ -4819,6 +5097,13 @@ he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==

hoist-non-react-statics@^3.3.1:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
dependencies:
react-is "^16.7.0"

hoopy@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d"
@@ -7397,7 +7682,7 @@ prompts@^2.0.1, prompts@^2.4.2:
kleur "^3.0.3"
sisteransi "^1.0.5"

prop-types@^15.8.1:
prop-types@^15.6.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -7530,7 +7815,7 @@ react-error-overlay@^6.0.11:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==

react-is@^16.13.1:
react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -7540,7 +7825,7 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==

react-is@^18.0.0:
react-is@^18.0.0, react-is@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
@@ -7550,6 +7835,21 @@ react-refresh@^0.11.0:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==

react-router-dom@^6.11.0:
version "6.11.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.11.0.tgz#e9507327dad2dfdd3c4815810dbd8de8fc2ee211"
integrity sha512-Q3mK1c/CYoF++J6ZINz7EZzwlgSOZK/kc7lxIA7PhtWhKju4KfF1WHqlx0kVCIFJAWztuYVpXZeljEbds8z4Og==
dependencies:
"@remix-run/router" "1.6.0"
react-router "6.11.0"

react-router@6.11.0:
version "6.11.0"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.11.0.tgz#2e9008e40f8ce69e381373a7916ebadfbf2ff184"
integrity sha512-hTm6KKNpj9SDG4syIWRjCU219O0RZY8RUPobCFt9p+PlF7nnkRgMoh2DieTKvw3F3Mw6zg565HGnSv8BuoY5oQ==
dependencies:
"@remix-run/router" "1.6.0"

react-scripts@5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-5.0.1.tgz#6285dbd65a8ba6e49ca8d651ce30645a6d980003"
@@ -7605,6 +7905,16 @@ react-scripts@5.0.1:
optionalDependencies:
fsevents "^2.3.2"

react-transition-group@^4.4.5:
version "4.4.5"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
dependencies:
"@babel/runtime" "^7.5.5"
dom-helpers "^5.0.1"
loose-envify "^1.4.0"
prop-types "^15.6.2"

react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
@@ -8123,6 +8433,11 @@ source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, sourc
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==

source-map@^0.5.7:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==

source-map@^0.7.3:
version "0.7.4"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
@@ -8355,6 +8670,11 @@ stylehacks@^5.1.1:
browserslist "^4.21.4"
postcss-selector-parser "^6.0.4"

stylis@4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.4.tgz#9cb60e7153d8ac6d02d773552bf51c7a0344535b"
integrity sha512-USf5pszRYwuE6hg9by0OkKChkQYEXfkeTtm0xKw+jqQhwyjCVLdYyMBK7R+n7dhzsblAWJnGxju4vxq5eH20GQ==

sucrase@^3.32.0:
version "3.32.0"
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.32.0.tgz#c4a95e0f1e18b6847127258a75cf360bc568d4a7"


Laddar…
Avbryt
Spara