Browse Source

全体整備

develop
sosuke.iwabuchi 2 years ago
parent
commit
38b4bbf13e
27 changed files with 932 additions and 124 deletions
  1. +2
    -0
      package.json
  2. +0
    -15
      src/api/app.ts
  3. +21
    -0
      src/api/app/index.ts
  4. +40
    -0
      src/api/app/receipt-issuing-order.ts
  5. +2
    -4
      src/api/custom/hello-techno/receipt-issuing-order.ts
  6. +8
    -0
      src/api/index.ts
  7. +16
    -3
      src/api/receipt-issuing-order.ts
  8. +2
    -0
      src/api/url.ts
  9. +1
    -0
      src/codes/page.ts
  10. +59
    -0
      src/codes/prefcode.ts
  11. +0
    -40
      src/codes/receipt-issuing-order.ts
  12. +6
    -0
      src/components/hook-form/ex/RHFPrefCodeSelect.tsx
  13. +56
    -2
      src/contexts/AppContext.tsx
  14. +86
    -0
      src/hooks/useDialog.tsx
  15. +198
    -0
      src/pages/app/MailOrder.tsx
  16. +137
    -27
      src/pages/app/ReceiptIssuingOrder.tsx
  17. +119
    -0
      src/pages/app/hooks/useInputMailStep.tsx
  18. +103
    -0
      src/pages/app/hooks/useReceiptIssuingOrderConfirm.tsx
  19. +47
    -0
      src/pages/app/hooks/useReceiptIssuingOrderSelectHowToGet.tsx
  20. +1
    -3
      src/pages/dashboard/receipt-issuing-order/create.tsx
  21. +1
    -22
      src/pages/dashboard/receipt-issuing-order/hooks/useConfirm.tsx
  22. +0
    -6
      src/pages/dashboard/receipt-issuing-order/hooks/useInputReceiptStep.tsx
  23. +1
    -2
      src/pages/dashboard/receipt-issuing-order/list.tsx
  24. +5
    -0
      src/routes/index.tsx
  25. +2
    -0
      src/routes/path.ts
  26. +9
    -0
      src/theme/index.tsx
  27. +10
    -0
      yarn.lock

+ 2
- 0
package.json View File

@@ -21,6 +21,7 @@
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/react-router-dom": "^5.3.3",
"@types/sprintf-js": "^1.1.2",
"axios": "^1.4.0",
"date-fns": "^2.30.0",
"lodash": "^4.17.21",
@@ -30,6 +31,7 @@
"react-hook-form": "^7.43.9",
"react-router-dom": "^6.11.0",
"react-scripts": "5.0.1",
"sprintf-js": "^1.1.2",
"typescript": "^4.4.2",
"web-vitals": "^2.1.0",
"yup": "^1.1.1"


+ 0
- 15
src/api/app.ts View File

@@ -1,15 +0,0 @@
import { ApiId, HttpMethod, request } from ".";
import { getUrl } from "./url";

export type CheckTokenRequest = {
access_token: string;
};

export const checkToken = async (data: CheckTokenRequest) => {
const res = await request({
url: getUrl(ApiId.APP_TOKEN_CHECK),
method: HttpMethod.GET,
data: new URLSearchParams(data),
});
return res;
};

+ 21
- 0
src/api/app/index.ts View File

@@ -0,0 +1,21 @@
import { APICommonResponse, ApiId, HttpMethod, request } from "..";
import { ReceiptIssuingOrder } from "../receipt-issuing-order";
import { getUrl } from "../url";

export type CheckTokenRequest = {
access_token: string;
};

export type CheckTokenResponse = {
data: {
receipt_issuing_order: ReceiptIssuingOrder;
};
} & APICommonResponse;
export const checkToken = async (data: CheckTokenRequest) => {
const res = await request<CheckTokenResponse>({
url: getUrl(ApiId.APP_TOKEN_CHECK),
method: HttpMethod.GET,
data: new URLSearchParams(data),
});
return res;
};

+ 40
- 0
src/api/app/receipt-issuing-order.ts View File

@@ -0,0 +1,40 @@
import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "..";
import { ReceiptIssuingOrder } from "../receipt-issuing-order";
import { getUrl } from "../url";

// 領収証確定
export type ConfirmRequest = {
id: string;
receipt_name: string;
timestamp: string;
};

export const confirm = async (data: ConfirmRequest) => {
const res = await request({
url: getUrl(ApiId.RECEIPT_ISSUING_ORDER_CONFIRM),
method: HttpMethod.POST,
data: makeParam(data),
});
return res;
};

// 領収証郵送依頼 -------------------------------
export type MailOrderRequest = {
id: string;
mail_pref_code: string;
mail_zip_code: string;
mail_address1: string;
mail_address2: string;
mail_address3?: string;
mail_name: string;
timestamp: string;
};

export const mailRequest = async (data: MailOrderRequest) => {
const res = await request({
url: getUrl(ApiId.RECEIPT_ISSUING_ORDER_MAIL_ORDER),
method: HttpMethod.POST,
data: makeParam(data),
});
return res;
};

+ 2
- 4
src/api/custom/hello-techno/receipt-issuing-order.ts View File

@@ -6,7 +6,6 @@ import {
request,
} from "api";
import { getUrl } from "api/url";
import { ReceiptIssuingOrderStatus } from "codes/receipt-issuing-order";

