diff --git a/src/api/auth.ts b/src/api/auth.ts index 28dfb42..a6c57fd 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -1,12 +1,12 @@ import { UserRole } from "codes/user"; -import { APICommonResponse, ApiId, HttpMethod, request } from "."; +import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "."; import { getUrl } from "./url"; import { CustomCode } from "codes/custom"; type MeResponse = { data: { id: string; - contract_id: string; + contract_id: string | null; role: UserRole; name: string; custom?: CustomCode[]; @@ -45,3 +45,13 @@ export const logout = async () => { }); return res; }; + +// 成り代わり +export const changeContract = async (param: { contract_id: string | null }) => { + const res = await request({ + url: getUrl(ApiId.CHANGE_CONTRACT), + method: HttpMethod.POST, + data: makeParam(param), + }); + return res; +}; diff --git a/src/api/index.ts b/src/api/index.ts index d8331c7..1b0aa15 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -21,6 +21,8 @@ export const ApiId = { RECEIPT_ISSUING_ORDER_MAIL_ORDER: id++, // DASHBOARD向け------------------------------- + CHANGE_CONTRACT: id++, + RECEIPT_ISSUING_ORDERS: id++, RECEIPT_ISSUING_ORDER: id++, RECEIPT_ISSUING_ORDER_CREATE: id++, diff --git a/src/api/url.ts b/src/api/url.ts index 2a9a358..e4dbe04 100644 --- a/src/api/url.ts +++ b/src/api/url.ts @@ -7,6 +7,7 @@ const urls = { [A.ME]: "me", [A.LOGIN]: "login", [A.LOGOUT]: "logout", + [A.CHANGE_CONTRACT]: "change-contract", [A.APP_TOKEN_CHECK]: "app-token-check", [A.RECEIPT_ISSUING_ORDER_CONFIRM]: "receipt-issuing-order/confirm", diff --git a/src/codes/page.ts b/src/codes/page.ts index 232ed69..3f6ff41 100644 --- a/src/codes/page.ts +++ b/src/codes/page.ts @@ -4,6 +4,7 @@ export const PageID = { LOGIN: id++, LOGOUT: id++, + CLEAR_CHANGE_CONTRACT: id++, APP_RECEIPT_ISSUING_ORDER_INDEX: id++, APP_RECEIPT_ISSUING_ORDER_MAIL_ORDER: id++, diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 41c6e1b..4ea310a 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -1,6 +1,11 @@ import { HasChildren } from "@types"; import { ResultCode } from "api"; -import { login as APILogin, logout as APILogout, me } from "api/auth"; +import { + login as APILogin, + logout as APILogout, + changeContract as APIChangeContract, + me, +} from "api/auth"; import { CustomCode } from "codes/custom"; import { PageID } from "codes/page"; import { UserRole } from "codes/user"; @@ -27,10 +32,12 @@ type Auth = { custom: CustomCode[]; customerName: string; + isChangedContractId: boolean; + login: (email: string, password: string) => Promise; logout: VoidFunction; - changeContractId: (contractId: string) => Promise; + changeContractId: (contractId: string | null) => Promise; checkRole: (role?: UserRole) => boolean; canAccess: (pageId: PageID) => boolean; @@ -47,9 +54,11 @@ export const AuthContext = createContext({ custom: [], customerName: "", + isChangedContractId: false, + login: async (email: string, password: string) => false, logout: () => {}, - changeContractId: async (contractId: string) => false, + changeContractId: async (contractId: string | null) => false, checkRole: (role?: UserRole) => false, canAccess: (pageId: PageID) => false, }); @@ -64,18 +73,24 @@ function AuthContextProvider({ children }: Props) { const [custom, setCustom] = useState([]); const [customerName, setCustomerName] = useState(""); + const isChangedContractId = useMemo(() => { + return role === UserRole.SUPER_ADMIN && contractId !== null; + }, [contractId]); + const authenticated = useMemo(() => { return role !== UserRole.NONE; }, [role]); const { callAPI: callMe } = useAPICall({ apiMethod: me, + backDrop: true, onSuccess: (res) => { setContractId(res.data.contract_id); setUserId(res.data.id); setRole(res.data.role); setName(res.data.name); setCustom(res.data.custom ?? []); + setCustomerName(res.data.contract_name ?? ""); setInitialized(true); }, onFailed: () => { @@ -86,12 +101,14 @@ function AuthContextProvider({ children }: Props) { const { callAPI: callLogin } = useAPICall({ apiMethod: APILogin, + backDrop: true, onSuccess: (res) => { setContractId(res.data.contract_id); setUserId(res.data.id); setRole(res.data.role); setName(res.data.name); setCustom(res.data.custom ?? []); + setCustomerName(res.data.contract_name ?? ""); setInitialized(true); }, }); @@ -102,6 +119,16 @@ function AuthContextProvider({ children }: Props) { clear(); }, }); + const { callAPI: callChangeContract } = useAPICall({ + apiMethod: APIChangeContract, + backDrop: true, + onSuccess: (res) => { + setContractId(res.data.contract_id); + setCustom(res.data.custom ?? []); + setCustomerName(res.data.contract_name ?? ""); + setInitialized(true); + }, + }); const clear = () => { setRole(UserRole.NONE); @@ -121,9 +148,9 @@ function AuthContextProvider({ children }: Props) { callLogout({}); console.info("ログアウト"); }; - const changeContractId = async (contractId: string) => { - console.error("未実装 成り代わり"); - return false; + const changeContractId = async (contractId: string | null) => { + const res = await callChangeContract({ contract_id: contractId }); + return res?.result === ResultCode.SUCCESS; }; const checkRole = useCallback( @@ -162,14 +189,19 @@ function AuthContextProvider({ children }: Props) { return ( !!authorization && + // 権限条件 authorization.role.includes(role) && + // カスタム条件 (authorization.custom.length === 0 || !!custom.find((c) => { return authorization.custom.includes(c); - })) + })) && + // 成り代わり条件 + (authorization.allowChangedContract === undefined || + isChangedContractId === authorization.allowChangedContract) ); }, - [initialized, role, custom] + [initialized, role, custom, isChangedContractId] ); useEffect(() => { @@ -187,8 +219,8 @@ function AuthContextProvider({ children }: Props) { userId, name, custom, - customerName, + isChangedContractId, // Func login, diff --git a/src/layouts/dashbord/navigator.tsx b/src/layouts/dashbord/navigator.tsx index 092f416..a54c2cc 100644 --- a/src/layouts/dashbord/navigator.tsx +++ b/src/layouts/dashbord/navigator.tsx @@ -4,8 +4,9 @@ import PeopleIcon from "@mui/icons-material/People"; import ArticleIcon from "@mui/icons-material/Article"; import SettingsIcon from "@mui/icons-material/Settings"; import AccountBoxIcon from "@mui/icons-material/AccountBox"; +import AccountCircleIcon from "@mui/icons-material/AccountCircle"; import AccountBalanceIcon from "@mui/icons-material/AccountBalance"; -import { Collapse } from "@mui/material"; +import { Collapse, Typography } from "@mui/material"; import Box from "@mui/material/Box"; import Divider from "@mui/material/Divider"; import Drawer, { DrawerProps } from "@mui/material/Drawer"; @@ -51,6 +52,12 @@ const item = { }, }; +const viewItem = { + py: "2px", + px: 3, + color: "rgba(255, 255, 255, 0.7)", +}; + const itemCategory = { boxShadow: "0 -1px 0 rgb(255,255,255,0.1) inset", py: 1.5, @@ -60,7 +67,7 @@ const itemCategory = { export default function Navigator(props: DrawerProps) { const { ...other } = props; - const { userId } = useAuth(); + const { userId, customerName, name, isChangedContractId } = useAuth(); const categories: Group[] = [ { @@ -138,6 +145,11 @@ export default function Navigator(props: DrawerProps) { }, ], }, + { + label: "成り代わり解除", + icon: , + id: PageID.CLEAR_CHANGE_CONTRACT, + }, { label: "ログアウト", icon: , id: PageID.LOGOUT }, ], }, @@ -151,11 +163,18 @@ export default function Navigator(props: DrawerProps) { > EasyReceipt - + - + - Project Overview + {} + + {customerName} + {name} + {isChangedContractId && ( + !!成り代わり中!! + )} + {categories.map((group, index) => { @@ -188,6 +207,7 @@ function Group(group: Group) { function SubGroup({ icon, label, id, children, option }: SubGroup) { const { pageId } = usePage(); + const { canAccess } = useAuth(); const { navigateWhenChanged } = useNavigateCustom(); const { elements, shouldOpen } = useContents(children ?? []); @@ -219,7 +239,7 @@ function SubGroup({ icon, label, id, children, option }: SubGroup) { ); } // 子要素なしの場合 - if (id !== undefined) { + if (id !== undefined && canAccess(id)) { const handleClick = () => { if (id) { const path = getPath(id, option); diff --git a/src/pages/auth/clear-contract.tsx b/src/pages/auth/clear-contract.tsx new file mode 100644 index 0000000..955da2a --- /dev/null +++ b/src/pages/auth/clear-contract.tsx @@ -0,0 +1,26 @@ +import { PageID } from "codes/page"; +import useAuth from "hooks/useAuth"; +import useNavigateCustom from "hooks/useNavigateCustom"; +import useSnackbarCustom from "hooks/useSnackbarCustom"; +import { useEffect } from "react"; +import { getPath } from "routes/path"; + +export default function ClearChangeContract() { + const { changeContractId } = useAuth(); + const { info, error } = useSnackbarCustom(); + + const { navigateWhenChanged } = useNavigateCustom(); + + useEffect(() => { + changeContractId(null).then((ret) => { + if (ret) { + info("成り代わり解除しました"); + } else { + error("失敗しました"); + } + navigateWhenChanged(getPath(PageID.DASHBOARD_OVERVIEW)); + }); + }, []); + + return null; +} diff --git a/src/pages/dashboard/contract/list.tsx b/src/pages/dashboard/contract/list.tsx index 15149d0..f6f6fa4 100644 --- a/src/pages/dashboard/contract/list.tsx +++ b/src/pages/dashboard/contract/list.tsx @@ -11,16 +11,19 @@ import { Typography, } from "@mui/material"; import { Dictionary } from "@types"; +import { changeContract } from "api/auth"; import { Contract, getContracts } from "api/contract"; import { PageID, TabID } from "codes/page"; import { FormProvider, RHFTextField } from "components/hook-form"; import { TableHeadCustom } from "components/table"; import { SearchConditionContextProvider } from "contexts/SearchConditionContext"; import useAPICall from "hooks/useAPICall"; +import useAuth from "hooks/useAuth"; import useBackDrop from "hooks/useBackDrop"; import useDashboard from "hooks/useDashBoard"; import useNavigateCustom from "hooks/useNavigateCustom"; import useSearchConditionContext from "hooks/useSearchConditionContext"; +import useSnackbarCustom from "hooks/useSnackbarCustom"; import useTable, { UseTableReturn } from "hooks/useTable"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; @@ -221,10 +224,16 @@ type RowProps = { }; function Row({ data }: RowProps) { const { navigateWhenChanged } = useNavigateCustom(); - + const { changeContractId } = useAuth(); + const { info } = useSnackbarCustom(); const handleClick = () => { - navigateWhenChanged(getPath(PageID.DASHBOARD_CONTRACT_DETAIL), { - id: data.id, + // navigateWhenChanged(getPath(PageID.DASHBOARD_CONTRACT_DETAIL), { + // id: data.id, + // }); + + changeContractId(data.id).then((res) => { + info("成り代わりました"); + navigateWhenChanged(getPath(PageID.DASHBOARD_OVERVIEW)); }); }; diff --git a/src/routes/auth.ts b/src/routes/auth.ts index df676bb..a2852a2 100644 --- a/src/routes/auth.ts +++ b/src/routes/auth.ts @@ -5,45 +5,56 @@ import { UserRole as R } from "codes/user"; type AuthConfiguration = { role: R[]; custom: C[]; + + allowChangedContract?: boolean; }; export const AUTH = { [P.NONE]: setAuth("all"), [P.LOGIN]: setAuth("all"), [P.LOGOUT]: setAuth("all"), + [P.CLEAR_CHANGE_CONTRACT]: setAuth("eq", R.SUPER_ADMIN, { + allowChangedContract: true, + }), [P.DASHBOARD_OVERVIEW]: setAuth("ge", R.NORMAL_ADMIN), - [P.DASHBOARD_CONTRACT_LIST]: setAuth("eq", R.SUPER_ADMIN), - [P.DASHBOARD_CONTRACT_DETAIL]: setAuth("eq", R.SUPER_ADMIN), - [P.DASHBOARD_CONTRACT_CREATE]: setAuth("eq", R.SUPER_ADMIN), + [P.DASHBOARD_CONTRACT_LIST]: setAuth("eq", R.SUPER_ADMIN, { + allowChangedContract: false, + }), + [P.DASHBOARD_CONTRACT_DETAIL]: setAuth("eq", R.SUPER_ADMIN, { + allowChangedContract: false, + }), + [P.DASHBOARD_CONTRACT_CREATE]: setAuth("eq", R.SUPER_ADMIN, { + allowChangedContract: false, + }), [P.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE_CUSTOM_HELLO_TECHNO]: setAuth( "ge", R.NORMAL_ADMIN, - [C.HELLO_TECHNO] + { custom: [C.HELLO_TECHNO] } ), [P.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST_CUSTOM_HELLO_TECHNO]: setAuth( "ge", R.NORMAL_ADMIN, - [C.HELLO_TECHNO] + { custom: [C.HELLO_TECHNO] } ), [P.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL_CUSTOM_HELLO_TECHNO]: setAuth( "ge", R.NORMAL_ADMIN, - [C.HELLO_TECHNO] + { custom: [C.HELLO_TECHNO] } ), [P.DASHBOARD_USE_SUMMARY_LIST_CUSTOM_HELLO_TECHNO]: setAuth( "ge", R.NORMAL_ADMIN, - [C.HELLO_TECHNO] + { custom: [C.HELLO_TECHNO] } ), [P.DASHBOARD_USE_SUMMARY_DETAIL_CUSTOM_HELLO_TECHNO]: setAuth( "ge", R.NORMAL_ADMIN, - [C.HELLO_TECHNO] + { custom: [C.HELLO_TECHNO] } ), - [P.DASHBOARD_LOGIN_USER_LIST]: setAuth("ge", R.CONTRACT_ADMIN), - [P.DASHBOARD_LOGIN_USER_CREATE]: setAuth("ge", R.CONTRACT_ADMIN), + [P.DASHBOARD_LOGIN_USER_LIST]: setAuth("eq", R.CONTRACT_ADMIN), + [P.DASHBOARD_LOGIN_USER_CREATE]: setAuth("eq", R.CONTRACT_ADMIN), [P.DASHBOARD_LOGIN_USER_CHANGE_PASSWORD]: setAuth("ge", R.NORMAL_ADMIN), [P.PAGE_403]: setAuth("all"), @@ -52,10 +63,14 @@ export const AUTH = { type Target = "ge" | "le" | "eq" | "all"; type UserRoleKey = keyof typeof R; +type OptionProps = { + custom?: C[]; + allowChangedContract?: boolean; +}; function setAuth( target: Target, targetRole?: R, - custom: C[] = [] + option?: OptionProps ): AuthConfiguration { const roles: R[] = []; @@ -87,6 +102,7 @@ function setAuth( return { role: roles, - custom: custom, + custom: option?.custom ?? [], + allowChangedContract: option?.allowChangedContract, }; } diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 76203c9..157bb54 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -28,6 +28,10 @@ const AuthRoutes = (): RouteObject => ({ path: getRoute(PageID.LOGOUT), element: , }, + { + path: getRoute(PageID.CLEAR_CHANGE_CONTRACT), + element: , + }, ], }); @@ -138,6 +142,7 @@ export function Routes() { // 認証関連 ------------------------------- const Login = Loadable(lazy(() => import("pages/auth/login"))); const Logout = Loadable(lazy(() => import("pages/auth/logout"))); +const ClearContract = Loadable(lazy(() => import("pages/auth/clear-contract"))); // App --------------------------- const ReceiptIssuingOrder = Loadable( diff --git a/src/routes/path.ts b/src/routes/path.ts index 289a519..0a26e8c 100644 --- a/src/routes/path.ts +++ b/src/routes/path.ts @@ -34,6 +34,7 @@ const PATHS = { // 認証 [makePathKey(PageID.LOGIN)]: "/login", [makePathKey(PageID.LOGOUT)]: "/logout", + [makePathKey(PageID.CLEAR_CHANGE_CONTRACT)]: "/clear-change-contract", [makePathKey(PageID.DASHBOARD_OVERVIEW)]: "/dashboard",