| @@ -21,6 +21,7 @@ | |||
| "@types/react": "^18.0.0", | |||
| "@types/react-dom": "^18.0.0", | |||
| "@types/react-router-dom": "^5.3.3", | |||
| "@types/sprintf-js": "^1.1.2", | |||
| "axios": "^1.4.0", | |||
| "date-fns": "^2.30.0", | |||
| "lodash": "^4.17.21", | |||
| @@ -30,6 +31,7 @@ | |||
| "react-hook-form": "^7.43.9", | |||
| "react-router-dom": "^6.11.0", | |||
| "react-scripts": "5.0.1", | |||
| "sprintf-js": "^1.1.2", | |||
| "typescript": "^4.4.2", | |||
| "web-vitals": "^2.1.0", | |||
| "yup": "^1.1.1" | |||
| @@ -1,15 +0,0 @@ | |||
| import { ApiId, HttpMethod, request } from "."; | |||
| import { getUrl } from "./url"; | |||
| export type CheckTokenRequest = { | |||
| access_token: string; | |||
| }; | |||
| export const checkToken = async (data: CheckTokenRequest) => { | |||
| const res = await request({ | |||
| url: getUrl(ApiId.APP_TOKEN_CHECK), | |||
| method: HttpMethod.GET, | |||
| data: new URLSearchParams(data), | |||
| }); | |||
| return res; | |||
| }; | |||
| @@ -0,0 +1,21 @@ | |||
| import { APICommonResponse, ApiId, HttpMethod, request } from ".."; | |||
| import { ReceiptIssuingOrder } from "../receipt-issuing-order"; | |||
| import { getUrl } from "../url"; | |||
| export type CheckTokenRequest = { | |||
| access_token: string; | |||
| }; | |||
| export type CheckTokenResponse = { | |||
| data: { | |||
| receipt_issuing_order: ReceiptIssuingOrder; | |||
| }; | |||
| } & APICommonResponse; | |||
| export const checkToken = async (data: CheckTokenRequest) => { | |||
| const res = await request<CheckTokenResponse>({ | |||
| url: getUrl(ApiId.APP_TOKEN_CHECK), | |||
| method: HttpMethod.GET, | |||
| data: new URLSearchParams(data), | |||
| }); | |||
| return res; | |||
| }; | |||
| @@ -0,0 +1,40 @@ | |||
| import { APICommonResponse, ApiId, HttpMethod, makeParam, request } from ".."; | |||
| import { ReceiptIssuingOrder } from "../receipt-issuing-order"; | |||
| import { getUrl } from "../url"; | |||
| // 領収証確定 | |||
| export type ConfirmRequest = { | |||
| id: string; | |||
| receipt_name: string; | |||
| timestamp: string; | |||
| }; | |||
| export const confirm = async (data: ConfirmRequest) => { | |||
| const res = await request({ | |||
| url: getUrl(ApiId.RECEIPT_ISSUING_ORDER_CONFIRM), | |||
| method: HttpMethod.POST, | |||
| data: makeParam(data), | |||
| }); | |||
| return res; | |||
| }; | |||
| // 領収証郵送依頼 ------------------------------- | |||
| export type MailOrderRequest = { | |||
| id: string; | |||
| mail_pref_code: string; | |||
| mail_zip_code: string; | |||
| mail_address1: string; | |||
| mail_address2: string; | |||
| mail_address3?: string; | |||
| mail_name: string; | |||
| timestamp: string; | |||
| }; | |||
| export const mailRequest = async (data: MailOrderRequest) => { | |||
| const res = await request({ | |||
| url: getUrl(ApiId.RECEIPT_ISSUING_ORDER_MAIL_ORDER), | |||
| method: HttpMethod.POST, | |||
| data: makeParam(data), | |||
| }); | |||
| return res; | |||
| }; | |||
| @@ -6,7 +6,6 @@ import { | |||
| request, | |||
| } from "api"; | |||
| import { getUrl } from "api/url"; | |||
| import { ReceiptIssuingOrderStatus } from "codes/receipt-issuing-order"; | |||
| export type HTCustomer = { | |||
| customer_code: string; | |||
| @@ -28,8 +27,7 @@ export type CreateReceiptIssuingOrderRequest = { | |||
| customer_code: string; | |||
| parking_management_code: string; | |||
| adjust_seq_no?: string | number; | |||
| receipt_name: string; | |||
| receipt_use_datetime: Date | null; | |||
| receipt_use_date: Date | null; | |||
| receipt_amount: string | number; | |||
| memo?: string; | |||
| sms_phone_number: string; | |||
| @@ -59,7 +57,7 @@ export type ReceiptIssuingOrder = { | |||
| parking_management_code: string; | |||
| customer_name: string; | |||
| parking_name: string; | |||
| status: ReceiptIssuingOrderStatus; | |||
| status_name: string; | |||
| handler_id: string; | |||
| handler_name: string; | |||
| }; | |||
| @@ -7,15 +7,20 @@ import axios from "utils/axios"; | |||
| let id = 0; | |||
| export const ApiId = { | |||
| // 共通--------------------------------------- | |||
| CSRF_TOKEN: id++, | |||
| ME: id++, | |||
| LOGIN: id++, | |||
| LOGOUT: id++, | |||
| // APP向け------------------------------------ | |||
| APP_TOKEN_CHECK: id++, | |||
| RECEIPT_ISSUING_ORDER_CONFIRM: id++, | |||
| DOWNLOAD_RECEIPT: id++, | |||
| RECEIPT_ISSUING_ORDER_MAIL_ORDER: id++, | |||
| // DASHBOARD向け------------------------------- | |||
| RECEIPT_ISSUING_ORDERS: id++, | |||
| RECEIPT_ISSUING_ORDER: id++, | |||
| RECEIPT_ISSUING_ORDER_CREATE: id++, | |||
| @@ -245,6 +250,9 @@ export async function apiRequest< | |||
| } | |||
| return res; | |||
| } catch (e) { | |||
| if (setSending) { | |||
| setSending(false); | |||
| } | |||
| console.error(e); | |||
| if (onFailed) { | |||
| onFailed(null); | |||
| @@ -1,15 +1,28 @@ | |||
| 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; | |||
| access_token_expires_at: string; | |||
| receipt_use_date?: string; | |||
| receipt_shop_name?: string; | |||
| receipt_issuer?: string; | |||
| receipt_purpose?: string; | |||
| receipt_name?: string; | |||
| receipt_amount?: string; | |||
| confirmed: boolean; | |||
| status_order_mail_datetime?: string; | |||
| status_mail_post_date?: string; | |||
| updated_at: string; | |||
| }; | |||
| // 領収証発行一覧取得 ----------------------- | |||
| export type ReceiptIssuingOrdersRequest = { | |||
| address?: string; | |||
| status?: ReceiptIssuingOrderStatus; | |||
| }; | |||
| export type ReceiptIssuingOrdersResponse = { | |||
| @@ -9,7 +9,9 @@ const urls = { | |||
| [A.LOGOUT]: "logout", | |||
| [A.APP_TOKEN_CHECK]: "app-token-check", | |||
| [A.RECEIPT_ISSUING_ORDER_CONFIRM]: "receipt-issuing-order/confirm", | |||
| [A.DOWNLOAD_RECEIPT]: "receipt/download", | |||
| [A.RECEIPT_ISSUING_ORDER_MAIL_ORDER]: "receipt-issuing-order/mail-order", | |||
| [A.RECEIPT_ISSUING_ORDERS]: "receipt-issuing-orders", | |||
| @@ -6,6 +6,7 @@ export const PageID = { | |||
| LOGOUT: id++, | |||
| APP_RECEIPT_ISSUING_ORDER_INDEX: id++, | |||
| APP_RECEIPT_ISSUING_ORDER_MAIL_ORDER: id++, | |||
| DASHBOARD_OVERVIEW: id++, | |||
| @@ -0,0 +1,59 @@ | |||
| import { SelectOptionProps } from "components/hook-form/RHFSelect"; | |||
| export const prefCodeOptions: SelectOptionProps[] = [ | |||
| { value: "01", label: "北海道" }, | |||
| { value: "02", label: "青森県" }, | |||
| { value: "03", label: "岩手県" }, | |||
| { value: "04", label: "宮城県" }, | |||
| { value: "05", label: "秋田県" }, | |||
| { value: "06", label: "山形県" }, | |||
| { value: "07", label: "福島県" }, | |||
| { value: "08", label: "茨城県" }, | |||
| { value: "09", label: "栃木県" }, | |||
| { value: "10", label: "群馬県" }, | |||
| { value: "11", label: "埼玉県" }, | |||
| { value: "12", label: "千葉県" }, | |||
| { value: "13", label: "東京都" }, | |||
| { value: "14", label: "神奈川県" }, | |||
| { value: "15", label: "新潟県" }, | |||
| { value: "16", label: "富山県" }, | |||
| { value: "17", label: "石川県" }, | |||
| { value: "18", label: "福井県" }, | |||
| { value: "19", label: "山梨県" }, | |||
| { value: "20", label: "長野県" }, | |||
| { value: "21", label: "岐阜県" }, | |||
| { value: "22", label: "静岡県" }, | |||
| { value: "23", label: "愛知県" }, | |||
| { value: "24", label: "三重県" }, | |||
| { value: "25", label: "滋賀県" }, | |||
| { value: "26", label: "京都府" }, | |||
| { value: "27", label: "大阪府" }, | |||
| { value: "28", label: "兵庫県" }, | |||
| { value: "29", label: "奈良県" }, | |||
| { value: "30", label: "和歌山県" }, | |||
| { value: "31", label: "鳥取県" }, | |||
| { value: "32", label: "島根県" }, | |||
| { value: "33", label: "岡山県" }, | |||
| { value: "34", label: "広島県" }, | |||
| { value: "35", label: "山口県" }, | |||
| { value: "36", label: "徳島県" }, | |||
| { value: "37", label: "香川県" }, | |||
| { value: "38", label: "愛媛県" }, | |||
| { value: "39", label: "高知県" }, | |||
| { value: "40", label: "福岡県" }, | |||
| { value: "41", label: "佐賀県" }, | |||
| { value: "42", label: "長崎県" }, | |||
| { value: "43", label: "熊本県" }, | |||
| { value: "44", label: "大分県" }, | |||
| { value: "45", label: "宮崎県" }, | |||
| { value: "46", label: "鹿児島県" }, | |||
| { value: "47", label: "沖縄県" }, | |||
| ]; | |||
| export function getPrefName(code: string): string { | |||
| const pref = prefCodeOptions.find((ele) => { | |||
| return ele.value === code; | |||
| }); | |||
| return pref?.label ?? "-"; | |||
| } | |||
| @@ -1,40 +0,0 @@ | |||
| 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]; | |||
| const ReceiptIssuingOrderStatusName = { | |||
| [ReceiptIssuingOrderStatus.NONE]: "", | |||
| [ReceiptIssuingOrderStatus.CREATED]: "受付済み", | |||
| [ReceiptIssuingOrderStatus.SMS_SENDING]: "SMS送信中", | |||
| [ReceiptIssuingOrderStatus.SMS_RECEIVED]: "SMS送信完了", | |||
| [ReceiptIssuingOrderStatus.SMS_OPENED]: "SMS開封済み", | |||
| [ReceiptIssuingOrderStatus.MAIL_REQUEST]: "郵送依頼中", | |||
| [ReceiptIssuingOrderStatus.PREPARING_FOR_MAIL]: "郵送準備中", | |||
| [ReceiptIssuingOrderStatus.MAIL_DONE]: "郵送投函済み", | |||
| [ReceiptIssuingOrderStatus.EMAIL_SENDING]: "メール送信中", | |||
| [ReceiptIssuingOrderStatus.EMAIL_DONE]: "メール送信済み", | |||
| [ReceiptIssuingOrderStatus.DOWNLOAD_DONE]: "ダウンロード済み", | |||
| } as const; | |||
| export function getStatusName(status: ReceiptIssuingOrderStatus): string { | |||
| return ReceiptIssuingOrderStatusName[status]; | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| import { prefCodeOptions } from "codes/prefcode"; | |||
| import RHFSelect, { RHFSelectProps, SelectOptionProps } from "../RHFSelect"; | |||
| export default function RHFPrefCodeSelect({ ...other }: RHFSelectProps) { | |||
| return <RHFSelect {...other} options={prefCodeOptions} />; | |||
| } | |||
| @@ -1,24 +1,45 @@ | |||
| import { HasChildren } from "@types"; | |||
| import { checkToken } from "api/app"; | |||
| import { | |||
| ReceiptIssuingOrder, | |||
| getReceiptIssuingOrders, | |||
| } from "api/receipt-issuing-order"; | |||
| import { PageID } from "codes/page"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import { createContext, useState } from "react"; | |||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||
| import { createContext, useEffect, useState } from "react"; | |||
| import { useParams } from "react-router-dom"; | |||
| import { getPath } from "routes/path"; | |||
| type App = { | |||
| token: string; | |||
| tokenResult: "cheking" | "ok" | "ng"; | |||
| receiptIssuingOrder: ReceiptIssuingOrder | null; | |||
| setToken: (token: string) => void; | |||
| fetch: VoidFunction; | |||
| navigateToHome: VoidFunction; | |||
| }; | |||
| export const AppContext = createContext<App>({ | |||
| token: "", | |||
| tokenResult: "cheking", | |||
| receiptIssuingOrder: null, | |||
| setToken: (token: string) => {}, | |||
| fetch: () => {}, | |||
| navigateToHome: () => {}, | |||
| }); | |||
| type Props = HasChildren; | |||
| export function AppContextProvider({ children }: Props) { | |||
| const { navigateWhenChanged } = useNavigateCustom(); | |||
| const { token: paramToken } = useParams(); | |||
| const [token, _setToken] = useState(""); | |||
| const [receiptIssuingOrder, setReceiptIssuingOrder] = | |||
| useState<ReceiptIssuingOrder | null>(null); | |||
| const [tokenResult, setTokenResult] = useState<"cheking" | "ok" | "ng">( | |||
| "cheking" | |||
| ); | |||
| @@ -28,9 +49,11 @@ export function AppContextProvider({ children }: Props) { | |||
| onSuccess: (res, sendData) => { | |||
| setTokenResult("ok"); | |||
| _setToken(sendData.access_token); | |||
| setReceiptIssuingOrder(res.data.receipt_issuing_order); | |||
| }, | |||
| onFailed: () => { | |||
| setTokenResult("ng"); | |||
| navigateWhenChanged(getPath(PageID.PAGE_404)); | |||
| }, | |||
| }); | |||
| @@ -40,8 +63,39 @@ export function AppContextProvider({ children }: Props) { | |||
| }); | |||
| }; | |||
| const fetch = () => { | |||
| if (token) { | |||
| setToken(token); | |||
| } | |||
| }; | |||
| const navigateToHome = () => { | |||
| const path = getPath(PageID.APP_RECEIPT_ISSUING_ORDER_INDEX, { | |||
| query: { token: token }, | |||
| }); | |||
| navigateWhenChanged(path); | |||
| }; | |||
| useEffect(() => { | |||
| if (paramToken && !token) { | |||
| setToken(paramToken); | |||
| } else if (!token && !paramToken) { | |||
| setTokenResult("ng"); | |||
| navigateWhenChanged(getPath(PageID.PAGE_404)); | |||
| } | |||
| }, [paramToken]); | |||
| return ( | |||
| <AppContext.Provider value={{ token, tokenResult, setToken }}> | |||
| <AppContext.Provider | |||
| value={{ | |||
| token, | |||
| tokenResult, | |||
| receiptIssuingOrder, | |||
| setToken, | |||
| fetch, | |||
| navigateToHome, | |||
| }} | |||
| > | |||
| {children} | |||
| </AppContext.Provider> | |||
| ); | |||
| @@ -0,0 +1,86 @@ | |||
| import { | |||
| Button, | |||
| Dialog, | |||
| DialogActions, | |||
| DialogContent, | |||
| DialogContentText, | |||
| DialogTitle, | |||
| } from "@mui/material"; | |||
| import { ReactNode, useState } from "react"; | |||
| type Props = { | |||
| message?: string; | |||
| onClose?: VoidFunction; | |||
| onAgree?: VoidFunction; | |||
| onDisagree?: VoidFunction; | |||
| }; | |||
| export type UseDialogReturn = { | |||
| show: boolean; | |||
| open: VoidFunction; | |||
| close: VoidFunction; | |||
| setShow: (show: boolean) => void; | |||
| element: ReactNode; | |||
| }; | |||
| export function useDialog({ | |||
| message, | |||
| onClose, | |||
| onAgree, | |||
| onDisagree, | |||
| }: Props = {}): UseDialogReturn { | |||
| const [show, setShow] = useState(false); | |||
| const open = () => { | |||
| setShow(true); | |||
| }; | |||
| const close = () => { | |||
| if (onClose) { | |||
| onClose(); | |||
| } | |||
| setShow(false); | |||
| }; | |||
| const agree = () => { | |||
| if (onAgree) { | |||
| onAgree(); | |||
| } | |||
| setShow(false); | |||
| }; | |||
| const disagree = () => { | |||
| if (onDisagree) { | |||
| onDisagree(); | |||
| } | |||
| setShow(false); | |||
| }; | |||
| const element = ( | |||
| <Dialog open={show} onClose={close}> | |||
| <DialogTitle>確認</DialogTitle> | |||
| {message && ( | |||
| <DialogContent> | |||
| <DialogContentText>{message}</DialogContentText> | |||
| </DialogContent> | |||
| )} | |||
| <DialogActions> | |||
| <Button onClick={disagree}>CANCEL</Button> | |||
| <Button onClick={agree}>OK</Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| ); | |||
| return { | |||
| // param | |||
| show, | |||
| // Element | |||
| element, | |||
| // function | |||
| open, | |||
| close, | |||
| setShow, | |||
| }; | |||
| } | |||
| @@ -0,0 +1,198 @@ | |||
| import { | |||
| Box, | |||
| Button, | |||
| Divider, | |||
| Paper, | |||
| Stack, | |||
| Step, | |||
| StepLabel, | |||
| Stepper, | |||
| Table, | |||
| TableBody, | |||
| TableCell, | |||
| TableRow, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { HasChildren } from "@types"; | |||
| import { getPrefName } from "codes/prefcode"; | |||
| import useApp from "hooks/useApp"; | |||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||
| import { useMemo, useState } from "react"; | |||
| import useInputMailStep from "./hooks/useInputMailStep"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import { mailRequest } from "api/app/receipt-issuing-order"; | |||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||
| type TableRowCustomProps = { | |||
| title: string; | |||
| value: string; | |||
| }; | |||
| const TableRowCustom = ({ title, value }: TableRowCustomProps) => { | |||
| return ( | |||
| <TableRow> | |||
| <TableCell sx={{ borderRight: "1px solid rgba(224, 224, 224, 1)" }}> | |||
| {title} | |||
| </TableCell> | |||
| <TableCell>{value}</TableCell> | |||
| </TableRow> | |||
| ); | |||
| }; | |||
| type SectionProps = { | |||
| title: string; | |||
| subtitle?: string; | |||
| } & HasChildren; | |||
| const Section = ({ title, subtitle, children }: SectionProps) => { | |||
| return ( | |||
| <Paper | |||
| sx={{ py: 2, border: "1px solid rgba(224, 224, 224, 1)" }} | |||
| elevation={0} | |||
| > | |||
| <Box> | |||
| <Typography variant="subtitle1">{title}</Typography> | |||
| {subtitle && <Typography variant="body2">{subtitle}</Typography>} | |||
| </Box> | |||
| <Box sx={{ mt: 2 }}>{children}</Box> | |||
| </Paper> | |||
| ); | |||
| }; | |||
| export default function MailOrder() { | |||
| const { | |||
| tokenResult, | |||
| navigateToHome, | |||
| fetch, | |||
| receiptIssuingOrder: order, | |||
| } = useApp(); | |||
| const { success, error } = useSnackbarCustom(); | |||
| const { navigate } = useNavigateCustom(); | |||
| const [mode, setMode] = useState<"input" | "confirm" | "done">("input"); | |||
| const input = useInputMailStep({ | |||
| onNext: () => { | |||
| setMode("confirm"); | |||
| }, | |||
| onPrev: () => { | |||
| navigate(-1); | |||
| }, | |||
| }); | |||
| const requestMailAPI = useAPICall({ | |||
| apiMethod: mailRequest, | |||
| onSuccess: () => { | |||
| fetch(); | |||
| setMode("done"); | |||
| }, | |||
| onFailed: () => { | |||
| error("登録失敗"); | |||
| }, | |||
| }); | |||
| const currentStep = useMemo(() => { | |||
| if (mode === "input") return 0; | |||
| if (mode === "confirm") return 1; | |||
| if (mode === "done") return 2; | |||
| }, [mode]); | |||
| const handleConfirm = () => { | |||
| if (!order) return; | |||
| requestMailAPI.callAPI({ | |||
| id: order.id, | |||
| timestamp: order.updated_at, | |||
| ...input.values(), | |||
| }); | |||
| }; | |||
| if (tokenResult !== "ok") { | |||
| return null; | |||
| } | |||
| return ( | |||
| <> | |||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | |||
| <Stack spacing={3}> | |||
| <Box> | |||
| <Typography variant="h5">領収証郵送依頼</Typography> | |||
| </Box> | |||
| <Section title=""> | |||
| <Stepper activeStep={currentStep}> | |||
| <Step> | |||
| <StepLabel>郵送先情報入力</StepLabel> | |||
| </Step> | |||
| <Step> | |||
| <StepLabel>確認</StepLabel> | |||
| </Step> | |||
| <Step> | |||
| <StepLabel>完了</StepLabel> | |||
| </Step> | |||
| </Stepper> | |||
| {mode === "input" && input.element} | |||
| {mode === "confirm" && ( | |||
| <Stack spacing={2} sx={{ p: 1, py: 3, m: 1 }}> | |||
| <Typography variant="h5">郵送先情報確認</Typography> | |||
| <Table> | |||
| <TableBody> | |||
| <TableRowCustom | |||
| title="都道府県" | |||
| value={getPrefName(input.values("mail_pref_code"))} | |||
| /> | |||
| <TableRowCustom | |||
| title="郵便番号" | |||
| value={"〒" + input.values("mail_zip_code")} | |||
| /> | |||
| <TableRowCustom | |||
| title="市区町村" | |||
| value={input.values("mail_address1")} | |||
| /> | |||
| <TableRowCustom | |||
| title="番地等" | |||
| value={input.values("mail_address2")} | |||
| /> | |||
| <TableRowCustom | |||
| title="建物名・部屋番号等" | |||
| value={input.values("mail_address3")} | |||
| /> | |||
| <TableRowCustom | |||
| title="宛名" | |||
| value={input.values("mail_name")} | |||
| /> | |||
| </TableBody> | |||
| </Table> | |||
| <Box> | |||
| <Stack> | |||
| <Box>郵送先に間違いがないか確認してください。</Box> | |||
| <Box>投函まで数営業日を要しますのでご了承ください。</Box> | |||
| </Stack> | |||
| </Box> | |||
| <Stack direction="row" spacing={2}> | |||
| <Button | |||
| variant="text" | |||
| onClick={() => { | |||
| setMode("input"); | |||
| }} | |||
| > | |||
| 戻る | |||
| </Button> | |||
| <Button variant="contained" onClick={handleConfirm}> | |||
| 確定 | |||
| </Button> | |||
| </Stack> | |||
| </Stack> | |||
| )} | |||
| {mode === "done" && ( | |||
| <Stack spacing={2} sx={{ p: 1, py: 3, m: 1 }}> | |||
| <Box>郵送依頼を受付いたしました。</Box> | |||
| <Box>到着までしばらくお待ちください。</Box> | |||
| <Button onClick={navigateToHome}>戻る</Button> | |||
| </Stack> | |||
| )} | |||
| </Section> | |||
| </Stack> | |||
| </Box> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -1,25 +1,85 @@ | |||
| import { Box, Button, Paper, Stack, Typography } from "@mui/material"; | |||
| import { | |||
| Box, | |||
| Button, | |||
| Paper, | |||
| Stack, | |||
| Table, | |||
| TableBody, | |||
| TableCell, | |||
| TableHead, | |||
| TableRow, | |||
| TextField, | |||
| Typography, | |||
| styled, | |||
| } from "@mui/material"; | |||
| import { HasChildren } from "@types"; | |||
| import { ApiId } from "api"; | |||
| import { confirm } from "api/app/receipt-issuing-order"; | |||
| import { getFullUrl } from "api/url"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useApp from "hooks/useApp"; | |||
| import { useEffect, useMemo } from "react"; | |||
| import { useDialog } from "hooks/useDialog"; | |||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||
| import React, { ReactNode, useEffect, useMemo, useRef, useState } from "react"; | |||
| import { useParams } from "react-router-dom"; | |||
| import { sprintf } from "sprintf-js"; | |||
| import { formatDateStr, formatDateTimeStr } from "utils/datetime"; | |||
| import useReceiptIssuingOrderConfirm from "./hooks/useReceiptIssuingOrderConfirm"; | |||
| import useReceiptIssuingOrderSelectHowToGet from "./hooks/useReceiptIssuingOrderSelectHowToGet"; | |||
| export default function ReceiptIssuingOrder() { | |||
| const { token: paramToken } = useParams(); | |||
| type TableRowCustomProps = { | |||
| title: string; | |||
| value: string; | |||
| }; | |||
| const TableRowCustom = ({ title, value }: TableRowCustomProps) => { | |||
| return ( | |||
| <TableRow> | |||
| <TableCell sx={{ borderRight: "1px solid rgba(224, 224, 224, 1)" }}> | |||
| {title} | |||
| </TableCell> | |||
| <TableCell>{value}</TableCell> | |||
| </TableRow> | |||
| ); | |||
| }; | |||
| type SectionProps = { | |||
| title: string; | |||
| subtitle?: string; | |||
| } & HasChildren; | |||
| const Section = ({ title, subtitle, children }: SectionProps) => { | |||
| return ( | |||
| <Paper | |||
| sx={{ py: 2, border: "1px solid rgba(224, 224, 224, 1)" }} | |||
| elevation={0} | |||
| > | |||
| <Box> | |||
| <Typography variant="subtitle1">{title}</Typography> | |||
| {subtitle && <Typography variant="body2">{subtitle}</Typography>} | |||
| </Box> | |||
| <Box sx={{ mt: 2 }}>{children}</Box> | |||
| </Paper> | |||
| ); | |||
| }; | |||
| const { token, setToken, tokenResult } = useApp(); | |||
| export default function ReceiptIssuingOrder() { | |||
| const { tokenResult, receiptIssuingOrder: order } = useApp(); | |||
| const downloadUrl = useMemo(() => { | |||
| return getFullUrl(ApiId.DOWNLOAD_RECEIPT) + "?access_token=" + token; | |||
| }, [token]); | |||
| const confirm = useReceiptIssuingOrderConfirm(); | |||
| const selectHowToGet = useReceiptIssuingOrderSelectHowToGet(); | |||
| useEffect(() => { | |||
| if (paramToken) { | |||
| setToken(paramToken); | |||
| console.log("render"); | |||
| const tokenExpiresAtStr = useMemo(() => { | |||
| if (order) { | |||
| return formatDateTimeStr(order.access_token_expires_at); | |||
| } | |||
| }, []); | |||
| return "-"; | |||
| }, [order]); | |||
| const mailOrderStatusStr = useMemo(() => { | |||
| if (!order) return "-"; | |||
| if (order.status_mail_post_date) return "投函済み"; | |||
| if (order.status_order_mail_datetime) return "郵送依頼確認中"; | |||
| return "-"; | |||
| }, [order]); | |||
| if (tokenResult === "cheking") { | |||
| return null; | |||
| @@ -30,19 +90,69 @@ export default function ReceiptIssuingOrder() { | |||
| } | |||
| return ( | |||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | |||
| <Stack spacing={3}> | |||
| <Typography variant="h5">領収証発行依頼</Typography> | |||
| <Paper> | |||
| <Typography variant="h5">状況</Typography> | |||
| </Paper> | |||
| <Paper> | |||
| <Button href={downloadUrl} variant="contained"> | |||
| PDFダウンロード | |||
| </Button> | |||
| </Paper> | |||
| </Stack> | |||
| </Box> | |||
| <> | |||
| <Box sx={{ p: 3, pt: 5, mx: "auto", maxWidth: 500 }} textAlign="center"> | |||
| <Stack spacing={3}> | |||
| <Box> | |||
| <Typography variant="h5">領収証発行依頼</Typography> | |||
| <Typography variant="body2"> | |||
| ページ有効期限:{tokenExpiresAtStr}まで | |||
| </Typography> | |||
| </Box> | |||
| <Section title="申込内容"> | |||
| <Table> | |||
| <TableBody> | |||
| <TableRowCustom | |||
| title="店舗名" | |||
| value={order?.receipt_shop_name ?? "-"} | |||
| /> | |||
| <TableRowCustom | |||
| title="金額" | |||
| value={ | |||
| order?.receipt_amount | |||
| ? sprintf("%d円", order.receipt_amount) | |||
| : "-" | |||
| } | |||
| /> | |||
| <TableRowCustom | |||
| title="利用日" | |||
| value={ | |||
| order?.receipt_use_date | |||
| ? formatDateStr(order.receipt_use_date) | |||
| : "-" | |||
| } | |||
| /> | |||
| <TableRowCustom | |||
| title="宛名" | |||
| value={order?.receipt_name ?? "-"} | |||
| /> | |||
| </TableBody> | |||
| </Table> | |||
| </Section> | |||
| {order?.status_order_mail_datetime && ( | |||
| <Section title="郵送依頼状況"> | |||
| <Table sx={{ borderBottom: "none" }}> | |||
| <TableBody> | |||
| <TableRow /> | |||
| <TableRowCustom title="状況" value={mailOrderStatusStr} /> | |||
| </TableBody> | |||
| </Table> | |||
| </Section> | |||
| )} | |||
| {confirm.shouldShow && ( | |||
| <Section title="宛名入力">{confirm.element}</Section> | |||
| )} | |||
| {selectHowToGet.shouldShow && ( | |||
| <Section title="領収証取得方法選択"> | |||
| {selectHowToGet.element} | |||
| </Section> | |||
| )} | |||
| </Stack> | |||
| </Box> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,119 @@ | |||
| import { yupResolver } from "@hookform/resolvers/yup"; | |||
| import { Box, Button, Divider, Stack, Typography } from "@mui/material"; | |||
| import { HasChildren } from "@types"; | |||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||
| import RHFDatePicker from "components/hook-form/RHFDatePicker"; | |||
| import RHFPrefCodeSelect from "components/hook-form/ex/RHFPrefCodeSelect"; | |||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||
| 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 = { | |||
| mail_pref_code: string; | |||
| mail_zip_code: string; | |||
| mail_address1: string; | |||
| mail_address2: string; | |||
| mail_address3: string; | |||
| mail_name: string; | |||
| }; | |||
| type Props = { | |||
| onNext?: VoidFunction; | |||
| onPrev?: VoidFunction; | |||
| }; | |||
| export default function useInputMailStep({ onNext, onPrev }: Props) { | |||
| const form = useForm<FormProps>({ | |||
| defaultValues: { | |||
| mail_pref_code: "", | |||
| mail_zip_code: "", | |||
| mail_address1: "", | |||
| mail_address2: "", | |||
| mail_address3: "", | |||
| mail_name: "", | |||
| }, | |||
| resolver: yupResolver( | |||
| Yup.object().shape({ | |||
| mail_pref_code: Yup.string() | |||
| .required("必須項目です") | |||
| .typeError("必須項目です"), | |||
| mail_zip_code: Yup.string() | |||
| .required("必須項目です") | |||
| .matches(/^[0-9]{7}$/, "半角数値7桁を入力してください"), | |||
| mail_address1: Yup.string() | |||
| .required("必須項目です") | |||
| .max(100, "100文字以内で入力してください"), | |||
| mail_address2: Yup.string() | |||
| .required("必須項目です") | |||
| .max(100, "100文字以内で入力してください"), | |||
| mail_address3: Yup.string().max(100, "100文字以内で入力してください"), | |||
| mail_name: Yup.string() | |||
| .required("必須項目です") | |||
| .max(100, "100文字以内で入力してください"), | |||
| }) | |||
| ), | |||
| }); | |||
| const handleSubmit = () => { | |||
| if (onNext) { | |||
| onNext(); | |||
| } | |||
| }; | |||
| const handlePrev = () => { | |||
| if (onPrev) { | |||
| onPrev(); | |||
| } | |||
| }; | |||
| const element = ( | |||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||
| <Stack spacing={2} sx={{ p: 1, m: 1 }} textAlign="left"> | |||
| <AreaBox title="都道府県"> | |||
| <RHFPrefCodeSelect name="mail_pref_code" size="small" /> | |||
| </AreaBox> | |||
| <AreaBox title="郵便番号"> | |||
| <Typography variant="body2">ハイフン無の7桁</Typography> | |||
| <RHFTextField | |||
| name="mail_zip_code" | |||
| InputProps={{ startAdornment: <div>〒</div> }} | |||
| /> | |||
| </AreaBox> | |||
| <AreaBox title="市区町村"> | |||
| <RHFTextField name="mail_address1" /> | |||
| </AreaBox> | |||
| <AreaBox title="番地等"> | |||
| <RHFTextField name="mail_address2" /> | |||
| </AreaBox> | |||
| <AreaBox title="建物名・部屋番号等"> | |||
| <RHFTextField name="mail_address3" /> | |||
| </AreaBox> | |||
| <Divider /> | |||
| <AreaBox title="宛先名"> | |||
| <RHFTextField name="mail_name" /> | |||
| </AreaBox> | |||
| <Stack direction="row" spacing={2}> | |||
| <Button variant="text" onClick={handlePrev}> | |||
| 戻る | |||
| </Button> | |||
| <Button variant="contained" type="submit"> | |||
| 次へ | |||
| </Button> | |||
| </Stack> | |||
| </Stack> | |||
| </FormProvider> | |||
| ); | |||
| return { element, values: form.getValues, setValue: form.setValue }; | |||
| } | |||
| @@ -0,0 +1,103 @@ | |||
| import { yupResolver } from "@hookform/resolvers/yup"; | |||
| import { Box, Button, Stack, Typography } from "@mui/material"; | |||
| import { confirm } from "api/app/receipt-issuing-order"; | |||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useApp from "hooks/useApp"; | |||
| import useBackDrop from "hooks/useBackDrop"; | |||
| import { useDialog } from "hooks/useDialog"; | |||
| import useSnackbarCustom from "hooks/useSnackbarCustom"; | |||
| import { useEffect, useMemo } from "react"; | |||
| import { useForm } from "react-hook-form"; | |||
| import { sprintf } from "sprintf-js"; | |||
| import * as Yup from "yup"; | |||
| type FormProps = { | |||
| receipt_name: string; | |||
| }; | |||
| const schema = Yup.object().shape({ | |||
| receipt_name: Yup.string().required("必須項目です"), | |||
| }); | |||
| export default function useReceiptIssuingOrderConfirm() { | |||
| const { receiptIssuingOrder: order, fetch } = useApp(); | |||
| const { success, error } = useSnackbarCustom(); | |||
| const { setShowBackDrop } = useBackDrop(); | |||
| const shouldShow = useMemo(() => { | |||
| return !order?.confirmed; | |||
| }, [order]); | |||
| const form = useForm<FormProps>({ | |||
| defaultValues: { | |||
| receipt_name: "", | |||
| }, | |||
| resolver: yupResolver(schema), | |||
| }); | |||
| const receiptName = form.watch("receipt_name"); | |||
| const confirmMessage = useMemo(() => { | |||
| return sprintf("宛名[%s]でよろしいでしょうか。", receiptName); | |||
| }, [receiptName]); | |||
| const confirmDialog = useDialog({ | |||
| message: confirmMessage, | |||
| onAgree: () => { | |||
| if (!order) return; | |||
| api.callAPI({ | |||
| id: order.id, | |||
| receipt_name: receiptName, | |||
| timestamp: order.updated_at, | |||
| }); | |||
| }, | |||
| }); | |||
| const api = useAPICall({ | |||
| apiMethod: confirm, | |||
| onSuccess: () => { | |||
| success("登録しました"); | |||
| fetch(); | |||
| }, | |||
| onFailed: () => { | |||
| error("失敗しました"); | |||
| }, | |||
| }); | |||
| const handleSubmit = () => { | |||
| confirmDialog.setShow(true); | |||
| }; | |||
| useEffect(() => { | |||
| setShowBackDrop(api.sending); | |||
| }, [api.sending]); | |||
| const element = ( | |||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||
| <Stack spacing={1}> | |||
| <Box> | |||
| <Typography variant="body2"> | |||
| 領収証の宛名を入力してください | |||
| </Typography> | |||
| <Typography variant="body2"> | |||
| 一度登録すると変更できないので、ご注意ください | |||
| </Typography> | |||
| </Box> | |||
| <Box sx={{ px: 2 }}> | |||
| <RHFTextField label="宛名" name="receipt_name" /> | |||
| </Box> | |||
| <Box> | |||
| <Button type="submit" variant="contained"> | |||
| 登録 | |||
| </Button> | |||
| </Box> | |||
| </Stack> | |||
| {confirmDialog.element} | |||
| </FormProvider> | |||
| ); | |||
| return { | |||
| element, | |||
| shouldShow, | |||
| }; | |||
| } | |||
| @@ -0,0 +1,47 @@ | |||
| import { Box, Button, Stack } from "@mui/material"; | |||
| import { ApiId } from "api"; | |||
| import { getFullUrl } from "api/url"; | |||
| import { PageID } from "codes/page"; | |||
| import useApp from "hooks/useApp"; | |||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||
| import { useMemo } from "react"; | |||
| import { getPath } from "routes/path"; | |||
| export default function useReceiptIssuingOrderSelectHowToGet() { | |||
| const { token, receiptIssuingOrder: order } = useApp(); | |||
| const { navigateWhenChanged } = useNavigateCustom(); | |||
| const shouldShow = useMemo(() => { | |||
| return !!order?.confirmed; | |||
| }, [order]); | |||
| const downloadUrl = useMemo(() => { | |||
| return getFullUrl(ApiId.DOWNLOAD_RECEIPT) + "?access_token=" + token; | |||
| }, [token]); | |||
| const handleClickMail = () => { | |||
| navigateWhenChanged(getPath(PageID.APP_RECEIPT_ISSUING_ORDER_MAIL_ORDER)); | |||
| }; | |||
| const element = ( | |||
| <Stack spacing={1}> | |||
| <Box> | |||
| <Button href={downloadUrl} variant="contained"> | |||
| PDFダウンロード | |||
| </Button> | |||
| </Box> | |||
| {!order?.status_order_mail_datetime && ( | |||
| <Box> | |||
| <Button variant="text" onClick={handleClickMail}> | |||
| 郵送希望 | |||
| </Button> | |||
| </Box> | |||
| )} | |||
| </Stack> | |||
| ); | |||
| return { | |||
| element, | |||
| shouldShow, | |||
| }; | |||
| } | |||
| @@ -47,7 +47,6 @@ export default function ReceiptIssuingOrderCreate() { | |||
| adjustSeqNo: selectParkingStep.values("adjustSeqNo"), | |||
| amount: inputReceiptStep.values("amount"), | |||
| date: inputReceiptStep.values("date"), | |||
| name: inputReceiptStep.values("name"), | |||
| address: inputSMSSendAddress.values("address"), | |||
| memo: inputReceiptStep.values("memo"), | |||
| }; | |||
| @@ -112,8 +111,7 @@ export default function ReceiptIssuingOrderCreate() { | |||
| 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_use_date: formData.date, | |||
| receipt_amount: formData.amount, | |||
| sms_phone_number: formData.address, | |||
| }); | |||
| @@ -1,4 +1,3 @@ | |||
| import { yupResolver } from "@hookform/resolvers/yup"; | |||
| import { | |||
| Box, | |||
| Button, | |||
| @@ -7,26 +6,12 @@ import { | |||
| 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"; | |||
| import { formatDateStr } from "utils/datetime"; | |||
| type AreaBoxProps = { | |||
| title: string; | |||
| @@ -51,7 +36,6 @@ export type ConfirmDataProps = { | |||
| adjustSeqNo: string; | |||
| amount: string; | |||
| date: Date | null; | |||
| name: string; | |||
| address: string; | |||
| memo: string; | |||
| }; | |||
| @@ -64,7 +48,6 @@ export default function useConfirm({ onNext, onPrev }: Props) { | |||
| adjustSeqNo: "", | |||
| amount: "", | |||
| date: null, | |||
| name: "", | |||
| address: "", | |||
| memo: "", | |||
| }); | |||
| @@ -102,10 +85,6 @@ export default function useConfirm({ onNext, onPrev }: Props) { | |||
| <TableCell>利用日</TableCell> | |||
| <TableCell>{formatDateStr(data.date)}</TableCell> | |||
| </TableRow> | |||
| <TableRow> | |||
| <TableCell>宛名</TableCell> | |||
| <TableCell>{data.name}</TableCell> | |||
| </TableRow> | |||
| <TableRow> | |||
| <TableCell>金額</TableCell> | |||
| <TableCell>{data.amount}円</TableCell> | |||
| @@ -31,7 +31,6 @@ function AreaBox({ title, children }: AreaBoxProps) { | |||
| type FormProps = { | |||
| amount: string; | |||
| date: Date | null; | |||
| name: string; | |||
| memo: string; | |||
| }; | |||
| @@ -46,7 +45,6 @@ export default function useInputReceiptStep({ onNext, onPrev }: Props) { | |||
| defaultValues: { | |||
| amount: "", | |||
| date: null, | |||
| name: "", | |||
| memo: "", | |||
| }, | |||
| resolver: yupResolver( | |||
| @@ -55,7 +53,6 @@ export default function useInputReceiptStep({ onNext, onPrev }: Props) { | |||
| .required("必須項目です") | |||
| .typeError("正しく入力してください"), | |||
| amount: Yup.number().required("必須項目です"), | |||
| name: Yup.string().required("必須項目です"), | |||
| memo: Yup.string().nullable(), | |||
| }) | |||
| ), | |||
| @@ -89,9 +86,6 @@ export default function useInputReceiptStep({ onNext, onPrev }: Props) { | |||
| sx={{ maxWidth: 150 }} | |||
| /> | |||
| </AreaBox> | |||
| <AreaBox title="宛名"> | |||
| <RHFTextField name="name" size="small" /> | |||
| </AreaBox> | |||
| <AreaBox title="メモ"> | |||
| <RHFTextField name="memo" size="small" multiline rows={3} /> | |||
| </AreaBox> | |||
| @@ -14,7 +14,6 @@ import { | |||
| getReceiptIssuingOrders, | |||
| } from "api/custom/hello-techno/receipt-issuing-order"; | |||
| import { PageID, TabID } from "codes/page"; | |||
| import { getStatusName } from "codes/receipt-issuing-order"; | |||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||
| import { TableHeadCustom } from "components/table"; | |||
| import { SearchConditionContextProvider } from "contexts/SearchConditionContext"; | |||
| @@ -235,7 +234,7 @@ function Row({ data }: RowProps) { | |||
| <TableCell>{data.order_datetime}</TableCell> | |||
| <TableCell>{data.customer_name}</TableCell> | |||
| <TableCell>{data.parking_name}</TableCell> | |||
| <TableCell>{getStatusName(data.status)}</TableCell> | |||
| <TableCell>{data.status_name}</TableCell> | |||
| <TableCell>{data.handler_name}</TableCell> | |||
| </TableRow> | |||
| ); | |||
| @@ -42,6 +42,10 @@ const AppRoutes = (): RouteObject => ({ | |||
| path: getRoute(PageID.APP_RECEIPT_ISSUING_ORDER_INDEX), | |||
| element: <ReceiptIssuingOrder />, | |||
| }, | |||
| { | |||
| path: getRoute(PageID.APP_RECEIPT_ISSUING_ORDER_MAIL_ORDER), | |||
| element: <MailOrder />, | |||
| }, | |||
| ], | |||
| }); | |||
| @@ -116,6 +120,7 @@ const Logout = Loadable(lazy(() => import("pages/auth/logout"))); | |||
| const ReceiptIssuingOrder = Loadable( | |||
| lazy(() => import("pages/app/ReceiptIssuingOrder")) | |||
| ); | |||
| const MailOrder = Loadable(lazy(() => import("pages/app/MailOrder"))); | |||
| //ダッシュボード ---------------------------- | |||
| const Dashboard = Loadable(lazy(() => import("pages/dashboard"))); | |||
| @@ -40,6 +40,8 @@ const PATHS = { | |||
| // APP | |||
| [makePathKey(PageID.APP_RECEIPT_ISSUING_ORDER_INDEX)]: | |||
| "/app/receipt-issuing-oreder/:token", | |||
| [makePathKey(PageID.APP_RECEIPT_ISSUING_ORDER_MAIL_ORDER)]: | |||
| "/app/receipt-issuing-oreder/mail", | |||
| // 契約関連 | |||
| [makePathKey(PageID.DASHBOARD_CONTRACT_LIST)]: | |||
| @@ -158,6 +158,15 @@ theme = { | |||
| }, | |||
| }, | |||
| }, | |||
| MuiTableRow: { | |||
| styleOverrides: { | |||
| root: { | |||
| "&:last-child td, &:last-child th ": { | |||
| borderBottomStyle: "none", | |||
| }, | |||
| }, | |||
| }, | |||
| }, | |||
| }, | |||
| }; | |||
| @@ -2484,6 +2484,11 @@ | |||
| dependencies: | |||
| "@types/node" "*" | |||
| "@types/sprintf-js@^1.1.2": | |||
| version "1.1.2" | |||
| resolved "https://registry.yarnpkg.com/@types/sprintf-js/-/sprintf-js-1.1.2.tgz#a4fcb84c7344f39f70dc4eec0e1e7f10a48597a3" | |||
| integrity sha512-hkgzYF+qnIl8uTO8rmUSVSfQ8BIfMXC4yJAF4n8BE758YsKBZvFC4NumnAegj7KmylP0liEZNpb9RRGFMbFejA== | |||
| "@types/stack-utils@^2.0.0": | |||
| version "2.0.1" | |||
| resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" | |||
| @@ -8634,6 +8639,11 @@ spdy@^4.0.2: | |||
| select-hose "^2.0.0" | |||
| spdy-transport "^3.0.0" | |||
| sprintf-js@^1.1.2: | |||
| version "1.1.2" | |||
| resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" | |||
| integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== | |||
| sprintf-js@~1.0.2: | |||
| version "1.0.3" | |||
| resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" | |||