浏览代码

いろいろ対応

develop
sosuke.iwabuchi 1年前
父节点
当前提交
f56131f66f
共有 33 个文件被更改,包括 1438 次插入136 次删除
  1. +14
    -14
      src/App.tsx
  2. +54
    -0
      src/api/auth.ts
  3. +4
    -0
      src/api/index.ts
  4. +31
    -6
      src/api/shop.ts
  5. +4
    -0
      src/api/url.ts
  6. +12
    -1
      src/auth/route.ts
  7. +107
    -30
      src/contexts/AuthContext.tsx
  8. +102
    -0
      src/contexts/page/dashboard/shop/店舗詳細Context.tsx
  9. +3
    -1
      src/hooks/useDashBoard.ts
  10. +29
    -10
      src/layouts/dashbord/navigator.tsx
  11. +0
    -31
      src/layouts/dashbord/tab/ContractTabs.tsx
  12. +49
    -0
      src/layouts/dashbord/tab/ShopTabs.tsx
  13. +10
    -9
      src/layouts/dashbord/tab/tabutil.tsx
  14. +1
    -1
      src/layouts/simple/index.tsx
  15. +8
    -3
      src/pages/auth/logout.tsx
  16. +0
    -2
      src/pages/dashboard/login-user/顧客ログインユーザ一覧/SearchBox.tsx
  17. +10
    -1
      src/pages/dashboard/login-user/顧客ログインユーザ一覧/TableBox.tsx
  18. +81
    -0
      src/pages/dashboard/shop/店舗一覧/SearchBox.tsx
  19. +113
    -0
      src/pages/dashboard/shop/店舗一覧/TableBox.tsx
  20. +35
    -0
      src/pages/dashboard/shop/店舗一覧/index.tsx
  21. +82
    -0
      src/pages/dashboard/shop/店舗新規登録/index.tsx
  22. +25
    -0
      src/pages/dashboard/shop/店舗詳細/index.tsx
  23. +84
    -0
      src/pages/dashboard/shop/店舗詳細/デポジットチャージ.tsx
  24. +25
    -0
      src/pages/dashboard/shop/店舗詳細/設定/index.tsx
  25. +120
    -0
      src/pages/dashboard/shop/店舗詳細/設定/発行設定.tsx
  26. +120
    -0
      src/pages/dashboard/shop/店舗詳細/設定/設定.tsx
  27. +32
    -0
      src/pages/dashboard/shop/店舗詳細/詳細情報.tsx
  28. +20
    -0
      src/pages/dashboard/成り代わり終了.tsx
  29. +10
    -1
      src/pages/index.ts
  30. +19
    -6
      src/routes/index.tsx
  31. +25
    -4
      src/routes/path.ts
  32. +124
    -0
      src/routes/sub/________dashboard.tsx
  33. +85
    -16
      src/routes/sub/dashboard.tsx

+ 14
- 14
src/App.tsx 查看文件

@@ -16,23 +16,23 @@ import BackDropContextProvider from "contexts/BackDropContext";
export default function App() {
return (
<LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={ja}>
<AuthContextProvider>
<PageContextProvider>
<WindowSizeContextProvider>
<BrowserRouter>
<AppThemeProvider>
<SnackbarProvider>
<BackDropContextProvider>
<PageContextProvider>
<WindowSizeContextProvider>
<BrowserRouter>
<AppThemeProvider>
<SnackbarProvider>
<BackDropContextProvider>
<AuthContextProvider>
<CsrfTokenProvider />
<CssBaseline />
<Routes />
</BackDropContextProvider>
</SnackbarProvider>
</AppThemeProvider>
</BrowserRouter>
</WindowSizeContextProvider>
</PageContextProvider>
</AuthContextProvider>
</AuthContextProvider>
</BackDropContextProvider>
</SnackbarProvider>
</AppThemeProvider>
</BrowserRouter>
</WindowSizeContextProvider>
</PageContextProvider>
</LocalizationProvider>
);
}

+ 54
- 0
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;
};

+ 4
- 0
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++,


+ 31
- 6
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;
};

+ 4
- 0
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",


