Fetch sum of column - react-admin

I am using React-Admin to create little app for evidence of tools and who borrowed them.
I have table of tools with id, code, name, state atd.
using List to render it.
<List {...props} filters={<ToolFilter />}>
<Datagrid rowClick="edit">
<TextField source="id" />
<TextField source="code" label="Kód" />
<TextField source="name" />
<TextField source="state" />
<NumberField source="free" />
<DateField source="add_time" />
<EditButton />
</Datagrid>
</List>
Than I have table B_tools, that holds data of borrowed tools.
it has id, userId, toolId, durationOfBorrow
What I want to do is to add column to the tools list, that SUMs durationOfBorrow from B_tool table for every tool and render it to list of tools.
for example if I have B_tool table:
id userId toolId durationOfBorrow
1 1 1 7
2 1 1 7
3 2 2 2
4 1 2 2
I need list of tools to look like this:
id code name state durationOfBorrow
1 123 Drill 1 14 (7+7)
2 456 Wrench 1 4 (2+2)
I tried use Querying The API With fetch from React-Admin documentation.
I have prepared route app.get('/api/tools/borrowcount:id', toolController.borrowCount); that shoud return sum of column:
borrowCount(req, res) {
//console.log(req);
const filtry = JSON.parse(req.query.filter);
console.log(filtry);
const options = {
attributes: [
[sequelize.fn('sum', sequelize.col('time')), 'total_time'],
],
raw: true,
where: ({
}),
order:
[]
,
};
if (typeof filtry.id !== "undefined") {
options.where.id = filtry.id;
}
return B_tool
.findAll(options)
//console.log(user);
//res.status(201).send(test);
.then(b_tool => {
//console.log(user.rows);
res.status(200).send(b_tool);
})
.catch(error => res.status(400).send(error));
},
But dont know how to implement it to show it in the tools list.

I got it.
I created element BorrowCount that useQuery api/toolsborrow/:id and returns total_time.
import React from 'react';
import { useQuery, Loading, Error } from 'react-admin';
const BorrowCount = ({ record }) => {
const { data, loading, error } = useQuery({
type: 'getOne',
resource: 'toolsborrow',
payload: { id: record.id }
});
if (loading) return <Loading />;
if (error) return <Error />;
if (!data) return null;
console.log(data);
return (
data[0].total_time
)
};
export default BorrowCount;
In API I call new controller:
app.get('/api/toolsborrow/:id', toolController.borrowCount);
And controller returns SUM of time column where ToolId is id of tool row:
borrowCount(req, res) {
const options = {
attributes: [
[Sequelize.fn('sum', Sequelize.col('time')), 'total_time'],
],
raw: true,
where: ({
ToolId: req.params.id
}),
order:
[]
,
};
return B_tool
.findAll(options)
.then(b_tool => {
res.status(200).send(b_tool);
})
.catch(error => res.status(400).send(error));
},
Finally, I just use this new element BorrowCount in List of tools:
<List {...props} filters={<ToolFilter />}>
<Datagrid rowClick="edit">
<TextField source="id" />
<TextField source="code" label="Kód" />
<TextField source="name" />
<TextField source="state" />
<BorrowCount label="Total_sum"/>
<NumberField source="free" />
<DateField source="add_time" />
<EditButton />
</Datagrid>
</List>
If someone has more elegant solution, please leave it here.

Related

Material UI TextField select not showing initial value on form edit

I have an edit form in a modal that lets you edit user info. Here is the relevant portion:
export const EditUserModal = (Props) => {
const [formValues, setFormValues] = useState(Props.userData);
const [reporterData, setReporterData] = React.useState<any[]>([]);
React.useEffect(() => {
let apiClient = new APIClient();
apiClient.getOwners().then((response) => {
setReporterData(response);
});
}, []);
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormValues({
...formValues,
[name]: value,
});
};
return (
<>
<Modal
open={Props.show}
onClose={() => { Props.toggleModal(false)}}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box
component="form"
autoComplete="off"
>
<Typography id="modal-modal-title" variant="h4" component="h4">
Edit User {Props.userData.name}
</Typography>
<div>
<TextField
required
id="outlined-select-basic"
select
name="reports_to"
label="Reports To"
value={formValues.reports_to}
onChange={handleInputChange}
>
{reporterData.map((option) => (
<MenuItem key={option.owner} value={option.owner}>
{option.owner}
</MenuItem>
))}
</TextField>
</div>
</Box>
</Modal>
</>
)
};
The TextField is supposed to show the initial value of {formValues.reports_to}, but it remains blank (the value is still correct) until you make a new selection. reporterData just returns a list of names(string).
I have tried adding it as a defaultValue, which did not work.

How to fill the form in smartclient?

