| @@ -29,6 +29,9 @@ export const ApiId = { | |||
| CONTRACTS: id++, | |||
| LOGIN_USERS: id++, | |||
| LOGIN_USER_CREATE: id++, | |||
| // FOR CUSTOM | |||
| HT_CUSTOM_CUSTOMERS: id++, | |||
| HT_CUSTOM_PARKINGS: id++, | |||
| @@ -0,0 +1,48 @@ | |||
| import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "."; | |||
| import { getUrl } from "./url"; | |||
| export type LoginUser = { | |||
| id: string; | |||
| name: string; | |||
| email: string; | |||
| contract_id: string; | |||
| updated_at: string; | |||
| }; | |||
| // ログインユーザー一覧取得 ----------------------- | |||
| export type LoginUsersRequest = { | |||
| id?: string; | |||
| name?: string; | |||
| email?: string; | |||
| }; | |||
| export type LoginUsersResponse = { | |||
| data: { | |||
| records: LoginUser[]; | |||
| }; | |||
| } & APICommonResponse; | |||
| export const getLoginUsers = async (data: LoginUsersRequest) => { | |||
| const res = await request<LoginUsersResponse>({ | |||
| url: getUrl(ApiId.LOGIN_USERS), | |||
| method: HttpMethod.GET, | |||
| data: new URLSearchParams(data), | |||
| }); | |||
| return res; | |||
| }; | |||
| // ログインユーザー新規作成 | |||
| export type LoginUserCreateRequest = { | |||
| name: string; | |||
| email: string; | |||
| password: string; | |||
| }; | |||
| export const createLoginUser = async (data: LoginUserCreateRequest) => { | |||
| const res = await request({ | |||
| url: getUrl(ApiId.LOGIN_USER_CREATE), | |||
| method: HttpMethod.POST, | |||
| data: makeParam(data), | |||
| }); | |||
| return res; | |||
| }; | |||
| @@ -19,6 +19,8 @@ const urls = { | |||
| [A.RECEIPT_ISSUING_ORDERS]: "receipt-issuing-orders", | |||
| [A.CONTRACTS]: "contracts", | |||
| [A.LOGIN_USERS]: "users", | |||
| [A.LOGIN_USER_CREATE]: "user/create", | |||
| // FOR CUSTOM | |||
| [A.HT_CUSTOM_CUSTOMERS]: "custom/hello-techno/customers", | |||
| @@ -18,6 +18,9 @@ export const PageID = { | |||
| DASHBOARD_RECEIPT_ISSUING_ORDER_LIST_CUSTOM_HELLO_TECHNO: id++, | |||
| DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL_CUSTOM_HELLO_TECHNO: id++, | |||
| DASHBOARD_LOGIN_USER_LIST: id++, | |||
| DASHBOARD_LOGIN_USER_CREATE: id++, | |||
| PAGE_403: id++, | |||
| PAGE_404: id++, | |||
| } as const; | |||
| @@ -1,6 +1,7 @@ | |||
| import { ExpandLess, ExpandMore } from "@mui/icons-material"; | |||
| 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 { Collapse } from "@mui/material"; | |||
| import Box from "@mui/material/Box"; | |||
| @@ -57,7 +58,7 @@ const categories: Group[] = [ | |||
| }, | |||
| { | |||
| label: "領収証発行依頼", | |||
| icon: <PeopleIcon />, | |||
| icon: <ArticleIcon />, | |||
| children: [ | |||
| { | |||
| id: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST_CUSTOM_HELLO_TECHNO, | |||
| @@ -69,6 +70,20 @@ const categories: Group[] = [ | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| label: "ユーザ管理", | |||
| icon: <PeopleIcon />, | |||
| children: [ | |||
| { | |||
| id: PageID.DASHBOARD_LOGIN_USER_LIST, | |||
| label: "一覧", | |||
| }, | |||
| { | |||
| id: PageID.DASHBOARD_LOGIN_USER_CREATE, | |||
| label: "作成", | |||
| }, | |||
| ], | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| @@ -103,7 +118,7 @@ export default function Navigator(props: DrawerProps) { | |||
| <ListItem | |||
| sx={{ ...item, ...itemCategory, fontSize: 22, color: "#fff" }} | |||
| > | |||
| SateReceipt | |||
| EasyReceipt | |||
| </ListItem> | |||
| <ListItem sx={{ ...item, ...itemCategory }}> | |||
| <ListItemIcon> | |||
| @@ -0,0 +1,117 @@ | |||
| import { yupResolver } from "@hookform/resolvers/yup"; | |||
| import { Box, Button, Card, Stack, Typography } from "@mui/material"; | |||
| import { HasChildren } from "@types"; | |||
| import { createLoginUser } 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 } from "react"; | |||
| import { useForm } from "react-hook-form"; | |||
| 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 = { | |||
| name: string; | |||
| email: string; | |||
| password: string; | |||
| password_retype: string; | |||
| }; | |||
| export default function LoginUserCreate() { | |||
| const { setHeaderTitle, setTabs } = useDashboard( | |||
| PageID.DASHBOARD_LOGIN_USER_CREATE, | |||
| TabID.NONE | |||
| ); | |||
| const { success, error } = useSnackbarCustom(); | |||
| const { navigateWhenChanged } = useNavigateCustom(); | |||
| const form = useForm<FormProps>({ | |||
| defaultValues: { | |||
| name: "", | |||
| email: "", | |||
| password: "", | |||
| password_retype: "", | |||
| }, | |||
| resolver: yupResolver( | |||
| Yup.object().shape({ | |||
| name: Yup.string().required("必須項目です"), | |||
| email: Yup.string().required("必須項目です"), | |||
| password: Yup.string().required("必須項目です"), | |||
| password_retype: Yup.string() | |||
| .required("必須項目です") | |||
| .test("retype", "入力が一致しません", function (value) { | |||
| return this.parent.password === value; | |||
| }), | |||
| }) | |||
| ), | |||
| }); | |||
| const { callAPI } = useAPICall({ | |||
| apiMethod: createLoginUser, | |||
| backDrop: true, | |||
| form, | |||
| onSuccess: (res, sendData) => { | |||
| success("登録しました"); | |||
| navigateWhenChanged( | |||
| getPath(PageID.DASHBOARD_LOGIN_USER_LIST, { | |||
| query: { email: sendData.email }, | |||
| }) | |||
| ); | |||
| }, | |||
| onFailed: () => { | |||
| error("失敗しました"); | |||
| }, | |||
| }); | |||
| const handleSubmt = (data: FormProps) => { | |||
| callAPI({ ...data }); | |||
| }; | |||
| useEffect(() => { | |||
| setHeaderTitle("ユーザ新規作成"); | |||
| setTabs(null); | |||
| }, []); | |||
| return ( | |||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmt)}> | |||
| <Box> | |||
| <Stack spacing={2}> | |||
| <AreaBox title="Email"> | |||
| <RHFTextField name="email" type="email" /> | |||
| </AreaBox> | |||
| <AreaBox title="名前"> | |||
| <RHFTextField name="name" /> | |||
| </AreaBox> | |||
| <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> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,309 @@ | |||
| import { | |||
| Box, | |||
| Button, | |||
| Dialog, | |||
| DialogActions, | |||
| DialogContent, | |||
| DialogTitle, | |||
| Drawer, | |||
| FormControl, | |||
| Grid, | |||
| IconButton, | |||
| Stack, | |||
| Table, | |||
| TableBody, | |||
| TableCell, | |||
| TableContainer, | |||
| TablePagination, | |||
| TableRow, | |||
| TextField, | |||
| Tooltip, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { Dictionary } from "@types"; | |||
| import { | |||
| ReceiptIssuingOrderHTCustom, | |||
| getReceiptIssuingOrders, | |||
| } from "api/custom/hello-techno/receipt-issuing-order"; | |||
| import { PageID, TabID } from "codes/page"; | |||
| import { | |||
| FormProvider, | |||
| RHFCheckbox, | |||
| RHFSelect, | |||
| RHFTextField, | |||
| } from "components/hook-form"; | |||
| import { TableHeadCustom } from "components/table"; | |||
| import { SearchConditionContextProvider } from "contexts/SearchConditionContext"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useBackDrop from "hooks/useBackDrop"; | |||
| import useDashboard from "hooks/useDashBoard"; | |||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||
| import useSearchConditionContext from "hooks/useSearchConditionContext"; | |||
| import useTable, { UseTableReturn } from "hooks/useTable"; | |||
| import { useEffect, useMemo, useState } from "react"; | |||
| import { useForm } from "react-hook-form"; | |||
| import { getPath } from "routes/path"; | |||
| import AccountCircleIcon from "@mui/icons-material/AccountCircle"; | |||
| import { RHFSelectMuiliple } from "components/hook-form/RHFSelect"; | |||
| import useAuth from "hooks/useAuth"; | |||
| import RHFDatePicker from "components/hook-form/RHFDatePicker"; | |||
| import { dateParse, formatDateStr } from "utils/datetime"; | |||
| import { Clear } from "@mui/icons-material"; | |||
| import { LoginUser, getLoginUsers } from "api/login-user"; | |||
| export default function LoginUserList() { | |||
| const { setHeaderTitle, setTabs } = useDashboard( | |||
| PageID.DASHBOARD_LOGIN_USER_LIST, | |||
| TabID.NONE | |||
| ); | |||
| useEffect(() => { | |||
| setHeaderTitle("ユーザ一覧"); | |||
| setTabs(null); | |||
| }, []); | |||
| return ( | |||
| <SearchConditionContextProvider> | |||
| <Page /> | |||
| </SearchConditionContextProvider> | |||
| ); | |||
| } | |||
| function Page() { | |||
| const table = useTable<LoginUser>(); | |||
| return ( | |||
| <Box> | |||
| <SearchBox table={table} /> | |||
| <TableBox table={table} /> | |||
| </Box> | |||
| ); | |||
| } | |||
| type FormProps = { | |||
| name: string; | |||
| email: string; | |||
| }; | |||
| type CommonProps = { | |||
| table: UseTableReturn<LoginUser>; | |||
| }; | |||
| function SearchBox({ table }: CommonProps) { | |||
| const { | |||
| condition, | |||
| initialized, | |||
| get, | |||
| addCondition: add, | |||
| } = useSearchConditionContext(); | |||
| const { setShowBackDrop } = useBackDrop(); | |||
| const { name: myName } = useAuth(); | |||
| const [openDateDialog, setOpenDateDialog] = useState(false); | |||
| const form = useForm<FormProps>({ | |||
| defaultValues: { | |||
| name: "", | |||
| email: "", | |||
| }, | |||
| }); | |||
| const { | |||
| callAPI: callGetLoginUsers, | |||
| makeSendData, | |||
| sending, | |||
| } = useAPICall({ | |||
| apiMethod: getLoginUsers, | |||
| form, | |||
| onSuccess: (res) => { | |||
| table.setRowData(res.data.records); | |||
| }, | |||
| }); | |||
| const handleSubmit = async (data: FormProps) => { | |||
| addCondition(data); | |||
| }; | |||
| const addCondition = (data: FormProps) => { | |||
| add({ | |||
| ...data, | |||
| }); | |||
| }; | |||
| const handleBlur = () => { | |||
| addCondition(form.getValues()); | |||
| }; | |||
| const fetch = async (data: Dictionary) => { | |||
| const sendData = makeSendData({ | |||
| ...data, | |||
| }); | |||
| callGetLoginUsers(sendData); | |||
| }; | |||
| // 初期値設定 | |||
| useEffect(() => { | |||
| if (initialized) { | |||
| form.setValue("name", get("name")); | |||
| form.setValue("email", get("email")); | |||
| } | |||
| }, [initialized, condition]); | |||
| // Fetchアクション | |||
| useEffect(() => { | |||
| if (initialized) { | |||
| fetch(condition); | |||
| } | |||
| }, [condition, initialized]); | |||
| // バックドロップ | |||
| useEffect(() => { | |||
| setShowBackDrop(sending); | |||
| }, [sending]); | |||
| return ( | |||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||
| <Box sx={{ p: 1, m: 1 }}> | |||
| <Grid container spacing={2} mt={1}> | |||
| <Grid item xs={3} lg={2}> | |||
| <Stack> | |||
| <Typography>名前</Typography> | |||
| <RHFTextField name="name" onBlur={handleBlur} /> | |||
| </Stack> | |||
| </Grid> | |||
| <Grid item xs={3} lg={2}> | |||
| <Stack> | |||
| <Typography>Email</Typography> | |||
| <RHFTextField name="email" onBlur={handleBlur} /> | |||
| </Stack> | |||
| </Grid> | |||
| </Grid> | |||
| </Box> | |||
| <Dialog | |||
| open={openDateDialog} | |||
| onClose={() => { | |||
| setOpenDateDialog(false); | |||
| }} | |||
| > | |||
| <DialogTitle>受付時刻検索条件</DialogTitle> | |||
| <DialogContent> | |||
| <Stack direction="row" spacing={2}> | |||
| <Box> | |||
| <Typography>From</Typography> | |||
| <RHFDatePicker name="order_date_from" /> | |||
| </Box> | |||
| <Box> | |||
| <Typography> </Typography> | |||
| <Typography>~</Typography> | |||
| </Box> | |||
| <Box> | |||
| <Typography>To</Typography> | |||
| <RHFDatePicker name="order_date_to" /> | |||
| </Box> | |||
| </Stack> | |||
| </DialogContent> | |||
| <DialogActions> | |||
| <Button | |||
| onClick={() => { | |||
| setOpenDateDialog(false); | |||
| }} | |||
| > | |||
| 決定 | |||
| </Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| </FormProvider> | |||
| ); | |||
| } | |||
| function TableBox({ table }: CommonProps) { | |||
| const TABLE_HEAD = [ | |||
| { id: "name", label: "名前", align: "left" }, | |||
| { id: "email", label: "Email", align: "left" }, | |||
| ]; | |||
| const { | |||
| order, | |||
| page, | |||
| sort, | |||
| rowsPerPage, | |||
| fetched, | |||
| fillteredRow, | |||
| isNotFound, | |||
| dataLength, | |||
| // | |||
| onSort, | |||
| onChangePage, | |||
| onChangeRowsPerPage, | |||
| // | |||
| setRowData, | |||
| // | |||
| ROWS_PER_PAGES, | |||
| } = table; | |||
| return ( | |||
| <> | |||
| <TableContainer | |||
| sx={{ | |||
| // minWidth: 800, | |||
| position: "relative", | |||
| }} | |||
| > | |||
| <Table size="small"> | |||
| <TableHeadCustom | |||
| order={order} | |||
| orderBy={sort} | |||
| headLabel={TABLE_HEAD} | |||
| rowCount={1} | |||
| numSelected={0} | |||
| onSort={onSort} | |||
| /> | |||
| <TableBody> | |||
| {fillteredRow.map((row, index) => ( | |||
| <Row data={row} key={index} /> | |||
| ))} | |||
| </TableBody> | |||
| </Table> | |||
| </TableContainer> | |||
| <Box sx={{ position: "relative" }}> | |||
| <TablePagination | |||
| rowsPerPageOptions={ROWS_PER_PAGES} | |||
| component="div" | |||
| count={dataLength} | |||
| rowsPerPage={rowsPerPage} | |||
| page={page} | |||
| onPageChange={onChangePage} | |||
| onRowsPerPageChange={onChangeRowsPerPage} | |||
| /> | |||
| </Box> | |||
| </> | |||
| ); | |||
| } | |||
| type RowProps = { | |||
| data: LoginUser; | |||
| }; | |||
| function Row({ data }: RowProps) { | |||
| const { navigateWhenChanged } = useNavigateCustom(); | |||
| const handleClick = () => { | |||
| // navigateWhenChanged( | |||
| // getPath( | |||
| // PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL_CUSTOM_HELLO_TECHNO, | |||
| // { | |||
| // query: { | |||
| // id: data.id, | |||
| // }, | |||
| // } | |||
| // ) | |||
| // ); | |||
| }; | |||
| return ( | |||
| <TableRow hover sx={{ cursor: "pointer" }} onClick={handleClick}> | |||
| <TableCell>{data.name}</TableCell> | |||
| <TableCell>{data.email}</TableCell> | |||
| </TableRow> | |||
| ); | |||
| } | |||
| @@ -32,6 +32,8 @@ export const AUTH = { | |||
| R.NORMAL_ADMIN, | |||
| [C.HELLO_TECHNO] | |||
| ), | |||
| [P.DASHBOARD_LOGIN_USER_LIST]: setAuth("ge", R.CONTRACT_ADMIN), | |||
| [P.DASHBOARD_LOGIN_USER_CREATE]: setAuth("ge", R.CONTRACT_ADMIN), | |||
| [P.PAGE_403]: setAuth("all"), | |||
| [P.PAGE_404]: setAuth("all"), | |||
| @@ -81,6 +81,14 @@ const DashboardRoutes = (): RouteObject => { | |||
| pageId: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL_CUSTOM_HELLO_TECHNO, | |||
| element: <ReceiptIssuingOrderDetailHelloTechno />, | |||
| }, | |||
| { | |||
| pageId: PageID.DASHBOARD_LOGIN_USER_LIST, | |||
| element: <LoginUserList />, | |||
| }, | |||
| { | |||
| pageId: PageID.DASHBOARD_LOGIN_USER_CREATE, | |||
| element: <LoginUserCreate />, | |||
| }, | |||
| ]; | |||
| const children: RouteObject[] = useMemo(() => { | |||
| @@ -157,6 +165,14 @@ const ReceiptIssuingOrderDetailHelloTechno = Loadable( | |||
| ) | |||
| ); | |||
| // ログインユーザー管理 | |||
| const LoginUserList = Loadable( | |||
| lazy(() => import("pages/dashboard/login-user/list")) | |||
| ); | |||
| const LoginUserCreate = Loadable( | |||
| lazy(() => import("pages/dashboard/login-user/create")) | |||
| ); | |||
| // その他 --------------------------------- | |||
| const Page403 = Loadable(lazy(() => import("pages/common/Page403"))); | |||
| const Page404 = Loadable(lazy(() => import("pages/common/Page404"))); | |||
| @@ -61,6 +61,12 @@ const PATHS = { | |||
| PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL_CUSTOM_HELLO_TECHNO | |||
| )]: "/dashboard/receipt-issusing-order/detail/:id", | |||
| // ログインユーザ管理 | |||
| [makePathKey(PageID.DASHBOARD_LOGIN_USER_LIST)]: | |||
| "/dashboard/login-user/list/:page", | |||
| [makePathKey(PageID.DASHBOARD_LOGIN_USER_CREATE)]: | |||
| "/dashboard/login-user/create", | |||
| // その他 | |||
| [makePathKey(PageID.PAGE_403)]: "403", | |||
| [makePathKey(PageID.PAGE_404)]: "404", | |||