From 82171b652ee32df88c0301660f050ac63cdba4cf Mon Sep 17 00:00:00 2001
From: meskio <meskio@sindominio.net>
Date: Tue, 16 Mar 2021 17:01:46 +0100
Subject: [PATCH] Separate the product editor out of the list

---
 src/Panel.js               |   4 +
 src/product/ProductList.js | 194 ++++++-------------------------------
 src/product/ShowProduct.js | 144 +++++++++++++++++++++++++++
 3 files changed, 180 insertions(+), 162 deletions(-)
 create mode 100644 src/product/ShowProduct.js

diff --git a/src/Panel.js b/src/Panel.js
index ab0633f..ffa697b 100644
--- a/src/Panel.js
+++ b/src/Panel.js
@@ -5,6 +5,7 @@ import MemberAdder from "./member/MemberAdder";
 import MemberEditer from "./member/MemberEditer";
 import MemberList from "./member/MemberList";
 import ProductList from "./product/ProductList";
+import ShowProduct from "./product/ShowProduct";
 import CreateProduct from "./product/CreateProduct";
 import CreateInventary from "./inventary/CreateInventary";
 import InventaryList from "./inventary/InventaryList";
@@ -57,6 +58,9 @@ function LogedPanel(props) {
             <Route path="/product/add">
               <CreateProduct />
             </Route>
+            <Route path="/product/:code">
+              <ShowProduct />
+            </Route>
             <Route path="/inventary/add">
               <CreateInventary />
             </Route>
diff --git a/src/product/ProductList.js b/src/product/ProductList.js
index d3a4ef7..62e2f51 100644
--- a/src/product/ProductList.js
+++ b/src/product/ProductList.js
@@ -1,170 +1,40 @@
-import React from "react";
-import { Table, Button, Alert, Modal } from "react-bootstrap";
+import React, { useState } from "react";
+import { Table } from "react-bootstrap";
+import { LinkContainer } from "react-router-bootstrap";
 import Fetcher from "../Fetcher";
-import EditableCell from "../EditableCell";
-import AuthContext from "../AuthContext";
-import { url } from "../util";
+import { printMoney } from "../util";
 
-class ProductList extends React.Component {
-  static contextType = AuthContext;
-
-  constructor(props) {
-    super(props);
-    this.state = {
-      delete: {
-        name: null,
-      },
-      products: [],
-      error: null,
-    };
-
-    this.modalClose = this.modalClose.bind(this);
-    this.delProduct = this.delProduct.bind(this);
-  }
-
-  update(row, key, value) {
-    this.setState({ error: null });
-
-    let products = this.state.products;
-    const code = products[row].code;
-
-    switch (key) {
-      case "code":
-      case "stock":
-        value = parseInt(value);
-        break;
-      default:
-        break;
-    }
-    products[row][key] = value;
-    this.setState({ products });
-
-    const body = JSON.stringify(products[row]);
-
-    fetch(url("/api/product/" + code), {
-      headers: { "x-authentication": this.context.token },
-      method: "PUT",
-      body,
-    }).then((response) => {
-      if (!response.ok) {
-        this.setState({
-          error: response.status.toString() + " " + response.statusText,
-        });
-      }
-    });
-  }
-
-  delProduct() {
-    const code = this.state.delete.code;
-    let products = this.state.products;
-    const index = products.findIndex((p) => p.code === code);
-    products.splice(index, 1);
-    this.setState({ products });
-    this.modalClose();
-
-    fetch(url("/api/product/" + code), {
-      headers: { "x-authentication": this.context.token },
-      method: "DELETE",
-    }).then((response) => {
-      if (!response.ok) {
-        this.setState({
-          error: response.status.toString() + " " + response.statusText,
-        });
-      }
-    });
-  }
-
-  modalClose() {
-    this.setState({ delete: { name: null } });
-  }
-
-  render() {
-    let alert = null;
-    if (this.state.error !== null) {
-      alert = (
-        <Alert variant="danger">
-          Ha ocurrido un error enviando cambios: {this.state.error}
-        </Alert>
-      );
-    }
-
-    const isAdmin = this.context.role === "admin";
-    const entries = this.state.products.map((product, row) => {
-      return (
-        <tr key={product.code}>
-          <EditableCell
-            onChange={(v) => this.update(row, "code", v)}
-            value={product.code}
-            ro={!isAdmin}
-          />
-          <EditableCell
-            onChange={(v) => this.update(row, "name", v)}
-            value={product.name}
-            ro={!isAdmin}
-          />
-          <EditableCell
-            onChange={(v) => this.update(row, "price", v)}
-            value={product.price}
-            ro={!isAdmin}
-            price
-          />
-          <EditableCell
-            onChange={(v) => this.update(row, "stock", v)}
-            value={product.stock}
-            ro={!isAdmin}
-          />
-          {isAdmin && (
-            <td sm={1}>
-              <Button
-                variant="danger"
-                onClick={() => this.setState({ delete: product })}
-              >
-                -
-              </Button>
-            </td>
-          )}
-        </tr>
-      );
-    });
+function ProductList() {
+  const [products, setProducts] = useState([]);
 
+  const entries = products.map((product) => {
     return (
-      <Fetcher
-        url="/api/product"
-        onFetch={(products) => this.setState({ products })}
-      >
-        {alert}
-        <Table striped bordered hover responsive>
-          <thead>
-            <tr>
-              <th>codigo</th>
-              <th>Nombre</th>
-              <th>Precio</th>
-              <th>Existencias</th>
-              {isAdmin && <th sm={1}></th>}
-            </tr>
-          </thead>
-          <tbody>{entries}</tbody>
-        </Table>
-
-        <Modal show={this.state.delete.name != null} onHide={this.modalClose}>
-          <Modal.Header closeButton>
-            <Modal.Title>Confirmar la elminicacion</Modal.Title>
-          </Modal.Header>
-          <Modal.Body>
-            ¿Borrar permanentemente el producto {this.state.delete.name}?
-          </Modal.Body>
-          <Modal.Footer>
-            <Button variant="secondary" onClick={this.modalClose}>
-              Cancelar
-            </Button>
-            <Button variant="danger" onClick={this.delProduct}>
-              Eliminar
-            </Button>
-          </Modal.Footer>
-        </Modal>
-      </Fetcher>
+      <LinkContainer key={product.code} to={"/product/" + product.code}>
+        <tr>
+          <td>{product.code}</td>
+          <td>{product.name}</td>
+          <td>{printMoney(product.price)}</td>
+          <td>{product.stock}</td>
+        </tr>
+      </LinkContainer>
     );
-  }
+  });
+
+  return (
+    <Fetcher url="/api/product" onFetch={setProducts}>
+      <Table striped bordered hover responsive>
+        <thead>
+          <tr>
+            <th>codigo</th>
+            <th>Nombre</th>
+            <th>Precio</th>
+            <th>Existencias</th>
+          </tr>
+        </thead>
+        <tbody>{entries}</tbody>
+      </Table>
+    </Fetcher>
+  );
 }
 
 export default ProductList;
diff --git a/src/product/ShowProduct.js b/src/product/ShowProduct.js
new file mode 100644
index 0000000..cbd9710
--- /dev/null
+++ b/src/product/ShowProduct.js
@@ -0,0 +1,144 @@
+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 Sender from "../Sender";
+import Fetcher from "../Fetcher";
+import AuthContext from "../AuthContext";
+import { url, printMoney } from "../util";
+
+function ShowProduct() {
+  const { code } = useParams();
+  const auth = useContext(AuthContext);
+  const [product, _setProduct] = useState({
+    price: 0,
+    stock: 0,
+  });
+  const [name, setName] = useState("");
+  const [newCode, setNewCode] = useState(code);
+  const [error, setError] = useState("");
+  const [showDelete, setShowDelete] = useState(false);
+  const [loading, setLoading] = useState(false);
+  const [redirect, setRedirect] = useState(false);
+
+  const setProduct = (p) => {
+    _setProduct(p);
+    setName(p.name);
+  };
+
+  if (redirect) {
+    return <Redirect to="/products" />;
+  }
+
+  if (loading) {
+    return <Spinner animation="border" />;
+  }
+
+  if (error) {
+    return <Alert variant="danger">{error}</Alert>;
+  }
+
+  const handleClose = () => setShowDelete(false);
+
+  const delProduct = () => {
+    setLoading(true);
+    fetch(url("/api/product/" + code), {
+      headers: { "x-authentication": auth.token },
+      method: "DELETE",
+    }).then((response) => {
+      if (!response.ok) {
+        setError(
+          "No pudo eliminar el producto: " +
+            response.status.toString() +
+            " " +
+            response.statusText
+        );
+      } else {
+        setRedirect(true);
+      }
+    });
+  };
+
+  const invalid = !name;
+
+  const body = {
+    name,
+    code: parseInt(newCode),
+    price: product.price,
+    stock: -1,
+  };
+
+  return (
+    <Fetcher url={"/api/product/" + code} onFetch={setProduct} oneShot>
+      <Sender
+        url={"/api/product/" + code}
+        method="PUT"
+        body={body}
+        onSuccess={() => setRedirect(true)}
+      >
+        <Form.Row>
+          <Form.Group as={Col}>
+            <Form.Label>Codigo:</Form.Label>
+            <Form.Control
+              type="number"
+              placeholder="codigo"
+              value={newCode}
+              onChange={(e) => setNewCode(e.target.value)}
+            />
+          </Form.Group>
+          <Form.Group as={Col} xs={12} sm={10}>
+            <Form.Label>Producto:</Form.Label>
+            <Form.Control
+              placeholder="nombre"
+              value={name}
+              onChange={(e) => setName(e.target.value)}
+            />
+          </Form.Group>
+        </Form.Row>
+        <Form.Row>
+          <Form.Group as={Col}>
+            <Form.Label>Precio:</Form.Label>
+            <p>{printMoney(product.price)}</p>
+          </Form.Group>
+          <Form.Group as={Col}>
+            <Form.Label>Cantidad:</Form.Label>
+            <p>{product.stock}</p>
+          </Form.Group>
+        </Form.Row>
+        <Form.Row>
+          <Button type="submit" disabled={invalid}>
+            Guardar
+          </Button>
+          &nbsp;
+          <LinkContainer to="/products">
+            <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 permanentemente el producto {product.name}?
+          </Modal.Body>
+          <Modal.Footer>
+            <Button variant="secondary" onClick={handleClose}>
+              Cancelar
+            </Button>
+            <Button variant="danger" onClick={delProduct}>
+              Eliminar
+            </Button>
+          </Modal.Footer>
+        </Modal>
+      </Sender>
+    </Fetcher>
+  );
+}
+
+export default ShowProduct;
-- 
GitLab