Skip to content
Snippets Groups Projects
Commit c2aee14c authored by meskio's avatar meskio :tent:
Browse files

Clean up member admin management

Add support for order permitions
parent ab78cbd7
No related branches found
No related tags found
No related merge requests found
......@@ -9,18 +9,19 @@ class Fetcher extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
isLoading: true,
error: null,
};
}
componentDidMount() {
this.setState({ isLoading: true });
this.fetch();
this.timerID = setInterval(
() => this.fetch(),
10000 // every 10 seconds
);
if (!this.props.oneShot) {
this.timerID = setInterval(
() => this.fetch(),
10000 // every 10 seconds
);
}
}
compomentWillUnmount() {
......
......@@ -50,9 +50,11 @@ function Head(props) {
<LinkContainer to="/purchase">
<Nav.Link>Comprar</Nav.Link>
</LinkContainer>
<LinkContainer to="/order/create">
<Nav.Link>Abrir pedido</Nav.Link>
</LinkContainer>
{(auth.role === "order" || auth.role === "admin") && (
<LinkContainer to="/order/create">
<Nav.Link>Abrir pedido</Nav.Link>
</LinkContainer>
)}
</Nav>
<Nav className="ml-auto" activeKey={location.pathname}>
......
import React from "react";
import { Redirect } from "react-router-dom";
import { Form, Button, InputGroup } from "react-bootstrap";
import Sender from "./Sender";
class MemberAdder extends React.Component {
constructor(props) {
super(props);
this.state = {
num: null,
login: null,
name: null,
email: null,
phone: null,
password: null,
euros: 0,
cents: 0,
admin: false,
redirect: false,
};
}
render() {
if (this.state.redirect) {
return <Redirect to="/members" />;
}
const invalid =
!this.state.num || !this.state.login || !this.state.password;
const body = () => {
return {
num: parseInt(this.state.num),
login: this.state.login,
name: this.state.name,
email: this.state.email,
phone: this.state.phone,
password: this.state.password,
balance: parseInt(this.state.euros) * 100 + parseInt(this.state.cents),
role: this.state.admin ? "admin" : "member",
};
};
return (
<Sender
url="/api/member"
body={body}
onSuccess={() => this.setState({ redirect: true })}
>
<Form.Group>
<Form.Label>Numero de socia:</Form.Label>
<Form.Control
type="number"
placeholder="numero"
value={this.state.num}
onChange={(e) => this.setState({ num: e.target.value })}
isInvalid={!this.state.num}
/>
</Form.Group>
<Form.Group>
<Form.Label>Nombre de acceso:</Form.Label>
<Form.Control
placeholder="login"
value={this.state.login}
onChange={(e) => this.setState({ login: e.target.value })}
isInvalid={!this.state.login}
/>
</Form.Group>
<Form.Group>
<Form.Label>Nombre:</Form.Label>
<Form.Control
placeholder="nombre"
value={this.state.name}
onChange={(e) => this.setState({ name: e.target.value })}
/>
</Form.Group>
<Form.Group>
<Form.Label>Email:</Form.Label>
<Form.Control
type="email"
placeholder="email"
value={this.state.email}
onChange={(e) => this.setState({ email: e.target.value })}
/>
</Form.Group>
<Form.Group>
<Form.Label>Telefono:</Form.Label>
<Form.Control
placeholder="telefono"
value={this.state.phone}
onChange={(e) => this.setState({ phone: e.target.value })}
/>
</Form.Group>
<Form.Group>
<Form.Label>Contraseña:</Form.Label>
<Form.Control
type="password"
placeholder="contraseña"
value={this.state.password}
onChange={(e) => this.setState({ password: e.target.value })}
isInvalid={!this.state.password}
/>
</Form.Group>
<Form.Group>
<Form.Check
type="switch"
id="admin"
label="Es administradora"
checked={this.state.admin}
onChange={(e) => this.setState({ admin: e.target.checked })}
/>
</Form.Group>
<Form.Group>
<Form.Label>Saldo inicial:</Form.Label>
<InputGroup>
<Form.Control
placeholder="euros"
value={this.state.euros}
onChange={(e) => this.setState({ euros: e.target.value })}
/>
<InputGroup.Append>
<InputGroup.Text>.</InputGroup.Text>
</InputGroup.Append>
<Form.Control
placeholder="centimos"
value={this.state.cents}
onChange={(e) => this.setState({ cents: e.target.value })}
min="0"
max="99"
/>
<InputGroup.Append>
<InputGroup.Text></InputGroup.Text>
</InputGroup.Append>
</InputGroup>
</Form.Group>
<Button type="submit" disabled={invalid}>
Crear usuaria
</Button>
</Sender>
);
}
}
export default MemberAdder;
import React from "react";
import { Table, Alert, Form, Button } from "react-bootstrap";
import Fetcher from "./Fetcher";
import EditableCell from "./EditableCell";
import AuthContext from "./AuthContext";
import { printMoney, url } from "./util";
class MemberList extends React.Component {
static contextType = AuthContext;
constructor(props) {
super(props);
this.state = {
members: [],
error: null,
};
}
update(row, key, value) {
this.setState({ error: null });
let members = this.state.members;
const num = members[row].num;
if (key === "num") {
value = parseInt(value);
}
members[row][key] = value;
this.setState({ members });
let update = {};
update[key] = value;
const body = JSON.stringify(update);
fetch(url("/api/member/" + num), {
headers: { "x-authentication": this.context.token },
method: "PUT",
body,
}).then((response) => {
if (!response.ok) {
this.setState({
error: response.status.toString() + " " + response.statusText,
});
}
});
}
setAdmin(row, checked) {
const role = checked ? "admin" : "member";
this.update(row, "role", role);
}
delMember(num) {
let members = this.state.members;
const index = members.findIndex((m) => m.num === num);
members.splice(index, 1);
this.setState({ members });
fetch(url("/api/member/" + num), {
headers: { "x-authentication": this.context.token },
method: "DELETE",
}).then((response) => {
if (!response.ok) {
this.setState({
error: response.status.toString() + " " + response.statusText,
});
}
});
}
render() {
let alert = null;
if (this.state.error !== null) {
alert = (
<Alert variant="danger">
Ha ocurrido un error enviando cambios: {this.state.error}
</Alert>
);
}
const entries = this.state.members.map((member, row) => {
return (
<tr key={member.num}>
<EditableCell
onChange={(v) => this.update(row, "num", v)}
value={member.num}
/>
<EditableCell
onChange={(v) => this.update(row, "login", v)}
value={member.login}
/>
<EditableCell
onChange={(v) => this.update(row, "name", v)}
value={member.name}
/>
<EditableCell
onChange={(v) => this.update(row, "email", v)}
value={member.email}
/>
<EditableCell
onChange={(v) => this.update(row, "phone", v)}
value={member.phone}
/>
<td>{printMoney(member.balance)}</td>
<td sm={1}>
<Form>
<Form.Check
type="switch"
id={"admin-" + member.num}
label=""
checked={member.role === "admin"}
onChange={(e) => this.setAdmin(row, e.target.checked)}
/>
</Form>
</td>
<td sm={1}>
<Button variant="danger" onClick={() => this.delMember(member.num)}>
-
</Button>
</td>
</tr>
);
});
return (
<Fetcher
url="/api/member"
onFetch={(members) => this.setState({ members })}
>
{alert}
<Table striped bordered hover>
<thead>
<tr>
<th></th>
<th>Login</th>
<th>Nombre</th>
<th>Email</th>
<th>Telefono</th>
<th>Saldo</th>
<th sm={1}>Admin</th>
<th sm={1}></th>
</tr>
</thead>
<tbody>{entries}</tbody>
</Table>
</Fetcher>
);
}
}
export default MemberList;
import React from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import { Container, Row } from "react-bootstrap";
import MemberAdder from "./MemberAdder";
import MemberList from "./MemberList";
import MemberAdder from "./member/MemberAdder";
import MemberEditer from "./member/MemberEditer";
import MemberList from "./member/MemberList";
import ProductList from "./ProductList";
import Dashboard from "./Dashboard";
import OwnPassword from "./OwnPassword";
......@@ -34,6 +35,9 @@ function LogedPanel(props) {
<Route path="/members/add">
<MemberAdder />
</Route>
<Route path="/member/:num">
<MemberEditer />
</Route>
<Route path="/members/purchase">
<Purchase member />
</Route>
......@@ -52,6 +56,9 @@ function LogedPanel(props) {
<Route path="/purchase">
<Purchase />
</Route>
<Route path="/topup/:num">
<Topup />
</Route>
<Route path="/topup">
<Topup />
</Route>
......
......@@ -143,7 +143,7 @@ class ProductList extends React.Component {
onFetch={(products) => this.setState({ products })}
>
{alert}
<Table striped bordered hover>
<Table striped bordered hover responsive>
<thead>
<tr>
<th>codigo</th>
......
import React, { useState } from "react";
import { Redirect } from "react-router-dom";
import MemberPicker from "./MemberPicker";
import { useParams, Redirect } from "react-router-dom";
import MemberPicker from "./member/MemberPicker";
import { Form, Col, Row, Button, InputGroup } from "react-bootstrap";
import Sender from "./Sender";
function Topup() {
const { num } = useParams();
const [member, setMember] = useState(null);
const [amount, setAmount] = useState(0);
const [comment, setComment] = useState("");
......@@ -27,7 +28,7 @@ function Topup() {
return (
<Sender url="/api/topup" body={body} onSuccess={setTransaction}>
<MemberPicker member={member} onChange={setMember} />
<MemberPicker member={member} onChange={setMember} num={parseInt(num)} />
<Form.Group as={Row}>
<Form.Label as="legend" column sm={2}>
Recarga
......
import React, { useState } from "react";
import { Button, Alert } from "react-bootstrap";
import MemberForm from "./MemberForm";
import Sender from "../Sender";
function MemberAdder() {
const [member, setMember] = useState({
num: "",
name: "",
email: "",
phone: "",
role: "member",
});
const [success, setSuccess] = useState(false);
if (success) {
return (
<Alert variant="success">
{member.num} ya es parte del garbanzo, recibirá un email con un enlace
para poner su contraseña y nombre de acceso.
</Alert>
);
}
const invalid = !member.num || !member.name || !member.email;
return (
<Sender url="/api/member" body={member} onSuccess={() => setSuccess(true)}>
<MemberForm value={member} onChange={setMember} />
<Button type="submit" disabled={invalid}>
Crear socia
</Button>
</Sender>
);
}
export default MemberAdder;
import React, { useState, useContext } from "react";
import { Redirect, useParams } from "react-router-dom";
import { LinkContainer } from "react-router-bootstrap";
import { Form, Col, Spinner, Alert, Button, Modal } from "react-bootstrap";
import MemberForm from "./MemberForm";
import Sender from "../Sender";
import Fetcher from "../Fetcher";
import AuthContext from "../AuthContext";
import { url } from "../util";
function MemberEditer() {
const { num } = useParams();
const auth = useContext(AuthContext);
const [member, setMember] = useState({
num: "",
name: "",
email: "",
phone: "",
role: "member",
});
const [error, setError] = useState("");
const [showDelete, setShowDelete] = useState(false);
const [loading, setLoading] = useState(false);
const [redirect, setRedirect] = useState(false);
const setMembers = (members) => {
const mem = members.find((m) => m.num === parseInt(num));
if (!mem) {
setError("Numero de socia invalido");
} else {
setMember({
num: mem.num,
name: mem.name,
email: mem.email,
phone: mem.phone,
role: mem.role,
});
}
};
const handleClose = () => setShowDelete(false);
const delMember = () => {
setLoading(true);
fetch(url("/api/member/" + num), {
headers: { "x-authentication": auth.token },
method: "DELETE",
}).then((response) => {
if (!response.ok) {
setError(
"No pudo eliminar la socia: " +
response.status.toString() +
" " +
response.statusText
);
} else {
setRedirect(true);
}
});
};
if (redirect) {
return <Redirect to="/members" />;
}
if (loading) {
return <Spinner animation="border" />;
}
if (error) {
return <Alert variant="danger">{error}</Alert>;
}
const invalid = !member.num || !member.name || !member.email;
return (
<Fetcher url="/api/member" onFetch={setMembers} oneShot>
<Sender
url={"/api/member/" + num}
method="PUT"
body={member}
onSuccess={() => setRedirect(true)}
>
<MemberForm value={member} onChange={setMember} />
<Form.Row>
<Button type="submit" disabled={invalid}>
Guardar
</Button>
&nbsp;
<LinkContainer to="/members">
<Button variant="secondary">Cancelar</Button>
</LinkContainer>
<Col className="text-right">
<Button variant="danger" onClick={() => setShowDelete(true)}>
Eliminar
</Button>
</Col>
</Form.Row>
<Modal show={showDelete} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Confirmar la elminicacion</Modal.Title>
</Modal.Header>
<Modal.Body>
¿Borrar la cuenta permanentemente la cuenta de {member.name}?
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Cancelar
</Button>
<Button variant="danger" onClick={delMember}>
Eliminar
</Button>
</Modal.Footer>
</Modal>
</Sender>
</Fetcher>
);
}
export default MemberEditer;
import React from "react";
import { Form } from "react-bootstrap";
import { printRole } from "../util";
function toRole(roleStr) {
switch (roleStr) {
case "admin":
return "admin";
case "pedidos":
return "order";
default:
return "member";
}
}
function MemberForm(props) {
const member = props.value;
const set = (key, value) => {
let m = { ...member };
m[key] = value;
props.onChange(m);
};
return (
<div>
<Form.Group>
<Form.Label>Numero de socia:</Form.Label>
<Form.Control
type="number"
placeholder="numero"
value={member.num}
onChange={(e) => set("num", parseInt(e.target.value))}
isInvalid={!member.num}
/>
</Form.Group>
<Form.Group>
<Form.Label>Nombre:</Form.Label>
<Form.Control
placeholder="nombre"
value={member.name}
onChange={(e) => set("name", e.target.value)}
/>
</Form.Group>
<Form.Group>
<Form.Label>Email:</Form.Label>
<Form.Control
type="email"
placeholder="email"
value={member.email}
onChange={(e) => set("email", e.target.value)}
isInvalid={!member.email}
/>
</Form.Group>
<Form.Group>
<Form.Label>Telefono:</Form.Label>
<Form.Control
placeholder="telefono"
value={member.phone}
onChange={(e) => set("phone", e.target.value)}
/>
</Form.Group>
<Form.Group>
<Form.Label>Rol:</Form.Label>
<Form.Control
as="select"
value={printRole(member.role)}
onChange={(e) => set("role", toRole(e.target.value))}
>
<option>socia</option>
<option>pedidos</option>
<option>admin</option>
</Form.Control>
</Form.Group>
</div>
);
}
export default MemberForm;
import React, { useState } from "react";
import { Table } from "react-bootstrap";
import { LinkContainer } from "react-router-bootstrap";
import { GiPayMoney } from "react-icons/gi";
import Fetcher from "../Fetcher";
import { printMoney, printRole } from "../util";
function MemberList() {
const [members, setMembers] = useState([]);
const entries = members.map((member) => {
return (
<LinkContainer to={"/member/" + member.num}>
<tr key={member.num}>
<td>{member.num}</td>
<td>{member.name}</td>
<td>{member.login}</td>
<td>{member.email}</td>
<td>{member.phone}</td>
<td>{printRole(member.role)}</td>
<td>{printMoney(member.balance)}</td>
<td md={1}>
<LinkContainer to={"/topup/" + member.num}>
<GiPayMoney />
</LinkContainer>
</td>
</tr>
</LinkContainer>
);
});
return (
<Fetcher url="/api/member" onFetch={setMembers}>
<Table striped bordered hover responsive>
<thead>
<tr>
<th></th>
<th>Nombre</th>
<th>Login</th>
<th>Email</th>
<th>Telefono</th>
<th>Rol</th>
<th>Saldo</th>
</tr>
</thead>
<tbody>{entries}</tbody>
</Table>
</Fetcher>
);
}
export default MemberList;
import React, { useState } from "react";
import { Form, Col } from "react-bootstrap";
import { Typeahead } from "react-bootstrap-typeahead";
import Fetcher from "./Fetcher";
import Fetcher from "../Fetcher";
function MemberPicker(props) {
const [members, setStateMembers] = useState([]);
......@@ -13,6 +13,11 @@ function MemberPicker(props) {
return m;
});
setStateMembers(newMembers);
if (!props.member && props.num) {
const member = newMembers.find((m) => m.num === props.num);
props.onChange(member);
}
};
return (
......
......@@ -2,7 +2,7 @@ import React, { useState } from "react";
import { Redirect } from "react-router-dom";
import { Row, Col, Button, Alert } from "react-bootstrap";
import ProductPicker from "../ProductPicker";
import MemberPicker from "../MemberPicker";
import MemberPicker from "../member/MemberPicker";
import Sender from "../Sender";
import { printMoney } from "../util";
......
......@@ -6,6 +6,17 @@ function printDate(date) {
return new Date(date).toLocaleDateString();
}
function printRole(role) {
switch (role) {
case "admin":
return "admin";
case "order":
return "pedidos";
default:
return "socia";
}
}
function url(path) {
let api = process.env.REACT_APP_API;
if (!api) {
......@@ -14,4 +25,4 @@ function url(path) {
return api + path;
}
export { printMoney, printDate, url };
export { printMoney, printDate, printRole, url };
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment