From 75069b25bd19d6f0067ecbed7fd22d2630643da3 Mon Sep 17 00:00:00 2001 From: "sosuke.iwabuchi" Date: Fri, 19 May 2023 19:59:28 +0900 Subject: [PATCH] =?UTF-8?q?=E9=A0=98=E5=8F=8E=E8=A8=BC=E7=99=BA=E8=A1=8C?= =?UTF-8?q?=E4=BE=9D=E9=A0=BC=E3=80=80=E3=81=BE=E3=81=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/App.tsx | 39 ++- src/api/custom/hello-techno/index.ts | 69 +++++ .../hello-techno/receipt-issuing-order.ts | 52 ++++ src/api/index.ts | 67 +++-- src/api/receipt-issuing-order.ts | 30 ++ src/api/url.ts | 25 +- src/codes/receipt-issuing-order.ts | 22 ++ src/components/hook-form/RHFDatePicker.tsx | 81 ++---- src/contexts/BackDropContext.tsx | 35 +++ src/hooks/useBackDrop.ts | 6 + src/pages/dashboard/index.tsx | 2 +- .../receipt-issuing-order/create.tsx | 147 +++++++++- .../hooks/useConfirm.tsx | 137 ++++++++++ .../hooks/useInputReceiptStep.tsx | 111 ++++++++ .../hooks/useInputSMSSendAddress.tsx | 83 ++++++ .../hooks/useSelectParkingStep.tsx | 148 ++++++++++ .../dashboard/receipt-issuing-order/list.tsx | 257 ++++++++++++++++++ src/routes/index.tsx | 8 + src/theme/index.tsx | 9 + yarn.lock | 12 + 21 files changed, 1237 insertions(+), 104 deletions(-) create mode 100644 src/api/custom/hello-techno/index.ts create mode 100644 src/api/custom/hello-techno/receipt-issuing-order.ts create mode 100644 src/api/receipt-issuing-order.ts create mode 100644 src/codes/receipt-issuing-order.ts create mode 100644 src/contexts/BackDropContext.tsx create mode 100644 src/hooks/useBackDrop.ts create mode 100644 src/pages/dashboard/receipt-issuing-order/hooks/useConfirm.tsx create mode 100644 src/pages/dashboard/receipt-issuing-order/hooks/useInputReceiptStep.tsx create mode 100644 src/pages/dashboard/receipt-issuing-order/hooks/useInputSMSSendAddress.tsx create mode 100644 src/pages/dashboard/receipt-issuing-order/hooks/useSelectParkingStep.tsx create mode 100644 src/pages/dashboard/receipt-issuing-order/list.tsx diff --git a/package.json b/package.json index 52da025..0fc3382 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@mui/icons-material": "^5.11.16", "@mui/lab": "^5.0.0-alpha.129", "@mui/material": "^5.12.2", + "@mui/x-date-pickers": "^6.4.0", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^13.0.0", "@testing-library/user-event": "^13.2.1", diff --git a/src/App.tsx b/src/App.tsx index eae4fa7..7e9e1e1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,22 +8,31 @@ import { BrowserRouter } from "react-router-dom"; import { Routes } from "routes"; import AppThemeProvider from "theme"; +import { LocalizationProvider } from "@mui/x-date-pickers"; +import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns"; +import ja from "date-fns/locale/ja"; +import BackDropContextProvider from "contexts/BackDropContext"; + export default function App() { return ( - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + ); } diff --git a/src/api/custom/hello-techno/index.ts b/src/api/custom/hello-techno/index.ts new file mode 100644 index 0000000..2998f6b --- /dev/null +++ b/src/api/custom/hello-techno/index.ts @@ -0,0 +1,69 @@ +import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "api"; +import { getUrl } from "api/url"; + +export type HTCustomer = { + customer_code: string; + name: string; +}; +export type HTParking = { + customer_code: string; + parking_management_code: string; + name: string; +}; +export type HTAdjustData = { + customer_code: string; + parking_management_code: string; + adjust_seq_no: number; + adjust_datetime: string; +}; + +export type HTCustomersResponse = { + data: { + records: HTCustomer[]; + }; +} & APICommonResponse; + +export const getHTCustomers = async (data = {}) => { + const res = await request({ + url: getUrl(ApiId.HT_CUSTOM_CUSTOMERS), + method: HttpMethod.GET, + }); + return res; +}; + +export type HTParkingsRequest = { + customer_code: string; +}; + +export type HTParkingsResponse = { + data: { + records: HTParking[]; + }; +} & APICommonResponse; + +export const getHTParkings = async (data: HTParkingsRequest) => { + console.log({ data }); + const res = await request({ + url: getUrl(ApiId.HT_CUSTOM_PARKINGS), + method: HttpMethod.GET, + data: new URLSearchParams(data), + }); + return res; +}; + +export type HTAdjustDataRequest = { + customer_code: string; +}; + +export type HTAdjustDataResponse = { + data: HTAdjustData; +} & APICommonResponse; + +export const getHTAdjustData = async (data: HTAdjustDataRequest) => { + const res = await request({ + url: getUrl(ApiId.HT_CUSTOM_ADJUST_DATA), + method: HttpMethod.GET, + data: new URLSearchParams(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 new file mode 100644 index 0000000..7266397 --- /dev/null +++ b/src/api/custom/hello-techno/receipt-issuing-order.ts @@ -0,0 +1,52 @@ +import { + APICommonResponse, + ApiId, + HttpMethod, + makeFormData, + request, +} from "api"; +import { getUrl } from "api/url"; + +export type HTCustomer = { + customer_code: string; + name: string; +}; +export type HTParking = { + customer_code: string; + parking_management_code: string; + name: string; +}; +export type HTAdjustData = { + customer_code: string; + parking_management_code: string; + adjust_seq_no: number; + adjust_datetime: string; +}; + +export type CreateReceiptIssuingOrderRequest = { + customer_code: string; + parking_management_code: string; + adjust_seq_no?: string | number; + receipt_name: string; + receipt_use_datetime: Date | null; + receipt_amount: string | number; + memo?: string; + sms_phone_number: string; +}; + +export type HTCustomersResponse = { + data: { + records: HTCustomer[]; + }; +} & APICommonResponse; + +export const createReceiptIssuingOrder = async ( + data: CreateReceiptIssuingOrderRequest +) => { + const res = await request({ + url: getUrl(ApiId.HT_CUSTOM_RECEIPT_ISSUING_ORDER_CREATE), + method: HttpMethod.POST, + data: makeFormData(data), + }); + return res; +}; diff --git a/src/api/index.ts b/src/api/index.ts index e60980d..c43eadc 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -12,6 +12,16 @@ export const ApiId = { LOGIN: id++, LOGOUT: id++, + + RECEIPT_ISSUING_ORDERS: id++, + RECEIPT_ISSUING_ORDER: id++, + RECEIPT_ISSUING_ORDER_CREATE: id++, + + // FOR CUSTOM + HT_CUSTOM_CUSTOMERS: id++, + HT_CUSTOM_PARKINGS: id++, + HT_CUSTOM_ADJUST_DATA: id++, + HT_CUSTOM_RECEIPT_ISSUING_ORDER_CREATE: id++, } as const; export type ApiId = (typeof ApiId)[keyof typeof ApiId]; @@ -201,34 +211,43 @@ export async function apiRequest< if (setSending) { setSending(true); } - const res = await apiMethod(sendData); - if (setSending) { - setSending(false); - } - - if (res?.result === ResultCode.SUCCESS) { - if (onSuccess) { - onSuccess(res, sendData); + try { + const res = await apiMethod(sendData); + if (setSending) { + setSending(false); } - } else { - if (res?.messages.errors) { - if (errorSetter) { - const errorCount = setFormErrorMessages( - sendData, - errorSetter, - res.messages.errors - ); - console.log("FormErrorCount", errorCount); + + if (res?.result === ResultCode.SUCCESS) { + if (onSuccess) { + onSuccess(res, sendData); + } + } else { + if (res?.messages.errors) { + if (errorSetter) { + const errorCount = setFormErrorMessages( + sendData, + errorSetter, + res.messages.errors + ); + console.log("FormErrorCount", errorCount); + } + } + if (onFailed) { + onFailed(res); + } + if (onFinaly) { + onFinaly(res); } } + return res; + } catch (e) { + console.error(e); if (onFailed) { - onFailed(res); + onFailed(null); } + if (onFinaly) { + onFinaly(null); + } + return null; } - - if (onFinaly) { - onFinaly(res); - } - - return res; } diff --git a/src/api/receipt-issuing-order.ts b/src/api/receipt-issuing-order.ts new file mode 100644 index 0000000..45159cd --- /dev/null +++ b/src/api/receipt-issuing-order.ts @@ -0,0 +1,30 @@ +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; +}; + +export type ReceiptIssuingOrdersRequest = { + address?: string; + status?: ReceiptIssuingOrderStatus; +}; + +export type ReceiptIssuingOrdersResponse = { + data: { + records: ReceiptIssuingOrder[]; + }; +} & APICommonResponse; + +export const getReceiptIssuingOrders = async ( + data: ReceiptIssuingOrdersRequest +) => { + const res = await request({ + url: getUrl(ApiId.RECEIPT_ISSUING_ORDERS), + method: HttpMethod.GET, + data: makeParam(data), + }); + return res; +}; diff --git a/src/api/url.ts b/src/api/url.ts index fc3b347..8a2dec1 100644 --- a/src/api/url.ts +++ b/src/api/url.ts @@ -1,22 +1,31 @@ -import { ApiId } from "."; +import { ApiId as A } from "."; const urls = { - [ApiId.CSRF_TOKEN]: "sanctum/csrf-cookie", - [ApiId.ME]: "me", - [ApiId.LOGIN]: "login", - [ApiId.LOGOUT]: "logout", + [A.CSRF_TOKEN]: "sanctum/csrf-cookie", + [A.ME]: "me", + [A.LOGIN]: "login", + [A.LOGOUT]: "logout", + + [A.RECEIPT_ISSUING_ORDERS]: "receipt-issuing-orders", + + // FOR CUSTOM + [A.HT_CUSTOM_CUSTOMERS]: "custom/hello-techno/customers", + [A.HT_CUSTOM_PARKINGS]: "custom/hello-techno/parkings", + [A.HT_CUSTOM_ADJUST_DATA]: "custom/hello-techno/adjust-data", + [A.HT_CUSTOM_RECEIPT_ISSUING_ORDER_CREATE]: + "custom/hello-techno/receipt-issuing-order/create", }; const prefixs = { - [ApiId.CSRF_TOKEN]: "", + [A.CSRF_TOKEN]: "", }; const DEFAULT_API_URL_PREFIX = "api"; -const getPrefix = (apiId: ApiId) => { +const getPrefix = (apiId: A) => { return prefixs[apiId] ?? DEFAULT_API_URL_PREFIX; }; -export const getUrl = (apiId: ApiId) => { +export const getUrl = (apiId: A) => { let url = getPrefix(apiId); if (url.length !== 0) { url += "/"; diff --git a/src/codes/receipt-issuing-order.ts b/src/codes/receipt-issuing-order.ts new file mode 100644 index 0000000..492e8a6 --- /dev/null +++ b/src/codes/receipt-issuing-order.ts @@ -0,0 +1,22 @@ +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]; diff --git a/src/components/hook-form/RHFDatePicker.tsx b/src/components/hook-form/RHFDatePicker.tsx index 8d4632f..3ca9e34 100644 --- a/src/components/hook-form/RHFDatePicker.tsx +++ b/src/components/hook-form/RHFDatePicker.tsx @@ -1,4 +1,3 @@ -// form import { Controller, ControllerFieldState, @@ -6,15 +5,8 @@ import { FieldValues, useFormContext, } from "react-hook-form"; -// @mui -import ClearIcon from "@mui/icons-material/Clear"; -import { DatePicker } from "@mui/lab"; -import { - IconButton, - InputAdornment, - TextField, - TextFieldProps, -} from "@mui/material"; +import { TextFieldProps } from "@mui/material"; +import { DatePicker } from "@mui/x-date-pickers"; import React, { useMemo } from "react"; import RHFTextField from "./RHFTextField"; @@ -35,36 +27,21 @@ const RHFDatePicker = ({ datePickerProps, ...other }: Props) => { - const { control, watch, setValue } = useFormContext(); + const { + control, + watch, + setValue, + formState: { errors }, + } = useFormContext(); const value: Date | null = watch(name); - const handleClear = () => { - setValue(name, null); - }; - - const icon = useMemo(() => { - if (value !== null) { - return ( - - - - - - ); - } else { - return null; - } - }, [value]); - - const iconMerge = (ele: React.ReactNode) => { - return ( - <> - {icon} - {ele} - - ); - }; + const isError = useMemo(() => { + return !!errors[name]; + }, [errors, name]); + const errorMessage = useMemo(() => { + return errors[name]?.message ?? ""; + }, [errors, name]); if (readOnly) { return ; @@ -79,23 +56,21 @@ const RHFDatePicker = ({ }) => { return ( ( - - )} - {...datePickerProps} + slotProps={{ + actionBar: { + actions: ["accept", "clear", "cancel"], + }, + inputAdornment: { + position: "start", + }, + textField: { + size: "small", + error: isError, + helperText: errorMessage, + }, + }} {...field} + {...datePickerProps} /> ); }; diff --git a/src/contexts/BackDropContext.tsx b/src/contexts/BackDropContext.tsx new file mode 100644 index 0000000..bf9af92 --- /dev/null +++ b/src/contexts/BackDropContext.tsx @@ -0,0 +1,35 @@ +import { Backdrop, CircularProgress, useTheme } from "@mui/material"; +import { createContext, useState } from "react"; + +type Props = { + children: React.ReactNode; +}; + +type ContextProps = { + showBackDrop: boolean; + setShowBackDrop: (show: boolean) => void; +}; +const defaultProps: ContextProps = { + showBackDrop: false, + setShowBackDrop: (show: boolean) => {}, +}; +export const BackDroptContext = createContext(defaultProps); + +export default function BackDropContextProvider({ children }: Props) { + const [showBackDrop, setShowBackDrop] = useState(false); + return ( + + {children} + + + + + ); +} diff --git a/src/hooks/useBackDrop.ts b/src/hooks/useBackDrop.ts new file mode 100644 index 0000000..56d9fc6 --- /dev/null +++ b/src/hooks/useBackDrop.ts @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import { BackDroptContext } from "contexts/BackDropContext"; + +export default function useBackDrop() { + return useContext(BackDroptContext); +} diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index 84f80d9..cf2fc39 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -13,5 +13,5 @@ export default function Overview() { setHeaderTitle("Dashboard"); setTabs(null); }, []); - return ; + return ; } diff --git a/src/pages/dashboard/receipt-issuing-order/create.tsx b/src/pages/dashboard/receipt-issuing-order/create.tsx index c543e1e..84adf4c 100644 --- a/src/pages/dashboard/receipt-issuing-order/create.tsx +++ b/src/pages/dashboard/receipt-issuing-order/create.tsx @@ -1,7 +1,16 @@ -import { Box } from "@mui/material"; +import { Box, Step, StepLabel, Stepper } from "@mui/material"; import { PageID, TabID } from "codes/page"; import useDashboard from "hooks/useDashBoard"; -import { useEffect } from "react"; +import { useEffect, useMemo, useState } from "react"; +import useSelectParkingStep from "./hooks/useSelectParkingStep"; +import useInputReceiptStep from "./hooks/useInputReceiptStep"; +import useInputSMSSendAddress from "./hooks/useInputSMSSendAddress"; +import useConfirm, { ConfirmDataProps } from "./hooks/useConfirm"; +import useAPICall from "hooks/useAPICall"; +import { createReceiptIssuingOrder } from "api/custom/hello-techno/receipt-issuing-order"; +import useSnackbarCustom from "hooks/useSnackbarCustom"; +import useBackDrop from "hooks/useBackDrop"; +import { getValue } from "components/hook-form/RHFAutoComplete"; export default function ReceiptIssuingOrderCreate() { const { setHeaderTitle, setTabs } = useDashboard( @@ -9,9 +18,141 @@ export default function ReceiptIssuingOrderCreate() { TabID.NONE ); + const { success, error } = useSnackbarCustom(); + const { setShowBackDrop } = useBackDrop(); + + const [mode, setMode] = useState< + "parking_select" | "input_receipt" | "input_address" | "confirm" | "done" + >("parking_select"); + + const step = useMemo(() => { + switch (mode) { + case "parking_select": + return 0; + case "input_receipt": + return 1; + case "input_address": + return 2; + case "confirm": + return 3; + case "done": + return 4; + } + }, [mode]); + + const getConfimData = (): ConfirmDataProps => { + return { + customerName: selectParkingStep.values("customerCode.label"), + parkingName: selectParkingStep.values("parkingManagementCode.label"), + adjustSeqNo: selectParkingStep.values("adjustSeqNo"), + amount: inputReceiptStep.values("amount"), + date: inputReceiptStep.values("date"), + name: inputReceiptStep.values("name"), + address: inputSMSSendAddress.values("address"), + memo: inputReceiptStep.values("memo"), + }; + }; + const getFormData = () => { + return { + ...selectParkingStep.values(), + ...inputReceiptStep.values(), + ...inputSMSSendAddress.values(), + }; + }; + + const selectParkingStep = useSelectParkingStep({ + onNext: () => { + setMode("input_receipt"); + }, + }); + + const inputReceiptStep = useInputReceiptStep({ + onNext: () => { + setMode("input_address"); + }, + onPrev: () => { + setMode("parking_select"); + }, + }); + const inputSMSSendAddress = useInputSMSSendAddress({ + onNext: () => { + confirm.setData(getConfimData()); + setMode("confirm"); + }, + onPrev: () => { + setMode("input_receipt"); + }, + }); + + const confirm = useConfirm({ + onNext: () => { + send(); + }, + onPrev: () => { + setMode("input_address"); + }, + }); + + const createAPI = useAPICall({ + apiMethod: createReceiptIssuingOrder, + onSuccess: () => { + setMode("done"); + success("成功しました"); + }, + onFailed: () => { + error("失敗しました"); + }, + }); + + const send = () => { + const { callAPI, makeSendData } = createAPI; + + const formData = getFormData(); + const sendData = makeSendData({ + 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_amount: formData.amount, + sms_phone_number: formData.address, + }); + + callAPI(sendData); + }; + + useEffect(() => { + setShowBackDrop(createAPI.sending); + }, [createAPI.sending]); + useEffect(() => { setHeaderTitle("領収証発行依頼作成"); setTabs(null); }, []); - return Create; + return ( + + + + 駐車場選択 + + + 領収証情報入力 + + + SMS送信先入力 + + + 確認 + + + 完了 + + + {mode === "parking_select" && selectParkingStep.element} + {mode === "input_receipt" && inputReceiptStep.element} + {mode === "input_address" && inputSMSSendAddress.element} + {mode === "confirm" && confirm.element} + {mode === "done" && 受付しました。} + + ); } diff --git a/src/pages/dashboard/receipt-issuing-order/hooks/useConfirm.tsx b/src/pages/dashboard/receipt-issuing-order/hooks/useConfirm.tsx new file mode 100644 index 0000000..b32a347 --- /dev/null +++ b/src/pages/dashboard/receipt-issuing-order/hooks/useConfirm.tsx @@ -0,0 +1,137 @@ +import { yupResolver } from "@hookform/resolvers/yup"; +import { + Box, + Button, + Stack, + Table, + 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"; + +type AreaBoxProps = { + title: string; +} & HasChildren; +function AreaBox({ title, children }: AreaBoxProps) { + return ( + + {title} + {children} + + ); +} + +type Props = { + onNext?: VoidFunction; + onPrev?: VoidFunction; +}; + +export type ConfirmDataProps = { + customerName: string; + parkingName: string; + adjustSeqNo: string; + amount: string; + date: Date | null; + name: string; + address: string; + memo: string; +}; +export default function useConfirm({ onNext, onPrev }: Props) { + const [adjustSeqNo, setAdjustSeqNo] = useState(""); + + const [data, setData] = useState({ + customerName: "", + parkingName: "", + adjustSeqNo: "", + amount: "", + date: null, + name: "", + address: "", + memo: "", + }); + + const handleNext = () => { + if (onNext) { + onNext(); + } + }; + + const handlePrev = () => { + if (onPrev) { + onPrev(); + } + }; + + const element = ( + + 領収証発行内容確認 + + + + 運営会社名 + {data.customerName} + + + 駐車場名 + {data.parkingName} + + + 精算連番 + {data.adjustSeqNo} + + + 利用日 + {formatDateStr(data.date)} + + + 宛名 + {data.name} + + + 金額 + {data.amount}円 + + + メモ + + + + + + SMS送信先 + {data.address} + + +
+ + + + +
+ ); + + return { element, setData }; +} diff --git a/src/pages/dashboard/receipt-issuing-order/hooks/useInputReceiptStep.tsx b/src/pages/dashboard/receipt-issuing-order/hooks/useInputReceiptStep.tsx new file mode 100644 index 0000000..41ab5d2 --- /dev/null +++ b/src/pages/dashboard/receipt-issuing-order/hooks/useInputReceiptStep.tsx @@ -0,0 +1,111 @@ +import { yupResolver } from "@hookform/resolvers/yup"; +import { Box, Button, Stack, TextField, Typography } from "@mui/material"; +import { HasChildren } from "@types"; +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 * as Yup from "yup"; + +type AreaBoxProps = { + title: string; +} & HasChildren; +function AreaBox({ title, children }: AreaBoxProps) { + return ( + + {title} + {children} + + ); +} + +type FormProps = { + amount: string; + date: Date | null; + name: string; + memo: string; +}; + +type Props = { + onNext?: VoidFunction; + onPrev?: VoidFunction; +}; +export default function useInputReceiptStep({ onNext, onPrev }: Props) { + const [adjustSeqNo, setAdjustSeqNo] = useState(""); + + const form = useForm({ + defaultValues: { + amount: "", + date: null, + name: "", + memo: "", + }, + resolver: yupResolver( + Yup.object().shape({ + date: Yup.date() + .required("必須項目です") + .typeError("正しく入力してください"), + amount: Yup.number().required("必須項目です"), + name: Yup.string().required("必須項目です"), + memo: Yup.string().nullable(), + }) + ), + }); + + const handleSubmit = (data: FormProps) => { + console.log(data); + if (onNext) { + onNext(); + } + }; + + const handlePrev = () => { + if (onPrev) { + onPrev(); + } + }; + + const element = ( + + + + + + + 円 }} + sx={{ maxWidth: 150 }} + /> + + + + + + + + + + + + + + ); + + return { element, values: form.getValues, setValue: form.setValue }; +} diff --git a/src/pages/dashboard/receipt-issuing-order/hooks/useInputSMSSendAddress.tsx b/src/pages/dashboard/receipt-issuing-order/hooks/useInputSMSSendAddress.tsx new file mode 100644 index 0000000..8414386 --- /dev/null +++ b/src/pages/dashboard/receipt-issuing-order/hooks/useInputSMSSendAddress.tsx @@ -0,0 +1,83 @@ +import { yupResolver } from "@hookform/resolvers/yup"; +import { Box, Button, Stack, Typography } from "@mui/material"; +import { HasChildren } from "@types"; +import { + FormProvider, + RHFAutoComplete, + RHFTextField, +} from "components/hook-form"; +import { + AutoCompleteOption, + AutoCompleteOptionType, + getValue, +} from "components/hook-form/RHFAutoComplete"; +import { useState } from "react"; +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 = { + address: string; +}; + +type Props = { + onNext?: VoidFunction; + onPrev?: VoidFunction; +}; +export default function useInputSMSSendAddress({ onNext, onPrev }: Props) { + const form = useForm({ + defaultValues: { + address: "", + }, + resolver: yupResolver( + Yup.object().shape({ + address: Yup.string() + .required("必須項目です") + .matches(/^[0-9]{11}$/, "正しい電話番号を入力してください"), + }) + ), + }); + + const handleSubmit = () => { + if (onNext) { + onNext(); + } + }; + + const handlePrev = () => { + if (onPrev) { + onPrev(); + } + }; + + const element = ( + + + + + + + + + + + + ); + + return { element, values: form.getValues }; +} diff --git a/src/pages/dashboard/receipt-issuing-order/hooks/useSelectParkingStep.tsx b/src/pages/dashboard/receipt-issuing-order/hooks/useSelectParkingStep.tsx new file mode 100644 index 0000000..6e60917 --- /dev/null +++ b/src/pages/dashboard/receipt-issuing-order/hooks/useSelectParkingStep.tsx @@ -0,0 +1,148 @@ +import { yupResolver } from "@hookform/resolvers/yup"; +import { Box, Button, Stack, Typography } from "@mui/material"; +import { HasChildren } from "@types"; +import { getHTCustomers, getHTParkings } from "api/custom/hello-techno"; +import { + FormProvider, + RHFAutoComplete, + RHFTextField, +} from "components/hook-form"; +import { + AutoCompleteOption, + AutoCompleteOptionType, + getValue, +} from "components/hook-form/RHFAutoComplete"; +import useAPICall from "hooks/useAPICall"; +import { useEffect, useState } from "react"; +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 = { + customerCode: AutoCompleteOptionType; + parkingManagementCode: AutoCompleteOptionType; + adjustSeqNo: string; +}; + +type Props = { + onNext?: VoidFunction; +}; +export default function useSelectParkingStep({ onNext }: Props) { + const [customers, setCustomers] = useState([]); + const [parkings, setParkings] = useState([]); + + const customerAPI = useAPICall({ + apiMethod: getHTCustomers, + onSuccess: ({ data }) => { + const options: AutoCompleteOption[] = data.records.map( + ({ customer_code, name }) => { + return { + label: name, + value: customer_code, + }; + } + ); + setCustomers(options); + }, + }); + + const parkingAPI = useAPICall({ + apiMethod: getHTParkings, + onSuccess: ({ data }) => { + const options: AutoCompleteOption[] = data.records.map( + ({ parking_management_code, name }) => { + return { + label: name, + value: parking_management_code, + }; + } + ); + setParkings(options); + }, + }); + + const form = useForm({ + defaultValues: { + customerCode: null, + parkingManagementCode: null, + adjustSeqNo: "", + }, + resolver: yupResolver( + Yup.object().shape({ + customerCode: Yup.object().required("必須項目です"), + parkingManagementCode: Yup.object().required("必須項目です"), + adjustSeqNo: Yup.number() + .nullable() + .transform((value, originalValue) => + String(originalValue).trim() === "" ? null : value + ) + .typeError("数値を入力してください"), + }) + ), + }); + + const customerCode = form.watch("customerCode.value"); + + const handleSubmit = () => { + if (onNext) { + onNext(); + } + }; + + // 顧客一覧取得 + useEffect(() => { + customerAPI.callAPI({}); + }, []); + + // 駐車場一覧取得 + useEffect(() => { + setParkings([]); + form.setValue("parkingManagementCode", null); + + if (customerCode) { + parkingAPI.callAPI({ customer_code: customerCode }); + } + }, [customerCode]); + + const element = ( + + + + + + + + + + + + + + + + + ); + + return { element, values: form.getValues, setValue: form.setValue }; +} diff --git a/src/pages/dashboard/receipt-issuing-order/list.tsx b/src/pages/dashboard/receipt-issuing-order/list.tsx new file mode 100644 index 0000000..1dac25a --- /dev/null +++ b/src/pages/dashboard/receipt-issuing-order/list.tsx @@ -0,0 +1,257 @@ +import { + Box, + Grid, + Table, + TableBody, + TableCell, + TableContainer, + TablePagination, + TableRow, + TextField, +} from "@mui/material"; +import { Dictionary } from "@types"; +import { + ReceiptIssuingOrder, + getReceiptIssuingOrders, +} from "api/receipt-issuing-order"; +import { PageID, TabID } from "codes/page"; +import { ReceiptIssuingOrderStatus } from "codes/receipt-issuing-order"; +import { FormProvider, RHFTextField } from "components/hook-form"; +import { TableHeadCustom } from "components/table"; +import { SearchConditionContextProvider } from "contexts/SearchConditionContext"; +import useAPICall from "hooks/useAPICall"; +import useDashboard from "hooks/useDashBoard"; +import useSearchConditionContext from "hooks/useSearchConditionContext"; +import useTable, { UseTableReturn } from "hooks/useTable"; +import ContractTabs from "layouts/dashbord/tab/ContractTabs"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { Contract } from "types/contract"; + +export default function ReceiptIssuingOrderList() { + const { setHeaderTitle, setTabs } = useDashboard( + PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST, + TabID.NONE + ); + + const table = useTable(); + + useEffect(() => { + setHeaderTitle("領収証発行依頼一覧"); + setTabs(null); + }, []); + + return ( + + + + ); +} + +type CommonProps = { + table: UseTableReturn; +}; +function Page({ table }: CommonProps) { + const { + order, + page, + sort, + rowsPerPage, + fetched, + fillteredRow, + isNotFound, + dataLength, + // + onSort, + onChangePage, + onChangeRowsPerPage, + // + setRowData, + // + ROWS_PER_PAGES, + } = table; + return ( + + + + + ); +} + +type FormProps = { + address: string; +}; +function SearchBox({ table }: CommonProps) { + const { + condition, + initialized, + get, + addCondition: add, + } = useSearchConditionContext(); + + const form = useForm({ + defaultValues: { + address: "", + }, + }); + + const { callAPI: calGetReceiptIssuingOrders, makeSendData } = useAPICall({ + apiMethod: getReceiptIssuingOrders, + form, + onSuccess: (res) => { + table.setRowData(res.data.records); + }, + }); + + const handleSubmit = async (data: FormProps) => { + addCondition(data); + }; + + const addCondition = (data: FormProps) => { + add({ ...data }); + }; + + const fetch = async (data: Dictionary) => { + const sendData = makeSendData({ + ...data, + }); + calGetReceiptIssuingOrders(sendData); + }; + + // 初期値設定 + useEffect(() => { + if (initialized) { + form.setValue("address", get("address")); + } + }, [initialized, condition]); + + // Fetchアクション + useEffect(() => { + if (initialized) { + fetch(condition); + } + }, [condition, initialized]); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} + +function TableBox({ table }: CommonProps) { + const TABLE_HEAD = [ + { id: "id", label: "ID", align: "left" }, + { id: "name", label: "名前", align: "left" }, + { id: "emply", label: "---", align: "left" }, + ]; + const { + order, + page, + sort, + rowsPerPage, + fetched, + fillteredRow, + isNotFound, + dataLength, + // + onSort, + onChangePage, + onChangeRowsPerPage, + // + setRowData, + // + ROWS_PER_PAGES, + } = table; + + useEffect(() => { + setRowData([ + { id: "iwabuchi", status: ReceiptIssuingOrderStatus.NONE }, + { id: "iwabuchi", status: ReceiptIssuingOrderStatus.MAIL_DONE }, + ]); + }, []); + + return ( + <> + + + + + + {fillteredRow.map((row, index) => ( + + ))} + +
+
+ + + + + + ); +} + +type RowProps = { + data: ReceiptIssuingOrder; +}; +function Row({ data }: RowProps) { + return ( + + {data.id} + {data.status} + + ); +} diff --git a/src/routes/index.tsx b/src/routes/index.tsx index d68a7cb..2a993c3 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -54,6 +54,11 @@ const DashboardRoutes = (): RouteObject => { element: , target: UserRole.NORMAL_ADMIN, }, + { + pageId: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST, + element: , + target: UserRole.NORMAL_ADMIN, + }, ]; const children: RouteObject[] = useMemo(() => { @@ -104,6 +109,9 @@ const ContractDetail = Loadable( const ReceiptIssuingOrderCreate = Loadable( lazy(() => import("pages/dashboard/receipt-issuing-order/create")) ); +const ReceiptIssuingOrderList = Loadable( + lazy(() => import("pages/dashboard/receipt-issuing-order/list")) +); // その他 --------------------------------- const Page403 = Loadable(lazy(() => import("pages/common/Page403"))); diff --git a/src/theme/index.tsx b/src/theme/index.tsx index b98edcb..5ceacf7 100644 --- a/src/theme/index.tsx +++ b/src/theme/index.tsx @@ -149,6 +149,15 @@ theme = { }, }, }, + MuiInput: { + styleOverrides: { + root: { + "&.Mui-disabled:before": { + "border-bottom-style": "none", + }, + }, + }, + }, }, }; diff --git a/yarn.lock b/yarn.lock index ee1b4ff..b957fcf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1825,6 +1825,18 @@ prop-types "^15.8.1" react-is "^18.2.0" +"@mui/x-date-pickers@^6.4.0": + version "6.4.0" + resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-6.4.0.tgz#97ca181edbc0d56abe08c93b036f97d8e9946572" + integrity sha512-EdKj+TVaEvx+nIljsv1BlDOYYwcMWVYB+ZMRoOCStFojY5uuDzhttQAP6DbsAPPrpKt1lzyecIukLfmIfIGcsA== + dependencies: + "@babel/runtime" "^7.21.0" + "@mui/utils" "^5.12.3" + "@types/react-transition-group" "^4.4.5" + clsx "^1.2.1" + prop-types "^15.8.1" + react-transition-group "^4.4.5" + "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"