From 5c0a8b984d7ba3265d58d9cdd316d13b5496cc36 Mon Sep 17 00:00:00 2001 From: "sosuke.iwabuchi" Date: Wed, 17 May 2023 09:22:19 +0900 Subject: [PATCH] =?UTF-8?q?=E8=AA=8D=E8=A8=BC=E9=96=A2=E9=80=A3=E3=80=80?= =?UTF-8?q?=E6=95=B4=E5=82=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/codes/page.ts | 9 ++ src/contexts/AuthContext.tsx | 33 ++++- src/layouts/dashbord/index.tsx | 3 +- src/layouts/dashbord/navigator.tsx | 119 +++++++++--------- src/pages/auth/login.tsx | 2 +- src/pages/common/Page403.tsx | 5 + src/pages/common/Page404.tsx | 2 +- src/pages/dashboard/index.tsx | 17 +++ .../receipt-issuing-order/create.tsx | 17 +++ src/routes/auth.ts | 54 ++++++++ src/routes/index.tsx | 70 ++++++++--- src/routes/path.ts | 43 +++---- 12 files changed, 270 insertions(+), 104 deletions(-) create mode 100644 src/pages/common/Page403.tsx create mode 100644 src/pages/dashboard/index.tsx create mode 100644 src/pages/dashboard/receipt-issuing-order/create.tsx create mode 100644 src/routes/auth.ts diff --git a/src/codes/page.ts b/src/codes/page.ts index dbf9e73..2875db3 100644 --- a/src/codes/page.ts +++ b/src/codes/page.ts @@ -5,8 +5,17 @@ export const PageID = { LOGIN: id++, LOGOUT: id++, + DASHBOARD_OVERVIEW: id++, + DASHBOARD_CONTRACT_LIST: id++, DASHBOARD_CONTRACT_DETAIL: id++, + + DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE: id++, + DASHBOARD_RECEIPT_ISSUING_ORDER_LIST: id++, + DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL: id++, + + PAGE_403: id++, + PAGE_404: id++, } as const; export type PageID = (typeof PageID)[keyof typeof PageID]; diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index d8863f0..31a600f 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -1,9 +1,18 @@ import { HasChildren } from "@types"; import { ResultCode } from "api"; import { login as APILogin, logout as APILogout, me } from "api/auth"; +import { PageID } from "codes/page"; import { UserRole } from "codes/user"; import useAPICall from "hooks/useAPICall"; -import { createContext, memo, useEffect, useMemo, useState } from "react"; +import { + createContext, + memo, + useCallback, + useEffect, + useMemo, + useState, +} from "react"; +import { AUTH } from "routes/auth"; type Auth = { initialized: boolean; @@ -19,6 +28,7 @@ type Auth = { changeContractId: (contractId: string) => Promise; checkRole: (role?: UserRole) => boolean; + canAccess: (pageId: PageID) => boolean; }; export const AuthContext = createContext({ initialized: false, @@ -32,6 +42,7 @@ export const AuthContext = createContext({ logout: () => {}, changeContractId: async (contractId: string) => false, checkRole: (role?: UserRole) => false, + canAccess: (pageId: PageID) => false, }); type Props = HasChildren; @@ -93,10 +104,21 @@ function AuthContextProvider({ children }: Props) { return false; }; - const checkRole = (targetRole?: UserRole): boolean => { - if (targetRole === undefined) return true; - return targetRole <= role; - }; + const checkRole = useCallback( + (targetRole?: UserRole): boolean => { + if (targetRole === undefined) return true; + return targetRole <= role; + }, + [role] + ); + + const canAccess = useCallback( + (pageId: PageID): boolean => { + const roles = AUTH[pageId] ?? []; + return roles.includes(role); + }, + [role] + ); useEffect(() => { callMe({}); @@ -116,6 +138,7 @@ function AuthContextProvider({ children }: Props) { logout, changeContractId, checkRole, + canAccess, }} > {children} diff --git a/src/layouts/dashbord/index.tsx b/src/layouts/dashbord/index.tsx index 2c0d946..f040d27 100644 --- a/src/layouts/dashbord/index.tsx +++ b/src/layouts/dashbord/index.tsx @@ -23,8 +23,7 @@ function Copyright() { function App() { const [mobileOpen, setMobileOpen] = useState(false); - const { drawerWidth, innerHeight, innerWidth, contentsWidth, showDrawer } = - useDashboard(); + const { drawerWidth, innerWidth, contentsWidth, showDrawer } = useDashboard(); const handleDrawerToggle = () => { setMobileOpen(!mobileOpen); diff --git a/src/layouts/dashbord/navigator.tsx b/src/layouts/dashbord/navigator.tsx index ad8ee06..1611a1b 100644 --- a/src/layouts/dashbord/navigator.tsx +++ b/src/layouts/dashbord/navigator.tsx @@ -12,7 +12,6 @@ import ListItemButton from "@mui/material/ListItemButton"; import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemText from "@mui/material/ListItemText"; import { PageID } from "codes/page"; -import { UserRole } from "codes/user"; import useAuth from "hooks/useAuth"; import useNavigateCustom from "hooks/useNavigateCustom"; import usePage from "hooks/usePage"; @@ -22,7 +21,6 @@ import { getPath } from "routes/path"; type Group = { label: string; children: SubGroup[]; - role?: UserRole; }; type SubGroup = { @@ -32,13 +30,11 @@ type SubGroup = { // 子要素を持たない場合は設定 id?: PageID; - role?: UserRole; }; type Child = { label: string; id: PageID; - role?: UserRole; }; const categories: Group[] = [ @@ -48,7 +44,6 @@ const categories: Group[] = [ { label: "契約", icon: , - role: UserRole.SUPER_ADMIN, children: [ { id: PageID.DASHBOARD_CONTRACT_LIST, @@ -60,6 +55,20 @@ const categories: Group[] = [ }, ], }, + { + label: "領収証発行依頼", + icon: , + children: [ + { + id: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST, + label: "一覧", + }, + { + id: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE, + label: "新規", + }, + ], + }, ], }, { @@ -114,23 +123,24 @@ export default function Navigator(props: DrawerProps) { function Group(group: Group) { const { label, children } = group; - const { checkRole } = useAuth(); - if (!checkRole(group.role)) return null; + const elements = children.map((ele, index) => ( + + )); + + if (elements.length === 0) return null; return ( {label} - {children.map((ele, index) => ( - - ))} + {elements} ); } -function SubGroup({ icon, label, id, children, role }: SubGroup) { +function SubGroup({ icon, label, id, children }: SubGroup) { const { pageId } = usePage(); const { navigateWhenChanged } = useNavigateCustom(); @@ -138,20 +148,15 @@ function SubGroup({ icon, label, id, children, role }: SubGroup) { const [open, setOpen] = React.useState(false); - const { checkRole } = useAuth(); - React.useEffect(() => { setOpen(shouldOpen); }, [shouldOpen]); - if (!checkRole(role)) return null; - // 子要素ありの場合 if (elements && elements.length !== 0) { const handleClick = () => { setOpen(!open); }; - return ( <> @@ -167,57 +172,59 @@ function SubGroup({ icon, label, id, children, role }: SubGroup) { ); } - // 子要素なしの場合 - const handleClick = () => { - if (id) { - const path = getPath(id); - navigateWhenChanged(path); - } - }; - const selected = id === pageId; - return ( - - {icon} - {label} - - ); + if (id !== undefined) { + const handleClick = () => { + if (id) { + const path = getPath(id); + navigateWhenChanged(path); + } + }; + const selected = id === pageId; + return ( + + {icon} + {label} + + ); + } + return null; } function useContents(children: Child[]) { const { pageId } = usePage(); const { navigateWhenChanged } = useNavigateCustom(); - const { checkRole } = useAuth(); + const { canAccess, initialized } = useAuth(); const [shouldOpen, setShouldOpen] = React.useState(false); const elements = React.useMemo(() => { setShouldOpen(false); - return children.map(({ label, id, role }, index) => { - if (!checkRole(role)) return; - - const selected = id === pageId; - if (selected) { - setShouldOpen(true); - } - - const handleClick = () => { - const path = getPath(id); - navigateWhenChanged(path); - }; - - return ( - - - - ); - }); - }, [pageId]); + return children + .filter(({ id }) => canAccess(id)) + .map(({ label, id }, index) => { + const selected = id === pageId; + if (selected) { + setShouldOpen(true); + } + + const handleClick = () => { + const path = getPath(id); + navigateWhenChanged(path); + }; + + return ( + + + + ); + }); + }, [pageId, initialized]); return { elements, diff --git a/src/pages/auth/login.tsx b/src/pages/auth/login.tsx index 5d3184a..d879c98 100644 --- a/src/pages/auth/login.tsx +++ b/src/pages/auth/login.tsx @@ -49,7 +49,7 @@ export default function Login() { if (ret) { success("ログイン成功"); - navigateWhenChanged(getPath(PageID.DASHBOARD_CONTRACT_LIST)); + navigateWhenChanged(getPath(PageID.DASHBOARD_OVERVIEW)); } else { error("ログイン失敗"); setMessage("入力情報を確認してください"); diff --git a/src/pages/common/Page403.tsx b/src/pages/common/Page403.tsx new file mode 100644 index 0000000..9945740 --- /dev/null +++ b/src/pages/common/Page403.tsx @@ -0,0 +1,5 @@ +import { Box } from "@mui/material"; + +export default function Page403() { + return Un Authenticated.; +} diff --git a/src/pages/common/Page404.tsx b/src/pages/common/Page404.tsx index 0ea8238..7766d71 100644 --- a/src/pages/common/Page404.tsx +++ b/src/pages/common/Page404.tsx @@ -1,5 +1,5 @@ import { Box } from "@mui/material"; -export default function TestB() { +export default function Page404() { return NotFound.; } diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx new file mode 100644 index 0000000..84f80d9 --- /dev/null +++ b/src/pages/dashboard/index.tsx @@ -0,0 +1,17 @@ +import { Box } from "@mui/material"; +import { PageID, TabID } from "codes/page"; +import useDashboard from "hooks/useDashBoard"; +import { useEffect } from "react"; + +export default function Overview() { + const { setHeaderTitle, setTabs } = useDashboard( + PageID.DASHBOARD_OVERVIEW, + TabID.NONE + ); + + useEffect(() => { + setHeaderTitle("Dashboard"); + setTabs(null); + }, []); + return ; +} diff --git a/src/pages/dashboard/receipt-issuing-order/create.tsx b/src/pages/dashboard/receipt-issuing-order/create.tsx new file mode 100644 index 0000000..c543e1e --- /dev/null +++ b/src/pages/dashboard/receipt-issuing-order/create.tsx @@ -0,0 +1,17 @@ +import { Box } from "@mui/material"; +import { PageID, TabID } from "codes/page"; +import useDashboard from "hooks/useDashBoard"; +import { useEffect } from "react"; + +export default function ReceiptIssuingOrderCreate() { + const { setHeaderTitle, setTabs } = useDashboard( + PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE, + TabID.NONE + ); + + useEffect(() => { + setHeaderTitle("領収証発行依頼作成"); + setTabs(null); + }, []); + return Create; +} diff --git a/src/routes/auth.ts b/src/routes/auth.ts new file mode 100644 index 0000000..6a1ce64 --- /dev/null +++ b/src/routes/auth.ts @@ -0,0 +1,54 @@ +import { PageID as P } from "codes/page"; +import { UserRole as R } from "codes/user"; + +export const AUTH = { + [P.NONE]: setAuth("all"), + [P.LOGIN]: setAuth("all"), + [P.LOGOUT]: setAuth("all"), + + [P.DASHBOARD_OVERVIEW]: setAuth("ge", R.NORMAL_ADMIN), + + [P.DASHBOARD_CONTRACT_LIST]: setAuth("ge", R.SUPER_ADMIN), + [P.DASHBOARD_CONTRACT_DETAIL]: setAuth("ge", R.SUPER_ADMIN), + + [P.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE]: setAuth("ge", R.NORMAL_ADMIN), + [P.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST]: setAuth("ge", R.NORMAL_ADMIN), + [P.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL]: setAuth("ge", R.NORMAL_ADMIN), + + [P.PAGE_403]: setAuth("all"), + [P.PAGE_404]: setAuth("all"), +}; + +type Target = "ge" | "le" | "eq" | "all"; +type UserRoleKey = keyof typeof R; +function setAuth(target: Target, targetRole?: R): R[] { + const ret: R[] = []; + + for (const key in R) { + const role = R[key as UserRoleKey]; + + if (target === "all") { + ret.push(role); + continue; + } + + if (targetRole === undefined) { + continue; + } + + if (target === "ge" && role >= targetRole) { + ret.push(role); + continue; + } + if (target === "le" && role <= targetRole) { + ret.push(role); + continue; + } + if (target === "eq" && role === targetRole) { + ret.push(role); + continue; + } + } + + return ret; +} diff --git a/src/routes/index.tsx b/src/routes/index.tsx index f1ac201..d68a7cb 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,10 +1,12 @@ +import { PageID } from "codes/page"; import LoadingScreen from "components/LoadingScreen"; import DashboardLayout from "layouts/dashbord"; -import { ElementType, Suspense, lazy } from "react"; -import { RouteObject, useLocation, useRoutes } from "react-router-dom"; -import { PATH, getRoute } from "./path"; -import { PageID } from "codes/page"; import SimpleLayout from "layouts/simple"; +import { ElementType, Suspense, lazy, useMemo } from "react"; +import { RouteObject, useRoutes } from "react-router-dom"; +import { getRoute } from "./path"; +import useAuth from "hooks/useAuth"; +import { UserRole } from "codes/user"; const Loadable = (Component: ElementType) => (props: any) => { return ( @@ -28,41 +30,81 @@ const AuthRoutes = (): RouteObject => ({ ], }); -const DashboardRoutes = (): RouteObject => ({ - element: , - children: [ +const DashboardRoutes = (): RouteObject => { + const { canAccess } = useAuth(); + + const allChildren = [ { - path: getRoute(PageID.DASHBOARD_CONTRACT_LIST), + pageId: PageID.DASHBOARD_OVERVIEW, + element: , + target: UserRole.NORMAL_ADMIN, + }, + { + pageId: PageID.DASHBOARD_CONTRACT_LIST, element: , + target: UserRole.SUPER_ADMIN, }, { - path: getRoute(PageID.DASHBOARD_CONTRACT_DETAIL), + pageId: PageID.DASHBOARD_CONTRACT_DETAIL, element: , + target: UserRole.SUPER_ADMIN, }, - ], -}); + { + pageId: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE, + element: , + target: UserRole.NORMAL_ADMIN, + }, + ]; + + const children: RouteObject[] = useMemo(() => { + return allChildren + .filter(({ pageId }) => canAccess(pageId)) + .map(({ pageId, ...others }) => ({ + ...others, + path: getRoute(pageId), + })); + }, [canAccess]); + + return { + element: , + children, + }; +}; export function Routes() { + const { initialized } = useAuth(); return useRoutes([ AuthRoutes(), DashboardRoutes(), + { + path: "403", + element: , + }, { path: "*", - element: , + element: initialized ? : , }, ]); } -const TestAPage = Loadable(lazy(() => import("pages/test/TestA"))); -const TestBPage = Loadable(lazy(() => import("pages/test/TestB"))); +// 認証関連 ------------------------------- const Login = Loadable(lazy(() => import("pages/auth/login"))); const Logout = Loadable(lazy(() => import("pages/auth/logout"))); +//ダッシュボード ---------------------------- +const Dashboard = Loadable(lazy(() => import("pages/dashboard"))); +// 契約関連 const ContractList = Loadable( lazy(() => import("pages/dashboard/contract/list")) ); const ContractDetail = Loadable( lazy(() => import("pages/dashboard/contract/detail")) ); +// 領収証発行依頼 +const ReceiptIssuingOrderCreate = Loadable( + lazy(() => import("pages/dashboard/receipt-issuing-order/create")) +); +// その他 --------------------------------- +const Page403 = Loadable(lazy(() => import("pages/common/Page403"))); const Page404 = Loadable(lazy(() => import("pages/common/Page404"))); diff --git a/src/routes/path.ts b/src/routes/path.ts index bfe1a77..d604a83 100644 --- a/src/routes/path.ts +++ b/src/routes/path.ts @@ -1,30 +1,6 @@ import { Dictionary } from "@types"; import { PageID, TabID } from "codes/page"; -import { get, isArray, isNumber, isString, replace } from "lodash"; - -const DASHBOARD = "dashboard"; - -export const PATH = { - login: "/login", - logout: "/logout", - dashboard: { - root: "/dashboard", - contract: "/contract", - }, -}; - -// const makePath = (paths: string[]): string => { -// return "/" + paths.join("/"); -// }; - -// const makeListPageCallback = (path: string) => { -// return (page: number) => { -// return [path, String(page)].join("/"); -// }; -// }; -// const makeDashboardPath = (paths: string[]): string => { -// return makePath([PATH.dashboard.root, ...paths]); -// }; +import { get, isArray, isString, replace } from "lodash"; type PathKey = [PageID, TabID?] | PageID; const makePathKey = (arg: PathKey): string => { @@ -53,13 +29,30 @@ const getTabId = (key: PathKey): TabID => { }; const PATHS = { + [makePathKey(PageID.NONE)]: "/", + // 認証 [makePathKey(PageID.LOGIN)]: "/login", [makePathKey(PageID.LOGOUT)]: "/logout", + [makePathKey(PageID.DASHBOARD_OVERVIEW)]: "/dashboard", + + // 契約関連 [makePathKey(PageID.DASHBOARD_CONTRACT_LIST)]: "/dashboard/contract/list/:page", [makePathKey(PageID.DASHBOARD_CONTRACT_DETAIL)]: "/dashboard/contract/detail", + + // 領収証発行依頼関連 + [makePathKey(PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE)]: + "/dashboard/receipt-issusing-order/create", + [makePathKey(PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST)]: + "/dashboard/receipt-issusing-order/list/:page", + [makePathKey(PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL)]: + "/dashboard/receipt-issusing-order/detail", + + // その他 + [makePathKey(PageID.PAGE_403)]: "403", + [makePathKey(PageID.PAGE_404)]: "404", }; export type PathOption = {