+ 12
- 1
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]: [
...共通ルート,


+ 107
- 30
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<boolean>;
logout: VoidFunction;
logout: () => Promise<void>;
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>({
initialized: false,

authenticated: false,

id: "",
@@ -40,14 +51,18 @@ export const AuthContext = createContext<Auth>({
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>(UserRole.NONE);
const [changedRole, setChangedRole] = useState<ChangedRole | null>(null);
const [switchedUser, setSwitchedUser] = useState<SwitchedUser | null>(null);
const [shopId, setShopId] = useState<number | null>(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}


+ 102
- 0
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<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;

+ 3
- 1
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;
}

+ 29
- 10
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: <ArticleIcon />,
id: PageID.店舗新規登録,
},
{
label: "店舗一覧",
icon: <ArticleIcon />,
id: PageID.サービス券発行用QRコード,
id: PageID.店舗一覧,
},
],
},
{
label: "QRサービス券",
children: [
{
label: "利用履歴",
icon: <ArticleIcon />,
@@ -111,6 +122,12 @@ export default function Navigator(props: DrawerProps) {
{
label: "アカウント",
children: [
{
label: "成り代わり終了",
icon: <SettingsIcon />,
id: PageID.成り代わり終了,
whenIsSwitched: true,
},
{ label: "ログアウト", icon: <SettingsIcon />, id: PageID.LOGOUT },
],
},
@@ -131,7 +148,8 @@ export default function Navigator(props: DrawerProps) {
</ListItemIcon>
<ListItemText>{}</ListItemText>
<ListItemText>
<Typography>{name} 様</Typography>
<Typography>{currentName} 様</Typography>
{!!isSwitched && <Typography color="red">成り代わり中</Typography>}
</ListItemText>
</ListItem>

@@ -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) => <SubGroup {...ele} key={index} />);

if (elements.length === 0) return null;


+ 0
- 31
src/layouts/dashbord/tab/ContractTabs.tsx 查看文件

@@ -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>
);
}

+ 49
- 0
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: (
<Tabs
value={getTabIndex}
textColor="inherit"
// scrollButtons
// allowScrollButtonsMobile
variant="scrollable"
>
{elements}
</Tabs>
),
};
}

+ 10
- 9
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 <Tab {...{ label, navigate: path, key: index }} />;
});
}, [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 {


+ 1
- 1
src/layouts/simple/index.tsx 查看文件

@@ -14,7 +14,7 @@ export default function SimpleLayout() {
<Grid container>
<Grid item xs />
<Grid item xs={5} textAlign="center">
MyPage
HT Dashboard
</Grid>
<Grid item xs />
</Grid>


+ 8
- 3
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;
}

+ 0
- 2
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);
}


+ 10
- 1
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 (
<TableRow hover sx={{ cursor: "pointer" }}>
<TableRow hover sx={{ cursor: "pointer" }} onClick={handleClick}>
<TableCell>{data.name}</TableCell>
<TableCell>{data.email}</TableCell>
<TableCell>


+ 81
- 0
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<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>
);
}

+ 113
- 0
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 (
<>
<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>
);
}

+ 35
- 0
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 (
<SearchConditionContextProvider>
<Page />
</SearchConditionContextProvider>
);
}

function Page() {
const table = useTable<店舗>();

return (
<Box>
<SearchBox table={table} />
<TableBox table={table} />
</Box>
);
}

+ 82
- 0
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<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>
);
}

+ 25
- 0
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 <Page />;
}

function Page() {
return (
<Box>
<Stack spacing={2}>
<詳細情報 />
<デポジットチャージ />
{/* 詳細情報テーブル */}
{/* チャージ */}
{/* 利用履歴 */}
</Stack>
</Box>
);
}

+ 84
- 0
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<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>
);
}

+ 25
- 0
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 <Page />;
}

function Page() {
return (
<Box>
<Stack spacing={2}>
<設定 />
</Stack>
</Box>
);
}

+ 120
- 0
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<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>
);
}

+ 120
- 0
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<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>
);
}

+ 32
- 0
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 (
<Card elevation={1} sx={{ p: 2 }}>
<Stack spacing={2}>
<Typography variant="h6">基本情報</Typography>
<Box>
<SimpleDataList data={listData} />
</Box>
</Stack>
</Card>
);
}

+ 20
- 0
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;
}

+ 10
- 1
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];

+ 19
- 6
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) => {
</Suspense>
);
};
export const Load = (path: string) => {
return Loadable(lazy(() => import(path)));
};

const CommonRoutes = (): RouteObject => ({
element: <SimpleLayout />,
@@ -58,8 +62,19 @@ export function Routes() {
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 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");

+ 25
- 4
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 + "/", "");


+ 124
- 0
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: <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,
},
];
}

+ 85
- 16
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: <Enpty />,
ele: {
element: <Enpty />,
path: getPath(PageID.DASHBOARD_ENPTY),
},
},
{
pageId: PageID.DASHBOARD_OVERVIEW,
element: <Dashboard />,
ele: {
element: <Dashboard />,
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>
<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);
})
.map(({ pageId, ...others }) => ({
...others,
path: getRoute(pageId),
}));
.map(({ ele }) => ele);
}, [currentRole]);

return [


正在加载...
取消
保存