edit dropDown list field in material table reactJS - javascript

i have the following code,
class AdminPanel extends Component {
state = {
idProject: ''
}
columns = [
{ title: 'Projet Prod', field: 'name', editable: 'never' },
{
title: 'Projet Preprod', field: 'project',editable: true, render: rowData =>
rowData.preprodName === '' ?
<Select projectPreprod={this.props.preprodProjects} onSelectProject={this.handleSelectedProject} />
:
<Label>{rowData.preprodName}</Label>
},
]
handleSelectedProject = (idProjectValue) => {
this.setState({ idProject: idProjectValue }, function () {
console.log(this.state.idProject);
});
}
render() {
let projectsTableData = [];
for (let key in this.props.projects) {
projectsTableData.push({
...this.props.projects[key],
});
}
let projects = (<MaterialTable
title="Gestion des projets"
columns={this.columns}
data={projectsTableData}
LoadingComponent={Spinner}
editable={{
onRowUpdate: (newData, oldData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
{
let data = [...projectsTableData.project];
let index = data.indexOf(oldData);
data[index] = { ...newData };
this.handleUpdate(newData.id, newData, data);
}
resolve()
}, 1000)
}),
}}
/>);
return (
<Container>
<Row>
<Col xs={10} md={{ offset: 2 }}>
{projects}
</Col>
</Row>
</Container>
)
}
}
i want to edit the project field the probleme is i have a conditional rendering so when i want to edit it and have rowData.preprodName !== '' it renders a label not the select and i want to edit the data in the select,,
Can anyone help me please?

Related

Antd Design EditableRow not changing buttons from "edit" to "save" and "cancel"