Lets say there is such form:
{
this._dynamicForm =
<DynamicForm dataSource={InvoiceAdHocChargeDataSource.sc()} useAllDataSourceFields={true}>
<FormFieldItem name="feeCode"
required={true}
/>
<FormFieldItem name="description"
required={true}
/>
<FormFieldItem name="price"
required={true}
width={100}
/>
<FormFieldItem name="quantity"
required={true}
width={100}
/>
<FormFieldItem name="vatCode"
required={true}
width={100}
/>
<FormFieldItem name="remarks"/>
</DynamicForm>
}
How it should be filled with current values? Like I have table of records, click on the table - I want the data from the table row to be in the form fields.
I have done that on table click it reacts, gives me console.log output. But what next?
Cannot find in documenation and quick start quide.
So the main thing I was missing was
layout._dynamicForm.editRecord(record);
full method:
protected onInit(layout: page.invoicing.tabs.InvoiceAdHocChargesTabLayout) {
const onSelectionUpdated = suppressibleFunction((record: InvoiceAdHocChargeDataSource.Record | null) => {
if (record) {
console.log('adhoc on updated' , record);
layout._dynamicForm.editRecord(record);
}
});
layout._listGridRecords.selectionUpdated = onSelectionUpdated
this.tabEvents.onSelectedChanged.add(() => this.load())
//
this.editorEventsRecords.editStarted.add(({record}) => {
// this is running on invoice line double click
console.log('edit started // this is running on invoice line double click');
console.log(record);
this.invoice = record
this.load()
})
}
suppressibleFunction:
export function suppressibleFunction(f: (...args) => any) {
let suppress = false
return Object.assign(
(...args) => suppress ? undefined : f.apply(this, args),
{
suppressWith: <T>(f: () => T): T => {
suppress = true
const value = f()
suppress = false
return value
}
}
)
}

Passing <List> filter values to an action component (REACTADMIN)

How can I get the filter values of my ReactAdmin Element ?
I can get the size of the List, but I don't know how to get the filter, that I can pass to my custom action component:
in my App.tsx:
const MyActions = ({
total,
filters,
filterValues,
...rest
}) => (
<MyButton listsize={total} filters={filters} filterValues={filterValues} />
);
export const MyFilter = (props) => (
<Filter {...props}>
<TextInput label="Search" source="q" alwaysOn/>
</Filter>
);
<List {...props} actions={<MyActions {...props}/>} title="My List" perPage={25} filters={<MyFilter/>}>
<Datagrid rowClick="edit">
<TextField source="id"/>
<TextField source="name"/>
<TextField source="info"/>
</Datagrid>
</List>
);
My Action Button Component;
class MyButton extends React.Component {
constructor(props) {
super(props)
}
render() {
console.log('this.props.listsize : ' + JSON.stringify(this.props.listsize));
console.log('this.props.filters : ' + this.props.filters);
console.log('this.props.filterValues.q : ' + this.props.filterValues.q);
return (
<anyContent...>
)
}
}
The Console Output is just:
this.props.listsize : 145 (Size is correct)
this.props.filters : [object Object]
this.props.filters.filterValues.q : undefined
How can I fetch the current filter for the ?
Filter value can be obtained from the property: filterValues: {q: "test"}
https://marmelab.com/react-admin/List.html#empty-page

Custom React-Admin Component

I am trying to create a custom component for edit/create in react admin. The problem I am facing is that the new updated value is not being taken, what my component is doing is adding and removing values from ChipArray with the values coming from a dropdown select
render() {
return (
<React.Fragment>
<Paper>
{this.state.scopes.map((item, key) => {
return (
<Chip
key={key}
label={item.description}
onDelete={(e) => this.handleDelete(e, item)}
/>
)
})}
</Paper>
<div>
<button onClick={(e) => this.displayChoices(e)}>
{this.state.show ? <span>Hide Scopes Select List</span> : <span>Show Scopes Select List</span>}
</button>
<div/>
{this.state.show ? <Field name="scope" component="Select" placeholder="Spaces"
onChange={(e) => this.handleAdd(e)}>
{this.state.data.map((option, index) =>
<option key={index} value={JSON.stringify(option)}>
{option.description}
</option>
)}
</Field> : ''}
</div>
</React.Fragment>
)
}
}
I manged to remove and add to the ChipArray but when I save the channges were never taken, here is the delete and add functions :
handleAdd = (e) => {
e.preventDefault();
const value = JSON.parse(e.target.value);
let selectedScopes = this.state.scopes;
if (selectedScopes.filter(s => (s.id === value.id)).length === 0) {
selectedScopes.push(value);
this.setState({scopes: [...selectedScopes]});
}
};
handleDelete = (e, value) => {
e.preventDefault();
const selectedScopes = this.state.scopes;
this.setState((prevState, props) => {
return {scopes: prevState.scopes.filter(scope => scope !== value)};
})
};
I know I am supposed to use <Field/> from "react-final-form" but I am not sure how or where.
Really sorry if this question was asked before but I looked online and wasn't so lucky and I am stuck on this.
Thanks

Editing a Material UI row within React + Redux