export type HTCustomer = {
customer_code: string;
@@ -28,8 +27,7 @@ export type CreateReceiptIssuingOrderRequest = {
customer_code: string;
parking_management_code: string;
adjust_seq_no?: string | number;
receipt_name: string;
receipt_use_datetime: Date | null;
receipt_use_date: Date | null;
receipt_amount: string | number;
memo?: string;
sms_phone_number: string;
@@ -59,7 +57,7 @@ export type ReceiptIssuingOrder = {
parking_management_code: string;
customer_name: string;
parking_name: string;
status: ReceiptIssuingOrderStatus;
status_name: string;
handler_id: string;
handler_name: string;
};


+ 8
- 0
src/api/index.ts View File

@@ -7,15 +7,20 @@ import axios from "utils/axios";

let id = 0;
export const ApiId = {
// 共通---------------------------------------
CSRF_TOKEN: id++,
ME: id++,

LOGIN: id++,
LOGOUT: id++,

// APP向け------------------------------------
APP_TOKEN_CHECK: id++,
RECEIPT_ISSUING_ORDER_CONFIRM: id++,
DOWNLOAD_RECEIPT: id++,
RECEIPT_ISSUING_ORDER_MAIL_ORDER: id++,

// DASHBOARD向け-------------------------------
RECEIPT_ISSUING_ORDERS: id++,
RECEIPT_ISSUING_ORDER: id++,
RECEIPT_ISSUING_ORDER_CREATE: id++,
@@ -245,6 +250,9 @@ export async function apiRequest<
}
return res;
} catch (e) {
if (setSending) {
setSending(false);
}
console.error(e);
if (onFailed) {
onFailed(null);


+ 16
- 3
src/api/receipt-issuing-order.ts View File

@@ -1,15 +1,28 @@
import { ReceiptIssuingOrderStatus } from "codes/receipt-issuing-order";
import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from ".";
import { getUrl } from "./url";

export type ReceiptIssuingOrder = {
id: string;
status: ReceiptIssuingOrderStatus;

access_token_expires_at: string;

receipt_use_date?: string;
receipt_shop_name?: string;
receipt_issuer?: string;
receipt_purpose?: string;
receipt_name?: string;
receipt_amount?: string;
confirmed: boolean;

status_order_mail_datetime?: string;
status_mail_post_date?: string;

updated_at: string;
};

// 領収証発行一覧取得 -----------------------
export type ReceiptIssuingOrdersRequest = {
address?: string;
status?: ReceiptIssuingOrderStatus;
};

export type ReceiptIssuingOrdersResponse = {


+ 2
- 0
src/api/url.ts View File

@@ -9,7 +9,9 @@ const urls = {
[A.LOGOUT]: "logout",

[A.APP_TOKEN_CHECK]: "app-token-check",
[A.RECEIPT_ISSUING_ORDER_CONFIRM]: "receipt-issuing-order/confirm",
[A.DOWNLOAD_RECEIPT]: "receipt/download",
[A.RECEIPT_ISSUING_ORDER_MAIL_ORDER]: "receipt-issuing-order/mail-order",

[A.RECEIPT_ISSUING_ORDERS]: "receipt-issuing-orders",



+ 1
- 0
src/codes/page.ts View File

@@ -6,6 +6,7 @@ export const PageID = {
LOGOUT: id++,

APP_RECEIPT_ISSUING_ORDER_INDEX: id++,
APP_RECEIPT_ISSUING_ORDER_MAIL_ORDER: id++,

DASHBOARD_OVERVIEW: id++,



+ 59
- 0
src/codes/prefcode.ts View File

@@ -0,0 +1,59 @@
import { SelectOptionProps } from "components/hook-form/RHFSelect";

export const prefCodeOptions: SelectOptionProps[] = [
{ value: "01", label: "北海道" },
{ value: "02", label: "青森県" },
{ value: "03", label: "岩手県" },
{ value: "04", label: "宮城県" },
{ value: "05", label: "秋田県" },
{ value: "06", label: "山形県" },
{ value: "07", label: "福島県" },
{ value: "08", label: "茨城県" },
{ value: "09", label: "栃木県" },
{ value: "10", label: "群馬県" },
{ value: "11", label: "埼玉県" },
{ value: "12", label: "千葉県" },
{ value: "13", label: "東京都" },
{ value: "14", label: "神奈川県" },
{ value: "15", label: "新潟県" },
{ value: "16", label: "富山県" },
{ value: "17", label: "石川県" },
{ value: "18", label: "福井県" },
{ value: "19", label: "山梨県" },
{ value: "20", label: "長野県" },
{ value: "21", label: "岐阜県" },
{ value: "22", label: "静岡県" },
{ value: "23", label: "愛知県" },
{ value: "24", label: "三重県" },
{ value: "25", label: "滋賀県" },
{ value: "26", label: "京都府" },
{ value: "27", label: "大阪府" },
{ value: "28", label: "兵庫県" },
{ value: "29", label: "奈良県" },
{ value: "30", label: "和歌山県" },
{ value: "31", label: "鳥取県" },
{ value: "32", label: "島根県" },
{ value: "33", label: "岡山県" },
{ value: "34", label: "広島県" },
{ value: "35", label: "山口県" },
{ value: "36", label: "徳島県" },
{ value: "37", label: "香川県" },
{ value: "38", label: "愛媛県" },
{ value: "39", label: "高知県" },
{ value: "40", label: "福岡県" },
{ value: "41", label: "佐賀県" },
{ value: "42", label: "長崎県" },
{ value: "43", label: "熊本県" },
{ value: "44", label: "大分県" },
{ value: "45", label: "宮崎県" },
{ value: "46", label: "鹿児島県" },
{ value: "47", label: "沖縄県" },
];

export function getPrefName(code: string): string {
const pref = prefCodeOptions.find((ele) => {
return ele.value === code;
});

return pref?.label ?? "-";
}

+ 0
- 40
src/codes/receipt-issuing-order.ts View File

@@ -1,40 +0,0 @@
export const ReceiptIssuingOrderStatus = {
NONE: 0,
CREATED: 100, // 新規作成
SMS_SENDING: 200, // SMS送信中
SMS_RECEIVED: 300, // SMS送信完了
SMS_OPENED: 400, // SMS開封

// 郵送関連
MAIL_REQUEST: 500, // 郵送依頼中
PREPARING_FOR_MAIL: 510, // 郵送準備中
MAIL_DONE: 520, // 郵送完了

// Email送信関連
EMAIL_SENDING: 600, // Email送信中
EMAIL_DONE: 610, // Email送信完了

// PDFダウンロード
DOWNLOAD_DONE: 700, // ダウンロード完了
} as const;

export type ReceiptIssuingOrderStatus =
(typeof ReceiptIssuingOrderStatus)[keyof typeof ReceiptIssuingOrderStatus];

const ReceiptIssuingOrderStatusName = {
[ReceiptIssuingOrderStatus.NONE]: "",
[ReceiptIssuingOrderStatus.CREATED]: "受付済み",
[ReceiptIssuingOrderStatus.SMS_SENDING]: "SMS送信中",
[ReceiptIssuingOrderStatus.SMS_RECEIVED]: "SMS送信完了",
[ReceiptIssuingOrderStatus.SMS_OPENED]: "SMS開封済み",
[ReceiptIssuingOrderStatus.MAIL_REQUEST]: "郵送依頼中",
[ReceiptIssuingOrderStatus.PREPARING_FOR_MAIL]: "郵送準備中",
[ReceiptIssuingOrderStatus.MAIL_DONE]: "郵送投函済み",
[ReceiptIssuingOrderStatus.EMAIL_SENDING]: "メール送信中",
[ReceiptIssuingOrderStatus.EMAIL_DONE]: "メール送信済み",
[ReceiptIssuingOrderStatus.DOWNLOAD_DONE]: "ダウンロード済み",
} as const;

export function getStatusName(status: ReceiptIssuingOrderStatus): string {
return ReceiptIssuingOrderStatusName[status];
}

+ 6
- 0
src/components/hook-form/ex/RHFPrefCodeSelect.tsx View File

@@ -0,0 +1,6 @@
import { prefCodeOptions } from "codes/prefcode";
import RHFSelect, { RHFSelectProps, SelectOptionProps } from "../RHFSelect";

export default function RHFPrefCodeSelect({ ...other }: RHFSelectProps) {
return <RHFSelect {...other} options={prefCodeOptions} />;
}

+ 56
- 2
src/contexts/AppContext.tsx View File

@@ -1,24 +1,45 @@
import { HasChildren } from "@types";
import { checkToken } from "api/app";
import {
ReceiptIssuingOrder,
getReceiptIssuingOrders,
} from "api/receipt-issuing-order";
import { PageID } from "codes/page";
import useAPICall from "hooks/useAPICall";
import { createContext, useState } from "react";
import useNavigateCustom from "hooks/useNavigateCustom";
import { createContext, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { getPath } from "routes/path";

type App = {
token: string;
tokenResult: "cheking" | "ok" | "ng";
receiptIssuingOrder: ReceiptIssuingOrder | null;
setToken: (token: string) => void;
fetch: VoidFunction;
navigateToHome: VoidFunction;
};

export const AppContext = createContext<App>({
token: "",
tokenResult: "cheking",
receiptIssuingOrder: null,
setToken: (token: string) => {},
fetch: () => {},
navigateToHome: () => {},
});

type Props = HasChildren;
export function AppContextProvider({ children }: Props) {
const { navigateWhenChanged } = useNavigateCustom();

const { token: paramToken } = useParams();

const [token, _setToken] = useState("");

const [receiptIssuingOrder, setReceiptIssuingOrder] =
useState<ReceiptIssuingOrder | null>(null);

const [tokenResult, setTokenResult] = useState<"cheking" | "ok" | "ng">(
"cheking"
);
@@ -28,9 +49,11 @@ export function AppContextProvider({ children }: Props) {
onSuccess: (res, sendData) => {
setTokenResult("ok");
_setToken(sendData.access_token);
setReceiptIssuingOrder(res.data.receipt_issuing_order);
},
onFailed: () => {
setTokenResult("ng");
navigateWhenChanged(getPath(PageID.PAGE_404));
},
});

@@ -40,8 +63,39 @@ export function AppContextProvider({ children }: Props) {
});
};

const fetch = () => {
if (token) {
setToken(token);
}
};

const navigateToHome = () => {
const path = getPath(PageID.APP_RECEIPT_ISSUING_ORDER_INDEX, {
query: { token: token },
});
navigateWhenChanged(path);
};

useEffect(() => {
if (paramToken && !token) {
setToken(paramToken);
} else if (!token && !paramToken) {
setTokenResult("ng");
navigateWhenChanged(getPath(PageID.PAGE_404));
}
}, [paramToken]);

return (
<AppContext.Provider value={{ token, tokenResult, setToken }}>
<AppContext.Provider
value={{
token,
tokenResult,
receiptIssuingOrder,
setToken,
fetch,
navigateToHome,
}}
>
{children}
</AppContext.Provider>
);


+ 86
- 0
src/hooks/useDialog.tsx View File

@@ -0,0 +1,86 @@
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
} from "@mui/material";
import { ReactNode, useState } from "react";

type Props = {
message?: string;
onClose?: VoidFunction;
onAgree?: VoidFunction;
onDisagree?: VoidFunction;
};

export type UseDialogReturn = {
show: boolean;
open: VoidFunction;
close: VoidFunction;
setShow: (show: boolean) => void;
element: ReactNode;
};

export function useDialog({
message,
onClose,
onAgree,
onDisagree,
}: Props = {}): UseDialogReturn {
const [show, setShow] = useState(false);

const open = () => {
setShow(true);
};

const close = () => {
if (onClose) {
onClose();
}
setShow(false);
};

const agree = () => {
if (onAgree) {
onAgree();
}
setShow(false);
};

const disagree = () => {
if (onDisagree) {
onDisagree();
}
setShow(false);
};

const element = (
<Dialog open={show} onClose={close}>
<DialogTitle>確認</DialogTitle>
{message && (
<DialogContent>
<DialogContentText>{message}</DialogContentText>
</DialogContent>
)}
<DialogActions>
<Button onClick={disagree}>CANCEL</Button>
<Button onClick={agree}>OK</Button>
</DialogActions>
</Dialog>
);

return {
// param
show,

// Element
element,
// function

open,
close,
setShow,
};
}

+ 198
- 0
src/pages/app/MailOrder.tsx View File

@@ -0,0 +1,198 @@
import {
Box,
Button,
Divider,
Paper,
Stack,
Step,
StepLabel,
Stepper,
Table,
TableBody,
TableCell,
TableRow,
Typography,
} from "@mui/material";
import { HasChildren } from "@types";
import { getPrefName } from "codes/prefcode";
import useApp from "hooks/useApp";
import useNavigateCustom from "hooks/useNavigateCustom";
import { useMemo, useState } from "react";
import useInputMailStep from "./hooks/useInputMailStep";
import useAPICall from "hooks/useAPICall";
import { mailRequest } from "api/app/receipt-issuing-order";
import useSnackbarCustom from "hooks/useSnackbarCustom";

type TableRowCustomProps = {
title: string;
value: string;
};
const TableRowCustom = ({ title, value }: TableRowCustomProps) => {
return (
<TableRow>
<TableCell sx={{ borderRight: "1px solid rgba(224, 224, 224, 1)" }}>
{title}
</TableCell>
<TableCell>{value}</TableCell>
</TableRow>
);
};

type SectionProps = {
title: string;
subtitle?: string;
} & HasChildren;
const Section = ({ title, subtitle, children }: SectionProps) => {
return (
<Paper
sx={{ py: 2, border: "1px solid rgba(224, 224, 224, 1)" }}
elevation={0}
>
<Box>
<Typography variant="subtitle1">{title}</Typography>
{subtitle && <Typography variant="body2">{subtitle}</Typography>}
</Box>
<Box sx={{ mt: 2 }}>{children}</Box>
</Paper>
);
};

export default function MailOrder() {
const {
tokenResult,
navigateToHome,
fetch,
receiptIssuingOrder: order,
} = useApp();

const { success, error } = useSnackbarCustom();

const { navigate } = useNavigateCustom();

const [mode, setMode] = useState<"input" | "confirm" | "done">("input");

const input = useInputMailStep({
onNext: () => {
setMode("confirm");
},
onPrev: () => {
navigate(-1);
},
});

const requestMailAPI = useAPICall({
apiMethod: mailRequest,
onSuccess: () => {
fetch();
setMode("done");
},
onFailed: () => {
error("登録失敗");
},
});

const currentStep = useMemo(() => {
if (mode === "input") return 0;
if (mode === "confirm") return 1;
if (mode === "done") return 2;
}, [mode]);

const handleConfirm = () => {
if (!order) return;
requestMailAPI.callAPI({
id: order.id,
timestamp: order.updated_at,
...input.values(),
});
};

if (tokenResult !== "ok") {
return null;
}

return (
<>
<Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center">
<Stack spacing={3}>
<Box>
<Typography variant="h5">領収証郵送依頼</Typography>
</Box>

<Section title="">
<Stepper activeStep={currentStep}>
<Step>
<StepLabel>郵送先情報入力</StepLabel>
</Step>
<Step>
<StepLabel>確認</StepLabel>
</Step>
<Step>
<StepLabel>完了</StepLabel>
</Step>
</Stepper>
{mode === "input" && input.element}
{mode === "confirm" && (
<Stack spacing={2} sx={{ p: 1, py: 3, m: 1 }}>
<Typography variant="h5">郵送先情報確認</Typography>
<Table>
<TableBody>
<TableRowCustom
title="都道府県"
value={getPrefName(input.values("mail_pref_code"))}
/>
<TableRowCustom
title="郵便番号"
value={"〒" + input.values("mail_zip_code")}
/>
<TableRowCustom
title="市区町村"
value={input.values("mail_address1")}
/>
<TableRowCustom
title="番地等"
value={input.values("mail_address2")}
/>
<TableRowCustom
title="建物名・部屋番号等"
value={input.values("mail_address3")}
/>
<TableRowCustom
title="宛名"
value={input.values("mail_name")}
/>
</TableBody>
</Table>
<Box>
<Stack>
<Box>郵送先に間違いがないか確認してください。</Box>
<Box>投函まで数営業日を要しますのでご了承ください。</Box>
</Stack>
</Box>
<Stack direction="row" spacing={2}>
<Button
variant="text"
onClick={() => {
setMode("input");
}}
>
戻る
</Button>
<Button variant="contained" onClick={handleConfirm}>
確定
</Button>
</Stack>
</Stack>
)}
{mode === "done" && (
<Stack spacing={2} sx={{ p: 1, py: 3, m: 1 }}>
<Box>郵送依頼を受付いたしました。</Box>
<Box>到着までしばらくお待ちください。</Box>
<Button onClick={navigateToHome}>戻る</Button>
</Stack>
)}
</Section>
</Stack>
</Box>
</>
);
}

+ 137
- 27
src/pages/app/ReceiptIssuingOrder.tsx View File

@@ -1,25 +1,85 @@
import { Box, Button, Paper, Stack, Typography } from "@mui/material";
import {
Box,
Button,
Paper,
Stack,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
TextField,
Typography,
styled,
} from "@mui/material";
import { HasChildren } from "@types";
import { ApiId } from "api";
import { confirm } from "api/app/receipt-issuing-order";
import { getFullUrl } from "api/url";
import useAPICall from "hooks/useAPICall";
import useApp from "hooks/useApp";
import { useEffect, useMemo } from "react";
import { useDialog } from "hooks/useDialog";
import useSnackbarCustom from "hooks/useSnackbarCustom";
import React, { ReactNode, useEffect, useMemo, useRef, useState } from "react";
import { useParams } from "react-router-dom";
import { sprintf } from "sprintf-js";
import { formatDateStr, formatDateTimeStr } from "utils/datetime";
import useReceiptIssuingOrderConfirm from "./hooks/useReceiptIssuingOrderConfirm";
import useReceiptIssuingOrderSelectHowToGet from "./hooks/useReceiptIssuingOrderSelectHowToGet";

export default function ReceiptIssuingOrder() {
const { token: paramToken } = useParams();
type TableRowCustomProps = {
title: string;
value: string;
};
const TableRowCustom = ({ title, value }: TableRowCustomProps) => {
return (
<TableRow>
<TableCell sx={{ borderRight: "1px solid rgba(224, 224, 224, 1)" }}>
{title}
</TableCell>
<TableCell>{value}</TableCell>
</TableRow>
);
};

type SectionProps = {
title: string;
subtitle?: string;
} & HasChildren;
const Section = ({ title, subtitle, children }: SectionProps) => {
return (
<Paper
sx={{ py: 2, border: "1px solid rgba(224, 224, 224, 1)" }}
elevation={0}
>
<Box>
<Typography variant="subtitle1">{title}</Typography>
{subtitle && <Typography variant="body2">{subtitle}</Typography>}
</Box>
<Box sx={{ mt: 2 }}>{children}</Box>
</Paper>
);
};

const { token, setToken, tokenResult } = useApp();
export default function ReceiptIssuingOrder() {
const { tokenResult, receiptIssuingOrder: order } = useApp();

const downloadUrl = useMemo(() => {
return getFullUrl(ApiId.DOWNLOAD_RECEIPT) + "?access_token=" + token;
}, [token]);
const confirm = useReceiptIssuingOrderConfirm();
const selectHowToGet = useReceiptIssuingOrderSelectHowToGet();

useEffect(() => {
if (paramToken) {
setToken(paramToken);
console.log("render");
const tokenExpiresAtStr = useMemo(() => {
if (order) {
return formatDateTimeStr(order.access_token_expires_at);
}
}, []);
return "-";
}, [order]);

const mailOrderStatusStr = useMemo(() => {
if (!order) return "-";
if (order.status_mail_post_date) return "投函済み";
if (order.status_order_mail_datetime) return "郵送依頼確認中";
return "-";
}, [order]);

if (tokenResult === "cheking") {
return null;
@@ -30,19 +90,69 @@ export default function ReceiptIssuingOrder() {
}

return (
<Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center">
<Stack spacing={3}>
<Typography variant="h5">領収証発行依頼</Typography>

<Paper>
<Typography variant="h5">状況</Typography>
</Paper>
<Paper>
<Button href={downloadUrl} variant="contained">
PDFダウンロード
</Button>
</Paper>
</Stack>
</Box>
<>
<Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center">
<Stack spacing={3}>
<Box>
<Typography variant="h5">領収証発行依頼</Typography>
<Typography variant="body2">
ページ有効期限:{tokenExpiresAtStr}まで
</Typography>
</Box>

<Section title="申込内容">
<Table>
<TableBody>
<TableRowCustom
title="店舗名"
value={order?.receipt_shop_name ?? "-"}
/>
<TableRowCustom
title="金額"
value={
order?.receipt_amount
? sprintf("%d円", order.receipt_amount)
: "-"
}
/>
<TableRowCustom
title="利用日"
value={
order?.receipt_use_date
? formatDateStr(order.receipt_use_date)
: "-"
}
/>
<TableRowCustom
title="宛名"
value={order?.receipt_name ?? "-"}
/>
</TableBody>
</Table>
</Section>

{order?.status_order_mail_datetime && (
<Section title="郵送依頼状況">
<Table sx={{ borderBottom: "none" }}>
<TableBody>
<TableRow />
<TableRowCustom title="状況" value={mailOrderStatusStr} />
</TableBody>
</Table>
</Section>
)}

{confirm.shouldShow && (
<Section title="宛名入力">{confirm.element}</Section>
)}

{selectHowToGet.shouldShow && (
<Section title="領収証取得方法選択">
{selectHowToGet.element}
</Section>
)}
</Stack>
</Box>
</>
);
}

+ 119
- 0
src/pages/app/hooks/useInputMailStep.tsx View File

@@ -0,0 +1,119 @@
import { yupResolver } from "@hookform/resolvers/yup";
import { Box, Button, Divider, Stack, Typography } from "@mui/material";
import { HasChildren } from "@types";
import { FormProvider, RHFTextField } from "components/hook-form";
import RHFDatePicker from "components/hook-form/RHFDatePicker";
import RHFPrefCodeSelect from "components/hook-form/ex/RHFPrefCodeSelect";
import useNavigateCustom from "hooks/useNavigateCustom";
import { useForm } from "react-hook-form";
import * as Yup from "yup";

type AreaBoxProps = {
title: string;
} & HasChildren;
function AreaBox({ title, children }: AreaBoxProps) {
return (
<Box sx={{ maxWidth: 500 }}>
<Typography variant="subtitle1">{title}</Typography>
{children}
</Box>
);
}

type FormProps = {
mail_pref_code: string;
mail_zip_code: string;
mail_address1: string;
mail_address2: string;
mail_address3: string;
mail_name: string;
};

type Props = {
onNext?: VoidFunction;
onPrev?: VoidFunction;
};
export default function useInputMailStep({ onNext, onPrev }: Props) {
const form = useForm<FormProps>({
defaultValues: {
mail_pref_code: "",
mail_zip_code: "",
mail_address1: "",
mail_address2: "",
mail_address3: "",
mail_name: "",
},
resolver: yupResolver(
Yup.object().shape({
mail_pref_code: Yup.string()
.required("必須項目です")
.typeError("必須項目です"),
mail_zip_code: Yup.string()
.required("必須項目です")
.matches(/^[0-9]{7}$/, "半角数値7桁を入力してください"),
mail_address1: Yup.string()
.required("必須項目です")
.max(100, "100文字以内で入力してください"),
mail_address2: Yup.string()
.required("必須項目です")
.max(100, "100文字以内で入力してください"),
mail_address3: Yup.string().max(100, "100文字以内で入力してください"),
mail_name: Yup.string()
.required("必須項目です")
.max(100, "100文字以内で入力してください"),
})
),
});

const handleSubmit = () => {
if (onNext) {
onNext();
}
};

const handlePrev = () => {
if (onPrev) {
onPrev();
}
};

const element = (
<FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}>
<Stack spacing={2} sx={{ p: 1, m: 1 }} textAlign="left">
<AreaBox title="都道府県">
<RHFPrefCodeSelect name="mail_pref_code" size="small" />
</AreaBox>
<AreaBox title="郵便番号">
<Typography variant="body2">ハイフン無の7桁</Typography>
<RHFTextField
name="mail_zip_code"
InputProps={{ startAdornment: <div>〒</div> }}
/>
</AreaBox>
<AreaBox title="市区町村">
<RHFTextField name="mail_address1" />
</AreaBox>
<AreaBox title="番地等">
<RHFTextField name="mail_address2" />
</AreaBox>
<AreaBox title="建物名・部屋番号等">
<RHFTextField name="mail_address3" />
</AreaBox>
<Divider />
<AreaBox title="宛先名">
<RHFTextField name="mail_name" />
</AreaBox>
<Stack direction="row" spacing={2}>
<Button variant="text" onClick={handlePrev}>
戻る
</Button>
<Button variant="contained" type="submit">
次へ
</Button>
</Stack>
</Stack>
</FormProvider>
);

return { element, values: form.getValues, setValue: form.setValue };
}

+ 103
- 0
src/pages/app/hooks/useReceiptIssuingOrderConfirm.tsx View File

@@ -0,0 +1,103 @@
import { yupResolver } from "@hookform/resolvers/yup";
import { Box, Button, Stack, Typography } from "@mui/material";
import { confirm } from "api/app/receipt-issuing-order";
import { FormProvider, RHFTextField } from "components/hook-form";
import useAPICall from "hooks/useAPICall";
import useApp from "hooks/useApp";
import useBackDrop from "hooks/useBackDrop";
import { useDialog } from "hooks/useDialog";
import useSnackbarCustom from "hooks/useSnackbarCustom";
import { useEffect, useMemo } from "react";
import { useForm } from "react-hook-form";
import { sprintf } from "sprintf-js";
import * as Yup from "yup";

type FormProps = {
receipt_name: string;
};

const schema = Yup.object().shape({
receipt_name: Yup.string().required("必須項目です"),
});

export default function useReceiptIssuingOrderConfirm() {
const { receiptIssuingOrder: order, fetch } = useApp();
const { success, error } = useSnackbarCustom();
const { setShowBackDrop } = useBackDrop();

const shouldShow = useMemo(() => {
return !order?.confirmed;
}, [order]);

const form = useForm<FormProps>({
defaultValues: {
receipt_name: "",
},
resolver: yupResolver(schema),
});

const receiptName = form.watch("receipt_name");

const confirmMessage = useMemo(() => {
return sprintf("宛名[%s]でよろしいでしょうか。", receiptName);
}, [receiptName]);
const confirmDialog = useDialog({
message: confirmMessage,
onAgree: () => {
if (!order) return;
api.callAPI({
id: order.id,
receipt_name: receiptName,
timestamp: order.updated_at,
});
},
});

const api = useAPICall({
apiMethod: confirm,
onSuccess: () => {
success("登録しました");
fetch();
},
onFailed: () => {
error("失敗しました");
},
});

const handleSubmit = () => {
confirmDialog.setShow(true);
};

useEffect(() => {
setShowBackDrop(api.sending);
}, [api.sending]);

const element = (
<FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}>
<Stack spacing={1}>
<Box>
<Typography variant="body2">
領収証の宛名を入力してください
</Typography>
<Typography variant="body2">
一度登録すると変更できないので、ご注意ください
</Typography>
</Box>
<Box sx={{ px: 2 }}>
<RHFTextField label="宛名" name="receipt_name" />
</Box>
<Box>
<Button type="submit" variant="contained">
登録
</Button>
</Box>
</Stack>
{confirmDialog.element}
</FormProvider>
);

return {
element,
shouldShow,
};
}

+ 47
- 0
src/pages/app/hooks/useReceiptIssuingOrderSelectHowToGet.tsx View File

@@ -0,0 +1,47 @@
import { Box, Button, Stack } from "@mui/material";
import { ApiId } from "api";
import { getFullUrl } from "api/url";
import { PageID } from "codes/page";
import useApp from "hooks/useApp";
import useNavigateCustom from "hooks/useNavigateCustom";
import { useMemo } from "react";
import { getPath } from "routes/path";

export default function useReceiptIssuingOrderSelectHowToGet() {
const { token, receiptIssuingOrder: order } = useApp();
const { navigateWhenChanged } = useNavigateCustom();

const shouldShow = useMemo(() => {
return !!order?.confirmed;
}, [order]);

const downloadUrl = useMemo(() => {
return getFullUrl(ApiId.DOWNLOAD_RECEIPT) + "?access_token=" + token;
}, [token]);

const handleClickMail = () => {
navigateWhenChanged(getPath(PageID.APP_RECEIPT_ISSUING_ORDER_MAIL_ORDER));
};

const element = (
<Stack spacing={1}>
<Box>
<Button href={downloadUrl} variant="contained">
PDFダウンロード
</Button>
</Box>
{!order?.status_order_mail_datetime && (
<Box>
<Button variant="text" onClick={handleClickMail}>
郵送希望
</Button>
</Box>
)}
</Stack>
);

return {
element,
shouldShow,
};
}

+ 1
- 3
src/pages/dashboard/receipt-issuing-order/create.tsx View File

@@ -47,7 +47,6 @@ export default function ReceiptIssuingOrderCreate() {
adjustSeqNo: selectParkingStep.values("adjustSeqNo"),
amount: inputReceiptStep.values("amount"),
date: inputReceiptStep.values("date"),
name: inputReceiptStep.values("name"),
address: inputSMSSendAddress.values("address"),
memo: inputReceiptStep.values("memo"),
};
@@ -112,8 +111,7 @@ export default function ReceiptIssuingOrderCreate() {
customer_code: getValue(formData.customerCode),
parking_management_code: getValue(formData.parkingManagementCode),
adjust_seq_no: formData.adjustSeqNo,
receipt_name: formData.name,
receipt_use_datetime: formData.date,
receipt_use_date: formData.date,
receipt_amount: formData.amount,
sms_phone_number: formData.address,
});


+ 1
- 22
src/pages/dashboard/receipt-issuing-order/hooks/useConfirm.tsx View File

@@ -1,4 +1,3 @@
import { yupResolver } from "@hookform/resolvers/yup";
import {
Box,
Button,
@@ -7,26 +6,12 @@ import {
TableBody,
TableCell,
TableRow,
TextField,
Typography,
} from "@mui/material";
import { HasChildren } from "@types";
import TextFieldEx from "components/form/TextFieldEx";
import {
FormProvider,
RHFAutoComplete,
RHFTextField,
} from "components/hook-form";
import {
AutoCompleteOption,
AutoCompleteOptionType,
getValue,
} from "components/hook-form/RHFAutoComplete";
import RHFDatePicker from "components/hook-form/RHFDatePicker";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { formatDateStr, formatDateTimeStr } from "utils/datetime";
import * as Yup from "yup";
import { formatDateStr } from "utils/datetime";

type AreaBoxProps = {
title: string;
@@ -51,7 +36,6 @@ export type ConfirmDataProps = {
adjustSeqNo: string;
amount: string;
date: Date | null;
name: string;
address: string;
memo: string;
};
@@ -64,7 +48,6 @@ export default function useConfirm({ onNext, onPrev }: Props) {
adjustSeqNo: "",
amount: "",
date: null,
name: "",
address: "",
memo: "",
});
@@ -102,10 +85,6 @@ export default function useConfirm({ onNext, onPrev }: Props) {
<TableCell>利用日</TableCell>
<TableCell>{formatDateStr(data.date)}</TableCell>
</TableRow>
<TableRow>
<TableCell>宛名</TableCell>
<TableCell>{data.name}</TableCell>
</TableRow>
<TableRow>
<TableCell>金額</TableCell>
<TableCell>{data.amount}円</TableCell>


+ 0
- 6
src/pages/dashboard/receipt-issuing-order/hooks/useInputReceiptStep.tsx View File

@@ -31,7 +31,6 @@ function AreaBox({ title, children }: AreaBoxProps) {
type FormProps = {
amount: string;
date: Date | null;
name: string;
memo: string;
};

@@ -46,7 +45,6 @@ export default function useInputReceiptStep({ onNext, onPrev }: Props) {
defaultValues: {
amount: "",
date: null,
name: "",
memo: "",
},
resolver: yupResolver(
@@ -55,7 +53,6 @@ export default function useInputReceiptStep({ onNext, onPrev }: Props) {
.required("必須項目です")
.typeError("正しく入力してください"),
amount: Yup.number().required("必須項目です"),
name: Yup.string().required("必須項目です"),
memo: Yup.string().nullable(),
})
),
@@ -89,9 +86,6 @@ export default function useInputReceiptStep({ onNext, onPrev }: Props) {
sx={{ maxWidth: 150 }}
/>
</AreaBox>
<AreaBox title="宛名">
<RHFTextField name="name" size="small" />
</AreaBox>
<AreaBox title="メモ">
<RHFTextField name="memo" size="small" multiline rows={3} />
</AreaBox>


+ 1
- 2
src/pages/dashboard/receipt-issuing-order/list.tsx View File

@@ -14,7 +14,6 @@ import {
getReceiptIssuingOrders,
} from "api/custom/hello-techno/receipt-issuing-order";
import { PageID, TabID } from "codes/page";
import { getStatusName } from "codes/receipt-issuing-order";
import { FormProvider, RHFTextField } from "components/hook-form";
import { TableHeadCustom } from "components/table";
import { SearchConditionContextProvider } from "contexts/SearchConditionContext";
@@ -235,7 +234,7 @@ function Row({ data }: RowProps) {
<TableCell>{data.order_datetime}</TableCell>
<TableCell>{data.customer_name}</TableCell>
<TableCell>{data.parking_name}</TableCell>
<TableCell>{getStatusName(data.status)}</TableCell>
<TableCell>{data.status_name}</TableCell>
<TableCell>{data.handler_name}</TableCell>
</TableRow>
);


+ 5
- 0
src/routes/index.tsx View File

@@ -42,6 +42,10 @@ const AppRoutes = (): RouteObject => ({
path: getRoute(PageID.APP_RECEIPT_ISSUING_ORDER_INDEX),
element: <ReceiptIssuingOrder />,
},
{
path: getRoute(PageID.APP_RECEIPT_ISSUING_ORDER_MAIL_ORDER),
element: <MailOrder />,
},
],
});

@@ -116,6 +120,7 @@ const Logout = Loadable(lazy(() => import("pages/auth/logout")));
const ReceiptIssuingOrder = Loadable(
lazy(() => import("pages/app/ReceiptIssuingOrder"))
);
const MailOrder = Loadable(lazy(() => import("pages/app/MailOrder")));

//ダッシュボード ----------------------------
const Dashboard = Loadable(lazy(() => import("pages/dashboard")));


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

@@ -40,6 +40,8 @@ const PATHS = {
// APP
[makePathKey(PageID.APP_RECEIPT_ISSUING_ORDER_INDEX)]:
"/app/receipt-issuing-oreder/:token",
[makePathKey(PageID.APP_RECEIPT_ISSUING_ORDER_MAIL_ORDER)]:
"/app/receipt-issuing-oreder/mail",

// 契約関連
[makePathKey(PageID.DASHBOARD_CONTRACT_LIST)]:


+ 9
- 0
src/theme/index.tsx View File

@@ -158,6 +158,15 @@ theme = {
},
},
},
MuiTableRow: {
styleOverrides: {
root: {
"&:last-child td, &:last-child th ": {
borderBottomStyle: "none",
},
},
},
},
},
};



+ 10
- 0
yarn.lock View File

@@ -2484,6 +2484,11 @@
dependencies:
"@types/node" "*"

"@types/sprintf-js@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@types/sprintf-js/-/sprintf-js-1.1.2.tgz#a4fcb84c7344f39f70dc4eec0e1e7f10a48597a3"
integrity sha512-hkgzYF+qnIl8uTO8rmUSVSfQ8BIfMXC4yJAF4n8BE758YsKBZvFC4NumnAegj7KmylP0liEZNpb9RRGFMbFejA==

"@types/stack-utils@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
@@ -8634,6 +8639,11 @@ spdy@^4.0.2:
select-hose "^2.0.0"
spdy-transport "^3.0.0"

sprintf-js@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==

sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"


Loading…
Cancel
Save