| @@ -8,28 +8,21 @@ import { | |||
| import { getUrl } from "../../url"; | |||
| export type UseSummary = { | |||
| id: string; | |||
| summary_yyyymm: string; | |||
| summary_key1: string; | |||
| summary_key2?: string; | |||
| date_from: string; | |||
| date_to: string; | |||
| customer_code: string; | |||
| customer_name: string; | |||
| parking_name?: string; | |||
| receipt_order_count: string; | |||
| mail_order_count: string; | |||
| sms_send_count: string; | |||
| sms_send_cost: string; | |||
| is_fixed: boolean; | |||
| updated_at: string; | |||
| }; | |||
| // 利用実績一覧取得 ----------------------- | |||
| export type UseSummariesRequest = { | |||
| summary_yyyymm: string; | |||
| date_from: string; | |||
| date_to: string; | |||
| } & ListRequest; | |||
| export type UseSummariesResponse = { | |||
| @@ -47,6 +47,7 @@ export const ApiId = { | |||
| HT_CUSTOM_RECEIPT_ISSUING_ORDERS: id++, | |||
| HT_CUSTOM_RECEIPT_ISSUING_ORDER_CREATE: id++, | |||
| HT_CUSTOM_USE_SUMMARIES: id++, | |||
| HT_CUSTOM_USE_SUMMARY_DOWNLOAD_CSV: id++, | |||
| } as const; | |||
| export type ApiId = (typeof ApiId)[keyof typeof ApiId]; | |||
| @@ -40,12 +40,14 @@ const urls = { | |||
| [A.HT_CUSTOM_RECEIPT_ISSUING_ORDER_CREATE]: | |||
| "custom/hello-techno/receipt-issuing-order/create", | |||
| [A.HT_CUSTOM_USE_SUMMARIES]: "custom/hello-techno/use-summaries", | |||
| [A.HT_CUSTOM_USE_SUMMARY_DOWNLOAD_CSV]: "custom/hello-techno/use-summary/csv", | |||
| }; | |||
| const prefixs = { | |||
| [A.CSRF_TOKEN]: "", | |||
| [A.DOWNLOAD_RECEIPT]: "", | |||
| [A.DOWNLOAD_RECEIPT_LETTER]: "", | |||
| [A.HT_CUSTOM_USE_SUMMARY_DOWNLOAD_CSV]: "", | |||
| }; | |||
| const DEFAULT_API_URL_PREFIX = "api"; | |||
| @@ -1,5 +1,5 @@ | |||
| // @mui | |||
| import { Theme } from '@mui/material/styles'; | |||
| import { Theme } from "@mui/material/styles"; | |||
| import { | |||
| Box, | |||
| SxProps, | |||
| @@ -8,7 +8,7 @@ import { | |||
| TableCell, | |||
| TableHead, | |||
| TableSortLabel, | |||
| } from '@mui/material'; | |||
| } from "@mui/material"; | |||
| // ---------------------------------------------------------------------- | |||
| @@ -16,20 +16,29 @@ const visuallyHidden = { | |||
| border: 0, | |||
| margin: -1, | |||
| padding: 0, | |||
| width: '1px', | |||
| height: '1px', | |||
| overflow: 'hidden', | |||
| position: 'absolute', | |||
| whiteSpace: 'nowrap', | |||
| clip: 'rect(0 0 0 0)', | |||
| width: "1px", | |||
| height: "1px", | |||
| overflow: "hidden", | |||
| position: "absolute", | |||
| whiteSpace: "nowrap", | |||
| clip: "rect(0 0 0 0)", | |||
| } as const; | |||
| // ---------------------------------------------------------------------- | |||
| export type HeadLabelProps = { | |||
| id: string; | |||
| label?: string; | |||
| align?: "inherit" | "left" | "center" | "right" | "justify"; | |||
| width?: number | string; | |||
| minWidth?: number | string; | |||
| needSort?: boolean; | |||
| // { id: "customer_name", label: "運営会社名", align: "left" }, | |||
| }; | |||
| type Props = { | |||
| order?: 'asc' | 'desc'; | |||
| order?: "asc" | "desc"; | |||
| orderBy?: string; | |||
| headLabel: any[]; | |||
| headLabel: HeadLabelProps[]; | |||
| rowCount?: number; | |||
| numSelected?: number; | |||
| onSort?: (id: string) => void; | |||
| @@ -65,7 +74,7 @@ export default function TableHeadCustom({ | |||
| {headLabel.map((headCell) => ( | |||
| <TableCell | |||
| key={headCell.id} | |||
| align={headCell.align || 'left'} | |||
| align={headCell.align || "left"} | |||
| sortDirection={orderBy === headCell.id ? order : false} | |||
| sx={{ width: headCell.width, minWidth: headCell.minWidth }} | |||
| > | |||
| @@ -73,15 +82,17 @@ export default function TableHeadCustom({ | |||
| <TableSortLabel | |||
| hideSortIcon | |||
| active={orderBy === headCell.id} | |||
| direction={orderBy === headCell.id ? order : 'asc'} | |||
| direction={orderBy === headCell.id ? order : "asc"} | |||
| onClick={() => onSort(headCell.id)} | |||
| sx={{ textTransform: 'capitalize' }} | |||
| sx={{ textTransform: "capitalize" }} | |||
| > | |||
| {headCell.label} | |||
| {orderBy === headCell.id ? ( | |||
| <Box sx={{ ...visuallyHidden }}> | |||
| {order === 'desc' ? 'sorted descending' : 'sorted ascending'} | |||
| {order === "desc" | |||
| ? "sorted descending" | |||
| : "sorted ascending"} | |||
| </Box> | |||
| ) : null} | |||
| </TableSortLabel> | |||
| @@ -30,7 +30,7 @@ export type UseTableReturn<T extends object> = { | |||
| }; | |||
| export default function useTable<T extends object>(): UseTableReturn<T> { | |||
| const ROWS_PER_PAGES = [20, 50, 100]; | |||
| const ROWS_PER_PAGES = [50, 100, 500]; | |||
| const ORDER = "order"; | |||
| const SORT = "sort"; | |||
| @@ -16,6 +16,7 @@ import { Contract, getContracts } from "api/contract"; | |||
| import { PageID, TabID } from "codes/page"; | |||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||
| import { TableHeadCustom } from "components/table"; | |||
| import { HeadLabelProps } from "components/table/TableHeadCustom"; | |||
| import { SearchConditionContextProvider } from "contexts/SearchConditionContext"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useAuth from "hooks/useAuth"; | |||
| @@ -155,7 +156,7 @@ function SearchBox({ table }: CommonProps) { | |||
| } | |||
| function TableBox({ table }: CommonProps) { | |||
| const TABLE_HEAD = [ | |||
| const TABLE_HEAD: HeadLabelProps[] = [ | |||
| { id: "id", label: "ID", align: "left" }, | |||
| { id: "name", label: "名前", align: "left" }, | |||
| ]; | |||
| @@ -50,6 +50,7 @@ import RHFDatePicker from "components/hook-form/RHFDatePicker"; | |||
| import { dateParse, formatDateStr } from "utils/datetime"; | |||
| import { Clear } from "@mui/icons-material"; | |||
| import { LoginUser, getLoginUsers } from "api/login-user"; | |||
| import { HeadLabelProps } from "components/table/TableHeadCustom"; | |||
| export default function LoginUserList() { | |||
| const { setHeaderTitle, setTabs } = useDashboard( | |||
| @@ -217,7 +218,7 @@ function SearchBox({ table }: CommonProps) { | |||
| } | |||
| function TableBox({ table }: CommonProps) { | |||
| const TABLE_HEAD = [ | |||
| const TABLE_HEAD: HeadLabelProps[] = [ | |||
| { id: "name", label: "名前", align: "left" }, | |||
| { id: "email", label: "Email", align: "left" }, | |||
| ]; | |||
| @@ -30,6 +30,7 @@ import { PageID, TabID } from "codes/page"; | |||
| import { FormProvider, RHFCheckbox, RHFTextField } from "components/hook-form"; | |||
| import RHFDatePicker from "components/hook-form/RHFDatePicker"; | |||
| import { TableHeadCustom } from "components/table"; | |||
| import { HeadLabelProps } from "components/table/TableHeadCustom"; | |||
| import { SearchConditionContextProvider } from "contexts/SearchConditionContext"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useAuth from "hooks/useAuth"; | |||
| @@ -43,6 +44,19 @@ import { useForm } from "react-hook-form"; | |||
| import { getPath } from "routes/path"; | |||
| import { dateParse, formatDateStr } from "utils/datetime"; | |||
| export const SearchParam = { | |||
| CUSTOMER_NAME: "customer_name", | |||
| PARKING_NAME: "parking_name", | |||
| INCLUDE_DONE: "include_done", | |||
| STATUS: "status", | |||
| SMS_PHONE_NUMBER: "sms_phone_number", | |||
| HANDLER_NAME: "handler_name", | |||
| ORDER_DATE_FROM: "order_date_from", | |||
| ORDER_DATE_TO: "order_date_to", | |||
| RECEIPT_NO: "receipt_no", | |||
| } as const; | |||
| export type SearchParam = (typeof SearchParam)[keyof typeof SearchParam]; | |||
| export default function ReceiptIssuingOrderList() { | |||
| const { setHeaderTitle, setTabs } = useDashboard( | |||
| PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST_CUSTOM_HELLO_TECHNO, | |||
| @@ -73,15 +87,15 @@ function Page() { | |||
| } | |||
| type FormProps = { | |||
| customer_name: string; | |||
| parking_name: string; | |||
| include_done: boolean; | |||
| status: string; | |||
| sms_phone_number: string; | |||
| handler_name: string; | |||
| order_date_from: Date | null; | |||
| order_date_to: Date | null; | |||
| receipt_no: string; | |||
| [SearchParam.CUSTOMER_NAME]: string; | |||
| [SearchParam.PARKING_NAME]: string; | |||
| [SearchParam.INCLUDE_DONE]: boolean; | |||
| [SearchParam.STATUS]: string; | |||
| [SearchParam.SMS_PHONE_NUMBER]: string; | |||
| [SearchParam.HANDLER_NAME]: string; | |||
| [SearchParam.ORDER_DATE_FROM]: Date | null; | |||
| [SearchParam.ORDER_DATE_TO]: Date | null; | |||
| [SearchParam.RECEIPT_NO]: string; | |||
| }; | |||
| type CommonProps = { | |||
| table: UseTableReturn<ReceiptIssuingOrderHTCustom>; | |||
| @@ -215,7 +229,7 @@ function SearchBox({ table }: CommonProps) { | |||
| <Typography>担当者</Typography> | |||
| <Stack direction="row"> | |||
| <RHFTextField | |||
| name="handler_name" | |||
| name={SearchParam.HANDLER_NAME} | |||
| fullWidth={false} | |||
| onBlur={handleBlur} | |||
| /> | |||
| @@ -232,13 +246,17 @@ function SearchBox({ table }: CommonProps) { | |||
| <Grid item xs={3} lg={2}> | |||
| <Stack> | |||
| <Typography>ステータス</Typography> | |||
| <RHFTextField name="status" onBlur={handleBlur} /> | |||
| <RHFTextField name={SearchParam.STATUS} onBlur={handleBlur} /> | |||
| </Stack> | |||
| </Grid> | |||
| <Grid item> | |||
| <Stack> | |||
| <Typography>完了を含む</Typography> | |||
| <RHFCheckbox name="include_done" label="" sx={{ mx: "auto" }} /> | |||
| <RHFCheckbox | |||
| name={SearchParam.INCLUDE_DONE} | |||
| label="" | |||
| sx={{ mx: "auto" }} | |||
| /> | |||
| </Stack> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -246,22 +264,31 @@ function SearchBox({ table }: CommonProps) { | |||
| <Grid item xs={3} lg={2}> | |||
| <Stack> | |||
| <Typography>運営会社</Typography> | |||
| <RHFTextField name="customer_name" onBlur={handleBlur} /> | |||
| <RHFTextField | |||
| name={SearchParam.CUSTOMER_NAME} | |||
| onBlur={handleBlur} | |||
| /> | |||
| </Stack> | |||
| </Grid> | |||
| <Grid item xs={3} lg={2}> | |||
| <Stack> | |||
| <Typography>駐車場名</Typography> | |||
| <RHFTextField name="parking_name" onBlur={handleBlur} /> | |||
| <RHFTextField | |||
| name={SearchParam.PARKING_NAME} | |||
| onBlur={handleBlur} | |||
| /> | |||
| </Stack> | |||
| </Grid> | |||
| <Grid item xs={3} lg={2}> | |||
| <Typography>電話番号</Typography> | |||
| <RHFTextField name="sms_phone_number" onBlur={handleBlur} /> | |||
| <RHFTextField | |||
| name={SearchParam.SMS_PHONE_NUMBER} | |||
| onBlur={handleBlur} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={3} lg={2}> | |||
| <Typography>領収証番号</Typography> | |||
| <RHFTextField name="receipt_no" onBlur={handleBlur} /> | |||
| <RHFTextField name={SearchParam.RECEIPT_NO} onBlur={handleBlur} /> | |||
| </Grid> | |||
| <Grid item xs={3} lg={2}> | |||
| <Typography>受付時刻</Typography> | |||
| @@ -309,7 +336,7 @@ function SearchBox({ table }: CommonProps) { | |||
| <Stack direction="row" spacing={2}> | |||
| <Box> | |||
| <Typography>From</Typography> | |||
| <RHFDatePicker name="order_date_from" /> | |||
| <RHFDatePicker name={SearchParam.ORDER_DATE_FROM} /> | |||
| </Box> | |||
| <Box> | |||
| <Typography> </Typography> | |||
| @@ -317,7 +344,7 @@ function SearchBox({ table }: CommonProps) { | |||
| </Box> | |||
| <Box> | |||
| <Typography>To</Typography> | |||
| <RHFDatePicker name="order_date_to" /> | |||
| <RHFDatePicker name={SearchParam.ORDER_DATE_TO} /> | |||
| </Box> | |||
| </Stack> | |||
| </DialogContent> | |||
| @@ -336,7 +363,7 @@ function SearchBox({ table }: CommonProps) { | |||
| } | |||
| function TableBox({ table }: CommonProps) { | |||
| const TABLE_HEAD = [ | |||
| const TABLE_HEAD: HeadLabelProps[] = [ | |||
| { id: "order_datetime", label: "受付時刻", align: "left" }, | |||
| { id: "customer_name", label: "運営会社名", align: "left" }, | |||
| { id: "parking_name", label: "駐車場名", align: "left" }, | |||
| @@ -2,6 +2,7 @@ import { | |||
| Box, | |||
| Button, | |||
| Grid, | |||
| Stack, | |||
| Table, | |||
| TableBody, | |||
| TableCell, | |||
| @@ -10,24 +11,31 @@ import { | |||
| TableRow, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { ApiId } from "api"; | |||
| import { | |||
| UseSummary, | |||
| getUseSummaries, | |||
| getUseSummaryYYYYMMs, | |||
| } from "api/custom/hello-techno/use-summary"; | |||
| import { getFullUrl } from "api/url"; | |||
| import { PageID, TabID } from "codes/page"; | |||
| import { FormProvider } from "components/hook-form"; | |||
| import RHFDatePicker from "components/hook-form/RHFDatePicker"; | |||
| import RHFSelect, { SelectOptionProps } from "components/hook-form/RHFSelect"; | |||
| import { TableHeadCustom } from "components/table"; | |||
| import { HeadLabelProps } from "components/table/TableHeadCustom"; | |||
| import { SearchConditionContextProvider } from "contexts/SearchConditionContext"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useDashboard from "hooks/useDashBoard"; | |||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||
| import useSearchConditionContext from "hooks/useSearchConditionContext"; | |||
| import useTable, { UseTableReturn } from "hooks/useTable"; | |||
| import { SearchParam } from "pages/dashboard/receipt-issuing-order/custom/hello-techno/list"; | |||
| import { useEffect, useMemo, useState } from "react"; | |||
| import { useForm } from "react-hook-form"; | |||
| import { getPath } from "routes/path"; | |||
| import { sprintf } from "sprintf-js"; | |||
| import { dateParse, formatDateStr } from "utils/datetime"; | |||
| export default function UseSummaryList() { | |||
| const { setHeaderTitle, setTabs } = useDashboard( | |||
| @@ -60,7 +68,8 @@ function Page() { | |||
| } | |||
| type FormProps = { | |||
| summary_yyyymm: string; | |||
| date_from: Date | null; | |||
| date_to: Date | null; | |||
| }; | |||
| type CommonProps = { | |||
| @@ -76,50 +85,15 @@ function SearchBox({ table }: CommonProps) { | |||
| const form = useForm<FormProps>({ | |||
| defaultValues: { | |||
| summary_yyyymm: "", | |||
| date_from: null, | |||
| date_to: null, | |||
| }, | |||
| }); | |||
| const selectedYYYYMM = form.watch("summary_yyyymm"); | |||
| const dateFrom = form.watch("date_from"); | |||
| const dateTo = form.watch("date_to"); | |||
| const [yyyymm, setYYYYMM] = useState<string[] | null>(null); | |||
| const yyyymmOptions: SelectOptionProps[] = useMemo(() => { | |||
| if (yyyymm === null) return []; | |||
| return yyyymm.map((ele) => { | |||
| return { | |||
| value: ele, | |||
| label: sprintf("%s/%s", ele.substring(0, 4), ele.substring(4, 6)), | |||
| }; | |||
| }); | |||
| }, [yyyymm]); | |||
| const downloadCsvUrl = useMemo(() => { | |||
| if (!selectedYYYYMM) return ""; | |||
| const param = new URLSearchParams({ | |||
| summary_yyyymm: selectedYYYYMM, | |||
| }); | |||
| return ( | |||
| process.env.REACT_APP_HOST_API_KEY + | |||
| "/custom/hello-techno/use-summary/csv?" + | |||
| param.toString() | |||
| ); | |||
| }, [selectedYYYYMM]); | |||
| const { callAPI: callGetUseSummaryYYYYMMs } = useAPICall({ | |||
| apiMethod: getUseSummaryYYYYMMs, | |||
| backDrop: true, | |||
| onSuccess: ({ data: { records } }) => { | |||
| setYYYYMM(records); | |||
| }, | |||
| }); | |||
| const { | |||
| callAPI: callGetContracts, | |||
| makeSendData, | |||
| sending, | |||
| } = useAPICall({ | |||
| const { callAPI: callGetContracts } = useAPICall({ | |||
| apiMethod: getUseSummaries, | |||
| backDrop: true, | |||
| onSuccess: ({ data }) => { | |||
| @@ -134,61 +108,59 @@ function SearchBox({ table }: CommonProps) { | |||
| const addCondition = (data: FormProps) => { | |||
| add({ | |||
| ...data, | |||
| date_from: formatDateStr(data.date_from), | |||
| date_to: formatDateStr(data.date_to), | |||
| }); | |||
| }; | |||
| const fetch = async () => { | |||
| const dateFrom = form.getValues("date_from"); | |||
| const dateTo = form.getValues("date_to"); | |||
| if (!dateFrom || !dateTo) return; | |||
| const sendData = { | |||
| ...condition, | |||
| ...form.getValues(), | |||
| date_from: formatDateStr(dateFrom), | |||
| date_to: formatDateStr(dateTo), | |||
| }; | |||
| if (sendData.summary_yyyymm) { | |||
| callGetContracts(sendData); | |||
| } | |||
| callGetContracts(sendData); | |||
| }; | |||
| // 初期値設定 | |||
| useEffect(() => { | |||
| if (initialized && yyyymm !== null) { | |||
| form.setValue( | |||
| "summary_yyyymm", | |||
| get("summary_yyyymm", yyyymm.find(() => true) ?? "") | |||
| ); | |||
| addCondition(form.getValues()); | |||
| if (initialized) { | |||
| form.setValue("date_from", dateParse(get("date_from"))); | |||
| form.setValue("date_to", dateParse(get("date_to"))); | |||
| } | |||
| }, [initialized, condition, yyyymm]); | |||
| }, [initialized]); | |||
| // Fetchアクション | |||
| useEffect(() => { | |||
| if (initialized) { | |||
| fetch(); | |||
| } | |||
| }, [condition, initialized, selectedYYYYMM]); | |||
| }, [condition, initialized]); | |||
| useEffect(() => { | |||
| callGetUseSummaryYYYYMMs({}); | |||
| }, []); | |||
| addCondition(form.getValues()); | |||
| }, [dateFrom, dateTo]); | |||
| return ( | |||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||
| <Box sx={{ p: 1, m: 1 }}> | |||
| <Grid container spacing={2}> | |||
| <Grid item xs={3} lg={2}> | |||
| <Typography>年月</Typography> | |||
| <RHFSelect | |||
| options={yyyymmOptions} | |||
| name="summary_yyyymm" | |||
| <Typography>開始年月日</Typography> | |||
| <RHFDatePicker | |||
| name="date_from" | |||
| size="small" | |||
| onChange={() => { | |||
| console.log("ge"); | |||
| }} | |||
| /> | |||
| </Grid> | |||
| {!!selectedYYYYMM && ( | |||
| <Grid item xs={3} lg={2}> | |||
| <Typography>CSV</Typography> | |||
| <Button variant="contained" color="info" href={downloadCsvUrl}> | |||
| ダウンロード | |||
| </Button> | |||
| </Grid> | |||
| )} | |||
| <Grid item xs={3} lg={2}> | |||
| <Typography>終了年月日</Typography> | |||
| <RHFDatePicker name="date_to" size="small" /> | |||
| </Grid> | |||
| </Grid> | |||
| </Box> | |||
| </FormProvider> | |||
| @@ -196,12 +168,12 @@ function SearchBox({ table }: CommonProps) { | |||
| } | |||
| function TableBox({ table }: CommonProps) { | |||
| const TABLE_HEAD = [ | |||
| const TABLE_HEAD: HeadLabelProps[] = [ | |||
| { id: "customer_name", label: "運営会社名", align: "left" }, | |||
| { id: "parking_name", label: "駐車場名", align: "left" }, | |||
| { id: "receipt_order_count", label: "領収証発行件数", align: "left" }, | |||
| { id: "mail_order_count", label: "郵送依頼件数", align: "left" }, | |||
| { id: "sms_send_count", label: "SMS送信件数", align: "left" }, | |||
| { id: "actions", label: "アクション", align: "left", needSort: false }, | |||
| ]; | |||
| const { | |||
| order, | |||
| @@ -267,13 +239,51 @@ type RowProps = { | |||
| data: UseSummary; | |||
| }; | |||
| function Row({ data }: RowProps) { | |||
| const { navigateWhenChanged } = useNavigateCustom(); | |||
| const handleClickDetail = () => { | |||
| const param = new URLSearchParams({ | |||
| [SearchParam.CUSTOMER_NAME]: data.customer_name, | |||
| [SearchParam.ORDER_DATE_FROM]: data.date_from, | |||
| [SearchParam.ORDER_DATE_TO]: data.date_to, | |||
| [SearchParam.INCLUDE_DONE]: "1", | |||
| }); | |||
| navigateWhenChanged( | |||
| getPath(PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST_CUSTOM_HELLO_TECHNO), | |||
| param | |||
| ); | |||
| }; | |||
| const downloadCsvUrl = useMemo(() => { | |||
| const param = new URLSearchParams({ | |||
| date_from: data.date_from, | |||
| date_to: data.date_to, | |||
| customer_code: data.customer_code, | |||
| }); | |||
| return ( | |||
| getFullUrl(ApiId.HT_CUSTOM_USE_SUMMARY_DOWNLOAD_CSV) + | |||
| "?" + | |||
| param.toString() | |||
| ); | |||
| }, [data]); | |||
| return ( | |||
| <TableRow> | |||
| <TableCell>{data.customer_name}</TableCell> | |||
| <TableCell>{data.parking_name}</TableCell> | |||
| <TableCell>{data.receipt_order_count}件</TableCell> | |||
| <TableCell>{data.mail_order_count}件</TableCell> | |||
| <TableCell>{data.sms_send_count}件</TableCell> | |||
| <TableCell> | |||
| <Stack direction="row" spacing={2}> | |||
| <Button variant="contained" onClick={handleClickDetail}> | |||
| 詳細 | |||
| </Button> | |||
| <Button variant="contained" href={downloadCsvUrl}> | |||
| CSV | |||
| </Button> | |||
| </Stack> | |||
| </TableCell> | |||
| </TableRow> | |||
| ); | |||
| } | |||