| @@ -0,0 +1,65 @@ | |||
| import { | |||
| APICommonResponse, | |||
| ApiId, | |||
| HttpMethod, | |||
| ListRequest, | |||
| request, | |||
| } from "../.."; | |||
| import { getUrl } from "../../url"; | |||
| export type UseSummary = { | |||
| id: string; | |||
| summary_yyyymm: string; | |||
| summary_key1: string; | |||
| summary_key2?: string; | |||
| customer_name: string; | |||
| parking_name?: string; | |||
| receipt_order_count: string; | |||
| sms_send_count: string; | |||
| sms_send_cost: string; | |||
| is_fixed: boolean; | |||
| updated_at: string; | |||
| }; | |||
| // 利用実績一覧取得 ----------------------- | |||
| export type UseSummariesRequest = { | |||
| summary_yyyymm: string; | |||
| } & ListRequest; | |||
| export type UseSummariesResponse = { | |||
| data: { | |||
| records: UseSummary[]; | |||
| }; | |||
| } & APICommonResponse; | |||
| export const getUseSummaries = async (data: UseSummariesRequest) => { | |||
| const res = await request<UseSummariesResponse>({ | |||
| url: getUrl(ApiId.HT_CUSTOM_USE_SUMMARIES), | |||
| method: HttpMethod.GET, | |||
| data: new URLSearchParams(data), | |||
| }); | |||
| return res; | |||
| }; | |||
| // 利用実績年月取得 ----------------------- | |||
| export type UseSummaryYYYYMMsRequest = {}; | |||
| export type UseSummaryYYYYMMsResponse = { | |||
| data: { | |||
| records: string[]; | |||
| }; | |||
| } & APICommonResponse; | |||
| export const getUseSummaryYYYYMMs = async (data: UseSummaryYYYYMMsRequest) => { | |||
| const res = await request<UseSummaryYYYYMMsResponse>({ | |||
| url: getUrl(ApiId.USE_SUMMARY_YYYYMM), | |||
| method: HttpMethod.GET, | |||
| data: new URLSearchParams(data), | |||
| }); | |||
| return res; | |||
| }; | |||
| @@ -29,6 +29,8 @@ export const ApiId = { | |||
| CONTRACTS: id++, | |||
| USE_SUMMARY_YYYYMM: id++, | |||
| LOGIN_USERS: id++, | |||
| LOGIN_USER_CREATE: id++, | |||
| LOGIN_USER_CHANGE_PASSWORD: id++, | |||
| @@ -39,6 +41,7 @@ export const ApiId = { | |||
| HT_CUSTOM_ADJUST_DATA: id++, | |||
| HT_CUSTOM_RECEIPT_ISSUING_ORDERS: id++, | |||
| HT_CUSTOM_RECEIPT_ISSUING_ORDER_CREATE: id++, | |||
| HT_CUSTOM_USE_SUMMARIES: id++, | |||
| } as const; | |||
| export type ApiId = (typeof ApiId)[keyof typeof ApiId]; | |||
| @@ -59,6 +62,10 @@ export type ResultCode = (typeof ResultCode)[keyof typeof ResultCode]; | |||
| export interface TimestampRequest { | |||
| timestamp: string; | |||
| } | |||
| export type ListRequest = { | |||
| sort?: string; | |||
| order?: string; | |||
| }; | |||
| export interface APICommonResponse { | |||
| result: ResultCode; | |||
| @@ -19,6 +19,9 @@ const urls = { | |||
| [A.RECEIPT_ISSUING_ORDERS]: "receipt-issuing-orders", | |||
| [A.CONTRACTS]: "contracts", | |||
| [A.USE_SUMMARY_YYYYMM]: "use-summary/yyyymm", | |||
| [A.LOGIN_USERS]: "users", | |||
| [A.LOGIN_USER_CREATE]: "user/create", | |||
| [A.LOGIN_USER_CHANGE_PASSWORD]: "user/change-password", | |||
| @@ -31,6 +34,7 @@ const urls = { | |||
| "custom/hello-techno/receipt-issuing-orders", | |||
| [A.HT_CUSTOM_RECEIPT_ISSUING_ORDER_CREATE]: | |||
| "custom/hello-techno/receipt-issuing-order/create", | |||
| [A.HT_CUSTOM_USE_SUMMARIES]: "custom/hello-techno/use-summaries", | |||
| }; | |||
| const prefixs = { | |||
| @@ -18,6 +18,9 @@ export const PageID = { | |||
| DASHBOARD_RECEIPT_ISSUING_ORDER_LIST_CUSTOM_HELLO_TECHNO: id++, | |||
| DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL_CUSTOM_HELLO_TECHNO: id++, | |||
| DASHBOARD_USE_SUMMARY_LIST_CUSTOM_HELLO_TECHNO: id++, | |||
| DASHBOARD_USE_SUMMARY_DETAIL_CUSTOM_HELLO_TECHNO: id++, | |||
| DASHBOARD_LOGIN_USER_LIST: id++, | |||
| DASHBOARD_LOGIN_USER_CREATE: id++, | |||
| DASHBOARD_LOGIN_USER_CHANGE_PASSWORD: id++, | |||
| @@ -12,7 +12,7 @@ const _condition: Dictionary = {}; | |||
| const initialState = { | |||
| initialized: false, | |||
| condition: _condition, | |||
| get: (key: string): string => "", | |||
| get: (key: string, defaultValue?: string): string => "", | |||
| initializeCondition: () => {}, | |||
| addCondition: (condition: Dictionary) => { | |||
| console.log("not init SearchConditionContext"); | |||
| @@ -52,8 +52,8 @@ export function SearchConditionContextProvider({ children }: Props) { | |||
| setInitialized(true); | |||
| }; | |||
| const get = (key: string) => { | |||
| return condition[key] ?? ""; | |||
| const get = (key: string, defaultValue?: string) => { | |||
| return condition[key] ?? defaultValue ?? ""; | |||
| }; | |||
| const getCondition = useMemo(() => { | |||
| @@ -4,6 +4,7 @@ import PeopleIcon from "@mui/icons-material/People"; | |||
| import ArticleIcon from "@mui/icons-material/Article"; | |||
| import SettingsIcon from "@mui/icons-material/Settings"; | |||
| import AccountBoxIcon from "@mui/icons-material/AccountBox"; | |||
| import AccountBalanceIcon from "@mui/icons-material/AccountBalance"; | |||
| import { Collapse } from "@mui/material"; | |||
| import Box from "@mui/material/Box"; | |||
| import Divider from "@mui/material/Divider"; | |||
| @@ -93,6 +94,16 @@ export default function Navigator(props: DrawerProps) { | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| label: "利用実績", | |||
| icon: <AccountBalanceIcon />, | |||
| children: [ | |||
| { | |||
| id: PageID.DASHBOARD_USE_SUMMARY_LIST_CUSTOM_HELLO_TECHNO, | |||
| label: "一覧", | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| label: "ユーザ管理", | |||
| icon: <PeopleIcon />, | |||
| @@ -0,0 +1,72 @@ | |||
| import { | |||
| Box, | |||
| Button, | |||
| Grid, | |||
| Table, | |||
| TableBody, | |||
| TableCell, | |||
| TableContainer, | |||
| TableHead, | |||
| TableRow, | |||
| TextField, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { PageID, TabID } from "codes/page"; | |||
| import { RHFTextField } from "components/hook-form"; | |||
| import useDashboard from "hooks/useDashBoard"; | |||
| import { useEffect } from "react"; | |||
| export default function UseSummaryList() { | |||
| const { setHeaderTitle, setTabs } = useDashboard( | |||
| PageID.DASHBOARD_USE_SUMMARY_DETAIL_CUSTOM_HELLO_TECHNO, | |||
| TabID.NONE | |||
| ); | |||
| useEffect(() => { | |||
| setHeaderTitle("利用実績詳細"); | |||
| setTabs(null); | |||
| }, []); | |||
| return ( | |||
| <> | |||
| <Box sx={{ p: 1, m: 1 }}> | |||
| <Grid container spacing={2}> | |||
| <Grid item> | |||
| <Typography>運営会社</Typography> | |||
| <Typography>京都</Typography> | |||
| </Grid> | |||
| <Grid item> | |||
| <Typography>対象月</Typography> | |||
| <Typography>2023/05</Typography> | |||
| </Grid> | |||
| <Grid item xs /> | |||
| <Grid item> | |||
| <Button variant="contained">CSVダウンロード</Button> | |||
| </Grid> | |||
| </Grid> | |||
| </Box> | |||
| <TableContainer | |||
| sx={{ | |||
| // minWidth: 800, | |||
| position: "relative", | |||
| }} | |||
| > | |||
| <Table size="small"> | |||
| <TableHead> | |||
| <TableCell>駐車場名</TableCell> | |||
| <TableCell>領収証発行件数</TableCell> | |||
| </TableHead> | |||
| <TableBody> | |||
| <TableRow> | |||
| <TableCell>A駐車場</TableCell> | |||
| <TableCell>1件</TableCell> | |||
| </TableRow> | |||
| <TableRow> | |||
| <TableCell>B駐車場</TableCell> | |||
| <TableCell>3件</TableCell> | |||
| </TableRow> | |||
| </TableBody> | |||
| </Table> | |||
| </TableContainer> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,278 @@ | |||
| import { | |||
| Box, | |||
| Grid, | |||
| Stack, | |||
| Table, | |||
| TableBody, | |||
| TableCell, | |||
| TableContainer, | |||
| TableHead, | |||
| TablePagination, | |||
| TableRow, | |||
| TextField, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { Dictionary } from "@types"; | |||
| import { | |||
| UseSummary, | |||
| getUseSummaries, | |||
| getUseSummaryYYYYMMs, | |||
| } from "api/custom/hello-techno/use-summary"; | |||
| import { PageID, TabID } from "codes/page"; | |||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||
| import RHFSelect, { SelectOptionProps } from "components/hook-form/RHFSelect"; | |||
| import { TableHeadCustom } from "components/table"; | |||
| 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 { useEffect, useMemo, useState } from "react"; | |||
| import { useForm } from "react-hook-form"; | |||
| import { getPath } from "routes/path"; | |||
| import { sprintf } from "sprintf-js"; | |||
| import { formatYYYYMMStr, now } from "utils/datetime"; | |||
| export default function UseSummaryList() { | |||
| const { setHeaderTitle, setTabs } = useDashboard( | |||
| PageID.DASHBOARD_USE_SUMMARY_LIST_CUSTOM_HELLO_TECHNO, | |||
| TabID.NONE | |||
| ); | |||
| const { navigateWhenChanged } = useNavigateCustom(); | |||
| useEffect(() => { | |||
| setHeaderTitle("利用実績一覧"); | |||
| setTabs(null); | |||
| }, []); | |||
| return ( | |||
| <SearchConditionContextProvider> | |||
| <Page /> | |||
| </SearchConditionContextProvider> | |||
| ); | |||
| } | |||
| function Page() { | |||
| const table = useTable<UseSummary>(); | |||
| return ( | |||
| <Box> | |||
| <SearchBox table={table} /> | |||
| <TableBox table={table} /> | |||
| </Box> | |||
| ); | |||
| } | |||
| type FormProps = { | |||
| summary_yyyymm: string; | |||
| }; | |||
| type CommonProps = { | |||
| table: UseTableReturn<UseSummary>; | |||
| }; | |||
| function SearchBox({ table }: CommonProps) { | |||
| const { | |||
| condition, | |||
| initialized, | |||
| get, | |||
| addCondition: add, | |||
| } = useSearchConditionContext(); | |||
| const form = useForm<FormProps>({ | |||
| defaultValues: { | |||
| summary_yyyymm: "", | |||
| }, | |||
| }); | |||
| const selectedYYYYMM = form.watch("summary_yyyymm"); | |||
| 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 { callAPI: callGetUseSummaryYYYYMMs } = useAPICall({ | |||
| apiMethod: getUseSummaryYYYYMMs, | |||
| onSuccess: ({ data: { records } }) => { | |||
| setYYYYMM(records); | |||
| }, | |||
| }); | |||
| const { | |||
| callAPI: callGetContracts, | |||
| makeSendData, | |||
| sending, | |||
| } = useAPICall({ | |||
| apiMethod: getUseSummaries, | |||
| backDrop: true, | |||
| onSuccess: ({ data }) => { | |||
| table.setRowData(data.records); | |||
| }, | |||
| }); | |||
| const handleSubmit = async (data: FormProps) => { | |||
| addCondition(data); | |||
| }; | |||
| const addCondition = (data: FormProps) => { | |||
| add({ | |||
| ...data, | |||
| }); | |||
| }; | |||
| const handleBlur = () => { | |||
| addCondition(form.getValues()); | |||
| }; | |||
| const fetch = async () => { | |||
| const sendData = { | |||
| ...form.getValues(), | |||
| }; | |||
| if (sendData.summary_yyyymm) { | |||
| callGetContracts(sendData); | |||
| } | |||
| }; | |||
| // 初期値設定 | |||
| useEffect(() => { | |||
| if (initialized && yyyymm !== null) { | |||
| form.setValue( | |||
| "summary_yyyymm", | |||
| get("summary_yyyymm", yyyymm.find(() => true) ?? "") | |||
| ); | |||
| addCondition(form.getValues()); | |||
| } | |||
| }, [initialized, condition, yyyymm]); | |||
| // Fetchアクション | |||
| useEffect(() => { | |||
| if (initialized) { | |||
| fetch(); | |||
| } | |||
| }, [condition, initialized, selectedYYYYMM]); | |||
| useEffect(() => { | |||
| callGetUseSummaryYYYYMMs({}); | |||
| }, []); | |||
| 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> | |||
| {/* <RHFTextField | |||
| name="summary_yyyymm" | |||
| placeholder="YYYYMM" | |||
| onBlur={handleBlur} | |||
| /> */} | |||
| <RHFSelect | |||
| options={yyyymmOptions} | |||
| name="summary_yyyymm" | |||
| size="small" | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| </Box> | |||
| </FormProvider> | |||
| ); | |||
| } | |||
| function TableBox({ table }: CommonProps) { | |||
| const TABLE_HEAD = [ | |||
| { id: "customer_name", label: "運営会社名", align: "left" }, | |||
| { id: "parking_name", label: "駐車場名", align: "left" }, | |||
| { id: "receipt_order_count", label: "領収証発行件数", align: "left" }, | |||
| // { id: "", label: "郵送件数", align: "left" }, | |||
| { id: "sms_send_count", label: "SMS送信件数", align: "left" }, | |||
| ]; | |||
| const { | |||
| order, | |||
| page, | |||
| sort, | |||
| rowsPerPage, | |||
| fetched, | |||
| fillteredRow, | |||
| isNotFound, | |||
| dataLength, | |||
| // | |||
| onSort, | |||
| onChangePage, | |||
| onChangeRowsPerPage, | |||
| // | |||
| setRowData, | |||
| // | |||
| ROWS_PER_PAGES, | |||
| } = table; | |||
| return ( | |||
| <> | |||
| <TableContainer | |||
| sx={{ | |||
| // minWidth: 800, | |||
| position: "relative", | |||
| }} | |||
| > | |||
| <Table size="small"> | |||
| <TableHeadCustom | |||
| order={order} | |||
| orderBy={sort} | |||
| headLabel={TABLE_HEAD} | |||
| rowCount={1} | |||
| numSelected={0} | |||
| onSort={onSort} | |||
| /> | |||
| <TableBody> | |||
| {fillteredRow.map((row, index) => ( | |||
| <Row data={row} key={index} /> | |||
| ))} | |||
| </TableBody> | |||
| </Table> | |||
| </TableContainer> | |||
| <Box sx={{ position: "relative" }}> | |||
| <TablePagination | |||
| rowsPerPageOptions={ROWS_PER_PAGES} | |||
| component="div" | |||
| count={dataLength} | |||
| rowsPerPage={rowsPerPage} | |||
| page={page} | |||
| onPageChange={onChangePage} | |||
| onRowsPerPageChange={onChangeRowsPerPage} | |||
| /> | |||
| </Box> | |||
| </> | |||
| ); | |||
| } | |||
| type RowProps = { | |||
| data: UseSummary; | |||
| }; | |||
| function Row({ data }: RowProps) { | |||
| const { navigateWhenChanged } = useNavigateCustom(); | |||
| const handleClick = () => { | |||
| // navigateWhenChanged(getPath(PageID.DASHBOARD_CONTRACT_DETAIL), { | |||
| // id: data.id, | |||
| // }); | |||
| }; | |||
| return ( | |||
| <TableRow hover sx={{ cursor: "pointer" }} onClick={handleClick}> | |||
| <TableCell>{data.customer_name}</TableCell> | |||
| <TableCell>{data.parking_name}</TableCell> | |||
| <TableCell>{data.receipt_order_count}件</TableCell> | |||
| {/* <TableCell>{data.parking_name}</TableCell> */} | |||
| <TableCell>{data.sms_send_count}件</TableCell> | |||
| </TableRow> | |||
| ); | |||
| } | |||
| @@ -32,6 +32,16 @@ export const AUTH = { | |||
| R.NORMAL_ADMIN, | |||
| [C.HELLO_TECHNO] | |||
| ), | |||
| [P.DASHBOARD_USE_SUMMARY_LIST_CUSTOM_HELLO_TECHNO]: setAuth( | |||
| "ge", | |||
| R.NORMAL_ADMIN, | |||
| [C.HELLO_TECHNO] | |||
| ), | |||
| [P.DASHBOARD_USE_SUMMARY_DETAIL_CUSTOM_HELLO_TECHNO]: setAuth( | |||
| "ge", | |||
| R.NORMAL_ADMIN, | |||
| [C.HELLO_TECHNO] | |||
| ), | |||
| [P.DASHBOARD_LOGIN_USER_LIST]: setAuth("ge", R.CONTRACT_ADMIN), | |||
| [P.DASHBOARD_LOGIN_USER_CREATE]: setAuth("ge", R.CONTRACT_ADMIN), | |||
| [P.DASHBOARD_LOGIN_USER_CHANGE_PASSWORD]: setAuth("ge", R.NORMAL_ADMIN), | |||
| @@ -81,6 +81,14 @@ const DashboardRoutes = (): RouteObject => { | |||
| pageId: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL_CUSTOM_HELLO_TECHNO, | |||
| element: <ReceiptIssuingOrderDetailHelloTechno />, | |||
| }, | |||
| { | |||
| pageId: PageID.DASHBOARD_USE_SUMMARY_LIST_CUSTOM_HELLO_TECHNO, | |||
| element: <UseSummaryListHelloTechno />, | |||
| }, | |||
| { | |||
| pageId: PageID.DASHBOARD_USE_SUMMARY_DETAIL_CUSTOM_HELLO_TECHNO, | |||
| element: <UseSummaryDetailHelloTechno />, | |||
| }, | |||
| { | |||
| pageId: PageID.DASHBOARD_LOGIN_USER_LIST, | |||
| element: <LoginUserList />, | |||
| @@ -169,6 +177,14 @@ const ReceiptIssuingOrderDetailHelloTechno = Loadable( | |||
| ) | |||
| ); | |||
| // 利用実績関連 | |||
| const UseSummaryListHelloTechno = Loadable( | |||
| lazy(() => import("pages/dashboard/use-summary/custom/hello-techno/list")) | |||
| ); | |||
| const UseSummaryDetailHelloTechno = Loadable( | |||
| lazy(() => import("pages/dashboard/use-summary/custom/hello-techno/detail")) | |||
| ); | |||
| // ログインユーザー管理 | |||
| const LoginUserList = Loadable( | |||
| lazy(() => import("pages/dashboard/login-user/list")) | |||
| @@ -61,6 +61,12 @@ const PATHS = { | |||
| PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL_CUSTOM_HELLO_TECHNO | |||
| )]: "/dashboard/receipt-issusing-order/detail/:id", | |||
| // 利用実績関連 | |||
| [makePathKey(PageID.DASHBOARD_USE_SUMMARY_LIST_CUSTOM_HELLO_TECHNO)]: | |||
| "/dashboard/use-summary/hello-techno/list", | |||
| [makePathKey(PageID.DASHBOARD_USE_SUMMARY_DETAIL_CUSTOM_HELLO_TECHNO)]: | |||
| "/dashboard/use-summary/hello-techno/detail", | |||
| // ログインユーザ管理 | |||
| [makePathKey(PageID.DASHBOARD_LOGIN_USER_LIST)]: | |||
| "/dashboard/login-user/list/:page", | |||
| @@ -1,7 +1,8 @@ | |||
| import { format, isValid, parse, parseISO } from "date-fns"; | |||
| export const DEFAULT_DATE_FORMAT = "yyyy/MM/dd"; | |||
| export const DEFAULT_DATET_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss"; | |||
| export const DEFAULT_DATE_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss"; | |||
| export const DEFAULT_YYYYMM_FORMAT = "yyyyMM"; | |||
| type Input = Date | string | null | undefined; | |||
| @@ -10,7 +11,10 @@ export const formatDateStr = (source: Input) => { | |||
| }; | |||
| export const formatDateTimeStr = (source: Date | string | null | undefined) => { | |||
| return formatToStr(source, DEFAULT_DATET_TIME_FORMAT); | |||
| return formatToStr(source, DEFAULT_DATE_TIME_FORMAT); | |||
| }; | |||
| export const formatYYYYMMStr = (source: Date | string | null | undefined) => { | |||
| return formatToStr(source, DEFAULT_YYYYMM_FORMAT); | |||
| }; | |||
| const formatToStr = (source: Input, formatStr: string) => { | |||
| @@ -31,7 +35,7 @@ export const dateParse = (source: Input): Date | null => { | |||
| }; | |||
| export const dateTimeParse = (source: Input): Date | null => { | |||
| return parseFromFormat(source, DEFAULT_DATET_TIME_FORMAT); | |||
| return parseFromFormat(source, DEFAULT_DATE_TIME_FORMAT); | |||
| }; | |||
| const parseFromFormat = (source: Input, format: string): Date | null => { | |||