Im using Antd library and i can't seem to find where i have the bug.
This is my EditableTableCell component
import React, {Component} from 'react';
import { Form } from '#ant-design/compatible';
import '#ant-design/compatible/assets/index.css';
import { Input, InputNumber, Select, DatePicker } from "antd";
import moment from "moment";
import {EditableContext} from "./EditableTableRow";
const FormItem = Form.Item;
const Option = Select.Option;
class EditableTableCell extends Component {
getInput = (record, dataIndex, title, getFieldDecorator) => {
switch (this.props.inputType) {
case "number":
return (
<FormItem style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
rules: [
{
required: true,
message: `Please Input ${title}!`
}
],
initialValue: record[dataIndex]
})(
<InputNumber formatter={value => value} parser={value => value} />
)}
</FormItem>
);
case "date":
return (
<FormItem style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
initialValue: moment(record[dataIndex], this.dateFormat)
})(<DatePicker format={this.dateFormat} />)}
</FormItem>
);
case "select":
return (
<FormItem style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
initialValue: record[dataIndex]
})(
<Select style={{ width: 150 }}>
{[...Array(11).keys()]
.filter(x => x > 0)
.map(c => `Product ${c}`)
.map((p, index) => (
<Option value={p} key={index}>
{p}
</Option>
))}
</Select>
)}
</FormItem>
);
default:
return (
<FormItem style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
rules: [
{
required: true,
message: `Please Input ${title}!`
}
],
initialValue: record[dataIndex]
})(<Input />)}
</FormItem>
);
}
}
render() {
const { editing, dataIndex, title, inputType, record, index,...restProps} = this.props;
return (
<EditableContext.Consumer>
{form => {
const { getFieldDecorator } = form;
return (
<td {...restProps}>
{editing ?
this.getInput(record, dataIndex, title, getFieldDecorator)
: restProps.children}
</td>
);
}}
</EditableContext.Consumer>
);
}
}
export default EditableTableCell;
This is my EditableTableCell component
import React, {Component} from 'react';
import { Form} from '#ant-design/compatible';
export const EditableContext = React.createContext();
class EditableTableRow extends Component {
render() {
return (
<EditableContext.Provider value={this.props.form}>
<tr {...this.props} />
</EditableContext.Provider>
);
}
}
export default EditableTableRow=Form.create()(EditableTableRow);
This is my ProductsPage component im having bug in
import React, {Component} from 'react';
import {Button, Layout, notification, Popconfirm, Space, Table,Typography} from "antd";
import {Link} from "react-router-dom";
import {Content} from "antd/es/layout/layout";
import EditableTableRow, {EditableContext} from "../components/EditableTableRow";
import EditableTableCell from "../components/EditableTableCell";
import API from "../server-apis/api";
import {employeesDataColumns} from "../tableColumnsData/employeesDataColumns";
import {CheckCircleFilled, InfoCircleFilled} from "#ant-design/icons";
class ProductsPage extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
error: null,
isLoaded: false,
editingKey: "",
errorMessage: "",
}
}
columns = [
...employeesDataColumns,
{
title: "Actions",
dataIndex: "actions",
width: "10%",
render: (text, record) => {
const editable = this.isEditing(record);
return editable ? (
<span>
<EditableContext.Consumer>
{form => (<a onClick={() => this.saveData(form, record.username)} style={{ marginRight: 8 }}>Save</a>)}
</EditableContext.Consumer>
<a onClick={this.cancel}>Cancel</a>
</span>
) : (
<Space size="middle">
<a onClick={() => this.edit(record.username)}>Edit</a>
<Popconfirm title="Are you sure you want to delete this product?"
onConfirm={() => this.remove(record.username)}>
<a style={{color:"red"}}>Delete</a>
</Popconfirm>
</Space>
);
},
}
];
isEditing = (record) => {
return record.username === this.state.editingKey;
};
edit(username) {
this.setState({editingKey:username});
}
cancel = () => {
this.setState({ editingKey: ""});
};
componentDidMount() {
this.setState({ loading: true });
const token="Bearer "+ JSON.parse(localStorage.getItem("token"));
API.get(`users/all`,{ headers: { Authorization: token}})
.then(res => {
// console.log(res.data._embedded.productList);
const employees = res.data._embedded.employeeInfoDtoList;
this.setState({loading: false,data:employees });
})
}
async remove(username) {
const token="Bearer "+ JSON.parse(localStorage.getItem("token"));
API.delete(`/users/${username}`,{ headers: { Authorization: token}})
.then(() => {
let updatedProducts = [...this.state.data].filter(i => i.username !== username);
this.setState({data: updatedProducts});
this.successfullyAdded("Employee is deleted. It wont have any access to the website anymore.")
}).catch(()=>this.errorHappend("Failed to delete"));
}
hasWhiteSpace(s) {
return /\s/g.test(s);
}
saveData(form,username) {
form.validateFields((error, row) => {
if (error) {
return;
}
const newData = [...this.state.data];
const index = newData.findIndex(item => username === item.username);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row
});
const token="Bearer "+ JSON.parse(localStorage.getItem("token"));
const response = API.put(`/users/${username}/update`, row,{ headers: { Authorization: token}})
.then((response) => {
this.setState({ data: newData, editingKey: ""});
this.successfullyAdded("Empolyee info is updated")
})
.catch(error => {
this.setState({ errorMessage: error.message });
this.errorHappend("Failed to save changes.")
console.error('There was an error!', error);
});
});
}
successfullyAdded = (message) => {
notification.info({
message: `Notification`,
description:message,
placement:"bottomRight",
icon: <CheckCircleFilled style={{ color: '#0AC035' }} />
});
};
errorHappend = (error) => {
notification.info({
message: `Notification`,
description:
`There was an error! ${error}`,
placement:"bottomRight",
icon: <InfoCircleFilled style={{ color: '#f53333' }} />
});
};
render() {
const components = {
body: {
row: EditableTableRow,
cell: EditableTableCell
}
};
const columns = this.columns.map(col => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: record => {
const checkInput = index => {
switch (index) {
case "price":
return "number";
default:
return "text";
}
};
return {
record,
// inputType: col.dataIndex === "age" ? "number" : "text",
inputType: checkInput(col.dataIndex),
dataIndex: col.dataIndex,
title: col.title,
editing: this.isEditing(record)
};
}
};
});
const { data, loading } = this.state;
return (
<Layout>
<div>
<Link to="/add-product">
<Button style={{float:"right", background: "#0AC035",marginBottom:"1em", marginTop:"1em" }}
type="primary">New emplyee</Button>
</Link>
</div>
<Content>
<Table components={components} bordered dataSource={data} columns={columns} loading={loading} rowKey={data.username} rowClassName="editable-row"/>
</Content>
</Layout>
);
}
}
export default ProductsPage;
This is the bug I'm having:
enter image description here
And i want to have this result like its shown in Antd docs:
enter image description here
Id really appreciate if you take a look and help me figure out where im wrong
Updated Solution:
I find the issue. In render where you map the columns, you just return the column if it's not an editable column. You can check the code below. I added a check if it's dataIndex === 'actions', then return the following code:
Please Follow the link:
https://react-ts-v3fbst.stackblitz.io
Changes:
1.In columns, i remove the render function from the action object:
{
title: 'Actions',
dataIndex: 'actions',
width: '10%',
},
2. In render function where you map the columns, add the following code before this condition if(!col.editable) {,:
if (col.dataIndex === 'actions') {
return {
...col,
render: (text, record) => {
const editable = this.isEditing(record);
return editable ? (
<span>
<EditableContext.Consumer>
{(form) => (
<a onClick={() => this.saveData(form, record.username)} style={{ marginRight: 8 }}>
Save
</a>
)}
</EditableContext.Consumer>
<a onClick={this.cancel}>Cancel</a>
</span>
) : (
<Space size='middle'>
<a onClick={() => this.edit(record.username)}>Edit</a>
<Popconfirm title='Are you sure you want to delete this product?' onConfirm={() => this.remove(record.username)}>
<a style={{ color: 'red' }}>Delete</a>
</Popconfirm>
</Space>
);
}
};
}
When you click on edit, you set the username as key for that particular row for editing, make sure you have username in each record. I tested this using the following data:
const data = [
{ id: 8, name: 'baun', model: '2022', color: 'black', price: 358, quantity: 3, username: 'brvim' },
{ id: 3, name: 'galileo', model: '20221', color: 'white', price: 427, quantity: 7, username: 'john' }
];
Most important, you should select that attribute as key that is unique in all records. As you are using username, i don't know what is your business logic or data looks like, but technically each record can have same username. So you must select something that would always be unique in your complete data.

How to customize Ant table rowselection

I used Ant table to show some information.
https://codesandbox.io/s/proud-architecture-lsb85?file=/src/index.js
I want to customize the position of the checkbox for row selection.
In this application, you can see the header in the following order of checkbox, Name, Age, Address but I want to swap checkbox and Name.
You can add checkbox columns and customize render and titleRender of it to checkbox and then handle the events. if you incounter performance issue you have to add some memoization on columns or evenet handlers.
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import { Table, Button, Checkbox } from "antd";
const data = [];
for (let i = 0; i < 46; i++) {
data.push({
key: i,
name: `Edward King ${i}`,
age: 32,
address: `London, Park Lane no. ${i}`
});
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedRowKeys: [], // Check here to configure the default column
loading: false,
allChecked: false
};
this.columns = [
{
title: "Name",
dataIndex: "name"
},
{
dataIndex: "checked",
title: () => {
return (
<Checkbox
checked={this.state.allChecked}
onChange={(e) => this.selectAll(e)}
></Checkbox>
);
},
render: (text, rec, index) => {
return (
<Checkbox
checked={
this.state.selectedRowKeys.includes(rec.key) ||
this.state.allChecked
}
onChange={(e) => this.onChange(e, rec)}
></Checkbox>
);
}
},
{
title: "Age",
dataIndex: "age"
},
{
title: "Address",
dataIndex: "address"
}
];
}
start = () => {
this.setState({ loading: true });
// ajax request after empty completing
setTimeout(() => {
this.setState({
selectedRowKeys: [],
loading: false
});
}, 1000);
};
onChange = (e, rec) => {
const checked = e.target.checked;
if (checked) {
this.setState((state) => ({
...state,
selectedRowKeys: [...state.selectedRowKeys, rec.key]
}));
} else {
this.setState((state) => ({
...state,
selectedRowKeys: [
...state.selectedRowKeys.filter((item) => item !== rec.key)
]
}));
}
};
selectAll = (e) => {
const checked = e.target.checked;
if (checked) {
this.setState((state) => ({
...state,
allChecked: true
}));
} else {
this.setState((state) => ({
...state,
allChecked: false
}));
}
};
onSelectChange = (selectedRowKeys) => {
console.log("selectedRowKeys changed: ", selectedRowKeys);
this.setState({ selectedRowKeys });
};
render() {
const { loading, selectedRowKeys } = this.state;
const hasSelected = selectedRowKeys.length > 0;
return (
<div>
<div style={{ marginBottom: 16 }}>
<Button
type="primary"
onClick={this.start}
disabled={!hasSelected}
loading={loading}
>
Reload
</Button>
<span style={{ marginLeft: 8 }}>
{hasSelected ? `Selected ${selectedRowKeys.length} items` : ""}
</span>
</div>
<Table columns={this.columns} dataSource={data} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));

