diff --git a/api/topup.go b/api/topup.go index cc6397eda1280d8c6f3f75f5b661f1023fff5744..74ed8559497839742e1df7c913849d9daa65ac07 100644 --- a/api/topup.go +++ b/api/topup.go @@ -43,7 +43,7 @@ func (a *api) AddTopup(adminNum int, w http.ResponseWriter, req *http.Request) { MemberNum: adminNum, Comment: topup.Comment, }, - Type: "toupup", + Type: "topup", Total: topup.Ammount, } err = a.db.Create(&transaction).Error diff --git a/src/App.js b/src/App.js index cc381c5c1edd8d18879cf4fd526fcc2646f2b2a3..b9e81ad18564b7ccb7d3a35be8bec4589f84decf 100644 --- a/src/App.js +++ b/src/App.js @@ -4,6 +4,7 @@ import MemberList from './member'; import ProductList from './ProductList'; import Dashboard from './Dashboard'; import Purchase from './purchase/Purchase'; +import Topup from './Topup'; import ShowTransaction from './ShowTransaction'; import AuthContext from './AuthContext'; import SignIn from './SignIn'; @@ -27,6 +28,9 @@ function Panel(props) { <Route path="/purchase"> <Purchase /> </Route> + <Route path="/topup"> + <Topup /> + </Route> <Route path="/"> <Dashboard /> </Route> diff --git a/src/Head.js b/src/Head.js index f300d89fa7c4a5af2557f4f61ddccc4d5ee0d7f0..acb1efb67f0422ef85526cf14a4b85f1b33b4303 100644 --- a/src/Head.js +++ b/src/Head.js @@ -11,6 +11,7 @@ function Head(props) { adminNav = ( <NavDropdown title="Admin" id="admin"> <Nav.Link href="/members">Socias</Nav.Link> + <Nav.Link href="/topup">Recarga</Nav.Link> </NavDropdown> ); } diff --git a/src/ShowTransaction.js b/src/ShowTransaction.js index 304f0b6f6670d4fde739d442e975f7ab905a5d07..6a48ae1ff69ba6a51b79d76b794570830f8a8166 100644 --- a/src/ShowTransaction.js +++ b/src/ShowTransaction.js @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { useParams } from 'react-router-dom'; -import { Row, Col } from 'react-bootstrap'; +import { Container, Row, Col } from 'react-bootstrap'; import Fetcher from './Fetcher'; import ShowPurchase from './purchase/ShowPurchase'; import { printMoney, printDate } from './util'; @@ -14,12 +14,16 @@ function ShowTransaction() { case "purchase": show_list = <ShowPurchase purchase={transaction.purchase} />; break; + case "topup": + show_list = <p>{transaction.topup.comment}</p>; + break; default: show_list = null; } return ( <Fetcher url={"/api/transaction/"+id} onFetch={setTransaction} > + <Container> {show_list} <Row> <Col> @@ -29,6 +33,7 @@ function ShowTransaction() { <p className="text-right">{printDate(transaction.date)}</p> </Col> </Row> + </Container> </Fetcher> ); } diff --git a/src/Topup.js b/src/Topup.js new file mode 100644 index 0000000000000000000000000000000000000000..ecf4fbc787533292fe7503e55f539f7160776b35 --- /dev/null +++ b/src/Topup.js @@ -0,0 +1,153 @@ +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import Fetcher from './Fetcher'; +import { Container, Form, Col, Row, Button, Alert, Spinner, InputGroup } from 'react-bootstrap'; +import { Typeahead } from 'react-bootstrap-typeahead'; +import AuthContext from './AuthContext'; + +class Topup extends React.Component { + static contextType = AuthContext; + + constructor(props) { + super(props); + this.state = { + members: [], + numInvalid: false, + ammount: 0, + num: null, + name: "", + comment: "", + transactionId: null, + isLoading: false, + badAuth: false, + error: null + }; + } + + setNum(numStr) { + const num = parseInt(numStr); + const member = this.state.members.find(p => p.num === num); + let name = ""; + if (member) { + name = member.name; + } + this.setState({ + num: numStr, + name: name, + numInvalid: name === undefined + }); + } + + setName(member) { + this.setState({ + num: member.num, + name: member.name, + numInvalid: false, + }); + } + + send(e) { + e.preventDefault(); + + this.setState({isLoading: true, error: null}); + const body = JSON.stringify({ + member: parseInt(this.state.num), + comment: this.state.comment, + ammount: parseInt(this.state.ammount)*100 + }); + fetch("/api/topup", {headers: {'x-authentication': this.context.token}, method: "POST", body}) + .then(response => { + if (!response.ok) { + throw new Error(response.status.toString()+' '+response.statusText); + } + return response.json(); + }) + .then(transaction => { + this.setState({transactionId: transaction.ID, isLoading: false}); + }) + .catch(error => { + this.setState({isLoading: false, error: error.message}); + }); + } + + render() { + if (this.state.isLoading) { + return <Spinner animation="border" />; + } + + let alert; + if (this.state.error != null) { + alert = ( + <Alert variant="danger"> + Ha ocurrido un error enviando la compra: {this.state.error} + </Alert> + ); + } + if (this.state.transactionId !== null) { + return <Redirect to={"/transaction/"+this.state.transactionId} />; + } + + return ( + <Fetcher url="/api/member" onFetch={members => this.setState({ members })} > + <Container> + {alert} + <Form onSubmit={e => this.send(e)}> + <Form.Group as={Row}> + <Form.Label as="legend" column sm={2}>Socia</Form.Label> + <Col sm={5}> + <Form.Control + placeholder="numero de socia" + value={this.state.num} + onChange={e => this.setNum(e.target.value)} + isInvalid={this.state.numInvalid} + /> + </Col> + <Col sm={5}> + <Typeahead + id="name" + labelKey="name" + options={this.state.members} + onChange={m => this.setName(m[0])} + selected={this.state.name? [this.state.name]:[]} + /> + </Col> + </Form.Group> + <Form.Group as={Row}> + <Form.Label as="legend" column sm={2}>Recarga</Form.Label> + <Col sm={10}> + <InputGroup> + <Form.Control + type="number" + placeholder="euros" + value={this.state.ammount} + onChange={e => this.setState({ammount: e.target.value})} + /> + <InputGroup.Append> + <InputGroup.Text>.00 €</InputGroup.Text> + </InputGroup.Append> + </InputGroup> + </Col> + </Form.Group> + <Form.Group as={Row}> + <Form.Label as="legend" column sm={2}>Comentario</Form.Label> + <Col sm={10}> + <Form.Control + placeholder="..." + value={this.state.comment} + onChange={e => this.setState({comment: e.target.value})} + /> + </Col> + </Form.Group> + <Form.Group as={Row}> + <Col sm={{ span: 10, offset: 2 }}> + <Button type="submit">Envia</Button> + </Col> + </Form.Group> + </Form> + </Container> + </Fetcher> + ); + } +} + +export default Topup; diff --git a/src/TransactionList.js b/src/TransactionList.js index 9684dcc4db582251b4b0ecd4b4527b3d5f9416b1..98b1e2ef5409e9cec01ea8e68355b9e8ac47c13e 100644 --- a/src/TransactionList.js +++ b/src/TransactionList.js @@ -2,15 +2,21 @@ import React, { useState } from 'react'; import { Redirect } from 'react-router-dom'; import BootstrapTable from 'react-bootstrap-table-next'; import { FaShoppingBasket, FaMoneyBillAlt } from 'react-icons/fa'; +import { GiPayMoney, GiReceiveMoney } from 'react-icons/gi'; import Fetcher from './Fetcher'; import { printMoney, printDate } from './util'; const columns = [ {dataField: 'type', text: '', align: 'center', - formatter: cell => { + formatter: (cell, row) => { switch (cell) { case "purchase": return <FaShoppingBasket />; + case "topup": + if (row.total < 0) { + return <GiReceiveMoney />; + } + return <GiPayMoney />; default: return <FaMoneyBillAlt />; }; @@ -20,7 +26,7 @@ const columns = [ ] function rowStyle(row) { - if (["purchase"].includes(row.type)) { + if (row.total < 0) { return { backgroundColor: "#fcbabf" }; diff --git a/src/member.js b/src/member.js index 454b0cd0eb9de8ebcbc6464186e69329e00033a2..85cd9afac225b96992d74af1043b55c3ef055f86 100644 --- a/src/member.js +++ b/src/member.js @@ -4,7 +4,6 @@ import Fetcher from './Fetcher'; import { printMoney } from './util'; class MemberList extends React.Component { - constructor(props) { super(props); this.state = {