| @@ -7,8 +7,10 @@ export type HTCustomer = { | |||||
| }; | }; | ||||
| export type HTParking = { | export type HTParking = { | ||||
| customer_code: string; | customer_code: string; | ||||
| customer_name: string; | |||||
| parking_management_code: string; | parking_management_code: string; | ||||
| name: string; | |||||
| parking_name: string; | |||||
| name: string; // 旧API仕様 | |||||
| }; | }; | ||||
| export type HTAdjustData = { | export type HTAdjustData = { | ||||
| customer_code: string; | customer_code: string; | ||||
| @@ -39,7 +41,9 @@ export const getHTCustomers = async (data = {}) => { | |||||
| // 駐車場一覧取得 --------------------- | // 駐車場一覧取得 --------------------- | ||||
| export type HTParkingsRequest = { | export type HTParkingsRequest = { | ||||
| customer_code: string; | |||||
| customer_code?: string; | |||||
| parking_management_code?: string; | |||||
| parking_name?: string; | |||||
| }; | }; | ||||
| export type HTParkingsResponse = { | export type HTParkingsResponse = { | ||||
| @@ -0,0 +1,41 @@ | |||||
| import { HasChildren } from "@types"; | |||||
| import { HTAdjustData, HTParking } from "api/custom/hello-techno"; | |||||
| import { createContext, useState } from "react"; | |||||
| type ContextProps = { | |||||
| parking: HTParking | null; | |||||
| setParking: (parking: HTParking | null) => void; | |||||
| adjustData: HTAdjustData | null; | |||||
| setAdjustData: (adjustData: HTAdjustData | null) => void; | |||||
| }; | |||||
| export const CreateContext = createContext<ContextProps>({ | |||||
| parking: null, | |||||
| setParking: (parking: HTParking | null) => { | |||||
| console.error("setParking未定義"); | |||||
| }, | |||||
| adjustData: null, | |||||
| setAdjustData: (adjustData: HTAdjustData | null) => { | |||||
| console.error("setAdjustData未定義"); | |||||
| }, | |||||
| }); | |||||
| type ProviderProps = {} & HasChildren; | |||||
| export function CreateContextProvider({ children }: ProviderProps) { | |||||
| const [parking, setParking] = useState<HTParking | null>(null); | |||||
| const [adjustData, setAdjustData] = useState<HTAdjustData | null>(null); | |||||
| return ( | |||||
| <CreateContext.Provider | |||||
| value={{ | |||||
| parking, | |||||
| setParking, | |||||
| adjustData, | |||||
| setAdjustData, | |||||
| }} | |||||
| > | |||||
| {children} | |||||
| </CreateContext.Provider> | |||||
| ); | |||||
| } | |||||
| @@ -1,16 +1,17 @@ | |||||
| import { Box, Step, StepLabel, Stepper } from "@mui/material"; | import { Box, Step, StepLabel, Stepper } from "@mui/material"; | ||||
| import { getHTAdjustData } from "api/custom/hello-techno"; | |||||
| import { HTParking } from "api/custom/hello-techno"; | |||||
| import { createReceiptIssuingOrder } from "api/custom/hello-techno/receipt-issuing-order"; | import { createReceiptIssuingOrder } from "api/custom/hello-techno/receipt-issuing-order"; | ||||
| import { PageID, TabID } from "codes/page"; | import { PageID, TabID } from "codes/page"; | ||||
| import { getValue } from "components/hook-form/RHFAutoComplete"; | |||||
| import useAPICall from "hooks/useAPICall"; | import useAPICall from "hooks/useAPICall"; | ||||
| import useDashboard from "hooks/useDashBoard"; | import useDashboard from "hooks/useDashBoard"; | ||||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | import useSnackbarCustom from "hooks/useSnackbarCustom"; | ||||
| import { useEffect, useMemo, useState } from "react"; | import { useEffect, useMemo, useState } from "react"; | ||||
| import { CreateContextProvider } from "./contexts/CreateContext"; | |||||
| import useConfirm, { ConfirmDataProps } from "./hooks/useConfirm"; | import useConfirm, { ConfirmDataProps } from "./hooks/useConfirm"; | ||||
| import useCreate from "./hooks/useCreate"; | |||||
| import useInputReceiptStep from "./hooks/useInputReceiptStep"; | import useInputReceiptStep from "./hooks/useInputReceiptStep"; | ||||
| import useInputSMSSendAddress from "./hooks/useInputSMSSendAddress"; | import useInputSMSSendAddress from "./hooks/useInputSMSSendAddress"; | ||||
| import useSelectParkingStep from "./hooks/useSelectParkingStep"; | |||||
| import useParkingList from "./hooks/useParkingList"; | |||||
| export default function ReceiptIssuingOrderCreate() { | export default function ReceiptIssuingOrderCreate() { | ||||
| const { setHeaderTitle, setTabs } = useDashboard( | const { setHeaderTitle, setTabs } = useDashboard( | ||||
| @@ -18,6 +19,19 @@ export default function ReceiptIssuingOrderCreate() { | |||||
| TabID.NONE | TabID.NONE | ||||
| ); | ); | ||||
| useEffect(() => { | |||||
| setHeaderTitle("領収証発行依頼作成"); | |||||
| setTabs(null); | |||||
| }, []); | |||||
| return ( | |||||
| <CreateContextProvider> | |||||
| <Create /> | |||||
| </CreateContextProvider> | |||||
| ); | |||||
| } | |||||
| function Create() { | |||||
| const { parking, adjustData } = useCreate(); | |||||
| const { success, error } = useSnackbarCustom(); | const { success, error } = useSnackbarCustom(); | ||||
| const [mode, setMode] = useState< | const [mode, setMode] = useState< | ||||
| @@ -41,9 +55,9 @@ export default function ReceiptIssuingOrderCreate() { | |||||
| const getConfimData = (): ConfirmDataProps => { | const getConfimData = (): ConfirmDataProps => { | ||||
| return { | return { | ||||
| customerName: selectParkingStep.values("customerCode.label"), | |||||
| parkingName: selectParkingStep.values("parkingManagementCode.label"), | |||||
| adjustSeqNo: selectParkingStep.values("adjustSeqNo"), | |||||
| customerName: parking?.customer_name ?? "XXXXXXXXXXXX", | |||||
| parkingName: parking?.parking_name ?? "XXXXXXXXXXXXXXX", | |||||
| adjustSeqNo: adjustData ? String(adjustData.adjust_seq_no) : "", | |||||
| amount: inputReceiptStep.values("amount"), | amount: inputReceiptStep.values("amount"), | ||||
| date: inputReceiptStep.values("date"), | date: inputReceiptStep.values("date"), | ||||
| address: inputSMSSendAddress.values("address"), | address: inputSMSSendAddress.values("address"), | ||||
| @@ -52,39 +66,17 @@ export default function ReceiptIssuingOrderCreate() { | |||||
| }; | }; | ||||
| const getFormData = () => { | const getFormData = () => { | ||||
| return { | return { | ||||
| ...selectParkingStep.values(), | |||||
| ...parking, | |||||
| ...inputReceiptStep.values(), | ...inputReceiptStep.values(), | ||||
| ...inputSMSSendAddress.values(), | ...inputSMSSendAddress.values(), | ||||
| }; | }; | ||||
| }; | }; | ||||
| const adjustDataAPI = useAPICall({ | |||||
| apiMethod: getHTAdjustData, | |||||
| backDrop: true, | |||||
| onSuccess: ({ data }) => { | |||||
| inputReceiptStep.setAdjustData(data); | |||||
| const selectParkingStep = useParkingList({ | |||||
| onNext: (parking: HTParking) => { | |||||
| inputReceiptStep.init(); | |||||
| setMode("input_receipt"); | setMode("input_receipt"); | ||||
| }, | }, | ||||
| onFailed: () => { | |||||
| error("精算履歴が存在しません"); | |||||
| }, | |||||
| }); | |||||
| const selectParkingStep = useSelectParkingStep({ | |||||
| onNext: () => { | |||||
| const { customerCode, parkingManagementCode, adjustSeqNo } = | |||||
| getFormData(); | |||||
| if (adjustSeqNo) { | |||||
| adjustDataAPI.callAPI({ | |||||
| customer_code: getValue(customerCode), | |||||
| parking_management_code: getValue(parkingManagementCode), | |||||
| adjust_seq_no: adjustSeqNo, | |||||
| }); | |||||
| } else { | |||||
| inputReceiptStep.setAdjustData(null); | |||||
| setMode("input_receipt"); | |||||
| } | |||||
| }, | |||||
| }); | }); | ||||
| const inputReceiptStep = useInputReceiptStep({ | const inputReceiptStep = useInputReceiptStep({ | ||||
| @@ -131,9 +123,9 @@ export default function ReceiptIssuingOrderCreate() { | |||||
| const formData = getFormData(); | const formData = getFormData(); | ||||
| const sendData = makeSendData({ | const sendData = makeSendData({ | ||||
| customer_code: getValue(formData.customerCode), | |||||
| parking_management_code: getValue(formData.parkingManagementCode), | |||||
| adjust_seq_no: formData.adjustSeqNo, | |||||
| customer_code: formData.customer_code ?? "", | |||||
| parking_management_code: formData.parking_management_code ?? "", | |||||
| adjust_seq_no: adjustData?.adjust_seq_no, | |||||
| receipt_use_date: formData.date, | receipt_use_date: formData.date, | ||||
| receipt_amount: formData.amount, | receipt_amount: formData.amount, | ||||
| tax_amount: formData.tax_amount, | tax_amount: formData.tax_amount, | ||||
| @@ -144,10 +136,6 @@ export default function ReceiptIssuingOrderCreate() { | |||||
| callAPI(sendData); | callAPI(sendData); | ||||
| }; | }; | ||||
| useEffect(() => { | |||||
| setHeaderTitle("領収証発行依頼作成"); | |||||
| setTabs(null); | |||||
| }, []); | |||||
| return ( | return ( | ||||
| <Box sx={{ p: 1, m: 1 }}> | <Box sx={{ p: 1, m: 1 }}> | ||||
| <Stepper activeStep={step}> | <Stepper activeStep={step}> | ||||
| @@ -0,0 +1,8 @@ | |||||
| import { useContext } from "react"; | |||||
| import { CreateContext } from "../contexts/CreateContext"; | |||||
| export default function useCreate() { | |||||
| const context = useContext(CreateContext); | |||||
| return context; | |||||
| } | |||||
| @@ -1,16 +1,19 @@ | |||||
| import { yupResolver } from "@hookform/resolvers/yup"; | import { yupResolver } from "@hookform/resolvers/yup"; | ||||
| import { Box, Button, Stack, Typography } from "@mui/material"; | |||||
| import { Box, Button, Divider, Stack, Typography } from "@mui/material"; | |||||
| import { HasChildren } from "@types"; | import { HasChildren } from "@types"; | ||||
| import { HTAdjustData } from "api/custom/hello-techno"; | |||||
| import { getHTAdjustData } from "api/custom/hello-techno"; | |||||
| import RequireChip from "components/chip/RequireChip"; | import RequireChip from "components/chip/RequireChip"; | ||||
| import { FormProvider, RHFTextField } from "components/hook-form"; | import { FormProvider, RHFTextField } from "components/hook-form"; | ||||
| import RHFDatePicker from "components/hook-form/RHFDatePicker"; | import RHFDatePicker from "components/hook-form/RHFDatePicker"; | ||||
| import StackRow from "components/stack/StackRow"; | import StackRow from "components/stack/StackRow"; | ||||
| import { useEffect, useMemo, useState } from "react"; | |||||
| import useAPICall from "hooks/useAPICall"; | |||||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||||
| import { useEffect, useMemo } from "react"; | |||||
| import { useForm } from "react-hook-form"; | import { useForm } from "react-hook-form"; | ||||
| import { dateTimeParse } from "utils/datetime"; | import { dateTimeParse } from "utils/datetime"; | ||||
| import { calcInnerTax } from "utils/tax"; | import { calcInnerTax } from "utils/tax"; | ||||
| import * as Yup from "yup"; | import * as Yup from "yup"; | ||||
| import useCreate from "./useCreate"; | |||||
| type AreaBoxProps = { | type AreaBoxProps = { | ||||
| title: string; | title: string; | ||||
| @@ -18,17 +21,18 @@ type AreaBoxProps = { | |||||
| } & HasChildren; | } & HasChildren; | ||||
| function AreaBox({ title, children, require }: AreaBoxProps) { | function AreaBox({ title, children, require }: AreaBoxProps) { | ||||
| return ( | return ( | ||||
| <Box sx={{ maxWidth: 500 }}> | |||||
| <Stack sx={{ maxWidth: 500 }}> | |||||
| <StackRow> | <StackRow> | ||||
| <Typography variant="subtitle1">{title} </Typography> | <Typography variant="subtitle1">{title} </Typography> | ||||
| <RequireChip show={require ?? false} /> | |||||
| <RequireChip require={require ?? false} /> | |||||
| </StackRow> | </StackRow> | ||||
| {children} | {children} | ||||
| </Box> | |||||
| </Stack> | |||||
| ); | ); | ||||
| } | } | ||||
| type FormProps = { | type FormProps = { | ||||
| adjust_seq_no: string; | |||||
| amount: string; | amount: string; | ||||
| tax_amount: string; | tax_amount: string; | ||||
| date: Date | null; | date: Date | null; | ||||
| @@ -40,10 +44,13 @@ type Props = { | |||||
| onPrev?: VoidFunction; | onPrev?: VoidFunction; | ||||
| }; | }; | ||||
| export default function useInputReceiptStep({ onNext, onPrev }: Props) { | export default function useInputReceiptStep({ onNext, onPrev }: Props) { | ||||
| const [adjustData, _setAdjustData] = useState<HTAdjustData | null>(null); | |||||
| const { parking, adjustData, setAdjustData } = useCreate(); | |||||
| const { error } = useSnackbarCustom(); | |||||
| const form = useForm<FormProps>({ | const form = useForm<FormProps>({ | ||||
| defaultValues: { | defaultValues: { | ||||
| adjust_seq_no: "", | |||||
| amount: "", | amount: "", | ||||
| tax_amount: "", | tax_amount: "", | ||||
| date: null, | date: null, | ||||
| @@ -82,10 +89,6 @@ export default function useInputReceiptStep({ onNext, onPrev }: Props) { | |||||
| } | } | ||||
| }; | }; | ||||
| const setAdjustData = (data: HTAdjustData | null) => { | |||||
| _setAdjustData(data); | |||||
| }; | |||||
| const canCalcTax = useMemo(() => { | const canCalcTax = useMemo(() => { | ||||
| return !adjustData; | return !adjustData; | ||||
| }, [adjustData]); | }, [adjustData]); | ||||
| @@ -97,29 +100,108 @@ export default function useInputReceiptStep({ onNext, onPrev }: Props) { | |||||
| form.setValue("tax_amount", String(calcInnerTax(amount))); | form.setValue("tax_amount", String(calcInnerTax(amount))); | ||||
| }; | }; | ||||
| const adjustDataAPI = useAPICall({ | |||||
| apiMethod: getHTAdjustData, | |||||
| backDrop: true, | |||||
| onSuccess: ({ data }) => { | |||||
| setAdjustData(data); | |||||
| }, | |||||
| onFailed: () => { | |||||
| error("精算履歴が存在しません"); | |||||
| }, | |||||
| }); | |||||
| const handleSearchAdjsutData = () => { | |||||
| const adjustSeqNo = form.getValues("adjust_seq_no"); | |||||
| if (!adjustSeqNo) { | |||||
| error("精算連番を入力してください"); | |||||
| return; | |||||
| } | |||||
| const adjustSeqNoNumber = Number(adjustSeqNo); | |||||
| if (!(1 <= adjustSeqNoNumber && adjustSeqNoNumber <= 99999)) { | |||||
| error("1-999999の範囲で入力してください"); | |||||
| return; | |||||
| } | |||||
| if (!parking) { | |||||
| error("駐車場未選択"); | |||||
| return; | |||||
| } | |||||
| adjustDataAPI.callAPI({ | |||||
| adjust_seq_no: adjustSeqNo, | |||||
| customer_code: parking.customer_code, | |||||
| parking_management_code: parking.parking_management_code, | |||||
| }); | |||||
| }; | |||||
| const handleClearAdjustData = () => { | |||||
| init(); | |||||
| }; | |||||
| const readOnly = useMemo(() => { | const readOnly = useMemo(() => { | ||||
| return adjustData !== null; | return adjustData !== null; | ||||
| }, [adjustData]); | }, [adjustData]); | ||||
| const init = () => { | |||||
| form.clearErrors(); | |||||
| setAdjustData(null); | |||||
| form.setValue("adjust_seq_no", ""); | |||||
| form.setValue("amount", ""); | |||||
| form.setValue("tax_amount", ""); | |||||
| form.setValue("date", null); | |||||
| form.setValue("memo", ""); | |||||
| }; | |||||
| const element = ( | const element = ( | ||||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | ||||
| <Stack spacing={2} sx={{ p: 1, m: 1 }}> | <Stack spacing={2} sx={{ p: 1, m: 1 }}> | ||||
| <AreaBox title="利用日" require={true}> | |||||
| <Box mt={3}>〇精算連番が特定できる場合は入力してください</Box> | |||||
| <StackRow> | |||||
| <AreaBox title="精算連番"> | |||||
| <RHFTextField | |||||
| name="adjust_seq_no" | |||||
| size="small" | |||||
| type="number" | |||||
| readOnly={readOnly} | |||||
| /> | |||||
| </AreaBox> | |||||
| <Box display="flex" justifyContent="flex-end" flexDirection="column"> | |||||
| <Button | |||||
| variant="contained" | |||||
| color="info" | |||||
| onClick={handleSearchAdjsutData} | |||||
| > | |||||
| 検索 | |||||
| </Button> | |||||
| </Box> | |||||
| <Box display="flex" justifyContent="flex-end" flexDirection="column"> | |||||
| <Button variant="outlined" onClick={handleClearAdjustData}> | |||||
| クリアー | |||||
| </Button> | |||||
| </Box> | |||||
| </StackRow> | |||||
| <Divider /> | |||||
| <AreaBox title="利用日" require> | |||||
| <RHFDatePicker name="date" size="small" readOnly={readOnly} /> | <RHFDatePicker name="date" size="small" readOnly={readOnly} /> | ||||
| </AreaBox> | </AreaBox> | ||||
| <AreaBox title="金額" require={true}> | |||||
| <RHFTextField | |||||
| type="number" | |||||
| name="amount" | |||||
| size="small" | |||||
| InputProps={{ | |||||
| endAdornment: <div style={{ color: "black !important" }}>円</div>, | |||||
| }} | |||||
| sx={{ maxWidth: 150 }} | |||||
| readOnly={readOnly} | |||||
| /> | |||||
| <AreaBox title="金額" require> | |||||
| <Box> | |||||
| <RHFTextField | |||||
| type="number" | |||||
| name="amount" | |||||
| size="small" | |||||
| InputProps={{ | |||||
| endAdornment: ( | |||||
| <div style={{ color: "black !important" }}>円</div> | |||||
| ), | |||||
| }} | |||||
| sx={{ maxWidth: 150 }} | |||||
| readOnly={readOnly} | |||||
| /> | |||||
| </Box> | |||||
| </AreaBox> | </AreaBox> | ||||
| <AreaBox title="消費税(内税)" require={true}> | |||||
| <AreaBox title="消費税(内税)" require> | |||||
| <Stack direction="row" spacing={2}> | <Stack direction="row" spacing={2}> | ||||
| <RHFTextField | <RHFTextField | ||||
| type="number" | type="number" | ||||
| @@ -171,5 +253,6 @@ export default function useInputReceiptStep({ onNext, onPrev }: Props) { | |||||
| values: form.getValues, | values: form.getValues, | ||||
| setValue: form.setValue, | setValue: form.setValue, | ||||
| setAdjustData, | setAdjustData, | ||||
| init, | |||||
| }; | }; | ||||
| } | } | ||||
| @@ -1,29 +1,25 @@ | |||||
| import { yupResolver } from "@hookform/resolvers/yup"; | import { yupResolver } from "@hookform/resolvers/yup"; | ||||
| import { Box, Button, Stack, Typography } from "@mui/material"; | |||||
| import { Button, Stack, Typography } from "@mui/material"; | |||||
| import { HasChildren } from "@types"; | 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 RequireChip from "components/chip/RequireChip"; | |||||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||||
| import StackRow from "components/stack/StackRow"; | |||||
| import { useForm } from "react-hook-form"; | import { useForm } from "react-hook-form"; | ||||
| import * as Yup from "yup"; | import * as Yup from "yup"; | ||||
| type AreaBoxProps = { | type AreaBoxProps = { | ||||
| title: string; | title: string; | ||||
| require?: boolean; | |||||
| } & HasChildren; | } & HasChildren; | ||||
| function AreaBox({ title, children }: AreaBoxProps) { | |||||
| function AreaBox({ title, children, require }: AreaBoxProps) { | |||||
| return ( | return ( | ||||
| <Box sx={{ maxWidth: 500 }}> | |||||
| <Typography variant="subtitle1">{title}</Typography> | |||||
| <Stack sx={{ maxWidth: 500 }}> | |||||
| <StackRow> | |||||
| <Typography variant="subtitle1">{title} </Typography> | |||||
| <RequireChip require={require ?? false} /> | |||||
| </StackRow> | |||||
| {children} | {children} | ||||
| </Box> | |||||
| </Stack> | |||||
| ); | ); | ||||
| } | } | ||||
| @@ -64,7 +60,7 @@ export default function useInputSMSSendAddress({ onNext, onPrev }: Props) { | |||||
| const element = ( | const element = ( | ||||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | ||||
| <Stack spacing={2} sx={{ p: 1, m: 1 }}> | <Stack spacing={2} sx={{ p: 1, m: 1 }}> | ||||
| <AreaBox title="SMS送信先"> | |||||
| <AreaBox title="SMS送信先" require> | |||||
| <RHFTextField name="address" size="small" /> | <RHFTextField name="address" size="small" /> | ||||
| </AreaBox> | </AreaBox> | ||||
| <Stack direction="row" spacing={2}> | <Stack direction="row" spacing={2}> | ||||
| @@ -0,0 +1,227 @@ | |||||
| import { | |||||
| Box, | |||||
| Grid, | |||||
| Stack, | |||||
| Table, | |||||
| TableBody, | |||||
| TableCell, | |||||
| TableContainer, | |||||
| TablePagination, | |||||
| TableRow, | |||||
| Typography, | |||||
| } from "@mui/material"; | |||||
| import { | |||||
| HTParking, | |||||
| HTParkingsRequest, | |||||
| getHTParkings, | |||||
| } from "api/custom/hello-techno"; | |||||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||||
| import { TableHeadCustom } from "components/table"; | |||||
| import { HeadLabelProps } from "components/table/TableHeadCustom"; | |||||
| import useAPICall from "hooks/useAPICall"; | |||||
| import useTable, { UseTableReturn } from "hooks/useTable"; | |||||
| import { isEqual } from "lodash"; | |||||
| import { useState } from "react"; | |||||
| import { useForm } from "react-hook-form"; | |||||
| import useCreate from "./useCreate"; | |||||
| export const SearchParam = { | |||||
| PARKING_MANAGEMENT_CODE: "parking_management_code", | |||||
| PARKING_NAME: "parking_name", | |||||
| } as const; | |||||
| export type SearchParam = (typeof SearchParam)[keyof typeof SearchParam]; | |||||
| type Props = { onNext: (parking: HTParking) => void }; | |||||
| export default function useParkingList({ onNext }: Props) { | |||||
| const table = useTable<HTParking>(); | |||||
| const { setParking } = useCreate(); | |||||
| const handleNext = (parking: HTParking) => { | |||||
| setParking(parking); | |||||
| onNext(parking); | |||||
| }; | |||||
| const element = ( | |||||
| <Box> | |||||
| <SearchBox table={table} /> | |||||
| <TableBox table={table} onNext={handleNext} /> | |||||
| </Box> | |||||
| ); | |||||
| return { element }; | |||||
| } | |||||
| type FormProps = { | |||||
| [SearchParam.PARKING_MANAGEMENT_CODE]: string; | |||||
| [SearchParam.PARKING_NAME]: string; | |||||
| }; | |||||
| type CommonProps = { | |||||
| table: UseTableReturn<HTParking>; | |||||
| }; | |||||
| function SearchBox({ table }: CommonProps) { | |||||
| const form = useForm<FormProps>({ | |||||
| defaultValues: { | |||||
| [SearchParam.PARKING_MANAGEMENT_CODE]: "", | |||||
| [SearchParam.PARKING_NAME]: "", | |||||
| }, | |||||
| }); | |||||
| const { callAPI: callGetHTParkings, makeSendData } = useAPICall({ | |||||
| apiMethod: getHTParkings, | |||||
| backDrop: true, | |||||
| form, | |||||
| onSuccess: (res) => { | |||||
| table.setRowData(res.data.records); | |||||
| }, | |||||
| }); | |||||
| const [currentSendData, setCurrentSendData] = | |||||
| useState<HTParkingsRequest | null>(null); | |||||
| const handleSubmit = () => { | |||||
| fetch(); | |||||
| }; | |||||
| const handleBlur = () => { | |||||
| fetch(); | |||||
| }; | |||||
| const fetch = async () => { | |||||
| const data = form.getValues(); | |||||
| if (!data.parking_name && !data.parking_management_code) return; | |||||
| const sendData = makeSendData({ | |||||
| ...data, | |||||
| }); | |||||
| if (!isEqual(sendData, currentSendData)) { | |||||
| callGetHTParkings(sendData); | |||||
| setCurrentSendData(sendData); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||||
| <Box sx={{ p: 1, m: 1 }}> | |||||
| <Grid container spacing={2}> | |||||
| <Grid item xs={6} lg={4}> | |||||
| <Stack> | |||||
| <Typography>駐車場管理コード5桁</Typography> | |||||
| <RHFTextField | |||||
| name={SearchParam.PARKING_MANAGEMENT_CODE} | |||||
| onBlur={handleBlur} | |||||
| /> | |||||
| </Stack> | |||||
| </Grid> | |||||
| <Grid item xs={6} lg={8}> | |||||
| <Stack> | |||||
| <Typography>駐車場名</Typography> | |||||
| <RHFTextField | |||||
| name={SearchParam.PARKING_NAME} | |||||
| onBlur={handleBlur} | |||||
| /> | |||||
| </Stack> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Box> | |||||
| </FormProvider> | |||||
| ); | |||||
| } | |||||
| type TableProps = { | |||||
| onNext: (parking: HTParking) => void; | |||||
| } & CommonProps; | |||||
| function TableBox({ table, onNext }: TableProps) { | |||||
| const TABLE_HEAD: HeadLabelProps[] = [ | |||||
| { | |||||
| id: "customer_name", | |||||
| label: "運営会社名", | |||||
| align: "left", | |||||
| needSort: false, | |||||
| }, | |||||
| { id: "parking_name", label: "駐車場名", align: "left", needSort: false }, | |||||
| { | |||||
| id: "parking_management_code", | |||||
| label: "駐車場管理コード", | |||||
| align: "left", | |||||
| needSort: false, | |||||
| }, | |||||
| ]; | |||||
| const { | |||||
| order, | |||||
| page, | |||||
| sort, | |||||
| rowsPerPage, | |||||
| fetched, | |||||
| fillteredRow, | |||||
| isNotFound, | |||||
| dataLength, | |||||
| // | |||||
| onSort, | |||||
| onChangePage, | |||||
| onChangeRowsPerPage, | |||||
| // | |||||
| setRowData, | |||||
| // | |||||
| ROWS_PER_PAGES, | |||||
| } = table; | |||||
| return ( | |||||
| <> | |||||
| <TableContainer | |||||
| sx={{ | |||||
| // minWidth: 800, | |||||
| position: "relative", | |||||
| }} | |||||
| > | |||||
| <Table size="small"> | |||||
| <TableHeadCustom | |||||
| order={order} | |||||
| orderBy={sort} | |||||
| headLabel={TABLE_HEAD} | |||||
| rowCount={1} | |||||
| numSelected={0} | |||||
| onSort={onSort} | |||||
| /> | |||||
| <TableBody> | |||||
| {fillteredRow.map((row, index) => ( | |||||
| <Row data={row} key={index} onNext={onNext} /> | |||||
| ))} | |||||
| </TableBody> | |||||
| </Table> | |||||
| </TableContainer> | |||||
| <Box sx={{ position: "relative" }}> | |||||
| <TablePagination | |||||
| rowsPerPageOptions={ROWS_PER_PAGES} | |||||
| component="div" | |||||
| count={dataLength} | |||||
| rowsPerPage={rowsPerPage} | |||||
| page={page} | |||||
| onPageChange={onChangePage} | |||||
| onRowsPerPageChange={onChangeRowsPerPage} | |||||
| /> | |||||
| </Box> | |||||
| </> | |||||
| ); | |||||
| } | |||||
| type RowProps = { | |||||
| data: HTParking; | |||||
| onNext: (parking: HTParking) => void; | |||||
| }; | |||||
| function Row({ data, onNext }: RowProps) { | |||||
| const handleClick = () => { | |||||
| onNext(data); | |||||
| }; | |||||
| return ( | |||||
| <TableRow hover sx={{ cursor: "pointer" }} onClick={handleClick}> | |||||
| <TableCell>{data.customer_name}</TableCell> | |||||
| <TableCell>{data.parking_name ?? data.name}</TableCell> | |||||
| <TableCell>{data.parking_management_code}</TableCell> | |||||
| </TableRow> | |||||
| ); | |||||
| } | |||||
| @@ -1,221 +0,0 @@ | |||||
| import { yupResolver } from "@hookform/resolvers/yup"; | |||||
| import { Box, Button, Grid, Stack, Typography } from "@mui/material"; | |||||
| import { HasChildren } from "@types"; | |||||
| import { | |||||
| getHTCustomers, | |||||
| getHTParking, | |||||
| getHTParkings, | |||||
| } from "api/custom/hello-techno"; | |||||
| import { | |||||
| FormProvider, | |||||
| RHFAutoComplete, | |||||
| RHFTextField, | |||||
| } from "components/hook-form"; | |||||
| import { | |||||
| AutoCompleteOption, | |||||
| AutoCompleteOptionType, | |||||
| } from "components/hook-form/RHFAutoComplete"; | |||||
| import useAPICall from "hooks/useAPICall"; | |||||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||||
| import { useEffect, useMemo, 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 ( | |||||
| <Box sx={{ maxWidth: 500 }}> | |||||
| <Typography variant="subtitle1">{title}</Typography> | |||||
| {children} | |||||
| </Box> | |||||
| ); | |||||
| } | |||||
| type FormProps = { | |||||
| customerCode: AutoCompleteOptionType; | |||||
| parkingManagementCode: AutoCompleteOptionType; | |||||
| adjustSeqNo: string; | |||||
| serachParkingManagementCode: string; | |||||
| }; | |||||
| type Props = { | |||||
| onNext?: VoidFunction; | |||||
| }; | |||||
| export default function useSelectParkingStep({ onNext }: Props) { | |||||
| const [customers, setCustomers] = useState<AutoCompleteOption[]>([]); | |||||
| const [parkings, setParkings] = useState<AutoCompleteOption[]>([]); | |||||
| const { error } = useSnackbarCustom(); | |||||
| const customerAPI = useAPICall({ | |||||
| apiMethod: getHTCustomers, | |||||
| backDrop: true, | |||||
| onSuccess: ({ data }) => { | |||||
| const options: AutoCompleteOption[] = data.records.map( | |||||
| ({ customer_code, name }) => { | |||||
| return { | |||||
| label: name, | |||||
| value: customer_code, | |||||
| }; | |||||
| } | |||||
| ); | |||||
| setCustomers(options); | |||||
| }, | |||||
| }); | |||||
| const parkingAPI = useAPICall({ | |||||
| apiMethod: getHTParkings, | |||||
| backDrop: true, | |||||
| onSuccess: ({ data }) => { | |||||
| const options: AutoCompleteOption[] = data.records.map( | |||||
| ({ parking_management_code, name }) => { | |||||
| return { | |||||
| label: name, | |||||
| value: parking_management_code, | |||||
| }; | |||||
| } | |||||
| ); | |||||
| setParkings(options); | |||||
| }, | |||||
| }); | |||||
| const parkingByCodeAPI = useAPICall({ | |||||
| apiMethod: getHTParking, | |||||
| backDrop: true, | |||||
| onSuccess: ({ data }) => { | |||||
| const options: AutoCompleteOption[] = []; | |||||
| options.push({ | |||||
| label: data.name, | |||||
| value: data.parking_management_code, | |||||
| }); | |||||
| setParkings(options); | |||||
| form.setValue("customerCode", data.customer_code); | |||||
| }, | |||||
| onFailed: (res) => { | |||||
| error("駐車場の特定に失敗しました"); | |||||
| console.log(res); | |||||
| }, | |||||
| }); | |||||
| const form = useForm<FormProps>({ | |||||
| defaultValues: { | |||||
| customerCode: null, | |||||
| parkingManagementCode: null, | |||||
| adjustSeqNo: "", | |||||
| serachParkingManagementCode: "", | |||||
| }, | |||||
| 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 searchParkingManagementCode = form.watch("serachParkingManagementCode"); | |||||
| const handleSubmit = () => { | |||||
| if (onNext) { | |||||
| onNext(); | |||||
| } | |||||
| }; | |||||
| const handleClickSearchParking = () => { | |||||
| parkingByCodeAPI.callAPI({ | |||||
| parking_management_code: searchParkingManagementCode, | |||||
| }); | |||||
| }; | |||||
| const canSearchParking = useMemo(() => { | |||||
| const reg = /^[0-9]{5}$/; | |||||
| return reg.test(searchParkingManagementCode); | |||||
| }, [searchParkingManagementCode]); | |||||
| // 顧客一覧取得 | |||||
| useEffect(() => { | |||||
| customerAPI.callAPI({}); | |||||
| }, []); | |||||
| // 駐車場一覧取得 | |||||
| useEffect(() => { | |||||
| if (!canSearchParking) { | |||||
| setParkings([]); | |||||
| form.setValue("parkingManagementCode", null); | |||||
| if (customerCode) { | |||||
| parkingAPI.callAPI({ customer_code: customerCode }); | |||||
| } | |||||
| } | |||||
| }, [customerCode]); | |||||
| useEffect(() => { | |||||
| if (parkings.length === 1) { | |||||
| const parking = parkings[0]; | |||||
| form.setValue("parkingManagementCode", parking); | |||||
| } | |||||
| }, [parkings]); | |||||
| const element = ( | |||||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||||
| <Stack spacing={2} sx={{ p: 1, m: 1 }}> | |||||
| <Grid container spacing={2}> | |||||
| <Grid item xs={12} md={4}> | |||||
| <Stack> | |||||
| <AreaBox title="運営会社"> | |||||
| <RHFAutoComplete | |||||
| name="customerCode" | |||||
| options={customers} | |||||
| size="small" | |||||
| /> | |||||
| </AreaBox> | |||||
| <AreaBox title="駐車場"> | |||||
| <RHFAutoComplete | |||||
| name="parkingManagementCode" | |||||
| options={parkings} | |||||
| size="small" | |||||
| /> | |||||
| </AreaBox> | |||||
| </Stack> | |||||
| </Grid> | |||||
| <Grid item xs={12} md={4}> | |||||
| <Stack spacing={3}> | |||||
| <AreaBox title="駐車場管理コード"> | |||||
| <RHFTextField name="serachParkingManagementCode" /> | |||||
| </AreaBox> | |||||
| <Stack direction="row"> | |||||
| <Button | |||||
| variant="outlined" | |||||
| color="info" | |||||
| onClick={handleClickSearchParking} | |||||
| disabled={!canSearchParking} | |||||
| > | |||||
| 検索 | |||||
| </Button> | |||||
| </Stack> | |||||
| </Stack> | |||||
| </Grid> | |||||
| </Grid> | |||||
| <AreaBox title="精算連番"> | |||||
| <RHFTextField name="adjustSeqNo" size="small" /> | |||||
| </AreaBox> | |||||
| <Stack direction="row" spacing={2}> | |||||
| <Button variant="contained" type="submit"> | |||||
| 次へ | |||||
| </Button> | |||||
| </Stack> | |||||
| </Stack> | |||||
| </FormProvider> | |||||
| ); | |||||
| return { element, values: form.getValues, setValue: form.setValue }; | |||||
| } | |||||