I've made a custom filter for MUI's datagrid, the filter has two select's which allow you to filter by the column and filter type. The selects are quite big and endup outside the modal, when clicking on an option the whole modal closes, how can I prevent this from happening?
I've used this tutorial - Detect click outside React component to detect clicks outside the filter.
The code below shows the filter and I've also made an codesandbox example here - https://codesandbox.io/s/awesome-panka-g92vhn?file=/src/DataGridCustomFilter.js:0-6708
any help would be appreciated
import React, { useState, useEffect, useRef } from "react";
import {
Button,
Stack,
FormControl,
InputLabel,
Select,
MenuItem,
Paper,
Grid,
IconButton,
TextField,
ClickAwayListener
} from "#material-ui/core";
import FilterListIcon from "#mui/icons-material/FilterList";
import AddIcon from "#mui/icons-material/Add";
import CloseIcon from "#mui/icons-material/Close";
import { useForm, useFieldArray, Controller } from "react-hook-form";
import { columns } from "./columns";
const filterTypes = {
string: ["contains", "equals", "starts with", "ends with", "is any of"],
int: ["contains", "equals", "less than", "greater than"]
};
function FilterRow({
len,
setOpen,
field,
control,
columns,
index,
handleRemoveFilter
}) {
return (
<Grid container spacing={0}>
<Grid
item
md={1}
style={{
display: "flex",
alignSelf: "flex-end",
alignItems: "center"
}}
>
<IconButton
size="small"
onClick={() => {
if (len === 1) {
setOpen(false);
} else {
console.log(index, "---");
handleRemoveFilter(index);
}
}}
>
<CloseIcon style={{ fontSize: "20px" }} />
</IconButton>
</Grid>
<Grid item md={4}>
<Controller
name={`filterForm.${index}.column`}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl variant="standard" sx={{ width: "100%" }}>
<InputLabel>Column</InputLabel>
<Select
value={value}
onChange={onChange}
label="Column"
defaultValue=""
>
{columns.map((a) => {
return a.exclude_filter === true ? null : (
<MenuItem value={a.headerName}>{a.headerName}</MenuItem>
);
})}
</Select>
</FormControl>
)}
/>
</Grid>
<Grid item md={3}>
<Controller
name={`filterForm.${index}.filter`}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl variant="standard" sx={{ width: "100%" }}>
<InputLabel>Filter</InputLabel>
<Select
value={value}
onChange={onChange}
label="Filter"
defaultValue=""
>
{filterTypes.string.map((a) => {
return <MenuItem value={a}>{a}</MenuItem>;
})}
</Select>
</FormControl>
)}
/>
</Grid>
<Grid item md={4}>
<Controller
name={`filterForm.${index}.value`}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl>
<TextField
onChange={onChange}
value={value}
label="Value"
variant="standard"
/>
</FormControl>
)}
/>
{/* )} */}
</Grid>
</Grid>
);
}
function DataGridCustomFilter() {
const { control, handleSubmit } = useForm();
const { fields, append, remove } = useFieldArray({
control,
name: "filterForm"
});
const [open, setOpen] = useState(false);
const onSubmit = (data) => {};
useEffect(() => {
if (fields.length === 0) {
append({
column: "ID",
filter: filterTypes.string[0],
value: ""
});
}
}, [fields]);
const [clickedOutside, setClickedOutside] = useState(false);
const myRef = useRef();
const handleClickOutside = (e) => {
if (myRef.current && !myRef.current.contains(e.target)) {
setClickedOutside(true);
setOpen(!open);
}
};
useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
});
return (
<>
<Button
startIcon={<FilterListIcon />}
size="small"
onClick={() => {
setOpen(!open);
}}
// disabled={isDisabled}
>
FILTERS
</Button>
{open ? (
<div ref={myRef}>
<Paper
style={{
width: 550,
padding: 10,
zIndex: 1300,
position: "absolute",
inset: "0px auto auto 0px",
margin: 0,
display: "block"
// transform: "translate3d(160.556px, 252.222px, 0px)",
}}
variant="elevation"
elevation={5}
>
<form onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={0.5}>
<div style={{ maxHeight: 210, overflow: "scroll" }}>
{fields.map((field, index) => {
return (
<div style={{ paddingBottom: 5 }}>
<FilterRow
len={fields.length}
control={control}
setOpen={setOpen}
field={field}
columns={columns}
handleRemoveFilter={() => remove(index)}
{...{ control, index, field }}
// handleClickAway={handleClickAway}
/>
</div>
);
})}
</div>
<div style={{ marginTop: 10, paddingLeft: 40 }}>
<Stack direction="row" spacing={1}>
<Button size="small" startIcon={<AddIcon />}>
ADD FILTER
</Button>
<Button size="small" type="submit">
{fields.length > 1 ? "APPLY FILTERS" : "APPLY FILTER"}
</Button>
</Stack>
</div>
</Stack>
</form>
</Paper>
</div>
) : null}
</>
);
}
export default DataGridCustomFilter;
So far I've tried MUI's ClickAwayListener and the example above, both seem to give the same result
DataGrid component uses NativeSelect. I have checked your codesandbox and tried replacing Select to NativeSelect and MenuItem to Option. filter is working properly. below is sample code for update.
...
<NativeSelect
value={value}
onChange={onChange}
label="Column"
defaultValue=""
>
{columns.map((a) => {
return a.exclude_filter === true ? null : (
<option value={a.headerName}>{a.headerName}</option >
);
})}
</NativeSelect>
...
Related
I'm new with react form, I'm using Material UI and Controller Component, and I'm sending a React Hook form request but not getting any response, form's (HTML form tag) onSubmit event is occurring but handleSubmit is not working I have one more form like that it is working perfectly fine but I don't know why it's not working, can anybody please help me with that
import { Button, useTheme, Grid, TextField, Typography } from '#mui/material';
import WSSelectBox from 'components/common/WSSelect';
import React, { FC } from 'react';
import { Controller, FormProvider, useForm } from 'react-hook-form';
import { isMobile } from 'utils/mediaqueries';
import CheckBoxButton from '../CheckBoxButton';
import { formTypes } from './utils';
import { yupResolver } from '#hookform/resolvers/yup';
import * as yup from 'yup';
import { FormPropTypes } from './type';
const schema = yup
.object()
.shape({
name: yup.string().required(),
residentialCountry: yup.number().required(),
initiator: yup.string().required(),
program: yup.string().required(),
list: yup.string().required(),
})
.required();
const Entities: FC<FormPropTypes> = ({
handleClick,
selectedType,
handleSearch,
}) => {
const theme = useTheme();
const methods = useForm({
resolver: yupResolver(schema),
});
const { handleSubmit, control } = methods;
const onSubmit = (data) => {
// Backend is not done yet
console.log('DataData', data);
};
return (
<FormProvider {...methods}>
<form
onSubmit={(e) => {
e.preventDefault();
console.log('skdjskd', 'Line no 44');
handleSubmit(onSubmit);
}}
>
<Grid container spacing={'16px'}>
{formTypes.map((type) => (
<Grid item xs={6} sm={'auto'} md={'auto'} key={type}>
<CheckBoxButton
key={type}
name={'type'}
value={type}
handleClick={handleClick}
active={type == selectedType ? true : false}
/>
</Grid>
))}
</Grid>
<Grid container pt={4} columnSpacing="20px" rowSpacing={'16px'}>
<Grid item xs={12} sm={12} md={6}>
<Controller
name="name"
render={({
// eslint-disable-next-line #typescript-eslint/no-unused-vars
field: { value, ...otherFieldProps },
fieldState,
}) => (
<TextField
fullWidth
id="sanctions-form-name"
label="Name"
variant="outlined"
helperText={
<p
style={{
position: 'relative',
left: -13,
fontSize: 12,
}}
>
Try to enter the full name or part of it. It is possible
to use original language or the Latin alphabet.
</p>
}
required
error={!!fieldState.error}
{...otherFieldProps}
/>
)}
/>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<WSSelectBox
id="sanctions-form-residential-country"
label="Residential country"
name={'residentialCountry'}
data={['Ten', 'Twenty']}
handleSelect={() => {}}
type={'text'}
control={control}
/>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<WSSelectBox
data={['Ten', 'Twenty']}
handleSelect={() => {}}
id="sanctions-form-initiator"
name={'initiator'}
label="Initiator"
type="text"
control={control}
/>
</Grid>
<Grid item xs={12} sm={6} md={6}>
<WSSelectBox
id="sanctions-form-program"
label="Program"
data={['Ten', 'Twenty']}
handleSelect={() => {}}
type="text"
name={'program'}
control={control}
/>
</Grid>
<Grid item xs={12} sm={6} md={6}>
<WSSelectBox
id="sanctions-form-list"
label="List"
data={['Ten', 'Twenty']}
handleSelect={() => {}}
type={'text'}
name={'list'}
control={control}
/>
</Grid>
</Grid>
<Grid
container
pt={{ xs: '20px', md: '30px' }}
rowSpacing="10px"
alignItems="center"
sx={{
[isMobile(theme)]: {
textAlign: 'center',
},
}}
justifyContent="left"
>
<Grid item xs={6} sm={'auto'}>
<Button
variant="contained"
color="primary"
sx={{
minWidth: '200px',
['#media (max-width:460px)']: {
minWidth: '120px',
},
}}
type="submit"
>
Search
</Button>
</Grid>
<Grid item xs={6} sm={'auto'}>
<Button
sx={{
minWidth: '200px',
color: '#3883FA',
['#media (max-width:460px)']: {
minWidth: '120px',
},
}}
type="submit"
>
Reset
</Button>
</Grid>
</Grid>
</form>
<Typography
variant="body2"
sx={{ fontSize: 17, color: '#121E3D', marginTop: '20px' }}
>
Use filters to start your search.
</Typography>
</FormProvider>
);
};
export default Entities;
SELECT BOX
import { TextField, MenuItem, styled, Select } from '#mui/material';
import { Controller, useForm } from 'react-hook-form';
export type SelectBoxProps = {
label: string;
id: string;
data: Array<string>;
handleSelect: VoidFunction;
type: string;
Control: () => {};
};
const SelectBox = styled(TextField)`
& .MuiOutlinedInput-root:focus {
&.Mui-focused fieldset {
border-bottom: none !important;
}
}
`;
const WSSelectBox = ({
label,
id,
data,
handleSelect,
name,
type,
control,
}) => {
return (
<>
<Controller
name={name}
render={({ field }) => (
<SelectBox
defaultValue=""
fullWidth
autoComplete="off"
id={id}
type={type}
label={label}
variant="outlined"
required
select
{...field}
>
{data.map((opt) => (
<MenuItem key={opt} value={opt}>
{opt}
</MenuItem>
))}
</SelectBox>
)}
control={control}
/>
</>
);
};
export default WSSelectBox;
I have this project and as it is clear in the postman, I have a request and through the request I must send the invoiceId to the invoice and the user ID must be sent in addition to a message, but the real problem is that I did not know how to get the user ID, the meaning is that when I printed “invoiceID” , "UserID "and "Message", the invoiceID and the message have a value, but UserID is worthless without value "undefined", and the reason is that I have list of the Users, and I must choose one user and then I want to pass this userID for the user that i was choosen and pass it in the "assignToUser" function, and i don't know how to do that.
And in the network I have these errors:
and in Network i have this errors:
enter image description here
how can i solve my problem?
invoiceSlice.js:
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
import FuseUtils from "#fuse/utils";
import { getInvoices } from "./invoicesSlice";
export const assignToUser = createAsyncThunk(
"invoicesApp/invoice/assignToUser",
async ({ invoiceId, userId, message }, { dispatch }) => {
console.log("invoiceId, userId, message", invoiceId, userId, message);
const response = await axios
.post(`/invoices/flow/${invoiceId}/approve`, { userId, message })
.catch((error) => {
console.log("error response: ", error);
});
const data = await response.data.data;
console.log("approve invoices: ", data);
dispatch(getInvoices());
return data;
}
);
const invoiceSlice = createSlice({
name: "invoicesApp/invoice",
initialState: null,
reducers: {
resetInvoice: () => null,
newInvoice: {
reducer: (state, action) => action.payload,
prepare: (event) => ({
payload: {
invoice: "",
netAmount: 0,
taxNumber: 0,
grossAmount: 0,
dueDate: "",
issueDate: "",
},
}),
},
},
extraReducers: {
[assignToUser.fulfilled]: (state, action) => action.payload,
},
});
export const { newInvoice, resetInvoice } = invoiceSlice.actions;
export default invoiceSlice.reducer;
approveUser.js:
import { Fragment, useState } from "react";
import { ButtonGroup } from "#material-ui/core";
import React from "react";
import Button from "#material-ui/core/Button";
import Dialog from "#material-ui/core/Dialog";
import DialogActions from "#material-ui/core/DialogActions";
import DialogContent from "#material-ui/core/DialogContent";
import DialogContentText from "#material-ui/core/DialogContentText";
import DialogTitle from "#material-ui/core/DialogTitle";
import useMediaQuery from "#material-ui/core/useMediaQuery";
import { useTheme } from "#material-ui/core/styles";
import { useSnackbar } from "notistack";
import Slide from "#material-ui/core/Slide";
import {
rejectInvoice,
approveInvoice,
assignToUser,
} from "../../store/invoiceSlice";
import { useDispatch, useSelector } from "react-redux";
import TextField from "#mui/material/TextField";
import FlagIcon from "#mui/icons-material/Flag";
import { makeStyles } from "#material-ui/core/styles";
import Autocomplete from "#mui/material/Autocomplete";
import { getUsers } from "../../store/invoiceSlice";
import { useEffect } from "react";
const useStyles = makeStyles((theme) => ({
paper: { padding: "3rem", maxWidth: "990px", minWidth: "300px" },
textStyle: {
paddingLeft: "2rem",
},
formControl: {
margin: theme.spacing(1),
minWidth: 120,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
font: {
fontSize: "5rem",
},
}));
const GroupButttonApproveStatus = (id) => {
const [dialogOpen, setDialogOpen] = useState(false);
const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
const [assignToUserDialog, setAssignToUserDialog] = useState(false);
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
const dispatch = useDispatch();
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));
const AssignToUserFullScreen = useMediaQuery(theme.breakpoints.down("md"));
const classes = useStyles();
const [users, setUsers] = useState([]);
const [message, setMessage] = useState("");
useEffect(() => {
getUsers().then((response) => {
setUsers(response);
});
}, []);
// confirm
console.log("users: ", users);
const handleAssignToUserDialogOpen = () => {
setAssignToUserDialog(true);
};
const handleAssignToUserDialogClose = () => setAssignToUserDialog(false);
// end assign to user
const handleConfirmDialogClose = () => setConfirmDialogOpen(false);
const handleClickConfirmDialogOpen = () => {
setConfirmDialogOpen(true);
};
//end confirm
const handleDialogClose = () => setDialogOpen(false);
const handleClickOpen = () => {
setDialogOpen(true);
};
const handleClose = () => {
setDialogOpen(false);
};
const rejectInvoiceHandleClick = () => {
enqueueSnackbar(
"Invoice rejected successfully",
{ variant: "error" },
{
anchorOrigin: {
vertical: "top",
horizontal: "right",
},
},
{ TransitionComponent: Slide }
);
};
const approveInvoiceHandleClick = () => {
enqueueSnackbar(
"Invoice approved successfully",
{ variant: "success" },
{
anchorOrigin: {
vertical: "top",
horizontal: "right",
},
},
{ TransitionComponent: Slide }
);
};
return (
<Fragment>
<ButtonGroup size="large">
<Button
onClick={(ev) => {
handleClickConfirmDialogOpen();
}}
>
Approve
</Button>
<Button
onClick={(ev) => {
handleClickOpen();
}}
>
Reject
</Button>
<Button
onClick={(ev) => {
handleAssignToUserDialogOpen();
}}
>
Assign to User to approve
</Button>
</ButtonGroup>
{/* reject Dialog */}
<Dialog
classes={{ paper: classes.paper }}
maxWidth="sm"
fullScreen={fullScreen}
open={dialogOpen}
onClose={handleDialogClose}
>
<DialogTitle style={{ fontWeight: "bold" }}>Reject Invoice</DialogTitle>
<DialogContent>
<div
style={{
backgroundColor: "#F8F9FA",
borderRadius: 10,
padding: "3rem",
}}
>
<DialogContentText>
<FlagIcon
style={{ fontSize: 40, color: "#dc3c24", paddingRight: "1rem" }}
/>
Do you really want to reject this invoice ?
</DialogContentText>
<DialogContentText>
<FlagIcon
style={{ fontSize: 40, color: "#F8F9FA", paddingRight: "1rem" }}
/>
Keep in mind that once the invoice is rejected you won’t be able
to proceed with it.
</DialogContentText>
</div>
</DialogContent>
<DialogActions>
<div style={{ paddingRight: "1rem" }}>
<Button
onClick={handleClose}
style={{ color: "#dc3c24", fontWeight: 500 }}
autoFocus
>
Cancel
</Button>
<Button
onClick={(ev) => {
dispatch(rejectInvoice(id?.id));
rejectInvoiceHandleClick(ev);
handleClose();
}}
style={{ color: "#212529", fontWeight: 500 }}
color="primary"
autoFocus
>
Reject Invoice
</Button>
</div>
</DialogActions>
</Dialog>
{/* End reject Dialog */}
{/* Confirm Dialog */}
<Dialog
classes={{ paper: classes.paper }}
maxWidth="sm"
fullScreen={fullScreen}
open={confirmDialogOpen}
onClose={handleConfirmDialogClose}
>
<DialogTitle style={{ fontWeight: "bold" }}>
Approve Invoice
</DialogTitle>
<DialogContent>
<div
style={{
backgroundColor: "#F8F9FA",
borderRadius: 10,
padding: "3rem",
}}
>
<DialogContentText>Almost ready for payment !</DialogContentText>
<DialogContentText>
By confirming you mark this invoice ready for approval.
</DialogContentText>
</div>
</DialogContent>
<DialogActions>
<div style={{ paddingRight: "1rem" }}>
<Button
onClick={handleConfirmDialogClose}
style={{ color: "#dc3c24", fontWeight: 500 }}
autoFocus
>
Cancel
</Button>
<Button
onClick={(ev) => {
dispatch(approveInvoice(id?.id));
approveInvoiceHandleClick(ev);
handleConfirmDialogClose();
}}
style={{ color: "#212529", fontWeight: 500 }}
color="primary"
autoFocus
>
yes, Confirm
</Button>
</div>
</DialogActions>
</Dialog>
{/* End Confirm Dialog */}
{/* assign to user dialog */}
<Dialog
classes={{ paper: classes.paper }}
maxWidth="sm"
fullScreen={AssignToUserFullScreen}
open={assignToUserDialog}
onClose={handleAssignToUserDialogClose}
>
<DialogTitle style={{ fontWeight: "bold", fontSize: "3rem" }}>
Request approval
</DialogTitle>
<div
style={{
backgroundColor: "#F8F9FA",
borderRadius: 10,
padding: "2rem",
paddingLeft: "2rem",
}}
>
<DialogContentText style={{ fontWeight: 600 }}>
{" "}
<FlagIcon
style={{ fontSize: 40, color: "#aacc00", paddingRight: "1rem" }}
/>
Send an invoice approval request to a team member.
</DialogContentText>
<DialogContentText style={{ paddingLeft: 10 }}>
The assigned member will receive a notification asking them to
approve this invoice. Once they accept, payment is on the way!
</DialogContentText>
</div>
<DialogTitle>Assign a member to approve</DialogTitle>
<DialogContent>
<Autocomplete
id="combo-box-demo"
// value={users || ""}
options={users || []}
getOptionLabel={(option) => option.name || ""}
sx={{ width: 860 }}
renderInput={(params) => (
<TextField
{...params}
placeholder="Search Member"
fullWidth
InputProps={{ ...params.InputProps, style: { fontSize: 17 } }}
InputLabelProps={{ style: { fontSize: 17 } }}
/>
)}
/>
</DialogContent>
<DialogContent style={{ marginTop: "15rem" }}>
<form className={classes.root} noValidate autoComplete="off">
<TextField
value={message}
onChange={(e) => setMessage(e.target.value)}
id="outlined-basic"
variant="outlined"
placeholder="Add a message"
fullWidth
size="medium"
InputProps={{ style: { fontSize: 17 } }}
InputLabelProps={{ style: { fontSize: 17 } }}
/>
</form>
</DialogContent>
<DialogActions>
<div style={{ paddingRight: "1rem" }}>
<Button
onClick={handleAssignToUserDialogClose}
style={{ color: "#dc3c24", fontWeight: 500 }}
autoFocus
>
Cancel
</Button>
<Button
onClick={(ev) => {
dispatch(assignToUser(id?.id, users.id, message));
approveInvoiceHandleClick(ev);
handleAssignToUserDialogClose();
}}
style={{ color: "#212529", fontWeight: 500 }}
color="primary"
autoFocus
>
Assign to approve
</Button>
</div>
</DialogActions>
</Dialog>
</Fragment>
);
};
export default GroupButttonApproveStatus;
invoiceDetails.js:
import React from "react";
import { getInvoice } from "../../store/invoiceSlice";
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import Grid from "#material-ui/core/Grid";
import TextField from "#material-ui/core/TextField";
import moment from "moment";
import InputAdornment from "#material-ui/core/InputAdornment";
import TodayIcon from "#material-ui/icons/Today";
import { makeStyles } from "#material-ui/core/styles";
import RejectDialog from "./rejectDialog";
import GroupButton from "./groupButttonReviewStatus";
import GroupButttonReviewStatus from "./groupButttonReviewStatus";
import GroupButttonApproveStatus from "./groupButtonApproveStatus";
import GroupButttonPaymentStatus from "./groupButtonPaymentStatus";
import useMediaQuery from "#material-ui/core/useMediaQuery";
import { useTheme } from "#material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
root: {
"& > *": {
margin: theme.spacing(1),
},
},
input: {
display: "none",
},
button: {
margin: theme.spacing(1),
// padding: theme.spacing(4),
},
}));
const InvoiceDetails = () => {
const classes = useStyles();
const theme = useTheme();
const routeParams = useParams();
const [invoice, setInvoice] = useState([]);
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const [selectedIndex, setSelectedIndex] = React.useState(1);
const breakpoint = useMediaQuery(theme.breakpoints.down("sm"));
// const defaultLayoutPluginInstance = defaultLayoutPlugin();
useEffect(() => {
getInvoice(routeParams).then((response) => {
setInvoice(response);
});
}, []);
const handleClick = () => {
console.info(`You clicked ${options[selectedIndex]}`);
};
const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setOpen(false);
};
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};
const handleClose = (event) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
}
setOpen(false);
};
console.log("invoice url: ", invoice?.file?.url);
console.log("invoice tara : ", invoice);
const statusGropButton = (status, id) => {
switch (status) {
case "review_pending":
return <GroupButttonReviewStatus id={id} />;
case "approval_pending":
return <GroupButttonApproveStatus id={id} />;
case "payment_pending":
return <GroupButttonPaymentStatus id={id} />;
case "rejected":
return <GroupButton id={id} />;
default:
return;
}
};
return (
<>
<Grid container>
<Grid item xs={7} sm={7}>
{/* pdf viewer */}
<object
// data={invoice?.file?.url}
data="https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea%20Brochure.pdf"
type="application/pdf"
width="100%"
height="100%"
>
<p>
Alternative text - include a link{" "}
<a href="https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea%20Brochure.pdf">
to the PDF!
</a>
</p>
</object>
</Grid>
<Grid item xs={5} sm={5} style={{ padding: "4rem" }}>
<Grid item>
<h1 style={{ fontWeight: "bold" }}>Invoice Details</h1>
</Grid>
<Grid item style={{ marginTop: "3rem", marginBottom: "2rem" }}>
<Grid item style={{ marginBottom: 10 }}>
<h3>From</h3>
</Grid>
<Grid item>
<h3>{invoice?.submittedBy?.name || ""}</h3>
</Grid>
<Grid item>
<h3>{invoice?.submittedBy?.email || ""}</h3>
</Grid>
</Grid>
<Grid item>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Invoice ID</h3>
</Grid>
<Grid item xs={12} sm={12}>
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={invoice.id || ""}
variant="outlined"
fullWidth
/>
</Grid>
</Grid>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Issue Date</h3>
</Grid>
<Grid item xs={12} sm={12}>
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={
moment(moment.utc(invoice.issueDate).toDate())
.local()
.format("YYYY-MM-DD HH:mm:ss") || ""
}
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment position="start">
<TodayIcon />
</InputAdornment>
),
}}
fullWidth
/>
</Grid>
</Grid>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Due Date</h3>
</Grid>
<Grid item xs={12} sm={12}>
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={
moment(moment.utc(invoice.dueDate).toDate())
.local()
.format("YYYY-MM-DD HH:mm:ss") || ""
}
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment position="start">
<TodayIcon />
</InputAdornment>
),
}}
fullWidth
/>
</Grid>
</Grid>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Net Amount</h3>
</Grid>
<Grid item xs={12} sm={12}>
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={invoice.netAmount || ""}
variant="outlined"
fullWidth
/>
</Grid>
</Grid>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Tax Number</h3>
</Grid>
<Grid item xs={12} sm={12}>
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={invoice.taxNumber || ""}
variant="outlined"
fullWidth
/>
</Grid>
</Grid>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Gross Amount</h3>
</Grid>
<Grid item xs={12} sm={12}>
<TextField
className="mt-8 mb-16"
// label="Size"
id="outlined-size-normal"
value={invoice.grossAmount || ""}
variant="outlined"
fullWidth
/>
</Grid>
</Grid>
<Grid
container
direction="row"
justifyContent="center"
alignItems="center"
style={{ marginTop: "3rem" }}
>
<Grid item>{statusGropButton(invoice.status, invoice?.id)}</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
</>
);
};
export default InvoiceDetails;
and this code:
useEffect(() => {
getUsers().then((response) => {
setUsers(response);
});
}, []);
return:
Based on your code I observed this.
You are already passing invoice?.id in statusGropButton(invoice.status, invoice?.id) method.
You are again expecting id from the input as id?.id in assignToUser(id?.id, users.id, message). Why?
Just assuming, because of that the invoiceId is going as undefined. Try by passing just id instead of id?.id while calling the api functions.
You are opening a Dialogue by using assignToUserDialog flag. While submitting the form, you are doing dispatch(assignToUser(id?.id, users.id, message)); in which users is an array. That is the reason you are getting undefined. I would suggest you maintain the specific user information for which the dialogue is opened and send that specifc user.id to the API call.
invoiceId undefined when send request ,you could check assignToUser
I am not able to focus on the first element of my form which is an Autocomplete component of the Material UI.
When opening the modal, the focus is on the 'unit' element, as shown in the image below:
It dont's work
I want the focus to be on the 'group' element, as shown below:
It work
Here is the Sandbox link:
https://codesandbox.io/s/awesome-bassi-zjp50?file=/src/MyForm.jsx:1422-1431
can anyone help me?
MyForm
import React from "react";
import { Formik, Form, Field } from "formik";
import * as Yup from "yup";
import { Grid } from "#material-ui/core";
import Input from "./components/Input";
import Autocomplete from "./components/Autocomplete";
const initialValues = {
id: 0,
group: {
value: 0,
label: ""
},
name: "",
unit: {
value: 0,
label: ""
}
};
const groupList = [
{
value: "1",
label: "Seeds"
},
{
value: "2",
label: "Fertilizers"
}
];
const unitList = [
{
value: "1",
label: "kg"
},
{
value: "2",
label: "t"
}
];
const validationSchema = Yup.object({
group: Yup.object().shape({
value: Yup.number().required().min(1).nullable()
}),
name: Yup.string().required().max(50),
unit: Yup.object().shape({
value: Yup.number().required().min(1).nullable()
})
});
export default function MyForm() {
function _onSubmit(fields, { props, setErrors, setSubmitting }) {
console.log(fields);
}
return (
<Formik
enableReinitialize={true}
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={_onSubmit}
>
{function Render({ errors, touched, isSubmitting, setFieldValue }) {
return (
<Form id="form">
<Grid container direction="row">
<Grid item xs={12}>
<Field
name="group"
autoFocus
component={Autocomplete}
label="Group"
options={groupList}
disabled={isSubmitting}
textFieldProps={{
fullWidth: true,
margin: "dense",
variant: "outlined"
}}
/>
</Grid>
<Grid item xs={12}>
<Field
name="name"
fullWidth
label="Name"
disabled={isSubmitting}
component={Input}
/>
</Grid>
<Grid item xs={12}>
<Field
name="unit"
component={Autocomplete}
label="Unit"
options={unitList}
disabled={isSubmitting}
textFieldProps={{
fullWidth: true,
margin: "dense",
variant: "outlined"
}}
/>
</Grid>
</Grid>
</Form>
);
}}
</Formik>
);
}
Popup Modal
import React from "react";
import {
Dialog,
DialogTitle,
DialogContent,
makeStyles,
Typography,
Divider,
DialogActions,
Button,
Grid,
Box
} from "#material-ui/core";
const useStyles = makeStyles((theme) => ({
dialogWrapper: {
padding: theme.spacing(2),
position: "absolute",
top: theme.spacing(5)
},
dialogTitle: {
paddingRight: 0
},
button: {
margin: theme.spacing(0.5),
textTransform: "none"
},
buttonsContainer: {
display: "flex",
flex: "0 0 auto",
justifyContent: "flex-end",
width: "100%",
height: "100%"
},
buttons: {
display: "flex"
},
loadingIcon: {
display: "flex",
alignItems: "center",
justifyContent: "center",
marginRight: theme.spacing(1)
}
}));
function Popup(props) {
const classes = useStyles();
const { title, children, openPopup, setOpenPopup, ...rest } = props;
return (
<Dialog
{...rest}
open={openPopup}
maxWidth="sm"
classes={{ paper: classes.dialogWrapper }}
>
<DialogTitle dividers="true" className={classes.dialogTitle}>
<div style={{ display: "flex" }}>
<Typography variant="h4" component="div" style={{ flexGrow: 1 }}>
{title}
</Typography>
</div>
</DialogTitle>
<Divider />
<DialogContent {...rest} direction="row">
{children}
</DialogContent>
<DialogActions {...rest}>
<Grid {...rest} container className={classes.buttonsContainer}>
<Box {...rest} className={classes.buttons}>
<Button
className={classes.button}
variant="contained"
color="primary"
type="submit"
form="form"
{...rest}
>
Save
</Button>
<Button
className={classes.button}
color="default"
fullWidth
onClick={() => {
setOpenPopup(false);
}}
{...rest}
>
Cancel
</Button>
</Box>
</Grid>
</DialogActions>
</Dialog>
);
}
export default Popup;
Autocomplete
import React from "react";
import { FieldProps, getIn } from "formik";
import { TextField, CircularProgress } from "#material-ui/core";
import MuiAutocomplete, {
createFilterOptions
} from "#material-ui/lab/Autocomplete";
const NewAutocomplete: React.FC<
FieldProps & {
label?: string,
options: Array<{ label: string, value: number }>
}
> = ({ textFieldProps, field, form, label, options, isLoading, ...props }) => {
const filterOptions = createFilterOptions({
matchFrom: "start",
limit: 500
});
const errorText =
getIn(form.touched, field.name) && getIn(form.errors, field.name);
const valueInit = [
{
value: 0,
label: ""
}
];
return (
<MuiAutocomplete
{...props}
{...field}
filterOptions={filterOptions}
options={[...valueInit, ...options]}
getOptionLabel={(option) => (option ? option.label : "")}
getOptionSelected={(option, value) => option.value === value?.value}
style={{ width: 300 }}
loading={isLoading}
value={field.value}
onChange={(e, value) => {
form.setFieldValue(field.name, value);
}}
renderInput={(props) => (
<>
<TextField
{...props}
{...textFieldProps}
label={label}
helperText={errorText?.value || errorText}
error={!!errorText}
autoFocus
InputProps={{
...props.InputProps,
endAdornment: (
<React.Fragment>
{isLoading ? (
<CircularProgress color="primary" size={20} />
) : null}
{props.InputProps.endAdornment}
</React.Fragment>
)
}}
/>
</>
)}
/>
);
};
export default NewAutocomplete;
Thanks for your help!
You're actually setting autoFocus to true on TextField by default inside your NewAutocomplete definition (src/components/Autocomplete.jsx:50 in your sandbox).
Once that line removed, you can then add autoFocus: true to textFieldProps value of your first Field in MyForm component.
See codesandbox example
I have a component in my react s application where i use ClickAwayListener from #mui/material/ClickAwayListener.
export default function MultipleSelectChip() {
const theme = useTheme();
const [personName, setPersonName] = React.useState([]);
const handleChange = (event) => {
const {
target: { value }
} = event;
setPersonName(
// On autofill we get a the stringified value.
typeof value === "string" ? value.split(",") : value
);
};
return (
<div>
{[1, 2, 3].map(() => {
return (
<div>
<ClickAwayListener onClickAway={() => console.log("click outside")}>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id="demo-multiple-chip-label">Chip</InputLabel>
<Select
labelId="demo-multiple-chip-label"
id="demo-multiple-chip"
multiple
value={personName}
onChange={handleChange}
input={
<OutlinedInput id="select-multiple-chip" label="Chip" />
}
renderValue={(selected) => (
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
{selected.map((value) => (
<Chip key={value} label={value} />
))}
</Box>
)}
MenuProps={MenuProps}
>
{names.map((name) => (
<MenuItem
key={name}
value={name}
style={getStyles(name, personName, theme)}
>
{name}
</MenuItem>
))}
</Select>
</FormControl>
</ClickAwayListener>
</div>
);
})}
</div>
);
}
I expect to trigger onClickAway function only when i click outside a dropdown. But when i click the dropdown plus when i select an option the function also is triggered and i don't understand why. Hw to get the expected behaviour when click outside one of the dropdown? NOTE: the functionality works if i don't use map(), meaning without many dropdowns. demo: https://codesandbox.io/s/multipleselectchip-material-demo-forked-n03vq?file=/demo.js:1080-2905
I use ant design in my application, where i use dynamic form. Users can add how many descendant forms they want.
<Form
name="dynamic_form_item"
{...formItemLayoutWithOutLabel}
onFinish={onFinish}
>
<Form.List name="names">
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item
{...(index === 0
? formItemLayout
: formItemLayoutWithOutLabel)}
label={index === 0 ? "Passengers" : ""}
required={false}
key={field.key}
>
<Form.Item
{...field}
validateTrigger={["onChange", "onBlur"]}
rules={[
{
required: true,
whitespace: true,
message:
"Please input passenger's name or delete this field."
}
]}
noStyle
>
<Input
placeholder="passenger name"
style={{ width: "60%" }}
/>
</Form.Item>
<InnerForm /> //my inner form
{fields.length > 1 ? (
<MinusCircleOutlined
className="dynamic-delete-button"
style={{ margin: "0 8px" }}
onClick={() => {
remove(field.name);
}}
/>
) : null}
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "60%" }}
>
<PlusOutlined /> Add field
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
The idea of the application is next:
User click on add field and appears an input where he can add a value, also appears another button with add inner field where user also can add another data.
Now the application works and when i save each form, i get the value, but also i get a warning: validateDOMNesting(...): <form> cannot appear as a descendant of <form>, which means that i can't use inner form inside the main how i understand. But how to solve this issue and to make disappears the warning?
demo: https://codesandbox.io/s/ecstatic-paper-0933k?file=/index.js:677-2823
SubForm.js. Removing the form tag gets rid of the warning. and also remove the inner submit button
import React from "react";
import { Form, Input, Button } from "antd";
import { PlusOutlined } from "#ant-design/icons";
const SubForm = props => {
return (
<>
<Form.List name={[props.fieldKey, "inner"]}>
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item
// name={"aar"}
{...field}
name={[field.name, "innerName"]}
fieldKey={[field.fieldKey, "innerName"]}
key={index}
noStyle
>
<Input
placeholder="passenger name"
style={{ width: "60%" }}
/>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "60%" }}
>
<PlusOutlined /> Add field
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
</>
);
};
export default SubForm;
index.js
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Form, Input, Button } from "antd";
import { MinusCircleOutlined, PlusOutlined } from "#ant-design/icons";
import InnerForm from "./SubForm";
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 4 }
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 20 }`
}
};`
const formItemLayoutWithOutLabel = {
wrapperCol: {
xs: { span: 24, offset: 0 },
sm: { span: 20, offset: 4 }
}
};
const DynamicFieldSet = () => {
const onFinish = values => {
console.log("Received values of form:", values);
};
return (
<Form
name="dynamic_form_item"
{...formItemLayoutWithOutLabel}
onFinish={onFinish}
>
<Form.List name="names">
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => {
console.log(field);
return (
<Form.Item
{...(index === 0
? formItemLayout
: formItemLayoutWithOutLabel)}
label={index === 0 ? "Passengers" : ""}
required={false}
key={field.key}
>
<Form.Item
{...field}
name={[field.name, "outer"]}
fieldKey={[field.fieldKey, "outer"]}
validateTrigger={["onChange", "onBlur"]}
rules={[
{
required: true,
whitespace: true,
message:
"Please input passenger's"
}
]}
noStyle
>
<Input
placeholder="passenger name"
style={{ width: "60%" }}
/>
</Form.Item>
<Form.Item>
<InnerForm fieldKey={field.key} />
</Form.Item>
{fields.length > 1 ? (
<MinusCircleOutlined
className="dynamic-delete-button"
style={{ margin: "0 8px" }}
onClick={() => {
remove(field.name);
}}
/>
) : null}
</Form.Item>
);
})}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "60%" }}
>
<PlusOutlined /> Add field
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
ReactDOM.render(<DynamicFieldSet />, document.getElementById("container"));