| @@ -51,6 +51,8 @@ export const ApiId = { | |||||
| QRサービス券駐車場グループ新規登録: id++, | QRサービス券駐車場グループ新規登録: id++, | ||||
| QRサービス券駐車場グループ駐車場追加登録: id++, | QRサービス券駐車場グループ駐車場追加登録: id++, | ||||
| QRサービス券駐車場グループ駐車場削除登録: id++, | QRサービス券駐車場グループ駐車場削除登録: id++, | ||||
| QRサービス券取得用トークン取得: id++, | |||||
| QRサービス券取得用トークンリフレッシュ: id++, | |||||
| QRサービス券取得: id++, | QRサービス券取得: id++, | ||||
| } as const; | } as const; | ||||
| export type ApiId = (typeof ApiId)[keyof typeof ApiId]; | export type ApiId = (typeof ApiId)[keyof typeof ApiId]; | ||||
| @@ -1,7 +1,7 @@ | |||||
| import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "api"; | import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "api"; | ||||
| import { getUrl } from "./url"; | import { getUrl } from "./url"; | ||||
| export type 運営会社ログインユーザ = { | |||||
| export type 顧客ログインユーザ = { | |||||
| id: string; | id: string; | ||||
| name: string; | name: string; | ||||
| email: string; | email: string; | ||||
| @@ -9,6 +9,14 @@ export type 運営会社ログインユーザ = { | |||||
| customer_name: string; | customer_name: string; | ||||
| }; | }; | ||||
| export type 店舗ログインユーザ = { | |||||
| id: string; | |||||
| name: string; | |||||
| email: string; | |||||
| shop_id: string; | |||||
| shop_name: string; | |||||
| }; | |||||
| // -------顧客ログインユーザ新規登録--------------- | // -------顧客ログインユーザ新規登録--------------- | ||||
| export type 顧客ログインユーザ新規登録Request = { | export type 顧客ログインユーザ新規登録Request = { | ||||
| name: string; | name: string; | ||||
| @@ -62,7 +70,7 @@ export type 顧客ログインユーザ一覧取得Request = { | |||||
| }; | }; | ||||
| export type 顧客ログインユーザ一覧取得Response = { | export type 顧客ログインユーザ一覧取得Response = { | ||||
| data: { | data: { | ||||
| list: 運営会社ログインユーザ[]; | |||||
| list: 顧客ログインユーザ[]; | |||||
| }; | }; | ||||
| } & APICommonResponse; | } & APICommonResponse; | ||||
| export const 顧客ログインユーザ一覧取得 = async ( | export const 顧客ログインユーザ一覧取得 = async ( | ||||
| @@ -75,3 +83,24 @@ export const 顧客ログインユーザ一覧取得 = async ( | |||||
| }); | }); | ||||
| return res; | return res; | ||||
| }; | }; | ||||
| // -------店舗ログインユーザ一覧取得--------------- | |||||
| export type 店舗ログインユーザ一覧取得Request = { | |||||
| email?: string; | |||||
| name?: string; | |||||
| }; | |||||
| export type 店舗ログインユーザ一覧取得Response = { | |||||
| data: { | |||||
| list: 店舗ログインユーザ[]; | |||||
| }; | |||||
| } & APICommonResponse; | |||||
| export const 店舗ログインユーザ一覧取得 = async ( | |||||
| param: 店舗ログインユーザ一覧取得Request | |||||
| ) => { | |||||
| const res = await request<店舗ログインユーザ一覧取得Response>({ | |||||
| url: getUrl(ApiId.店舗ログインユーザ一覧取得), | |||||
| method: HttpMethod.GET, | |||||
| data: new URLSearchParams(param), | |||||
| }); | |||||
| return res; | |||||
| }; | |||||
| @@ -102,3 +102,36 @@ export const QRサービス券駐車場グループ駐車場削除登録 = async | |||||
| }); | }); | ||||
| return res; | return res; | ||||
| }; | }; | ||||
| // -------QRサービス券取得用トークン取得--------------- | |||||
| export type QRサービス券取得用トークン取得Request = {}; | |||||
| export type QRサービス券取得用トークン取得Response = { | |||||
| data: { | |||||
| token: string; | |||||
| }; | |||||
| } & APICommonResponse; | |||||
| export const QRサービス券取得用トークン取得 = async ( | |||||
| param: QRサービス券取得用トークン取得Request | |||||
| ) => { | |||||
| const res = await request<QRサービス券取得用トークン取得Response>({ | |||||
| url: getUrl(ApiId.QRサービス券取得用トークン取得), | |||||
| method: HttpMethod.GET, | |||||
| data: makeParam(param), | |||||
| }); | |||||
| return res; | |||||
| }; | |||||
| // -------QRサービス券取得用トークンリフレッシュ--------------- | |||||
| export type QRサービス券取得用トークンリフレッシュRequest = {}; | |||||
| export type QRサービス券取得用トークンリフレッシュResponse = | |||||
| {} & APICommonResponse; | |||||
| export const QRサービス券取得用トークンリフレッシュ = async ( | |||||
| param: QRサービス券取得用トークンリフレッシュRequest | |||||
| ) => { | |||||
| const res = await request<QRサービス券取得用トークンリフレッシュResponse>({ | |||||
| url: getUrl(ApiId.QRサービス券取得用トークンリフレッシュ), | |||||
| method: HttpMethod.POST, | |||||
| data: makeParam(param), | |||||
| }); | |||||
| return res; | |||||
| }; | |||||
| @@ -49,6 +49,9 @@ const urls = { | |||||
| "qr-service/parking-group/parking/add", | "qr-service/parking-group/parking/add", | ||||
| [A.QRサービス券駐車場グループ駐車場削除登録]: | [A.QRサービス券駐車場グループ駐車場削除登録]: | ||||
| "qr-service/parking-group/parking/remove", | "qr-service/parking-group/parking/remove", | ||||
| [A.QRサービス券取得用トークン取得]: "qr-service/acquisition/token", | |||||
| [A.QRサービス券取得用トークンリフレッシュ]: | |||||
| "qr-service/acquisition/token/refresh", | |||||
| [A.QRサービス券取得]: "qr-service/get-ticket", | [A.QRサービス券取得]: "qr-service/get-ticket", | ||||
| }; | }; | ||||
| @@ -1,9 +1,9 @@ | |||||
| import { PageID as P } from "pages"; | import { PageID as P } from "pages"; | ||||
| import { UserRole } from "./UserRole"; | import { UserRole } from "./UserRole"; | ||||
| const 共通ルート = [P.LOGIN, P.LOGOUT]; | |||||
| const 認証後共通ルート = [P.DASHBOARD_ENPTY, P.DASHBOARD_OVERVIEW]; | |||||
| const 管理者顧客共通ルート = [P.成り代わり終了]; | |||||
| const 共通ルート: P[] = [P.LOGIN, P.LOGOUT, P.成り代わり終了]; | |||||
| const 認証後共通ルート: P[] = [P.DASHBOARD_ENPTY, P.DASHBOARD_OVERVIEW]; | |||||
| const 管理者顧客共通ルート: P[] = []; | |||||
| const 認可別許可ルート: { | const 認可別許可ルート: { | ||||
| [route: string]: P[]; | [route: string]: P[]; | ||||
| @@ -176,7 +176,6 @@ function AuthContextProvider({ children }: Props) { | |||||
| const cacheClear = () => { | const cacheClear = () => { | ||||
| 駐車場マスタストア.clear(); | 駐車場マスタストア.clear(); | ||||
| 駐車場マスタストア.clear(); | |||||
| }; | }; | ||||
| const login = async (email: string, password: string) => { | const login = async (email: string, password: string) => { | ||||
| @@ -193,6 +192,7 @@ function AuthContextProvider({ children }: Props) { | |||||
| user_id, | user_id, | ||||
| }); | }); | ||||
| navigateWhenChanged(getPath(PageID.DASHBOARD_OVERVIEW)); | navigateWhenChanged(getPath(PageID.DASHBOARD_OVERVIEW)); | ||||
| cacheClear(); | |||||
| }; | }; | ||||
| const switchShopRole = async (user_id: string) => { | const switchShopRole = async (user_id: string) => { | ||||
| await call店舗成り代わり開始({ user_id }); | await call店舗成り代わり開始({ user_id }); | ||||
| @@ -20,8 +20,8 @@ import 駐車場マスタストア from "storage/cache/駐車場マスタ"; | |||||
| type Context = { | type Context = { | ||||
| shop: 店舗 | null; | shop: 店舗 | null; | ||||
| certificationSetting: QRサービス券認証設定[]; | |||||
| acquisitionSetting: QRサービス券取得設定 | null; | |||||
| certificationSetting: QRサービス券認証設定[] | undefined; | |||||
| acquisitionSetting: QRサービス券取得設定 | null | undefined; | |||||
| parkings: 駐車場マスタ[]; | parkings: 駐車場マスタ[]; | ||||
| fetch: () => Promise<void>; | fetch: () => Promise<void>; | ||||
| moveToMain: VoidFunction; | moveToMain: VoidFunction; | ||||
| @@ -43,10 +43,11 @@ function 店舗詳細ContextProvider({ children }: Props) { | |||||
| const [shop, setShop] = useState<店舗 | null>(null); | const [shop, setShop] = useState<店舗 | null>(null); | ||||
| const [parkings, setParkings] = useState<駐車場マスタ[]>([]); | const [parkings, setParkings] = useState<駐車場マスタ[]>([]); | ||||
| const [certificationSetting, setCertificationSetting] = useState< | const [certificationSetting, setCertificationSetting] = useState< | ||||
| QRサービス券認証設定[] | |||||
| >([]); | |||||
| const [acquisitionSetting, setAcquisitionSetting] = | |||||
| useState<QRサービス券取得設定 | null>(null); | |||||
| QRサービス券認証設定[] | undefined | |||||
| >(undefined); | |||||
| const [acquisitionSetting, setAcquisitionSetting] = useState< | |||||
| QRサービス券取得設定 | null | undefined | |||||
| >(undefined); | |||||
| const { success, error } = useSnackbarCustom(); | const { success, error } = useSnackbarCustom(); | ||||
| const { navigateWhenChanged } = useNavigateCustom(); | const { navigateWhenChanged } = useNavigateCustom(); | ||||
| @@ -90,20 +90,30 @@ export default function Navigator(props: DrawerProps) { | |||||
| icon: <ArticleIcon />, | icon: <ArticleIcon />, | ||||
| id: PageID.ログインユーザ_顧客新規登録, | id: PageID.ログインユーザ_顧客新規登録, | ||||
| }, | }, | ||||
| { | |||||
| label: "店舗一覧", | |||||
| icon: <ArticleIcon />, | |||||
| id: PageID.ログインユーザ_店舗一覧, | |||||
| }, | |||||
| { | |||||
| label: "店舗新規登録", | |||||
| icon: <ArticleIcon />, | |||||
| id: PageID.ログインユーザ_店舗新規登録, | |||||
| }, | |||||
| ], | ], | ||||
| }, | }, | ||||
| { | { | ||||
| label: "店舗管理", | label: "店舗管理", | ||||
| children: [ | children: [ | ||||
| { | { | ||||
| label: "店舗新規登録", | |||||
| label: "店舗一覧", | |||||
| icon: <ArticleIcon />, | icon: <ArticleIcon />, | ||||
| id: PageID.店舗新規登録, | |||||
| id: PageID.店舗一覧, | |||||
| }, | }, | ||||
| { | { | ||||
| label: "店舗一覧", | |||||
| label: "店舗新規登録", | |||||
| icon: <ArticleIcon />, | icon: <ArticleIcon />, | ||||
| id: PageID.店舗一覧, | |||||
| id: PageID.店舗新規登録, | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -129,6 +139,11 @@ export default function Navigator(props: DrawerProps) { | |||||
| { | { | ||||
| label: "QRサービス券", | label: "QRサービス券", | ||||
| children: [ | children: [ | ||||
| { | |||||
| label: "サービス券発行", | |||||
| icon: <ArticleIcon />, | |||||
| id: PageID.サービス券発行用QRコード, | |||||
| }, | |||||
| { | { | ||||
| label: "利用履歴", | label: "利用履歴", | ||||
| icon: <ArticleIcon />, | icon: <ArticleIcon />, | ||||
| @@ -0,0 +1,85 @@ | |||||
| import { Box, Grid, Stack, Typography } from "@mui/material"; | |||||
| import { Dictionary } from "@types"; | |||||
| import { 店舗ログインユーザ, 店舗ログインユーザ一覧取得 } from "api/login-user"; | |||||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||||
| import useAPICall from "hooks/useAPICall"; | |||||
| import useSearchConditionContext from "hooks/useSearchConditionContext"; | |||||
| import { UseTableReturn } from "hooks/useTable"; | |||||
| import { isEqual } from "lodash"; | |||||
| import { useEffect, useState } from "react"; | |||||
| import { useForm } from "react-hook-form"; | |||||
| type FormProps = { | |||||
| name: string; | |||||
| email: string; | |||||
| }; | |||||
| type CommonProps = { | |||||
| table: UseTableReturn<店舗ログインユーザ>; | |||||
| }; | |||||
| export default function SearchBox({ table }: CommonProps) { | |||||
| const [lastSendSearchCondition, setLastSendSearchCondition] = | |||||
| useState<object>({ default: false }); | |||||
| const { condition, initialized, get, addCondition } = | |||||
| useSearchConditionContext(); | |||||
| const form = useForm<FormProps>({ | |||||
| defaultValues: { | |||||
| name: "", | |||||
| email: "", | |||||
| }, | |||||
| }); | |||||
| const { callAPI: call店舗ログインユーザ一覧取得, makeSendData } = useAPICall({ | |||||
| apiMethod: 店舗ログインユーザ一覧取得, | |||||
| form, | |||||
| backDrop: true, | |||||
| onSuccess: ({ data }) => { | |||||
| table.setRowData(data.list); | |||||
| }, | |||||
| }); | |||||
| const handleBlur = () => { | |||||
| addCondition(form.getValues()); | |||||
| }; | |||||
| const handleSubmit = async (data: FormProps) => { | |||||
| addCondition(data); | |||||
| }; | |||||
| const fetch = async (data: Dictionary) => { | |||||
| const sendData = makeSendData(data); | |||||
| if (!isEqual(sendData, lastSendSearchCondition)) { | |||||
| setLastSendSearchCondition(sendData); | |||||
| call店舗ログインユーザ一覧取得(sendData); | |||||
| } | |||||
| }; | |||||
| // 初期値設定 | |||||
| useEffect(() => { | |||||
| if (initialized) { | |||||
| fetch(condition); | |||||
| form.setValue("name", get("name")); | |||||
| form.setValue("email", get("email")); | |||||
| } | |||||
| }, [condition, initialized]); | |||||
| return ( | |||||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||||
| <Box sx={{ p: 1, m: 1 }}> | |||||
| <Grid container spacing={2} mt={-1}> | |||||
| <Grid item xs={12} lg={6}> | |||||
| <Stack> | |||||
| <Typography>名前</Typography> | |||||
| <RHFTextField name="name" onBlur={handleBlur} /> | |||||
| </Stack> | |||||
| </Grid> | |||||
| <Grid item xs={12} lg={6}> | |||||
| <Stack> | |||||
| <Typography>Email</Typography> | |||||
| <RHFTextField name="email" onBlur={handleBlur} /> | |||||
| </Stack> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Box> | |||||
| </FormProvider> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,100 @@ | |||||
| import { | |||||
| Box, | |||||
| Table, | |||||
| TableBody, | |||||
| TableCell, | |||||
| TableContainer, | |||||
| TablePagination, | |||||
| TableRow, | |||||
| } from "@mui/material"; | |||||
| import { 店舗ログインユーザ } from "api/login-user"; | |||||
| import TableHeadCustom, { | |||||
| HeadLabelProps, | |||||
| } from "components/table/TableHeadCustom"; | |||||
| import useAuth from "hooks/useAuth"; | |||||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||||
| import { UseTableReturn } from "hooks/useTable"; | |||||
| type CommonProps = { | |||||
| table: UseTableReturn<店舗ログインユーザ>; | |||||
| }; | |||||
| export default function TableBox({ table }: CommonProps) { | |||||
| const TABLE_HEAD: HeadLabelProps[] = [ | |||||
| { id: "name", label: "名前", align: "left", needSort: false }, | |||||
| { id: "email", label: "Email", align: "left", needSort: false }, | |||||
| { id: "shop_name", label: "店舗名", align: "left", needSort: false }, | |||||
| ]; | |||||
| const { | |||||
| order, | |||||
| page, | |||||
| sort, | |||||
| rowsPerPage, | |||||
| fetched, | |||||
| fillteredRow, | |||||
| isNotFound, | |||||
| dataLength, | |||||
| // | |||||
| onSort, | |||||
| onChangePage, | |||||
| onChangeRowsPerPage, | |||||
| // | |||||
| setRowData, | |||||
| // | |||||
| ROWS_PER_PAGES, | |||||
| } = table; | |||||
| return ( | |||||
| <> | |||||
| <TableContainer> | |||||
| <Table sx={{ minWidth: 800 }} 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: 店舗ログインユーザ; | |||||
| }; | |||||
| function Row({ data }: RowProps) { | |||||
| const { switchShopRole } = useAuth(); | |||||
| const { info } = useSnackbarCustom(); | |||||
| const handleClick = () => { | |||||
| switchShopRole(data.id); | |||||
| info("成り代わり開始しました"); | |||||
| }; | |||||
| return ( | |||||
| <TableRow hover sx={{ cursor: "pointer" }} onClick={handleClick}> | |||||
| <TableCell>{data.name}</TableCell> | |||||
| <TableCell>{data.email}</TableCell> | |||||
| <TableCell>{data.shop_name}</TableCell> | |||||
| </TableRow> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,38 @@ | |||||
| import { Box } from "@mui/material"; | |||||
| import { 店舗ログインユーザ } from "api/login-user"; | |||||
| import { SearchConditionContextProvider } from "contexts/SearchConditionContext"; | |||||
| import useDashboard from "hooks/useDashBoard"; | |||||
| import useTable from "hooks/useTable"; | |||||
| import { PageID, TabID } from "pages"; | |||||
| import { useEffect } from "react"; | |||||
| import SearchBox from "./SearchBox"; | |||||
| import TableBox from "./TableBox"; | |||||
| export default function Main() { | |||||
| const { setHeaderTitle, setTabs } = useDashboard( | |||||
| PageID.ログインユーザ_店舗一覧, | |||||
| TabID.NONE | |||||
| ); | |||||
| useEffect(() => { | |||||
| setHeaderTitle("ログインユーザ一覧"); | |||||
| setTabs(null); | |||||
| }, []); | |||||
| return ( | |||||
| <SearchConditionContextProvider> | |||||
| <Page /> | |||||
| </SearchConditionContextProvider> | |||||
| ); | |||||
| } | |||||
| function Page() { | |||||
| const table = useTable<店舗ログインユーザ>(); | |||||
| return ( | |||||
| <Box> | |||||
| <SearchBox table={table} /> | |||||
| <TableBox table={table} /> | |||||
| </Box> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,149 @@ | |||||
| import { yupResolver } from "@hookform/resolvers/yup"; | |||||
| import { Box, Button, Stack, Typography } from "@mui/material"; | |||||
| import { 店舗ログインユーザ新規登録 } from "api/login-user"; | |||||
| import { 店舗, 店舗一覧取得 } from "api/shop"; | |||||
| import { | |||||
| FormProvider, | |||||
| RHFAutoComplete, | |||||
| RHFTextField, | |||||
| } from "components/hook-form"; | |||||
| import { | |||||
| AutoCompleteOption, | |||||
| AutoCompleteOptionType, | |||||
| getValue, | |||||
| } from "components/hook-form/RHFAutoComplete"; | |||||
| import StackRow from "components/stack/StackRow"; | |||||
| import useAPICall from "hooks/useAPICall"; | |||||
| import useDashboard from "hooks/useDashBoard"; | |||||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||||
| import { PageID, TabID } from "pages"; | |||||
| import { useEffect, useMemo, useState } from "react"; | |||||
| import { useForm } from "react-hook-form"; | |||||
| import { getPath } from "routes/path"; | |||||
| import { object, string } from "yup"; | |||||
| type FormProps = { | |||||
| name: string; | |||||
| email: string; | |||||
| password: string; | |||||
| password_retype: string; | |||||
| shop_id: AutoCompleteOptionType; | |||||
| }; | |||||
| export default function Main() { | |||||
| const { setHeaderTitle, setTabs } = useDashboard( | |||||
| PageID.ログインユーザ_店舗新規登録, | |||||
| TabID.NONE | |||||
| ); | |||||
| const { success, error } = useSnackbarCustom(); | |||||
| const { navigateWhenChanged } = useNavigateCustom(); | |||||
| const [shops, setShops] = useState<店舗[]>([]); | |||||
| const shopOptions: AutoCompleteOption[] = useMemo(() => { | |||||
| return shops.map((data) => { | |||||
| return { | |||||
| label: data.name, | |||||
| value: data.shop_id, | |||||
| }; | |||||
| }); | |||||
| }, [shops]); | |||||
| const form = useForm<FormProps>({ | |||||
| defaultValues: { | |||||
| name: "", | |||||
| email: "", | |||||
| password: "", | |||||
| password_retype: "", | |||||
| shop_id: "", | |||||
| }, | |||||
| resolver: yupResolver( | |||||
| object().shape({ | |||||
| name: string().required("必須項目です"), | |||||
| email: string().required("必須項目です"), | |||||
| password: string().required("必須項目です"), | |||||
| password_retype: string() | |||||
| .required("必須項目です") | |||||
| .test("retype", "入力が一致しません", function (value) { | |||||
| return this.parent.password === value; | |||||
| }), | |||||
| shop_id: object().required("必須項目です"), | |||||
| }) | |||||
| ), | |||||
| }); | |||||
| const { callAPI: call店舗ログインユーザ新規登録 } = useAPICall({ | |||||
| apiMethod: 店舗ログインユーザ新規登録, | |||||
| form, | |||||
| backDrop: true, | |||||
| onSuccess: ({ data }, sendData) => { | |||||
| success("登録しました"); | |||||
| navigateWhenChanged( | |||||
| getPath(PageID.ログインユーザ_店舗一覧), | |||||
| new URLSearchParams({ email: sendData.email }) | |||||
| ); | |||||
| }, | |||||
| onFailed: () => { | |||||
| error("失敗しました"); | |||||
| }, | |||||
| }); | |||||
| const { callAPI: call店舗一覧取得 } = useAPICall({ | |||||
| apiMethod: 店舗一覧取得, | |||||
| onSuccess: ({ data }) => { | |||||
| setShops(data.list); | |||||
| }, | |||||
| }); | |||||
| const handleSubmit = (data: FormProps) => { | |||||
| call店舗ログインユーザ新規登録({ | |||||
| ...data, | |||||
| shop_id: getValue(data.shop_id), | |||||
| }); | |||||
| }; | |||||
| useEffect(() => { | |||||
| setHeaderTitle("ログインユーザ登録"); | |||||
| setTabs(null); | |||||
| call店舗一覧取得({}); | |||||
| }, []); | |||||
| return ( | |||||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||||
| <Box> | |||||
| <Stack> | |||||
| <Box> | |||||
| <Typography>名称</Typography> | |||||
| <RHFTextField name="name" /> | |||||
| </Box> | |||||
| <Box> | |||||
| <Typography>Email</Typography> | |||||
| <RHFTextField type="email" name="email" /> | |||||
| </Box> | |||||
| <Box> | |||||
| <Typography>パスワード</Typography> | |||||
| <RHFTextField type="password" name="password" /> | |||||
| </Box> | |||||
| <Box> | |||||
| <Typography>パスワード(再入力)</Typography> | |||||
| <RHFTextField type="password" name="password_retype" /> | |||||
| </Box> | |||||
| <Box> | |||||
| <Typography>店舗</Typography> | |||||
| <RHFAutoComplete | |||||
| name="shop_id" | |||||
| options={shopOptions} | |||||
| size="small" | |||||
| /> | |||||
| </Box> | |||||
| <StackRow> | |||||
| <Button type="submit">登録</Button> | |||||
| </StackRow> | |||||
| </Stack> | |||||
| </Box> | |||||
| </FormProvider> | |||||
| ); | |||||
| } | |||||
| @@ -1,9 +1,6 @@ | |||||
| import { Box, Grid, Stack, Typography } from "@mui/material"; | import { Box, Grid, Stack, Typography } from "@mui/material"; | ||||
| import { Dictionary } from "@types"; | import { Dictionary } from "@types"; | ||||
| import { | |||||
| 運営会社ログインユーザ, | |||||
| 顧客ログインユーザ一覧取得, | |||||
| } from "api/login-user"; | |||||
| import { 顧客ログインユーザ, 顧客ログインユーザ一覧取得 } from "api/login-user"; | |||||
| import { FormProvider, RHFTextField } from "components/hook-form"; | import { FormProvider, RHFTextField } from "components/hook-form"; | ||||
| import useAPICall from "hooks/useAPICall"; | import useAPICall from "hooks/useAPICall"; | ||||
| import useSearchConditionContext from "hooks/useSearchConditionContext"; | import useSearchConditionContext from "hooks/useSearchConditionContext"; | ||||
| @@ -18,7 +15,7 @@ type FormProps = { | |||||
| }; | }; | ||||
| type CommonProps = { | type CommonProps = { | ||||
| table: UseTableReturn<運営会社ログインユーザ>; | |||||
| table: UseTableReturn<顧客ログインユーザ>; | |||||
| }; | }; | ||||
| export default function SearchBox({ table }: CommonProps) { | export default function SearchBox({ table }: CommonProps) { | ||||
| const [lastSendSearchCondition, setLastSendSearchCondition] = | const [lastSendSearchCondition, setLastSendSearchCondition] = | ||||
| @@ -7,7 +7,7 @@ import { | |||||
| TablePagination, | TablePagination, | ||||
| TableRow, | TableRow, | ||||
| } from "@mui/material"; | } from "@mui/material"; | ||||
| import { 運営会社ログインユーザ } from "api/login-user"; | |||||
| import { 顧客ログインユーザ } from "api/login-user"; | |||||
| import TableHeadCustom, { | import TableHeadCustom, { | ||||
| HeadLabelProps, | HeadLabelProps, | ||||
| } from "components/table/TableHeadCustom"; | } from "components/table/TableHeadCustom"; | ||||
| @@ -16,7 +16,7 @@ import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||||
| import { UseTableReturn } from "hooks/useTable"; | import { UseTableReturn } from "hooks/useTable"; | ||||
| type CommonProps = { | type CommonProps = { | ||||
| table: UseTableReturn<運営会社ログインユーザ>; | |||||
| table: UseTableReturn<顧客ログインユーザ>; | |||||
| }; | }; | ||||
| export default function TableBox({ table }: CommonProps) { | export default function TableBox({ table }: CommonProps) { | ||||
| const TABLE_HEAD: HeadLabelProps[] = [ | const TABLE_HEAD: HeadLabelProps[] = [ | ||||
| @@ -80,7 +80,7 @@ export default function TableBox({ table }: CommonProps) { | |||||
| } | } | ||||
| type RowProps = { | type RowProps = { | ||||
| data: 運営会社ログインユーザ; | |||||
| data: 顧客ログインユーザ; | |||||
| }; | }; | ||||
| function Row({ data }: RowProps) { | function Row({ data }: RowProps) { | ||||
| const { switchCustomerRole } = useAuth(); | const { switchCustomerRole } = useAuth(); | ||||
| @@ -1,5 +1,5 @@ | |||||
| import { Box } from "@mui/material"; | import { Box } from "@mui/material"; | ||||
| import { 運営会社ログインユーザ } from "api/login-user"; | |||||
| import { 顧客ログインユーザ } from "api/login-user"; | |||||
| import { SearchConditionContextProvider } from "contexts/SearchConditionContext"; | import { SearchConditionContextProvider } from "contexts/SearchConditionContext"; | ||||
| import useDashboard from "hooks/useDashBoard"; | import useDashboard from "hooks/useDashBoard"; | ||||
| import useTable from "hooks/useTable"; | import useTable from "hooks/useTable"; | ||||
| @@ -27,7 +27,7 @@ export default function 顧客一覧() { | |||||
| } | } | ||||
| function Page() { | function Page() { | ||||
| const table = useTable<運営会社ログインユーザ>(); | |||||
| const table = useTable<顧客ログインユーザ>(); | |||||
| return ( | return ( | ||||
| <Box> | <Box> | ||||
| @@ -1,9 +1,18 @@ | |||||
| import { Box, Button, Paper, Stack } from "@mui/material"; | import { Box, Button, Paper, Stack } from "@mui/material"; | ||||
| import { HasChildren } from "@types"; | |||||
| import { | |||||
| QRサービス券取得用トークンリフレッシュ, | |||||
| QRサービス券取得用トークン取得, | |||||
| } from "api/qr-service"; | |||||
| import useAPICall from "hooks/useAPICall"; | |||||
| import useAuth from "hooks/useAuth"; | |||||
| import useDashboard from "hooks/useDashBoard"; | import useDashboard from "hooks/useDashBoard"; | ||||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||||
| import { useDialog } from "hooks/useDialog"; | |||||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||||
| import { PageID, TabID } from "pages"; | import { PageID, TabID } from "pages"; | ||||
| import QRCode from "qrcode"; | import QRCode from "qrcode"; | ||||
| import { useEffect, useState } from "react"; | |||||
| import { useEffect, useMemo, useState } from "react"; | |||||
| import { getFullPath, getPath } from "routes/path"; | |||||
| export default function サービス券発行用QRコード() { | export default function サービス券発行用QRコード() { | ||||
| const { setHeaderTitle, setTabs } = useDashboard( | const { setHeaderTitle, setTabs } = useDashboard( | ||||
| @@ -12,23 +21,53 @@ export default function サービス券発行用QRコード() { | |||||
| ); | ); | ||||
| const size = 200; | const size = 200; | ||||
| const url = "http://localhost:8080/dashboard/qr/generate"; | |||||
| const { error } = useSnackbarCustom(); | |||||
| const [token, setToken] = useState(""); | |||||
| const [qr, setQr] = useState(""); | |||||
| const { navigateWhenChanged } = useNavigateCustom(); | |||||
| const { callAPI: callQRサービス券取得用トークン取得 } = useAPICall({ | |||||
| apiMethod: QRサービス券取得用トークン取得, | |||||
| backDrop: true, | |||||
| onSuccess: ({ data }) => { | |||||
| setToken(data.token); | |||||
| }, | |||||
| onFailed: () => { | |||||
| error("QR表示失敗しました"); | |||||
| }, | |||||
| }); | |||||
| const [qr, setQr] = useState(""); | |||||
| const url = useMemo(() => { | |||||
| if (!token) return ""; | |||||
| return getFullPath(PageID.QRサービス券発行申請, { | |||||
| query: { | |||||
| token, | |||||
| }, | |||||
| }); | |||||
| }, [token]); | |||||
| const fetch = () => { | |||||
| callQRサービス券取得用トークン取得({}); | |||||
| }; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| QRCode.toDataURL(url, { | |||||
| errorCorrectionLevel: "H", | |||||
| }).then((data: string) => { | |||||
| setQr(data); | |||||
| }); | |||||
| }, []); | |||||
| if (url) { | |||||
| console.log({ url }); | |||||
| QRCode.toDataURL(url, { | |||||
| errorCorrectionLevel: "H", | |||||
| }).then((data: string) => { | |||||
| setQr(data); | |||||
| }); | |||||
| } | |||||
| }, [url]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| setHeaderTitle("サービス券発行用QRコード"); | setHeaderTitle("サービス券発行用QRコード"); | ||||
| }, [setHeaderTitle, setTabs]); | }, [setHeaderTitle, setTabs]); | ||||
| useEffect(() => { | |||||
| fetch(); | |||||
| }, []); | |||||
| return ( | return ( | ||||
| <Box> | <Box> | ||||
| <Stack spacing={2}> | <Stack spacing={2}> | ||||
| @@ -38,8 +77,7 @@ export default function サービス券発行用QRコード() { | |||||
| {!!qr && <img src={qr} width={size} height={size}></img>} | {!!qr && <img src={qr} width={size} height={size}></img>} | ||||
| </Box> | </Box> | ||||
| <Box mx="auto"> | <Box mx="auto"> | ||||
| <Button variant="contained">印刷用</Button> | |||||
| <Button variant="contained">QRコード変更</Button> | |||||
| <RefreshButton fetch={fetch}>QRコードリフレッシュ</RefreshButton> | |||||
| </Box> | </Box> | ||||
| </Stack> | </Stack> | ||||
| </Paper> | </Paper> | ||||
| @@ -47,3 +85,42 @@ export default function サービス券発行用QRコード() { | |||||
| </Box> | </Box> | ||||
| ); | ); | ||||
| } | } | ||||
| type RefreshButtonProps = { | |||||
| fetch: VoidFunction; | |||||
| } & HasChildren; | |||||
| function RefreshButton({ fetch }: RefreshButtonProps) { | |||||
| const {} = useAuth(); | |||||
| const { success, error } = useSnackbarCustom(); | |||||
| const { callAPI: callQRサービス券取得用トークンリフレッシュ } = useAPICall({ | |||||
| apiMethod: QRサービス券取得用トークンリフレッシュ, | |||||
| onSuccess: () => { | |||||
| success("リフレッシュしました"); | |||||
| fetch(); | |||||
| }, | |||||
| onFailed: () => { | |||||
| error("失敗しました"); | |||||
| }, | |||||
| }); | |||||
| const { element, open, isAgree } = useDialog({ | |||||
| message: "リフレッシュすると以前のQRコードが使えなくなります", | |||||
| }); | |||||
| const handleClick = () => { | |||||
| open(); | |||||
| }; | |||||
| useEffect(() => { | |||||
| if (isAgree) { | |||||
| callQRサービス券取得用トークンリフレッシュ({}); | |||||
| } | |||||
| }, [isAgree]); | |||||
| return ( | |||||
| <> | |||||
| {element} | |||||
| <Button onClick={handleClick}>QRコードリフレッシュ</Button> | |||||
| </> | |||||
| ); | |||||
| } | |||||
| @@ -14,7 +14,11 @@ function Page() { | |||||
| const { shop, acquisitionSetting, certificationSetting } = | const { shop, acquisitionSetting, certificationSetting } = | ||||
| useContext(店舗詳細Context); | useContext(店舗詳細Context); | ||||
| if (!shop || !acquisitionSetting || !certificationSetting) { | |||||
| if ( | |||||
| !shop || | |||||
| acquisitionSetting === undefined || | |||||
| !certificationSetting === undefined | |||||
| ) { | |||||
| return null; | return null; | ||||
| } | } | ||||
| @@ -18,7 +18,7 @@ export default function Page() { | |||||
| const 駐車場ごと設定: 駐車場ごと設定 = useMemo(() => { | const 駐車場ごと設定: 駐車場ごと設定 = useMemo(() => { | ||||
| const ret: 駐車場ごと設定 = {}; | const ret: 駐車場ごと設定 = {}; | ||||
| certificationSetting.forEach((ele) => { | |||||
| certificationSetting?.forEach((ele) => { | |||||
| if (has(ret, ele.parking_management_code)) { | if (has(ret, ele.parking_management_code)) { | ||||
| ret[ele.parking_management_code].push(ele); | ret[ele.parking_management_code].push(ele); | ||||
| } else { | } else { | ||||
| @@ -59,6 +59,7 @@ export default function 駐車場追加() { | |||||
| }); | }); | ||||
| const options: AutoCompleteOption[] = useMemo(() => { | const options: AutoCompleteOption[] = useMemo(() => { | ||||
| if (certificationSetting === undefined) return []; | |||||
| return parkings | return parkings | ||||
| .filter( | .filter( | ||||
| (p) => | (p) => | ||||
| @@ -56,7 +56,7 @@ export default function QRサービス券発行申請() { | |||||
| <Box> | <Box> | ||||
| <Stack sx={{ p: 2 }}> | <Stack sx={{ p: 2 }}> | ||||
| <Box mx="auto"> | <Box mx="auto"> | ||||
| <Typography>取得に失敗しました。店舗へお問合せ下さい。</Typography> | |||||
| <Typography>無効なURLです。店舗へお問合せ下さい。</Typography> | |||||
| </Box> | </Box> | ||||
| </Stack> | </Stack> | ||||
| </Box> | </Box> | ||||
| @@ -77,14 +77,14 @@ const PATHS = { | |||||
| [makePathKey(PageID.LOGOUT)]: "/logout", | [makePathKey(PageID.LOGOUT)]: "/logout", | ||||
| [makePathKey(PageID.成り代わり終了)]: "/role/switch/end", | [makePathKey(PageID.成り代わり終了)]: "/role/switch/end", | ||||
| [makePathKey(PageID.QRサービス券発行申請)]: "qr-service/acquitision/:token", | |||||
| [makePathKey(PageID.QRサービス券発行申請)]: "/qr-service/acquitision/:token", | |||||
| // ダッシュボード---------------- | // ダッシュボード---------------- | ||||
| ...PATHS_DASHBOARD, | ...PATHS_DASHBOARD, | ||||
| // その他 | // その他 | ||||
| [makePathKey(PageID.PAGE_403)]: "403", | |||||
| [makePathKey(PageID.PAGE_404)]: "404", | |||||
| [makePathKey(PageID.PAGE_403)]: "/403", | |||||
| [makePathKey(PageID.PAGE_404)]: "/404", | |||||
| }; | }; | ||||
| export type PathOption = { | export type PathOption = { | ||||
| @@ -112,6 +112,9 @@ export function getPath(key: PathKey, option?: PathOption) { | |||||
| return path; | return path; | ||||
| } | } | ||||
| export function getFullPath(key: PathKey, option?: PathOption) { | |||||
| return window.location.origin + getPath(key, option); | |||||
| } | |||||
| export function getListPagePath(key: PathKey, page: number): string { | export function getListPagePath(key: PathKey, page: number): string { | ||||
| return getPath(key, { page }); | return getPath(key, { page }); | ||||
| @@ -50,6 +50,12 @@ export default function DashboardRoutes(): RouteObject[] { | |||||
| () => import("pages/dashboard/login-user/顧客ログインユーザ新規登録") | () => import("pages/dashboard/login-user/顧客ログインユーザ新規登録") | ||||
| ) | ) | ||||
| ); | ); | ||||
| const 店舗ログインユーザ一覧 = Loadable( | |||||
| lazy(() => import("pages/dashboard/login-user/店舗/一覧")) | |||||
| ); | |||||
| const 店舗ログインユーザ新規登録 = Loadable( | |||||
| lazy(() => import("pages/dashboard/login-user/店舗/新規登録")) | |||||
| ); | |||||
| const 店舗新規登録 = Loadable( | const 店舗新規登録 = Loadable( | ||||
| lazy(() => import("pages/dashboard/shop/店舗新規登録")) | lazy(() => import("pages/dashboard/shop/店舗新規登録")) | ||||
| ); | ); | ||||
| @@ -108,14 +114,20 @@ export default function DashboardRoutes(): RouteObject[] { | |||||
| path: getPath(PageID.ログインユーザ_顧客新規登録), | path: getPath(PageID.ログインユーザ_顧客新規登録), | ||||
| }, | }, | ||||
| }, | }, | ||||
| // { | |||||
| // pageId: PageID.ログインユーザ_店舗一覧, | |||||
| // element: <顧客ログインユーザ一覧 />, | |||||
| // }, | |||||
| // { | |||||
| // pageId: PageID.ログインユーザ_店舗新規登録, | |||||
| // element: <顧客ログインユーザ新規登録 />, | |||||
| // }, | |||||
| { | |||||
| pageId: PageID.ログインユーザ_店舗一覧, | |||||
| ele: { | |||||
| element: <店舗ログインユーザ一覧 />, | |||||
| path: getPath(PageID.ログインユーザ_店舗一覧), | |||||
| }, | |||||
| }, | |||||
| { | |||||
| pageId: PageID.ログインユーザ_店舗新規登録, | |||||
| ele: { | |||||
| element: <店舗ログインユーザ新規登録 />, | |||||
| path: getPath(PageID.ログインユーザ_店舗新規登録), | |||||
| }, | |||||
| }, | |||||
| { | { | ||||
| pageId: PageID.店舗新規登録, | pageId: PageID.店舗新規登録, | ||||
| ele: { | ele: { | ||||
| @@ -191,6 +203,13 @@ export default function DashboardRoutes(): RouteObject[] { | |||||
| ], | ], | ||||
| }, | }, | ||||
| }, | }, | ||||
| { | |||||
| pageId: PageID.サービス券発行用QRコード, | |||||
| ele: { | |||||
| element: <サービス券発行用QRコード />, | |||||
| path: getPath(PageID.サービス券発行用QRコード), | |||||
| }, | |||||
| }, | |||||
| ]; | ]; | ||||
| return allChildren | return allChildren | ||||