| @@ -51,6 +51,8 @@ export const ApiId = { | |||
| QRサービス券駐車場グループ新規登録: id++, | |||
| QRサービス券駐車場グループ駐車場追加登録: id++, | |||
| QRサービス券駐車場グループ駐車場削除登録: id++, | |||
| QRサービス券取得用トークン取得: id++, | |||
| QRサービス券取得用トークンリフレッシュ: id++, | |||
| QRサービス券取得: id++, | |||
| } as const; | |||
| export type ApiId = (typeof ApiId)[keyof typeof ApiId]; | |||
| @@ -1,7 +1,7 @@ | |||
| import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "api"; | |||
| import { getUrl } from "./url"; | |||
| export type 運営会社ログインユーザ = { | |||
| export type 顧客ログインユーザ = { | |||
| id: string; | |||
| name: string; | |||
| email: string; | |||
| @@ -9,6 +9,14 @@ export type 運営会社ログインユーザ = { | |||
| customer_name: string; | |||
| }; | |||
| export type 店舗ログインユーザ = { | |||
| id: string; | |||
| name: string; | |||
| email: string; | |||
| shop_id: string; | |||
| shop_name: string; | |||
| }; | |||
| // -------顧客ログインユーザ新規登録--------------- | |||
| export type 顧客ログインユーザ新規登録Request = { | |||
| name: string; | |||
| @@ -62,7 +70,7 @@ export type 顧客ログインユーザ一覧取得Request = { | |||
| }; | |||
| export type 顧客ログインユーザ一覧取得Response = { | |||
| data: { | |||
| list: 運営会社ログインユーザ[]; | |||
| list: 顧客ログインユーザ[]; | |||
| }; | |||
| } & APICommonResponse; | |||
| export const 顧客ログインユーザ一覧取得 = async ( | |||
| @@ -75,3 +83,24 @@ export const 顧客ログインユーザ一覧取得 = async ( | |||
| }); | |||
| 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; | |||
| }; | |||
| // -------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", | |||
| [A.QRサービス券駐車場グループ駐車場削除登録]: | |||
| "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", | |||
| }; | |||
| @@ -1,9 +1,9 @@ | |||
| import { PageID as P } from "pages"; | |||
| 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 認可別許可ルート: { | |||
| [route: string]: P[]; | |||
| @@ -176,7 +176,6 @@ function AuthContextProvider({ children }: Props) { | |||
| const cacheClear = () => { | |||
| 駐車場マスタストア.clear(); | |||
| 駐車場マスタストア.clear(); | |||
| }; | |||
| const login = async (email: string, password: string) => { | |||
| @@ -193,6 +192,7 @@ function AuthContextProvider({ children }: Props) { | |||
| user_id, | |||
| }); | |||
| navigateWhenChanged(getPath(PageID.DASHBOARD_OVERVIEW)); | |||
| cacheClear(); | |||
| }; | |||
| const switchShopRole = async (user_id: string) => { | |||
| await call店舗成り代わり開始({ user_id }); | |||
| @@ -20,8 +20,8 @@ import 駐車場マスタストア from "storage/cache/駐車場マスタ"; | |||
| type Context = { | |||
| shop: 店舗 | null; | |||
| certificationSetting: QRサービス券認証設定[]; | |||
| acquisitionSetting: QRサービス券取得設定 | null; | |||
| certificationSetting: QRサービス券認証設定[] | undefined; | |||
| acquisitionSetting: QRサービス券取得設定 | null | undefined; | |||
| parkings: 駐車場マスタ[]; | |||
| fetch: () => Promise<void>; | |||
| moveToMain: VoidFunction; | |||
| @@ -43,10 +43,11 @@ function 店舗詳細ContextProvider({ children }: Props) { | |||
| const [shop, setShop] = useState<店舗 | null>(null); | |||
| const [parkings, setParkings] = 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 { navigateWhenChanged } = useNavigateCustom(); | |||
| @@ -90,20 +90,30 @@ export default function Navigator(props: DrawerProps) { | |||
| icon: <ArticleIcon />, | |||
| id: PageID.ログインユーザ_顧客新規登録, | |||
| }, | |||
| { | |||
| label: "店舗一覧", | |||
| icon: <ArticleIcon />, | |||
| id: PageID.ログインユーザ_店舗一覧, | |||
| }, | |||
| { | |||
| label: "店舗新規登録", | |||
| icon: <ArticleIcon />, | |||
| id: PageID.ログインユーザ_店舗新規登録, | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| label: "店舗管理", | |||
| children: [ | |||
| { | |||
| label: "店舗新規登録", | |||
| label: "店舗一覧", | |||
| icon: <ArticleIcon />, | |||
| id: PageID.店舗新規登録, | |||
| id: PageID.店舗一覧, | |||
| }, | |||
| { | |||
| label: "店舗一覧", | |||
| label: "店舗新規登録", | |||
| icon: <ArticleIcon />, | |||
| id: PageID.店舗一覧, | |||
| id: PageID.店舗新規登録, | |||
| }, | |||
| ], | |||
| }, | |||
| @@ -129,6 +139,11 @@ export default function Navigator(props: DrawerProps) { | |||
| { | |||
| label: "QRサービス券", | |||
| children: [ | |||
| { | |||
| label: "サービス券発行", | |||
| icon: <ArticleIcon />, | |||
| id: PageID.サービス券発行用QRコード, | |||
| }, | |||
| { | |||
| label: "利用履歴", | |||
| 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 { Dictionary } from "@types"; | |||
| import { | |||
| 運営会社ログインユーザ, | |||
| 顧客ログインユーザ一覧取得, | |||
| } from "api/login-user"; | |||
| import { 顧客ログインユーザ, 顧客ログインユーザ一覧取得 } from "api/login-user"; | |||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useSearchConditionContext from "hooks/useSearchConditionContext"; | |||
| @@ -18,7 +15,7 @@ type FormProps = { | |||
| }; | |||
| type CommonProps = { | |||
| table: UseTableReturn<運営会社ログインユーザ>; | |||
| table: UseTableReturn<顧客ログインユーザ>; | |||
| }; | |||
| export default function SearchBox({ table }: CommonProps) { | |||
| const [lastSendSearchCondition, setLastSendSearchCondition] = | |||
| @@ -7,7 +7,7 @@ import { | |||
| TablePagination, | |||
| TableRow, | |||
| } from "@mui/material"; | |||
| import { 運営会社ログインユーザ } from "api/login-user"; | |||
| import { 顧客ログインユーザ } from "api/login-user"; | |||
| import TableHeadCustom, { | |||
| HeadLabelProps, | |||
| } from "components/table/TableHeadCustom"; | |||
| @@ -16,7 +16,7 @@ import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||
| import { UseTableReturn } from "hooks/useTable"; | |||
| type CommonProps = { | |||
| table: UseTableReturn<運営会社ログインユーザ>; | |||
| table: UseTableReturn<顧客ログインユーザ>; | |||
| }; | |||
| export default function TableBox({ table }: CommonProps) { | |||
| const TABLE_HEAD: HeadLabelProps[] = [ | |||
| @@ -80,7 +80,7 @@ export default function TableBox({ table }: CommonProps) { | |||
| } | |||
| type RowProps = { | |||
| data: 運営会社ログインユーザ; | |||
| data: 顧客ログインユーザ; | |||
| }; | |||
| function Row({ data }: RowProps) { | |||
| const { switchCustomerRole } = useAuth(); | |||
| @@ -1,5 +1,5 @@ | |||
| import { Box } from "@mui/material"; | |||
| import { 運営会社ログインユーザ } from "api/login-user"; | |||
| import { 顧客ログインユーザ } from "api/login-user"; | |||
| import { SearchConditionContextProvider } from "contexts/SearchConditionContext"; | |||
| import useDashboard from "hooks/useDashBoard"; | |||
| import useTable from "hooks/useTable"; | |||
| @@ -27,7 +27,7 @@ export default function 顧客一覧() { | |||
| } | |||
| function Page() { | |||
| const table = useTable<運営会社ログインユーザ>(); | |||
| const table = useTable<顧客ログインユーザ>(); | |||
| return ( | |||
| <Box> | |||
| @@ -1,9 +1,18 @@ | |||
| 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 useNavigateCustom from "hooks/useNavigateCustom"; | |||
| import { useDialog } from "hooks/useDialog"; | |||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||
| import { PageID, TabID } from "pages"; | |||
| 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コード() { | |||
| const { setHeaderTitle, setTabs } = useDashboard( | |||
| @@ -12,23 +21,53 @@ export default function サービス券発行用QRコード() { | |||
| ); | |||
| 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(() => { | |||
| 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(() => { | |||
| setHeaderTitle("サービス券発行用QRコード"); | |||
| }, [setHeaderTitle, setTabs]); | |||
| useEffect(() => { | |||
| fetch(); | |||
| }, []); | |||
| return ( | |||
| <Box> | |||
| <Stack spacing={2}> | |||
| @@ -38,8 +77,7 @@ export default function サービス券発行用QRコード() { | |||
| {!!qr && <img src={qr} width={size} height={size}></img>} | |||
| </Box> | |||
| <Box mx="auto"> | |||
| <Button variant="contained">印刷用</Button> | |||
| <Button variant="contained">QRコード変更</Button> | |||
| <RefreshButton fetch={fetch}>QRコードリフレッシュ</RefreshButton> | |||
| </Box> | |||
| </Stack> | |||
| </Paper> | |||
| @@ -47,3 +85,42 @@ export default function サービス券発行用QRコード() { | |||
| </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 } = | |||
| useContext(店舗詳細Context); | |||
| if (!shop || !acquisitionSetting || !certificationSetting) { | |||
| if ( | |||
| !shop || | |||
| acquisitionSetting === undefined || | |||
| !certificationSetting === undefined | |||
| ) { | |||
| return null; | |||
| } | |||
| @@ -18,7 +18,7 @@ export default function Page() { | |||
| const 駐車場ごと設定: 駐車場ごと設定 = useMemo(() => { | |||
| const ret: 駐車場ごと設定 = {}; | |||
| certificationSetting.forEach((ele) => { | |||
| certificationSetting?.forEach((ele) => { | |||
| if (has(ret, ele.parking_management_code)) { | |||
| ret[ele.parking_management_code].push(ele); | |||
| } else { | |||
| @@ -59,6 +59,7 @@ export default function 駐車場追加() { | |||
| }); | |||
| const options: AutoCompleteOption[] = useMemo(() => { | |||
| if (certificationSetting === undefined) return []; | |||
| return parkings | |||
| .filter( | |||
| (p) => | |||
| @@ -56,7 +56,7 @@ export default function QRサービス券発行申請() { | |||
| <Box> | |||
| <Stack sx={{ p: 2 }}> | |||
| <Box mx="auto"> | |||
| <Typography>取得に失敗しました。店舗へお問合せ下さい。</Typography> | |||
| <Typography>無効なURLです。店舗へお問合せ下さい。</Typography> | |||
| </Box> | |||
| </Stack> | |||
| </Box> | |||
| @@ -77,14 +77,14 @@ const PATHS = { | |||
| [makePathKey(PageID.LOGOUT)]: "/logout", | |||
| [makePathKey(PageID.成り代わり終了)]: "/role/switch/end", | |||
| [makePathKey(PageID.QRサービス券発行申請)]: "qr-service/acquitision/:token", | |||
| [makePathKey(PageID.QRサービス券発行申請)]: "/qr-service/acquitision/:token", | |||
| // ダッシュボード---------------- | |||
| ...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 = { | |||
| @@ -112,6 +112,9 @@ export function getPath(key: PathKey, option?: PathOption) { | |||
| return path; | |||
| } | |||
| export function getFullPath(key: PathKey, option?: PathOption) { | |||
| return window.location.origin + getPath(key, option); | |||
| } | |||
| export function getListPagePath(key: PathKey, page: number): string { | |||
| return getPath(key, { page }); | |||
| @@ -50,6 +50,12 @@ export default function DashboardRoutes(): RouteObject[] { | |||
| () => import("pages/dashboard/login-user/顧客ログインユーザ新規登録") | |||
| ) | |||
| ); | |||
| const 店舗ログインユーザ一覧 = Loadable( | |||
| lazy(() => import("pages/dashboard/login-user/店舗/一覧")) | |||
| ); | |||
| const 店舗ログインユーザ新規登録 = Loadable( | |||
| lazy(() => import("pages/dashboard/login-user/店舗/新規登録")) | |||
| ); | |||
| const 店舗新規登録 = Loadable( | |||
| lazy(() => import("pages/dashboard/shop/店舗新規登録")) | |||
| ); | |||
| @@ -108,14 +114,20 @@ export default function DashboardRoutes(): RouteObject[] { | |||
| 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.店舗新規登録, | |||
| ele: { | |||
| @@ -191,6 +203,13 @@ export default function DashboardRoutes(): RouteObject[] { | |||
| ], | |||
| }, | |||
| }, | |||
| { | |||
| pageId: PageID.サービス券発行用QRコード, | |||
| ele: { | |||
| element: <サービス券発行用QRコード />, | |||
| path: getPath(PageID.サービス券発行用QRコード), | |||
| }, | |||
| }, | |||
| ]; | |||
| return allChildren | |||