| @@ -17,7 +17,7 @@ export const ApiId = { | |||
| PASSWORD_SETTING_START: id++, | |||
| PASSWORD_SETTING_VERIFY: id++, | |||
| // 定期券連 --------------------------------- | |||
| // 定期関連--------------------------------- | |||
| SEASON_TICKET_CONTRACTS: id++, | |||
| PAYMENT_PLANS: id++, | |||
| SEASON_TICKET_CONTRACT_STICKER_RE_ORDER: id++, | |||
| @@ -30,6 +30,12 @@ export const ApiId = { | |||
| SEASON_TICKET_CONTRACT_UPLOAD_STUDENT_LICENSE_IMAGES: id++, | |||
| SEASON_TICKET_CONTRACT_UPLOAD_OTHER_LICENSE_IMAGES: id++, | |||
| // 定期申込関連--------------------------------- | |||
| SEASON_TICKET_CONTRACT_ENTRY_INFO: id++, | |||
| SEASON_TICKET_CONTRACT_ENTRY_CANCEL: id++, | |||
| SEASON_TICKET_CONTRACT_SELECTION_INFO: id++, | |||
| SEASON_TICKET_CONTRACT_SELECTION_ENTRY: id++, | |||
| // 問い合わせ関連------------------------------- | |||
| FAQ: id++, | |||
| FAQ_GENRES: id++, | |||
| @@ -0,0 +1,48 @@ | |||
| import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "api"; | |||
| import { getUrl } from "./url"; | |||
| export type SeasonTicketContractEntry = { | |||
| customer_name: string | null; | |||
| parking_name: string | null; | |||
| entry_datetime: string | null; | |||
| plan_name: string | null; | |||
| address: string | null; | |||
| phone_no: string | null; | |||
| car_amount: string | null; | |||
| revision: number; | |||
| }; | |||
| // -------申込内容取得--------------- | |||
| type SeasonTicketContractEntryRequest = { | |||
| record_no: string; | |||
| fs: string; // ハッシュパスワード | |||
| }; | |||
| type SeasonTicketContractEntryResponse = { | |||
| data: SeasonTicketContractEntry; | |||
| } & APICommonResponse; | |||
| export const getSeasonTicketContractEntry = async ( | |||
| data: SeasonTicketContractEntryRequest | |||
| ) => { | |||
| const res = await request<SeasonTicketContractEntryResponse>({ | |||
| url: getUrl(ApiId.SEASON_TICKET_CONTRACT_ENTRY_INFO), | |||
| method: HttpMethod.GET, | |||
| data: new URLSearchParams(data), | |||
| }); | |||
| return res; | |||
| }; | |||
| // -------申込キャンセル--------------- | |||
| type SeasonTicketContractEntryCancelRequest = { | |||
| record_no: string; | |||
| fs: string; // ハッシュパスワード | |||
| }; | |||
| export const cancelSeasonTicketContractEntry = async ( | |||
| data: SeasonTicketContractEntryCancelRequest | |||
| ) => { | |||
| const res = await request({ | |||
| url: getUrl(ApiId.SEASON_TICKET_CONTRACT_ENTRY_CANCEL), | |||
| method: HttpMethod.POST, | |||
| data: makeParam(data), | |||
| }); | |||
| return res; | |||
| }; | |||
| @@ -0,0 +1,43 @@ | |||
| import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from "api"; | |||
| import { getUrl } from "./url"; | |||
| import { SeasonTicketContractEntry } from "./season-ticket-contract-entry"; | |||
| // -------申込内容取得--------------- | |||
| type SeasonTicketContractEntryRequest = { | |||
| selection_record_no: string; | |||
| entry_record_no: string; | |||
| fs: string; // ハッシュパスワード | |||
| }; | |||
| type SeasonTicketContractEntryResponse = { | |||
| data: SeasonTicketContractEntry; | |||
| } & APICommonResponse; | |||
| export const getSeasonTicketContractSelection = async ( | |||
| data: SeasonTicketContractEntryRequest | |||
| ) => { | |||
| const res = await request<SeasonTicketContractEntryResponse>({ | |||
| url: getUrl(ApiId.SEASON_TICKET_CONTRACT_SELECTION_INFO), | |||
| method: HttpMethod.GET, | |||
| data: new URLSearchParams(data), | |||
| }); | |||
| return res; | |||
| }; | |||
| // -------選考申込--------------- | |||
| type SeasonTicketContractSelectionEntryRequest = { | |||
| selection_record_no: string; | |||
| entry_record_no: string; | |||
| fs: string; // ハッシュパスワード | |||
| address: string; | |||
| phone_no: string; | |||
| }; | |||
| export const entrySeasonTicketContractSelection = async ( | |||
| data: SeasonTicketContractSelectionEntryRequest | |||
| ) => { | |||
| const res = await request({ | |||
| url: getUrl(ApiId.SEASON_TICKET_CONTRACT_SELECTION_ENTRY), | |||
| method: HttpMethod.POST, | |||
| data: makeParam(data), | |||
| }); | |||
| return res; | |||
| }; | |||
| @@ -31,6 +31,15 @@ const urls = { | |||
| "upload/student-license-images", | |||
| [A.SEASON_TICKET_CONTRACT_UPLOAD_OTHER_LICENSE_IMAGES]: | |||
| "upload/other-license-images", | |||
| [A.SEASON_TICKET_CONTRACT_ENTRY_INFO]: "season-ticket-contract/entry/info", | |||
| [A.SEASON_TICKET_CONTRACT_ENTRY_CANCEL]: | |||
| "season-ticket-contract/entry/cancel", | |||
| [A.SEASON_TICKET_CONTRACT_SELECTION_INFO]: | |||
| "season-ticket-contract/selection/info", | |||
| [A.SEASON_TICKET_CONTRACT_SELECTION_ENTRY]: | |||
| "season-ticket-contract/selection/entry", | |||
| [A.START_CHANGE_EMAIL]: "email/change/start", | |||
| [A.VERIFY_CHANGE_EMAIL]: "email/change/verify", | |||
| [A.CUSTOMER_UPDATE_INFO_ORDER]: "customer/update-info-order", | |||
| @@ -31,6 +31,9 @@ export const PageID = { | |||
| DASHBOARD_USER_UPDATE_USER_INFO: id++, | |||
| DASHBOARD_USER_BANK_REGISTER: id++, | |||
| SEASON_TICKET_CONTRACT_SELECTION_ENTRY: id++, | |||
| SEASON_TICKET_CONTRACT_ENTRY_CANCEL: id++, | |||
| PAGE_403: id++, | |||
| PAGE_404: id++, | |||
| } as const; | |||
| @@ -0,0 +1,130 @@ | |||
| import { | |||
| Box, | |||
| Button, | |||
| Stack, | |||
| Table, | |||
| TableBody, | |||
| TableCell, | |||
| TableRow, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { | |||
| cancelSeasonTicketContractEntry, | |||
| getSeasonTicketContractEntry, | |||
| } from "api/season-ticket-contract-entry"; | |||
| import TextFieldEx from "components/form/TextFieldEx"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import { useEffect, useState } from "react"; | |||
| import { useParams } from "react-router-dom"; | |||
| export default function Cancel() { | |||
| const { entryRecordNo: paramEntryRecordNo, fs: paramFs } = useParams(); | |||
| const [mode, setMode] = useState< | |||
| "initialize" | "failed" | "confirm" | "done" | |||
| >("initialize"); | |||
| const [parkingName, setParkingName] = useState(""); | |||
| const [planName, setPlanName] = useState(""); | |||
| const [entryDatetime, setEntryDatetime] = useState(""); | |||
| const { callAPI: callGetSeasonTicketContractEntry, generalErrorMessage } = | |||
| useAPICall({ | |||
| apiMethod: getSeasonTicketContractEntry, | |||
| backDrop: true, | |||
| onSuccess: ({ data }) => { | |||
| setParkingName(data.parking_name ?? "-"); | |||
| setPlanName(data.plan_name ?? "-"); | |||
| setEntryDatetime(data.entry_datetime ?? "-"); | |||
| setMode("confirm"); | |||
| }, | |||
| onFailed: () => { | |||
| setMode("failed"); | |||
| }, | |||
| }); | |||
| const { callAPI: callCancelSeasonTicketContractEntry } = useAPICall({ | |||
| apiMethod: cancelSeasonTicketContractEntry, | |||
| backDrop: true, | |||
| onSuccess: () => { | |||
| setMode("done"); | |||
| }, | |||
| onFailed: () => { | |||
| setMode("failed"); | |||
| }, | |||
| }); | |||
| const handleSubmit = () => { | |||
| callCancelSeasonTicketContractEntry({ | |||
| record_no: paramEntryRecordNo ?? "", | |||
| fs: paramFs ?? "", | |||
| }); | |||
| }; | |||
| useEffect(() => { | |||
| if (paramEntryRecordNo && paramFs) { | |||
| callGetSeasonTicketContractEntry({ | |||
| record_no: paramEntryRecordNo, | |||
| fs: paramFs, | |||
| }); | |||
| } | |||
| }, [paramEntryRecordNo, paramFs]); | |||
| if (mode === "failed") { | |||
| return ( | |||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | |||
| <Stack spacing={3}> | |||
| <Box>{generalErrorMessage ? generalErrorMessage : "認証失敗"}</Box> | |||
| </Stack> | |||
| </Box> | |||
| ); | |||
| } | |||
| if (mode === "confirm") { | |||
| return ( | |||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | |||
| <Stack spacing={3}> | |||
| <Typography variant="h5">申込キャンセル</Typography> | |||
| <Box>下記内容で申込を承っております。</Box> | |||
| <Box> | |||
| <Table size="small"> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell>駐車場</TableCell> | |||
| <TableCell> | |||
| <TextFieldEx value={parkingName} readOnly fullWidth /> | |||
| </TableCell> | |||
| </TableRow> | |||
| <TableRow> | |||
| <TableCell>プラン</TableCell> | |||
| <TableCell> | |||
| <TextFieldEx value={planName} readOnly fullWidth /> | |||
| </TableCell> | |||
| </TableRow> | |||
| <TableRow> | |||
| <TableCell>申込日時</TableCell> | |||
| <TableCell> | |||
| <TextFieldEx value={entryDatetime} readOnly fullWidth /> | |||
| </TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| </Box> | |||
| <Button variant="contained" onClick={handleSubmit}> | |||
| キャンセルする | |||
| </Button> | |||
| </Stack> | |||
| </Box> | |||
| ); | |||
| } | |||
| if (mode === "done") { | |||
| return ( | |||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | |||
| <Stack spacing={3}> | |||
| <Typography variant="h5">申込キャンセル</Typography> | |||
| <Box>キャンセルを承りました。</Box> | |||
| </Stack> | |||
| </Box> | |||
| ); | |||
| } | |||
| return null; | |||
| } | |||
| @@ -0,0 +1,227 @@ | |||
| import { | |||
| Box, | |||
| Button, | |||
| Stack, | |||
| Table, | |||
| TableBody, | |||
| TableCell, | |||
| TableRow, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { HasChildren } from "@types"; | |||
| import { SeasonTicketContractEntry } from "api/season-ticket-contract-entry"; | |||
| import { | |||
| entrySeasonTicketContractSelection, | |||
| getSeasonTicketContractSelection, | |||
| } from "api/season-ticket-contract-selection"; | |||
| import RequireChip from "components/chip/RequireChip"; | |||
| import InputAlert from "components/form/InputAlert"; | |||
| import TextFieldEx from "components/form/TextFieldEx"; | |||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||
| import StackRow from "components/stack/StackRow"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import { useEffect, useState } from "react"; | |||
| import { useForm } from "react-hook-form"; | |||
| import { useParams } from "react-router-dom"; | |||
| import { scrollToTop } from "utils/page"; | |||
| type AreaBoxProps = { | |||
| label: string; | |||
| require?: boolean; | |||
| } & HasChildren; | |||
| function AreaBox({ label, children, require }: AreaBoxProps) { | |||
| return ( | |||
| <Box> | |||
| <StackRow> | |||
| <Typography variant="subtitle1">〇{label}</Typography> | |||
| <RequireChip require={require ?? false} /> | |||
| </StackRow> | |||
| {children} | |||
| </Box> | |||
| ); | |||
| } | |||
| type FormProps = { | |||
| address: string; | |||
| phone_no: string; | |||
| parking_name: string; | |||
| plan_name: string; | |||
| car_amount: string; | |||
| }; | |||
| export default function Entry() { | |||
| const { | |||
| selectionRecordNo: paramSelectionRecordNo, | |||
| entryRecordNo: paramEntryRecordNo, | |||
| fs: paramFs, | |||
| } = useParams(); | |||
| const [mode, setMode] = useState< | |||
| "initialize" | "failed" | "input" | "confirm" | "done" | |||
| >("initialize"); | |||
| const [entry, setEntry] = useState<SeasonTicketContractEntry | null>(null); | |||
| const form = useForm<FormProps>({ | |||
| defaultValues: { | |||
| address: "", | |||
| phone_no: "", | |||
| parking_name: "", | |||
| plan_name: "", | |||
| car_amount: "", | |||
| }, | |||
| }); | |||
| const { callAPI: callGetSeasonTicketContractSelection, generalErrorMessage } = | |||
| useAPICall({ | |||
| apiMethod: getSeasonTicketContractSelection, | |||
| backDrop: true, | |||
| form, | |||
| onSuccess: ({ data }) => { | |||
| setMode("input"); | |||
| setEntry(data); | |||
| }, | |||
| onFailed: () => { | |||
| setMode("failed"); | |||
| }, | |||
| }); | |||
| const { callAPI: callEntrySeasonTicketContractSelection } = useAPICall({ | |||
| apiMethod: entrySeasonTicketContractSelection, | |||
| backDrop: true, | |||
| form, | |||
| onSuccess: () => { | |||
| setMode("done"); | |||
| }, | |||
| onFailed: () => { | |||
| setMode("input"); | |||
| }, | |||
| }); | |||
| const handleSubmit = (data: FormProps) => { | |||
| callEntrySeasonTicketContractSelection({ | |||
| ...data, | |||
| selection_record_no: paramSelectionRecordNo ?? "", | |||
| entry_record_no: paramEntryRecordNo ?? "", | |||
| fs: paramFs ?? "", | |||
| }); | |||
| }; | |||
| useEffect(() => { | |||
| if (paramSelectionRecordNo && paramEntryRecordNo && paramFs) { | |||
| callGetSeasonTicketContractSelection({ | |||
| selection_record_no: paramSelectionRecordNo, | |||
| entry_record_no: paramEntryRecordNo, | |||
| fs: paramFs, | |||
| }); | |||
| } | |||
| }, [paramSelectionRecordNo, paramEntryRecordNo, paramFs]); | |||
| useEffect(() => { | |||
| if (entry) { | |||
| form.setValue("address", entry.address ?? ""); | |||
| form.setValue("phone_no", entry.phone_no ?? ""); | |||
| form.setValue("parking_name", entry.parking_name ?? ""); | |||
| form.setValue("plan_name", entry.plan_name ?? ""); | |||
| form.setValue("car_amount", (entry.car_amount ?? "0") + "台"); | |||
| } | |||
| }, [entry]); | |||
| useEffect(() => { | |||
| scrollToTop("auto"); | |||
| }, [mode]); | |||
| return ( | |||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | |||
| <Stack spacing={3}> | |||
| <Typography variant="h5">申込</Typography> | |||
| {mode === "initialize" && <Typography>読込中</Typography>} | |||
| {mode === "failed" && ( | |||
| <Typography>{generalErrorMessage ?? "認証失敗"}</Typography> | |||
| )} | |||
| {mode === "input" && ( | |||
| <> | |||
| <InputAlert formState={form.formState} /> | |||
| <AreaBox label="駐車場名"> | |||
| <RHFTextField name="parking_name" readOnly /> | |||
| </AreaBox> | |||
| <AreaBox label="プラン名"> | |||
| <RHFTextField name="plan_name" readOnly /> | |||
| </AreaBox> | |||
| <AreaBox label="台数"> | |||
| <RHFTextField name="car_amount" readOnly /> | |||
| </AreaBox> | |||
| <AreaBox label="住所" require> | |||
| <RHFTextField name="address" /> | |||
| </AreaBox> | |||
| <AreaBox label="電話番号" require> | |||
| <RHFTextField name="phone_no" /> | |||
| </AreaBox> | |||
| <Button | |||
| variant="contained" | |||
| onClick={() => { | |||
| setMode("confirm"); | |||
| }} | |||
| > | |||
| 次へ | |||
| </Button> | |||
| </> | |||
| )} | |||
| {mode === "confirm" && ( | |||
| <> | |||
| <Box> | |||
| <Table size="small"> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell>駐車場名</TableCell> | |||
| <TableCell> | |||
| <TextFieldEx | |||
| value={form.getValues("parking_name")} | |||
| readOnly | |||
| fullWidth | |||
| /> | |||
| </TableCell> | |||
| </TableRow> | |||
| <TableRow> | |||
| <TableCell>プラン名</TableCell> | |||
| <TableCell> | |||
| <TextFieldEx | |||
| value={form.getValues("plan_name")} | |||
| readOnly | |||
| fullWidth | |||
| /> | |||
| </TableCell> | |||
| </TableRow> | |||
| <TableRow> | |||
| <TableCell>住所</TableCell> | |||
| <TableCell> | |||
| <TextFieldEx | |||
| value={form.getValues("address")} | |||
| readOnly | |||
| fullWidth | |||
| /> | |||
| </TableCell> | |||
| </TableRow> | |||
| <TableRow> | |||
| <TableCell>電話番号</TableCell> | |||
| <TableCell> | |||
| <TextFieldEx | |||
| value={form.getValues("phone_no")} | |||
| readOnly | |||
| fullWidth | |||
| /> | |||
| </TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| </Box> | |||
| <Button variant="contained" type="submit"> | |||
| 確定 | |||
| </Button> | |||
| </> | |||
| )} | |||
| {mode === "done" && <Box>申込受付完了しました</Box>} | |||
| </Stack> | |||
| </Box> | |||
| </FormProvider> | |||
| ); | |||
| } | |||
| @@ -45,6 +45,19 @@ const AuthRoutes = (): RouteObject => ({ | |||
| }, | |||
| ], | |||
| }); | |||
| const SeasonTicketEntryRoutes = (): RouteObject => ({ | |||
| element: <SimpleLayout />, | |||
| children: [ | |||
| { | |||
| path: getRoute(PageID.SEASON_TICKET_CONTRACT_SELECTION_ENTRY), | |||
| element: <SeasonTicketContractSelectionEntry />, | |||
| }, | |||
| { | |||
| path: getRoute(PageID.SEASON_TICKET_CONTRACT_ENTRY_CANCEL), | |||
| element: <SeasonTicketContractEntryCancel />, | |||
| }, | |||
| ], | |||
| }); | |||
| export function Routes() { | |||
| const { initialized } = useAuth(); | |||
| @@ -52,6 +65,7 @@ export function Routes() { | |||
| return useRoutes([ | |||
| CommonRoutes(), | |||
| AuthRoutes(), | |||
| SeasonTicketEntryRoutes(), | |||
| ...DashboardRoutes(), | |||
| { | |||
| path: "403", | |||
| @@ -77,6 +91,14 @@ const PasswordSettingVerify = Loadable( | |||
| lazy(() => import("pages/auth/password-setting-verify")) | |||
| ); | |||
| // 申込関連 | |||
| const SeasonTicketContractSelectionEntry = Loadable( | |||
| lazy(() => import("pages/season-ticket-contract-selection/entry")) | |||
| ); | |||
| const SeasonTicketContractEntryCancel = Loadable( | |||
| lazy(() => import("pages/season-ticket-contract-selection/cancel")) | |||
| ); | |||
| // その他 --------------------------------- | |||
| const Page403 = Loadable(lazy(() => import("pages/common/Page403"))); | |||
| @@ -79,6 +79,12 @@ const PATHS = { | |||
| [makePathKey(PageID.USER_SETTING_PASSWORD_VERIFY)]: | |||
| "/setting/password/verify/:token", | |||
| // 申込/選考 | |||
| [makePathKey(PageID.SEASON_TICKET_CONTRACT_SELECTION_ENTRY)]: | |||
| "/season-ticket-contract/selection/entry/:selectionRecordNo/:entryRecordNo/:fs", | |||
| [makePathKey(PageID.SEASON_TICKET_CONTRACT_ENTRY_CANCEL)]: | |||
| "/season-ticket-contract/entry/cancel/:entryRecordNo/:fs", | |||
| // ダッシュボード---------------- | |||
| ...PATHS_DASHBOARD, | |||