Antd UI Table: Dynamically add/delete columns - reactjs

Based on ANTD's Table example: https://ant.design/components/table/#components-table-demo-edit-cell, I would like to replicate this, with the addition of having the ability to add/delete new columns. The sample from the link above only illustrates how to add new rows.
Here's the code from the sample:
import { Table, Input, Button, Popconfirm, Form } from 'antd';
const FormItem = Form.Item;
const EditableContext = React.createContext();
const EditableRow = ({ form, index, ...props }) => (
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
);
const EditableFormRow = Form.create()(EditableRow);
class EditableCell extends React.Component {
state = {
editing: false,
}
componentDidMount() {
if (this.props.editable) {
document.addEventListener('click', this.handleClickOutside, true);
}
}
componentWillUnmount() {
if (this.props.editable) {
document.removeEventListener('click', this.handleClickOutside, true);
}
}
toggleEdit = () => {
const editing = !this.state.editing;
this.setState({ editing }, () => {
if (editing) {
this.input.focus();
}
});
}
handleClickOutside = (e) => {
const { editing } = this.state;
if (editing && this.cell !== e.target && !this.cell.contains(e.target)) {
this.save();
}
}
save = () => {
const { record, handleSave } = this.props;
this.form.validateFields((error, values) => {
if (error) {
return;
}
this.toggleEdit();
handleSave({ ...record, ...values });
});
}
render() {
const { editing } = this.state;
const {
editable,
dataIndex,
title,
record,
index,
handleSave,
...restProps
} = this.props;
return (
<td ref={node => (this.cell = node)} {...restProps}>
{editable ? (
<EditableContext.Consumer>
{(form) => {
this.form = form;
return (
editing ? (
<FormItem style={{ margin: 0 }}>
{form.getFieldDecorator(dataIndex, {
rules: [{
required: true,
message: `${title} is required.`,
}],
initialValue: record[dataIndex],
})(
<Input
ref={node => (this.input = node)}
onPressEnter={this.save}
/>
)}
</FormItem>
) : (
<div
className="editable-cell-value-wrap"
style={{ paddingRight: 24 }}
onClick={this.toggleEdit}
>
{restProps.children}
</div>
)
);
}}
</EditableContext.Consumer>
) : restProps.children}
</td>
);
}
}
class EditableTable extends React.Component {
constructor(props) {
super(props);
this.columns = [{
title: 'name',
dataIndex: 'name',
width: '30%',
editable: true,
}, {
title: 'age',
dataIndex: 'age',
}, {
title: 'address',
dataIndex: 'address',
}, {
title: 'operation',
dataIndex: 'operation',
render: (text, record) => (
this.state.dataSource.length >= 1
? (
<Popconfirm title="Sure to delete?" onConfirm={() => this.handleDelete(record.key)}>
Delete
</Popconfirm>
) : null
),
}];
this.state = {
dataSource: [{
key: '0',
name: 'Edward King 0',
age: '32',
address: 'London, Park Lane no. 0',
}, {
key: '1',
name: 'Edward King 1',
age: '32',
address: 'London, Park Lane no. 1',
}],
count: 2,
};
}
handleDelete = (key) => {
const dataSource = [...this.state.dataSource];
this.setState({ dataSource: dataSource.filter(item => item.key !== key) });
}
handleAdd = () => {
const { count, dataSource } = this.state;
const newData = {
key: count,
name: `Edward King ${count}`,
age: 32,
address: `London, Park Lane no. ${count}`,
};
this.setState({
dataSource: [...dataSource, newData],
count: count + 1,
});
}
handleSave = (row) => {
const newData = [...this.state.dataSource];
const index = newData.findIndex(item => row.key === item.key);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row,
});
this.setState({ dataSource: newData });
}
render() {
const { dataSource } = this.state;
const components = {
body: {
row: EditableFormRow,
cell: EditableCell,
},
};
const columns = this.columns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: record => ({
record,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave: this.handleSave,
}),
};
});
return (
<div>
<Button onClick={this.handleAdd} type="primary" style={{ marginBottom: 16 }}>
Add a row
</Button>
<Table
components={components}
rowClassName={() => 'editable-row'}
bordered
dataSource={dataSource}
columns={columns}
/>
</div>
);
}
}
ReactDOM.render(<EditableTable />, mountNode);

You can make your columns array a part of the state and update it through setState.
Here is a working codepen: https://codepen.io/gges5110/pen/GLPjYr?editors=0010
// State
this.state = {
dataSource: [{
key: '0',
name: 'Edward King 0',
age: '32',
address: 'London, Park Lane no. 0',
}, {
key: '1',
name: 'Edward King 1',
age: '32',
address: 'London, Park Lane no. 1',
}],
columns: [{
title: 'name',
dataIndex: 'name',
width: '30%',
editable: true,
}, {
title: 'age',
dataIndex: 'age',
}]
};
// Event to add new column
handleAddColumn = () => {
const { columns } = this.state;
const newColumn = {
title: 'age',
dataIndex: 'age',
};
this.setState({
columns: [...columns, newColumn]
});
}
// Render method
render() {
const { dataSource, columns } = this.state;
return (
<Table
dataSource={dataSource}
columns={columns}
/>
);
}

Related

MUI datagrid view mode not persisting

