diff --git a/package.json b/package.json index 0fc3382..1177220 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/api/app.ts b/src/api/app.ts deleted file mode 100644 index f8faf8e..0000000 --- a/src/api/app.ts +++ /dev/null @@ -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; -}; diff --git a/src/api/app/index.ts b/src/api/app/index.ts new file mode 100644 index 0000000..e6c1459 --- /dev/null +++ b/src/api/app/index.ts @@ -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({ + url: getUrl(ApiId.APP_TOKEN_CHECK), + method: HttpMethod.GET, + data: new URLSearchParams(data), + }); + return res; +}; diff --git a/src/api/app/receipt-issuing-order.ts b/src/api/app/receipt-issuing-order.ts new file mode 100644 index 0000000..ee55a34 --- /dev/null +++ b/src/api/app/receipt-issuing-order.ts @@ -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; +}; diff --git a/src/api/custom/hello-techno/receipt-issuing-order.ts b/src/api/custom/hello-techno/receipt-issuing-order.ts index eca8f1b..fb52a3b 100644 --- a/src/api/custom/hello-techno/receipt-issuing-order.ts +++ b/src/api/custom/hello-techno/receipt-issuing-order.ts @@ -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; }; diff --git a/src/api/index.ts b/src/api/index.ts index c97ead0..0c820a1 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -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); diff --git a/src/api/receipt-issuing-order.ts b/src/api/receipt-issuing-order.ts index 45159cd..5707e09 100644 --- a/src/api/receipt-issuing-order.ts +++ b/src/api/receipt-issuing-order.ts @@ -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 = { diff --git a/src/api/url.ts b/src/api/url.ts index e18cb89..ab19838 100644 --- a/src/api/url.ts +++ b/src/api/url.ts @@ -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", diff --git a/src/codes/page.ts b/src/codes/page.ts index f4ca1d3..bfcba90 100644 --- a/src/codes/page.ts +++ b/src/codes/page.ts @@ -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++, diff --git a/src/codes/prefcode.ts b/src/codes/prefcode.ts new file mode 100644 index 0000000..88ab638 --- /dev/null +++ b/src/codes/prefcode.ts @@ -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 ?? "-"; +} diff --git a/src/codes/receipt-issuing-order.ts b/src/codes/receipt-issuing-order.ts deleted file mode 100644 index 8594bba..0000000 --- a/src/codes/receipt-issuing-order.ts +++ /dev/null @@ -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]; -} diff --git a/src/components/hook-form/ex/RHFPrefCodeSelect.tsx b/src/components/hook-form/ex/RHFPrefCodeSelect.tsx new file mode 100644 index 0000000..9ad2980 --- /dev/null +++ b/src/components/hook-form/ex/RHFPrefCodeSelect.tsx @@ -0,0 +1,6 @@ +import { prefCodeOptions } from "codes/prefcode"; +import RHFSelect, { RHFSelectProps, SelectOptionProps } from "../RHFSelect"; + +export default function RHFPrefCodeSelect({ ...other }: RHFSelectProps) { + return ; +} diff --git a/src/contexts/AppContext.tsx b/src/contexts/AppContext.tsx index 50bc41d..8801921 100644 --- a/src/contexts/AppContext.tsx +++ b/src/contexts/AppContext.tsx @@ -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({ 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(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 ( - + {children} ); diff --git a/src/hooks/useDialog.tsx b/src/hooks/useDialog.tsx new file mode 100644 index 0000000..eaa817d --- /dev/null +++ b/src/hooks/useDialog.tsx @@ -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 = ( + + 確認 + {message && ( + + {message} + + )} + + + + + + ); + + return { + // param + show, + + // Element + element, + // function + + open, + close, + setShow, + }; +} diff --git a/src/pages/app/MailOrder.tsx b/src/pages/app/MailOrder.tsx new file mode 100644 index 0000000..199b261 --- /dev/null +++ b/src/pages/app/MailOrder.tsx @@ -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 ( + + + {title} + + {value} + + ); +}; + +type SectionProps = { + title: string; + subtitle?: string; +} & HasChildren; +const Section = ({ title, subtitle, children }: SectionProps) => { + return ( + + + {title} + {subtitle && {subtitle}} + + {children} + + ); +}; + +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 ( + <> + + + + 領収証郵送依頼 + + +
+ + + 郵送先情報入力 + + + 確認 + + + 完了 + + + {mode === "input" && input.element} + {mode === "confirm" && ( + + 郵送先情報確認 + + + + + + + + + +
+ + + 郵送先に間違いがないか確認してください。 + 投函まで数営業日を要しますのでご了承ください。 + + + + + + +
+ )} + {mode === "done" && ( + + 郵送依頼を受付いたしました。 + 到着までしばらくお待ちください。 + + + )} +
+
+
+ + ); +} diff --git a/src/pages/app/ReceiptIssuingOrder.tsx b/src/pages/app/ReceiptIssuingOrder.tsx index 9d3b809..7ad24d2 100644 --- a/src/pages/app/ReceiptIssuingOrder.tsx +++ b/src/pages/app/ReceiptIssuingOrder.tsx @@ -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 ( + + + {title} + + {value} + + ); +}; + +type SectionProps = { + title: string; + subtitle?: string; +} & HasChildren; +const Section = ({ title, subtitle, children }: SectionProps) => { + return ( + + + {title} + {subtitle && {subtitle}} + + {children} + + ); +}; - 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 ( - - - 領収証発行依頼 - - - 状況 - - - - - - + <> + + + + 領収証発行依頼 + + ページ有効期限:{tokenExpiresAtStr}まで + + + +
+ + + + + + + +
+
+ + {order?.status_order_mail_datetime && ( +
+ + + + + +
+
+ )} + + {confirm.shouldShow && ( +
{confirm.element}
+ )} + + {selectHowToGet.shouldShow && ( +
+ {selectHowToGet.element} +
+ )} +
+
+ ); } diff --git a/src/pages/app/hooks/useInputMailStep.tsx b/src/pages/app/hooks/useInputMailStep.tsx new file mode 100644 index 0000000..7bca1a3 --- /dev/null +++ b/src/pages/app/hooks/useInputMailStep.tsx @@ -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 ( + + {title} + {children} + + ); +} + +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({ + 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 = ( + + + + + + + ハイフン無の7桁 + 〒 }} + /> + + + + + + + + + + + + + + + + + + + + + ); + + return { element, values: form.getValues, setValue: form.setValue }; +} diff --git a/src/pages/app/hooks/useReceiptIssuingOrderConfirm.tsx b/src/pages/app/hooks/useReceiptIssuingOrderConfirm.tsx new file mode 100644 index 0000000..a130e26 --- /dev/null +++ b/src/pages/app/hooks/useReceiptIssuingOrderConfirm.tsx @@ -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({ + 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 = ( + + + + + 領収証の宛名を入力してください + + + 一度登録すると変更できないので、ご注意ください + + + + + + + + + + {confirmDialog.element} + + ); + + return { + element, + shouldShow, + }; +} diff --git a/src/pages/app/hooks/useReceiptIssuingOrderSelectHowToGet.tsx b/src/pages/app/hooks/useReceiptIssuingOrderSelectHowToGet.tsx new file mode 100644 index 0000000..0edc36b --- /dev/null +++ b/src/pages/app/hooks/useReceiptIssuingOrderSelectHowToGet.tsx @@ -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 = ( + + + + + {!order?.status_order_mail_datetime && ( + + + + )} + + ); + + return { + element, + shouldShow, + }; +} diff --git a/src/pages/dashboard/receipt-issuing-order/create.tsx b/src/pages/dashboard/receipt-issuing-order/create.tsx index 84adf4c..dd3158c 100644 --- a/src/pages/dashboard/receipt-issuing-order/create.tsx +++ b/src/pages/dashboard/receipt-issuing-order/create.tsx @@ -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, }); diff --git a/src/pages/dashboard/receipt-issuing-order/hooks/useConfirm.tsx b/src/pages/dashboard/receipt-issuing-order/hooks/useConfirm.tsx index b32a347..0a36f3d 100644 --- a/src/pages/dashboard/receipt-issuing-order/hooks/useConfirm.tsx +++ b/src/pages/dashboard/receipt-issuing-order/hooks/useConfirm.tsx @@ -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) { 利用日 {formatDateStr(data.date)} - - 宛名 - {data.name} - 金額 {data.amount}円 diff --git a/src/pages/dashboard/receipt-issuing-order/hooks/useInputReceiptStep.tsx b/src/pages/dashboard/receipt-issuing-order/hooks/useInputReceiptStep.tsx index 41ab5d2..c87a5ca 100644 --- a/src/pages/dashboard/receipt-issuing-order/hooks/useInputReceiptStep.tsx +++ b/src/pages/dashboard/receipt-issuing-order/hooks/useInputReceiptStep.tsx @@ -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 }} /> - - - diff --git a/src/pages/dashboard/receipt-issuing-order/list.tsx b/src/pages/dashboard/receipt-issuing-order/list.tsx index dbeb6dc..6d18530 100644 --- a/src/pages/dashboard/receipt-issuing-order/list.tsx +++ b/src/pages/dashboard/receipt-issuing-order/list.tsx @@ -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) { {data.order_datetime} {data.customer_name} {data.parking_name} - {getStatusName(data.status)} + {data.status_name} {data.handler_name} ); diff --git a/src/routes/index.tsx b/src/routes/index.tsx index b0f9e8e..8e61d00 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -42,6 +42,10 @@ const AppRoutes = (): RouteObject => ({ path: getRoute(PageID.APP_RECEIPT_ISSUING_ORDER_INDEX), element: , }, + { + path: getRoute(PageID.APP_RECEIPT_ISSUING_ORDER_MAIL_ORDER), + element: , + }, ], }); @@ -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"))); diff --git a/src/routes/path.ts b/src/routes/path.ts index 594f93c..baf9a24 100644 --- a/src/routes/path.ts +++ b/src/routes/path.ts @@ -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)]: diff --git a/src/theme/index.tsx b/src/theme/index.tsx index 501ef1e..8311587 100644 --- a/src/theme/index.tsx +++ b/src/theme/index.tsx @@ -158,6 +158,15 @@ theme = { }, }, }, + MuiTableRow: { + styleOverrides: { + root: { + "&:last-child td, &:last-child th ": { + borderBottomStyle: "none", + }, + }, + }, + }, }, }; diff --git a/yarn.lock b/yarn.lock index b957fcf..2528ff5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"