Browse Source

顧客一覧 登録 追加

develop
sosuke.iwabuchi 1 year ago
parent
commit
383bf09d27
15 changed files with 466 additions and 14 deletions
  1. +1
    -1
      src/api/customer.ts
  2. +10
    -8
      src/api/login-user.ts
  3. +9
    -3
      src/auth/route.ts
  4. +6
    -1
      src/components/table/TableHeadCustom.tsx
  5. +1
    -1
      src/hooks/useNavigateCustom.ts
  6. +15
    -0
      src/layouts/dashbord/navigator.tsx
  7. +3
    -0
      src/pages/dashboard/empty.tsx
  8. +87
    -0
      src/pages/dashboard/login-user/顧客ログインユーザ一覧/SearchBox.tsx
  9. +93
    -0
      src/pages/dashboard/login-user/顧客ログインユーザ一覧/TableBox.tsx
  10. +38
    -0
      src/pages/dashboard/login-user/顧客ログインユーザ一覧/index.tsx
  11. +148
    -0
      src/pages/dashboard/login-user/顧客ログインユーザ新規登録/index.tsx
  12. +12
    -0
      src/pages/index.ts
  13. +7
    -0
      src/routes/path.ts
  14. +21
    -0
      src/routes/sub/dashboard.tsx
  15. +15
    -0
      src/storage/cache/顧客マスタ.ts

+ 1
- 1
src/api/customer.ts View File

@@ -1,7 +1,7 @@
import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "api";
import { getUrl } from "./url";