I'm implementing a MUI datagrid with inline editing. Whenever a cell loses focus, the code sets the mode to 'view' (as it should) but then immediately the processRowUpdate callback is called (to send data to the endpoint) it sets the cell mode back to 'edit' meaning that the cell remains in edit mode.
Does anyone know why this is happening?
Maybe something to do with this processRowUpdate error logged to console?:
TypeError: Cannot read properties of undefined (reading 'id')
at getRowId (productTable.js:213:1)
at getRowIdFromRowModel (gridRowsUtils.js:17:1)
at useGridRows.js:106:1
at Array.forEach (<anonymous>)
at Object.updateRows (useGridRows.js:105:1)
at apiRef.current.<computed> [as updateRows] (useGridApiMethod.js:14:1)
at useGridCellEditing.new.js:334:1
Code:
export default function FullFeaturedCrudGrid(props) {
const [rows, setRows] = React.useState([]);
const [cellModesModel, setCellModesModel] = React.useState({})
const [selectedCellParams, setSelectedCellParams] = React.useState(null);
const { tableName } = props
const [snackbar, setSnackbar] = React.useState(null);
const handleCloseSnackbar = () => setSnackbar(null);
React.useEffect(() => {
console.log('useEffect called')
axios.get(`http://localhost:8000/mvhr/all`)
.then((response) => {
setRows(response.data);
})
}, [])
React.useEffect(() => {
console.log('cellModesModel',cellModesModel)
});
const handleCellFocus = React.useCallback((event) => {
const row = event.currentTarget.parentElement;
const id = row.dataset.id;
const field = event.currentTarget.dataset.field;
setSelectedCellParams({ id, field });
}, []);
const handleDeleteClick = (id) => () => {
axios.delete(`http://localhost:8000/delete_mvhr/${id}`
).then(() => {
setRows(rows.filter((row) => row.id !== id));
setSnackbar({ children: tableName + ' successfully deleted', severity: 'success' });
})
};
const handleCancel = () => {
if (!selectedCellParams) {
return;
}
const { id, field } = selectedCellParams;
setCellModesModel({
...cellModesModel,
[id]: {
...cellModesModel[id],
[field]: { mode: GridCellModes.View },
},
});
};
const processRowUpdate = React.useCallback(
(newRow) => {
axios.put(`http://localhost:8000/mvhr/`, newRow)
.then((response) => {
const updatedRow = { ...newRow, isNew: false };
setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row)));
setSnackbar({ children: tableName + ' successfully saved', severity: 'success' });
return updatedRow
})
});
const handleProcessRowUpdateError = React.useCallback((error) => {
setSnackbar({ children: error.message, severity: 'error' });
}, []);
const columns = [
{ field: 'description', headerName: 'description', width: 180, editable: true },
{ field: 'elec_efficiency', headerName: 'elec_efficiency', type: 'number', editable: true },
{ field: 'heat_recovery_eff', headerName: 'heat_recovery_eff', type: 'number', editable: true },
{ field: 'range_low', headerName: 'range_low', type: 'number', editable: true },
{ field: 'range_high', headerName: 'range_high', type: 'number', editable: true },
{ field: 'superseded', headerName: 'superseded', type: 'boolean', editable: true },
{
field: 'actions',
type: 'actions',
headerName: 'Actions',
width: 100,
cellClassName: 'actions',
getActions: ({ id }) => {
return [
<GridActionsCellItem
icon={<DeleteIcon />}
label="Delete"
onClick={handleDeleteClick(id)}
color="inherit"
/>,
];
},
},
];
return (
<Box
sx={{
height: '100vh',
width: '100%',
'& .actions': {
color: 'text.secondary',
},
'& .textPrimary': {
color: 'text.primary',
},
}}
>
<StripedDataGrid
rows={rows}
columns={columns}
processRowUpdate={processRowUpdate}
onProcessRowUpdateError={handleProcessRowUpdateError}
onCellEditStop={handleCancel}
cellModesModel={cellModesModel}
onCellModesModelChange={(model) => setCellModesModel(model)}
components={{
Toolbar: AddToolbar,
}}
componentsProps={{
toolbar: { setRows, setSnackbar, tableName },
cell: {
onFocus: handleCellFocus,
},
}}
experimentalFeatures={{ newEditingApi: true }}
getRowClassName={(params) =>
params.indexRelativeToCurrentPage % 2 === 0 ? 'even' : 'odd'
}
/>
{!!snackbar && (
<Snackbar
open
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
onClose={handleCloseSnackbar}
autoHideDuration={6000}
>
<Alert {...snackbar} onClose={handleCloseSnackbar} />
</Snackbar>
)}
</Box>
);
}
Solved it. I refactored the code to return the updatedRow synchronously rather than as a callback to the axios request:
const processRowUpdate = React.useCallback(
(newRow) => {
const updatedRow = { ...newRow, isNew: false };
setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row)));
setSnackbar({ children: tableName + ' successfully saved', severity: 'success' });
axios.put(`http://localhost:8000/mvhr/`, newRow)
return updatedRow
});

Custom handler search filter table antd

