From f56131f66f2bdefb5cbc38184a8b2b2efd77a663 Mon Sep 17 00:00:00 2001 From: "sosuke.iwabuchi" Date: Tue, 26 Mar 2024 17:16:33 +0900 Subject: [PATCH] =?UTF-8?q?=E3=81=84=E3=82=8D=E3=81=84=E3=82=8D=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 28 ++-- src/api/auth.ts | 54 +++++++ src/api/index.ts | 4 + src/api/shop.ts | 37 ++++- src/api/url.ts | 4 + src/auth/route.ts | 13 +- src/contexts/AuthContext.tsx | 137 ++++++++++++++---- .../dashboard/shop/店舗詳細Context.tsx | 102 +++++++++++++ src/hooks/useDashBoard.ts | 4 +- src/layouts/dashbord/navigator.tsx | 39 +++-- src/layouts/dashbord/tab/ContractTabs.tsx | 31 ---- src/layouts/dashbord/tab/ShopTabs.tsx | 49 +++++++ src/layouts/dashbord/tab/tabutil.tsx | 19 +-- src/layouts/simple/index.tsx | 2 +- src/pages/auth/logout.tsx | 11 +- .../SearchBox.tsx | 2 - .../TableBox.tsx | 11 +- .../dashboard/shop/店舗一覧/SearchBox.tsx | 81 +++++++++++ .../dashboard/shop/店舗一覧/TableBox.tsx | 113 +++++++++++++++ .../dashboard/shop/店舗一覧/index.tsx | 35 +++++ .../shop/店舗新規登録/index.tsx | 82 +++++++++++ .../dashboard/shop/店舗詳細/index.tsx | 25 ++++ .../デポジットチャージ.tsx | 84 +++++++++++ .../shop/店舗詳細/設定/index.tsx | 25 ++++ .../shop/店舗詳細/設定/発行設定.tsx | 120 +++++++++++++++ .../shop/店舗詳細/設定/設定.tsx | 120 +++++++++++++++ .../shop/店舗詳細/詳細情報.tsx | 32 ++++ src/pages/dashboard/成り代わり終了.tsx | 20 +++ src/pages/index.ts | 11 +- src/routes/index.tsx | 25 +++- src/routes/path.ts | 29 +++- src/routes/sub/________dashboard.tsx | 124 ++++++++++++++++ src/routes/sub/dashboard.tsx | 101 +++++++++++-- 33 files changed, 1438 insertions(+), 136 deletions(-) create mode 100644 src/contexts/page/dashboard/shop/店舗詳細Context.tsx delete mode 100644 src/layouts/dashbord/tab/ContractTabs.tsx create mode 100644 src/layouts/dashbord/tab/ShopTabs.tsx create mode 100644 src/pages/dashboard/shop/店舗一覧/SearchBox.tsx create mode 100644 src/pages/dashboard/shop/店舗一覧/TableBox.tsx create mode 100644 src/pages/dashboard/shop/店舗一覧/index.tsx create mode 100644 src/pages/dashboard/shop/店舗新規登録/index.tsx create mode 100644 src/pages/dashboard/shop/店舗詳細/index.tsx create mode 100644 src/pages/dashboard/shop/店舗詳細/デポジットチャージ.tsx create mode 100644 src/pages/dashboard/shop/店舗詳細/設定/index.tsx create mode 100644 src/pages/dashboard/shop/店舗詳細/設定/発行設定.tsx create mode 100644 src/pages/dashboard/shop/店舗詳細/設定/設定.tsx create mode 100644 src/pages/dashboard/shop/店舗詳細/詳細情報.tsx create mode 100644 src/pages/dashboard/成り代わり終了.tsx create mode 100644 src/routes/sub/________dashboard.tsx diff --git a/src/App.tsx b/src/App.tsx index 7e9e1e1..68b8f9e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,23 +16,23 @@ import BackDropContextProvider from "contexts/BackDropContext"; export default function App() { return ( - - - - - - - + + + + + + + - - - - - - - + + + + + + + ); } diff --git a/src/api/auth.ts b/src/api/auth.ts index cb9c55e..ebb496b 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -8,6 +8,9 @@ export type Me = { email: string; role: UserRole; shop_id: string | null; + switched_user_id: string | null; + switched_name: string | null; + switched_role: UserRole | null; }; type MeResponse = { @@ -45,3 +48,54 @@ export const logout = async () => { }); 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; +}; diff --git a/src/api/index.ts b/src/api/index.ts index e178f34..5d130f5 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -14,6 +14,9 @@ export const ApiId = { ME: id++, LOGIN: id++, LOGOUT: id++, + 顧客成り代わり開始: id++, + 店舗成り代わり開始: id++, + 成り代わり終了: id++, // ログインユーザ関連 ---------------------------------- 顧客ログインユーザ新規登録: id++, @@ -29,6 +32,7 @@ export const ApiId = { 店舗新規登録: id++, デポジット情報取得: id++, デポジットチャージ: id++, + 店舗設定: id++, // QRサービス券関連------------------------------- QRサービス券取得: id++, diff --git a/src/api/shop.ts b/src/api/shop.ts index 6f05213..64d70ee 100644 --- a/src/api/shop.ts +++ b/src/api/shop.ts @@ -1,13 +1,18 @@ import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "api"; import { getUrl } from "./url"; -type 店舗 = { - shp_id: string; - deposit: string; +export type 店舗 = { + shop_id: string; + deposit: number; 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 = { + shop_id?: string; email?: string; name?: string; }; @@ -61,9 +66,7 @@ export const デポジット情報取得 = async (param: デポジット情報 // -------デポジットチャージ--------------- export type デポジットチャージRequest = { shop_id: string; - name: string; - email: string; - password: string; + amount: string; }; export type デポジットチャージResponse = { data: { @@ -79,3 +82,25 @@ export const デポジットチャージ = async (param: デポジットチャ }); 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; +}; diff --git a/src/api/url.ts b/src/api/url.ts index 215ca07..01f8283 100644 --- a/src/api/url.ts +++ b/src/api/url.ts @@ -9,6 +9,9 @@ const urls = { [A.ME]: "me", [A.LOGIN]: "login", [A.LOGOUT]: "logout", + [A.顧客成り代わり開始]: "role/switch/customer", + [A.店舗成り代わり開始]: "role/switch/shop", + [A.成り代わり終了]: "role/switch/end", // ログインユーザ関連 ---------------------------------- [A.顧客ログインユーザ新規登録]: "login-user/customer/register", @@ -24,6 +27,7 @@ const urls = { [A.店舗新規登録]: "shop/register", [A.デポジット情報取得]: "shop/deposit", [A.デポジットチャージ]: "shop/deposit/charge", + [A.店舗設定]: "shop/config", // QRサービス券関連------------------------------- [A.QRサービス券取得]: "qr-service/get-ticket", diff --git a/src/auth/route.ts b/src/auth/route.ts index d0396b3..bfe3179 100644 --- a/src/auth/route.ts +++ b/src/auth/route.ts @@ -3,6 +3,7 @@ import { UserRole } from "./UserRole"; const 共通ルート = [P.LOGIN, P.LOGOUT]; const 認証後共通ルート = [P.DASHBOARD_ENPTY, P.DASHBOARD_OVERVIEW]; +const 管理者顧客共通ルート = [P.成り代わり終了]; const 認可別許可ルート: { [route: string]: P[]; @@ -11,11 +12,21 @@ const 認可別許可ルート: { [UserRole.ADMIN]: [ ...共通ルート, ...認証後共通ルート, + ...管理者顧客共通ルート, P.ログインユーザ_顧客一覧, P.ログインユーザ_顧客新規登録, ], - [UserRole.CUSTOMER]: [...共通ルート, ...認証後共通ルート], + [UserRole.CUSTOMER]: [ + ...共通ルート, + ...認証後共通ルート, + ...管理者顧客共通ルート, + P.ログインユーザ_店舗一覧, + P.ログインユーザ_店舗新規登録, + P.店舗一覧, + P.店舗新規登録, + P.店舗詳細, + ], [UserRole.SHOP]: [ ...共通ルート, diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 7afbd13..18a5bad 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -1,14 +1,24 @@ 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 useAPICall from "hooks/useAPICall"; +import useNavigateCustom from "hooks/useNavigateCustom"; +import { PageID } from "pages"; 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; - customerCode: string; }; type Auth = { initialized: boolean; @@ -20,19 +30,20 @@ type Auth = { role: UserRole; // デフォルトロール currentRole: UserRole; // 現在のロール + currentName: string; // 現在のロール - changedRole: ChangedRole | null; - - shopId: number | null; + switchedUser: SwitchedUser | null; + isSwitched: boolean; login: (email: string, password: string) => Promise; - logout: VoidFunction; + logout: () => Promise; me: VoidFunction; - changeRole: (changedRole: ChangedRole | null) => void; + switchCustomerRole: (userId: string) => Promise; + switchShopRole: (userId: string) => Promise; + switchEnd: () => Promise; }; export const AuthContext = createContext({ initialized: false, - authenticated: false, id: "", @@ -40,14 +51,18 @@ export const AuthContext = createContext({ email: "", role: UserRole.NONE, - changedRole: null, currentRole: UserRole.NONE, - shopId: null, + currentName: "", + + switchedUser: null, + isSwitched: false, login: async (email: string, password: string) => false, - logout: () => {}, + logout: async () => {}, me: () => {}, - changeRole: (changedRole: ChangedRole | null) => {}, + switchCustomerRole: async (userId: string) => {}, + switchShopRole: async (userId: string) => {}, + switchEnd: async () => {}, }); type Props = HasChildren; @@ -57,34 +72,47 @@ function AuthContextProvider({ children }: Props) { const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [role, setRole] = useState(UserRole.NONE); - const [changedRole, setChangedRole] = useState(null); + const [switchedUser, setSwitchedUser] = useState(null); const [shopId, setShopId] = useState(null); + const { navigateWhenChanged } = useNavigateCustom(); const authenticated = useMemo(() => { return !!email; }, [email]); const currentRole = useMemo(() => { - if (changedRole) { - return changedRole.role; + if (switchedUser) { + return switchedUser.role; } return role; - }, [changedRole, role]); + }, [switchedUser, role]); + + const currentName = useMemo(() => { + if (switchedUser) { + return switchedUser.name; + } + return name; + }, [switchedUser, name]); const { callAPI: callMe } = useAPICall({ apiMethod: me, backDrop: true, onSuccess: ({ data }) => { - console.log("認証有"); setInitialized(true); setId(data.id); setEmail(data.email); setName(data.name); 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: () => { - console.log("認証無"); clear(); setInitialized(true); }, @@ -100,32 +128,78 @@ function AuthContextProvider({ children }: Props) { const { callAPI: callLogout } = useAPICall({ apiMethod: APILogout, + backDrop: true, onSuccess: () => { 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 = () => { setId(""); setName(""); setEmail(""); setRole(UserRole.NONE); - setChangedRole(null); + setSwitchedUser(null); setShopId(null); }; 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; }; - const logout = () => { - callLogout({}); + const logout = async () => { + await callLogout({}); 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 = () => { callMe({}); @@ -147,14 +221,17 @@ function AuthContextProvider({ children }: Props) { role, currentRole, - changedRole, - shopId, + currentName, + switchedUser, // Func login, logout, me: meFetch, - changeRole, + switchCustomerRole, + switchShopRole, + switchEnd, + isSwitched, }} > {children} diff --git a/src/contexts/page/dashboard/shop/店舗詳細Context.tsx b/src/contexts/page/dashboard/shop/店舗詳細Context.tsx new file mode 100644 index 0000000..8ddff30 --- /dev/null +++ b/src/contexts/page/dashboard/shop/店舗詳細Context.tsx @@ -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; + moveToMain: VoidFunction; +}; + +export const 店舗詳細Context = createContext({ + 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, + }} + > + + {shop && children} + + ); +} + +function TabInit() { + const { setTabs, tabId } = useDashboard(); + const { shop } = useContext(店舗詳細Context); + const { element } = useShopTabs(); + + useEffect(() => { + setTabs(element); + }, [shop, tabId]); + + return null; +} + +export default 店舗詳細ContextProvider; diff --git a/src/hooks/useDashBoard.ts b/src/hooks/useDashBoard.ts index 6f77a02..353a6e9 100644 --- a/src/hooks/useDashBoard.ts +++ b/src/hooks/useDashBoard.ts @@ -11,8 +11,10 @@ export default function useDashboard(pageId?: PageID, tabId?: TabID) { } if (tabId) { context.setTabId(tabId); + } else { + context.setTabId(TabID.NONE); } - }, []); + }, [pageId, tabId]); return context; } diff --git a/src/layouts/dashbord/navigator.tsx b/src/layouts/dashbord/navigator.tsx index 184de6c..24254b5 100644 --- a/src/layouts/dashbord/navigator.tsx +++ b/src/layouts/dashbord/navigator.tsx @@ -17,18 +17,19 @@ import { ページアクセス許可判定 } from "auth/route"; import useAuth from "hooks/useAuth"; import useNavigateCustom from "hooks/useNavigateCustom"; import usePage from "hooks/usePage"; -import { PageID } from "pages"; +import { PageID, TabID } from "pages"; import * as React from "react"; -import { PathOption, getPath } from "routes/path"; +import { PathKey, PathOption, getPath } from "routes/path"; type Group = { label: string; children: SubGroup[]; }; type SubGroup = { - label: string; + label: string | React.ReactNode; icon: React.ReactNode; children?: Child[]; + whenIsSwitched?: boolean; // 子要素を持たない場合は設定 id?: PageID; @@ -37,7 +38,7 @@ type SubGroup = { type Child = { label: string; - id: PageID; + id: PathKey; option?: PathOption; }; @@ -65,7 +66,7 @@ const itemCategory = { export default function Navigator(props: DrawerProps) { const { ...other } = props; - const { name } = useAuth(); + const { currentName, isSwitched } = useAuth(); const { navigateWhenChanged } = useNavigateCustom(); @@ -94,13 +95,23 @@ export default function Navigator(props: DrawerProps) { ], }, { - label: "QRサービス券", + label: "店舗管理", children: [ { - label: "券発行用QRコード", + label: "店舗新規登録", + icon: , + id: PageID.店舗新規登録, + }, + { + label: "店舗一覧", icon: , - id: PageID.サービス券発行用QRコード, + id: PageID.店舗一覧, }, + ], + }, + { + label: "QRサービス券", + children: [ { label: "利用履歴", icon: , @@ -111,6 +122,12 @@ export default function Navigator(props: DrawerProps) { { label: "アカウント", children: [ + { + label: "成り代わり終了", + icon: , + id: PageID.成り代わり終了, + whenIsSwitched: true, + }, { label: "ログアウト", icon: , id: PageID.LOGOUT }, ], }, @@ -131,7 +148,8 @@ export default function Navigator(props: DrawerProps) { {} - {name} 様 + {currentName} 様 + {!!isSwitched && 成り代わり中} @@ -144,11 +162,12 @@ export default function Navigator(props: DrawerProps) { } function Group(group: Group) { - const { currentRole } = useAuth(); + const { currentRole, isSwitched } = useAuth(); const { label, children } = group; const elements = children .filter(({ id }) => ページアクセス許可判定(currentRole, id ?? -1)) + .filter(({ whenIsSwitched }) => whenIsSwitched !== true || isSwitched) .map((ele, index) => ); if (elements.length === 0) return null; diff --git a/src/layouts/dashbord/tab/ContractTabs.tsx b/src/layouts/dashbord/tab/ContractTabs.tsx deleted file mode 100644 index 9d2cf75..0000000 --- a/src/layouts/dashbord/tab/ContractTabs.tsx +++ /dev/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 ( - - {elements} - - ); -} diff --git a/src/layouts/dashbord/tab/ShopTabs.tsx b/src/layouts/dashbord/tab/ShopTabs.tsx new file mode 100644 index 0000000..cbde045 --- /dev/null +++ b/src/layouts/dashbord/tab/ShopTabs.tsx @@ -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: ( + + {elements} + + ), + }; +} diff --git a/src/layouts/dashbord/tab/tabutil.tsx b/src/layouts/dashbord/tab/tabutil.tsx index 00a449d..582a456 100644 --- a/src/layouts/dashbord/tab/tabutil.tsx +++ b/src/layouts/dashbord/tab/tabutil.tsx @@ -1,30 +1,31 @@ import { PageID, TabID } from "pages"; import usePage from "hooks/usePage"; -import { useMemo } from "react"; +import { useEffect, useMemo } from "react"; import { Tab } from "."; import { getPath } from "routes/path"; +import userEvent from "@testing-library/user-event"; export type TabProps = { label: string; tabId: TabID; + path: string; }; export function useTab(tabs: TabProps[]) { const { pageId, tabId } = usePage(); const elements = useMemo(() => { - return tabs.map(({ label, tabId: elementTabId }, index) => { - const path = getPath([pageId, tabId]); + return tabs.map(({ label, path }, index) => { return ; }); - }, [tabs]); + }, [pageId, tabId, tabs]); 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]); return { diff --git a/src/layouts/simple/index.tsx b/src/layouts/simple/index.tsx index 7072966..034f66c 100644 --- a/src/layouts/simple/index.tsx +++ b/src/layouts/simple/index.tsx @@ -14,7 +14,7 @@ export default function SimpleLayout() { - MyPage + HT Dashboard diff --git a/src/pages/auth/logout.tsx b/src/pages/auth/logout.tsx index ff0e6df..d5714a4 100644 --- a/src/pages/auth/logout.tsx +++ b/src/pages/auth/logout.tsx @@ -6,16 +6,21 @@ import { useEffect } from "react"; import { getPath } from "routes/path"; export default function Logout() { - const { logout } = useAuth(); - const { info, success } = useSnackbarCustom(); + const { authenticated, logout } = useAuth(); + const { info } = useSnackbarCustom(); const { navigateWhenChanged } = useNavigateCustom(); useEffect(() => { logout(); info("ログアウトしました"); - navigateWhenChanged(getPath(PageID.LOGIN)); }, []); + useEffect(() => { + if (authenticated === false) { + navigateWhenChanged(getPath(PageID.LOGIN)); + } + }, [authenticated]); + return null; } diff --git a/src/pages/dashboard/login-user/顧客ログインユーザ一覧/SearchBox.tsx b/src/pages/dashboard/login-user/顧客ログインユーザ一覧/SearchBox.tsx index 211279b..be4ceb3 100644 --- a/src/pages/dashboard/login-user/顧客ログインユーザ一覧/SearchBox.tsx +++ b/src/pages/dashboard/login-user/顧客ログインユーザ一覧/SearchBox.tsx @@ -50,7 +50,6 @@ export default function SearchBox({ table }: CommonProps) { const fetch = async (data: Dictionary) => { const sendData = makeSendData(data); - console.log({ sendData }); if (!isEqual(sendData, lastSendSearchCondition)) { setLastSendSearchCondition(sendData); call顧客一覧取得(sendData); @@ -58,7 +57,6 @@ export default function SearchBox({ table }: CommonProps) { }; useEffect(() => { - console.log({ initialized, condition }); if (initialized) { fetch(condition); } diff --git a/src/pages/dashboard/login-user/顧客ログインユーザ一覧/TableBox.tsx b/src/pages/dashboard/login-user/顧客ログインユーザ一覧/TableBox.tsx index 97dd592..259296e 100644 --- a/src/pages/dashboard/login-user/顧客ログインユーザ一覧/TableBox.tsx +++ b/src/pages/dashboard/login-user/顧客ログインユーザ一覧/TableBox.tsx @@ -11,6 +11,8 @@ import { 運営会社ログインユーザ } from "api/login-user"; import TableHeadCustom, { HeadLabelProps, } from "components/table/TableHeadCustom"; +import useAuth from "hooks/useAuth"; +import useSnackbarCustom from "hooks/useSnackbarCustom"; import { UseTableReturn } from "hooks/useTable"; type CommonProps = { @@ -81,8 +83,15 @@ type RowProps = { data: 運営会社ログインユーザ; }; function Row({ data }: RowProps) { + const { switchCustomerRole } = useAuth(); + const { info } = useSnackbarCustom(); + const handleClick = () => { + switchCustomerRole(data.id); + info("成り代わり開始しました"); + }; + return ( - + {data.name} {data.email} diff --git a/src/pages/dashboard/shop/店舗一覧/SearchBox.tsx b/src/pages/dashboard/shop/店舗一覧/SearchBox.tsx new file mode 100644 index 0000000..64cf5f6 --- /dev/null +++ b/src/pages/dashboard/shop/店舗一覧/SearchBox.tsx @@ -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({ default: false }); + const { condition, initialized, get, addCondition } = + useSearchConditionContext(); + const form = useForm({ + 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 ( + + + + + + 名前 + + + + + + + ); +} diff --git a/src/pages/dashboard/shop/店舗一覧/TableBox.tsx b/src/pages/dashboard/shop/店舗一覧/TableBox.tsx new file mode 100644 index 0000000..20bf45c --- /dev/null +++ b/src/pages/dashboard/shop/店舗一覧/TableBox.tsx @@ -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 ( + <> + + + + + + {fillteredRow.map((row, index) => ( + + ))} + +
+
+ + + + + + ); +} + +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 ( + + {name} + {formatDeposit} + + ); +} diff --git a/src/pages/dashboard/shop/店舗一覧/index.tsx b/src/pages/dashboard/shop/店舗一覧/index.tsx new file mode 100644 index 0000000..5935f6d --- /dev/null +++ b/src/pages/dashboard/shop/店舗一覧/index.tsx @@ -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 ( + + + + ); +} + +function Page() { + const table = useTable<店舗>(); + + return ( + + + + + ); +} diff --git a/src/pages/dashboard/shop/店舗新規登録/index.tsx b/src/pages/dashboard/shop/店舗新規登録/index.tsx new file mode 100644 index 0000000..eb54c64 --- /dev/null +++ b/src/pages/dashboard/shop/店舗新規登録/index.tsx @@ -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({ + 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 ( + + + + + 名称 + + + + + + + + + ); +} diff --git a/src/pages/dashboard/shop/店舗詳細/index.tsx b/src/pages/dashboard/shop/店舗詳細/index.tsx new file mode 100644 index 0000000..809e16e --- /dev/null +++ b/src/pages/dashboard/shop/店舗詳細/index.tsx @@ -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 ; +} + +function Page() { + return ( + + + <詳細情報 /> + <デポジットチャージ /> + {/* 詳細情報テーブル */} + {/* チャージ */} + {/* 利用履歴 */} + + + ); +} diff --git a/src/pages/dashboard/shop/店舗詳細/デポジットチャージ.tsx b/src/pages/dashboard/shop/店舗詳細/デポジットチャージ.tsx new file mode 100644 index 0000000..177dfd3 --- /dev/null +++ b/src/pages/dashboard/shop/店舗詳細/デポジットチャージ.tsx @@ -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({ + 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 ( + + + + + デポジットチャージ + + + + チャージ金額 + + + + + + + + + + + + + + + ); +} diff --git a/src/pages/dashboard/shop/店舗詳細/設定/index.tsx b/src/pages/dashboard/shop/店舗詳細/設定/index.tsx new file mode 100644 index 0000000..3623cfd --- /dev/null +++ b/src/pages/dashboard/shop/店舗詳細/設定/index.tsx @@ -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 ; +} + +function Page() { + return ( + + + <設定 /> + + + ); +} diff --git a/src/pages/dashboard/shop/店舗詳細/設定/発行設定.tsx b/src/pages/dashboard/shop/店舗詳細/設定/発行設定.tsx new file mode 100644 index 0000000..18504ca --- /dev/null +++ b/src/pages/dashboard/shop/店舗詳細/設定/発行設定.tsx @@ -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({ + 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 ( + + + + + 基本設定 + + + QRサービス券有効期限 + + + + + + + + + 発行時デポジット下限値 + + + + + + + + + 認証時デポジット下限値 + + + + + + + + + 利用時デポジット下限値 + + + + + + + + + + + + + + ); +} diff --git a/src/pages/dashboard/shop/店舗詳細/設定/設定.tsx b/src/pages/dashboard/shop/店舗詳細/設定/設定.tsx new file mode 100644 index 0000000..18504ca --- /dev/null +++ b/src/pages/dashboard/shop/店舗詳細/設定/設定.tsx @@ -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({ + 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 ( + + + + + 基本設定 + + + QRサービス券有効期限 + + + + + + + + + 発行時デポジット下限値 + + + + + + + + + 認証時デポジット下限値 + + + + + + + + + 利用時デポジット下限値 + + + + + + + + + + + + + + ); +} diff --git a/src/pages/dashboard/shop/店舗詳細/詳細情報.tsx b/src/pages/dashboard/shop/店舗詳細/詳細情報.tsx new file mode 100644 index 0000000..31d4c7c --- /dev/null +++ b/src/pages/dashboard/shop/店舗詳細/詳細情報.tsx @@ -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 ( + + + 基本情報 + + + + + + ); +} diff --git a/src/pages/dashboard/成り代わり終了.tsx b/src/pages/dashboard/成り代わり終了.tsx new file mode 100644 index 0000000..a42b3a9 --- /dev/null +++ b/src/pages/dashboard/成り代わり終了.tsx @@ -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; +} diff --git a/src/pages/index.ts b/src/pages/index.ts index 4f0ac2f9..f33e63e 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -5,6 +5,7 @@ export const PageID = { // 認証関連 ---------------------------------- LOGIN: id++, LOGOUT: id++, + 成り代わり終了: id++, // ダッシュボード系 START ---------------------------------- DASHBOARD_ENPTY: id++, @@ -17,6 +18,12 @@ export const PageID = { ログインユーザ_顧客一覧: id++, ログインユーザ_顧客新規登録: id++, ログインユーザ_店舗一覧: id++, + ログインユーザ_店舗新規登録: id++, + + // 店舗管理 + 店舗一覧: id++, + 店舗新規登録: id++, + 店舗詳細: id++, サービス券発行用QRコード: id++, サービス券利用履歴: id++, @@ -33,7 +40,9 @@ export type PageID = (typeof PageID)[keyof typeof PageID]; id = 0; export const TabID = { NONE: id++, - A: id++, + + 店舗詳細_メイン: id++, + 店舗詳細_設定: id++, } as const; export type TabID = (typeof TabID)[keyof typeof TabID]; diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 2ab11ee..0bbedd7 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -7,6 +7,7 @@ import { RouteObject, useRoutes } from "react-router-dom"; import { getRoute } from "./path"; import DashboardRoutes from "./sub/dashboard"; import QRServiceSimpleLayout from "layouts/qr-service"; +import AuthGuard from "guards/AuthGuard"; export const Loadable = (Component: ElementType) => (props: any) => { return ( @@ -15,6 +16,9 @@ export const Loadable = (Component: ElementType) => (props: any) => { ); }; +export const Load = (path: string) => { + return Loadable(lazy(() => import(path))); +}; const CommonRoutes = (): RouteObject => ({ element: , @@ -58,8 +62,19 @@ export function Routes() { element: , }, { - path: "*", - element: initialized ? : , + element: , + children: [ + { + path: "*", + element: initialized ? ( + + + + ) : ( + + ), + }, + ], }, ]); } @@ -67,6 +82,7 @@ export function Routes() { // 認証関連 ------------------------------- const Login = Loadable(lazy(() => import("pages/auth/login"))); const Logout = Loadable(lazy(() => import("pages/auth/logout"))); + // QRサービス券関連 const QRサービス券発行申請 = Loadable( lazy(() => import("pages/qr-service/QRサービス券発行申請")) @@ -77,7 +93,4 @@ const QRサービス券発行申請 = Loadable( // const Page403 = Loadable(lazy(() => import("pages/common/Page403"))); 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"); diff --git a/src/routes/path.ts b/src/routes/path.ts index 05df51b..82821ef 100644 --- a/src/routes/path.ts +++ b/src/routes/path.ts @@ -2,7 +2,7 @@ import { Dictionary } from "@types"; import { PageID, TabID } from "pages"; import { get, isArray, isString, replace } from "lodash"; -type PathKey = [PageID, TabID?] | PageID; +export type PathKey = [PageID, TabID?] | PageID; const makePathKey = (arg: PathKey): string => { if (isArray(arg)) { const tabStr = arg[1] !== undefined ? "/" + String(arg[1]) : ""; @@ -37,6 +37,16 @@ const PATHS_DASHBOARD = { "/dashboard/login-user/customer/register", [makePathKey(PageID.ログインユーザ_店舗一覧)]: "/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.サービス券利用履歴)]: "/dashboard/qrcode/history", @@ -44,10 +54,12 @@ const PATHS_DASHBOARD = { const PATHS = { [makePathKey(PageID.NONE)]: "/", + [makePathKey([PageID.NONE, TabID.NONE])]: "/", // 認証 [makePathKey(PageID.LOGIN)]: "/login", [makePathKey(PageID.LOGOUT)]: "/logout", + [makePathKey(PageID.成り代わり終了)]: "/role/switch/end", [makePathKey(PageID.QRサービス券発行申請)]: "qr-service/acquitision/:token", @@ -67,7 +79,7 @@ export function getPath(key: PathKey, option?: PathOption) { const pageId = getPageId(key); const tabId = getTabId(key); - let path = getRoute(pageId); + let path = getRoute(key); // ページ番号解決 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 { - 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) { path = replace(path, "/" + exclude + "/", ""); diff --git a/src/routes/sub/________dashboard.tsx b/src/routes/sub/________dashboard.tsx new file mode 100644 index 0000000..205c9b7 --- /dev/null +++ b/src/routes/sub/________dashboard.tsx @@ -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: , + }, + { + pageId: PageID.DASHBOARD_OVERVIEW, + element: , + }, + { + 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: ( + + + + ), + children: children, + }, + ]; +} diff --git a/src/routes/sub/dashboard.tsx b/src/routes/sub/dashboard.tsx index ddbc0f8..839b5ff 100644 --- a/src/routes/sub/dashboard.tsx +++ b/src/routes/sub/dashboard.tsx @@ -1,12 +1,13 @@ import { ページアクセス許可判定 } from "auth/route"; +import 店舗詳細ContextProvider from "contexts/page/dashboard/shop/店舗詳細Context"; import AuthGuard from "guards/AuthGuard"; import useAuth from "hooks/useAuth"; import DashboardLayout from "layouts/dashbord"; -import { PageID } from "pages"; +import { PageID, TabID } from "pages"; import { lazy, useMemo } from "react"; -import { RouteObject } from "react-router-dom"; +import { Outlet, RouteObject } from "react-router-dom"; import { Loadable } from "routes"; -import { getRoute } from "routes/path"; +import { getPath } from "routes/path"; export default function DashboardRoutes(): RouteObject[] { const { currentRole } = useAuth(); @@ -14,6 +15,9 @@ export default function DashboardRoutes(): RouteObject[] { 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コード")) @@ -29,31 +33,99 @@ export default function DashboardRoutes(): RouteObject[] { () => 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, - element: , + ele: { + element: , + path: getPath(PageID.DASHBOARD_ENPTY), + }, }, { pageId: PageID.DASHBOARD_OVERVIEW, - element: , + ele: { + element: , + path: getPath(PageID.DASHBOARD_OVERVIEW), + }, + }, + { + pageId: PageID.成り代わり終了, + ele: { + element: <成り代わり終了 />, + path: getPath(PageID.成り代わり終了), + }, }, { pageId: PageID.ログインユーザ_顧客一覧, - element: <顧客ログインユーザ一覧 />, + ele: { + element: <顧客ログインユーザ一覧 />, + path: getPath(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> + + + ), + children: [ + { + element: <店舗詳細 />, + path: getPath([PageID.店舗詳細, TabID.店舗詳細_メイン]), + }, + { + element: <店舗詳細設定 />, + path: getPath([PageID.店舗詳細, TabID.店舗詳細_設定]), + }, + ], + }, }, ]; @@ -64,10 +136,7 @@ export default function DashboardRoutes(): RouteObject[] { } return ページアクセス許可判定(currentRole, pageId); }) - .map(({ pageId, ...others }) => ({ - ...others, - path: getRoute(pageId), - })); + .map(({ ele }) => ele); }, [currentRole]); return [