I have a material UI table in a react project, and I want a user to click on the pencil/Edit Icon to edit the table row.
This is a loaded question, but would this require an additional piece of Material UI? what would the logic look like to make this happen?? I have most of the code set up on the backend, but don't know how to write this code in the component???
Thanks for looking!
//STYLE VARIABLE BOR MATERIAL BUTTON
const style = {
margin: 12
//
};
const mapStateToProps = (state) => ({
user: state.user,
reduxState: state.getExpense
});
class ExpenseTable extends Component {
constructor(props) {
super(props);
this.state = {
getExpense: []
};
}
isSelected = (index) => {
return this.state.selected.indexOf(index) !== -1;
};
handleRowSelection = (selectedRows) => {
this.setState({
selected: selectedRows,
});
};
//on page load, DISPATCH GET_EXPENSE is
//SENT TO expenseSaga which then
//goes to getExpenseReducer and appends EXPENSE_DATA to the
//DOM
componentDidMount() {
const { id } = this.props.match.params;
this.props.dispatch({ type: USER_ACTIONS.FETCH_USER });
this.props.dispatch({ type: 'GET_EXPENSE' });
}
componentDidUpdate() {
if (!this.props.user.isLoading && this.props.user.userName ===
null) {
this.props.history.push('home');
}
}
logout = () => {
this.props.dispatch(triggerLogout());
// this.props.history.push('home');
};
//SETS STATE FOR ALL INPUTS
handleChange = (name) => {
return (event) => {
this.setState({
[name]: event.target.value
});
};
};
//SUBMIT BUTTON- TRIGGERS DISPATCH TO EXPENSE SAGA TO ADD DATA
handleClick = () => {
console.log('add expense', this.state);
this.props.dispatch({
type: 'ADD_EXPENSE',
payload: this.state
});
};
//TRASH ICON-TRIGGERS DISPATCH TO EXPENSE SAGA DELETE
handleClickRemove = (id) => {
console.log('delete expense', this.state);
this.props.dispatch({
type: 'DELETE_EXPENSE',
payload: id
});
};
handleClickEdit = (row) => {
console.log('edit expense', this.state)
this.props.dispatch({
type: 'EDIT_EXPENSE',
payload: row
})
}
render() {
console.log('HEY-oooo expense render', this.state);
let content = null;
if (this.props.user.userName) {
//MAP OVER REDUX STATE.
const tableRows = this.props.reduxState.map((row) => {
//.MAP SEPARATES DATA INTO INDIVIDUAL ITEMS.
const { id, item_description, purchase_date,
item_price, item_link } = row;
return (
<TableRow selectable={false}>
{/* TABLE ROWS */}
<TableRowColumn>{item_description}
</TableRowColumn>
<TableRowColumn>{purchase_date}
</TableRowColumn>
<TableRowColumn>${item_price}</TableRowColumn>
<TableRowColumn><a href={item_link}>{item_link}
</a></TableRowColumn>
<TableRowColumn><EditIcon class="grow:hover"
onClick={() => {this.handleClickEdit(row)}} />
</TableRowColumn>
<TableRowColumn><TrashIcon onClick={() =>
{this.handleClickRemove(id);
}}/>
</TableRowColumn>
</TableRow>
// END TABLE ROWS
);
});
<div>
{/* FORM FOR ADDING EXPENSES(DATA) */}
<form id="expenseForm">
<h3>
Add a new <br />
expense
</h3>
<input
type="text"
id="fname"
name="fname"
placeholder="Item description"
onChange=
{this.handleChange('item_description')}
/>
<br />
<br />
<input
type="text"
id="lname"
name="lname"
placeholder="Item price"
onChange={this.handleChange('item_price')}
/>
<br />
<input
type="text"
id="lname"
name="lname"
placeholder="Item link"
onChange={this.handleChange('item_link')}
/>
<br />
{/* END FORM */}
<RaisedButton
id="expSubmit"
label="Submit Expense"
primary={true}
style={style}
onClick={this.handleClick}
/>
{/* TABLE TOTAL KEEPS CURRENT TOTAL OF PRICE COLOUMN */}
<h1>Total:</h1>
<br />
<h3>$748.93</h3>
</form>
{/* TABLE HEADERS */}
<Table>
<TableHeader>
<TableRow>
<TableHeaderColumn>Item
description</TableHeaderColumn>
<TableHeaderColumn>Purchase
Date</TableHeaderColumn>
<TableHeaderColumn>Item
Price</TableHeaderColumn>
<TableHeaderColumn>Item
Link</TableHeaderColumn>
<TableHeaderColumn>Edit
entry</TableHeaderColumn>
<TableHeaderColumn>Delete
entry</TableHeaderColumn>
</TableRow>
</TableHeader>
<TableBody>
{tableRows}
</TableBody>
</Table>
</div>
);
}
return (
<div>
<Nav />
{content}
</div>
);
}
}
// this allows us to use <App /> in index.js
export default connect(mapStateToProps)(ExpenseTable);
The default material-ui table doesn't have the feature of in-line editing. You will have to write your own wrapper to achieve this which can be time consuming. To overcome the time issue we can use some good existing libraries.
Here's one powerful library from DevExtreme for the same which i used, but go through their licensing before you take the final decision to use.
https://devexpress.github.io/devextreme-reactive/react/grid/demos/featured/controlled-mode/