| @@ -1,12 +1,15 @@ | |||
| import { UserRole } from "codes/user"; | |||
| import { APICommonResponse, ApiId, HttpMethod, request } from "."; | |||
| import { getUrl } from "./url"; | |||
| import { CustomCode } from "codes/custom"; | |||
| type MeResponse = { | |||
| data: { | |||
| id: string; | |||
| contract_id: string; | |||
| role: UserRole; | |||
| name: string; | |||
| custom: CustomCode[]; | |||
| }; | |||
| } & APICommonResponse; | |||
| @@ -3,6 +3,7 @@ import { | |||
| ApiId, | |||
| HttpMethod, | |||
| makeFormData, | |||
| makeParam, | |||
| request, | |||
| } from "api"; | |||
| import { ReceiptIssuingOrder } from "api/receipt-issuing-order"; | |||
| @@ -65,6 +66,12 @@ export type ReceiptIssuingOrdersRequest = { | |||
| customer_name?: string; | |||
| parking_management_code?: string; | |||
| parking_name?: string; | |||
| status?: string; | |||
| order_date_from?: string; | |||
| order_date_to?: string; | |||
| done?: string; | |||
| sms_phone_number?: string; | |||
| handler_name?: string; | |||
| }; | |||
| export type ReceiptIssuingOrdersResponse = { | |||
| @@ -0,0 +1,7 @@ | |||
| export const CustomCode = { | |||
| NONE: "", | |||
| HELLO_TECHNO: "HELLO_TECHNO", | |||
| } as const; | |||
| export type CustomCode = (typeof CustomCode)[keyof typeof CustomCode]; | |||
| @@ -13,9 +13,9 @@ export const PageID = { | |||
| DASHBOARD_CONTRACT_LIST: id++, | |||
| DASHBOARD_CONTRACT_DETAIL: id++, | |||
| DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE: id++, | |||
| DASHBOARD_RECEIPT_ISSUING_ORDER_LIST: id++, | |||
| DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL: id++, | |||
| DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE_CUSTOM_HELLO_TECHNO: id++, | |||
| DASHBOARD_RECEIPT_ISSUING_ORDER_LIST_CUSTOM_HELLO_TECHNO: id++, | |||
| DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL_CUSTOM_HELLO_TECHNO: id++, | |||
| PAGE_403: id++, | |||
| PAGE_404: id++, | |||
| @@ -1,18 +1,47 @@ | |||
| // form | |||
| import { useFormContext, Controller } from 'react-hook-form'; | |||
| import { useFormContext, Controller } from "react-hook-form"; | |||
| // @mui | |||
| import { Checkbox, FormControlLabel, FormGroup, FormControlLabelProps } from '@mui/material'; | |||
| import { | |||
| Checkbox, | |||
| FormControlLabel, | |||
| FormGroup, | |||
| FormControlLabelProps, | |||
| } from "@mui/material"; | |||
| import { useMemo } from "react"; | |||
| // ---------------------------------------------------------------------- | |||
| interface RHFCheckboxProps extends Omit<FormControlLabelProps, 'control'> { | |||
| interface RHFCheckboxProps extends Omit<FormControlLabelProps, "control"> { | |||
| name: string; | |||
| readOnly?: boolean; | |||
| } | |||
| export function RHFCheckbox({ name, readOnly, ...other }: RHFCheckboxProps) { | |||
| const { control, watch } = useFormContext(); | |||
| const formValue: boolean = watch(name); | |||
| const _formValue = watch(name); | |||
| // const formValue : boolean = useMemo(()=>{ | |||
| // if(_formValue typeof 'boolean') { | |||
| // return _formValue; | |||
| // } | |||
| // return false | |||
| // },[_formValue]) | |||
| const formValue = useMemo(() => { | |||
| if (typeof _formValue === "boolean") { | |||
| console.log("boolean", _formValue); | |||
| return _formValue; | |||
| } | |||
| if (typeof _formValue === "string") { | |||
| console.log("string", _formValue); | |||
| return _formValue === "1" || _formValue === "true"; | |||
| } | |||
| console.log("else"); | |||
| return false; | |||
| }, [_formValue]); | |||
| if (readOnly) { | |||
| return ( | |||
| @@ -24,12 +53,12 @@ export function RHFCheckbox({ name, readOnly, ...other }: RHFCheckboxProps) { | |||
| disableFocusRipple | |||
| checked={formValue} | |||
| sx={{ | |||
| cursor: 'default', | |||
| cursor: "default", | |||
| }} | |||
| /> | |||
| } | |||
| sx={{ | |||
| cursor: 'default', | |||
| cursor: "default", | |||
| }} | |||
| {...other} | |||
| /> | |||
| @@ -42,7 +71,7 @@ export function RHFCheckbox({ name, readOnly, ...other }: RHFCheckboxProps) { | |||
| <Controller | |||
| name={name} | |||
| control={control} | |||
| render={({ field }) => <Checkbox {...field} checked={field.value} />} | |||
| render={({ field }) => <Checkbox {...field} checked={formValue} />} | |||
| /> | |||
| } | |||
| {...other} | |||
| @@ -52,7 +81,8 @@ export function RHFCheckbox({ name, readOnly, ...other }: RHFCheckboxProps) { | |||
| // ---------------------------------------------------------------------- | |||
| interface RHFMultiCheckboxProps extends Omit<FormControlLabelProps, 'control' | 'label'> { | |||
| interface RHFMultiCheckboxProps | |||
| extends Omit<FormControlLabelProps, "control" | "label"> { | |||
| name: string; | |||
| options: { | |||
| label: string; | |||
| @@ -60,7 +90,11 @@ interface RHFMultiCheckboxProps extends Omit<FormControlLabelProps, 'control' | | |||
| }[]; | |||
| } | |||
| export function RHFMultiCheckbox({ name, options, ...other }: RHFMultiCheckboxProps) { | |||
| export function RHFMultiCheckbox({ | |||
| name, | |||
| options, | |||
| ...other | |||
| }: RHFMultiCheckboxProps) { | |||
| const { control } = useFormContext(); | |||
| return ( | |||
| @@ -1,14 +1,26 @@ | |||
| import { useFormContext, Controller } from "react-hook-form"; | |||
| import { MenuItem, TextField, TextFieldProps } from "@mui/material"; | |||
| import { | |||
| Box, | |||
| Chip, | |||
| FormControl, | |||
| FormHelperText, | |||
| IconButton, | |||
| MenuItem, | |||
| OutlinedInput, | |||
| Select, | |||
| TextField, | |||
| TextFieldProps, | |||
| } from "@mui/material"; | |||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||
| import TextFieldEx from "../form/TextFieldEx"; | |||
| import { Dictionary } from "@types"; | |||
| import { Clear, Label } from "@mui/icons-material"; | |||
| // ---------------------------------------------------------------------- | |||
| export type SelectOptionProps = { | |||
| value: string; | |||
| label: string; | |||
| label?: string; | |||
| }; | |||
| export const makeOptions = (list: Dictionary[]): SelectOptionProps[] => { | |||
| @@ -60,7 +72,7 @@ export default function RHFSelect({ | |||
| return options.map((option, index) => { | |||
| return ( | |||
| <MenuItem value={option.value} key={index}> | |||
| {option.label} | |||
| {option.label ?? option.value} | |||
| </MenuItem> | |||
| ); | |||
| }); | |||
| @@ -117,3 +129,93 @@ export default function RHFSelect({ | |||
| /> | |||
| ); | |||
| } | |||
| const ITEM_HEIGHT = 48; | |||
| const ITEM_PADDING_TOP = 8; | |||
| const MenuProps = { | |||
| PaperProps: { | |||
| style: { | |||
| maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, | |||
| width: 250, | |||
| }, | |||
| }, | |||
| }; | |||
| export function RHFSelectMuiliple({ | |||
| name, | |||
| children, | |||
| readOnly, | |||
| options, | |||
| onFix, | |||
| ...other | |||
| }: Props) { | |||
| const { control, watch, setValue } = useFormContext(); | |||
| const formValue: string[] = watch(name); | |||
| const getOptionElements = useCallback(() => { | |||
| if (options) { | |||
| if (options.length === 0) { | |||
| return [ | |||
| <MenuItem key="disabled" disabled> | |||
| 候補がありません | |||
| </MenuItem>, | |||
| ]; | |||
| } else { | |||
| return options.map((option, index) => { | |||
| return ( | |||
| <MenuItem value={option.value} key={index}> | |||
| {option.label ?? option.value} | |||
| </MenuItem> | |||
| ); | |||
| }); | |||
| } | |||
| } | |||
| return []; | |||
| }, [options]); | |||
| const clearButton = useMemo(() => { | |||
| if (formValue.length === 0) return null; | |||
| const handleClick = () => { | |||
| setValue(name, []); | |||
| }; | |||
| return ( | |||
| <IconButton sx={{ mr: 1 }} onClick={handleClick}> | |||
| <Clear /> | |||
| </IconButton> | |||
| ); | |||
| }, [formValue]); | |||
| return ( | |||
| <Controller | |||
| name={name} | |||
| control={control} | |||
| render={({ field, fieldState: { error } }) => ( | |||
| <FormControl> | |||
| <Select | |||
| {...field} | |||
| size="small" | |||
| multiple | |||
| value={formValue} | |||
| error={!!error} | |||
| input={<OutlinedInput endAdornment={clearButton} />} | |||
| renderValue={(selected) => ( | |||
| <Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}> | |||
| {selected.map((value) => ( | |||
| <Chip key={value} label={value} size="small" /> | |||
| ))} | |||
| </Box> | |||
| )} | |||
| MenuProps={MenuProps} | |||
| > | |||
| {children} | |||
| {getOptionElements()} | |||
| </Select> | |||
| {!!error?.message && <FormHelperText>{error.message}</FormHelperText>} | |||
| </FormControl> | |||
| )} | |||
| /> | |||
| ); | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| import { HasChildren } from "@types"; | |||
| import { ResultCode } from "api"; | |||
| import { login as APILogin, logout as APILogout, me } from "api/auth"; | |||
| import { CustomCode } from "codes/custom"; | |||
| import { PageID } from "codes/page"; | |||
| import { UserRole } from "codes/user"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| @@ -21,6 +22,9 @@ type Auth = { | |||
| role: UserRole; | |||
| contractId: string | null; | |||
| name: string; | |||
| custom: CustomCode[]; | |||
| customerName: string; | |||
| login: (email: string, password: string) => Promise<boolean>; | |||
| logout: VoidFunction; | |||
| @@ -37,6 +41,9 @@ export const AuthContext = createContext<Auth>({ | |||
| role: UserRole.NONE, | |||
| contractId: null, | |||
| name: "", | |||
| custom: [], | |||
| customerName: "", | |||
| login: async (email: string, password: string) => false, | |||
| logout: () => {}, | |||
| @@ -50,6 +57,9 @@ function AuthContextProvider({ children }: Props) { | |||
| const [initialized, setInitialized] = useState(false); | |||
| const [role, setRole] = useState<UserRole>(UserRole.NONE); | |||
| const [contractId, setContractId] = useState<string | null>(null); | |||
| const [name, setName] = useState(""); | |||
| const [custom, setCustom] = useState<CustomCode[]>([]); | |||
| const [customerName, setCustomerName] = useState(""); | |||
| const authenticated = useMemo(() => { | |||
| return role !== UserRole.NONE; | |||
| @@ -60,7 +70,8 @@ function AuthContextProvider({ children }: Props) { | |||
| onSuccess: (res) => { | |||
| setContractId(res.data.contract_id); | |||
| setRole(res.data.role); | |||
| setName(res.data.name); | |||
| setCustom(res.data.custom); | |||
| setInitialized(true); | |||
| }, | |||
| onFailed: () => { | |||
| @@ -132,6 +143,10 @@ function AuthContextProvider({ children }: Props) { | |||
| authenticated, | |||
| role, | |||
| contractId, | |||
| name, | |||
| custom, | |||
| customerName, | |||
| // Func | |||
| login, | |||
| @@ -72,7 +72,15 @@ export function SearchConditionContextProvider({ children }: Props) { | |||
| } | |||
| }); | |||
| console.log("compare condition", { | |||
| before, | |||
| after, | |||
| }); | |||
| if (!isEqual(before, after)) { | |||
| console.log("add condition", { | |||
| before, | |||
| after, | |||
| }); | |||
| setCondition(after, "addCondition"); | |||
| } | |||
| }; | |||
| @@ -90,9 +98,6 @@ export function SearchConditionContextProvider({ children }: Props) { | |||
| const applyToURL = () => { | |||
| if (!initialized) return; | |||
| const params = searchParams; | |||
| const searchStr = params.toString(); | |||
| const url = pathname + (searchStr ? "?" + searchStr : ""); | |||
| navigateWhenChanged(pathname, condition, "applyToURL"); | |||
| }; | |||
| @@ -60,11 +60,11 @@ const categories: Group[] = [ | |||
| icon: <PeopleIcon />, | |||
| children: [ | |||
| { | |||
| id: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST, | |||
| id: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST_CUSTOM_HELLO_TECHNO, | |||
| label: "一覧", | |||
| }, | |||
| { | |||
| id: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE, | |||
| id: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE_CUSTOM_HELLO_TECHNO, | |||
| label: "新規", | |||
| }, | |||
| ], | |||
| @@ -14,7 +14,7 @@ import { getValue } from "components/hook-form/RHFAutoComplete"; | |||
| export default function ReceiptIssuingOrderCreate() { | |||
| const { setHeaderTitle, setTabs } = useDashboard( | |||
| PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE, | |||
| PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE_CUSTOM_HELLO_TECHNO, | |||
| TabID.NONE | |||
| ); | |||
| @@ -30,7 +30,7 @@ import { ApiId } from "api"; | |||
| export default function ReceiptIssuingOrderDetail() { | |||
| const { setHeaderTitle, setTabs } = useDashboard( | |||
| PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL | |||
| PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL_CUSTOM_HELLO_TECHNO | |||
| ); | |||
| const { navigate } = useNavigateCustom(); | |||
| @@ -0,0 +1,425 @@ | |||
| import { | |||
| Box, | |||
| Button, | |||
| Dialog, | |||
| DialogActions, | |||
| DialogContent, | |||
| DialogTitle, | |||
| Drawer, | |||
| FormControl, | |||
| Grid, | |||
| IconButton, | |||
| Stack, | |||
| Table, | |||
| TableBody, | |||
| TableCell, | |||
| TableContainer, | |||
| TablePagination, | |||
| TableRow, | |||
| TextField, | |||
| Tooltip, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { Dictionary } from "@types"; | |||
| import { | |||
| ReceiptIssuingOrderHTCustom, | |||
| getReceiptIssuingOrders, | |||
| } from "api/custom/hello-techno/receipt-issuing-order"; | |||
| import { PageID, TabID } from "codes/page"; | |||
| import { | |||
| FormProvider, | |||
| RHFCheckbox, | |||
| RHFSelect, | |||
| RHFTextField, | |||
| } from "components/hook-form"; | |||
| import { TableHeadCustom } from "components/table"; | |||
| import { SearchConditionContextProvider } from "contexts/SearchConditionContext"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useBackDrop from "hooks/useBackDrop"; | |||
| 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 AccountCircleIcon from "@mui/icons-material/AccountCircle"; | |||
| import { RHFSelectMuiliple } from "components/hook-form/RHFSelect"; | |||
| import useAuth from "hooks/useAuth"; | |||
| import RHFDatePicker from "components/hook-form/RHFDatePicker"; | |||
| import { dateParse, formatDateStr } from "utils/datetime"; | |||
| import { Clear } from "@mui/icons-material"; | |||
| export default function ReceiptIssuingOrderList() { | |||
| const { setHeaderTitle, setTabs } = useDashboard( | |||
| PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST_CUSTOM_HELLO_TECHNO, | |||
| TabID.NONE | |||
| ); | |||
| useEffect(() => { | |||
| setHeaderTitle("領収証発行依頼一覧"); | |||
| setTabs(null); | |||
| }, []); | |||
| return ( | |||
| <SearchConditionContextProvider> | |||
| <Page /> | |||
| </SearchConditionContextProvider> | |||
| ); | |||
| } | |||
| function Page() { | |||
| const table = useTable<ReceiptIssuingOrderHTCustom>(); | |||
| return ( | |||
| <Box> | |||
| <SearchBox table={table} /> | |||
| <TableBox table={table} /> | |||
| </Box> | |||
| ); | |||
| } | |||
| 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; | |||
| }; | |||
| type CommonProps = { | |||
| table: UseTableReturn<ReceiptIssuingOrderHTCustom>; | |||
| }; | |||
| function SearchBox({ table }: CommonProps) { | |||
| const { | |||
| condition, | |||
| initialized, | |||
| get, | |||
| addCondition: add, | |||
| } = useSearchConditionContext(); | |||
| const { setShowBackDrop } = useBackDrop(); | |||
| const { name: myName } = useAuth(); | |||
| const [openDateDialog, setOpenDateDialog] = useState(false); | |||
| const form = useForm<FormProps>({ | |||
| defaultValues: { | |||
| customer_name: "", | |||
| parking_name: "", | |||
| include_done: false, | |||
| status: "", | |||
| sms_phone_number: "", | |||
| handler_name: "", | |||
| order_date_from: null, | |||
| order_date_to: null, | |||
| }, | |||
| }); | |||
| const { | |||
| callAPI: callGetReceiptIssuingOrders, | |||
| makeSendData, | |||
| sending, | |||
| } = useAPICall({ | |||
| apiMethod: getReceiptIssuingOrders, | |||
| form, | |||
| onSuccess: (res) => { | |||
| table.setRowData(res.data.records); | |||
| }, | |||
| }); | |||
| const includeDone = form.watch("include_done"); | |||
| const orderDateFrom = form.watch("order_date_from"); | |||
| const orderDateTo = form.watch("order_date_to"); | |||
| const isSetOrderDateConditions = useMemo(() => { | |||
| return !!orderDateFrom || !!orderDateTo; | |||
| }, [orderDateFrom, orderDateTo]); | |||
| const handleSubmit = async (data: FormProps) => { | |||
| addCondition(data); | |||
| }; | |||
| const addCondition = (data: FormProps) => { | |||
| add({ | |||
| ...data, | |||
| include_done: data.include_done ? "1" : "0", | |||
| order_date_from: formatDateStr(data.order_date_from), | |||
| order_date_to: formatDateStr(data.order_date_to), | |||
| }); | |||
| }; | |||
| const handleBlur = () => { | |||
| addCondition(form.getValues()); | |||
| }; | |||
| const setMyName = () => { | |||
| form.setValue("handler_name", myName); | |||
| addCondition(form.getValues()); | |||
| }; | |||
| const fetch = async (data: Dictionary) => { | |||
| const sendData = makeSendData({ | |||
| ...data, | |||
| // 完了を含む場合は、条件設定しない。含まない場合は未完了のみとする | |||
| done: data.include_done === "1" ? "" : "0", | |||
| }); | |||
| callGetReceiptIssuingOrders(sendData); | |||
| }; | |||
| // 初期値設定 | |||
| useEffect(() => { | |||
| if (initialized) { | |||
| form.setValue("customer_name", get("customer_name")); | |||
| form.setValue("parking_name", get("parking_name")); | |||
| form.setValue("include_done", get("include_done") === "1"); | |||
| form.setValue("status", get("status")); | |||
| form.setValue("handler_name", get("handler_name")); | |||
| form.setValue("sms_phone_number", get("sms_phone_number")); | |||
| form.setValue("order_date_from", dateParse(get("order_date_from"))); | |||
| form.setValue("order_date_to", dateParse(get("order_date_to"))); | |||
| } | |||
| }, [initialized, condition]); | |||
| // Fetchアクション | |||
| useEffect(() => { | |||
| if (initialized) { | |||
| fetch(condition); | |||
| } | |||
| }, [condition, initialized]); | |||
| // バックドロップ | |||
| useEffect(() => { | |||
| setShowBackDrop(sending); | |||
| }, [sending]); | |||
| useEffect(() => { | |||
| addCondition(form.getValues()); | |||
| }, [includeDone, orderDateFrom, orderDateTo]); | |||
| return ( | |||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||
| <Box sx={{ p: 1, m: 1 }}> | |||
| <Grid container spacing={2}> | |||
| <Grid item xs={3} lg={2}> | |||
| <Stack> | |||
| <Typography>担当者</Typography> | |||
| <Stack direction="row"> | |||
| <RHFTextField | |||
| name="handler_name" | |||
| fullWidth={false} | |||
| onBlur={handleBlur} | |||
| /> | |||
| <Box> | |||
| <Tooltip title="私が担当" placement="top"> | |||
| <IconButton aria-label="私が担当" onClick={setMyName}> | |||
| <AccountCircleIcon /> | |||
| </IconButton> | |||
| </Tooltip> | |||
| </Box> | |||
| </Stack> | |||
| </Stack> | |||
| </Grid> | |||
| <Grid item xs={3} lg={2}> | |||
| <Stack> | |||
| <Typography>ステータス</Typography> | |||
| <RHFTextField name="status" onBlur={handleBlur} /> | |||
| </Stack> | |||
| </Grid> | |||
| <Grid item> | |||
| <Stack> | |||
| <Typography>完了を含む</Typography> | |||
| <RHFCheckbox name="include_done" label="" sx={{ mx: "auto" }} /> | |||
| </Stack> | |||
| </Grid> | |||
| </Grid> | |||
| <Grid container spacing={2} mt={1}> | |||
| <Grid item xs={3} lg={2}> | |||
| <Stack> | |||
| <Typography>運営会社</Typography> | |||
| <RHFTextField name="customer_name" onBlur={handleBlur} /> | |||
| </Stack> | |||
| </Grid> | |||
| <Grid item xs={3} lg={2}> | |||
| <Stack> | |||
| <Typography>駐車場名</Typography> | |||
| <RHFTextField name="parking_name" onBlur={handleBlur} /> | |||
| </Stack> | |||
| </Grid> | |||
| {/* <Grid item xs={3} lg={2}> | |||
| <Stack> | |||
| <Typography>ああ</Typography> | |||
| <RHFSelectMuiliple | |||
| name="aa" | |||
| options={[ | |||
| { value: "aaaaaaaaaaaaaaaaa" }, | |||
| { value: "bbbbbbbbbbbbbbbbbbbb" }, | |||
| ]} | |||
| /> | |||
| </Stack> | |||
| </Grid> */} | |||
| <Grid item xs={3} lg={2}> | |||
| <Typography>電話番号</Typography> | |||
| <RHFTextField name="sms_phone_number" /> | |||
| </Grid> | |||
| <Grid item xs={3} lg={2}> | |||
| <Typography>受付時刻</Typography> | |||
| <Button | |||
| variant={isSetOrderDateConditions ? "contained" : "text"} | |||
| color={isSetOrderDateConditions ? "secondary" : "info"} | |||
| onClick={() => { | |||
| setOpenDateDialog(true); | |||
| }} | |||
| > | |||
| {isSetOrderDateConditions ? "設定あり" : "設定"} | |||
| </Button> | |||
| {isSetOrderDateConditions && ( | |||
| <IconButton | |||
| onClick={() => { | |||
| form.setValue("order_date_from", null); | |||
| form.setValue("order_date_to", null); | |||
| }} | |||
| > | |||
| <Clear /> | |||
| </IconButton> | |||
| )} | |||
| </Grid> | |||
| </Grid> | |||
| </Box> | |||
| <Dialog | |||
| open={openDateDialog} | |||
| onClose={() => { | |||
| setOpenDateDialog(false); | |||
| }} | |||
| > | |||
| <DialogTitle>受付時刻検索条件</DialogTitle> | |||
| <DialogContent> | |||
| <Stack direction="row" spacing={2}> | |||
| <Box> | |||
| <Typography>From</Typography> | |||
| <RHFDatePicker name="order_date_from" /> | |||
| </Box> | |||
| <Box> | |||
| <Typography> </Typography> | |||
| <Typography>~</Typography> | |||
| </Box> | |||
| <Box> | |||
| <Typography>To</Typography> | |||
| <RHFDatePicker name="order_date_to" /> | |||
| </Box> | |||
| </Stack> | |||
| </DialogContent> | |||
| <DialogActions> | |||
| <Button | |||
| onClick={() => { | |||
| setOpenDateDialog(false); | |||
| }} | |||
| > | |||
| 決定 | |||
| </Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| </FormProvider> | |||
| ); | |||
| } | |||
| function TableBox({ table }: CommonProps) { | |||
| const TABLE_HEAD = [ | |||
| { id: "order_datetime", label: "受付時刻", align: "left" }, | |||
| { id: "customer_name", label: "運営会社名", align: "left" }, | |||
| { id: "parking_name", label: "駐車場名", align: "left" }, | |||
| { id: "status_name", label: "ステータス", align: "left" }, | |||
| { id: "handler_name", label: "担当者", 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: ReceiptIssuingOrderHTCustom; | |||
| }; | |||
| function Row({ data }: RowProps) { | |||
| const { navigateWhenChanged } = useNavigateCustom(); | |||
| const handleClick = () => { | |||
| navigateWhenChanged( | |||
| getPath( | |||
| PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL_CUSTOM_HELLO_TECHNO, | |||
| { | |||
| query: { | |||
| id: data.id, | |||
| }, | |||
| } | |||
| ) | |||
| ); | |||
| }; | |||
| return ( | |||
| <TableRow hover sx={{ cursor: "pointer" }} onClick={handleClick}> | |||
| <TableCell>{data.order_datetime}</TableCell> | |||
| <TableCell>{data.customer_name}</TableCell> | |||
| <TableCell>{data.parking_name}</TableCell> | |||
| <TableCell>{data.status_name}</TableCell> | |||
| <TableCell>{data.handler_name}</TableCell> | |||
| </TableRow> | |||
| ); | |||
| } | |||
| @@ -1,255 +0,0 @@ | |||
| import { | |||
| Box, | |||
| Grid, | |||
| Table, | |||
| TableBody, | |||
| TableCell, | |||
| TableContainer, | |||
| TablePagination, | |||
| TableRow, | |||
| } from "@mui/material"; | |||
| import { Dictionary } from "@types"; | |||
| import { | |||
| ReceiptIssuingOrderHTCustom, | |||
| getReceiptIssuingOrders, | |||
| } from "api/custom/hello-techno/receipt-issuing-order"; | |||
| import { PageID, TabID } from "codes/page"; | |||
| import { FormProvider, RHFTextField } from "components/hook-form"; | |||
| import { TableHeadCustom } from "components/table"; | |||
| import { SearchConditionContextProvider } from "contexts/SearchConditionContext"; | |||
| import useAPICall from "hooks/useAPICall"; | |||
| import useBackDrop from "hooks/useBackDrop"; | |||
| import useDashboard from "hooks/useDashBoard"; | |||
| import useNavigateCustom from "hooks/useNavigateCustom"; | |||
| import useSearchConditionContext from "hooks/useSearchConditionContext"; | |||
| import useTable, { UseTableReturn } from "hooks/useTable"; | |||
| import { useEffect } from "react"; | |||
| import { useForm } from "react-hook-form"; | |||
| import { getPath } from "routes/path"; | |||
| export default function ReceiptIssuingOrderList() { | |||
| const { setHeaderTitle, setTabs } = useDashboard( | |||
| PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST, | |||
| TabID.NONE | |||
| ); | |||
| useEffect(() => { | |||
| setHeaderTitle("領収証発行依頼一覧"); | |||
| setTabs(null); | |||
| }, []); | |||
| return ( | |||
| <SearchConditionContextProvider> | |||
| <Page /> | |||
| </SearchConditionContextProvider> | |||
| ); | |||
| } | |||
| function Page() { | |||
| const table = useTable<ReceiptIssuingOrderHTCustom>(); | |||
| return ( | |||
| <Box> | |||
| <SearchBox table={table} /> | |||
| <TableBox table={table} /> | |||
| </Box> | |||
| ); | |||
| } | |||
| type FormProps = { | |||
| customer_name: string; | |||
| parking_name: string; | |||
| }; | |||
| type CommonProps = { | |||
| table: UseTableReturn<ReceiptIssuingOrderHTCustom>; | |||
| }; | |||
| function SearchBox({ table }: CommonProps) { | |||
| const { | |||
| condition, | |||
| initialized, | |||
| get, | |||
| addCondition: add, | |||
| } = useSearchConditionContext(); | |||
| const { setShowBackDrop } = useBackDrop(); | |||
| const form = useForm<FormProps>({ | |||
| defaultValues: { | |||
| customer_name: "", | |||
| parking_name: "", | |||
| }, | |||
| }); | |||
| const { | |||
| callAPI: calGetReceiptIssuingOrders, | |||
| makeSendData, | |||
| sending, | |||
| } = useAPICall({ | |||
| apiMethod: getReceiptIssuingOrders, | |||
| form, | |||
| onSuccess: (res) => { | |||
| table.setRowData(res.data.records); | |||
| }, | |||
| }); | |||
| const handleSubmit = async (data: FormProps) => { | |||
| addCondition(data); | |||
| }; | |||
| const addCondition = (data: FormProps) => { | |||
| add({ ...data }); | |||
| }; | |||
| const handleBlur = () => { | |||
| addCondition(form.getValues()); | |||
| }; | |||
| const fetch = async (data: Dictionary) => { | |||
| const sendData = makeSendData({ | |||
| ...data, | |||
| }); | |||
| calGetReceiptIssuingOrders(sendData); | |||
| }; | |||
| // 初期値設定 | |||
| useEffect(() => { | |||
| if (initialized) { | |||
| form.setValue("customer_name", get("customer_name")); | |||
| form.setValue("parking_name", get("parking_name")); | |||
| } | |||
| }, [initialized, condition]); | |||
| // Fetchアクション | |||
| useEffect(() => { | |||
| if (initialized) { | |||
| fetch(condition); | |||
| } | |||
| }, [condition, initialized]); | |||
| // バックドロップ | |||
| useEffect(() => { | |||
| setShowBackDrop(sending); | |||
| }, [sending]); | |||
| return ( | |||
| <FormProvider methods={form} onSubmit={form.handleSubmit(handleSubmit)}> | |||
| <Box sx={{ p: 1, m: 1 }}> | |||
| <Grid container spacing={1}> | |||
| <Grid item xs={3}> | |||
| <RHFTextField | |||
| label="運営会社名" | |||
| name="customer_name" | |||
| fullWidth | |||
| size="small" | |||
| onBlur={handleBlur} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={3}> | |||
| <RHFTextField | |||
| label="駐車場名" | |||
| name="parking_name" | |||
| fullWidth | |||
| size="small" | |||
| onBlur={handleBlur} | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| </Box> | |||
| </FormProvider> | |||
| ); | |||
| } | |||
| function TableBox({ table }: CommonProps) { | |||
| const TABLE_HEAD = [ | |||
| { id: "order_datetime", label: "受付時刻", align: "left" }, | |||
| { id: "customer_name", label: "運営会社名", align: "left" }, | |||
| { id: "parking_name", label: "駐車場名", align: "left" }, | |||
| { id: "status", label: "ステータス", align: "left" }, | |||
| { id: "handler_name", label: "担当者", 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: ReceiptIssuingOrderHTCustom; | |||
| }; | |||
| function Row({ data }: RowProps) { | |||
| const { navigateWhenChanged } = useNavigateCustom(); | |||
| const handleClick = () => { | |||
| navigateWhenChanged( | |||
| getPath(PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL, { | |||
| query: { | |||
| id: data.id, | |||
| }, | |||
| }) | |||
| ); | |||
| }; | |||
| return ( | |||
| <TableRow hover sx={{ cursor: "pointer" }} onClick={handleClick}> | |||
| <TableCell>{data.order_datetime}</TableCell> | |||
| <TableCell>{data.customer_name}</TableCell> | |||
| <TableCell>{data.parking_name}</TableCell> | |||
| <TableCell>{data.status_name}</TableCell> | |||
| <TableCell>{data.handler_name}</TableCell> | |||
| </TableRow> | |||
| ); | |||
| } | |||
| @@ -11,9 +11,18 @@ export const AUTH = { | |||
| [P.DASHBOARD_CONTRACT_LIST]: setAuth("ge", R.SUPER_ADMIN), | |||
| [P.DASHBOARD_CONTRACT_DETAIL]: setAuth("ge", R.SUPER_ADMIN), | |||
| [P.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE]: setAuth("ge", R.NORMAL_ADMIN), | |||
| [P.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST]: setAuth("ge", R.NORMAL_ADMIN), | |||
| [P.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL]: setAuth("ge", R.NORMAL_ADMIN), | |||
| [P.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE_CUSTOM_HELLO_TECHNO]: setAuth( | |||
| "ge", | |||
| R.NORMAL_ADMIN | |||
| ), | |||
| [P.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST_CUSTOM_HELLO_TECHNO]: setAuth( | |||
| "ge", | |||
| R.NORMAL_ADMIN | |||
| ), | |||
| [P.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL_CUSTOM_HELLO_TECHNO]: setAuth( | |||
| "ge", | |||
| R.NORMAL_ADMIN | |||
| ), | |||
| [P.PAGE_403]: setAuth("all"), | |||
| [P.PAGE_404]: setAuth("all"), | |||
| @@ -69,18 +69,18 @@ const DashboardRoutes = (): RouteObject => { | |||
| target: UserRole.SUPER_ADMIN, | |||
| }, | |||
| { | |||
| pageId: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE, | |||
| element: <ReceiptIssuingOrderCreate />, | |||
| pageId: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE_CUSTOM_HELLO_TECHNO, | |||
| element: <ReceiptIssuingOrderCreateHelloTechno />, | |||
| target: UserRole.NORMAL_ADMIN, | |||
| }, | |||
| { | |||
| pageId: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST, | |||
| element: <ReceiptIssuingOrderList />, | |||
| pageId: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST_CUSTOM_HELLO_TECHNO, | |||
| element: <ReceiptIssuingOrderListHelloTechno />, | |||
| target: UserRole.NORMAL_ADMIN, | |||
| }, | |||
| { | |||
| pageId: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL, | |||
| element: <ReceiptIssuingOrderDetail />, | |||
| pageId: PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL_CUSTOM_HELLO_TECHNO, | |||
| element: <ReceiptIssuingOrderDetailHelloTechno />, | |||
| target: UserRole.NORMAL_ADMIN, | |||
| }, | |||
| ]; | |||
| @@ -137,14 +137,23 @@ const ContractDetail = Loadable( | |||
| lazy(() => import("pages/dashboard/contract/detail")) | |||
| ); | |||
| // 領収証発行依頼 | |||
| const ReceiptIssuingOrderCreate = Loadable( | |||
| lazy(() => import("pages/dashboard/receipt-issuing-order/create")) | |||
| const ReceiptIssuingOrderCreateHelloTechno = Loadable( | |||
| lazy( | |||
| () => | |||
| import("pages/dashboard/receipt-issuing-order/custom/hello-techno/create") | |||
| ) | |||
| ); | |||
| const ReceiptIssuingOrderList = Loadable( | |||
| lazy(() => import("pages/dashboard/receipt-issuing-order/list")) | |||
| const ReceiptIssuingOrderListHelloTechno = Loadable( | |||
| lazy( | |||
| () => | |||
| import("pages/dashboard/receipt-issuing-order/custom/hello-techno/list") | |||
| ) | |||
| ); | |||
| const ReceiptIssuingOrderDetail = Loadable( | |||
| lazy(() => import("pages/dashboard/receipt-issuing-order/detail")) | |||
| const ReceiptIssuingOrderDetailHelloTechno = Loadable( | |||
| lazy( | |||
| () => | |||
| import("pages/dashboard/receipt-issuing-order/custom/hello-techno/detail") | |||
| ) | |||
| ); | |||
| // その他 --------------------------------- | |||
| @@ -49,12 +49,15 @@ const PATHS = { | |||
| [makePathKey(PageID.DASHBOARD_CONTRACT_DETAIL)]: "/dashboard/contract/detail", | |||
| // 領収証発行依頼関連 | |||
| [makePathKey(PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE)]: | |||
| "/dashboard/receipt-issusing-order/create", | |||
| [makePathKey(PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST)]: | |||
| "/dashboard/receipt-issusing-order/list/:page", | |||
| [makePathKey(PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL)]: | |||
| "/dashboard/receipt-issusing-order/detail/:id", | |||
| [makePathKey( | |||
| PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_CREATE_CUSTOM_HELLO_TECHNO | |||
| )]: "/dashboard/receipt-issusing-order/create", | |||
| [makePathKey( | |||
| PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_LIST_CUSTOM_HELLO_TECHNO | |||
| )]: "/dashboard/receipt-issusing-order/list/:page", | |||
| [makePathKey( | |||
| PageID.DASHBOARD_RECEIPT_ISSUING_ORDER_DETAIL_CUSTOM_HELLO_TECHNO | |||
| )]: "/dashboard/receipt-issusing-order/detail/:id", | |||
| // その他 | |||
| [makePathKey(PageID.PAGE_403)]: "403", | |||
| @@ -1,17 +1,20 @@ | |||
| import { format, parseISO } from 'date-fns'; | |||
| 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"; | |||
| type Input = Date | string | null | undefined; | |||
| export const formatDateStr = (source: Input) => { | |||
| return formatToStr(source, 'yyyy/MM/dd'); | |||
| return formatToStr(source, DEFAULT_DATE_FORMAT); | |||
| }; | |||
| export const formatDateTimeStr = (source: Date | string | null | undefined) => { | |||
| return formatToStr(source, 'yyyy/MM/dd HH:mm:ss'); | |||
| return formatToStr(source, DEFAULT_DATET_TIME_FORMAT); | |||
| }; | |||
| const formatToStr = (source: Input, formatStr: string) => { | |||
| if (source === null || source === undefined) return ''; | |||
| if (source === null || source === undefined) return ""; | |||
| if (source instanceof Date) return format(source, formatStr); | |||
| return format(parseISO(source), formatStr); | |||
| }; | |||
| @@ -22,3 +25,22 @@ export const now = () => { | |||
| export const nowStr = (): string => { | |||
| return formatDateTimeStr(now()); | |||
| }; | |||
| export const dateParse = (source: Input): Date | null => { | |||
| return parseFromFormat(source, DEFAULT_DATE_FORMAT); | |||
| }; | |||
| export const dateTimeParse = (source: Input): Date | null => { | |||
| return parseFromFormat(source, DEFAULT_DATET_TIME_FORMAT); | |||
| }; | |||
| const parseFromFormat = (source: Input, format: string): Date | null => { | |||
| if (source === null || source === undefined) return null; | |||
| if (source instanceof Date) return source; | |||
| const ret = parse(source, format, new Date()); | |||
| if (isValid(ret)) { | |||
| return ret; | |||
| } | |||
| return null; | |||
| }; | |||