| @@ -17,7 +17,7 @@ export const ApiId = { | |||||
| PASSWORD_SETTING_START: id++, | PASSWORD_SETTING_START: id++, | ||||
| PASSWORD_SETTING_VERIFY: id++, | PASSWORD_SETTING_VERIFY: id++, | ||||
| // 定期券連 --------------------------------- | |||||
| // 定期関連--------------------------------- | |||||
| SEASON_TICKET_CONTRACTS: id++, | SEASON_TICKET_CONTRACTS: id++, | ||||
| PAYMENT_PLANS: id++, | PAYMENT_PLANS: id++, | ||||
| SEASON_TICKET_CONTRACT_STICKER_RE_ORDER: 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_STUDENT_LICENSE_IMAGES: id++, | ||||
| SEASON_TICKET_CONTRACT_UPLOAD_OTHER_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: id++, | ||||
| FAQ_GENRES: 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", | "upload/student-license-images", | ||||
| [A.SEASON_TICKET_CONTRACT_UPLOAD_OTHER_LICENSE_IMAGES]: | [A.SEASON_TICKET_CONTRACT_UPLOAD_OTHER_LICENSE_IMAGES]: | ||||
| "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.START_CHANGE_EMAIL]: "email/change/start", | ||||
| [A.VERIFY_CHANGE_EMAIL]: "email/change/verify", | [A.VERIFY_CHANGE_EMAIL]: "email/change/verify", | ||||
| [A.CUSTOMER_UPDATE_INFO_ORDER]: "customer/update-info-order", | [A.CUSTOMER_UPDATE_INFO_ORDER]: "customer/update-info-order", | ||||
| @@ -31,6 +31,9 @@ export const PageID = { | |||||
| DASHBOARD_USER_UPDATE_USER_INFO: id++, | DASHBOARD_USER_UPDATE_USER_INFO: id++, | ||||
| DASHBOARD_USER_BANK_REGISTER: id++, | DASHBOARD_USER_BANK_REGISTER: id++, | ||||
| SEASON_TICKET_CONTRACT_SELECTION_ENTRY: id++, | |||||
| SEASON_TICKET_CONTRACT_ENTRY_CANCEL: id++, | |||||
| PAGE_403: id++, | PAGE_403: id++, | ||||
| PAGE_404: id++, | PAGE_404: id++, | ||||
| } as const; | } 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() { | export function Routes() { | ||||
| const { initialized } = useAuth(); | const { initialized } = useAuth(); | ||||
| @@ -52,6 +65,7 @@ export function Routes() { | |||||
| return useRoutes([ | return useRoutes([ | ||||
| CommonRoutes(), | CommonRoutes(), | ||||
| AuthRoutes(), | AuthRoutes(), | ||||
| SeasonTicketEntryRoutes(), | |||||
| ...DashboardRoutes(), | ...DashboardRoutes(), | ||||
| { | { | ||||
| path: "403", | path: "403", | ||||
| @@ -77,6 +91,14 @@ const PasswordSettingVerify = Loadable( | |||||
| lazy(() => import("pages/auth/password-setting-verify")) | 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"))); | const Page403 = Loadable(lazy(() => import("pages/common/Page403"))); | ||||
| @@ -79,6 +79,12 @@ const PATHS = { | |||||
| [makePathKey(PageID.USER_SETTING_PASSWORD_VERIFY)]: | [makePathKey(PageID.USER_SETTING_PASSWORD_VERIFY)]: | ||||
| "/setting/password/verify/:token", | "/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, | ...PATHS_DASHBOARD, | ||||