| @@ -31,6 +31,7 @@ export const ApiId = { | |||
| LOGIN_USERS: id++, | |||
| LOGIN_USER_CREATE: id++, | |||
| LOGIN_USER_CHANGE_PASSWORD: id++, | |||
| // FOR CUSTOM | |||
| HT_CUSTOM_CUSTOMERS: id++, | |||
| @@ -55,6 +56,10 @@ export const ResultCode = { | |||
| }; | |||
| export type ResultCode = (typeof ResultCode)[keyof typeof ResultCode]; | |||
| export interface TimestampRequest { | |||
| timestamp: string; | |||
| } | |||
| export interface APICommonResponse { | |||
| result: ResultCode; | |||
| messages: { | |||
| @@ -1,4 +1,11 @@ | |||
| import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "."; | |||
| import { | |||
| APICommonResponse, | |||
| ApiId, | |||
| HttpMethod, | |||
| TimestampRequest, | |||
| makeParam, | |||
| request, | |||
| } from "."; | |||
| import { getUrl } from "./url"; | |||
| export type LoginUser = { | |||
| @@ -46,3 +53,18 @@ export const createLoginUser = async (data: LoginUserCreateRequest) => { | |||
| }); | |||
| return res; | |||
| }; | |||
| // パスワード変更 | |||
| export type ChangePasswordRequest = { | |||
| password: string; | |||
| id?: string; // UserID | |||
| } & TimestampRequest; | |||
| export const changeLoginPassword = async (data: ChangePasswordRequest) => { | |||
| const res = await request({ | |||
| url: getUrl(ApiId.LOGIN_USER_CHANGE_PASSWORD), | |||
| method: HttpMethod.POST, | |||
| data: makeParam(data), | |||
| }); | |||
| return res; | |||
| }; | |||
| @@ -21,6 +21,7 @@ const urls = { | |||
| [A.CONTRACTS]: "contracts", | |||
| [A.LOGIN_USERS]: "users", | |||
| [A.LOGIN_USER_CREATE]: "user/create", | |||
| [A.LOGIN_USER_CHANGE_PASSWORD]: "user/change-password", | |||
| // FOR CUSTOM | |||
| [A.HT_CUSTOM_CUSTOMERS]: "custom/hello-techno/customers", | |||
| @@ -20,6 +20,7 @@ export const PageID = { | |||
| DASHBOARD_LOGIN_USER_LIST: id++, | |||
| DASHBOARD_LOGIN_USER_CREATE: id++, | |||
| DASHBOARD_LOGIN_USER_CHANGE_PASSWORD: id++, | |||
| PAGE_403: id++, | |||
| PAGE_404: id++, | |||
| @@ -22,6 +22,7 @@ type Auth = { | |||
| role: UserRole; | |||
| contractId: string | null; | |||
| userId: string | null; | |||
| name: string; | |||
| custom: CustomCode[]; | |||
| customerName: string; | |||
| @@ -41,6 +42,7 @@ export const AuthContext = createContext<Auth>({ | |||
| role: UserRole.NONE, | |||
| contractId: null, | |||
| userId: null, | |||
| name: "", | |||
| custom: [], | |||
| customerName: "", | |||
| @@ -57,6 +59,7 @@ function AuthContextProvider({ children }: Props) { | |||
| const [initialized, setInitialized] = useState(false); | |||
| const [role, setRole] = useState<UserRole>(UserRole.NONE); | |||
| const [contractId, setContractId] = useState<string | null>(null); | |||
| const [userId, setUserId] = useState<string | null>(null); | |||
| const [name, setName] = useState(""); | |||
| const [custom, setCustom] = useState<CustomCode[]>([]); | |||
| const [customerName, setCustomerName] = useState(""); | |||
| @@ -69,6 +72,7 @@ function AuthContextProvider({ children }: Props) { | |||
| apiMethod: me, | |||
| onSuccess: (res) => { | |||
| setContractId(res.data.contract_id); | |||
| setUserId(res.data.id); | |||
| setRole(res.data.role); | |||
| setName(res.data.name); | |||
| setCustom(res.data.custom ?? []); | |||
| @@ -84,6 +88,7 @@ function AuthContextProvider({ children }: Props) { | |||
| apiMethod: APILogin, | |||
| onSuccess: (res) => { | |||
| setContractId(res.data.contract_id); | |||
| setUserId(res.data.id); | |||
| setRole(res.data.role); | |||
| setName(res.data.name); | |||
| setCustom(res.data.custom ?? []); | |||
| @@ -101,6 +106,7 @@ function AuthContextProvider({ children }: Props) { | |||
| const clear = () => { | |||
| setRole(UserRole.NONE); | |||
| setContractId(null); | |||
| setUserId(null); | |||
| setName(""); | |||
| setCustom([]); | |||
| }; | |||
| @@ -178,6 +184,7 @@ function AuthContextProvider({ children }: Props) { | |||
| authenticated, | |||
| role, | |||
| contractId, | |||
| userId, | |||
| name, | |||
| custom, | |||
| @@ -3,6 +3,7 @@ import HomeIcon from "@mui/icons-material/Home"; | |||
| 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 { Collapse } from "@mui/material"; | |||
| import Box from "@mui/material/Box"; | |||
| import Divider from "@mui/material/Divider"; | |||
| @@ -17,7 +18,7 @@ import useAuth from "hooks/useAuth"; | |||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||
| import usePage from "hooks/usePage"; | |||
| import * as React from "react"; | |||
| import { getPath } from "routes/path"; | |||
| import { PathOption, getPath } from "routes/path"; | |||
| type Group = { | |||
| label: string; | |||
| @@ -31,69 +32,15 @@ type SubGroup = { | |||
| // 子要素を持たない場合は設定 | |||
| id?: PageID; | |||
| option?: PathOption; | |||
| }; | |||
| type Child = { | |||
| label: string; | |||
| id: PageID; | |||
| option?: PathOption; | |||
| }; | |||
| const categories: Group[] = [ | |||
| { | |||
| label: "管理", | |||
| children: [ | |||
| { | |||
| label: "契約", | |||
| icon: <PeopleIcon />, | |||
| children: [ | |||
| { | |||
| id: PageID.DASHBOARD_CONTRACT_LIST, | |||
| label: "一覧", | |||
| }, | |||
| { | |||
| id: PageID.DASHBOARD_CONTRACT_CREATE, | |||
| label: "作成", | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| label: "領収証発行依頼", | |||
| icon: <ArticleIcon />, | |||
| children: [ | |||
| { | |||
| id: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST_CUSTOM_HELLO_TECHNO, | |||
| label: "一覧", | |||
| }, | |||
| { | |||
| id: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE_CUSTOM_HELLO_TECHNO, | |||
| label: "新規", | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| label: "ユーザ管理", | |||
| icon: <PeopleIcon />, | |||
| children: [ | |||
| { | |||
| id: PageID.DASHBOARD_LOGIN_USER_LIST, | |||
| label: "一覧", | |||
| }, | |||
| { | |||
| id: PageID.DASHBOARD_LOGIN_USER_CREATE, | |||
| label: "作成", | |||
| }, | |||
| ], | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| label: "アカウント", | |||
| children: [ | |||
| { label: "ログアウト", icon: <SettingsIcon />, id: PageID.LOGOUT }, | |||
| ], | |||
| }, | |||
| ]; | |||
| const item = { | |||
| py: "2px", | |||
| px: 3, | |||
| @@ -112,6 +59,79 @@ const itemCategory = { | |||
| export default function Navigator(props: DrawerProps) { | |||
| const { ...other } = props; | |||
| const { userId } = useAuth(); | |||
| const categories: Group[] = [ | |||
| { | |||
| label: "管理", | |||
| children: [ | |||
| { | |||
| label: "契約", | |||
| icon: <PeopleIcon />, | |||
| children: [ | |||
| { | |||
| id: PageID.DASHBOARD_CONTRACT_LIST, | |||
| label: "一覧", | |||
| }, | |||
| { | |||
| id: PageID.DASHBOARD_CONTRACT_CREATE, | |||
| label: "作成", | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| label: "領収証発行依頼", | |||
| icon: <ArticleIcon />, | |||
| children: [ | |||
| { | |||
| id: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST_CUSTOM_HELLO_TECHNO, | |||
| label: "一覧", | |||
| }, | |||
| { | |||
| id: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE_CUSTOM_HELLO_TECHNO, | |||
| label: "新規", | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| label: "ユーザ管理", | |||
| icon: <PeopleIcon />, | |||
| children: [ | |||
| { | |||
| id: PageID.DASHBOARD_LOGIN_USER_LIST, | |||
| label: "一覧", | |||
| }, | |||
| { | |||
| id: PageID.DASHBOARD_LOGIN_USER_CREATE, | |||
| label: "作成", | |||
| }, | |||
| ], | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| label: "アカウント", | |||
| children: [ | |||
| { | |||
| label: "アカウント編集", | |||
| icon: <AccountBoxIcon />, | |||
| children: [ | |||
| { | |||
| id: PageID.DASHBOARD_LOGIN_USER_CHANGE_PASSWORD, | |||
| label: "パスワード変更", | |||
| option: { | |||
| query: { | |||
| id: userId ?? "empty", | |||
| }, | |||
| }, | |||
| }, | |||
| ], | |||
| }, | |||
| { label: "ログアウト", icon: <SettingsIcon />, id: PageID.LOGOUT }, | |||
| ], | |||
| }, | |||
| ]; | |||
| return ( | |||
| <Drawer variant="permanent" {...other}> | |||
| <List disablePadding> | |||
| @@ -155,7 +175,7 @@ function Group(group: Group) { | |||
| ); | |||
| } | |||
| function SubGroup({ icon, label, id, children }: SubGroup) { | |||
| function SubGroup({ icon, label, id, children, option }: SubGroup) { | |||
| const { pageId } = usePage(); | |||
| const { navigateWhenChanged } = useNavigateCustom(); | |||
| @@ -191,7 +211,7 @@ function SubGroup({ icon, label, id, children }: SubGroup) { | |||
| if (id !== undefined) { | |||
| const handleClick = () => { | |||
| if (id) { | |||
| const path = getPath(id); | |||
| const path = getPath(id, option); | |||
| navigateWhenChanged(path); | |||
| } | |||
| }; | |||
| @@ -217,14 +237,15 @@ function useContents(children: Child[]) { | |||
| setShouldOpen(false); | |||
| return children | |||
| .filter(({ id }) => canAccess(id)) | |||
| .map(({ label, id }, index) => { | |||
| .map(({ label, id, option }, index) => { | |||
| const selected = id === pageId; | |||
| if (selected) { | |||
| setShouldOpen(true); | |||
| } | |||
| const handleClick = () => { | |||
| const path = getPath(id); | |||
| const path = getPath(id, option); | |||
| console.log(path, id, option); | |||
| navigateWhenChanged(path); | |||
| }; | |||
| @@ -0,0 +1,132 @@ | |||
| import { yupResolver } from "@hookform/resolvers/yup"; | |||
| import { Box, Button, Card, Stack, Typography } from "@mui/material"; | |||
| import { HasChildren } from "@types"; | |||
| import { | |||
| changeLoginPassword, | |||
| createLoginUser, | |||
| getLoginUsers, | |||
| } from "api/login-user"; | |||
| import { PageID, TabID } from "codes/page"; | |||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useDashboard from "hooks/useDashBoard"; | |||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||
| import { useSnackbar } from "notistack"; | |||
| import { useEffect, useState } from "react"; | |||
| import { useForm } from "react-hook-form"; | |||
| import { useParams } from "react-router-dom"; | |||
| import { getPath } from "routes/path"; | |||
| import * as Yup from "yup"; | |||
| type AreaBoxProps = { | |||
| title: string; | |||
| } & HasChildren; | |||
| function AreaBox({ title, children }: AreaBoxProps) { | |||
| return ( | |||
| <Box sx={{ maxWidth: 500 }}> | |||
| <Typography variant="subtitle1">{title}</Typography> | |||
| {children} | |||
| </Box> | |||
| ); | |||
| } | |||
| type FormProps = { | |||
| password: string; | |||
| password_retype: string; | |||
| }; | |||
| export default function ChangePassword() { | |||
| const { setHeaderTitle, setTabs } = useDashboard( | |||
| PageID.DASHBOARD_LOGIN_USER_CHANGE_PASSWORD, | |||
| TabID.NONE | |||
| ); | |||
| const { success, error } = useSnackbarCustom(); | |||
| const { id: paramUserId } = useParams(); | |||
| const [timestamp, setTimestamp] = useState(""); | |||
| const [mode, setMode] = useState<"input" | "done">("input"); | |||
| const form = useForm<FormProps>({ | |||
| defaultValues: { | |||
| password: "", | |||
| password_retype: "", | |||
| }, | |||
| resolver: yupResolver( | |||
| Yup.object().shape({ | |||
| password: Yup.string().required("必須項目です"), | |||
| password_retype: Yup.string() | |||
| .required("必須項目です") | |||
| .test("retype", "入力が一致しません", function (value) { | |||
| return this.parent.password === value; | |||
| }), | |||
| }) | |||
| ), | |||
| }); | |||
| const { callAPI: callGetLoginUsers } = useAPICall({ | |||
| apiMethod: getLoginUsers, | |||
| onSuccess: ({ data: { records } }) => { | |||
| if (records.length === 1) { | |||
| setTimestamp(records[0].updated_at); | |||
| } | |||
| }, | |||
| }); | |||
| const { callAPI: callChangeLoginPassword } = useAPICall({ | |||
| apiMethod: changeLoginPassword, | |||
| backDrop: true, | |||
| form, | |||
| onSuccess: () => { | |||
| success("登録しました"); | |||
| setMode("done"); | |||
| }, | |||
| onFailed: () => { | |||
| error("失敗しました"); | |||
| }, | |||
| }); | |||
| const fetch = () => { | |||
| callGetLoginUsers({ | |||
| id: paramUserId, | |||
| }); | |||
| }; | |||
| const handleSubmt = (data: FormProps) => { | |||
| callChangeLoginPassword({ ...data, timestamp }); | |||
| }; | |||
| useEffect(() => { | |||
| setHeaderTitle("パスワード変更"); | |||
| setTabs(null); | |||
| fetch(); | |||
| }, []); | |||
| return ( | |||
| <> | |||
| {mode === "input" && ( | |||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmt)}> | |||
| <Box> | |||
| <Stack spacing={2}> | |||
| <AreaBox title="ログインパスワード"> | |||
| <RHFTextField name="password" type="password" /> | |||
| </AreaBox> | |||
| <AreaBox title="ログインパスワード(再入力)"> | |||
| <RHFTextField name="password_retype" type="password" /> | |||
| </AreaBox> | |||
| <Stack direction="row"> | |||
| <Button variant="contained" type="submit"> | |||
| 登録 | |||
| </Button> | |||
| </Stack> | |||
| </Stack> | |||
| </Box> | |||
| </FormProvider> | |||
| )} | |||
| {mode === "done" && <Box>変更完了しました</Box>} | |||
| </> | |||
| ); | |||
| } | |||
| @@ -102,7 +102,7 @@ export default function LoginUserCreate() { | |||
| <AreaBox title="ログインパスワード"> | |||
| <RHFTextField name="password" type="password" /> | |||
| </AreaBox> | |||
| <AreaBox title="ログインパスワード(再入力"> | |||
| <AreaBox title="ログインパスワード(再入力)"> | |||
| <RHFTextField name="password_retype" type="password" /> | |||
| </AreaBox> | |||
| <Stack direction="row"> | |||
| @@ -288,16 +288,13 @@ function Row({ data }: RowProps) { | |||
| const { navigateWhenChanged } = useNavigateCustom(); | |||
| const handleClick = () => { | |||
| // navigateWhenChanged( | |||
| // getPath( | |||
| // PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL_CUSTOM_HELLO_TECHNO, | |||
| // { | |||
| // query: { | |||
| // id: data.id, | |||
| // }, | |||
| // } | |||
| // ) | |||
| // ); | |||
| navigateWhenChanged( | |||
| getPath(PageID.DASHBOARD_LOGIN_USER_CHANGE_PASSWORD, { | |||
| query: { | |||
| id: data.id, | |||
| }, | |||
| }) | |||
| ); | |||
| }; | |||
| return ( | |||
| @@ -34,6 +34,7 @@ export const AUTH = { | |||
| ), | |||
| [P.DASHBOARD_LOGIN_USER_LIST]: setAuth("ge", R.CONTRACT_ADMIN), | |||
| [P.DASHBOARD_LOGIN_USER_CREATE]: setAuth("ge", R.CONTRACT_ADMIN), | |||
| [P.DASHBOARD_LOGIN_USER_CHANGE_PASSWORD]: setAuth("ge", R.NORMAL_ADMIN), | |||
| [P.PAGE_403]: setAuth("all"), | |||
| [P.PAGE_404]: setAuth("all"), | |||
| @@ -89,6 +89,10 @@ const DashboardRoutes = (): RouteObject => { | |||
| pageId: PageID.DASHBOARD_LOGIN_USER_CREATE, | |||
| element: <LoginUserCreate />, | |||
| }, | |||
| { | |||
| pageId: PageID.DASHBOARD_LOGIN_USER_CHANGE_PASSWORD, | |||
| element: <ChangePassword />, | |||
| }, | |||
| ]; | |||
| const children: RouteObject[] = useMemo(() => { | |||
| @@ -172,6 +176,9 @@ const LoginUserList = Loadable( | |||
| const LoginUserCreate = Loadable( | |||
| lazy(() => import("pages/dashboard/login-user/create")) | |||
| ); | |||
| const ChangePassword = Loadable( | |||
| lazy(() => import("pages/dashboard/login-user/change-password")) | |||
| ); | |||
| // その他 --------------------------------- | |||
| const Page403 = Loadable(lazy(() => import("pages/common/Page403"))); | |||
| @@ -66,6 +66,8 @@ const PATHS = { | |||
| "/dashboard/login-user/list/:page", | |||
| [makePathKey(PageID.DASHBOARD_LOGIN_USER_CREATE)]: | |||
| "/dashboard/login-user/create", | |||
| [makePathKey(PageID.DASHBOARD_LOGIN_USER_CHANGE_PASSWORD)]: | |||
| "/dashboard/login-user/change-password/:id", | |||
| // その他 | |||
| [makePathKey(PageID.PAGE_403)]: "403", | |||