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"