How to make several buttons simultaneously active but with some conditions?

I have a problem. I want to make buttons section, where user can click buttons to filter some content. When user click on 'all' button, all other should be turn off (change its color to initial, not active) in this moment. Also, user can check multiple buttons.
I can't get how to do this.
Example of JSON:
{
title: 'All',
id: 53,
},
{
title: 'Im a parent',
icon: <Parent />,
id: 0,
},
{
title: 'I live here',
icon: <ILiveHere />,
id: 2,
},
example of code: https://codesandbox.io/s/sleepy-haze-35htx?file=/src/App.js
Its wrong, I know. I tried some solutions, but I guess I can't get how to do it correctly.
With this code I can do active multiple buttons, but I can't get how to make conditions like
if (item.title === 'all){
TURN_OFF_ANY_OTHER_BTNS
}
I guess I should store checked buttons in temporary array to make these operations.
Will be really thankfull for help.
Is this something you would like?
const SocialRole = ({ item, selected, setSelected }) => {
let style =
[...selected].indexOf(item.id) !== -1
? { color: "red" }
: { color: "blue" };
return (
<button
style={style}
onClick={() => {
if (item.id === 53) {
setSelected(null);
} else {
setSelected(item.id);
}
}}
>
{item.icon}
<h1>{item.title}</h1>
</button>
);
};
export default function App() {
// We keep array of selected item ids
const [selected, setSelected] = useState([roles[0]]);
const addOrRemove = (item) => {
const exists = selected.includes(item);
if (exists) {
return selected.filter((c) => {
return c !== item;
});
} else {
return [...selected, item];
}
};
return (
<div>
{roles.map((item, index) => (
<SocialRole
key={index}
item={item}
selected={selected}
setSelected={(id) => {
if (id === null) setSelected([]);
else {
setSelected(addOrRemove(id));
}
}}
/>
))}
</div>
);
}
If I understand your problem, I think this is what you are looking for:
const roles = [
{
title: "All",
id: 53
},
{
title: "I live here",
id: 0
},
{
title: "I live here too",
id: 2
}
];
// button
const SocialRole = ({ item, selected, setSelected }) => {
const isActive = selected === item.title || selected === 'All';
return (
<button
style={isActive ? { color: "red" } : { color: "blue" }}
onClick={() => setSelected(item.title)}
>
{item.icon}
<h1>{item.title}</h1>
</button>
);
};
export default function App() {
const [selected, setSelected] = useState(roles[0].title);
return (
<div>
{roles.map((item, index) => (
<SocialRole
key={index}
item={item}
selected={selected}
setSelected={setSelected}
/>
))}
</div>
);
}
The problem was you were setting a new state into each button, when you should just use the state from the App.

