| @@ -36,6 +36,11 @@ export const ApiId = { | |||
| デポジット情報取得: id++, | |||
| デポジットチャージ: id++, | |||
| 店舗設定: id++, | |||
| 店舗QR設定取得: id++, | |||
| 店舗QR設定認証設定追加: id++, | |||
| 店舗QR設定認証設定削除: id++, | |||
| 店舗QR設定取得設定変更: id++, | |||
| 店舗QR設定取得設定無効化: id++, | |||
| // QRサービス券関連------------------------------- | |||
| QRサービス券駐車場グループ一覧取得: id++, | |||
| @@ -10,6 +10,21 @@ export type 店舗 = { | |||
| under_amount_when_auth: number; | |||
| under_amount_when_use: number; | |||
| }; | |||
| export type QRサービス券認証設定 = { | |||
| parking_management_code: string; | |||
| parking_name: string; | |||
| discount_ticket_code: number; | |||
| }; | |||
| export type QRサービス券取得設定 = { | |||
| qr_service_parking_group_id: string; | |||
| qr_service_parking_group_name: string; | |||
| parking_management_code: string; | |||
| parking_name: string; | |||
| shop_no: number; | |||
| discount_ticket_code: number; | |||
| }; | |||
| // -------店舗一覧取得--------------- | |||
| export type 店舗一覧取得Request = { | |||
| shop_id?: string; | |||
| @@ -104,3 +119,58 @@ export const 店舗設定 = async (param: 店舗設定Request) => { | |||
| }); | |||
| return res; | |||
| }; | |||
| // -------店舗QR設定取得--------------- | |||
| export type 店舗QR設定取得Request = { | |||
| shop_id: string; | |||
| }; | |||
| export type 店舗QR設定取得Response = { | |||
| data: { | |||
| shop_id: string; | |||
| certification: QRサービス券認証設定[]; | |||
| acquisition: QRサービス券取得設定 | null; | |||
| }; | |||
| } & APICommonResponse; | |||
| export const 店舗QR設定取得 = async (param: 店舗QR設定取得Request) => { | |||
| const res = await request<店舗QR設定取得Response>({ | |||
| url: getUrl(ApiId.店舗QR設定取得), | |||
| method: HttpMethod.GET, | |||
| data: new URLSearchParams(param), | |||
| }); | |||
| return res; | |||
| }; | |||
| // -------店舗QR設定取得設定変更--------------- | |||
| export type 店舗QR設定取得設定変更Request = { | |||
| shop_id: string; | |||
| qr_service_parking_group_id: string; | |||
| shop_no: string; | |||
| discount_ticket_code: string; | |||
| }; | |||
| 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; | |||
| }; | |||
| // -------店舗QR設定取得設定無効化--------------- | |||
| export type 店舗QR設定取得設定無効化Request = { | |||
| shop_id: string; | |||
| }; | |||
| 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; | |||
| }; | |||
| @@ -31,6 +31,12 @@ const urls = { | |||
| [A.デポジット情報取得]: "shop/deposit", | |||
| [A.デポジットチャージ]: "shop/deposit/charge", | |||
| [A.店舗設定]: "shop/config", | |||
| [A.店舗設定]: "shop/config", | |||
| [A.店舗QR設定取得]: "shop/config/detail", | |||
| [A.店舗QR設定認証設定追加]: "shop/config/certification/add", | |||
| [A.店舗QR設定認証設定削除]: "shop/config/certification/remove", | |||
| [A.店舗QR設定取得設定変更]: "shop/config/acquisition/enable", | |||
| [A.店舗QR設定取得設定無効化]: "shop/config/acquisition/disable", | |||
| // QRサービス券関連------------------------------- | |||
| [A.QRサービス券駐車場グループ一覧取得]: "qr-service/parking-group/list", | |||
| @@ -1,7 +1,7 @@ | |||
| import { useFormContext, Controller } from 'react-hook-form'; | |||
| import { Autocomplete, TextField, TextFieldProps } from '@mui/material'; | |||
| import React, { useEffect, useMemo } from 'react'; | |||
| import TextFieldEx from '../form/TextFieldEx'; | |||
| import { useFormContext, Controller } from "react-hook-form"; | |||
| import { Autocomplete, TextField, TextFieldProps } from "@mui/material"; | |||
| import React, { useEffect, useMemo } from "react"; | |||
| import TextFieldEx from "../form/TextFieldEx"; | |||
| // ---------------------------------------------------------------------- | |||
| @@ -14,15 +14,15 @@ export type AutoCompleteOptionType = AutoCompleteOption | string | null; | |||
| export const getValue = (option: AutoCompleteOptionType): string => { | |||
| if (option === null) { | |||
| return ''; | |||
| return ""; | |||
| } | |||
| if (typeof option === 'object') { | |||
| if (typeof option === "object") { | |||
| return option.value; | |||
| } | |||
| if (typeof option === 'string') { | |||
| if (typeof option === "string") { | |||
| return option; | |||
| } | |||
| return ''; | |||
| return ""; | |||
| }; | |||
| type IProps = { | |||
| @@ -45,24 +45,30 @@ export const getAutoCompleteOption = ( | |||
| return options.find((option) => option.value === value) ?? null; | |||
| }; | |||
| export default function RHFAutoComplete({ name, options, onFix, readOnly, ...other }: Props) { | |||
| export default function RHFAutoComplete({ | |||
| name, | |||
| options, | |||
| onFix, | |||
| readOnly, | |||
| ...other | |||
| }: Props) { | |||
| const { control, watch, setValue } = useFormContext(); | |||
| const value: AutoCompleteOption | string | null = watch(name); | |||
| const valueStr = useMemo(() => { | |||
| if (value === null) return ''; | |||
| if (value === undefined) return ''; | |||
| if (typeof value === 'string') { | |||
| if (value === null) return ""; | |||
| if (value === undefined) return ""; | |||
| if (typeof value === "string") { | |||
| return value; | |||
| } else { | |||
| return value.label ?? ''; | |||
| return value.label ?? ""; | |||
| } | |||
| }, [value]); | |||
| // string型からAutoCompleteOptionへ変換してフォームへセットする | |||
| useEffect(() => { | |||
| if (typeof value === 'string' && options) { | |||
| if (value === '') { | |||
| if (typeof value === "string" && options) { | |||
| if (value === "") { | |||
| setValue(name, null); | |||
| } else { | |||
| const val = getAutoCompleteOption(options, value); | |||
| @@ -74,9 +80,13 @@ export default function RHFAutoComplete({ name, options, onFix, readOnly, ...oth | |||
| }, [value, options]); | |||
| if (readOnly) { | |||
| return <TextFieldEx readOnly {...other} value={valueStr} variant="standard" />; | |||
| return ( | |||
| <TextFieldEx readOnly {...other} value={valueStr} variant="standard" /> | |||
| ); | |||
| } | |||
| if (typeof value === 'string') return null; | |||
| // if (typeof value === 'string') return null; | |||
| if (typeof value === "string") | |||
| return <TextFieldEx fullWidth {...other} value={""} />; | |||
| return ( | |||
| <Controller | |||
| name={name} | |||
| @@ -88,7 +98,7 @@ export default function RHFAutoComplete({ name, options, onFix, readOnly, ...oth | |||
| autoComplete | |||
| includeInputInList | |||
| noOptionsText="候補がありません" | |||
| getOptionLabel={(option) => option?.label ?? ''} | |||
| getOptionLabel={(option) => option?.label ?? ""} | |||
| isOptionEqualToValue={(option, value) => { | |||
| // if (typeof value !== 'object') return false; | |||
| return option.value === value.value; | |||
| @@ -14,6 +14,7 @@ import useNavigateCustom from "hooks/useNavigateCustom"; | |||
| import { PageID } from "pages"; | |||
| import { createContext, memo, useEffect, useMemo, useState } from "react"; | |||
| import { getPath } from "routes/path"; | |||
| import 駐車場マスタストア from "storage/cache/駐車場マスタ"; | |||
| type SwitchedUser = { | |||
| user_id: string; | |||
| @@ -173,6 +174,11 @@ function AuthContextProvider({ children }: Props) { | |||
| setShopId(null); | |||
| }; | |||
| const cacheClear = () => { | |||
| 駐車場マスタストア.clear(); | |||
| 駐車場マスタストア.clear(); | |||
| }; | |||
| const login = async (email: string, password: string) => { | |||
| const res: APICommonResponse | null = await callLogin({ email, password }); | |||
| return res?.result === ResultCode.SUCCESS; | |||
| @@ -191,10 +197,12 @@ function AuthContextProvider({ children }: Props) { | |||
| const switchShopRole = async (user_id: string) => { | |||
| await call店舗成り代わり開始({ user_id }); | |||
| navigateWhenChanged(getPath(PageID.DASHBOARD_OVERVIEW)); | |||
| cacheClear(); | |||
| }; | |||
| const switchEnd = async () => { | |||
| await call成り代わり終了({}); | |||
| navigateWhenChanged(getPath(PageID.DASHBOARD_OVERVIEW)); | |||
| cacheClear(); | |||
| }; | |||
| const isSwitched = useMemo(() => { | |||
| @@ -1,5 +1,5 @@ | |||
| import { HasChildren } from "@types"; | |||
| import { 駐車場マスタ, 駐車場マスタ一覧取得 } from "api/parking"; | |||
| import { 駐車場マスタ } from "api/parking"; | |||
| import { | |||
| QRサービス券駐車場グループ, | |||
| QRサービス券駐車場グループ一覧取得, | |||
| @@ -13,6 +13,7 @@ import { PageID, TabID } from "pages"; | |||
| import { createContext, useEffect, useMemo, useState } from "react"; | |||
| import { useParams } from "react-router-dom"; | |||
| import { getPath } from "routes/path"; | |||
| import 駐車場マスタストア from "storage/cache/駐車場マスタ"; | |||
| type Context = { | |||
| parkings: 駐車場マスタ[]; | |||
| @@ -55,16 +56,6 @@ function QRサービス券駐車場グループ管理ContextProvider({ children | |||
| error("データ取得失敗"); | |||
| }, | |||
| }); | |||
| const { callAPI: call駐車場マスタ一覧取得 } = useAPICall({ | |||
| apiMethod: 駐車場マスタ一覧取得, | |||
| backDrop: true, | |||
| onSuccess: ({ data }) => { | |||
| setParkings(data.list); | |||
| }, | |||
| onFailed: () => { | |||
| error("データ取得失敗"); | |||
| }, | |||
| }); | |||
| const fetch = async () => { | |||
| await callQRサービス券駐車場グループ一覧取得({}); | |||
| @@ -107,7 +98,9 @@ function QRサービス券駐車場グループ管理ContextProvider({ children | |||
| useEffect(() => { | |||
| fetch(); | |||
| call駐車場マスタ一覧取得({}); | |||
| 駐車場マスタストア.get().then((parkings) => { | |||
| setParkings(parkings); | |||
| }); | |||
| }, []); | |||
| return ( | |||
| @@ -1,5 +1,11 @@ | |||
| import { HasChildren } from "@types"; | |||
| import { 店舗, 店舗一覧取得 } from "api/shop"; | |||
| import { | |||
| QRサービス券取得設定, | |||
| QRサービス券認証設定, | |||
| 店舗, | |||
| 店舗QR設定取得, | |||
| 店舗一覧取得, | |||
| } from "api/shop"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useDashboard from "hooks/useDashBoard"; | |||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||
| @@ -12,12 +18,16 @@ import { getPath } from "routes/path"; | |||
| type Context = { | |||
| shop: 店舗 | null; | |||
| certificationSetting: QRサービス券認証設定[]; | |||
| acquisitionSetting: QRサービス券取得設定 | null; | |||
| fetch: () => Promise<void>; | |||
| moveToMain: VoidFunction; | |||
| }; | |||
| export const 店舗詳細Context = createContext<Context>({ | |||
| shop: null, | |||
| certificationSetting: [], | |||
| acquisitionSetting: null, | |||
| fetch: async () => {}, | |||
| moveToMain: () => {}, | |||
| }); | |||
| @@ -27,6 +37,11 @@ function 店舗詳細ContextProvider({ children }: Props) { | |||
| const { shopId: paramShopId } = useParams(); | |||
| const [shop, setShop] = useState<店舗 | null>(null); | |||
| const [certificationSetting, setCertificationSetting] = useState< | |||
| QRサービス券認証設定[] | |||
| >([]); | |||
| const [acquisitionSetting, setAcquisitionSetting] = | |||
| useState<QRサービス券取得設定 | null>(null); | |||
| const { success, error } = useSnackbarCustom(); | |||
| const { navigateWhenChanged } = useNavigateCustom(); | |||
| @@ -47,8 +62,25 @@ function 店舗詳細ContextProvider({ children }: Props) { | |||
| }, | |||
| }); | |||
| const { callAPI: call店舗QR設定取得 } = useAPICall({ | |||
| apiMethod: 店舗QR設定取得, | |||
| backDrop: true, | |||
| onSuccess: ({ data }) => { | |||
| setAcquisitionSetting(data.acquisition); | |||
| setCertificationSetting(data.certification); | |||
| }, | |||
| onFailed: () => { | |||
| error("データ取得失敗"); | |||
| navigateWhenChanged(getPath(PageID.店舗一覧)); | |||
| }, | |||
| }); | |||
| const fetch = async () => { | |||
| await call店舗一覧取得({ | |||
| if (!paramShopId) return; | |||
| call店舗一覧取得({ | |||
| shop_id: paramShopId, | |||
| }); | |||
| call店舗QR設定取得({ | |||
| shop_id: paramShopId, | |||
| }); | |||
| }; | |||
| @@ -77,6 +109,8 @@ function 店舗詳細ContextProvider({ children }: Props) { | |||
| <店舗詳細Context.Provider | |||
| value={{ | |||
| shop, | |||
| certificationSetting, | |||
| acquisitionSetting, | |||
| fetch, | |||
| moveToMain, | |||
| }} | |||
| @@ -20,9 +20,27 @@ export default function useA店舗管理Tabs() { | |||
| }), | |||
| }, | |||
| { | |||
| label: "設定", | |||
| tabId: TabID.店舗詳細_設定, | |||
| path: getPath([PageID.店舗詳細, TabID.店舗詳細_設定], { | |||
| label: "基本設定", | |||
| tabId: TabID.店舗詳細_基本設定, | |||
| path: getPath([PageID.店舗詳細, TabID.店舗詳細_基本設定], { | |||
| query: { | |||
| shopId: shop?.shop_id ?? "aaaaa", | |||
| }, | |||
| }), | |||
| }, | |||
| { | |||
| label: "認証設定", | |||
| tabId: TabID.店舗詳細_QR認証設定, | |||
| path: getPath([PageID.店舗詳細, TabID.店舗詳細_QR認証設定], { | |||
| query: { | |||
| shopId: shop?.shop_id ?? "aaaaa", | |||
| }, | |||
| }), | |||
| }, | |||
| { | |||
| label: "取得設定", | |||
| tabId: TabID.店舗詳細_QR取得設定, | |||
| path: getPath([PageID.店舗詳細, TabID.店舗詳細_QR取得設定], { | |||
| query: { | |||
| shopId: shop?.shop_id ?? "aaaaa", | |||
| }, | |||
| @@ -0,0 +1,28 @@ | |||
| import { Box, Stack } from "@mui/material"; | |||
| import useDashboard from "hooks/useDashBoard"; | |||
| import { PageID, TabID } from "pages"; | |||
| import 取得設定 from "./取得設定"; | |||
| import { 店舗詳細Context } from "contexts/page/dashboard/shop/店舗詳細Context"; | |||
| import { useContext } from "react"; | |||
| export default function Main() { | |||
| const {} = useDashboard(PageID.店舗詳細, TabID.店舗詳細_QR取得設定); | |||
| return <Page />; | |||
| } | |||
| function Page() { | |||
| const { shop, acquisitionSetting, certificationSetting } = | |||
| useContext(店舗詳細Context); | |||
| if (!shop || !acquisitionSetting || !certificationSetting) { | |||
| return null; | |||
| } | |||
| return ( | |||
| <Box> | |||
| <Stack spacing={2}> | |||
| <取得設定 /> | |||
| </Stack> | |||
| </Box> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,186 @@ | |||
| import { yupResolver } from "@hookform/resolvers/yup"; | |||
| import { Box, Button, Card, Grid, Stack, Typography } from "@mui/material"; | |||
| import { | |||
| QRサービス券駐車場グループ, | |||
| QRサービス券駐車場グループ一覧取得, | |||
| } from "api/qr-service"; | |||
| import { | |||
| 店舗QR設定取得設定変更, | |||
| 店舗QR設定取得設定無効化, | |||
| 店舗設定, | |||
| } from "api/shop"; | |||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||
| import RHFAutoComplete, { | |||
| AutoCompleteOptionType, | |||
| getValue, | |||
| } from "components/hook-form/RHFAutoComplete"; | |||
| import StackRow from "components/stack/StackRow"; | |||
| import { 店舗詳細Context } from "contexts/page/dashboard/shop/店舗詳細Context"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import { useDialog } from "hooks/useDialog"; | |||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||
| import { useContext, useEffect, useMemo, useState } from "react"; | |||
| import { useForm } from "react-hook-form"; | |||
| import { number, object } from "yup"; | |||
| type FormProps = { | |||
| qr_service_parking_group_id: AutoCompleteOptionType; | |||
| shop_no: string; | |||
| discount_ticket_code: string; | |||
| }; | |||
| export default function 取得設定() { | |||
| const { shop, fetch, moveToMain, acquisitionSetting } = | |||
| useContext(店舗詳細Context); | |||
| const { success, error } = useSnackbarCustom(); | |||
| const { navigateWhenChanged } = useNavigateCustom(); | |||
| const [groups, setGroups] = useState<QRサービス券駐車場グループ[] | null>( | |||
| null | |||
| ); | |||
| const form = useForm<FormProps>({ | |||
| defaultValues: { | |||
| qr_service_parking_group_id: acquisitionSetting | |||
| ? String(acquisitionSetting.qr_service_parking_group_id) | |||
| : null, | |||
| shop_no: acquisitionSetting ? String(acquisitionSetting.shop_no) : "", | |||
| discount_ticket_code: acquisitionSetting | |||
| ? String(acquisitionSetting.discount_ticket_code) | |||
| : "", | |||
| }, | |||
| resolver: yupResolver( | |||
| object().shape({ | |||
| qr_service_parking_group_id: object().required("必須項目です"), | |||
| shop_no: number().typeError("数値を入力してください"), | |||
| discount_ticket_code: number().typeError("数値を入力してください"), | |||
| }) | |||
| ), | |||
| }); | |||
| const { callAPI: callQRサービス券駐車場グループ一覧取得 } = useAPICall({ | |||
| apiMethod: QRサービス券駐車場グループ一覧取得, | |||
| backDrop: true, | |||
| onSuccess: ({ data }) => { | |||
| setGroups(data.list); | |||
| }, | |||
| }); | |||
| const options = useMemo(() => { | |||
| return ( | |||
| groups?.map((g) => ({ | |||
| label: g.name, | |||
| value: g.id, | |||
| })) ?? [] | |||
| ); | |||
| }, [groups]); | |||
| const { callAPI: call店舗QR設定取得設定変更 } = useAPICall({ | |||
| apiMethod: 店舗QR設定取得設定変更, | |||
| backDrop: true, | |||
| form, | |||
| onSuccess: () => { | |||
| success("設定しました"); | |||
| fetch(); | |||
| moveToMain(); | |||
| }, | |||
| onFailed: () => { | |||
| error("失敗しました"); | |||
| }, | |||
| }); | |||
| const { callAPI: call店舗QR設定取得設定無効化 } = useAPICall({ | |||
| apiMethod: 店舗QR設定取得設定無効化, | |||
| backDrop: true, | |||
| form, | |||
| onSuccess: () => { | |||
| success("削除しました"); | |||
| fetch(); | |||
| moveToMain(); | |||
| }, | |||
| onFailed: () => { | |||
| error("失敗しました"); | |||
| }, | |||
| }); | |||
| const { element, isAgree, open } = useDialog({ | |||
| message: "削除しますか", | |||
| }); | |||
| const handleSubmit = (data: FormProps) => { | |||
| if (shop === null) return; | |||
| const qrServiceParkingGroupId = getValue(data.qr_service_parking_group_id); | |||
| call店舗QR設定取得設定変更({ | |||
| ...data, | |||
| shop_id: shop.shop_id, | |||
| qr_service_parking_group_id: qrServiceParkingGroupId, | |||
| }); | |||
| }; | |||
| const handleDelete = () => { | |||
| open(); | |||
| }; | |||
| useEffect(() => { | |||
| callQRサービス券駐車場グループ一覧取得({}); | |||
| }, []); | |||
| useEffect(() => { | |||
| if (isAgree && shop) { | |||
| call店舗QR設定取得設定無効化({ | |||
| shop_id: shop?.shop_id, | |||
| }); | |||
| } | |||
| }, [isAgree, shop]); | |||
| return ( | |||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||
| <Card sx={{ p: 2 }} elevation={1}> | |||
| <Box> | |||
| <Stack spacing={2}> | |||
| <Typography variant="h6">QRサービス券取得設定</Typography> | |||
| <Grid container columnGap={2}> | |||
| <Grid item xs={12} md={6}> | |||
| <Typography>QRサービス券駐車場グループ</Typography> | |||
| <StackRow> | |||
| <RHFAutoComplete | |||
| name="qr_service_parking_group_id" | |||
| size="small" | |||
| options={options} | |||
| /> | |||
| </StackRow> | |||
| </Grid> | |||
| </Grid> | |||
| <Grid container columnGap={2}> | |||
| <Grid item xs={12} md={6}> | |||
| <Typography>店舗番号</Typography> | |||
| <StackRow> | |||
| <RHFTextField type="number" name="shop_no" /> | |||
| </StackRow> | |||
| </Grid> | |||
| </Grid> | |||
| <Grid container columnGap={2}> | |||
| <Grid item xs={12} md={6}> | |||
| <Typography>サービス券コード</Typography> | |||
| <StackRow> | |||
| <RHFTextField type="number" name="discount_ticket_code" /> | |||
| </StackRow> | |||
| </Grid> | |||
| </Grid> | |||
| <StackRow> | |||
| <Button type="submit" variant="contained"> | |||
| 設定 | |||
| </Button> | |||
| {!!acquisitionSetting && ( | |||
| <Button type="button" color="error" onClick={handleDelete}> | |||
| 削除 | |||
| </Button> | |||
| )} | |||
| </StackRow> | |||
| </Stack> | |||
| </Box> | |||
| </Card> | |||
| {element} | |||
| </FormProvider> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| import { Box, Stack } from "@mui/material"; | |||
| import { 店舗詳細Context } from "contexts/page/dashboard/shop/店舗詳細Context"; | |||
| import useDashboard from "hooks/useDashBoard"; | |||
| import { PageID, TabID } from "pages"; | |||
| import { useContext } from "react"; | |||
| import 駐車場一覧 from "./駐車場一覧"; | |||
| import 駐車場追加 from "./駐車場追加"; | |||
| export default function Page() { | |||
| const {} = useDashboard(PageID.店舗詳細, TabID.店舗詳細_QR認証設定); | |||
| const {} = useContext(店舗詳細Context); | |||
| return ( | |||
| <Box> | |||
| <Stack spacing={2}> | |||
| <駐車場一覧 /> | |||
| <駐車場追加 /> | |||
| </Stack> | |||
| </Box> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,99 @@ | |||
| import { Box, Button, Card, Grid, Stack, Typography } from "@mui/material"; | |||
| import { 駐車場マスタ } from "api/parking"; | |||
| import { QRサービス券駐車場グループ駐車場削除登録 } from "api/qr-service"; | |||
| import { QRサービス券駐車場グループ管理Context } from "contexts/page/dashboard/parking/QRサービス券駐車場グループ管理Context"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import { useDialog } from "hooks/useDialog"; | |||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||
| import { useContext, useEffect, useState } from "react"; | |||
| export default function 駐車場一覧() { | |||
| const { fetch, selectedGroup } = useContext( | |||
| QRサービス券駐車場グループ管理Context | |||
| ); | |||
| const { success, error } = useSnackbarCustom(); | |||
| const { callAPI: callQRサービス券駐車場グループ駐車場削除登録 } = useAPICall({ | |||
| apiMethod: QRサービス券駐車場グループ駐車場削除登録, | |||
| backDrop: true, | |||
| onSuccess: () => { | |||
| success("削除しました"); | |||
| fetch(); | |||
| }, | |||
| onFailed: () => { | |||
| error("失敗しました"); | |||
| }, | |||
| }); | |||
| const [削除予定駐車場管理コード, set削除予定駐車場管理コード] = useState< | |||
| string | null | |||
| >(null); | |||
| const { element, open, isAgree } = useDialog({ | |||
| message: "削除しますか?", | |||
| }); | |||
| const deleteDataConfirm = (parkingManagementCode: string) => { | |||
| set削除予定駐車場管理コード(parkingManagementCode); | |||
| open(); | |||
| }; | |||
| useEffect(() => { | |||
| if (isAgree && selectedGroup && 削除予定駐車場管理コード) { | |||
| callQRサービス券駐車場グループ駐車場削除登録({ | |||
| id: selectedGroup.id, | |||
| parking_management_code: 削除予定駐車場管理コード, | |||
| }); | |||
| } | |||
| }, [isAgree]); | |||
| if (selectedGroup === null) { | |||
| return null; | |||
| } | |||
| return ( | |||
| <Card sx={{ p: 2 }} elevation={1}> | |||
| <Box> | |||
| <Stack spacing={2}> | |||
| <Typography variant="h6">グループに含まれる駐車場</Typography> | |||
| <Stack spacing={1}> | |||
| {selectedGroup.parkings.map((p) => ( | |||
| <Row | |||
| parking={p} | |||
| key={p.parking_management_code} | |||
| deleteData={deleteDataConfirm} | |||
| /> | |||
| ))} | |||
| {selectedGroup.parkings.length === 0 && ( | |||
| <Typography>登録なし</Typography> | |||
| )} | |||
| </Stack> | |||
| </Stack> | |||
| </Box> | |||
| {element} | |||
| </Card> | |||
| ); | |||
| } | |||
| type RowProps = { | |||
| parking: 駐車場マスタ; | |||
| deleteData: (parkingManagementCode: string) => void; | |||
| }; | |||
| function Row({ parking, deleteData }: RowProps) { | |||
| const handleClick = () => { | |||
| deleteData(parking.parking_management_code); | |||
| }; | |||
| return ( | |||
| <Grid container> | |||
| <Grid item xs={10}> | |||
| {parking.parking_name}({parking.parking_management_code}) | |||
| </Grid> | |||
| <Grid item xs={2}> | |||
| <Button color="error" onClick={handleClick}> | |||
| 削除 | |||
| </Button> | |||
| </Grid> | |||
| </Grid> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,106 @@ | |||
| import { yupResolver } from "@hookform/resolvers/yup"; | |||
| import { Box, Button, Card, Grid, Stack, Typography } from "@mui/material"; | |||
| import { QRサービス券駐車場グループ駐車場追加登録 } from "api/qr-service"; | |||
| import { FormProvider, RHFAutoComplete } from "components/hook-form"; | |||
| import { | |||
| AutoCompleteOption, | |||
| AutoCompleteOptionType, | |||
| getValue, | |||
| } from "components/hook-form/RHFAutoComplete"; | |||
| import StackRow from "components/stack/StackRow"; | |||
| import { QRサービス券駐車場グループ管理Context } from "contexts/page/dashboard/parking/QRサービス券駐車場グループ管理Context"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||
| import { useContext, useMemo } from "react"; | |||
| import { useForm } from "react-hook-form"; | |||
| import { object } from "yup"; | |||
| type FormProps = { | |||
| parking_management_code: AutoCompleteOptionType; | |||
| }; | |||
| export default function 駐車場追加() { | |||
| const { fetch, selectedGroup, parkings } = useContext( | |||
| QRサービス券駐車場グループ管理Context | |||
| ); | |||
| const { success, error } = useSnackbarCustom(); | |||
| const form = useForm<FormProps>({ | |||
| defaultValues: { | |||
| parking_management_code: null, | |||
| }, | |||
| resolver: yupResolver( | |||
| object().shape({ | |||
| parking_management_code: object().required("必須項目です"), | |||
| }) | |||
| ), | |||
| }); | |||
| const { callAPI: callQRサービス券駐車場グループ駐車場追加登録 } = useAPICall({ | |||
| apiMethod: QRサービス券駐車場グループ駐車場追加登録, | |||
| backDrop: true, | |||
| form, | |||
| onSuccess: () => { | |||
| success("追加しました"); | |||
| fetch(); | |||
| form.setValue("parking_management_code", null); | |||
| }, | |||
| onFailed: () => { | |||
| error("失敗しました"); | |||
| }, | |||
| }); | |||
| const options: AutoCompleteOption[] = useMemo(() => { | |||
| return parkings | |||
| .filter( | |||
| (p) => | |||
| !selectedGroup?.parkings.find( | |||
| (ele) => ele.parking_management_code === p.parking_management_code | |||
| ) | |||
| ) | |||
| .map((p) => ({ | |||
| label: p.parking_name, | |||
| value: p.parking_management_code, | |||
| })); | |||
| }, [parkings, selectedGroup]); | |||
| const handleSubmit = (data: FormProps) => { | |||
| if (selectedGroup === null) return; | |||
| const parkingManagementCode = getValue(data.parking_management_code); | |||
| callQRサービス券駐車場グループ駐車場追加登録({ | |||
| id: selectedGroup.id, | |||
| parking_management_code: parkingManagementCode, | |||
| }); | |||
| }; | |||
| return ( | |||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||
| <Card sx={{ p: 2 }} elevation={1}> | |||
| <Box> | |||
| <Stack spacing={2}> | |||
| <Typography variant="h6">駐車場追加</Typography> | |||
| <StackRow> | |||
| <Grid container columnGap={2}> | |||
| <Grid item xs={12} md={6}> | |||
| <Typography>駐車場</Typography> | |||
| <StackRow> | |||
| <RHFAutoComplete | |||
| name="parking_management_code" | |||
| options={options} | |||
| size="small" | |||
| /> | |||
| </StackRow> | |||
| </Grid> | |||
| </Grid> | |||
| </StackRow> | |||
| <StackRow> | |||
| <Button type="submit" variant="contained"> | |||
| 追加 | |||
| </Button> | |||
| </StackRow> | |||
| </Stack> | |||
| </Box> | |||
| </Card> | |||
| </FormProvider> | |||
| ); | |||
| } | |||
| @@ -4,7 +4,7 @@ import { PageID, TabID } from "pages"; | |||
| import 設定 from "./設定"; | |||
| export default function 店舗詳細設定() { | |||
| const {} = useDashboard(PageID.店舗詳細, TabID.店舗詳細_設定); | |||
| const {} = useDashboard(PageID.店舗詳細, TabID.店舗詳細_基本設定); | |||
| return <Page />; | |||
| } | |||
| @@ -1,120 +0,0 @@ | |||
| import { yupResolver } from "@hookform/resolvers/yup"; | |||
| import { Box, Button, Card, Grid, Stack, Typography } from "@mui/material"; | |||
| import { 店舗設定 } from "api/shop"; | |||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||
| import StackRow from "components/stack/StackRow"; | |||
| import { 店舗詳細Context } from "contexts/page/dashboard/shop/店舗詳細Context"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||
| import { useContext } from "react"; | |||
| import { useForm } from "react-hook-form"; | |||
| import { number, object } from "yup"; | |||
| type FormProps = { | |||
| qr_service_expire_min: number; | |||
| under_amount_when_create: number; | |||
| under_amount_when_auth: number; | |||
| under_amount_when_use: number; | |||
| }; | |||
| export default function 設定_() { | |||
| const { shop, fetch, moveToMain } = useContext(店舗詳細Context); | |||
| const { success, error } = useSnackbarCustom(); | |||
| const { navigateWhenChanged } = useNavigateCustom(); | |||
| const form = useForm<FormProps>({ | |||
| defaultValues: { | |||
| qr_service_expire_min: shop?.qr_service_expire_min ?? 0, | |||
| under_amount_when_create: shop?.under_amount_when_create ?? 0, | |||
| under_amount_when_auth: shop?.under_amount_when_auth ?? 0, | |||
| under_amount_when_use: shop?.under_amount_when_use ?? 0, | |||
| }, | |||
| resolver: yupResolver( | |||
| object().shape({ | |||
| qr_service_expire_min: number().typeError("数値を入力してください"), | |||
| under_amount_when_create: number().typeError("数値を入力してください"), | |||
| under_amount_when_auth: number().typeError("数値を入力してください"), | |||
| under_amount_when_use: number().typeError("数値を入力してください"), | |||
| }) | |||
| ), | |||
| }); | |||
| const { callAPI: call店舗設定 } = useAPICall({ | |||
| apiMethod: 店舗設定, | |||
| backDrop: true, | |||
| form, | |||
| onSuccess: () => { | |||
| success("設定しました"); | |||
| fetch(); | |||
| moveToMain(); | |||
| }, | |||
| onFailed: () => { | |||
| error("失敗しました"); | |||
| }, | |||
| }); | |||
| const handleSubmit = (data: FormProps) => { | |||
| if (shop === null) return; | |||
| call店舗設定({ | |||
| shop_id: shop.shop_id, | |||
| qr_service_expire_min: String(data.qr_service_expire_min), | |||
| under_amount_when_auth: String(data.under_amount_when_auth), | |||
| under_amount_when_create: String(data.under_amount_when_create), | |||
| under_amount_when_use: String(data.under_amount_when_use), | |||
| }); | |||
| }; | |||
| return ( | |||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||
| <Card sx={{ p: 2 }} elevation={1}> | |||
| <Box> | |||
| <Stack spacing={2}> | |||
| <Typography variant="h6">基本設定</Typography> | |||
| <Grid container columnGap={2}> | |||
| <Grid item xs={12} md={6}> | |||
| <Typography>QRサービス券有効期限</Typography> | |||
| <StackRow> | |||
| <RHFTextField type="number" name="qr_service_expire_min" /> | |||
| <Typography>分</Typography> | |||
| </StackRow> | |||
| </Grid> | |||
| </Grid> | |||
| <Grid container columnGap={2}> | |||
| <Grid item xs={12} md={6}> | |||
| <Typography>発行時デポジット下限値</Typography> | |||
| <StackRow> | |||
| <RHFTextField type="number" name="under_amount_when_create" /> | |||
| <Typography>円</Typography> | |||
| </StackRow> | |||
| </Grid> | |||
| </Grid> | |||
| <Grid container columnGap={2}> | |||
| <Grid item xs={12} md={6}> | |||
| <Typography>認証時デポジット下限値</Typography> | |||
| <StackRow> | |||
| <RHFTextField type="number" name="under_amount_when_auth" /> | |||
| <Typography>円</Typography> | |||
| </StackRow> | |||
| </Grid> | |||
| </Grid> | |||
| <Grid container columnGap={2}> | |||
| <Grid item xs={12} md={6}> | |||
| <Typography>利用時デポジット下限値</Typography> | |||
| <StackRow> | |||
| <RHFTextField type="number" name="under_amount_when_use" /> | |||
| <Typography>円</Typography> | |||
| </StackRow> | |||
| </Grid> | |||
| </Grid> | |||
| <StackRow> | |||
| <Button type="submit" variant="contained"> | |||
| 設定 | |||
| </Button> | |||
| </StackRow> | |||
| </Stack> | |||
| </Box> | |||
| </Card> | |||
| </FormProvider> | |||
| ); | |||
| } | |||
| @@ -8,7 +8,7 @@ import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||
| import { useContext } from "react"; | |||
| import { useForm } from "react-hook-form"; | |||
| import { number, object } from "yup"; | |||
| import { 店舗詳細Context } from "../../../../contexts/page/dashboard/shop/店舗詳細Context"; | |||
| import { 店舗詳細Context } from "contexts/page/dashboard/shop/店舗詳細Context"; | |||
| type FormProps = { | |||
| amount: string; | |||
| @@ -2,7 +2,7 @@ import { Box, Card, Stack, Typography } from "@mui/material"; | |||
| import { SimpleDataList } from "components/table"; | |||
| import { useContext, useMemo } from "react"; | |||
| import { numberFormat } from "utils/string"; | |||
| import { 店舗詳細Context } from "../../../../contexts/page/dashboard/shop/店舗詳細Context"; | |||
| import { 店舗詳細Context } from "contexts/page/dashboard/shop/店舗詳細Context"; | |||
| export default function 詳細情報() { | |||
| const { shop } = useContext(店舗詳細Context); | |||
| @@ -45,7 +45,9 @@ export const TabID = { | |||
| NONE: id++, | |||
| 店舗詳細_メイン: id++, | |||
| 店舗詳細_設定: id++, | |||
| 店舗詳細_基本設定: id++, | |||
| 店舗詳細_QR認証設定: id++, | |||
| 店舗詳細_QR取得設定: id++, | |||
| QRサービス券駐車場グループ管理_一覧: id++, | |||
| QRサービス券駐車場グループ管理_新規登録: id++, | |||
| @@ -57,8 +57,12 @@ const PATHS_DASHBOARD = { | |||
| [makePathKey(PageID.店舗新規登録)]: "/dashboard/shop/register", | |||
| [makePathKey([PageID.店舗詳細, TabID.店舗詳細_メイン])]: | |||
| "/dashboard/shop/detail/:shopId", | |||
| [makePathKey([PageID.店舗詳細, TabID.店舗詳細_設定])]: | |||
| [makePathKey([PageID.店舗詳細, TabID.店舗詳細_基本設定])]: | |||
| "/dashboard/shop/detail/setting/:shopId", | |||
| [makePathKey([PageID.店舗詳細, TabID.店舗詳細_QR認証設定])]: | |||
| "/dashboard/shop/detail/setting/qr/certification/:shopId", | |||
| [makePathKey([PageID.店舗詳細, TabID.店舗詳細_QR取得設定])]: | |||
| "/dashboard/shop/detail/setting/qr/acquisition/:shopId", | |||
| [makePathKey(PageID.サービス券発行用QRコード)]: "/dashboard/qrcode/generate", | |||
| [makePathKey(PageID.サービス券利用履歴)]: "/dashboard/qrcode/history", | |||
| @@ -57,10 +57,16 @@ export default function DashboardRoutes(): RouteObject[] { | |||
| lazy(() => import("pages/dashboard/shop/店舗一覧")) | |||
| ); | |||
| const 店舗詳細 = Loadable( | |||
| lazy(() => import("pages/dashboard/shop/店舗詳細")) | |||
| lazy(() => import("pages/dashboard/shop/店舗詳細/詳細")) | |||
| ); | |||
| const 店舗詳細設定 = Loadable( | |||
| lazy(() => import("pages/dashboard/shop/店舗詳細/設定")) | |||
| const 店舗詳細基本設定 = Loadable( | |||
| lazy(() => import("pages/dashboard/shop/店舗詳細/基本設定")) | |||
| ); | |||
| const 店舗詳細QR取得設定 = Loadable( | |||
| lazy(() => import("pages/dashboard/shop/店舗詳細/QR取得設定")) | |||
| ); | |||
| const 店舗詳細QR認証設定 = Loadable( | |||
| lazy(() => import("pages/dashboard/shop/店舗詳細/QR認証設定")) | |||
| ); | |||
| const allChildren: { | |||
| @@ -138,8 +144,16 @@ export default function DashboardRoutes(): RouteObject[] { | |||
| path: getPath([PageID.店舗詳細, TabID.店舗詳細_メイン]), | |||
| }, | |||
| { | |||
| element: <店舗詳細設定 />, | |||
| path: getPath([PageID.店舗詳細, TabID.店舗詳細_設定]), | |||
| element: <店舗詳細基本設定 />, | |||
| path: getPath([PageID.店舗詳細, TabID.店舗詳細_基本設定]), | |||
| }, | |||
| { | |||
| element: <店舗詳細QR認証設定 />, | |||
| path: getPath([PageID.店舗詳細, TabID.店舗詳細_QR認証設定]), | |||
| }, | |||
| { | |||
| element: <店舗詳細QR取得設定 />, | |||
| path: getPath([PageID.店舗詳細, TabID.店舗詳細_QR取得設定]), | |||
| }, | |||
| ], | |||
| }, | |||
| @@ -0,0 +1,19 @@ | |||
| import { 駐車場マスタ, 駐車場マスタ一覧取得 } from "api/parking"; | |||
| class 駐車場マスタストア { | |||
| private list: 駐車場マスタ[] | undefined = undefined; | |||
| async get() { | |||
| if (this.list === undefined) { | |||
| const res = await 駐車場マスタ一覧取得({}); | |||
| this.list = res?.data.list ?? []; | |||
| } | |||
| return this.list; | |||
| } | |||
| async clear() { | |||
| this.list = undefined; | |||
| } | |||
| } | |||
| export default new 駐車場マスタストア(); | |||