| @@ -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<MeResponse>({ | |||
| url: getUrl(ApiId.CHANGE_CONTRACT), | |||
| method: HttpMethod.POST, | |||
| data: makeParam(param), | |||
| }); | |||
| return res; | |||
| }; | |||
| @@ -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++, | |||
| @@ -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", | |||
| @@ -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++, | |||
| @@ -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<boolean>; | |||
| logout: VoidFunction; | |||
| changeContractId: (contractId: string) => Promise<boolean>; | |||
| changeContractId: (contractId: string | null) => Promise<boolean>; | |||
| checkRole: (role?: UserRole) => boolean; | |||
| canAccess: (pageId: PageID) => boolean; | |||
| @@ -47,9 +54,11 @@ export const AuthContext = createContext<Auth>({ | |||
| 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<CustomCode[]>([]); | |||
| 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, | |||
| @@ -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: <SettingsIcon />, | |||
| id: PageID.CLEAR_CHANGE_CONTRACT, | |||
| }, | |||
| { label: "ログアウト", icon: <SettingsIcon />, id: PageID.LOGOUT }, | |||
| ], | |||
| }, | |||
| @@ -151,11 +163,18 @@ export default function Navigator(props: DrawerProps) { | |||
| > | |||
| EasyReceipt | |||
| </ListItem> | |||
| <ListItem sx={{ ...item, ...itemCategory }}> | |||
| <ListItem sx={{ ...viewItem, ...itemCategory }}> | |||
| <ListItemIcon> | |||
| <HomeIcon /> | |||
| <AccountCircleIcon /> | |||
| </ListItemIcon> | |||
| <ListItemText>Project Overview</ListItemText> | |||
| <ListItemText>{}</ListItemText> | |||
| <ListItemText> | |||
| <Typography>{customerName}</Typography> | |||
| <Typography>{name}</Typography> | |||
| {isChangedContractId && ( | |||
| <Typography color="error">!!成り代わり中!!</Typography> | |||
| )} | |||
| </ListItemText> | |||
| </ListItem> | |||
| {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); | |||
| @@ -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; | |||
| } | |||
| @@ -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)); | |||
| }); | |||
| }; | |||
| @@ -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, | |||
| }; | |||
| } | |||
| @@ -28,6 +28,10 @@ const AuthRoutes = (): RouteObject => ({ | |||
| path: getRoute(PageID.LOGOUT), | |||
| element: <Logout />, | |||
| }, | |||
| { | |||
| path: getRoute(PageID.CLEAR_CHANGE_CONTRACT), | |||
| element: <ClearContract />, | |||
| }, | |||
| ], | |||
| }); | |||
| @@ -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( | |||
| @@ -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", | |||