Using material ui autocomplete on not triggered when writing async test

I am trying to write a test to for my component which uses the Material UI Autocomplete component. I am not sure what I am doing wrong but my test doesn't seem to be triggering the onChange of the Material UI Autocomplete component.
it('should render autocomplete and select a user', async () => {
searchContact.mockResolvedValueOnce({
data: {
value: [
{
displayName: 'Jan Travis',
userPrincipalName: 'JanT#email.uk',
},
{
displayName: 'Jon Test',
userPrincipalName: '',
},
{
displayName: 'Jay Test',
userPrincipalName: 'JayT#email.uk',
},
],
},
});
initialProps.activityName = 'some-activity';
initialProps.testId = 'contact-person[0]';
initialProps.fromType = 'planning-contact-person';
const { getByRole } = render(<AutoCompleteUserSearch {...initialProps} />);
const autocomplete = getByRole('textbox');
autocomplete.focus();
await act(async () => {
fireEvent.change(document.activeElement, { target: { value: 'Jay' } });
});
fireEvent.keyDown(document.activeElement, { key: 'ArrowDown' });
fireEvent.keyDown(document.activeElement, { key: 'Enter' });
await waitFor(() => {
expect(autocomplete.value).toEqual('Jay');
});
Here is what my autocomplete component looks like
return (
<React.Fragment>
<Autocomplete
id={props.activityName ? props.activityName : props.id}
freeSolo
data-testid={props.testId ? props.testId : 'autocomplete'}
defaultValue=""
getOptionLabel={(option) => (typeof option === 'string' ? option : option.displayName)}
getOptionSelected={(option, value) => {
return option.displayName === value;
}}
filterOptions={(x) => x}
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
value={autoCompleteValue}
autoComplete
includeInputInList
options={[...autoCompleteOptions]}
filterSelectedOptions
clearOnEscape
onChange={(event, newValue, reason) => {
setAutoCompleteOptions(newValue ? [newValue, ...autoCompleteOptions] : autoCompleteOptions);
if (newValue) {
setAutoCompleteValue(newValue.displayName);
if (newValue.userPrincipalName) {
setSelectUserEmailAddress(newValue.userPrincipalName);
setUserEmailAddressError();
} else {
setUserEmailAddressError('This user does not have an email address');
}
}
if (reason === 'clear') {
setAutoCompleteValue('');
}
}}
size="small"
renderInput={(params) => (
<div ref={params.InputProps.ref}>
<Input
type="text"
label="Search user"
name={props.activityName ? props.activityName : props.name}
{...params.inputProps}
inputProps={{ 'aria-label': 'Search user' }}
onChange={(ev) => {
onChangeHandle(ev.target.value);
}}
/>
</div>
)}
renderOption={(option, { inputValue }) => {
const matches = match(option.displayName, inputValue);
const parts = parse(option.displayName, matches);
return (
<div>
{parts.map((part, index) => (
<span key={index} style={{ fontWeight: part.highlight ? 700 : 400 }}>
{part.text}
</span>
))}
</div>
);
}}
/>
The onChange within the autocomplete doesn't seem to be trigged. Not sure if the within the test the keyDown is working properly.
Here is my onChangeHandle function within the Input onChange, which is being called
const onChangeHandle = (e) => {
setAutoCompleteValue(e);
if (e !== '') {
if (e) {
searchContact(e)
.then((res) => {
setAutoCompleteOptions(res.data.value);
})
.catch(() => {});
}
}
};
Any help would be appreciated, thanks.

React-data-grid - Changing cell value using global variable upon

I'm trying to create a certain functionality in my react-data-grid.
I have a column called subStart, and I have a dropdown that I want to use so that the user can set the value of a cell to the value of a state variable (this.state.timeTotalSec).
So if the user clicks on "Use Global Time" option in a 'Start Time' cell, it will replace the value of that cell with the value of this.state.timeTotalSec. How on earth do I do this?
I have the dropdown functionality working. But how do I get it to change the cell value?
const rows = [
{ id: 1, subStart: "00:00.000", subEnd: "00:00.000" , subText: 'Text1'},
{ id: 2, subStart: "00:00.000", subEnd: "00:00.000" , subText: 'Text2'},
{ id: 3, subStart: "00:00.000", subEnd: "00:00.000" , subText: 'Text3'}
];
const columns = [
{
key: "id",
name: "ID"
},
{
key: "subStart",
name: "Start Time",
editable: true
},
{
key: "subEnd",
name: "End Time",
editable: true
},
{
key: "subText",
name: "Text",
editable: true
}
].map(c => ({ ...c, ...defaultColumnProperties }));
const subStartActions = [
{
icon: <span className="glyphicon glyphicon-remove" />,
callback: () => {
alert("Deleting");
}
},
{
icon: "glyphicon glyphicon-link",
actions: [
{
text: "Use Global Time",
callback: () => {
// TODO
// **** TRYING TO MAKE THIS WORK ****
}
}
]
}
];
function getCellActions(column, row) {
const cellActions = {
subStart: subStartActions
};
return row.id % 2 === 0 ? cellActions[column.key] : null;
}
const ROW_COUNT = 50;
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
playing: false,
duration: 0,
timeMilli: 0,
timeSec: 0,
timeMin: 0,
timeTotalSec: 0,
rows
}
}
onDuration = (duration) => {
this.setState({ duration })
}
onProgress = (progress) => {
if (this.state.duration == 0) {
return
}
const timeTotalSec = progress.played * this.state.duration
if (timeTotalSec !== this.state.timeTotalSec) {
const timeMin = Math.floor(timeTotalSec / 60)
const timeSec = Math.floor(timeTotalSec - (timeMin)*60)
const timeMilli = (timeTotalSec - timeSec - timeMin*60).toFixed(3)
this.setState({ timeTotalSec })
this.setState({ timeMin })
this.setState({ timeSec })
this.setState({ timeMilli })
}
}
onGridRowsUpdated = ({ fromRow, toRow, updated }) => {
this.setState(state => {
const rows = state.rows.slice();
for (let i = fromRow; i <= toRow; i++) {
rows[i] = { ...rows[i], ...updated };
}
return { rows };
});
};
render () {
const { data } = this;
return (
<div className='player-wrapper'>
<ReactPlayer
url='https://www.youtube.com/watch?v=lhlZkqEag7E'
className='react-player'
playing={this.state.playing}
onPlay={() => this.setState({ playing: true })}
onPause={() => this.setState({ playing: false })}
controls='True'
onDuration={this.onDuration}
onProgress={this.onProgress}
/>
Video is currently: {this.state.playing ? 'playing' : 'paused'}
<br />
Duration: {Math.round(this.state.duration).toString() + ' seconds'}
<br />
Elapsed: {this.state.timeMin + 'min ' + this.state.timeSec + 'sec ' +
this.state.timeMilli + 'ms'}
<br />
<button onClick={() => this.setState({ playing: true })}>Play</button>
<button onClick={() => this.setState({ playing: false })}>Pause</button>
<ButtonToolbar>
<Button variant="primary" onClick={() => this.setState(this.state.playing ? false : true)}>Play/Pause</Button>
</ButtonToolbar>
<ReactDataGrid
columns={columns}
rowGetter={i => this.state.rows[i]}
rowsCount={ROW_COUNT}
// minHeight={500}
getCellActions={getCellActions}
onGridRowsUpdated={this.onGridRowsUpdated}
enableCellSelect={true}
/>
</div>
)
}
}
ReactDataGrid will just render what data you pass to it, If you want to change the value of a cell, you should update the rows from data source or state you are using.in your case rows
this.state = {
playing: false,
duration: 0,
timeMilli: 0,
timeSec: 0,
timeMin: 0,
timeTotalSec: 10,
rows // your datasourse
};
I've supposed,id is your data Key.Add updateRowDate to actions to handle your state changes.
actions: [
{
text: "Use Global Time",
callback: () => {
// TODO
// **** TRYING TO MAKE THIS WORK ****
updateRowDate(row.id);
}
}
]
and here is updateRowDate in App component
updateRowDate = rowId => {
this.setState(prv => ({
rows: prv.rows.map(q => {
if (q.id === rowId) return { ...q, subStart: this.state.timeTotalSec };
return q;
})
}));
finally, you need to pass updateRowDate to getCellActions
<ReactDataGrid
columns={columns}
rowGetter={i => this.state.rows[i]}
rowsCount={ROW_COUNT}
// minHeight={500}
getCellActions={(column, row) =>
getCellActions(column, row, this.updateRowDate)
}
onGridRowsUpdated={this.onGridRowsUpdated}
enableCellSelect={true}
/>
Here is the temporary sandbox containing the fixed version

Categories