| @@ -16,23 +16,23 @@ import BackDropContextProvider from "contexts/BackDropContext"; | |||||
| export default function App() { | export default function App() { | ||||
| return ( | return ( | ||||
| <LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={ja}> | <LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={ja}> | ||||
| <AuthContextProvider> | |||||
| <PageContextProvider> | |||||
| <WindowSizeContextProvider> | |||||
| <BrowserRouter> | |||||
| <AppThemeProvider> | |||||
| <SnackbarProvider> | |||||
| <BackDropContextProvider> | |||||
| <PageContextProvider> | |||||
| <WindowSizeContextProvider> | |||||
| <BrowserRouter> | |||||
| <AppThemeProvider> | |||||
| <SnackbarProvider> | |||||
| <BackDropContextProvider> | |||||
| <AuthContextProvider> | |||||
| <CsrfTokenProvider /> | <CsrfTokenProvider /> | ||||
| <CssBaseline /> | <CssBaseline /> | ||||
| <Routes /> | <Routes /> | ||||
| </BackDropContextProvider> | |||||
| </SnackbarProvider> | |||||
| </AppThemeProvider> | |||||
| </BrowserRouter> | |||||
| </WindowSizeContextProvider> | |||||
| </PageContextProvider> | |||||
| </AuthContextProvider> | |||||
| </AuthContextProvider> | |||||
| </BackDropContextProvider> | |||||
| </SnackbarProvider> | |||||
| </AppThemeProvider> | |||||
| </BrowserRouter> | |||||
| </WindowSizeContextProvider> | |||||
| </PageContextProvider> | |||||
| </LocalizationProvider> | </LocalizationProvider> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -8,6 +8,9 @@ export type Me = { | |||||
| email: string; | email: string; | ||||
| role: UserRole; | role: UserRole; | ||||
| shop_id: string | null; | shop_id: string | null; | ||||
| switched_user_id: string | null; | |||||
| switched_name: string | null; | |||||
| switched_role: UserRole | null; | |||||
| }; | }; | ||||
| type MeResponse = { | type MeResponse = { | ||||
| @@ -45,3 +48,54 @@ export const logout = async () => { | |||||
| }); | }); | ||||
| return res; | return res; | ||||
| }; | }; | ||||
| // -------顧客成り代わり開始--------------- | |||||
| export type 顧客成り代わり開始Request = { | |||||
| user_id: string; | |||||
| }; | |||||
| export type 顧客成り代わり開始Response = { | |||||
| data: { | |||||
| user_id: string; | |||||
| name: string; | |||||
| role: string; | |||||
| }; | |||||
| } & APICommonResponse; | |||||
| export const 顧客成り代わり開始 = async (param: 顧客成り代わり開始Request) => { | |||||
| const res = await request<顧客成り代わり開始Response>({ | |||||
| url: getUrl(ApiId.顧客成り代わり開始), | |||||
| method: HttpMethod.POST, | |||||
| data: makeParam(param), | |||||
| }); | |||||
| return res; | |||||
| }; | |||||
| // -------店舗成り代わり開始--------------- | |||||
| export type 店舗成り代わり開始Request = { | |||||
| user_id: string; | |||||
| }; | |||||
| export type 店舗成り代わり開始Response = { | |||||
| data: { | |||||
| user_id: string; | |||||
| name: string; | |||||
| role: string; | |||||
| }; | |||||
| } & APICommonResponse; | |||||
| export const 店舗成り代わり開始 = async (param: 店舗成り代わり開始Request) => { | |||||
| const res = await request<店舗成り代わり開始Response>({ | |||||
| url: getUrl(ApiId.店舗成り代わり開始), | |||||
| method: HttpMethod.POST, | |||||
| data: makeParam(param), | |||||
| }); | |||||
| return res; | |||||
| }; | |||||
| // -------成り代わり終了--------------- | |||||
| export type 成り代わり終了Request = {}; | |||||
| export type 成り代わり終了Response = {} & APICommonResponse; | |||||
| export const 成り代わり終了 = async (param: 成り代わり終了Request) => { | |||||
| const res = await request<成り代わり終了Response>({ | |||||
| url: getUrl(ApiId.成り代わり終了), | |||||
| method: HttpMethod.GET, | |||||
| data: new URLSearchParams(param), | |||||
| }); | |||||
| return res; | |||||
| }; | |||||
| @@ -14,6 +14,9 @@ export const ApiId = { | |||||
| ME: id++, | ME: id++, | ||||
| LOGIN: id++, | LOGIN: id++, | ||||
| LOGOUT: id++, | LOGOUT: id++, | ||||
| 顧客成り代わり開始: id++, | |||||
| 店舗成り代わり開始: id++, | |||||
| 成り代わり終了: id++, | |||||
| // ログインユーザ関連 ---------------------------------- | // ログインユーザ関連 ---------------------------------- | ||||
| 顧客ログインユーザ新規登録: id++, | 顧客ログインユーザ新規登録: id++, | ||||
| @@ -29,6 +32,7 @@ export const ApiId = { | |||||
| 店舗新規登録: id++, | 店舗新規登録: id++, | ||||
| デポジット情報取得: id++, | デポジット情報取得: id++, | ||||
| デポジットチャージ: id++, | デポジットチャージ: id++, | ||||
| 店舗設定: id++, | |||||
| // QRサービス券関連------------------------------- | // QRサービス券関連------------------------------- | ||||
| QRサービス券取得: id++, | QRサービス券取得: id++, | ||||
| @@ -1,13 +1,18 @@ | |||||
| import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "api"; | import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "api"; | ||||
| import { getUrl } from "./url"; | import { getUrl } from "./url"; | ||||
| type 店舗 = { | |||||
| shp_id: string; | |||||
| deposit: string; | |||||
| export type 店舗 = { | |||||
| shop_id: string; | |||||
| deposit: number; | |||||
| name: string; | name: string; | ||||
| qr_service_expire_min: number; | |||||
| under_amount_when_create: number; | |||||
| under_amount_when_auth: number; | |||||
| under_amount_when_use: number; | |||||
| }; | }; | ||||
| // -------店舗一覧取得--------------- | // -------店舗一覧取得--------------- | ||||
| export type 店舗一覧取得Request = { | export type 店舗一覧取得Request = { | ||||
| shop_id?: string; | |||||
| email?: string; | email?: string; | ||||
| name?: string; | name?: string; | ||||
| }; | }; | ||||
| @@ -61,9 +66,7 @@ export const デポジット情報取得 = async (param: デポジット情報 | |||||
| // -------デポジットチャージ--------------- | // -------デポジットチャージ--------------- | ||||
| export type デポジットチャージRequest = { | export type デポジットチャージRequest = { | ||||
| shop_id: string; | shop_id: string; | ||||
| name: string; | |||||
| email: string; | |||||
| password: string; | |||||
| amount: string; | |||||
| }; | }; | ||||
| export type デポジットチャージResponse = { | export type デポジットチャージResponse = { | ||||
| data: { | data: { | ||||
| @@ -79,3 +82,25 @@ export const デポジットチャージ = async (param: デポジットチャ | |||||
| }); | }); | ||||
| return res; | return res; | ||||
| }; | }; | ||||
| // -------店舗設定--------------- | |||||
| export type 店舗設定Request = { | |||||
| shop_id: string; | |||||
| qr_service_expire_min: string; | |||||
| under_amount_when_create: string; | |||||
| under_amount_when_auth: string; | |||||
| under_amount_when_use: string; | |||||
| }; | |||||
| export type 店舗設定Response = { | |||||
| data: { | |||||
| shop_id: string; | |||||
| }; | |||||
| } & APICommonResponse; | |||||
| export const 店舗設定 = async (param: 店舗設定Request) => { | |||||
| const res = await request<店舗設定Response>({ | |||||
| url: getUrl(ApiId.店舗設定), | |||||
| method: HttpMethod.POST, | |||||
| data: makeParam(param), | |||||
| }); | |||||
| return res; | |||||
| }; | |||||
| @@ -9,6 +9,9 @@ const urls = { | |||||
| [A.ME]: "me", | [A.ME]: "me", | ||||
| [A.LOGIN]: "login", | [A.LOGIN]: "login", | ||||
| [A.LOGOUT]: "logout", | [A.LOGOUT]: "logout", | ||||
| [A.顧客成り代わり開始]: "role/switch/customer", | |||||
| [A.店舗成り代わり開始]: "role/switch/shop", | |||||
| [A.成り代わり終了]: "role/switch/end", | |||||
| // ログインユーザ関連 ---------------------------------- | // ログインユーザ関連 ---------------------------------- | ||||
| [A.顧客ログインユーザ新規登録]: "login-user/customer/register", | [A.顧客ログインユーザ新規登録]: "login-user/customer/register", | ||||
| @@ -24,6 +27,7 @@ const urls = { | |||||
| [A.店舗新規登録]: "shop/register", | [A.店舗新規登録]: "shop/register", | ||||
| [A.デポジット情報取得]: "shop/deposit", | [A.デポジット情報取得]: "shop/deposit", | ||||
| [A.デポジットチャージ]: "shop/deposit/charge", | [A.デポジットチャージ]: "shop/deposit/charge", | ||||
| [A.店舗設定]: "shop/config", | |||||
| // QRサービス券関連------------------------------- | // QRサービス券関連------------------------------- | ||||
| [A.QRサービス券取得]: "qr-service/get-ticket", | [A.QRサービス券取得]: "qr-service/get-ticket", | ||||
| @@ -3,6 +3,7 @@ import { UserRole } from "./UserRole"; | |||||
| const 共通ルート = [P.LOGIN, P.LOGOUT]; | const 共通ルート = [P.LOGIN, P.LOGOUT]; | ||||
| const 認証後共通ルート = [P.DASHBOARD_ENPTY, P.DASHBOARD_OVERVIEW]; | const 認証後共通ルート = [P.DASHBOARD_ENPTY, P.DASHBOARD_OVERVIEW]; | ||||
| const 管理者顧客共通ルート = [P.成り代わり終了]; | |||||
| const 認可別許可ルート: { | const 認可別許可ルート: { | ||||
| [route: string]: P[]; | [route: string]: P[]; | ||||
| @@ -11,11 +12,21 @@ const 認可別許可ルート: { | |||||
| [UserRole.ADMIN]: [ | [UserRole.ADMIN]: [ | ||||
| ...共通ルート, | ...共通ルート, | ||||
| ...認証後共通ルート, | ...認証後共通ルート, | ||||
| ...管理者顧客共通ルート, | |||||
| P.ログインユーザ_顧客一覧, | P.ログインユーザ_顧客一覧, | ||||
| P.ログインユーザ_顧客新規登録, | P.ログインユーザ_顧客新規登録, | ||||
| ], | ], | ||||
| [UserRole.CUSTOMER]: [...共通ルート, ...認証後共通ルート], | |||||
| [UserRole.CUSTOMER]: [ | |||||
| ...共通ルート, | |||||
| ...認証後共通ルート, | |||||
| ...管理者顧客共通ルート, | |||||
| P.ログインユーザ_店舗一覧, | |||||
| P.ログインユーザ_店舗新規登録, | |||||
| P.店舗一覧, | |||||
| P.店舗新規登録, | |||||
| P.店舗詳細, | |||||
| ], | |||||
| [UserRole.SHOP]: [ | [UserRole.SHOP]: [ | ||||
| ...共通ルート, | ...共通ルート, | ||||
| @@ -1,14 +1,24 @@ | |||||
| import { HasChildren } from "@types"; | import { HasChildren } from "@types"; | ||||
| import { ResultCode } from "api"; | |||||
| import { login as APILogin, logout as APILogout, me } from "api/auth"; | |||||
| import { APICommonResponse, ResultCode } from "api"; | |||||
| import { | |||||
| login as APILogin, | |||||
| logout as APILogout, | |||||
| me, | |||||
| 店舗成り代わり開始, | |||||
| 成り代わり終了, | |||||
| 顧客成り代わり開始, | |||||
| } from "api/auth"; | |||||
| import { UserRole } from "auth/UserRole"; | import { UserRole } from "auth/UserRole"; | ||||
| import useAPICall from "hooks/useAPICall"; | import useAPICall from "hooks/useAPICall"; | ||||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||||
| import { PageID } from "pages"; | |||||
| import { createContext, memo, useEffect, useMemo, useState } from "react"; | import { createContext, memo, useEffect, useMemo, useState } from "react"; | ||||
| import { string } from "yup"; | |||||
| import { getPath } from "routes/path"; | |||||
| type ChangedRole = { | |||||
| type SwitchedUser = { | |||||
| user_id: string; | |||||
| name: string; | |||||
| role: UserRole; | role: UserRole; | ||||
| customerCode: string; | |||||
| }; | }; | ||||
| type Auth = { | type Auth = { | ||||
| initialized: boolean; | initialized: boolean; | ||||
| @@ -20,19 +30,20 @@ type Auth = { | |||||
| role: UserRole; // デフォルトロール | role: UserRole; // デフォルトロール | ||||
| currentRole: UserRole; // 現在のロール | currentRole: UserRole; // 現在のロール | ||||
| currentName: string; // 現在のロール | |||||
| changedRole: ChangedRole | null; | |||||
| shopId: number | null; | |||||
| switchedUser: SwitchedUser | null; | |||||
| isSwitched: boolean; | |||||
| login: (email: string, password: string) => Promise<boolean>; | login: (email: string, password: string) => Promise<boolean>; | ||||
| logout: VoidFunction; | |||||
| logout: () => Promise<void>; | |||||
| me: VoidFunction; | me: VoidFunction; | ||||
| changeRole: (changedRole: ChangedRole | null) => void; | |||||
| switchCustomerRole: (userId: string) => Promise<void>; | |||||
| switchShopRole: (userId: string) => Promise<void>; | |||||
| switchEnd: () => Promise<void>; | |||||
| }; | }; | ||||
| export const AuthContext = createContext<Auth>({ | export const AuthContext = createContext<Auth>({ | ||||
| initialized: false, | initialized: false, | ||||
| authenticated: false, | authenticated: false, | ||||
| id: "", | id: "", | ||||
| @@ -40,14 +51,18 @@ export const AuthContext = createContext<Auth>({ | |||||
| email: "", | email: "", | ||||
| role: UserRole.NONE, | role: UserRole.NONE, | ||||
| changedRole: null, | |||||
| currentRole: UserRole.NONE, | currentRole: UserRole.NONE, | ||||
| shopId: null, | |||||
| currentName: "", | |||||
| switchedUser: null, | |||||
| isSwitched: false, | |||||
| login: async (email: string, password: string) => false, | login: async (email: string, password: string) => false, | ||||
| logout: () => {}, | |||||
| logout: async () => {}, | |||||
| me: () => {}, | me: () => {}, | ||||
| changeRole: (changedRole: ChangedRole | null) => {}, | |||||
| switchCustomerRole: async (userId: string) => {}, | |||||
| switchShopRole: async (userId: string) => {}, | |||||
| switchEnd: async () => {}, | |||||
| }); | }); | ||||
| type Props = HasChildren; | type Props = HasChildren; | ||||
| @@ -57,34 +72,47 @@ function AuthContextProvider({ children }: Props) { | |||||
| const [name, setName] = useState(""); | const [name, setName] = useState(""); | ||||
| const [email, setEmail] = useState(""); | const [email, setEmail] = useState(""); | ||||
| const [role, setRole] = useState<UserRole>(UserRole.NONE); | const [role, setRole] = useState<UserRole>(UserRole.NONE); | ||||
| const [changedRole, setChangedRole] = useState<ChangedRole | null>(null); | |||||
| const [switchedUser, setSwitchedUser] = useState<SwitchedUser | null>(null); | |||||
| const [shopId, setShopId] = useState<number | null>(null); | const [shopId, setShopId] = useState<number | null>(null); | ||||
| const { navigateWhenChanged } = useNavigateCustom(); | |||||
| const authenticated = useMemo(() => { | const authenticated = useMemo(() => { | ||||
| return !!email; | return !!email; | ||||
| }, [email]); | }, [email]); | ||||
| const currentRole = useMemo(() => { | const currentRole = useMemo(() => { | ||||
| if (changedRole) { | |||||
| return changedRole.role; | |||||
| if (switchedUser) { | |||||
| return switchedUser.role; | |||||
| } | } | ||||
| return role; | return role; | ||||
| }, [changedRole, role]); | |||||
| }, [switchedUser, role]); | |||||
| const currentName = useMemo(() => { | |||||
| if (switchedUser) { | |||||
| return switchedUser.name; | |||||
| } | |||||
| return name; | |||||
| }, [switchedUser, name]); | |||||
| const { callAPI: callMe } = useAPICall({ | const { callAPI: callMe } = useAPICall({ | ||||
| apiMethod: me, | apiMethod: me, | ||||
| backDrop: true, | backDrop: true, | ||||
| onSuccess: ({ data }) => { | onSuccess: ({ data }) => { | ||||
| console.log("認証有"); | |||||
| setInitialized(true); | setInitialized(true); | ||||
| setId(data.id); | setId(data.id); | ||||
| setEmail(data.email); | setEmail(data.email); | ||||
| setName(data.name); | setName(data.name); | ||||
| setRole(data.role); | setRole(data.role); | ||||
| if (data.switched_user_id) { | |||||
| setSwitchedUser({ | |||||
| name: data.switched_name ?? "", | |||||
| user_id: data.switched_user_id ?? "", | |||||
| role: data.switched_role ?? UserRole.NONE, | |||||
| }); | |||||
| } | |||||
| }, | }, | ||||
| onFailed: () => { | onFailed: () => { | ||||
| console.log("認証無"); | |||||
| clear(); | clear(); | ||||
| setInitialized(true); | setInitialized(true); | ||||
| }, | }, | ||||
| @@ -100,32 +128,78 @@ function AuthContextProvider({ children }: Props) { | |||||
| const { callAPI: callLogout } = useAPICall({ | const { callAPI: callLogout } = useAPICall({ | ||||
| apiMethod: APILogout, | apiMethod: APILogout, | ||||
| backDrop: true, | |||||
| onSuccess: () => { | onSuccess: () => { | ||||
| clear(); | clear(); | ||||
| }, | }, | ||||
| }); | }); | ||||
| const { callAPI: call顧客成り代わり開始 } = useAPICall({ | |||||
| apiMethod: 顧客成り代わり開始, | |||||
| backDrop: true, | |||||
| onSuccess: ({ data }) => { | |||||
| const role = data.role as UserRole; | |||||
| setSwitchedUser({ | |||||
| ...data, | |||||
| role, | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| const { callAPI: call店舗成り代わり開始 } = useAPICall({ | |||||
| apiMethod: 店舗成り代わり開始, | |||||
| backDrop: true, | |||||
| onSuccess: ({ data }) => { | |||||
| const role = data.role as UserRole; | |||||
| setSwitchedUser({ | |||||
| ...data, | |||||
| role, | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| const { callAPI: call成り代わり終了 } = useAPICall({ | |||||
| apiMethod: 成り代わり終了, | |||||
| backDrop: true, | |||||
| onSuccess: () => { | |||||
| setSwitchedUser(null); | |||||
| }, | |||||
| }); | |||||
| const clear = () => { | const clear = () => { | ||||
| setId(""); | setId(""); | ||||
| setName(""); | setName(""); | ||||
| setEmail(""); | setEmail(""); | ||||
| setRole(UserRole.NONE); | setRole(UserRole.NONE); | ||||
| setChangedRole(null); | |||||
| setSwitchedUser(null); | |||||
| setShopId(null); | setShopId(null); | ||||
| }; | }; | ||||
| const login = async (email: string, password: string) => { | const login = async (email: string, password: string) => { | ||||
| const res = await callLogin({ email, password }); | |||||
| const res: APICommonResponse | null = await callLogin({ email, password }); | |||||
| return res?.result === ResultCode.SUCCESS; | return res?.result === ResultCode.SUCCESS; | ||||
| }; | }; | ||||
| const logout = () => { | |||||
| callLogout({}); | |||||
| const logout = async () => { | |||||
| await callLogout({}); | |||||
| console.info("ログアウト"); | console.info("ログアウト"); | ||||
| }; | }; | ||||
| const changeRole = (changedRole: ChangedRole | null) => { | |||||
| setChangedRole(changedRole); | |||||
| const switchCustomerRole = async (user_id: string) => { | |||||
| await call顧客成り代わり開始({ | |||||
| user_id, | |||||
| }); | |||||
| navigateWhenChanged(getPath(PageID.DASHBOARD_OVERVIEW)); | |||||
| }; | }; | ||||
| const switchShopRole = async (user_id: string) => { | |||||
| await call店舗成り代わり開始({ user_id }); | |||||
| navigateWhenChanged(getPath(PageID.DASHBOARD_OVERVIEW)); | |||||
| }; | |||||
| const switchEnd = async () => { | |||||
| await call成り代わり終了({}); | |||||
| navigateWhenChanged(getPath(PageID.DASHBOARD_OVERVIEW)); | |||||
| }; | |||||
| const isSwitched = useMemo(() => { | |||||
| return switchedUser !== null; | |||||
| }, [switchedUser]); | |||||
| const meFetch = () => { | const meFetch = () => { | ||||
| callMe({}); | callMe({}); | ||||
| @@ -147,14 +221,17 @@ function AuthContextProvider({ children }: Props) { | |||||
| role, | role, | ||||
| currentRole, | currentRole, | ||||
| changedRole, | |||||
| shopId, | |||||
| currentName, | |||||
| switchedUser, | |||||
| // Func | // Func | ||||
| login, | login, | ||||
| logout, | logout, | ||||
| me: meFetch, | me: meFetch, | ||||
| changeRole, | |||||
| switchCustomerRole, | |||||
| switchShopRole, | |||||
| switchEnd, | |||||
| isSwitched, | |||||
| }} | }} | ||||
| > | > | ||||
| {children} | {children} | ||||
| @@ -0,0 +1,102 @@ | |||||
| import { HasChildren } from "@types"; | |||||
| import { 店舗, 店舗一覧取得 } from "api/shop"; | |||||
| import useAPICall from "hooks/useAPICall"; | |||||
| import useDashboard from "hooks/useDashBoard"; | |||||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||||
| import useShopTabs from "layouts/dashbord/tab/ShopTabs"; | |||||
| import { PageID, TabID } from "pages"; | |||||
| import { createContext, useContext, useEffect, useState } from "react"; | |||||
| import { useParams } from "react-router-dom"; | |||||
| import { getPath } from "routes/path"; | |||||
| type Context = { | |||||
| shop: 店舗 | null; | |||||
| fetch: () => Promise<void>; | |||||
| moveToMain: VoidFunction; | |||||
| }; | |||||
| export const 店舗詳細Context = createContext<Context>({ | |||||
| shop: null, | |||||
| fetch: async () => {}, | |||||
| moveToMain: () => {}, | |||||
| }); | |||||
| type Props = HasChildren; | |||||
| function 店舗詳細ContextProvider({ children }: Props) { | |||||
| const { shopId: paramShopId } = useParams(); | |||||
| const [shop, setShop] = useState<店舗 | null>(null); | |||||
| const { success, error } = useSnackbarCustom(); | |||||
| const { navigateWhenChanged } = useNavigateCustom(); | |||||
| const { callAPI: call店舗一覧取得 } = useAPICall({ | |||||
| apiMethod: 店舗一覧取得, | |||||
| backDrop: true, | |||||
| onSuccess: ({ data }) => { | |||||
| if (data.list.length !== 1) { | |||||
| error("データ取得失敗"); | |||||
| navigateWhenChanged(getPath(PageID.店舗一覧)); | |||||
| return; | |||||
| } | |||||
| setShop(data.list[0]); | |||||
| }, | |||||
| onFailed: () => { | |||||
| error("データ取得失敗"); | |||||
| navigateWhenChanged(getPath(PageID.店舗一覧)); | |||||
| }, | |||||
| }); | |||||
| const fetch = async () => { | |||||
| await call店舗一覧取得({ | |||||
| shop_id: paramShopId, | |||||
| }); | |||||
| }; | |||||
| const moveToMain = () => { | |||||
| navigateWhenChanged( | |||||
| getPath([PageID.店舗詳細, TabID.店舗詳細_メイン], { | |||||
| query: { | |||||
| shopId: shop?.shop_id ?? "BBBBB", | |||||
| }, | |||||
| }) | |||||
| ); | |||||
| }; | |||||
| const { setHeaderTitle } = useDashboard(); | |||||
| useEffect(() => { | |||||
| setHeaderTitle("店舗詳細"); | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| fetch(); | |||||
| }, []); | |||||
| return ( | |||||
| <店舗詳細Context.Provider | |||||
| value={{ | |||||
| shop, | |||||
| fetch, | |||||
| moveToMain, | |||||
| }} | |||||
| > | |||||
| <TabInit /> | |||||
| {shop && children} | |||||
| </店舗詳細Context.Provider> | |||||
| ); | |||||
| } | |||||
| function TabInit() { | |||||
| const { setTabs, tabId } = useDashboard(); | |||||
| const { shop } = useContext(店舗詳細Context); | |||||
| const { element } = useShopTabs(); | |||||
| useEffect(() => { | |||||
| setTabs(element); | |||||
| }, [shop, tabId]); | |||||
| return null; | |||||
| } | |||||
| export default 店舗詳細ContextProvider; | |||||
| @@ -11,8 +11,10 @@ export default function useDashboard(pageId?: PageID, tabId?: TabID) { | |||||
| } | } | ||||
| if (tabId) { | if (tabId) { | ||||
| context.setTabId(tabId); | context.setTabId(tabId); | ||||
| } else { | |||||
| context.setTabId(TabID.NONE); | |||||
| } | } | ||||
| }, []); | |||||
| }, [pageId, tabId]); | |||||
| return context; | return context; | ||||
| } | } | ||||
| @@ -17,18 +17,19 @@ import { ページアクセス許可判定 } from "auth/route"; | |||||
| import useAuth from "hooks/useAuth"; | import useAuth from "hooks/useAuth"; | ||||
| import useNavigateCustom from "hooks/useNavigateCustom"; | import useNavigateCustom from "hooks/useNavigateCustom"; | ||||
| import usePage from "hooks/usePage"; | import usePage from "hooks/usePage"; | ||||
| import { PageID } from "pages"; | |||||
| import { PageID, TabID } from "pages"; | |||||
| import * as React from "react"; | import * as React from "react"; | ||||
| import { PathOption, getPath } from "routes/path"; | |||||
| import { PathKey, PathOption, getPath } from "routes/path"; | |||||
| type Group = { | type Group = { | ||||
| label: string; | label: string; | ||||
| children: SubGroup[]; | children: SubGroup[]; | ||||
| }; | }; | ||||
| type SubGroup = { | type SubGroup = { | ||||
| label: string; | |||||
| label: string | React.ReactNode; | |||||
| icon: React.ReactNode; | icon: React.ReactNode; | ||||
| children?: Child[]; | children?: Child[]; | ||||
| whenIsSwitched?: boolean; | |||||
| // 子要素を持たない場合は設定 | // 子要素を持たない場合は設定 | ||||
| id?: PageID; | id?: PageID; | ||||
| @@ -37,7 +38,7 @@ type SubGroup = { | |||||
| type Child = { | type Child = { | ||||
| label: string; | label: string; | ||||
| id: PageID; | |||||
| id: PathKey; | |||||
| option?: PathOption; | option?: PathOption; | ||||
| }; | }; | ||||
| @@ -65,7 +66,7 @@ const itemCategory = { | |||||
| export default function Navigator(props: DrawerProps) { | export default function Navigator(props: DrawerProps) { | ||||
| const { ...other } = props; | const { ...other } = props; | ||||
| const { name } = useAuth(); | |||||
| const { currentName, isSwitched } = useAuth(); | |||||
| const { navigateWhenChanged } = useNavigateCustom(); | const { navigateWhenChanged } = useNavigateCustom(); | ||||
| @@ -94,13 +95,23 @@ export default function Navigator(props: DrawerProps) { | |||||
| ], | ], | ||||
| }, | }, | ||||
| { | { | ||||
| label: "QRサービス券", | |||||
| label: "店舗管理", | |||||
| children: [ | children: [ | ||||
| { | { | ||||
| label: "券発行用QRコード", | |||||
| label: "店舗新規登録", | |||||
| icon: <ArticleIcon />, | |||||
| id: PageID.店舗新規登録, | |||||
| }, | |||||
| { | |||||
| label: "店舗一覧", | |||||
| icon: <ArticleIcon />, | icon: <ArticleIcon />, | ||||
| id: PageID.サービス券発行用QRコード, | |||||
| id: PageID.店舗一覧, | |||||
| }, | }, | ||||
| ], | |||||
| }, | |||||
| { | |||||
| label: "QRサービス券", | |||||
| children: [ | |||||
| { | { | ||||
| label: "利用履歴", | label: "利用履歴", | ||||
| icon: <ArticleIcon />, | icon: <ArticleIcon />, | ||||
| @@ -111,6 +122,12 @@ export default function Navigator(props: DrawerProps) { | |||||
| { | { | ||||
| label: "アカウント", | label: "アカウント", | ||||
| children: [ | children: [ | ||||
| { | |||||
| label: "成り代わり終了", | |||||
| icon: <SettingsIcon />, | |||||
| id: PageID.成り代わり終了, | |||||
| whenIsSwitched: true, | |||||
| }, | |||||
| { label: "ログアウト", icon: <SettingsIcon />, id: PageID.LOGOUT }, | { label: "ログアウト", icon: <SettingsIcon />, id: PageID.LOGOUT }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -131,7 +148,8 @@ export default function Navigator(props: DrawerProps) { | |||||
| </ListItemIcon> | </ListItemIcon> | ||||
| <ListItemText>{}</ListItemText> | <ListItemText>{}</ListItemText> | ||||
| <ListItemText> | <ListItemText> | ||||
| <Typography>{name} 様</Typography> | |||||
| <Typography>{currentName} 様</Typography> | |||||
| {!!isSwitched && <Typography color="red">成り代わり中</Typography>} | |||||
| </ListItemText> | </ListItemText> | ||||
| </ListItem> | </ListItem> | ||||
| @@ -144,11 +162,12 @@ export default function Navigator(props: DrawerProps) { | |||||
| } | } | ||||
| function Group(group: Group) { | function Group(group: Group) { | ||||
| const { currentRole } = useAuth(); | |||||
| const { currentRole, isSwitched } = useAuth(); | |||||
| const { label, children } = group; | const { label, children } = group; | ||||
| const elements = children | const elements = children | ||||
| .filter(({ id }) => ページアクセス許可判定(currentRole, id ?? -1)) | .filter(({ id }) => ページアクセス許可判定(currentRole, id ?? -1)) | ||||
| .filter(({ whenIsSwitched }) => whenIsSwitched !== true || isSwitched) | |||||
| .map((ele, index) => <SubGroup {...ele} key={index} />); | .map((ele, index) => <SubGroup {...ele} key={index} />); | ||||
| if (elements.length === 0) return null; | if (elements.length === 0) return null; | ||||
| @@ -1,31 +0,0 @@ | |||||
| import { Tabs } from "@mui/material"; | |||||
| import { TabProps, useTab } from "./tabutil"; | |||||
| import { PageID, TabID } from "pages"; | |||||
| import { getPath } from "routes/path"; | |||||
| const tabs: TabProps[] = [ | |||||
| { | |||||
| label: "一覧", | |||||
| tabId: TabID.NONE, | |||||
| }, | |||||
| { | |||||
| label: "詳細", | |||||
| tabId: TabID.A, | |||||
| }, | |||||
| ]; | |||||
| export default function ContractTabs() { | |||||
| const { elements, getTabIndex } = useTab(tabs); | |||||
| return ( | |||||
| <Tabs | |||||
| value={getTabIndex} | |||||
| textColor="inherit" | |||||
| // scrollButtons | |||||
| // allowScrollButtonsMobile | |||||
| variant="scrollable" | |||||
| > | |||||
| {elements} | |||||
| </Tabs> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,49 @@ | |||||
| import { Tabs } from "@mui/material"; | |||||
| import { TabProps, useTab } from "./tabutil"; | |||||
| import { PageID, TabID } from "pages"; | |||||
| import { getPath } from "routes/path"; | |||||
| import { useContext, useEffect, useMemo } from "react"; | |||||
| import { 店舗詳細Context } from "contexts/page/dashboard/shop/店舗詳細Context"; | |||||
| export default function useShopTabs() { | |||||
| const { shop } = useContext(店舗詳細Context); | |||||
| const tabs: TabProps[] = useMemo(() => { | |||||
| return [ | |||||
| { | |||||
| label: "詳細", | |||||
| tabId: TabID.店舗詳細_メイン, | |||||
| path: getPath([PageID.店舗詳細, TabID.店舗詳細_メイン], { | |||||
| query: { | |||||
| shopId: shop?.shop_id ?? "aaaaa", | |||||
| }, | |||||
| }), | |||||
| }, | |||||
| { | |||||
| label: "設定", | |||||
| tabId: TabID.店舗詳細_設定, | |||||
| path: getPath([PageID.店舗詳細, TabID.店舗詳細_設定], { | |||||
| query: { | |||||
| shopId: shop?.shop_id ?? "aaaaa", | |||||
| }, | |||||
| }), | |||||
| }, | |||||
| ]; | |||||
| }, [shop]); | |||||
| const { elements, getTabIndex } = useTab(tabs); | |||||
| return { | |||||
| element: ( | |||||
| <Tabs | |||||
| value={getTabIndex} | |||||
| textColor="inherit" | |||||
| // scrollButtons | |||||
| // allowScrollButtonsMobile | |||||
| variant="scrollable" | |||||
| > | |||||
| {elements} | |||||
| </Tabs> | |||||
| ), | |||||
| }; | |||||
| } | |||||
| @@ -1,30 +1,31 @@ | |||||
| import { PageID, TabID } from "pages"; | import { PageID, TabID } from "pages"; | ||||
| import usePage from "hooks/usePage"; | import usePage from "hooks/usePage"; | ||||
| import { useMemo } from "react"; | |||||
| import { useEffect, useMemo } from "react"; | |||||
| import { Tab } from "."; | import { Tab } from "."; | ||||
| import { getPath } from "routes/path"; | import { getPath } from "routes/path"; | ||||
| import userEvent from "@testing-library/user-event"; | |||||
| export type TabProps = { | export type TabProps = { | ||||
| label: string; | label: string; | ||||
| tabId: TabID; | tabId: TabID; | ||||
| path: string; | |||||
| }; | }; | ||||
| export function useTab(tabs: TabProps[]) { | export function useTab(tabs: TabProps[]) { | ||||
| const { pageId, tabId } = usePage(); | const { pageId, tabId } = usePage(); | ||||
| const elements = useMemo(() => { | const elements = useMemo(() => { | ||||
| return tabs.map(({ label, tabId: elementTabId }, index) => { | |||||
| const path = getPath([pageId, tabId]); | |||||
| return tabs.map(({ label, path }, index) => { | |||||
| return <Tab {...{ label, navigate: path, key: index }} />; | return <Tab {...{ label, navigate: path, key: index }} />; | ||||
| }); | }); | ||||
| }, [tabs]); | |||||
| }, [pageId, tabId, tabs]); | |||||
| const getTabIndex = useMemo(() => { | const getTabIndex = useMemo(() => { | ||||
| return ( | |||||
| tabs.findIndex((tab) => { | |||||
| return tab.tabId === tabId; | |||||
| }) ?? 0 | |||||
| ); | |||||
| const index = tabs.findIndex((tab) => { | |||||
| return tab.tabId === tabId; | |||||
| }); | |||||
| return index < 0 ? 0 : index; | |||||
| }, [tabId, tabs]); | }, [tabId, tabs]); | ||||
| return { | return { | ||||
| @@ -14,7 +14,7 @@ export default function SimpleLayout() { | |||||
| <Grid container> | <Grid container> | ||||
| <Grid item xs /> | <Grid item xs /> | ||||
| <Grid item xs={5} textAlign="center"> | <Grid item xs={5} textAlign="center"> | ||||
| MyPage | |||||
| HT Dashboard | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs /> | <Grid item xs /> | ||||
| </Grid> | </Grid> | ||||
| @@ -6,16 +6,21 @@ import { useEffect } from "react"; | |||||
| import { getPath } from "routes/path"; | import { getPath } from "routes/path"; | ||||
| export default function Logout() { | export default function Logout() { | ||||
| const { logout } = useAuth(); | |||||
| const { info, success } = useSnackbarCustom(); | |||||
| const { authenticated, logout } = useAuth(); | |||||
| const { info } = useSnackbarCustom(); | |||||
| const { navigateWhenChanged } = useNavigateCustom(); | const { navigateWhenChanged } = useNavigateCustom(); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| logout(); | logout(); | ||||
| info("ログアウトしました"); | info("ログアウトしました"); | ||||
| navigateWhenChanged(getPath(PageID.LOGIN)); | |||||
| }, []); | }, []); | ||||
| useEffect(() => { | |||||
| if (authenticated === false) { | |||||
| navigateWhenChanged(getPath(PageID.LOGIN)); | |||||
| } | |||||
| }, [authenticated]); | |||||
| return null; | return null; | ||||
| } | } | ||||
| @@ -50,7 +50,6 @@ export default function SearchBox({ table }: CommonProps) { | |||||
| const fetch = async (data: Dictionary) => { | const fetch = async (data: Dictionary) => { | ||||
| const sendData = makeSendData(data); | const sendData = makeSendData(data); | ||||
| console.log({ sendData }); | |||||
| if (!isEqual(sendData, lastSendSearchCondition)) { | if (!isEqual(sendData, lastSendSearchCondition)) { | ||||
| setLastSendSearchCondition(sendData); | setLastSendSearchCondition(sendData); | ||||
| call顧客一覧取得(sendData); | call顧客一覧取得(sendData); | ||||
| @@ -58,7 +57,6 @@ export default function SearchBox({ table }: CommonProps) { | |||||
| }; | }; | ||||
| useEffect(() => { | useEffect(() => { | ||||
| console.log({ initialized, condition }); | |||||
| if (initialized) { | if (initialized) { | ||||
| fetch(condition); | fetch(condition); | ||||
| } | } | ||||
| @@ -11,6 +11,8 @@ import { 運営会社ログインユーザ } from "api/login-user"; | |||||
| import TableHeadCustom, { | import TableHeadCustom, { | ||||
| HeadLabelProps, | HeadLabelProps, | ||||
| } from "components/table/TableHeadCustom"; | } from "components/table/TableHeadCustom"; | ||||
| import useAuth from "hooks/useAuth"; | |||||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||||
| import { UseTableReturn } from "hooks/useTable"; | import { UseTableReturn } from "hooks/useTable"; | ||||
| type CommonProps = { | type CommonProps = { | ||||
| @@ -81,8 +83,15 @@ type RowProps = { | |||||
| data: 運営会社ログインユーザ; | data: 運営会社ログインユーザ; | ||||
| }; | }; | ||||
| function Row({ data }: RowProps) { | function Row({ data }: RowProps) { | ||||
| const { switchCustomerRole } = useAuth(); | |||||
| const { info } = useSnackbarCustom(); | |||||
| const handleClick = () => { | |||||
| switchCustomerRole(data.id); | |||||
| info("成り代わり開始しました"); | |||||
| }; | |||||
| return ( | return ( | ||||
| <TableRow hover sx={{ cursor: "pointer" }}> | |||||
| <TableRow hover sx={{ cursor: "pointer" }} onClick={handleClick}> | |||||
| <TableCell>{data.name}</TableCell> | <TableCell>{data.name}</TableCell> | ||||
| <TableCell>{data.email}</TableCell> | <TableCell>{data.email}</TableCell> | ||||
| <TableCell> | <TableCell> | ||||
| @@ -0,0 +1,81 @@ | |||||
| import { Box, Grid, Stack, Typography } from "@mui/material"; | |||||
| import { Dictionary } from "@types"; | |||||
| import { 店舗, 店舗一覧取得 } from "api/shop"; | |||||
| 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; | |||||
| }; | |||||
| 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: "", | |||||
| }, | |||||
| }); | |||||
| 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) { | |||||
| form.setValue("name", get("name")); | |||||
| } | |||||
| }, [initialized, condition]); | |||||
| useEffect(() => { | |||||
| if (initialized) { | |||||
| fetch(condition); | |||||
| } | |||||
| }, [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> | |||||
| </Box> | |||||
| </FormProvider> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,113 @@ | |||||
| import { | |||||
| Box, | |||||
| Table, | |||||
| TableBody, | |||||
| TableCell, | |||||
| TableContainer, | |||||
| TablePagination, | |||||
| TableRow, | |||||
| } from "@mui/material"; | |||||
| import { 運営会社ログインユーザ } from "api/login-user"; | |||||
| import { 店舗 } from "api/shop"; | |||||
| import TableHeadCustom, { | |||||
| HeadLabelProps, | |||||
| } from "components/table/TableHeadCustom"; | |||||
| import useAuth from "hooks/useAuth"; | |||||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||||
| import { UseTableReturn } from "hooks/useTable"; | |||||
| import { PageID, TabID } from "pages"; | |||||
| import { useMemo } from "react"; | |||||
| import { getPath } from "routes/path"; | |||||
| import { numberFormat } from "utils/string"; | |||||
| type CommonProps = { | |||||
| table: UseTableReturn<店舗>; | |||||
| }; | |||||
| export default function TableBox({ table }: CommonProps) { | |||||
| const TABLE_HEAD: HeadLabelProps[] = [ | |||||
| { id: "name", label: "名前", align: "left", needSort: false }, | |||||
| { id: "deposit", 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: 1200 }} 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: { deposit, name, shop_id } }: RowProps) { | |||||
| const { info } = useSnackbarCustom(); | |||||
| const { navigateWhenChanged } = useNavigateCustom(); | |||||
| const handleClick = () => { | |||||
| navigateWhenChanged( | |||||
| getPath([PageID.店舗詳細, TabID.店舗詳細_メイン], { | |||||
| query: { | |||||
| shopId: shop_id, | |||||
| }, | |||||
| }) | |||||
| ); | |||||
| }; | |||||
| const formatDeposit = useMemo(() => { | |||||
| return numberFormat(deposit) + "円"; | |||||
| }, [deposit]); | |||||
| return ( | |||||
| <TableRow hover sx={{ cursor: "pointer" }} onClick={handleClick}> | |||||
| <TableCell>{name}</TableCell> | |||||
| <TableCell>{formatDeposit}</TableCell> | |||||
| </TableRow> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,35 @@ | |||||
| import { Box } from "@mui/material"; | |||||
| import { 店舗 } from "api/shop"; | |||||
| 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 顧客一覧() { | |||||
| 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,82 @@ | |||||
| import { yupResolver } from "@hookform/resolvers/yup"; | |||||
| import { Box, Button, Stack, Typography } from "@mui/material"; | |||||
| import { 店舗新規登録 } from "api/shop"; | |||||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||||
| 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 } from "react"; | |||||
| import { useForm } from "react-hook-form"; | |||||
| import { getPath } from "routes/path"; | |||||
| import { object, string } from "yup"; | |||||
| type FormProps = { | |||||
| name: string; | |||||
| }; | |||||
| export default function 店舗新規登録_() { | |||||
| const { setHeaderTitle, setTabs } = useDashboard( | |||||
| PageID.店舗新規登録, | |||||
| TabID.NONE | |||||
| ); | |||||
| const { success, error } = useSnackbarCustom(); | |||||
| const { navigateWhenChanged } = useNavigateCustom(); | |||||
| const form = useForm<FormProps>({ | |||||
| defaultValues: { | |||||
| name: "", | |||||
| }, | |||||
| resolver: yupResolver( | |||||
| object().shape({ | |||||
| name: string().required("必須項目です"), | |||||
| }) | |||||
| ), | |||||
| }); | |||||
| const { callAPI: call店舗新規登録 } = useAPICall({ | |||||
| apiMethod: 店舗新規登録, | |||||
| form, | |||||
| backDrop: true, | |||||
| onSuccess: ({ data }, sendData) => { | |||||
| success("登録しました"); | |||||
| navigateWhenChanged( | |||||
| getPath(PageID.店舗一覧), | |||||
| new URLSearchParams({ name: sendData.name }) | |||||
| ); | |||||
| }, | |||||
| onFailed: () => { | |||||
| error("失敗しました"); | |||||
| }, | |||||
| }); | |||||
| const handleSubmit = (data: FormProps) => { | |||||
| call店舗新規登録({ | |||||
| ...data, | |||||
| }); | |||||
| }; | |||||
| useEffect(() => { | |||||
| setHeaderTitle("店舗新規登録"); | |||||
| setTabs(null); | |||||
| }, []); | |||||
| return ( | |||||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||||
| <Box> | |||||
| <Stack> | |||||
| <Box> | |||||
| <Typography>名称</Typography> | |||||
| <RHFTextField name="name" /> | |||||
| </Box> | |||||
| <StackRow> | |||||
| <Button type="submit">登録</Button> | |||||
| </StackRow> | |||||
| </Stack> | |||||
| </Box> | |||||
| </FormProvider> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,25 @@ | |||||
| import { Box, Stack } from "@mui/material"; | |||||
| import useDashboard from "hooks/useDashBoard"; | |||||
| import { PageID, TabID } from "pages"; | |||||
| import デポジットチャージ from "./デポジットチャージ"; | |||||
| import 詳細情報 from "./詳細情報"; | |||||
| export default function 店舗詳細() { | |||||
| const {} = useDashboard(PageID.店舗詳細, TabID.店舗詳細_メイン); | |||||
| return <Page />; | |||||
| } | |||||
| function Page() { | |||||
| return ( | |||||
| <Box> | |||||
| <Stack spacing={2}> | |||||
| <詳細情報 /> | |||||
| <デポジットチャージ /> | |||||
| {/* 詳細情報テーブル */} | |||||
| {/* チャージ */} | |||||
| {/* 利用履歴 */} | |||||
| </Stack> | |||||
| </Box> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,84 @@ | |||||
| 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 useAPICall from "hooks/useAPICall"; | |||||
| 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"; | |||||
| type FormProps = { | |||||
| amount: string; | |||||
| }; | |||||
| export default function デポジットチャージ_() { | |||||
| const { shop, fetch } = useContext(店舗詳細Context); | |||||
| const { success, error } = useSnackbarCustom(); | |||||
| const form = useForm<FormProps>({ | |||||
| defaultValues: { | |||||
| amount: "", | |||||
| }, | |||||
| resolver: yupResolver( | |||||
| object().shape({ | |||||
| amount: number() | |||||
| .typeError("数値を入力してください") | |||||
| .min(1, "1円-10万円まで入力できます") | |||||
| .max(100000, "1円-10万円まで入力できます"), | |||||
| }) | |||||
| ), | |||||
| }); | |||||
| const { callAPI: callデポジットチャージ } = useAPICall({ | |||||
| apiMethod: デポジットチャージ, | |||||
| backDrop: true, | |||||
| form, | |||||
| onSuccess: () => { | |||||
| success("チャージしました"); | |||||
| fetch(); | |||||
| form.setValue("amount", ""); | |||||
| }, | |||||
| onFailed: () => { | |||||
| error("失敗しました"); | |||||
| }, | |||||
| }); | |||||
| const handleSubmit = (data: FormProps) => { | |||||
| if (shop === null) return; | |||||
| callデポジットチャージ({ | |||||
| shop_id: shop.shop_id, | |||||
| amount: data.amount, | |||||
| }); | |||||
| }; | |||||
| 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> | |||||
| <RHFTextField type="number" name="amount" /> | |||||
| <Typography>円</Typography> | |||||
| </StackRow> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </StackRow> | |||||
| <StackRow> | |||||
| <Button type="submit" variant="contained"> | |||||
| チャージ実行 | |||||
| </Button> | |||||
| </StackRow> | |||||
| </Stack> | |||||
| </Box> | |||||
| </Card> | |||||
| </FormProvider> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,25 @@ | |||||
| import { Box, Stack } from "@mui/material"; | |||||
| import { 店舗詳細Context } from "contexts/page/dashboard/shop/店舗詳細Context"; | |||||
| import useDashboard from "hooks/useDashBoard"; | |||||
| import ShopTabs from "layouts/dashbord/tab/ShopTabs"; | |||||
| import { PageID, TabID } from "pages"; | |||||
| import { useContext, useEffect } from "react"; | |||||
| import 設定 from "./設定"; | |||||
| export default function 店舗詳細設定() { | |||||
| const { setHeaderTitle, setTabs } = useDashboard( | |||||
| PageID.店舗詳細, | |||||
| TabID.店舗詳細_設定 | |||||
| ); | |||||
| return <Page />; | |||||
| } | |||||
| function Page() { | |||||
| return ( | |||||
| <Box> | |||||
| <Stack spacing={2}> | |||||
| <設定 /> | |||||
| </Stack> | |||||
| </Box> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,120 @@ | |||||
| 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> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,120 @@ | |||||
| 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> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,32 @@ | |||||
| 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"; | |||||
| export default function 詳細情報() { | |||||
| const { shop } = useContext(店舗詳細Context); | |||||
| const listData = useMemo(() => { | |||||
| return [ | |||||
| { | |||||
| title: "名称", | |||||
| value: shop?.name ?? "", | |||||
| }, | |||||
| { | |||||
| title: "デポジット", | |||||
| value: numberFormat(shop?.deposit ?? 0) + "円", | |||||
| }, | |||||
| ]; | |||||
| }, [shop]); | |||||
| return ( | |||||
| <Card elevation={1} sx={{ p: 2 }}> | |||||
| <Stack spacing={2}> | |||||
| <Typography variant="h6">基本情報</Typography> | |||||
| <Box> | |||||
| <SimpleDataList data={listData} /> | |||||
| </Box> | |||||
| </Stack> | |||||
| </Card> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,20 @@ | |||||
| import useAuth from "hooks/useAuth"; | |||||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||||
| import { PageID } from "pages"; | |||||
| import { useEffect } from "react"; | |||||
| import { getPath } from "routes/path"; | |||||
| export default function 成り代わり終了() { | |||||
| const { switchEnd } = useAuth(); | |||||
| const { navigateWhenChanged } = useNavigateCustom(); | |||||
| const { info } = useSnackbarCustom(); | |||||
| useEffect(() => { | |||||
| switchEnd(); | |||||
| navigateWhenChanged(getPath(PageID.LOGIN)); | |||||
| info("成り代わり終了しました"); | |||||
| }, [switchEnd, navigateWhenChanged]); | |||||
| return null; | |||||
| } | |||||
| @@ -5,6 +5,7 @@ export const PageID = { | |||||
| // 認証関連 ---------------------------------- | // 認証関連 ---------------------------------- | ||||
| LOGIN: id++, | LOGIN: id++, | ||||
| LOGOUT: id++, | LOGOUT: id++, | ||||
| 成り代わり終了: id++, | |||||
| // ダッシュボード系 START ---------------------------------- | // ダッシュボード系 START ---------------------------------- | ||||
| DASHBOARD_ENPTY: id++, | DASHBOARD_ENPTY: id++, | ||||
| @@ -17,6 +18,12 @@ export const PageID = { | |||||
| ログインユーザ_顧客一覧: id++, | ログインユーザ_顧客一覧: id++, | ||||
| ログインユーザ_顧客新規登録: id++, | ログインユーザ_顧客新規登録: id++, | ||||
| ログインユーザ_店舗一覧: id++, | ログインユーザ_店舗一覧: id++, | ||||
| ログインユーザ_店舗新規登録: id++, | |||||
| // 店舗管理 | |||||
| 店舗一覧: id++, | |||||
| 店舗新規登録: id++, | |||||
| 店舗詳細: id++, | |||||
| サービス券発行用QRコード: id++, | サービス券発行用QRコード: id++, | ||||
| サービス券利用履歴: id++, | サービス券利用履歴: id++, | ||||
| @@ -33,7 +40,9 @@ export type PageID = (typeof PageID)[keyof typeof PageID]; | |||||
| id = 0; | id = 0; | ||||
| export const TabID = { | export const TabID = { | ||||
| NONE: id++, | NONE: id++, | ||||
| A: id++, | |||||
| 店舗詳細_メイン: id++, | |||||
| 店舗詳細_設定: id++, | |||||
| } as const; | } as const; | ||||
| export type TabID = (typeof TabID)[keyof typeof TabID]; | export type TabID = (typeof TabID)[keyof typeof TabID]; | ||||
| @@ -7,6 +7,7 @@ import { RouteObject, useRoutes } from "react-router-dom"; | |||||
| import { getRoute } from "./path"; | import { getRoute } from "./path"; | ||||
| import DashboardRoutes from "./sub/dashboard"; | import DashboardRoutes from "./sub/dashboard"; | ||||
| import QRServiceSimpleLayout from "layouts/qr-service"; | import QRServiceSimpleLayout from "layouts/qr-service"; | ||||
| import AuthGuard from "guards/AuthGuard"; | |||||
| export const Loadable = (Component: ElementType) => (props: any) => { | export const Loadable = (Component: ElementType) => (props: any) => { | ||||
| return ( | return ( | ||||
| @@ -15,6 +16,9 @@ export const Loadable = (Component: ElementType) => (props: any) => { | |||||
| </Suspense> | </Suspense> | ||||
| ); | ); | ||||
| }; | }; | ||||
| export const Load = (path: string) => { | |||||
| return Loadable(lazy(() => import(path))); | |||||
| }; | |||||
| const CommonRoutes = (): RouteObject => ({ | const CommonRoutes = (): RouteObject => ({ | ||||
| element: <SimpleLayout />, | element: <SimpleLayout />, | ||||
| @@ -58,8 +62,19 @@ export function Routes() { | |||||
| element: <Page403 />, | element: <Page403 />, | ||||
| }, | }, | ||||
| { | { | ||||
| path: "*", | |||||
| element: initialized ? <Page404 /> : <LoadingScreen />, | |||||
| element: <SimpleLayout />, | |||||
| children: [ | |||||
| { | |||||
| path: "*", | |||||
| element: initialized ? ( | |||||
| <AuthGuard> | |||||
| <Page404 /> | |||||
| </AuthGuard> | |||||
| ) : ( | |||||
| <LoadingScreen /> | |||||
| ), | |||||
| }, | |||||
| ], | |||||
| }, | }, | ||||
| ]); | ]); | ||||
| } | } | ||||
| @@ -67,6 +82,7 @@ export function Routes() { | |||||
| // 認証関連 ------------------------------- | // 認証関連 ------------------------------- | ||||
| const Login = Loadable(lazy(() => import("pages/auth/login"))); | const Login = Loadable(lazy(() => import("pages/auth/login"))); | ||||
| const Logout = Loadable(lazy(() => import("pages/auth/logout"))); | const Logout = Loadable(lazy(() => import("pages/auth/logout"))); | ||||
| // QRサービス券関連 | // QRサービス券関連 | ||||
| const QRサービス券発行申請 = Loadable( | const QRサービス券発行申請 = Loadable( | ||||
| lazy(() => import("pages/qr-service/QRサービス券発行申請")) | lazy(() => import("pages/qr-service/QRサービス券発行申請")) | ||||
| @@ -77,7 +93,4 @@ const QRサービス券発行申請 = Loadable( | |||||
| // const Page403 = Loadable(lazy(() => import("pages/common/Page403"))); | // const Page403 = Loadable(lazy(() => import("pages/common/Page403"))); | ||||
| const Page404 = Loadable(lazy(() => import("pages/common/Page404"))); | const Page404 = Loadable(lazy(() => import("pages/common/Page404"))); | ||||
| const PP = (path: string) => { | |||||
| return Loadable(lazy(() => import(path))); | |||||
| }; | |||||
| const Page403 = PP("pages/common/Page403"); | |||||
| const Page403 = Load("pages/common/Page403"); | |||||
| @@ -2,7 +2,7 @@ import { Dictionary } from "@types"; | |||||
| import { PageID, TabID } from "pages"; | import { PageID, TabID } from "pages"; | ||||
| import { get, isArray, isString, replace } from "lodash"; | import { get, isArray, isString, replace } from "lodash"; | ||||
| type PathKey = [PageID, TabID?] | PageID; | |||||
| export type PathKey = [PageID, TabID?] | PageID; | |||||
| const makePathKey = (arg: PathKey): string => { | const makePathKey = (arg: PathKey): string => { | ||||
| if (isArray(arg)) { | if (isArray(arg)) { | ||||
| const tabStr = arg[1] !== undefined ? "/" + String(arg[1]) : ""; | const tabStr = arg[1] !== undefined ? "/" + String(arg[1]) : ""; | ||||
| @@ -37,6 +37,16 @@ const PATHS_DASHBOARD = { | |||||
| "/dashboard/login-user/customer/register", | "/dashboard/login-user/customer/register", | ||||
| [makePathKey(PageID.ログインユーザ_店舗一覧)]: | [makePathKey(PageID.ログインユーザ_店舗一覧)]: | ||||
| "/dashboard/login-user/shop/list", | "/dashboard/login-user/shop/list", | ||||
| [makePathKey(PageID.ログインユーザ_店舗新規登録)]: | |||||
| "/dashboard/login-user/shop/register", | |||||
| [makePathKey(PageID.店舗一覧)]: "/dashboard/shop/list", | |||||
| [makePathKey(PageID.店舗新規登録)]: "/dashboard/shop/register", | |||||
| [makePathKey([PageID.店舗詳細])]: "/dashboard/shop/detail/:shopId", | |||||
| [makePathKey([PageID.店舗詳細, TabID.店舗詳細_メイン])]: | |||||
| "/dashboard/shop/detail/:shopId", | |||||
| [makePathKey([PageID.店舗詳細, TabID.店舗詳細_設定])]: | |||||
| "/dashboard/shop/detail/setting/:shopId", | |||||
| [makePathKey(PageID.サービス券発行用QRコード)]: "/dashboard/qrcode/generate", | [makePathKey(PageID.サービス券発行用QRコード)]: "/dashboard/qrcode/generate", | ||||
| [makePathKey(PageID.サービス券利用履歴)]: "/dashboard/qrcode/history", | [makePathKey(PageID.サービス券利用履歴)]: "/dashboard/qrcode/history", | ||||
| @@ -44,10 +54,12 @@ const PATHS_DASHBOARD = { | |||||
| const PATHS = { | const PATHS = { | ||||
| [makePathKey(PageID.NONE)]: "/", | [makePathKey(PageID.NONE)]: "/", | ||||
| [makePathKey([PageID.NONE, TabID.NONE])]: "/", | |||||
| // 認証 | // 認証 | ||||
| [makePathKey(PageID.LOGIN)]: "/login", | [makePathKey(PageID.LOGIN)]: "/login", | ||||
| [makePathKey(PageID.LOGOUT)]: "/logout", | [makePathKey(PageID.LOGOUT)]: "/logout", | ||||
| [makePathKey(PageID.成り代わり終了)]: "/role/switch/end", | |||||
| [makePathKey(PageID.QRサービス券発行申請)]: "qr-service/acquitision/:token", | [makePathKey(PageID.QRサービス券発行申請)]: "qr-service/acquitision/:token", | ||||
| @@ -67,7 +79,7 @@ export function getPath(key: PathKey, option?: PathOption) { | |||||
| const pageId = getPageId(key); | const pageId = getPageId(key); | ||||
| const tabId = getTabId(key); | const tabId = getTabId(key); | ||||
| let path = getRoute(pageId); | |||||
| let path = getRoute(key); | |||||
| // ページ番号解決 | // ページ番号解決 | ||||
| path = replacePathParam(path, "page", option?.page ?? 0); | path = replacePathParam(path, "page", option?.page ?? 0); | ||||
| @@ -90,8 +102,17 @@ export function getListPagePath(key: PathKey, page: number): string { | |||||
| } | } | ||||
| export function getRoute(key: PathKey, exclude?: string): string { | export function getRoute(key: PathKey, exclude?: string): string { | ||||
| let path = get(PATHS, makePathKey(key)); | |||||
| if (!path) throw new Error("ルート未定義:" + makePathKey(key)); | |||||
| let path = ""; | |||||
| if (getTabId(key) === TabID.NONE) { | |||||
| path = get(PATHS, makePathKey(getPageId(key))); | |||||
| } else { | |||||
| path = get(PATHS, makePathKey(key)); | |||||
| } | |||||
| if (!path) { | |||||
| // throw new Error("ルート未定義:" + makePathKey(key)); | |||||
| // console.error("ルート未定義:" + makePathKey(key)); | |||||
| return ""; | |||||
| } | |||||
| if (exclude) { | if (exclude) { | ||||
| path = replace(path, "/" + exclude + "/", ""); | path = replace(path, "/" + exclude + "/", ""); | ||||
| @@ -0,0 +1,124 @@ | |||||
| import { ページアクセス許可判定 } from "auth/route"; | |||||
| import AuthGuard from "guards/AuthGuard"; | |||||
| import useAuth from "hooks/useAuth"; | |||||
| import DashboardLayout from "layouts/dashbord"; | |||||
| import { PageID, TabID } from "pages"; | |||||
| import React, { lazy, useMemo } from "react"; | |||||
| import { RouteObject } from "react-router-dom"; | |||||
| import { Loadable } from "routes"; | |||||
| import { getRoute } from "routes/path"; | |||||
| export default function DashboardRoutes(): RouteObject[] { | |||||
| const { currentRole } = useAuth(); | |||||
| const children: RouteObject[] = useMemo(() => { | |||||
| const Enpty = Loadable(lazy(() => import("pages/dashboard/empty"))); | |||||
| const Dashboard = Loadable(lazy(() => import("pages/dashboard"))); | |||||
| const 成り代わり終了 = Loadable( | |||||
| lazy(() => import("pages/dashboard/成り代わり終了")) | |||||
| ); | |||||
| const サービス券発行用QRコード = Loadable( | |||||
| lazy(() => import("pages/dashboard/qrcode/サービス券発行用QRコード")) | |||||
| ); | |||||
| const サービス券利用履歴 = Loadable( | |||||
| lazy(() => import("pages/dashboard/qrcode/サービス券利用履歴")) | |||||
| ); | |||||
| const 顧客ログインユーザ一覧 = Loadable( | |||||
| lazy(() => import("pages/dashboard/login-user/顧客ログインユーザ一覧")) | |||||
| ); | |||||
| const 顧客ログインユーザ新規登録 = Loadable( | |||||
| lazy( | |||||
| () => import("pages/dashboard/login-user/顧客ログインユーザ新規登録") | |||||
| ) | |||||
| ); | |||||
| const 店舗新規登録 = Loadable( | |||||
| lazy(() => import("pages/dashboard/shop/店舗新規登録")) | |||||
| ); | |||||
| const 店舗一覧 = Loadable( | |||||
| lazy(() => import("pages/dashboard/shop/店舗一覧")) | |||||
| ); | |||||
| const 店舗詳細 = Loadable( | |||||
| lazy(() => import("pages/dashboard/shop/店舗詳細")) | |||||
| ); | |||||
| const allChildren: { | |||||
| pageId: PageID; | |||||
| tabId?: TabID; | |||||
| element: JSX.Element; | |||||
| }[] = [ | |||||
| { | |||||
| pageId: PageID.DASHBOARD_ENPTY, | |||||
| element: <Enpty />, | |||||
| }, | |||||
| { | |||||
| pageId: PageID.DASHBOARD_OVERVIEW, | |||||
| element: <Dashboard />, | |||||
| }, | |||||
| { | |||||
| pageId: PageID.成り代わり終了, | |||||
| element: <成り代わり終了 />, | |||||
| }, | |||||
| { | |||||
| pageId: PageID.ログインユーザ_顧客一覧, | |||||
| element: <顧客ログインユーザ一覧 />, | |||||
| }, | |||||
| { | |||||
| pageId: PageID.ログインユーザ_顧客新規登録, | |||||
| element: <顧客ログインユーザ新規登録 />, | |||||
| }, | |||||
| // { | |||||
| // pageId: PageID.ログインユーザ_店舗一覧, | |||||
| // element: <顧客ログインユーザ一覧 />, | |||||
| // }, | |||||
| // { | |||||
| // pageId: PageID.ログインユーザ_店舗新規登録, | |||||
| // element: <顧客ログインユーザ新規登録 />, | |||||
| // }, | |||||
| { | |||||
| pageId: PageID.店舗新規登録, | |||||
| element: <店舗新規登録 />, | |||||
| }, | |||||
| { | |||||
| pageId: PageID.店舗一覧, | |||||
| element: <店舗一覧 />, | |||||
| }, | |||||
| { | |||||
| pageId: PageID.店舗詳細, | |||||
| tabId: TabID.店舗詳細_メイン, | |||||
| element: <店舗詳細 />, | |||||
| }, | |||||
| { | |||||
| pageId: PageID.サービス券発行用QRコード, | |||||
| element: <サービス券発行用QRコード />, | |||||
| }, | |||||
| { | |||||
| pageId: PageID.サービス券利用履歴, | |||||
| element: <サービス券利用履歴 />, | |||||
| }, | |||||
| ]; | |||||
| return allChildren | |||||
| .filter(({ pageId }) => { | |||||
| if (currentRole === null) { | |||||
| return false; | |||||
| } | |||||
| return ページアクセス許可判定(currentRole, pageId); | |||||
| }) | |||||
| .map(({ pageId, tabId, ...others }) => ({ | |||||
| ...others, | |||||
| path: getRoute([pageId, tabId]), | |||||
| })); | |||||
| }, [currentRole]); | |||||
| return [ | |||||
| { | |||||
| element: ( | |||||
| <AuthGuard> | |||||
| <DashboardLayout /> | |||||
| </AuthGuard> | |||||
| ), | |||||
| children: children, | |||||
| }, | |||||
| ]; | |||||
| } | |||||
| @@ -1,12 +1,13 @@ | |||||
| import { ページアクセス許可判定 } from "auth/route"; | import { ページアクセス許可判定 } from "auth/route"; | ||||
| import 店舗詳細ContextProvider from "contexts/page/dashboard/shop/店舗詳細Context"; | |||||
| import AuthGuard from "guards/AuthGuard"; | import AuthGuard from "guards/AuthGuard"; | ||||
| import useAuth from "hooks/useAuth"; | import useAuth from "hooks/useAuth"; | ||||
| import DashboardLayout from "layouts/dashbord"; | import DashboardLayout from "layouts/dashbord"; | ||||
| import { PageID } from "pages"; | |||||
| import { PageID, TabID } from "pages"; | |||||
| import { lazy, useMemo } from "react"; | import { lazy, useMemo } from "react"; | ||||
| import { RouteObject } from "react-router-dom"; | |||||
| import { Outlet, RouteObject } from "react-router-dom"; | |||||
| import { Loadable } from "routes"; | import { Loadable } from "routes"; | ||||
| import { getRoute } from "routes/path"; | |||||
| import { getPath } from "routes/path"; | |||||
| export default function DashboardRoutes(): RouteObject[] { | export default function DashboardRoutes(): RouteObject[] { | ||||
| const { currentRole } = useAuth(); | const { currentRole } = useAuth(); | ||||
| @@ -14,6 +15,9 @@ export default function DashboardRoutes(): RouteObject[] { | |||||
| const children: RouteObject[] = useMemo(() => { | const children: RouteObject[] = useMemo(() => { | ||||
| const Enpty = Loadable(lazy(() => import("pages/dashboard/empty"))); | const Enpty = Loadable(lazy(() => import("pages/dashboard/empty"))); | ||||
| const Dashboard = Loadable(lazy(() => import("pages/dashboard"))); | const Dashboard = Loadable(lazy(() => import("pages/dashboard"))); | ||||
| const 成り代わり終了 = Loadable( | |||||
| lazy(() => import("pages/dashboard/成り代わり終了")) | |||||
| ); | |||||
| const サービス券発行用QRコード = Loadable( | const サービス券発行用QRコード = Loadable( | ||||
| lazy(() => import("pages/dashboard/qrcode/サービス券発行用QRコード")) | lazy(() => import("pages/dashboard/qrcode/サービス券発行用QRコード")) | ||||
| @@ -29,31 +33,99 @@ export default function DashboardRoutes(): RouteObject[] { | |||||
| () => import("pages/dashboard/login-user/顧客ログインユーザ新規登録") | () => import("pages/dashboard/login-user/顧客ログインユーザ新規登録") | ||||
| ) | ) | ||||
| ); | ); | ||||
| const 店舗新規登録 = Loadable( | |||||
| lazy(() => import("pages/dashboard/shop/店舗新規登録")) | |||||
| ); | |||||
| const 店舗一覧 = Loadable( | |||||
| lazy(() => import("pages/dashboard/shop/店舗一覧")) | |||||
| ); | |||||
| const 店舗詳細 = Loadable( | |||||
| lazy(() => import("pages/dashboard/shop/店舗詳細")) | |||||
| ); | |||||
| const 店舗詳細設定 = Loadable( | |||||
| lazy(() => import("pages/dashboard/shop/店舗詳細/設定")) | |||||
| ); | |||||
| const allChildren = [ | |||||
| const allChildren: { | |||||
| pageId: PageID; | |||||
| ele: RouteObject; | |||||
| }[] = [ | |||||
| { | { | ||||
| pageId: PageID.DASHBOARD_ENPTY, | pageId: PageID.DASHBOARD_ENPTY, | ||||
| element: <Enpty />, | |||||
| ele: { | |||||
| element: <Enpty />, | |||||
| path: getPath(PageID.DASHBOARD_ENPTY), | |||||
| }, | |||||
| }, | }, | ||||
| { | { | ||||
| pageId: PageID.DASHBOARD_OVERVIEW, | pageId: PageID.DASHBOARD_OVERVIEW, | ||||
| element: <Dashboard />, | |||||
| ele: { | |||||
| element: <Dashboard />, | |||||
| path: getPath(PageID.DASHBOARD_OVERVIEW), | |||||
| }, | |||||
| }, | |||||
| { | |||||
| pageId: PageID.成り代わり終了, | |||||
| ele: { | |||||
| element: <成り代わり終了 />, | |||||
| path: getPath(PageID.成り代わり終了), | |||||
| }, | |||||
| }, | }, | ||||
| { | { | ||||
| pageId: PageID.ログインユーザ_顧客一覧, | pageId: PageID.ログインユーザ_顧客一覧, | ||||
| element: <顧客ログインユーザ一覧 />, | |||||
| ele: { | |||||
| element: <顧客ログインユーザ一覧 />, | |||||
| path: getPath(PageID.ログインユーザ_顧客一覧), | |||||
| }, | |||||
| }, | }, | ||||
| { | { | ||||
| pageId: PageID.ログインユーザ_顧客新規登録, | pageId: PageID.ログインユーザ_顧客新規登録, | ||||
| element: <顧客ログインユーザ新規登録 />, | |||||
| ele: { | |||||
| element: <顧客ログインユーザ新規登録 />, | |||||
| path: getPath(PageID.ログインユーザ_顧客新規登録), | |||||
| }, | |||||
| }, | |||||
| // { | |||||
| // pageId: PageID.ログインユーザ_店舗一覧, | |||||
| // element: <顧客ログインユーザ一覧 />, | |||||
| // }, | |||||
| // { | |||||
| // pageId: PageID.ログインユーザ_店舗新規登録, | |||||
| // element: <顧客ログインユーザ新規登録 />, | |||||
| // }, | |||||
| { | |||||
| pageId: PageID.店舗新規登録, | |||||
| ele: { | |||||
| element: <店舗新規登録 />, | |||||
| path: getPath(PageID.店舗新規登録), | |||||
| }, | |||||
| }, | }, | ||||
| { | { | ||||
| pageId: PageID.サービス券発行用QRコード, | |||||
| element: <サービス券発行用QRコード />, | |||||
| pageId: PageID.店舗一覧, | |||||
| ele: { | |||||
| element: <店舗一覧 />, | |||||
| path: getPath(PageID.店舗一覧), | |||||
| }, | |||||
| }, | }, | ||||
| { | { | ||||
| pageId: PageID.サービス券利用履歴, | |||||
| element: <サービス券利用履歴 />, | |||||
| pageId: PageID.店舗詳細, | |||||
| ele: { | |||||
| element: ( | |||||
| <店舗詳細ContextProvider> | |||||
| <Outlet /> | |||||
| </店舗詳細ContextProvider> | |||||
| ), | |||||
| children: [ | |||||
| { | |||||
| element: <店舗詳細 />, | |||||
| path: getPath([PageID.店舗詳細, TabID.店舗詳細_メイン]), | |||||
| }, | |||||
| { | |||||
| element: <店舗詳細設定 />, | |||||
| path: getPath([PageID.店舗詳細, TabID.店舗詳細_設定]), | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -64,10 +136,7 @@ export default function DashboardRoutes(): RouteObject[] { | |||||
| } | } | ||||
| return ページアクセス許可判定(currentRole, pageId); | return ページアクセス許可判定(currentRole, pageId); | ||||
| }) | }) | ||||
| .map(({ pageId, ...others }) => ({ | |||||
| ...others, | |||||
| path: getRoute(pageId), | |||||
| })); | |||||
| .map(({ ele }) => ele); | |||||
| }, [currentRole]); | }, [currentRole]); | ||||
| return [ | return [ | ||||