I am using antd to create a table with filter feature,
but I got a problem, I want to custom filter search of antd to call api,
antd table
How can I do that with react and antd,
Here is my code
const columns: ColumnsType<Product> = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
render: (name, record) => (
<div className="flex items-center">
<Avatar src={record.avatar}>{name[0].toUpperCase()}</Avatar>
<span style={{ marginLeft: 10 }} className="whitespace-nowrap">
<a title="View & Edit" href={'/product/' + record.id}>
{name}
</a>
</span>
</div>
),
},
{
title: 'About',
dataIndex: 'about',
key: 'about',
render: (about: string) => <p className="text-ellipsis line-clamp-2">{about}</p>,
width: 400,
},
{
title: 'Categories',
dataIndex: 'categories',
key: 'categories',
filterSearch: true,
filters: categories!.map((category: Category) => ({ text: category.name, value: category.id })),
render: (categories: any) => {
console.log(categories);
return '-';
},
// width: 400,
},
{ title: 'Addresses', dataIndex: 'contract_addresses', key: 'address', render: addresses => addresses?.[0] },
];
To filter the table, I usually provide a function that filters the data before it is passed to the table (In my example the function getData. This function can also be used to adjust the filter accordingly. Here is an example, which also consists of a Search-Input to modify the search:
const CustomersTable = (props: any) => {
const [searchText, setSearchText] = useState<string | undefined>(undefined);
const [customers, setCustomers] = useState<ICustomer[]>([]);
const getData = (
sourceData: Array<ICustomer>,
searchText: string | undefined
) => {
if (!searchText) {
return sourceData;
}
return sourceData.filter((item) => {
const comparisonString = `${item.firstname.toLowerCase()}${item.familyname.toLowerCase()}`;
//here you can provide a more sophisticared search
return comparisonString.includes(searchText.toLowerCase());
});
};
const columns = [
{
title: "Vorname",
dataIndex: "firstname",
key: "firstname",
sorter: (a: ICustomer, b: ICustomer) => {
if (a.firstname.toLowerCase() > b.firstname.toLowerCase()) {
return -1;
} else if (a.firstname.toLowerCase() < b.firstname.toLowerCase()) {
return 1;
} else {
return 0;
}
},
},
{
title: "Nachname",
dataIndex: "familyname",
key: "familyname",
},
];
return (
<>
<Search
placeholder="Vorname, Nachname"
value={searchText}
onChange={(e) => {
setSearchText(e.target.value);
}}
/>
<Table
columns={columns}
dataSource={getData(customers, searchText)}
/>
</>
);
};

Material-UI-Search Bar value not clearing

I am using the #material-ui-searchbar to filter data. I'm using the same container for multiple different pages to display the info. When ever I switch between pages (and thus the data) the whole container reloads to display the new data and new {subType}... however, despite me changing the value of {searched} using setSearched("")(verified that it does in fact change from the debugger), the old search value from the previous page/state remains in the search bar even though the search is inactive. Why won't the search bar value update with the change of state? Doesn't make intuitive sense. Here is the code for my container:
import SearchBar from 'material-ui-search-bar';
const facilityGridConfig = {
hospitals: {
columns: [
{ name: 'name', title: 'Name' },
{ name: 'address1', title: 'Address' },
{ name: 'city', title: 'City' },
{ name: 'state', title: 'State' },
{ name: 'zipcode', title: 'Zip' },
{ name: 'phoneNumber', title: 'Phone' },
{ name: 'userTotal', title: 'Therapists', link: '/users/therapists' },
{ name: 'endUserTotal', title: 'Patients', link: '/users/patients' },
]
},
clinics: {
columns: [
{ name: 'name', title: 'Name' },
{ name: 'address1', title: 'Address' },
{ name: 'city', title: 'City' },
{ name: 'state', title: 'State' },
{ name: 'zipcode', title: 'Zip' },
{ name: 'phoneNumber', title: 'Phone' },
{ name: 'userTotal', title: 'Therapists', link: '/users/therapists' },
{ name: 'endUserTotal', title: 'Patients', link: '/users/patients' },
]
},
staff: {
columns: [
{ name: 'firstName', title: 'First Name' },
{ name: 'lastName', title: 'Last Name' },
{ name: 'facility', title: 'Facility' },
{ name: 'logins', title: 'Logins', width: 90 },
{ name: 'enrollmentStatus', title: 'Status', width: 90 },
{ name: 'email', title: 'Email' },
{ name: 'phoneNumber', title: 'Phone' }
]
},
therapists: {
columns: [
{ name: 'firstName', title: 'First Name' },
{ name: 'lastName', title: 'Last Name' },
{ name: 'facility', title: 'Facility' },
{ name: 'logins', title: 'Logins', width: 90 },
{ name: 'enrollmentStatus', title: 'Status', width: 90 },
{ name: 'email', title: 'Email' },
{ name: 'phoneNumber', title: 'Phone' },
{ name: 'endUserTotal', title: 'Patients' },
]
},
patients: {
columns: [
{ name: 'firstName', title: 'First Name' },
{ name: 'lastName', title: 'Last Name' },
{ name: 'facility', title: 'Facility' },
{ name: 'email', title: 'Email' },
{ name: "sessionsInPastWeek", title: "Sessions Last 7 Days"},
]
}
};
const facilityGridConfigSmall = {
hospitals: {
columns: [
{ name: 'name', title: 'Name' },
{ name: 'userTotal', title: 'Therapists', link: '/users/therapists' },
{ name: 'endUserTotal', title: 'Patients', link: '/users/patients' },
]
},
clinics: {
columns: [
{ name: 'name', title: 'Name' },
{ name: 'userTotal', title: 'Therapists', link: '/users/therapists' },
{ name: 'endUserTotal', title: 'Patients', link: '/users/patients' },
]
},
staff: {
columns: [
{ name: 'firstName', title: 'First Name' },
{ name: 'lastName', title: 'Last Name' },
{ name: 'logins', title: 'Logins' }
]
},
therapists: {
columns: [
{ name: 'firstName', title: 'First Name' },
{ name: 'lastName', title: 'Last Name' },
{ name: 'logins', title: 'Logins' },
{ name: 'endUserTotal', title: 'Patients' },
]
},
patients: {
columns: [
{ name: 'firstName', title: 'First Name' },
{ name: 'lastName', title: 'Last Name' },
{ name: "sessionsInPastWeek", title: "Sessions Last 7 Days"},
]
}
};
const useStyles = makeStyles((theme: Theme) => ({
root: {
position: 'relative',
paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2),
height: '100%',
},
container: {
position: 'relative',
height: '100%',
},
breadcrumb: {
fontFamily: "Open Sans",
height: '28px',
},
content: {
position: 'relative',
height: 'calc(100% - 28px)',
},
gridHeaderEdit: {
textAlign: 'center!important' as any,
},
menuIcon: {
marginRight: theme.spacing(1)
},
}));
const FacilitiesGridContainer = (props: any) => {
const {
type,
subType,
uid,
} = props;
const mountedRef = useRef(true);
const classes = useStyles();
const dispatch = useDispatch();
const targetType = uid ? (
subType === Navigations.facilities.hospitals ? Navigations.facilities.clinics :
subType === Navigations.facilities.clinics ? Navigations.users.therapists : Navigations.users.patients
) : subType
const userProfile = useSelector(state => _get(state, ['user', 'profile']) || {}, shallowEqual);
const activeHospital = useSelector(state => _get(state, ['facilities', 'activeHospital']) || null, shallowEqual);
const activeClinic = useSelector(state => _get(state, ['facilities', 'activeClinic']) || null, shallowEqual);
const data = useSelector(state => _get(state, ['facilities', targetType]) || [], shallowEqual);
const loading = useSelector(state => _get(state, ['facilities', 'loading']) || false, shallowEqual);
const [fdata, setfData] = useState(data.data);
const [ openDeleteModal, setopenDeleteModal ] = useState(false);
const [ openInviteModal, setOpenInviteModal ] = useState(false);
const [ facilitiesInEdit, setFacilitiesInEdit ] = useState([] as any);
const [ sort, setSort ] = useState([] as any); //[{ field: 'name', dir: 'asc' }]
const [ visibleAddButton, setVisibleAddButton ] = useState(true);
const [ singleSubType, setSingleSubType ] = useState('');
const [ windowTitle, setWindowTitle ] = useState('');
const [ modalUserName, setModalUserName ] = useState('');
const [ visibleQRModal, setVisibleQRModal ] = useState(true);
const [ openQRModal, setOpenQRModal ] = useState(false);
const [ qrString, setQRString ] = useState('');
const [ qrFullName, setQRfullName ] = useState('');
const gridConfig = _get(facilityGridConfig, [targetType]) || {};
const gridConfigSmall = _get(facilityGridConfigSmall, [targetType]) || {};
const [searched, setSearched] = useState("");
const requestSearch = (searchedVal: string) => {
const filteredData = data.data.filter((row:any) => {
if (subType === "therapists" || subType === "patients" || subType === "staff"){
return (row['firstName'].toLowerCase().includes(searchedVal.toLowerCase()) || row['lastName'].toLowerCase().includes(searchedVal.toLowerCase()));
}
else {
return (row['name'].toLowerCase().includes(searchedVal.toLowerCase()));
}
});
setfData(filteredData);
};
const cancelSearch = () => {
setSearched("");
requestSearch(searched);
};
useEffect(() => {
setSearched("");
return () => {
mountedRef.current = false;
}
}, []);
useEffect(() => {setfData(data.data)}, [data.data] )
useEffect(() => {
setSearched("");
if (subType === Navigations.facilities.hospitals) {
if (uid) {
setSingleSubType('clinic');
} else {
setSingleSubType('hospital');
}
}
else if (subType === Navigations.facilities.clinics) {
if (uid) {
setSingleSubType('therapist');
} else {
setSingleSubType('clinic');
}
}
else if (subType === Navigations.users.therapists) {
if (uid) {
setSingleSubType('patient');
} else {
setSingleSubType('therapist');
}
}
else if (subType === Navigations.users.staff) {
setSingleSubType('staff');
}
else
setSingleSubType('patient');
return () => {
}
}, [subType, uid]);
useEffect(() => {
console.log('call to api here...')
dispatch({
type: FacilityActionType.GetFacilities,
payload: {
type: type,
subType: subType,
targetType: targetType,
facilityUid: targetType === 'therapists' ? (uid || null) : (_get(activeClinic, 'uid') || null),
parentFacilityUid: uid ? null : (_get(activeHospital, 'uid') || null),
parentUid: uid || null,
skip: 0,
take: 0
}
});
if (!userProfile)
setVisibleAddButton(false);
if (subType === 'hospitals') {
if ([UserRoles.SYSTEMADMIN].indexOf(userProfile.role) >= 0 && !!uid) {
setVisibleAddButton(true);
} else if ([UserRoles.SUPERUSER].indexOf(userProfile.role) < 0) {
setVisibleAddButton(false);
} else {
setVisibleAddButton(true);
}
}
else if (subType === 'clinics') {
if ([UserRoles.FACILITYADMIN].indexOf(userProfile.role) >= 0 && !!uid) {
setVisibleAddButton(true);
} else if ([UserRoles.SUPERUSER, UserRoles.SYSTEMADMIN, UserRoles.FACILITYADMIN].indexOf(userProfile.role) < 0) {
setVisibleAddButton(false);
} else {
setVisibleAddButton(true);
}
} else if (subType === 'therapists') {
if ([UserRoles.SUPERUSER, UserRoles.SYSTEMADMIN, UserRoles.FACILITYADMIN].indexOf(userProfile.role) < 0) {
setVisibleAddButton(false);
} else {
setVisibleAddButton(true);
}
} else if (subType === 'patients') {
if ([UserRoles.SUPERUSER, UserRoles.SYSTEMADMIN, UserRoles.FACILITYADMIN, UserRoles.USER].indexOf(userProfile.role) < 0) {
setVisibleAddButton(false);
} else {
setVisibleAddButton(true);
}
}
setWindowTitle("Augment Therapy: "+subType.charAt(0).toUpperCase() + subType.slice(1));
setSearched("");
return () => {
};
}, [userProfile, subType, uid, dispatch, type, targetType, activeClinic, activeHospital])
useEffect(() => {
setSearched("");
if (data) {
if (subType === Navigations.users.therapists) {
for (let rec of data.data) {
if (!rec['enrollmentStatus']) {
rec.enrollmentStatus = '--'
}
}
}
}
}, [subType,data]);
const setActiveFacility = (dataItem: any) => {
const payload: any = getActiveFacilityPayload(subType, uid);
if (dataItem.facilityType === FacilityType.Hospital) {
payload.activeHospital = dataItem;
} else if (dataItem.facilityType === FacilityType.Clinic) {
payload.activeClinic = dataItem;
} else if (subType === 'clinics' && !!uid) {
payload.activeTherapist = dataItem;
} else if (subType === 'staff' && !uid) {
payload.activeStaff = dataItem;
} else if (subType === 'therapists' && !uid) {
payload.activeTherapist = dataItem;
} else {
payload.activePatient = dataItem;
}
dispatch({type: FacilityActionType.SetActiveFacility, payload: payload});
}
const handleRowClick = (e: any) => {
setActiveFacility(e.dataItem);
if (e.dataItem) {
if (e.dataItem.facilityType === FacilityType.Hospital) {
navigate(`${Navigations.facilities.root}/${Navigations.facilities.hospitals}/${e.dataItem.uid}`, false);
} else if (e.dataItem.facilityType === FacilityType.Clinic) {
navigate(`${Navigations.facilities.root}/${Navigations.facilities.clinics}/${e.dataItem.uid}`, false);
} else if (subType === 'clinics') {
if (!uid) {
navigate(`${Navigations.facilities.root}/${Navigations.facilities.clinics}/${_get(e.dataItem, 'uid')}`, false);
} else { // it's therapist
navigate(`${Navigations.users.root}/${Navigations.users.therapists}/${_get(e.dataItem, 'uid')}`, false);
}
} else if (subType === 'therapists') {
if (!uid) {
navigate(`${Navigations.users.root}/${Navigations.users.therapists}/${_get(e.dataItem, 'uid')}`, false);
} else { // it's therapist
navigate(`${Navigations.users.root}/${Navigations.users.patients}/${_get(e.dataItem, 'uid')}`, false);
}
} else if (subType === 'patients') {
navigate(`${Navigations.users.root}/${Navigations.users.patients}/${_get(e.dataItem, 'uid')}`, false);
}
}
}
const handleLink = (dataItem: any, fieldName: string) => {
setActiveFacility(dataItem);
if (fieldName === 'userTotal') {
navigate(`${Navigations.users.root}/${Navigations.users.therapists}`, false);
} else if (fieldName === 'endUserTotal') {
navigate(`${Navigations.users.root}/${Navigations.users.patients}`, false);
}
}
const handleEdit = (dataItem: any) => {
let newType = Navigations.facilities.root;
if (targetType === Navigations.users.staff || targetType === Navigations.users.therapists || targetType === Navigations.users.patients) {
newType = Navigations.users.root;
}
if (dataItem) {
setActiveFacility(dataItem);
navigate(`${newType}/${targetType}/edit`, false, {uid: dataItem.uid});
} else {
navigate(`${newType}/${targetType}/new`);
}
}
const handleDelete = (dataItem: any) => {
setFacilitiesInEdit([...facilitiesInEdit, dataItem]);
setModalName(dataItem);
setopenDeleteModal(true);
}
const handleDeleteOk = () => {
dispatch({
type: FacilityActionType.DeleteFacilities,
payload: {
type: type,
subType: subType,
targetType: targetType,
body: facilitiesInEdit
}});
setopenDeleteModal(false);
}
const handleDeleteCancel = () => {
setFacilitiesInEdit([]);
setopenDeleteModal(false);
};
const handleInvite = (dataItem: any) => {
//setFacilitiesInEdit([...facilitiesInEdit, dataItem]);
setFacilitiesInEdit([dataItem]);
setModalName(dataItem);
setOpenInviteModal(true);
};
const handleInviteOk = () => {
dispatch({
type: FacilityActionType.PostUserSendInvite,
payload: {
body: facilitiesInEdit,
},
});
setOpenInviteModal(false);
};
const handleInviteCancel = () => {
setFacilitiesInEdit([]);
setOpenInviteModal(false);
};
const setModalName = (dataItem: any) => {
if (dataItem.firstName) {
setModalUserName(dataItem.firstName+' '+dataItem.lastName);
} else {
setModalUserName(dataItem.name);
}
return () => {
}
}
const closeQR = () => {
setOpenQRModal( false );
};
const handleQR = (dataItem: any) => {
setFacilitiesInEdit([...facilitiesInEdit, dataItem]);
setQRString(dataItem.qr);
if (dataItem.lastName) {
setQRfullName( dataItem.firstName + ' ' + dataItem.lastName.charAt(0) + '.' );
} else {
setQRfullName( dataItem.firstName );
}
setOpenQRModal(true);
};
return (
<Page
className={classes.root}
title={windowTitle}
>
<Container
maxWidth={false}
className={classes.container}
>
<Grid
container
direction='row'
style={{height: '100%'}}
>
<Grid
item
xs={12}
className={classes.breadcrumb}
>
<FacilitiesBreadCrumb
type={type}
subType={subType}
/>
</Grid>
<Grid
item
xs={12}
className={classes.content}
>
{
loading && (
<CircularIndeterminate/>
)
}
<Grid item xs={12} >
<StatsCards
type={subType}
uid={uid}
/>
</Grid>
<Hidden mdDown>
<DataGrid
style={{ height: '100%', overflow: 'auto', cursor:'pointer' }}
data={orderBy(fdata, sort)}
sortable={{
mode: 'multiple',
}}
sort={sort}
onSortChange={(e) => {
setSort(e.sort);
}}
onRowClick={(e: any) => handleRowClick(e)}
>
{
(visibleAddButton) && (
<GridToolbar>
<Grid container
style={{justifyContent: 'center', display: 'flex', alignItems: 'center', padding: 10}}>
<Grid item>
<SearchBar
value={searched}
onChange={(searchVal) => requestSearch(searchVal)}
onCancelSearch={() => cancelSearch()}
style={{
display: 'flex',
margin: '0 auto',
maxWidth: 800,
minWidth: 800,
paddingRight: 10
}}>
</SearchBar>
</Grid>
<Grid item>
<button
title="Add new"
className="k-button k-primary"
onClick={() => handleEdit(null)}
style={{
height: 50,
paddingLeft: 10
}}
>
Add New {singleSubType}
</button>
</Grid>
</Grid>
</GridToolbar>
)
}
{
(!(targetType==='hospitals' || targetType==='clinics')) && (
<GridColumn
title="Badge"
width="100"
headerClassName={classes.gridHeaderEdit}
cell={CellWithQR({userProfile: userProfile, type:"badge", handleAction: handleQR})}
/>
)
}
{
((targetType==='hospitals' || targetType==='clinics')) && (
<GridColumn
title=""
width="1"
headerClassName={classes.gridHeaderEdit}
cell={CellWithEdit({userProfile: userProfile, type:"empty", handleAction: handleInvite})}
/>
)
}
{
gridConfig.columns.map((c: any, index: number) => {
return (
<GridColumn key={`grid-${index}`}
field={c.name}
title={c.title}
cell={c.link ? CellWithLink({handleLink}) : undefined}
width= {c.width ? c.width : undefined}
/>
)
})
}
<GridColumn
title="Actions"
width="100"
headerClassName={classes.gridHeaderEdit}
cell={CellWithComboMenu({userProfile: userProfile, type:"edit", handleActionInvite: handleInvite, handleActionEdit: handleEdit, handleActionDelete: handleDelete})}
/>
</DataGrid>
</Hidden>
<Hidden lgUp>
<DataGrid
style={{ height: '100%', overflow: 'auto', cursor:'pointer' }}
data={orderBy(fdata, sort)}
sortable={{
mode: 'multiple',
}}
sort={sort}
onSortChange={(e) => {
setSort(e.sort);
}}
onRowClick={(e: any) => handleRowClick(e)}
>
{
(visibleAddButton) && (
<GridToolbar>
<Grid container
style={{justifyContent: 'center', display: 'flex', alignItems: 'center', padding: 10}}>
<Grid item>
<SearchBar
value={searched}
onChange={(searchVal) => requestSearch(searchVal)}
onCancelSearch={() => cancelSearch()}
style={{
display: 'flex',
margin: '0 auto',
maxWidth: 800,
paddingRight: 10
}}>
</SearchBar>
</Grid>
<Grid item>
<button
title="Add new"
className="k-button k-primary"
onClick={() => handleEdit(null)}
style={{
height: 50,
paddingLeft: 10
}}
>
Add New {singleSubType}
</button>
</Grid>
</Grid>
</GridToolbar>
)
}
{
(!(targetType==='hospitals' || targetType==='clinics')) && (
<GridColumn
title="Badge"
width="20"
headerClassName={classes.gridHeaderEdit}
cell={CellWithQR({userProfile: userProfile, type:"badge", handleAction: handleQR})}
/>
)
}
{
((targetType==='hospitals' || targetType==='clinics')) && (
<GridColumn
title=""
width="10"
headerClassName={classes.gridHeaderEdit}
cell={CellWithEdit({userProfile: userProfile, type:"empty", handleAction: handleInvite})}
/>
)
}
{
gridConfigSmall.columns.map((c: any, index: number) => {
return (
<GridColumn key={`grid-${index}`}
field={c.name}
title={c.title}
cell={c.link ? CellWithLink({handleLink}) : undefined}
minResizableWidth = {c.name.toLowerCase().includes("name")? 20 : 10}
/>
)
})
}
<GridColumn
title="Actions"
width="100"
headerClassName={classes.gridHeaderEdit}
cell={CellWithComboMenu({userProfile: userProfile, type:"edit", handleActionInvite: handleInvite, handleActionEdit: handleEdit, handleActionDelete: handleDelete})}
/>
</DataGrid>
</Hidden>
<ConfirmModal
title="Confirm Deletion"
content={`Are you sure you want to delete the ${singleSubType} ( ${modalUserName} ) ?`}
open={openDeleteModal}
handleClose={handleDeleteCancel}
handleOk={handleDeleteOk}
handleCancel={handleDeleteCancel}
/>
<ConfirmModal
title="Confirm Invite"
content={`Send ${modalUserName} an invitation to connect?`}
open={openInviteModal}
handleClose={handleInviteCancel}
handleOk={handleInviteOk}
handleCancel={handleInviteCancel}
/>
{
(openQRModal) && (
<UserQRCode
id="qrCode"
qrCodeString={qrString}
size={256}
userfullname={qrFullName}
open={true}
handleClose={closeQR}
/>
)
}
</Grid>
</Grid>
</Container>
</Page>
);
}
export default FacilitiesGridContainer;```
Well, I'm new to reactjs, but I suggest to call setSearched in a useEffect hook if your filteredData get changed. So it'll be something like this :
useEffect(() => {
setSearched("");
}, [filteredData])
..despite me changing the value of {searched} using setSearched("")..
this because you call setSearched("") on compoenent mount using (useEffect) but you need this to be called every time your filteredData get updated

Ant Design table delete and edit functionalities not reflecting on the PG database

I am trying to implement crud functionalities in Ant design table. I observed that the delete and edit functionalities only works on the instance when I perform the operations on initial render, but after reloading the component, the table returns back to its initial state and the operations don't affect the database in any way.
I see this error on my console
Type '{ title: string; dataIndex: string; key: string; align: string; editable: boolean;
render?: undefined; }' is not assignable to type 'ColumnType<any>'.
Types of property 'align' are incompatible.
Type 'string' is not assignable to type 'AlignType'.
These are the codes below, I hope to break them down in code blocks so they can be understandable
imports
import React, { useState, useEffect } from 'react';
import { Table, Popconfirm, Button, Space, Input, Form } from 'antd';
import { isEmpty } from 'lodash';
api
const apiUrl = 'api/terminals';
useState and useEffect codes
const [gridData, setGridData] = useState([]);
const [loader, setLoader] = useState(false);
const [editingKey, setEditingKey] = useState('');
const [editRow, setEditRow] = useState(false);
const [form] = Form.useForm();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
setLoader(true);
const response = await axios.get(apiUrl);
setGridData(response.data);
setLoader(false);
};
modifiedData codes
const modifiedData = gridData.map(({ ...item }) => ({
...item,
key: item.id,
}));
const save = async key => {
try {
const row = await form.validateFields();
const newData = [...modifiedData];
const index = newData.findIndex(item => key === item.key);
if (index > -1) {
const item = newData[index];
newData.splice(index, 1, { ...item, ...row });
setGridData(newData);
setEditingKey('');
}
} catch (error) {
console.warn('Error', error);
}
};
edit and cancel function code block
const edit = record => {
form.setFieldsValue({
name: '',
stock: '',
stockLevelDate: '',
tankThreatLevel: '',
tankThreatLevelColor: '',
tankTopPosition: '',
tankTopPositionColor: '',
lowPumpable: '',
lowPumpableColor: '',
tankCapacity: '',
...record,
});
setEditingKey(record.key);
};
const cancel = () => {
setEditingKey('');
};
editableCell function block
const EditableCell = ({ editing, dataIndex, title, record, children, ...restProps }) =>
{
const input = <Input />;
return (
<td {...restProps}>
{editing ? (
<Form.Item
name={dataIndex}
style={{ margin: 0 }}
rules={[
{
required: true,
message: `Please input ${title}`,
},
]}
>
{input}
</Form.Item>
) : (
children
)}
</td>
);
};
editing code block
const isEditing = record => {
return record.key === editingKey;
};
columns block
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
},
{
title: 'Name' as string,
dataIndex: 'name',
key: 'name',
align: 'center',
editable: true,
},
{
title: 'Stock',
dataIndex: 'stock',
key: 'stock',
align: 'center',
editable: true,
},
{
title: 'Stock Level Date',
dataIndex: 'stockLevelDate',
key: 'stockLevelDate',
align: 'center',
editable: true,
},
{
title: 'Tank Threat Level',
dataIndex: 'tankThreatLevel',
key: 'tankThreatLevel',
align: 'center',
editable: true,
},
{
title: 'Tank Threat Level Color',
dataIndex: 'tankThreatLevelColor',
key: 'tankThreatLevelColor',
align: 'center',
editable: true,
},
{
title: 'Tank Top Position',
dataIndex: 'tankTopPosition',
key: 'tankTopPosition',
align: 'center',
editable: true,
},
{
title: 'Tank Top Position Color',
dataIndex: 'tankTopPositionColor',
key: 'tankTopPositionColor',
align: 'center',
editable: true,
},
{
title: 'Low Pumpable',
dataIndex: 'lowPumpable',
key: 'lowPumpable',
align: 'center',
editable: true,
},
{
title: 'Low Pumpable Color',
dataIndex: 'lowPumpableColor',
key: 'lowPumpableColor',
align: 'center',
editable: true,
},
{
title: 'Tank Capacity',
dataIndex: 'tankCapacity',
key: 'tankCapacity',
align: 'center',
editable: true,
},
{
title: 'Actions',
dataIndex: 'actions',
key: 'actions',
align: 'center',
render: (_, record) => {
const editable = isEditing(record);
return modifiedData.length >= 1 ? (
<Space>
<Popconfirm title="Sure to delete?" onConfirm={() => handleDelete(record)}>
<Button type="primary" disabled={editable} danger>
Delete
</Button>
</Popconfirm>
{editable ? (
<span>
<Space size="middle">
<Button onClick={e => save(record.key)} type="primary" style={{ marginRight: 8
}}>
{' '}
Save
</Button>
<Popconfirm title="Sure to cancel?" onConfirm={cancel}>
<Button>Cancel</Button>
</Popconfirm>
</Space>
</span>
) : (
<Button onClick={() => edit(record)} type="primary">
Edit
</Button>
)}
</Space>
) : null;
},
},
];
mergedCoulumns block
const mergedColumns = columns.map(col => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: record => ({
record,
dataIndex: col.dataIndex,
title: col.title,
editing: isEditing(record),
}),
};
});
handleDelete code block
const handleDelete = value => {
const dataSource = [...modifiedData];
const filteredData = dataSource.filter(item => item.id !== value.id);
setGridData(filteredData);
};
return jsx
<>
<div>
<h2>Terminals</h2>
<Link to={`${match.url}/new`} className="btn btn-primary jh-create-entity" id="jh
-create-
entity" data-cy="entityCreateButton">
<FontAwesomeIcon icon="plus" />
Add Terminal
</Link>
<hr color="red" />
<Form form={form} component={false}>
<Table
components={{
body: {
cell: EditableCell,
},
}}
columns={mergedColumns}
dataSource={modifiedData}
bordered
loading={loader}
/>
</Form>
</div>
</>
Thanks in anticipation

Missing sort and filter icons in Ant design

I have the following table in Ant Design. The sort and filter icons in Ant Design. The icons aren't showing up, although if I hover over the column header, I can see the "sort" and "filter" text pop up, and the actual sort and filter functionality works.
In my parent component I am importing the CSS
App.css
#import "~antd/dist/antd.css";
App.js
import './App.css';
Assets.js
/* eslint-disable no-undef */
/*
From https://ant.design/components/table/ (Dynamic Settings example)
*/
import React, { Component } from 'react';
import Title from './Title';
import { Table, Icon, Switch, Radio, Form, Divider } from 'antd';
const FormItem = Form.Item;
/* const data = [];
for (let i = 1; i <= 10; i++) {
data.push({
key: i,
name: 'John Brown',
age: `${i}2`,
address: `New York No. ${i} Lake Park`,
description: `My name is John Brown, I am ${i}2 years old, living in New York No. ${i} Lake Park.`
});
} */
const expandedRowRender = record => <p>{record.description}</p>;
const title = () => 'Assets';
const showHeader = true;
const footer = () => 'Here is footer';
const scroll = { y: 240 };
const pagination = { position: 'bottom' };
// TODO: Delete this after swapping in the table
/* const renderAssets = (data, handleAssetClick) => {
console.log('renderAssets data', data);
const assetsToRender = data.map(asset => {
const assetDiv = (
<div style={{ display: 'block' }} onClick={handleAssetClick} value={asset.id} key={asset.id}>
{asset.name}
</div>
);
return assetDiv;
});
console.log('Assets assetsToRender', assetsToRender);
return assetsToRender;
}; */
const getAssets = data => {
console.log('getAssets data', data);
let assets = [];
assets = data.map(asset => {
//console.log(asset);
return {
key: asset.id,
id: asset.id,
name: asset.name,
type: asset.properties.class,
substation: asset.properties.substation,
feeder: asset.properties.feeder,
status: asset.properties.service_status,
vulnerability: asset.properties.peak_vulnerability
};
});
console.log('Assets', assets);
return assets;
};
class Assets extends Component {
constructor(props) {
super(props);
console.log('Assets data', this.props.data, 'handleAssetClick', this.props.handleAssetClick);
this.state = {
bordered: true,
loading: false,
pagination,
size: 'default',
//expandedRowRender,
title,
showHeader,
//footer,
// rowSelection: {},
scroll: undefined,
hasData: true
};
}
componentDidMount() {
if (!this.props.data || !this.props.data.length || this.props.data.length === 0) {
return null;
}
}
handleToggle = prop => {
return enable => {
this.setState({ [prop]: enable });
};
};
handleSizeChange = e => {
this.setState({ size: e.target.value });
};
handleExpandChange = enable => {
this.setState({ expandedRowRender: enable ? expandedRowRender : undefined });
};
handleTitleChange = enable => {
this.setState({ title: enable ? title : undefined });
};
handleHeaderChange = enable => {
this.setState({ showHeader: enable ? showHeader : false });
};
handleFooterChange = enable => {
this.setState({ footer: enable ? footer : undefined });
};
handleRowSelectionChange = enable => {
this.setState({ rowSelection: enable ? {} : undefined });
};
handleScollChange = enable => {
this.setState({ scroll: enable ? scroll : undefined });
};
handleDataChange = hasData => {
this.setState({ hasData });
};
handlePaginationChange = e => {
const { value } = e.target;
this.setState({
pagination: value === 'none' ? false : { position: value }
});
};
stringSorter(a, b) {
let a2 = '',
b2 = '';
if (a) {
a2 = a;
}
if (b) {
b2 = b;
}
return a2.localeCompare(b2);
}
stringFilterer(value, record, property) {
let recStatus = '';
if (record[property]) {
recStatus = record[property];
}
return recStatus.indexOf(value) === 0;
}
columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
//width: 50
width: '10%',
sorter: (a, b) => a.id - b.id
},
{
title: 'Name',
dataIndex: 'name',
key: 'name',
//width: 200
width: '15%',
sorter: (a, b) => this.stringSorter(a.name, b.name)
},
{
title: 'Type',
dataIndex: 'type',
key: 'type',
//width: 80
width: '12%',
sorter: (a, b) => this.stringSorter(a.type, b.type),
filters: [
{
text: 'meter',
value: 'meter'
},
{
text: 'climate',
value: 'climate'
},
{
text: 'pole',
value: 'pole'
},
{
text: 'overhead_line',
value: 'overhead_line'
}
],
onFilter: (value, record) => this.stringFilterer(value, record, 'type')
},
{
title: 'Substation',
dataIndex: 'substation',
key: 'substation',
//width: 150
width: '16%',
sorter: (a, b) => this.stringSorter(a.substation, b.substation)
// TODO: Add Filtering by Substation
},
{
title: 'Feeder',
dataIndex: 'feeder',
key: 'feeder',
//width: 100
width: '16%',
sorter: (a, b) => this.stringSorter(a.feeder, b.feeder)
// TODO: Add Filtering by Feeder
},
{
title: 'Status',
dataIndex: 'status',
key: 'status',
//width: 120
width: '18%',
sorter: (a, b) => this.stringSorter(a.status, b.status),
filters: [
{
text: 'IN_SERVICE',
value: 'IN_SERVICE'
},
{
text: 'OUT_OF_SERVICE',
value: 'OUT_OF_SERVICE'
}
],
onFilter: (value, record) => this.stringFilterer(value, record, 'status')
},
{
title: 'Peak\
Vulnerability\
(Pole Stress)',
key: 'vulnerability',
dataIndex: 'vulnerability',
sorter: (a, b) => this.stringSorter(a.vulnerability, b.vulnerability)
//width: 120
}
];
render() {
const { data, handleAssetClick, readyToLoad } = this.props;
{
/* <div style={{ display: 'inline-block', textAlign: 'left' }}>
<Title text="Assets" />
<div>{renderAssets(data, handleAssetClick)}</div>
</div> */
}
return (
<div>
{/* <div className="components-table-demo-control-bar">
<Form layout="inline">
<FormItem label="Bordered">
<Switch checked={this.state.bordered} onChange={this.handleToggle('bordered')} />
</FormItem>
<FormItem label="loading">
<Switch checked={this.state.loading} onChange={this.handleToggle('loading')} />
</FormItem>
<FormItem label="Title">
<Switch checked={!!this.state.title} onChange={this.handleTitleChange} />
</FormItem>
<FormItem label="Column Header">
<Switch checked={!!this.state.showHeader} onChange={this.handleHeaderChange} />
</FormItem>
<FormItem label="Footer">
<Switch checked={!!this.state.footer} onChange={this.handleFooterChange} />
</FormItem>
<FormItem label="Expandable">
<Switch checked={!!this.state.expandedRowRender} onChange={this.handleExpandChange} />
</FormItem>
<FormItem label="Checkbox">
<Switch
checked={!!this.state.rowSelection}
onChange={this.handleRowSelectionChange}
/>
</FormItem>
<FormItem label="Fixed Header">
<Switch checked={!!this.state.scroll} onChange={this.handleScollChange} />
</FormItem>
<FormItem label="Has Data">
<Switch checked={!!this.state.hasData} onChange={this.handleDataChange} />
</FormItem>
<FormItem label="Size">
<Radio.Group size="default" value={this.state.size} onChange={this.handleSizeChange}>
<Radio.Button value="default">Default</Radio.Button>
<Radio.Button value="middle">Middle</Radio.Button>
<Radio.Button value="small">Small</Radio.Button>
</Radio.Group>
</FormItem>
<FormItem label="Pagination">
<Radio.Group
value={this.state.pagination ? this.state.pagination.position : 'none'}
onChange={this.handlePaginationChange}
>
<Radio.Button value="top">Top</Radio.Button>
<Radio.Button value="bottom">Bottom</Radio.Button>
<Radio.Button value="both">Both</Radio.Button>
<Radio.Button value="none">None</Radio.Button>
</Radio.Group>
</FormItem>
</Form>
</div> */}
<Table
size="small"
onRow={record => {
return {
onClick: e => {
this.props.handleAssetClick(e);
} // click row
//onMouseEnter: () => {}, // mouse enter row
};
}}
{...this.state}
columns={this.columns}
dataSource={this.state.hasData ? getAssets(data) : null}
/>
</div>
);
}
}
export default Assets;
This turned out to be a CSS problem. I had padding around all of the SVGs, which made hid these small icons.

Resources