type 顧客マスタ = {
export type 顧客マスタ = {
id: number;
customer_id: string;
customer_name: string;


+ 10
- 8
src/api/login-user.ts View File

@@ -1,7 +1,7 @@
import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "api";
import { getUrl } from "./url";

type 運営会社ログインユーザ = {
export type 運営会社ログインユーザ = {
id: string;
name: string;
email: string;
@@ -14,7 +14,7 @@ export type 顧客ログインユーザ新規登録Request = {
name: string;
email: string;
password: string;
customerCode: string;
customer_code: string;
};
export type 顧客ログインユーザ新規登録Response = {
data: {
@@ -55,19 +55,21 @@ export const 店舗ログインユーザ新規登録 = async (
return res;
};

// -------顧客ログインユーザ新規登録---------------
export type 顧客一覧取得Request = {
// -------顧客ログインユーザ一覧取得---------------
export type 顧客ログインユーザ一覧取得Request = {
email?: string;
name?: string;
};
export type 顧客一覧取得Response = {
export type 顧客ログインユーザ一覧取得Response = {
data: {
list: 運営会社ログインユーザ[];
};
} & APICommonResponse;
export const 顧客一覧取得 = async (param: 顧客一覧取得Request) => {
const res = await request<顧客一覧取得Response>({
url: getUrl(ApiId.QRサービス券取得),
export const 顧客ログインユーザ一覧取得 = async (
param: 顧客ログインユーザ一覧取得Request
) => {
const res = await request<顧客ログインユーザ一覧取得Response>({
url: getUrl(ApiId.顧客ログインユーザ一覧取得),
method: HttpMethod.GET,
data: new URLSearchParams(param),
});


+ 9
- 3
src/auth/route.ts View File

@@ -2,18 +2,24 @@ import { PageID as P } from "pages";
import { UserRole } from "./UserRole";

const 共通ルート = [P.LOGIN, P.LOGOUT];
const 認証後共通ルート = [P.DASHBOARD_ENPTY, P.DASHBOARD_OVERVIEW];

const 認可別許可ルート: {
[route: string]: P[];
} = {
[UserRole.NONE]: [...共通ルート],
[UserRole.ADMIN]: [...共通ルート, P.DASHBOARD_OVERVIEW],
[UserRole.ADMIN]: [
...共通ルート,
...認証後共通ルート,
P.ログインユーザ_顧客一覧,
P.ログインユーザ_顧客新規登録,
],

[UserRole.CUSTOMER]: [...共通ルート, P.DASHBOARD_OVERVIEW],
[UserRole.CUSTOMER]: [...共通ルート, ...認証後共通ルート],

[UserRole.SHOP]: [
...共通ルート,
P.DASHBOARD_OVERVIEW,
...認証後共通ルート,
P.サービス券発行用QRコード,
P.サービス券利用履歴,
],


+ 6
- 1
src/components/table/TableHeadCustom.tsx View File

@@ -57,7 +57,12 @@ export default function TableHeadCustom({
sx,
}: Props) {
return (
<TableHead sx={sx}>
<TableHead
sx={{
backgroundColor: "#b0e0e6",
...sx,
}}
>
<TableRow>
{onSelectAllRows && (
<TableCell padding="checkbox">


+ 1
- 1
src/hooks/useNavigateCustom.ts View File

@@ -47,7 +47,7 @@ export default function useNavigateCustom() {
// 同じURLで遷移要求があった場合、reload設定されていれば同じページを読み込みなおす
// 一旦、空白のページを経由する必要がある
if (currentUrl === newPath && option?.reload) {
navigate(getPath(PageID.NONE));
navigate(getPath(PageID.DASHBOARD_ENPTY));
setTimeout(() => {
navigate(newPath);
}, 50);


+ 15
- 0
src/layouts/dashbord/navigator.tsx View File

@@ -78,6 +78,21 @@ export default function Navigator(props: DrawerProps) {
label: "管理メニュー",
children: [],
},
{
label: "ログインユーザ管理",
children: [
{
label: "顧客一覧",
icon: <ArticleIcon />,
id: PageID.ログインユーザ_顧客一覧,
},
{
label: "顧客新規登録",
icon: <ArticleIcon />,
id: PageID.ログインユーザ_顧客新規登録,
},
],
},
{
label: "QRサービス券",
children: [


+ 3
- 0
src/pages/dashboard/empty.tsx View File

@@ -0,0 +1,3 @@
export default function Empty() {
return null;
}

+ 87
- 0
src/pages/dashboard/login-user/顧客ログインユーザ一覧/SearchBox.tsx View File

@@ -0,0 +1,87 @@
import { Box, Grid, Stack, Typography } from "@mui/material";
import { Dictionary } from "@types";
import {
運営会社ログインユーザ,
顧客ログインユーザ一覧取得,
} from "api/login-user";
import { FormProvider, RHFTextField } from "components/hook-form";
import useAPICall from "hooks/useAPICall";
import useSearchConditionContext from "hooks/useSearchConditionContext";
import { UseTableReturn } from "hooks/useTable";
import { isEqual } from "lodash";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";

type FormProps = {
name: string;
email: string;
};

type CommonProps = {
table: UseTableReturn<運営会社ログインユーザ>;
};
export default function SearchBox({ table }: CommonProps) {
const [lastSendSearchCondition, setLastSendSearchCondition] =
useState<object>({ default: false });
const { condition, initialized, get, addCondition } =
useSearchConditionContext();
const form = useForm<FormProps>({
defaultValues: {
name: "",
email: "",
},
});

const { callAPI: call顧客一覧取得, makeSendData } = useAPICall({
apiMethod: 顧客ログインユーザ一覧取得,
form,
backDrop: true,
onSuccess: ({ data }) => {
table.setRowData(data.list);
},
});

const handleBlur = () => {
addCondition(form.getValues());
};
const handleSubmit = async (data: FormProps) => {
addCondition(data);
};

const fetch = async (data: Dictionary) => {
const sendData = makeSendData(data);
console.log({ sendData });
if (!isEqual(sendData, lastSendSearchCondition)) {
setLastSendSearchCondition(sendData);
call顧客一覧取得(sendData);
}
};

useEffect(() => {
console.log({ initialized, condition });
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 item xs={12} lg={6}>
<Stack>
<Typography>Email</Typography>
<RHFTextField name="email" onBlur={handleBlur} />
</Stack>
</Grid>
</Grid>
</Box>
</FormProvider>
);
}

+ 93
- 0
src/pages/dashboard/login-user/顧客ログインユーザ一覧/TableBox.tsx View File

@@ -0,0 +1,93 @@
import {
Box,
Table,
TableBody,
TableCell,
TableContainer,
TablePagination,
TableRow,
} from "@mui/material";
import { 運営会社ログインユーザ } from "api/login-user";
import TableHeadCustom, {
HeadLabelProps,
} from "components/table/TableHeadCustom";
import { UseTableReturn } from "hooks/useTable";

type CommonProps = {
table: UseTableReturn<運営会社ログインユーザ>;
};
export default function TableBox({ table }: CommonProps) {
const TABLE_HEAD: HeadLabelProps[] = [
{ id: "name", label: "名前", align: "left", needSort: false },
{ id: "email", label: "Email", align: "left", needSort: false },
{ id: "customer_name", label: "顧客名", align: "left", needSort: false },
];
const {
order,
page,
sort,
rowsPerPage,
fetched,
fillteredRow,
isNotFound,
dataLength,
//
onSort,
onChangePage,
onChangeRowsPerPage,
//
setRowData,
//
ROWS_PER_PAGES,
} = table;

return (
<>
<TableContainer>
<Table sx={{ minWidth: 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 }: RowProps) {
return (
<TableRow hover sx={{ cursor: "pointer" }}>
<TableCell>{data.name}</TableCell>
<TableCell>{data.email}</TableCell>
<TableCell>
{data.customer_name}({data.customer_code})
</TableCell>
</TableRow>
);
}

+ 38
- 0
src/pages/dashboard/login-user/顧客ログインユーザ一覧/index.tsx View File

@@ -0,0 +1,38 @@
import { Box } from "@mui/material";
import { 運営会社ログインユーザ } from "api/login-user";
import { SearchConditionContextProvider } from "contexts/SearchConditionContext";
import useDashboard from "hooks/useDashBoard";
import useTable from "hooks/useTable";
import { PageID, TabID } from "pages";
import { useEffect } from "react";
import SearchBox from "./SearchBox";
import TableBox from "./TableBox";

export default function 顧客一覧() {
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>
);
}

+ 148
- 0
src/pages/dashboard/login-user/顧客ログインユーザ新規登録/index.tsx View File

@@ -0,0 +1,148 @@
import { yupResolver } from "@hookform/resolvers/yup";
import { Box, Button, Stack, Typography } from "@mui/material";
import { 顧客マスタ, 顧客マスタ一覧取得 } from "api/customer";
import { 顧客ログインユーザ新規登録 } from "api/login-user";
import {
FormProvider,
RHFAutoComplete,
RHFTextField,
} from "components/hook-form";
import {
AutoCompleteOption,
AutoCompleteOptionType,
getValue,
} from "components/hook-form/RHFAutoComplete";
import StackRow from "components/stack/StackRow";
import useAPICall from "hooks/useAPICall";
import useDashboard from "hooks/useDashBoard";
import useNavigateCustom from "hooks/useNavigateCustom";
import useSnackbarCustom from "hooks/useSnackbarCustom";
import { PageID, TabID } from "pages";
import { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { getPath } from "routes/path";
import 顧客マスタストア from "storage/cache/顧客マスタ";
import { object, string } from "yup";

type FormProps = {
name: string;
email: string;
password: string;
password_retype: string;
customer_code: AutoCompleteOptionType;
};

export default function 顧客ログインユーザ新規登録_() {
const { setHeaderTitle, setTabs } = useDashboard(
PageID.ログインユーザ_顧客新規登録,
TabID.NONE
);

const { success, error } = useSnackbarCustom();
const { navigateWhenChanged } = useNavigateCustom();

const [顧客マスタ, set顧客マスタ] = useState<顧客マスタ[]>([]);

const 顧客マスタOptions: AutoCompleteOption[] = useMemo(() => {
return 顧客マスタ.map((data) => {
return {
label: data.customer_name,
value: data.customer_id,
};
});
}, [顧客マスタ]);

const form = useForm<FormProps>({
defaultValues: {
name: "",
email: "",
password: "",
password_retype: "",
customer_code: "",
},
resolver: yupResolver(
object().shape({
name: string().required("必須項目です"),
email: string().required("必須項目です"),
password: string().required("必須項目です"),
password_retype: string()
.required("必須項目です")
.test("retype", "入力が一致しません", function (value) {
return this.parent.password === value;
}),
customer_code: object().required("必須項目です"),
})
),
});

const { callAPI: call顧客ログインユーザ新規登録 } = useAPICall({
apiMethod: 顧客ログインユーザ新規登録,
form,
backDrop: true,
onSuccess: ({ data }, sendData) => {
success("登録しました");
navigateWhenChanged(
getPath(PageID.ログインユーザ_顧客一覧),
new URLSearchParams({ email: sendData.email })
);
},
onFailed: () => {
error("失敗しました");
},
});

const handleSubmit = (data: FormProps) => {
const customer_code = getValue(data.customer_code);
call顧客ログインユーザ新規登録({
...data,
customer_code,
});
};

useEffect(() => {
setHeaderTitle("ログインユーザ登録");
setTabs(null);

顧客マスタストア.get().then((data) => {
if (data) {
set顧客マスタ(data);
}
});
}, []);

return (
<FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}>
<Box>
<Stack>
<Box>
<Typography>名称</Typography>
<RHFTextField name="name" />
</Box>
<Box>
<Typography>Email</Typography>
<RHFTextField type="email" name="email" />
</Box>
<Box>
<Typography>パスワード</Typography>
<RHFTextField type="password" name="password" />
</Box>
<Box>
<Typography>パスワード(再入力)</Typography>
<RHFTextField type="password" name="password_retype" />
</Box>
<Box>
<Typography>顧客</Typography>
<RHFAutoComplete
name="customer_code"
options={顧客マスタOptions}
size="small"
/>
</Box>
<StackRow>
<Button type="submit">登録</Button>
</StackRow>
</Stack>
</Box>
</FormProvider>
);
}

+ 12
- 0
src/pages/index.ts View File

@@ -2,15 +2,27 @@ let id = 0;
export const PageID = {
NONE: id++,

// 認証関連 ----------------------------------
LOGIN: id++,
LOGOUT: id++,

// ダッシュボード系 START ----------------------------------
DASHBOARD_ENPTY: id++,
DASHBOARD_OVERVIEW: id++,

// 顧客管理
顧客一覧: id++,

// ログインユーザー管理
ログインユーザ_顧客一覧: id++,
ログインユーザ_顧客新規登録: id++,
ログインユーザ_店舗一覧: id++,

サービス券発行用QRコード: id++,
サービス券利用履歴: id++,

QRサービス券発行申請: id++,
// ダッシュボード系 END ----------------------------------

PAGE_403: id++,
PAGE_404: id++,


+ 7
- 0
src/routes/path.ts View File

@@ -29,7 +29,14 @@ const getTabId = (key: PathKey): TabID => {
};

const PATHS_DASHBOARD = {
[makePathKey(PageID.DASHBOARD_ENPTY)]: "/dashboard/loading",
[makePathKey(PageID.DASHBOARD_OVERVIEW)]: "/dashboard",
[makePathKey(PageID.ログインユーザ_顧客一覧)]:
"/dashboard/login-user/customer/list",
[makePathKey(PageID.ログインユーザ_顧客新規登録)]:
"/dashboard/login-user/customer/register",
[makePathKey(PageID.ログインユーザ_店舗一覧)]:
"/dashboard/login-user/shop/list",

[makePathKey(PageID.サービス券発行用QRコード)]: "/dashboard/qrcode/generate",
[makePathKey(PageID.サービス券利用履歴)]: "/dashboard/qrcode/history",


+ 21
- 0
src/routes/sub/dashboard.tsx View File

@@ -12,6 +12,7 @@ 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 サービス券発行用QRコード = Loadable(
@@ -20,12 +21,32 @@ export default function DashboardRoutes(): RouteObject[] {
const サービス券利用履歴 = Loadable(
lazy(() => import("pages/dashboard/qrcode/サービス券利用履歴"))
);
const 顧客ログインユーザ一覧 = Loadable(
lazy(() => import("pages/dashboard/login-user/顧客ログインユーザ一覧"))
);
const 顧客ログインユーザ新規登録 = Loadable(
lazy(
() => import("pages/dashboard/login-user/顧客ログインユーザ新規登録")
)
);

const allChildren = [
{
pageId: PageID.DASHBOARD_ENPTY,
element: <Enpty />,
},
{
pageId: PageID.DASHBOARD_OVERVIEW,
element: <Dashboard />,
},
{
pageId: PageID.ログインユーザ_顧客一覧,
element: <顧客ログインユーザ一覧 />,
},
{
pageId: PageID.ログインユーザ_顧客新規登録,
element: <顧客ログインユーザ新規登録 />,
},
{
pageId: PageID.サービス券発行用QRコード,
element: <サービス券発行用QRコード />,


+ 15
- 0
src/storage/cache/顧客マスタ.ts View File

@@ -0,0 +1,15 @@
import { 顧客マスタ, 顧客マスタ一覧取得 } from "api/customer";

class 顧客マスタストア {
private list: 顧客マスタ[] | undefined = undefined;

async get() {
if (this.list === undefined) {
const res = await 顧客マスタ一覧取得({});
this.list = res?.data.list ?? [];
}
return this.list;
}
}

export default new 顧客マスタストア();

Loading…
Cancel
Save