Related
i'm using the following ant component : https://ant.design/components/table/
and i'm really struggling to add the following search option in my table :
here's the table i've created so far :
i'm pretty new to both React and Typescript , since the project is not in js but in typescript i cannot get my code to work properly looking at ant documentation ; i've turned my component to a functional component as i'm gonna be using hooks and added some code to get out most of the compiler errors (type declaration for ts) , still have some syntax errors :
heres my code:
import React, { Component, useState } from 'react';
import {connect} from "react-redux";
// #ts-ignore
import Highlighter from 'react-highlight-words';
//**External Libraries */
//REACT MOSAIC
import {ExpandButton, MosaicWindow, RemoveButton} from "react-mosaic-component";
import {MosaicBranch} from "react-mosaic-component/src/types";
//BLUEPRINTJS
import {InputGroup} from "#blueprintjs/core";
//ANTDESIGN
import { Table , Button ,Tag , Input , Space} from "antd";
import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
import {CaretRightOutlined , SearchOutlined} from '#ant-design/icons';
//STYLES
import './styles.css'
//API
import {useGetAllUtenti} from '../../../api/index';
import { Utente } from '../../../api/types';
const UserSummaryWindow: React.FC<any> = (props) => {
//HOOKS STATE FOR TABLE SEARCH
const [searchText , setSearchText] = useState('');
const [searchedColumn , setSearchedColumn] = useState('');
const {path} = props;
const dataSource: Object | any = useGetAllUtenti().data;
const ruoli: any = [
{
text: 'User',
value: 'user',
},
{
text: 'Administrator',
value: 'administrator',
},
{
text: 'NumeroVerde',
value: 'numeroVerde',
},
]
const stati: any = [
{
text: 'Attivo',
value: 1,
},
{
text: 'Non Attivo',
value: 0,
}
]
const columns: any = [
{
title: 'Nome',
dataIndex: 'nome',
key: 'nome',
defaultSortOrder: 'descend',
sorter: (a:any, b:any) => { return a.nome.localeCompare(b.nome)},
},
{
title: 'Cognome',
dataIndex: 'cognome',
key: 'cognome',
sorter: (a:any, b:any) => { return a.cognome.localeCompare(b.cognome)},
},
{
title: 'Ruolo',
dataIndex: 'ruolo',
key: 'ruolo',
filters: ruoli,
onFilter: (value:any, record:any) => record.ruolo.indexOf(value) === 0,
sorter: (a:any, b:any) => { return a.ruolo.localeCompare(b.ruolo)},
render: (text: string) => (
<>
{
<Tag color={renderTagColor(text)}>
{text.toUpperCase()}
</Tag>
}
</>)
},
{
title: 'Gestore',
dataIndex: 'gestore',
key: 'gestore',
sorter: (a:any, b:any) => { return a.gestore.localeCompare(b.gestore)},
},
{
title: 'Username',
dataIndex: 'username',
key: 'username',
sorter: (a:any, b:any) => { return a.username.localeCompare(b.username)},
},
// {
// title: 'Password',
// dataIndex: 'password',
// key: 'password',
// },
// {
// title: 'IDEnte',
// dataIndex: 'idEnte',
// key: 'idEnte',
// },
{
title: 'Tipo',
dataIndex: 'tipo',
key: 'tipo',
sorter: (a:any, b:any) => a.tipo - b.tipo,
},
{
title: 'Stato',
dataIndex: 'stato',
key: 'stato',
filters: stati,
onFilter: (value:any, record:any) => record.stato.indexOf(value) === 0,
sorter: (a:any, b:any) => a.stato - b.stato,
render :(stato: number) => (
<>
{
(stato == 1) ?
(
<Tag color="#00cc00">
Attivo
</Tag>
) :
(
<Tag color="#ff0000">
Non Attivo
</Tag>
)
}
</>)
},
{
title: 'Ultimo aggiornamento password',
dataIndex: 'ultimoAggiornamentoPassword',
key: 'ultimoAggiornamentoPassword',
sorter: (a:any, b:any) => a.ultimoAggiornamentoPassword.localeCompare(b.ultimoAggiornamentoPassword)
},
{
title: '',
dataIndex: 'idUtente',
key: 'idUtente',
render: () => (
//da inserire link per andare al dettaglio / modifica utente
<Button type="primary"><CaretRightOutlined /></Button>
),
},
]
const toolbarControls = React.Children.toArray([
<ExpandButton/>,
<RemoveButton/>
]);
function renderTagColor(text:string): string {
switch(text) {
case 'user':
return 'gold';
case 'administrator':
return 'red';
case 'numeroVerde':
return 'green';
default:
return '';
}
}
getColumnSearchProps = dataIndex => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div style={{ padding: 8 }}>
<Input
ref={node => {
this.searchInput = node;
}}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
Search
</Button>
<Button onClick={() => handleReset(clearFilters)} size="small" style={{ width: 90 }}>
Reset
</Button>
</Space>
</div>
),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: visible => {
if (visible) {
setTimeout(() => this.searchInput.select());
}
},
render: (text:string) =>
searchText === dataIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[searchText]}
autoEscape
textToHighlight={text.toString()}
/>
) : (
text
),
});
function handleSearch (selectedKeys:any, confirm:any, dataIndex:any) {
confirm();
setSearchText(selectedKeys[0]);
setSearchedColumn(dataIndex);
};
function handleReset (clearFilters:any) {
clearFilters();
setSearchText('');
};
return (
<MosaicWindow<string>
title="Riepilogo Utenti"
path={path}
toolbarControls={toolbarControls}
>
<Table
dataSource={dataSource}
columns={columns}
bordered={true}
//pagination={{ pageSize:3,position: ['bottomCenter'] }}
pagination={{position: ['bottomCenter'] }}
rowKey={'idUtente'}
//stile per righe striped
rowClassName={(record, index) => index % 2 === 0 ? 'table-row-light' : 'table-row-dark'}
/>
</MosaicWindow>
);
};
export default UserSummaryWindow;
The part which is giving me headeache is (need to convert it to typescript [strict option in config file is enabled]):
getColumnSearchProps = dataIndex => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div style={{ padding: 8 }}>
<Input
ref={node => {
this.searchInput = node;
}}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
Search
</Button>
<Button onClick={() => handleReset(clearFilters)} size="small" style={{ width: 90 }}>
Reset
</Button>
</Space>
</div>
),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: visible => {
if (visible) {
setTimeout(() => this.searchInput.select());
}
},
render: (text:string) =>
searchText === dataIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[searchText]}
autoEscape
textToHighlight={text.toString()}
/>
) : (
text
),
});
I'm not here asking you to correct my code , just wanted to know if anyone could share a typescript implementation of that getColumnSearchProps function , or an example of the table component from ant design with typescript code..
Sorry for such a late reply, i was having the same problem.
I found the solution on a github repo someone made. i really wonder why the antd devs don't support typescript in these days.
anyways, here is the repo.
https://github.com/freewind-demos/typescript-react-antd-table-search-column-demo
Basically All the hardwork of moving the old JS code to typescript is done, although there are some linting errors here and there.
I hope anyone who comes looking for this answer really questions their decision to use antd instead of react-table or material-design.
Same answer as Lav Hinsu, but I'll paste the full code here, just in case that link dies.
import "antd/dist/antd.css";
import React from "react";
import ReactDOM from "react-dom";
import { SearchOutlined } from "#ant-design/icons";
import { Table, Button, Input } from "antd";
import { ColumnType } from "antd/lib/table";
function tableColumnTextFilterConfig<T>(): ColumnType<T> {
const searchInputHolder: { current: Input | null } = { current: null };
return {
filterDropdown: ({
setSelectedKeys,
selectedKeys,
confirm,
clearFilters,
}) => (
<div style={{ padding: 8 }}>
<Input
ref={(node) => {
searchInputHolder.current = node;
}}
placeholder="Search"
value={selectedKeys[0]}
onChange={(e) =>
setSelectedKeys(e.target.value ? [e.target.value] : [])
}
onPressEnter={() => confirm()}
style={{ width: 188, marginBottom: 8, display: "block" }}
/>
<Button
type="primary"
onClick={() => confirm()}
icon={<SearchOutlined />}
size="small"
style={{ width: 90, marginRight: 8 }}
>
Search
</Button>
<Button size="small" style={{ width: 90 }} onClick={clearFilters}>
Reset
</Button>
</div>
),
filterIcon: (filtered) => (
<SearchOutlined style={{ color: filtered ? "#1890ff" : undefined }} />
),
onFilterDropdownVisibleChange: (visible) => {
if (visible) {
setTimeout(() => searchInputHolder.current?.select());
}
},
};
}
type Data = {
key: string;
name: string;
};
const data: Data[] = [
{
key: "1",
name: "John Brown",
},
{
key: "2",
name: "Jim Green",
},
{
key: "3",
name: "Joe Black",
},
];
function Hello() {
return (
<div>
<Table
columns={[
{
title: "Name",
dataIndex: "name",
render: (text: string) => text,
...tableColumnTextFilterConfig<Data>(),
onFilter: (value, record) => {
return record.name
.toString()
.toLowerCase()
.includes(value.toString().toLowerCase());
},
},
]}
dataSource={data}
/>
</div>
);
}
ReactDOM.render(<Hello />, document.body);
In short, you can just extract the tableColumnTextFilterConfig function and implement the onFilter property on the desired column.
This is my code:
import React, { useState, useEffect } from 'react';
import { Fade } from "#material-ui/core";
import MaterialTable from 'material-table';
import { makeStyles } from '#material-ui/core/styles';
import './styles.css';
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
width: '70%',
margin: 'auto',
marginTop: 20,
boxShadow: '0px 0px 8px 0px rgba(0,0,0,0.4)'
}
}));
function User(props) {
const classes = useStyles();
const [checked, setChecked] = React.useState(false);
useEffect(() => {
setChecked(prev => !prev);
}, [])
const [state, setState] = React.useState({
columns: [
{ title: 'name', field: 'name' },
{ title: 'Setor', field: 'sector' }
],
data: [
{ name: 'Tom Brady', setor: 'Quarterback'},
{ name: 'Aaron Rodgers', setor: 'Quarterback'},
{ name: 'Ryan Tannehill', setor: 'Quarterback'},
{ name: 'Julian Edelman', setor: 'Wide Receiver'},
{ name: 'Julio Jones', setor: 'Wide Receiver'},
{ name: 'Marcus Mariota', setor: 'Quarterback'},
{ name: 'Patrick Mahomes', setor: 'Quarterback'},
{ name: 'Antonio Brown', setor: 'Wide Receiver'},
{ name: 'Eli Manning', setor: 'Quarterback'},
{ name: 'Antonio Brown', setor: 'Wide Receiver'},
{ name: 'Mike Evans', setor: 'Wide Receiver'},
{ name: 'Russel Wilson', setor: 'Quarterback'},
{ name: 'Drew Brees', setor: 'Quarterback'},
{ name: 'Cam Newton', setor: 'Quarterback'}
],
actions: [
{ icon: 'create', tooltip: 'Edit', onClick: (event, rowData) => alert('Edit ' + rowData.name + '?')},
{ icon: 'lock', tooltip: 'Block', onClick: (event, rowData) => alert('Block ' + rowData.name + '?')},
{ icon: 'delete', tooltip: 'Delete', onClick: (event, rowData) => alert('Delete ' + rowData.name + '?')}
],
options: {
headerStyle: { color: 'rgba(0, 0, 0, 0.54)' },
actionsColumnIndex: -1,
exportButton: true,
paging: true,
pageSize: 10,
pageSizeOptions: [],
paginationType: 'normal'
}
});
return (
<>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<Fade in={checked} style={{ transitionDelay: checked ? '300ms' : '0ms' }}>
<div className={classes.root}>
<MaterialTable options={state.options} title="Users" columns={state.columns} data={state.data} actions={state.actions}></MaterialTable>
</div>
</Fade>
</>
);
}
export default User;
I wanna add this button:
If I follow the default code from here https://material-ui.com/pt/components/tables/, I must add this code:
editable={{
onRowAdd: newData =>
new Promise(resolve => {
setTimeout(() => {
resolve();
setState(prevState => {
const data = [...prevState.data];
data.push(newData);
return { ...prevState, data };
});
}, 600);
}),
But this code calls a specific function for material-table, I wanna call my own function, how can I add the previous button and call my own function?
My own function will open a modal with some inputs, more inputs than I show on table.
You can create custom actions
<MaterialTable
title="Editable Example"
columns={state.columns}
data={state.data}
actions={[
{
icon: "add_box",
tooltip: "my tooltip",
position: "toolbar",
onClick: () => {
console.log("clicked");
}
}
]}
/>
Working demo: https://codesandbox.io/s/material-demo-mgh26
You can override the toolbar, adding your custom button (or some other content!), as explained at https://stackoverflow.com/a/69854673/1288109 :
<MaterialTable
components={{
Toolbar: (props) => (
<div
style={{
display: "flex",
justifyContent: "flex-end",
alignItems: "center"
}}
>
<div style={{ width: "13rem" }}>
<MTableToolbar {...props} />
</div>
<Tooltip title="Add">
<IconButton>
<AddBox />
</IconButton>
</Tooltip>
</div>
),
}}
/>
You can use the position or isFreeAction property for having the action displayed there.
<MaterialTable
title="Editable Example"
columns={state.columns}
data={state.data}
actions={[
{
icon: "add_box",
tooltip: "my tooltip",
isFreeAction: true,
onClick: () => {
console.log("clicked");
}
}
]}
/>
The position parameter is more flexible providing different placements for the Button.
isFreeAction in the other hand accepts just a boolean for the action type.
Sandbox cloned from Mosh Feu https://codesandbox.io/s/material-demo-m8eug
I have been using material-table in one of my projects.
While I am able to change the style ( font-size, color) of the user defined columns, I am not able to do so for the "Actions" column.
I am specially interested in changing the font-size.
Same issue with the pagenation: I need to change its font-size however it seems there is no option available.
Please take an example from :
https://material-ui.com/components/tables/#complementary-projects
For pagination, you should override pagination component.issue, documentation
const useStyles = makeStyles({
root: {
backgroundColor: "blue",
color: "green"
},
toolbar: {
backgroundColor: "white"
},
caption: {
color: "red",
fontSize: "20px"
},
selectIcon: {
color: "green"
},
select: {
color: "green",
fontSize: "20px"
},
actions: {
color: "blue"
}
});
...
<MaterialTable
.....
components={{
Pagination: props => (
console.log(props),
(
<TablePagination
{props.labelDisplayedRows(row)}</div>}
component="div"
colSpan={props.colSpan}
count={props.count}
rowsPerPage={props.rowsPerPage}
page={props.page}
onChangePage={props.onChangePage}
onChangeRowsPerPage={this.onChangeRowsPerPage}
classes={{
root: classes.root,
toolbar: classes.toolbar,
caption: classes.caption,
selectIcon: classes.selectIcon,
select: classes.select,
actions: classes.actions
}}
/>
)
)
}}
For for the "Actions" column, I've used actions property
actions={[
{
icon: "save",
iconProps: { style: { fontSize: "14px", color: "green" } },
tooltip: "Save User",
onClick: (event, rowData) => alert("You saved " + rowData.name)
}
]}
have look at this codesandbox,would be helpful.
if you want to stick to the user-defined theme then use props from icon api of material-ui.
actions={[
{
icon: "save",
iconProps: { fontSize: "small", color: "primary" },
tooltip: "Save User",
onClick: (event, rowData) => alert("You saved " + rowData.name)
}
]}
I tried the material table and wanted to add a menu item in the action, so that it doesn't display too many actions like edit, delete. I have a problem displaying the menu item.
Example code:
<MaterialTable
columns={[
{ title: t('customer'), field: 'name' },
{ title: t('email'), field: 'email' },
{ title: t('contactNumber'), field: 'contactNumber' },
{ title: t('status'), field: 'status' },
]}
data={list}
options={{
headerStyle: {
backgroundColor: '#00b7b2',
},
toolbar: false,
}}
actions={[
{
icon: 'menu',
tooltip: 'Menu',
onClick: (event, rowData) => {
this.openMenu(event)
},
},
]}
components={{
Action: props => (
<div>
<IconButton
onClick={(event) => props.action.onClick(event, props.data)}
>
<Icon>menu</Icon>
</IconButton>
<Menu
anchorEl={this.anchorEl}
open={Boolean(this.anchorEl)}
onClick={event => event.stopPropagation()}
onSelect={event => event.stopPropagation()}
onClose={this.closeMenu}
>
<MenuItem>
Action
</MenuItem>
</Menu>
</div>
),
}}
/>
Is there is a solution for me?
I successfully accomplished this. Basically you are on the right track.
Have an action as you said above:
<MaterialTable
...
actions={[
{
icon: MenuIcon,
tooltip: 'Actions',
isFreeAction: false,
onClick: (event, row) => {
this.openMenu(event, row);
}
}
]}
...
/>
but then have a separate Menu item in render:
<Menu
id="simple-menu"
keepMounted
anchorEl={this.state.menuAnchor}
open={this.state.menuOpen}
onClose={this.handleMenuClose}
>
<MenuItem onClick={this.doSomething}>do something</MenuItem>
<MenuItem onClick={this.doSomethingElse}>do something else</MenuItem>
</Menu>
</center>
Trick is openMenu() should first get the current target:
openMenu(event, row) {
let anchorElement = event.currentTarget;
this.setState({currentRow: row}, () => {
this.setState({menuAnchor: anchorElement});
this.setState({menuOpen: true});
});
}
doSomething() {
row = this.state.currentRow;
// do something
}
https://react.semantic-ui.com/modules/tab#types-basic
I am trying to center the menu tabs to the center (left and right) but I can't figure it out the tab documentation.
import React from 'react'
import { Tab } from 'semantic-ui-react'
const panes = [
{ menuItem: 'Tab 1', render: () => <Tab.Pane>Tab 1 Content</Tab.Pane> },
{ menuItem: 'Tab 2', render: () => <Tab.Pane>Tab 2 Content</Tab.Pane> },
{ menuItem: 'Tab 3', render: () => <Tab.Pane>Tab 3 Content</Tab.Pane> },
]
const TabExampleBasic = () => <Tab panes={panes} />
export default TabExampleBasic
You can do something like this:
<Tab
menu={{
attached: true,
tabular: true,
style: {
display: "flex",
justifyContent: "center"
}
}}
panes={panes}
/>
You have to do it like this :
Tab menu={{ attached: true, tabular: 'right' }} panes={panes}
You can set tabular value to true, left or right.
You can find this on https://react.semantic-ui.com/modules/tab also.