Browse Source

Se agrega la parte de desarrollo del front

master
Matias Gonzalez 4 years ago
parent
commit
985aa2b40f
  1. 45
      README.md
  2. 1
      frontend/.env
  3. 2
      frontend/.gitignore
  4. 32571
      frontend/package-lock.json
  5. 57
      frontend/package.json
  6. BIN
      frontend/public/favicon.ico
  7. 9
      frontend/public/font.css
  8. 29
      frontend/public/index.html
  9. 15
      frontend/public/manifest.json
  10. 3
      frontend/public/robots.txt
  11. 238
      frontend/public/style.css
  12. 65
      frontend/src/App.jsx
  13. 1
      frontend/src/assets/addButton.json
  14. 47
      frontend/src/assets/addCard.svg
  15. BIN
      frontend/src/assets/dificultades-tecnicas-1.jpg
  16. 1
      frontend/src/assets/downArrow.json
  17. 1
      frontend/src/assets/emptyBox.json
  18. 1
      frontend/src/assets/emptySearch.json
  19. 20
      frontend/src/components/AnimationEmptySearch.jsx
  20. 28
      frontend/src/components/ButtonAdd.jsx
  21. 39
      frontend/src/components/ButtonArchived.jsx
  22. 5
      frontend/src/components/Calculator.jsx
  23. 77
      frontend/src/components/CardGrid.jsx
  24. 82
      frontend/src/components/CardGridFabric.jsx
  25. 462
      frontend/src/components/ComponentEdit.jsx
  26. 198
      frontend/src/components/ComponentsProductModel.jsx
  27. 284
      frontend/src/components/DataTableComponents.jsx
  28. 274
      frontend/src/components/DataTableProvidersDetail.jsx
  29. 282
      frontend/src/components/DataTableSupplies.jsx
  30. 201
      frontend/src/components/DataTableSuppliesArchived.jsx
  31. 20
      frontend/src/components/DownArrow.jsx
  32. 330
      frontend/src/components/FormComponent.jsx
  33. 251
      frontend/src/components/FormNewOrder.jsx
  34. 372
      frontend/src/components/FormProductModel.jsx
  35. 531
      frontend/src/components/FormSupply.jsx
  36. 15
      frontend/src/components/GridHome.jsx
  37. 53
      frontend/src/components/ItemComponent.jsx
  38. 105
      frontend/src/components/ModalComponents.jsx
  39. 118
      frontend/src/components/NavBar.jsx
  40. 473
      frontend/src/components/NewProductFabricComponents.jsx
  41. 69
      frontend/src/components/ProductCard.jsx
  42. 74
      frontend/src/components/ProductCardFabric.jsx
  43. 209
      frontend/src/components/ProductModelInfo.jsx
  44. 653
      frontend/src/components/SupplyEdit.jsx
  45. 20
      frontend/src/components/emptyBox.jsx
  46. 30
      frontend/src/hooks/useFetch.js
  47. 16
      frontend/src/index.js
  48. 1
      frontend/src/logo.svg
  49. 373
      frontend/src/reducers/actions.js
  50. 34
      frontend/src/reducers/actionsProcessProducts.js
  51. 33
      frontend/src/reducers/notes.js
  52. 40
      frontend/src/reducers/providers.js
  53. 10
      frontend/src/reducers/store.js
  54. 55
      frontend/src/reducers/supplies.js
  55. 28
      frontend/src/services/categories.js
  56. 96
      frontend/src/services/components.js
  57. 33
      frontend/src/services/notes.js
  58. 48
      frontend/src/services/orders.js
  59. 73
      frontend/src/services/processProduct.js
  60. 48
      frontend/src/services/products.js
  61. 46
      frontend/src/services/providers.js
  62. 201
      frontend/src/services/supplies.js
  63. 26
      frontend/src/utils/formUtils.js
  64. 13
      frontend/src/utils/getComponentsUtils.js
  65. 4
      frontend/src/utils/imageUtils.js
  66. 40
      frontend/src/utils/tableUtils.js
  67. 20
      frontend/src/views/Calculator_View.jsx
  68. 47
      frontend/src/views/Components.jsx
  69. 11
      frontend/src/views/EditComponent.jsx
  70. 11
      frontend/src/views/EditSupply.jsx
  71. 40
      frontend/src/views/FabricDashboard.jsx
  72. 69
      frontend/src/views/Home.jsx
  73. 24
      frontend/src/views/Layout.jsx
  74. 36
      frontend/src/views/NewComponent.jsx
  75. 64
      frontend/src/views/NewOrder_View.jsx
  76. 303
      frontend/src/views/NewProductFabric.jsx
  77. 21
      frontend/src/views/NewProductModel.jsx
  78. 35
      frontend/src/views/NewSupply.jsx
  79. 31
      frontend/src/views/ProductModel.jsx
  80. 54
      frontend/src/views/ProvidersDetail.jsx
  81. 46
      frontend/src/views/Supplies.jsx
  82. 61
      frontend/src/views/SuppliesArchived.jsx

45
README.md

@ -0,0 +1,45 @@
# Instalación y backup de GITEA
En esta documentación se detallará como realizar la instalación de GITEA mediante docker y también como realizar un backup del mismo o restaurarlo mendiando un archivo .sql. El tiempo estimado de descarga e instalación de la base de datos y el servicio GITEA mediante docker es de 3:30 minutos en una Raspberry.
### Pre-requisitos 📋
_Para poder realizar la instalación de gitea es necesario tener instalado en nuestro terminal Docker y Docker Compose. Acceda a [Instalación de docker y docker compose](http://fabrica.faniot.ar:90/Soporte/docker-raspberry) para más detalles._
### Instalación 🔧
_Se deberá ejecutar el archivo docker-compose.yml para que este comience el proceso de descarga e instalación de GITEA y la base de datos Postgres._
```
docker-compose up -d
```
_Una vez terminado el servicio quedará establecido en el puerto 3000 por defecto, puede cambiarse este puerto por otro editando el archivo docker-compose.yml._
## Backup 💽
_Para poder realizar o cargar un backcup es necesario haber instalado previamente el servidor gitea con su base de datos postgres mediante docker._
_Una vez descargado el archivo "backup-gitea.sh" deberá ejecutarlo en su terminal de la siguiente manera:_
```
sudo sh backup-gitea.sh
```
_Una vez ejecutado siga las instrucciones provistas por el script para poder realizar la carga o descarga de su backup de la base de datos Postgres._
## Autores ✒️
* **Matias Gonzalez** - desarrollo de script y documentación - [matias.gonzalez](http://fabrica.faniot.ar:90/matias.gonzalez)
---

1
frontend/.env

@ -0,0 +1 @@
REACT_APP_API='http://192.168.1.18:1997'

2
frontend/.gitignore

@ -0,0 +1,2 @@
node_modules
build

32571
frontend/package-lock.json

File diff suppressed because it is too large

57
frontend/package.json

@ -0,0 +1,57 @@
{
"name": "crud-react",
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0",
"@mui/icons-material": "^5.3.1",
"@mui/material": "^5.4.0",
"@mui/x-data-grid": "^5.5.0",
"@testing-library/jest-dom": "^5.12.0",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
"animate.css": "^4.1.1",
"axios": "^0.21.1",
"lottie-react": "^2.2.1",
"multiselect-react-dropdown": "^2.0.17",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-editext": "^4.2.1",
"react-pro-sidebar": "^0.7.1",
"react-redux": "^7.2.6",
"react-router-dom": "^5.2.0",
"react-scripts": "^5.0.0",
"reakit": "^1.3.11",
"redux": "^4.1.2",
"redux-devtools-extension": "^2.13.9",
"redux-thunk": "^2.4.1",
"socket.io-client": "^4.4.1",
"styled-components": "^5.3.3",
"web-vitals": "^1.1.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

BIN
frontend/public/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

9
frontend/public/font.css

@ -0,0 +1,9 @@
.container-fluid,
.row,
.col-6,
.col-12,
.col-4,
.col-2 {
margin: 0px;
padding: 0px;
}

29
frontend/public/index.html

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" type="text/css" href="/style.css" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
<title>FanStock</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

15
frontend/public/manifest.json

@ -0,0 +1,15 @@
{
"short_name": "FanStock",
"name": "FanStock",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
frontend/public/robots.txt

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

238
frontend/public/style.css

@ -0,0 +1,238 @@
/* futureColor: #1d1d1d */
* {
margin: 0;
padding: 0;
list-style: none;
text-decoration: none;
border: none;
outline: none;
font-family: "Roboto", sans-serif;
}
body {
background: #11181b;
}
.link:link {
color: #fff;
font-size: 1.6rem;
line-height: 1.6rem;
text-decoration: none;
}
a:visited {
color: rgb(228, 228, 228);
}
a:hover {
color: rgb(228, 228, 228);
}
a:active {
color: whitesmoke;
}
.active {
background-color: #3082aa;
color: black;
}
.active a {
color: #fff;
}
.hide {
display: none;
}
.myDIV:hover + .hide {
display: block;
color: grey;
}
/* width */
::-webkit-scrollbar {
width: 3px;
}
/* Track */
::-webkit-scrollbar-track {
box-shadow: inset 0 0 5px rgb(42, 42, 42);
border-radius: 10px;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: rgb(180, 180, 180);
border-radius: 10px;
}
.styleProSidebar > * {
color: whitesmoke !important;
background: #0d1214 !important;
}
.styleProSidebarSM > * {
color: whitesmoke !important;
background: #0d1214 !important;
}
.darkgrey {
background: #23282c; /* navbar */
}
.lightblue {
background: #3082aa;
}
.lightblack {
background: #11181b; /* background bodi */
}
.MuiDataGrid-virtualScrollerContent * {
border: none !important;
}
.MuiDataGrid-menuIcon * {
color: whitesmoke !important;
}
p,
.MuiTablePagination-actions {
color: whitesmoke !important;
}
.textField * {
color: rgb(172, 172, 172) !important;
border-color: rgb(59, 59, 59) !important;
}
.darktext {
color: rgb(42, 42, 42) !important;
font-weight: 500 !important;
}
.textGrey,
.Mui-disabled {
color: #7a7979 !important;
}
.MuiPaper-root {
background: #23282c !important;
color: whitesmoke !important;
}
#mui-1,
.MuiInput-root {
color: whitesmoke !important;
}
.btn-hover:hover {
background-color: opacity(#000000, 0.9);
}
.MuiDialog-paper {
border-color: #26698b !important;
background: #11181b !important;
-webkit-box-shadow: 5px 5px 15px 5px #000000 !important;
box-shadow: 5px 5px 15px 5px #000000 !important;
}
.complete {
text-decoration: line-through !important;
}
.css-1ps6pg7-MuiPaper-root,
.MuiDataGrid-panelContent,
.css-1sjjn1c-MuiDataGrid-panelContent {
background: #23282c !important;
}
.MuiIconButton-sizeSmall * {
color: white !important ;
}
table {
background-color: #23282c;
}
.container-body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
width: 100vw;
}
.container-body-paper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
width: 95vw;
}
.wrapper-header {
width: 95vw;
display: flex;
justify-content: flex-end;
align-items: center;
}
.header-title {
position: absolute;
left: 3%;
color: rgb(82, 82, 82);
font-size: 20px;
}
.text-hover {
color: #3082aa !important;
}
.wrapper-header-providers {
position: absolute;
width: 95vw;
display: flex;
justify-content: flex-end;
align-items: center;
top: 11%;
}
.header-title-providers {
display: flex;
justify-content: space-between;
width: 93vw;
color: #3082aa;
font-size: 20px;
margin-bottom: 20px;
margin-top: 15%;
}
.container-body-providers {
display: flex;
flex-direction: column;
align-items: center;
}
.header-title-neworder {
display: flex;
justify-content: space-between;
width: 65vw;
color: #3082aa;
font-size: 20px;
margin-bottom: 20px;
margin-top: 3%;
}
.MuiMenuItem-root {
background: #23282c !important;
color: whitesmoke !important;
}
.MuiMenuItem-paper {
background: #23282c !important;
color: whitesmoke !important;
}

65
frontend/src/App.jsx

@ -0,0 +1,65 @@
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
//import views
import Home from "./views/Home";
import Components from "./views/Components";
import FormProductModel from "./components/FormProductModel";
import NewComponent from "./views/NewComponent";
import NewProductModel from "./views/NewProductModel";
import ProductModel from "./views/ProductModel";
import Layout from "./views/Layout";
import NewProductFabric from "./views/NewProductFabric";
import FabricDashboard from "./views/FabricDashboard";
import EditComponent from "./views/EditComponent";
import Supplies from "./views/Supplies";
import Calculator_View from "./views/Calculator_View";
import NewSupply from "./views/NewSupply";
import EditSupply from "./views/EditSupply";
import ProvidersDetail from "./views/ProvidersDetail";
import SuppliesArchived from "./views/SuppliesArchived";
import NewOrder_View from "./views/NewOrder_View";
//Utilizo el switch para que no se pierda el navbar a medida que uno se vaya moviendo por las diferentes vistas de la app :D
function App() {
return (
<div
className="App"
style={{
minHeight: "100vh",
margin: "0",
padding: "0",
boxSizing: "border-box",
}}
>
<Router>
<Layout>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/components" component={Components} />
<Route exact path="/test" component={FormProductModel} />
<Route exact path="/newcomponent" component={NewComponent} />
<Route exact path="/newproductmodel" component={NewProductModel} />
<Route exact path="/productmodel" component={ProductModel} />
<Route
exact
path="/newproductfabric"
component={NewProductFabric}
/>
<Route exact path="/fabricdashboard" component={FabricDashboard} />
<Route exact path="/editcomponent" component={EditComponent} />
<Route exact path="/editsupply/:id" component={EditSupply} />
<Route exact path="/supplies" component={Supplies} />
<Route exact path="/archivedsupplies" component={SuppliesArchived} />
<Route exact path="/newsupply" component={NewSupply} />
<Route exact path="/calculator" component={Calculator_View} />
<Route exact path="/providersDetail/:id" component={ProvidersDetail} />
<Route exact path="/neworder/:id" component={NewOrder_View} />
</Switch>
</Layout>
</Router>
</div>
);
}
export default App;

1
frontend/src/assets/addButton.json

File diff suppressed because one or more lines are too long

47
frontend/src/assets/addCard.svg

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 331 331" style="enable-background:new 0 0 331 331;" xml:space="preserve">
<style type="text/css">
.st0{fill:#E6E6E6;}
.st1{clip-path:url(#SVGID_4_);}
.st2{fill:#3598DB;}
</style>
<g>
<rect x="-0.5" y="0.5" class="st0" width="331" height="331"/>
</g>
<g>
<g>
<g>
<defs>
<circle id="SVGID_1_" cx="165" cy="166" r="96.25"/>
</defs>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
</g>
<g>
<defs>
<circle id="SVGID_3_" cx="165" cy="166" r="96.25"/>
</defs>
<use xlink:href="#SVGID_3_" style="overflow:visible;fill:#3598DB;"/>
<clipPath id="SVGID_4_">
<use xlink:href="#SVGID_3_" style="overflow:visible;"/>
</clipPath>
<g class="st1">
<path class="st2" d="M203.16,256.74l-53-53l56.39-57.24l61.06,61.06C267.61,207.55,271.85,252.5,203.16,256.74z"/>
</g>
</g>
<g>
<defs>
<circle id="SVGID_5_" cx="165" cy="166" r="96.25"/>
</defs>
<clipPath id="SVGID_6_">
<use xlink:href="#SVGID_5_" style="overflow:visible;"/>
</clipPath>
</g>
</g>
<polygon class="st0" points="151.43,151.58 151.43,123.6 180.26,123.6 180.26,151.58 207.4,151.58 207.4,180.42 179.42,180.42
179.42,208.4 151.43,208.4 151.43,180.42 122.6,180.42 122.6,151.58 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
frontend/src/assets/dificultades-tecnicas-1.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

1
frontend/src/assets/downArrow.json

@ -0,0 +1 @@
{"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"","k":"","d":"","tc":""},"fr":24,"ip":0,"op":60,"w":400,"h":400,"nm":"seat-2","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":9.6,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[1],"y":[0]},"t":28.8,"s":[100]},{"t":48,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":9.6,"s":[200,-23,0],"to":[0,21.667,0],"ti":[0,-41.667,0]},{"i":{"x":0.667,"y":1},"o":{"x":1,"y":0},"t":28.8,"s":[200,107,0],"to":[0,41.667,0],"ti":[0,-20,0]},{"t":48,"s":[200,227,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-85,4.5],[7,76.5],[99.5,4]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.18823529411764706,0.5098039215686274,0.6666666666666666,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":37,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":6,"op":78,"st":6,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[1],"y":[0]},"t":19.2,"s":[100]},{"t":38.400390625,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[200,70,0],"to":[0,21.667,0],"ti":[0,-38.333,0]},{"i":{"x":0.667,"y":1},"o":{"x":1,"y":0},"t":19.2,"s":[200,200,0],"to":[0,38.333,0],"ti":[0,-16.667,0]},{"t":38.400390625,"s":[200,300,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-85,4.5],[7,76.5],[99.5,4]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.18823529411764706,0.5098039215686274,0.6666666666666666,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":37,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":72,"st":0,"bm":0}],"markers":[]}

1
frontend/src/assets/emptyBox.json

File diff suppressed because one or more lines are too long

1
frontend/src/assets/emptySearch.json

File diff suppressed because one or more lines are too long

20
frontend/src/components/AnimationEmptySearch.jsx

@ -0,0 +1,20 @@
import { useLottie } from "lottie-react";
import emptySearch from "../assets/emptySearch.json";
const EmptySearch = () => {
const options = {
animationData: emptySearch,
loop: true,
autoplay: true,
};
const style = {
height: 150,
};
const { View } = useLottie(options, style);
return <>{View}</>;
};
export default EmptySearch;

28
frontend/src/components/ButtonAdd.jsx

@ -0,0 +1,28 @@
import Button from "@mui/material/Button";
import { useLottie } from "lottie-react";
import addButton from "../assets/addButton.json";
import { Link } from "react-router-dom";
const ButtonAdd = ({ router }) => {
const options = {
animationData: addButton,
loop: true,
autoplay: true,
};
const style = {
height: 70,
};
const { View } = useLottie(options, style);
return (
<Link to={router}>
<Button>
<div>{View}</div>
</Button>
</Link>
);
};
export default ButtonAdd;

39
frontend/src/components/ButtonArchived.jsx

@ -0,0 +1,39 @@
import Button from "@mui/material/Button";
import ArchiveOutlinedIcon from "@mui/icons-material/ArchiveOutlined";
import UnarchiveOutlinedIcon from "@mui/icons-material/UnarchiveOutlined";
import { Link } from "react-router-dom";
const ButtonArchived = ({ router, title, icon }) => {
if (icon === "down") {
return (
<Link to={router} >
<div style={{ marginRight: "15px" }}>
<Button
variant="contained"
style={{ backgroundColor: "#23282c" }}
endIcon={<ArchiveOutlinedIcon />}
>
{title}
</Button>
</div>
</Link>
);
} else {
return (
<Link to={router}>
<div style={{ marginRight: "15px" }}>
<Button
variant="contained"
style={{ backgroundColor: "#23282c" }}
endIcon={<UnarchiveOutlinedIcon />}
>
{title}
</Button>
</div>
</Link>
);
}
};
export default ButtonArchived;

5
frontend/src/components/Calculator.jsx

@ -0,0 +1,5 @@
export default function Calculator() {
return <div style={{display: 'flex', justifyContent: 'center'}}>
<img style={{width: '35%', borderRadius: '75%'}} src={require('../assets/dificultades-tecnicas-1.jpg')} alt="F" />
</div>;
}

77
frontend/src/components/CardGrid.jsx

@ -0,0 +1,77 @@
import React, { useEffect } from "react";
import { Link } from "react-router-dom";
import Grid from "@mui/material/Grid";
import { styled } from "@mui/material/styles";
import ProductCard from "./ProductCard";
import { useDispatch, useSelector } from "react-redux";
import { showImage } from "../utils/imageUtils";
import { Button } from "@mui/material";
import "animate.css";
const useStyles = styled((theme) => ({
root: {
flexGrow: 1,
padding: theme.spacing(2),
},
}));
export default function CardGrid() {
// redux state
const dispatch = useDispatch();
// state
const products = useSelector((state) => state.products);
// process image
const processImage = (imageData, contentType) => {
const img = showImage(imageData, contentType);
return img;
};
return (
// style={{ height: '70vh', overflowY: 'scroll', paddingTop: '2%', marginTop: '1.5%' }}
<div
style={{
display: "flex",
justifyContent: "center",
height: "75vh",
overflowY: "auto",
padding: "3%",
marginTop: "4%",
}}
>
<Grid
container
gap={2}
direction="row"
justifyContent="center"
alignItems="center"
>
{products.map((product) => {
let productImage = processImage(product.imageData, product.imageType);
return (
<Link
className="animate__animated animate__fadeIn"
key={product._id}
to={{
pathname: "/productmodel",
state: { product: product },
}}
style={{ paddingLeft: 13, textDecoration: "none" }}
onClick={() => {
dispatch({ type: "PRODUCT_SELECTED", payload: product });
}}
>
<ProductCard
name={product.name}
description={product.description}
img={productImage}
/>
</Link>
);
})}
</Grid>
</div>
);
}

82
frontend/src/components/CardGridFabric.jsx

@ -0,0 +1,82 @@
import { Link } from "react-router-dom";
import Grid from "@mui/material/Grid";
import { useDispatch, useSelector } from "react-redux";
import { showImage } from "../utils/imageUtils";
import ProductCardFabric from "./ProductCardFabric";
import EmptyBox from "./emptyBox";
import "animate.css";
export default function CardGridFabric() {
// redux state
const dispatch = useDispatch();
// state
const processProducts = useSelector((state) => state.processProducts);
// process image
const processImage = (imageData, contentType) => {
const img = showImage(imageData, contentType);
return img;
};
return (
<div
style={{
display: "flex",
justifyContent: "center",
height: "80vh",
overflowY: "auto",
padding: "2%",
marginTop: "1.5%",
}}
>
<Grid
container
spacing={2}
direction="row"
justifyContent="center"
alignItems="center"
gap={4}
>
{processProducts.length > 0 ? (
processProducts.map((processProduct) => {
let processProductImage = processImage(
processProduct.model.image.data,
processProduct.model.image.contentType
);
return (
<Link
className="animate__animated animate__fadeIn"
to={{
pathname: "/newproductfabric",
state: { product: processProduct },
}}
style={{ paddingLeft: 13, textDecoration: "none" }}
onClick={() => {
dispatch({
type: "PRODUCT_SELECTED",
payload: processProduct,
});
}}
>
<ProductCardFabric
key={processProduct.id}
name={processProduct.name}
description={processProduct.model.name}
img={processProductImage}
stateProduction={processProduct.stateProduction}
/>
</Link>
);
})
) : (
<div
style={{ display: "flex", alignItems: "center", height: "80vh" }}
>
<EmptyBox />
</div>
)}
</Grid>
</div>
);
}

462
frontend/src/components/ComponentEdit.jsx

@ -0,0 +1,462 @@
import Button from "@mui/material/Button";
import { showImage } from "../utils/imageUtils";
import EdiText from "react-editext";
import DeleteIcon from "@mui/icons-material/Delete";
import { useSelector } from "react-redux";
import styled from "styled-components";
import MenuItem from "@mui/material/MenuItem";
import TextField from "@mui/material/TextField";
import { deleteComponent, editComponentById } from "../services/components";
import { useHistory } from "react-router-dom";
import { useState } from "react";
import { createFormData } from "../utils/formUtils";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTheme } from "@mui/material/styles";
const categories = [
{
value: "Generales",
label: "Generales",
},
{
value: "Circuitos integrados",
label: "Circuitos integrados",
},
{
value: "Diodos",
label: "Diodos",
},
{
value: "Fichas, cables y pines",
label: "Fichas, cables y pines",
},
{
value: "Modulos",
label: "Modulos",
},
{
value: "Pasivos",
label: "Pasivos",
},
{
value: "Pantallas",
label: "Pantallas",
},
{
value: "Sensores",
label: "Sensores",
},
{
value: "Otros",
label: "Otros",
},
];
const H1Style = styled(EdiText)`
button {
border: none;
background: rgba(0, 0, 0, 0);
border-radius: 50%;
width: 20px;
}
button:hover {
background: #11181b;
}
button[editext="edit-button"] {
transform: scaleX(-1);
font-size: x-large;
color: grey;
width: 50px;
}
button[editext="save-button"] {
}
button[editext="cancel-button"] {
}
input,
textarea {
font-family: "Roboto";
font-size: x-large;
border: none;
}
div[editext="view-container"],
div[editext="edit-container"] {
font-family: "Roboto";
font-size: xx-large;
color: whitesmoke;
}
`;
const H3Style = styled(EdiText)`
button {
border: none;
background: rgba(0, 0, 0, 0);
border-radius: 50%;
width: 20px;
}
button:hover {
background: #11181b;
}
button[editext="edit-button"] {
transform: scaleX(-1);
font-size: x-large;
color: grey;
width: 50px;
}
button[editext="save-button"] {
}
button[editext="cancel-button"] {
}
input,
textarea {
font-family: "Roboto";
font-size: large;
color: grey;
border: none;
}
div[editext="view-container"],
div[editext="edit-container"] {
font-family: "Roboto";
font-size: large;
color: grey;
color: whitesmoke;
}
`;
const H2Style = styled(EdiText)`
button {
border: none;
background: rgba(0, 0, 0, 0);
border-radius: 50%;
width: 20px;
}
button:hover {
background: #11181b;
}
button[editext="edit-button"] {
transform: scaleX(-1);
font-size: x-large;
color: grey;
width: 50px;
}
button[editext="save-button"] {
}
button[editext="cancel-button"] {
}
input,
textarea {
font-family: "Roboto";
font-size: large;
color: grey;
}
div[editext="view-container"],
div[editext="edit-container"] {
font-family: "Roboto";
font-size: large;
color: whitesmoke;
border-radius: 15px;
padding-left: 8px;
border: 0.1px solid rgba(200, 200, 200, 0.1);
margin-top: 10px;
}
`;
export default function ComponentEdit() {
//popUp
const [open, setOpen] = useState(false);
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("md"));
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
//end PopUp
// history
const history = useHistory();
const componentSelected = useSelector((state) => state.componentSelected);
const [image, setImage] = useState({ preview: "", raw: "" });
// form state
const [form, setForm] = useState({
name: componentSelected.name,
description: componentSelected.description,
image: image,
quantity: componentSelected.quantity,
category: componentSelected.category,
price: componentSelected.price,
location: componentSelected.location,
});
//handlers
//delete service
const handleSubmitDelete = (e) => {
e.preventDefault();
const response = deleteComponent(componentSelected._id);
if (response !== null) {
history.push("/components");
}
};
// handlers for form
const handleForm = (value, inputProp) => {
setForm({
...form,
[inputProp]: value,
});
};
const handleFormCat = (e) => {
setForm({
...form,
[e.target.name]: e.target.value,
});
};
// image
const handleImage = (e) => {
if (e.target.files.length) {
setImage({
preview: URL.createObjectURL(e.target.files[0]),
raw: e.target.files[0],
});
}
setForm({
...form,
image: e.target.files[0],
});
};
//edit service
const handleEditSubmit = (e) => {
e.preventDefault();
const formData = createFormData(form);
const response = editComponentById(componentSelected._id, formData);
if (response !== null) {
}
history.push("/components");
};
// process image
const processImage = (imageData, contentType) => {
const img = showImage(imageData, contentType);
return img;
};
let productImage = processImage(
componentSelected.imageData,
componentSelected.imageType
);
return (
<div
id="container-screen"
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100%",
flexDirection: "column",
}}
>
<div
id="card"
style={{
width: "80%",
height: "60%",
background: "#23282c",
borderRadius: "10px",
display: "flex",
flexDirection: "row",
}}
>
<div
id="left-column"
style={{
display: "flex",
justifyContent: "center",
width: "50%",
alignItems: "center",
// background: "yellow",
}}
>
<div id="imageBox" style={{ maxWidth: "50%" }}>
<Button
href="#"
onClick={(e) => {
e.preventDefault();
document.getElementById("image").click();
}}
>
<img
src={image.preview ? image.preview : productImage}
alt="preview"
style={{
maxWidth: "100%",
borderRadius: "50%",
overflow: "hidden",
}}
/>
</Button>
<input
type="file"
id="image"
name="image"
accept="image/*"
style={{ display: "none" }}
onChange={handleImage}
/>
</div>
</div>
<div
id="right-col"
style={{
width: "50%",
display: "flex",
justifyContent: "center",
flexDirection: "column",
// background: 'yellow'
}}
>
<div
style={{
width: "90%",
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
}}
>
<H1Style
inputProps="name"
type="text"
value={form.name}
onSave={handleForm}
/>
<TextField
className="textField"
name="category"
select
helperText="Seleccione categoría"
defaultValue={componentSelected.category}
style={{ width: "28ch", marginBottom: "30px" }}
onChange={handleFormCat}
>
{categories.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
</div>
<div style={{ height: "200px", width: "90%" }}>
<p className="textGrey">Descripción</p>
<H2Style
inputProps="description"
type="text"
value={form.description}
onSave={handleForm}
/>
</div>
<div
style={{
width: "90%",
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
}}
>
<div style={{ display: "flex", flexDirection: "column" }}>
<p className="textGrey">Ubicacion</p>
<H2Style
inputProps="location"
type="text"
value={form.location}
onSave={handleForm}
/>
</div>
<div style={{ display: "flex", flexDirection: "column" }}>
<p className="textGrey">Cantidad</p>
<H2Style
inputProps="quantity"
type="text"
value={form.quantity}
onSave={handleForm}
/>
</div>
<div style={{ display: "flex", flexDirection: "column" }}>
<p className="textGrey">Precio</p>
<H2Style
inputProps="price"
type="text"
value={form.price}
onSave={handleForm}
/>
</div>
</div>
</div>
</div>
{/* footer */}
<div
style={{
width: "80%",
display: "flex",
justifyContent: "space-between",
marginTop: "1%",
}}
>
<div>
<Button variant="contained" color="error" onClick={handleClickOpen}>
<DeleteIcon />
</Button>
</div>
<Button
className="btn-hover"
variant="contained"
sx={{ background: "#3082AA !important" }}
onClick={handleEditSubmit}
>
CONFIRMAR
</Button>
{/* POP UP DIALOG */}
<Dialog
fullScreen={fullScreen}
open={open}
onClose={handleClose}
aria-labelledby="responsive-dialog-title"
>
<DialogContent>
<h3 style={{ fontWeight: "400", color: "lightgrey " }}>
¿Está seguro/a que desea eliminar el elemento?
</h3>
</DialogContent>
<DialogActions>
<Button className="textGrey" autoFocus onClick={handleClose}>
NO
</Button>
<Button className="textGrey" onClick={handleSubmitDelete} autoFocus>
SI
</Button>
</DialogActions>
</Dialog>
</div>
</div>
);
}

198
frontend/src/components/ComponentsProductModel.jsx

@ -0,0 +1,198 @@
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Paper from "@mui/material/Paper";
export default function ComponentsProductModel({ product }) {
/* useEffect(() => {
rows = [];
rows = product.components.map((component) => {
return {
name: component.name,
description: component.description,
quantity: component.quantity,
category: component.category,
};
});
}, []); */
return (
<div>
<TableContainer
component={Paper}
sx={{
width: "100%",
display: "fluid",
}}
>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead
sx={{
backgroundColor: "#23282C",
}}
>
<TableRow>
<TableCell
sx={{
color: "whitesmoke",
}}
>
Nombre del componente
</TableCell>
<TableCell
sx={{
color: "whitesmoke",
}}
align="right"
>
Categoría
</TableCell>
<TableCell
sx={{
color: "whitesmoke",
}}
align="right"
>
Descripción
</TableCell>
<TableCell
sx={{
color: "whitesmoke",
}}
align="right"
>
Ubicación
</TableCell>
<TableCell
sx={{
color: "whitesmoke",
}}
align="right"
>
Cantidad
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{product.components.map((component) =>
component.statusDB ? (
<TableRow
key={component._id}
sx={{
"&:last-child td, &:last-child th": { border: 0 },
}}
>
<TableCell
component="th"
scope="row"
style={{
background: "#23282C",
color: "whitesmoke",
width: "20%",
}}
>
{component.name}
</TableCell>
<TableCell
style={{
background: "#23282C",
color: "whitesmoke",
}}
align="right"
>
{component.category}
</TableCell>
<TableCell
style={{
background: "#23282C",
color: "whitesmoke",
}}
align="right"
>
{component.description}
</TableCell>
<TableCell
style={{
background: "#23282C",
color: "whitesmoke",
}}
align="right"
>
{component.location}
</TableCell>
<TableCell
style={{
background: "#23282C",
color: "whitesmoke",
}}
align="right"
>
{component.quantity}
</TableCell>
</TableRow>
) : (
<TableRow
key={component._id}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell
component="th"
scope="row"
style={{
color: "rgb(100, 100, 100)",
background: "#23282C",
}}
>
{component.name}
</TableCell>
<TableCell
align="right"
style={{
color: "rgb(100, 100, 100)",
background: "#23282C",
}}
>
{component.quantity}
</TableCell>
<TableCell
align="right"
style={{
color: "rgb(100, 100, 100)",
background: "#23282C",
}}
>
{component.location}
</TableCell>
<TableCell
align="right"
style={{
color: "rgb(100, 100, 100)",
background: "#23282C",
}}
>
{component.description}
</TableCell>
<TableCell
align="right"
style={{
color: "rgb(100, 100, 100)",
background: "#23282C",
}}
>
{component.category}
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
</TableContainer>
</div>
);
}

284
frontend/src/components/DataTableComponents.jsx

@ -0,0 +1,284 @@
import React, { useEffect, useRef, useState } from "react";
import { DataGrid } from "@mui/x-data-grid";
import { useDispatch, useSelector } from "react-redux";
import { getComponents } from "../services/components";
import { Button, CircularProgress, TextField } from "@mui/material";
import { showImage } from "../utils/imageUtils";
import { useHistory } from "react-router-dom";
import EditIcon from "@mui/icons-material/Edit";
import NoteIcon from "@mui/icons-material/Notes";
import EmptySearch from "./AnimationEmptySearch";
// import { addOrUpdateNotesThunk, getNotesThunk } from "../reducers/notes";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTheme } from "@mui/material/styles";
const columns = [
{ field: "id", headerName: "ID", width: 70, hide: true },
{
field: "image",
headerName: "Imagen",
width: 80,
renderCell: (rowData) => {
return (
<div
style={{
borderRadius: "50%",
overflow: "hidden",
width: "60px",
height: "60px",
}}
>
<img
style={{
height: "100%",
width: "100%",
}}
src={rowData.row.image}
/>
</div>
);
},
},
{ field: "name", headerName: "Nombre", width: 200 },
{ field: "category", headerName: "Categoría", width: 190 },
{ field: "description", headerName: "Descripción", width: 200 },
{ field: "location", headerName: "Ubicación", width: 130 },
{ field: "quantity", headerName: "Cantidad", width: 100 },
{ field: "unity", headerName: "Unidad de medida", width: 150 },
{ field: "price", headerName: "Precio", width: 100 },
{ field: "entryDate", headerName: "Fecha de Ingreso", width: 150 },
{
field: "note",
headerName: "Notas",
width: 100,
renderCell: (rowData) => (
<Button
className="btn-hover"
variant="contained"
sx={{ background: "#3082AA !important" }}
onClick={() => {
rowData.row.note(rowData.row);
}}
>
<NoteIcon />
</Button>
),
},
{
field: "edit",
headerName: "Editar",
width: 90,
renderCell: (rowData) => (
<Button
className="btn-hover"
variant="contained"
sx={{ background: "#3082AA !important" }}
onClick={() => {
rowData.row.edit(rowData.row);
}}
>
<EditIcon />
</Button>
),
},
];
const DataTableComponents = () => {
//popUp
const [open, setOpen] = useState(false);
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("md"));
const [selectedComponentModal, setSelectedComponentModal] = useState();
const notesThunk = useSelector((state) => state.notesThunk);
// get note by id component
const handleClickOpen = async (id) => {
// await dispatch(getNotesThunk(id));
setSelectedComponentModal(id);
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
//reference to textarea
const textAreaRef = useRef();
//end PopUp
const history = useHistory();
const components = useSelector((state) => state.componentsFull);
const dispatch = useDispatch();
const getComponentsFromServer = async () => {
const data = await getComponents();
return data;
};
useEffect(() => {
const fetchData = async () => {
const data = await getComponentsFromServer();
dispatch({ type: "GET_COMPONENTS", payload: data });
};
fetchData();
}, []);
const processImage = (imageData, imageType) => {
const img = showImage(imageData, imageType);
return img;
};
// handlers
const handleSubmitModal = (id, data) => {
let note = {
idComponent: id,
data: data,
};
// dispatch(addOrUpdateNotesThunk(selectedComponentModal, note));
setOpen(false);
};
return (
<div style={{ height: "70vh", width: "100%" }}>
{components.length > 0 ? (
<>
<DataGrid
autoPageSize={true}
columns={columns}
rows={components.map((component) => {
// process image
let img;
if (component.imageType) {
img = processImage(component.imageData, component.imageType);
}
return {
id: component._id,
name: component.name,
description: component.description,
image: img,
category: component.category,
location: component.location,
quantity: component.quantity,
price: component.price,
unity: 'm2',
entryDate: '21/04/2021',
edit: () => {
dispatch({ type: "COMPONENT_SELECTED", payload: component });
history.push("/editcomponent");
},
note: async () => {
handleClickOpen(component._id);
},
};
})}
getRowId={(row) => {
return row.id;
}}
onRowClick={(row) => {}}
rowHeight={80}
rowHeightMode="fixed"
rowSelectionMode="None"
selectionMode="None"
sx={{
height: "100%",
width: "100%",
background: "#23282C",
color: "whitesmoke",
border: "1px solid #23282C",
"& .super-app-theme--header": {
backgroundColor: "rgba(255, 7, 0, 0.55)",
},
boxShadow: 8,
}}
/>
{/* POP UP DIALOG */}
<Dialog
fullWidth={true}
fullScreen={fullScreen}
open={open}
onClose={handleClose}
aria-labelledby="responsive-dialog-title"
>
<DialogContent>
{!notesThunk.loading ? (
<TextField
ref={textAreaRef}
hiddenLabel
className="textField"
name="note"
label="Notas"
defaultValue={
!notesThunk.data[0] ? "" : notesThunk.data[0].data
}
multiline
maxRows={4}
variant="outlined"
style={{ marginBottom: "2ch" }}
inputProps={{
style: {
width: 500,
height: 80,
},
}}
/>
) : (
<CircularProgress />
)}
</DialogContent>
<DialogActions>
<Button className="textGrey" autoFocus onClick={handleClose}>
CANCELAR
</Button>
<Button
className="textGrey"
onClick={(e) => {
let id = selectedComponentModal;
let data = textAreaRef.current.children[1].children[0].value;
handleSubmitModal(id, data);
}}
autoFocus
>
GUARDAR
</Button>
</DialogActions>
</Dialog>
</>
) : (
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
height: "90%",
}}
>
<EmptySearch />
{/* lottie animation */}
</div>
)}
</div>
);
};
export default React.memo(DataTableComponents);

274
frontend/src/components/DataTableProvidersDetail.jsx

@ -0,0 +1,274 @@
import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import Box from "@mui/material/Box";
import Collapse from "@mui/material/Collapse";
import IconButton from "@mui/material/IconButton";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell, { tableCellClasses } from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
import { styled } from "@mui/material/styles";
import { getListProvidersByIdSupply } from "../services/orders";
import { grey } from "@mui/material/colors";
const StyledTableCell = styled(TableCell)(() => ({
[`&.${tableCellClasses.head}`]: {
backgroundColor: "#23282c",
color: "#fff",
fontSize: 16,
height: "10px",
},
[`&.${tableCellClasses.body}`]: {
fontSize: 14,
color: "#fff",
border: "unset",
backgroundColor: "#23282c",
paddingTop: "5px",
paddingBottom: "5px",
},
}));
const StyledTableRow = styled(TableRow)(() => ({
"&:nth-of-type(odd)": {
backgroundColor: "#23282c",
height: "20px",
},
// hide last border
"&:last-child td, &:last-child th": {
border: 0,
},
}));
function createData(
name,
price,
lastOrderQuantity,
lastOrderDate,
description,
orders,
providersFull
) {
/* console.log("provFULL: ");
console.log(
"name:" + name,
"price: " + price,
"lastOrderQuantity: " + lastOrderQuantity,
"lastOrderDate: " + lastOrderDate,
"description: " + description,
"orders: " + orders,
"providersFull: " + providersFull
);
console.log("providersFull dsde ACa");
console.log(providersFull); */
if (orders) {
const providerMaped = orders.map((order) => {
console.log("provMap: " + order.amount);
console.log("provMap- name: " + order.date);
return {
key: order.amount,
dateH: order.date.slice(0, 10),
priceH: order.amount,
quantityH: order.quantity,
};
});
return {
name,
price,
lastOrderQuantity,
lastOrderDate,
description,
history: providerMaped,
};
} else {
return {
name,
price,
lastOrderQuantity,
lastOrderDate,
description,
history: [],
};
}
}
function Row(props) {
const { row } = props;
const [open, setOpen] = React.useState(false);
return (
<React.Fragment>
<StyledTableRow
sx={{ "& > *": { borderBottom: "unset" }, background: "#23282C" }}
>
<StyledTableCell>
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
>
{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
</IconButton>
</StyledTableCell>
{/* <StyledTableCell component="th" scope="row">{row._id}</StyledTableCell> */}
<StyledTableCell style={{ display: "none" }} align="right">
{row._id}
</StyledTableCell>
<StyledTableCell align="left">{row.name}</StyledTableCell>
<StyledTableCell align="right">{row.price}</StyledTableCell>
<StyledTableCell align="right">{row.lastOrderQuantity}</StyledTableCell>
<StyledTableCell align="right">{row.lastOrderDate}</StyledTableCell>
</StyledTableRow>
<StyledTableRow>
<StyledTableCell style={{ paddingTop: 0 }} colSpan={6}>
<Collapse in={open} timeout="auto" unmountOnExit>
<Box sx={{ marginTop: 1, borderBottom: "0.5px solid grey" }}>
<h4
style={{ paddingLeft: 15, color: "#3082aa", fontWeight: "100" }}
>
Historial
</h4>
<Table
size="small"
aria-label="historial"
style={{ width: "100%" }}
>
<TableHead>
<StyledTableRow>
<StyledTableCell
style={{ fontSize: "13px" }}
align="right"
></StyledTableCell>
<StyledTableCell style={{ fontSize: "13px" }} align="right">
Precio
</StyledTableCell>
<StyledTableCell style={{ fontSize: "13px" }} align="right">
Cantidad
</StyledTableCell>
<StyledTableCell style={{ fontSize: "13px" }} align="right">
Fecha de ingreso
</StyledTableCell>
</StyledTableRow>
</TableHead>
<TableBody>
{row.history.map((historyRow) => (
<StyledTableRow key={historyRow._id}>
<StyledTableCell
style={{ fontSize: "13px" }}
align="right"
width="29%"
></StyledTableCell>
<StyledTableCell
style={{ fontSize: "13px" }}
align="right"
>
{historyRow.priceH}
</StyledTableCell>
<StyledTableCell
style={{ fontSize: "13px" }}
align="right"
width="17%"
>
{historyRow.quantityH}
</StyledTableCell>
<StyledTableCell
style={{ fontSize: "13px" }}
component="th"
scope="row"
align="right"
>
{historyRow.dateH}
</StyledTableCell>
</StyledTableRow>
))}
</TableBody>
</Table>
</Box>
</Collapse>
</StyledTableCell>
</StyledTableRow>
</React.Fragment>
);
}
export default function DataTableProvidersDetail(props) {
console.log("providers desde dataTableP");
console.log(props.selectedSupply);
const [orders, setOrders] = useState([]);
let total = 0;
const params = useParams();
const loadOrders = async () => {
const orders = await getListProvidersByIdSupply(params.id);
setOrders(orders);
};
useEffect(() => {
loadOrders();
}, []);
/* console.log("ver los orders");
console.log(orders); */
const rows = orders.map((e) => {
total += e.total;
props.funcTotal(total);
// last orders amount
const lastOrder = e.orders[e.orders.length - 1].amount;
//last orders quantity
const lastOrderQuantity = e.orders[e.orders.length - 1].quantity;
// last orders date
const lastOrderDate = e.orders[e.orders.length - 1].date.slice(0, 10);
return createData(
e.name.name,
lastOrder,
lastOrderQuantity,
lastOrderDate,
e.name.description,
e.orders,
orders
);
});
return (
<>
<TableContainer component={Paper} style={{ width: "93%" }}>
<Table aria-label="collapsible table">
<TableHead>
<StyledTableRow>
<StyledTableCell />
<StyledTableCell>Proveedor</StyledTableCell>
<StyledTableCell align="right">Precio</StyledTableCell>
<StyledTableCell align="right">Cantidad</StyledTableCell>
<StyledTableCell align="right">
última Fecha de Ingreso
</StyledTableCell>
</StyledTableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<Row key={row._id} row={row} />
))}
</TableBody>
</Table>
</TableContainer>
</>
);
}

282
frontend/src/components/DataTableSupplies.jsx

@ -0,0 +1,282 @@
import React, { useEffect, useRef, useState } from "react";
import { DataGrid } from "@mui/x-data-grid";
import { useDispatch, useSelector } from "react-redux";
import { Button, CircularProgress, TextField } from "@mui/material";
import { useHistory } from "react-router-dom";
import EditIcon from "@mui/icons-material/Edit";
import NoteIcon from "@mui/icons-material/Notes";
import EyeIcon from "@mui/icons-material/Visibility";
import EmptySearch from "./AnimationEmptySearch";
import { addOrUpdateNotesThunk, getNotesThunk } from "../reducers/notes";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTheme } from "@mui/material/styles";
import { assignProviderAndPrice } from "../services/supplies";
import { configure } from "@testing-library/react";
// import { getSuppliesThunk, newProviderThunkRedux } from "../reducers/supplies";
// import { ContentPasteGoOutlined } from "@mui/icons-material";
const columns = [
{ field: "id", headerName: "ID", width: 70, hide: true },
{
field: "imageSupply",
headerName: "Imagen",
width: 80,
renderCell: (rowData) => {
return (
<div
style={{
borderRadius: "50%",
overflow: "hidden",
width: "55px",
height: "55px",
}}
>
<img
alt="imagen"
style={{
height: "100%",
width: "100%",
}}
src={rowData.row.imageSupply}
/>
</div>
);
},
},
{ field: "name", headerName: "Nombre", flex: 1, minWidth: 70 },
{ field: "code", headerName: "Código", flex: 1, minWidth: 70 },
{ field: "category", headerName: "Categoría", flex: 1, minWidth: 100 },
{ field: "description", headerName: "Descripción", flex: 1, minWidth: 100 },
{ field: "location", headerName: "Ubicación", flex: 1, minWidth: 50 },
{ field: "unity", headerName: "Unidad de medida", flex: 1, minWidth: 80 },
{
field: "note",
headerName: "Notas",
width: 100,
renderCell: (rowData) => (
<Button
className="btn-hover"
variant="contained"
sx={{ background: "#3082AA !important" }}
onClick={() => {
rowData.row.note(rowData.row);
}}
>
<NoteIcon />
</Button>
),
},
{
field: "providers",
headerName: "Proveedores",
width: 100,
renderCell: (rowData) => (
<Button
className="btn-hover"
variant="contained"
sx={{ background: "#3082AA !important" }}
onClick={() => {
rowData.row.providers(rowData.row);
}}
>
<EyeIcon />
</Button>
),
},
{
field: "edit",
headerName: "Editar",
width: 90,
renderCell: (rowData) => (
<Button
className="btn-hover"
variant="contained"
sx={{ background: "#3082AA !important" }}
onClick={() => {
rowData.row.edit(rowData.row);
}}
>
<EditIcon />
</Button>
),
},
];
const DataTableSupplies = ({ suppliesThunk }) => {
const supplies = suppliesThunk.data;
//popUp
const [open, setOpen] = useState(false);
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("md"));
const [selectedComponentModal, setSelectedComponentModal] = useState();
const notesThunk = useSelector((state) => state.notesThunk);
// get note by id component
console.log("supplies ---->" + supplies);
const handleClickOpen = async (id) => {
await dispatch(getNotesThunk(id));
setSelectedComponentModal(id);
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
//reference to textarea
const textAreaRef = useRef();
//end PopUp
const history = useHistory();
const dispatch = useDispatch();
// handlers
const handleSubmitModal = (id, note) => {
dispatch(addOrUpdateNotesThunk(id, note));
setOpen(false);
};
return (
<div style={{ height: "70vh", width: "100%" }}>
{supplies.length > 0 ? (
<>
<DataGrid
autoPageSize={true}
columns={columns}
rows={supplies.map((supply, index) => {
return {
id: supply._id,
name: supply.name,
code: supply.code,
description: supply.description,
imageSupply: supply.imageSupply,
category: supply.category.name,
location: supply.location,
unity: supply.unity,
// edit: () => {
// history.push(`/editsupply/${supply._id}`);
// },
edit: () => {
dispatch({
type: "COMPONENT_SELECTED",
payload: supply,
});
// console.log("versupply", supply);
history.push(`/editsupply/${supply._id}`);
},
note: async () => {
handleClickOpen(supply._id);
},
providers: () => {
dispatch({
type: "COMPONENT_SELECTED",
payload: supply,
});
history.push(`/providersDetail/${supply._id}`);
},
};
})}
getRowId={(row) => {
return row.id;
}}
onRowClick={(row) => {}}
rowHeight={65}
rowHeightMode="fixed"
rowSelectionMode="None"
selectionMode="None"
sx={{
fontSize: "0.8rem",
height: "100%",
width: "100%",
background: "#23282C",
color: "whitesmoke",
border: "1px solid #23282C",
"& .super-app-theme--header": {
backgroundColor: "rgba(255, 7, 0, 0.55)",
},
boxShadow: 8,
}}
/>
{/* POP UP DIALOG */}
<Dialog
fullWidth={true}
fullScreen={fullScreen}
open={open}
onClose={handleClose}
aria-labelledby="responsive-dialog-title"
>
<DialogContent>
{!notesThunk.loading ? (
<TextField
ref={textAreaRef}
hiddenLabel
className="textField"
name="note"
label="Notas"
defaultValue={!notesThunk.data ? "" : notesThunk.data}
multiline
maxRows={4}
variant="outlined"
style={{ marginBottom: "2ch" }}
inputProps={{
style: {
width: 500,
height: 80,
},
}}
/>
) : (
<CircularProgress />
)}
</DialogContent>
<DialogActions>
<Button className="textGrey" autoFocus onClick={handleClose}>
CANCELAR
</Button>
<Button
className="textGrey"
onClick={(e) => {
let id = selectedComponentModal;
let data = textAreaRef.current.children[1].children[0].value;
handleSubmitModal(id, data);
}}
autoFocus
>
GUARDAR
</Button>
</DialogActions>
</Dialog>
</>
) : (
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
height: "90%",
}}
>
<EmptySearch />
{/* lottie animation */}
</div>
)}
</div>
);
};
export default React.memo(DataTableSupplies);

201
frontend/src/components/DataTableSuppliesArchived.jsx

@ -0,0 +1,201 @@
import React, { useState } from "react";
import { DataGrid } from "@mui/x-data-grid";
import { useDispatch} from "react-redux";
import { Button} from "@mui/material";
import { useHistory } from "react-router-dom";
import DeleteIcon from "@mui/icons-material/Delete";
import RestorePageIcon from "@mui/icons-material/RestorePage";
import EmptySearch from "./AnimationEmptySearch";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTheme } from "@mui/material/styles";
import { deleteSupply, deleteSupplyDefinitely } from "../services/supplies";
const columns = [
{ field: "id", headerName: "ID", width: 70, hide: true },
{
field: "imageSupply",
headerName: "Imagen",
width: 80,
renderCell: (rowData) => {
return (
<div
style={{
borderRadius: "50%",
overflow: "hidden",
width: "55px",
height: "55px",
}}
>
<img
alt="imagen"
style={{
height: "100%",
width: "100%",
opacity: "0.5",
}}
src={rowData.row.imageSupply}
/>
</div>
);
},
},
{ field: "name", headerName: "Nombre", flex: 1, minWidth: 70 },
{ field: "code", headerName: "Código", flex: 1, minWidth: 70 },
{ field: "category", headerName: "Categoría", flex: 1, minWidth: 100 },
{ field: "description", headerName: "Descripción", flex: 1, minWidth: 100 },
{ field: "location", headerName: "Ubicación", flex: 1, minWidth: 50 },
{ field: "quantity", headerName: "Cantidad", flex: 1, minWidth: 50 },
{ field: "unity", headerName: "Unidad de medida", flex: 1, minWidth: 80 },
{ field: "price", headerName: "Precio", flex: 1, minWidth: 30 },
{
field: "entryDate",
headerName: "Fecha de Ingreso",
flex: 1,
minWidth: 50,
},
{
field: "restore",
headerName: "Restaurar",
width: 100,
renderCell: (rowData) => (
<Button
className="btn-hover"
variant="contained"
color="success"
onClick={() => {
rowData.row.restore(rowData.row);
}}
>
<RestorePageIcon />
</Button>
),
},
{
field: "deleteDB",
headerName: "Eliminar",
width: 90,
renderCell: (rowData) => (
<Button
className="btn-hover"
variant="contained"
color="error"
onClick={() => {
rowData.row.deleteDB(rowData.row);
}}
>
<DeleteIcon />
</Button>
),
},
];
const DataTableSupplies = ({ suppliesThunk }) => {
const supplies = suppliesThunk;
//delete service
const handleSubmitDelete = (supplyId) => {
const response = deleteSupplyDefinitely(supplyId);
if (response !== null) {
history.push("/supplies");
}
};
// restore handler
const handleRestore = (supplyId) => {
const response = deleteSupply(supplyId);
if (response !== null) {
history.push("/supplies");
}
};
const history = useHistory();
const dispatch = useDispatch();
return (
<div style={{ height: "70vh", width: "100%" }}>
{supplies.length > 0 ? (
<>
<DataGrid
autoPageSize={true}
columns={columns}
rows={supplies.map((supply, index) => {
return {
id: supply._id,
name: supply.name,
code: supply.code,
description: supply.description,
imageSupply: supply.imageSupply,
category: supply.category.name,
location: supply.location,
quantity: supply.quantity,
price: "$500 hardcode", //TODO? ver
unity: supply.unity,
entryDate: supply.dateSupply.slice(0, 10),
deleteDB: () => {
dispatch({
type: "COMPONENT_SELECTED",
payload: supply,
});
handleSubmitDelete(supply._id);
console.log("versupply ID", supply._id);
//history.push(`/supplies`);
},
restore: () => {
dispatch({
type: "COMPONENT_SELECTED",
payload: supply,
});
handleRestore(supply._id);
history.push("/supplies");
},
};
})}
getRowId={(row) => {
return row.id;
}}
onRowClick={(row) => {}}
rowHeight={65}
rowHeightMode="fixed"
rowSelectionMode="None"
selectionMode="None"
sx={{
fontSize: "0.8rem",
height: "100%",
width: "100%",
background: "#23282C",
color: "#bebebe",
border: "1px solid #23282C",
"& .super-app-theme--header": {
backgroundColor: "rgba(255, 7, 0, 0.55)",
},
boxShadow: 8,
}}
/>
</>
) : (
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
height: "90%",
}}
>
{/* lottie animation */}
<EmptySearch />
</div>
)}
</div>
);
};
export default React.memo(DataTableSupplies);

20
frontend/src/components/DownArrow.jsx

@ -0,0 +1,20 @@
import { useLottie } from "lottie-react";
import downArrow from "../assets/downArrow.json";
const DownArrow = () => {
const options = {
animationData: downArrow,
loop: true,
autoplay: true,
};
const style = {
height: 150,
};
const { View } = useLottie(options, style);
return <>{View}</>;
};
export default DownArrow;

330
frontend/src/components/FormComponent.jsx

@ -0,0 +1,330 @@
import Box from "@mui/material/Box";
import TextField from "@mui/material/TextField";
import MenuItem from "@mui/material/MenuItem";
import Button from "@mui/material/Button";
import Paper from "@mui/material/Paper";
import Grid from "@mui/material/Grid";
import { InputAdornment } from "@mui/material";
import React, { useState } from "react";
import { createFormData } from "../utils/formUtils";
import { addComponent } from "../services/components";
import { useHistory } from "react-router-dom";
import { sendCsvFile } from "../services/components";
import { styled } from "@mui/material/styles";
import IconButton from "@mui/material/IconButton";
import UploadFileIcon from "@mui/icons-material/UploadFile";
const categories = [
{
value: "Generales",
label: "Generales",
},
{
value: "Circuitos integrados",
label: "Circuitos integrados",
},
{
value: "Diodos",
label: "Diodos",
},
{
value: "Fichas, cables y pines",
label: "Fichas, cables y pines",
},
{
value: "Modulos",
label: "Modulos",
},
{
value: "Pasivos",
label: "Pasivos",
},
{
value: "Pantallas",
label: "Pantallas",
},
{
value: "Sensores",
label: "Sensores",
},
{
value: "Otros",
label: "Otros",
},
];
const Input = styled("input")({
display: "none",
});
export default function FormComponent() {
// history
const history = useHistory();
const [csv, setCsv] = useState([]);
// form state
const [form, setForm] = useState({
name: "",
descript: "",
image: "",
quantity: "",
category: "",
price: "",
location: "",
});
const [image, setImage] = useState({ preview: "", raw: "" });
// handlers for form
const handleChange = (e) => {
setForm({
...form,
[e.target.name]: e.target.value,
});
};
const handleImage = (e) => {
if (e.target.files.length) {
setImage({
preview: URL.createObjectURL(e.target.files[0]),
raw: e.target.files[0],
});
}
setForm({
...form,
image: e.target.files[0],
});
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!csv.name) {
const formData = createFormData(form);
addComponentToServer(formData);
// reset states
setForm({
name: "",
descript: "",
image: "",
quantity: "",
category: "",
price: "",
location: "",
});
setImage({ preview: "", raw: "" });
} else {
const formData = new FormData();
formData.append("file", csv);
const res = await sendCsvFile(formData);
console.log(res);
if (res !== null) {
history.push("/components");
}
}
};
//==============================================================================
// add component
const addComponentToServer = async (form) => {
const response = await addComponent(form);
if (response !== null) {
history.push("/components");
}
};
return (
<>
<Paper elevation={3} style={{ padding: "5ch", background: " #23282C" }}>
<div
style={{
display: "flex",
justifyContent: "space-around",
width: "60vw",
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
flexDirection: "column",
}}
>
<TextField
name="name"
className="textField"
label="Nombre"
variant="outlined"
onChange={handleChange}
// sx= {{color: 'whitesmoke'}}
/>
<div style={{ display: "flex", flexDirection: "row" }}>
<TextField
className="textField"
name="category"
select
label="Categoría"
value={form.category}
onChange={handleChange}
style={{ width: "28ch" }}
>
{categories.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
<TextField
className="textField"
name="quantity"
label="Cantidad"
type="number"
style={{ width: "10ch", marginLeft: "2ch" }}
InputLabelProps={{
shrink: true,
}}
onChange={handleChange}
/>
</div>
<TextField
className="textField"
name="price"
label="Precio"
InputProps={{
startAdornment: (
<InputAdornment position="start">$</InputAdornment>
),
}}
variant="outlined"
onChange={handleChange}
type="number"
/>
<TextField
className="textField"
name="location"
label="Ubicación"
variant="outlined"
onChange={handleChange}
type="text"
/>
</div>
<div
style={{
display: "flex",
width: "50%",
justifyContent: "space-evenly",
flexDirection: "column",
}}
>
<TextField
className="textField"
name="descript"
label="Descripcion"
variant="outlined"
onChange={handleChange}
style={{ marginBottom: "2ch" }}
multiline="true"
inputProps={{
style: {
height: 80,
},
}}
/>
<Grid
container
direction="row"
alignItems="center"
justifyContent="center"
>
<Grid item xs={5}>
<div
style={{
background: "whitesmoke",
display: "flex",
justifyContent: "center",
overflow: "hidden",
borderRadius: "50%",
width: "200px",
height: "200px",
}}
>
<Button
style={{
padding: "0",
}}
href="#"
onClick={(e) => {
e.preventDefault();
document.getElementById("image").click();
}}
>
<img
src={
image.preview
? image.preview
: "https://thealmanian.com/wp-content/uploads/2019/01/product_image_thumbnail_placeholder.png"
}
alt="preview"
style={{
width: "100%",
objectFit: "fill",
}}
/>
</Button>
<input
type="file"
id="image"
name="image"
accept="image/*"
style={{ display: "none" }}
onChange={handleImage}
/>
</div>
</Grid>
</Grid>
</div>
</div>
</Paper>
<div
style={{
display: "flex",
justifyContent: "flex-end",
marginTop: "3ch",
width: "66vw",
}}
>
<label htmlFor="icon-button-file">
<Input
id="icon-button-file"
type="file"
name="file"
onChange={(e) => {
const file = e.target.files[0];
setCsv(file);
}}
/>
<IconButton color="primary" aria-label="upload file" component="span">
<UploadFileIcon />
</IconButton>
</label>
<Button
type="submit"
variant="contained"
color="primary"
onClick={handleSubmit}
sx={{ ml: "20px" }}
>
Agregar
</Button>
</div>
</>
);
}

251
frontend/src/components/FormNewOrder.jsx

@ -0,0 +1,251 @@
import TextField from "@mui/material/TextField";
import MenuItem from "@mui/material/MenuItem";
import Button from "@mui/material/Button";
import { DialogTitle } from "@mui/material";
import { useState, useEffect, useRef } from "react";
import { createFormData } from "../utils/formUtils";
import { useHistory } from "react-router-dom";
import { styled } from "@mui/material/styles";
import AddIcon from "@mui/icons-material/Add";
import { getProviders, addProvider } from "../services/providers";
import { useParams } from "react-router-dom";
import { addOrder } from "../services/orders";
import { getSupplyById } from "../services/supplies";
//sockets
import io from "socket.io-client";
//pop up
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
const API = process.env.REACT_APP_API;
export default function FormNewOrder({ supplyData }) {
console.log("supplyData", supplyData);
//popUp
const [openP, setOpenP] = useState(false);
const handleClickOpenP = () => {
setOpenP(true);
};
const handleCloseP = () => {
setOpenP(false);
};
// history
const history = useHistory();
// form state
const [form, setForm] = useState({
idProduct: supplyData._id,
idProvider: "",
description: "",
quantity: "",
amount: "",
});
console.log("form antes: " + form.idProduct);
// handlers
const handleChange = (e) => {
setForm({
...form,
idProduct: supplyData._id,
[e.target.name]: e.target.value,
});
};
const handleSubmit = async (e) => {
e.preventDefault();
addNewOrderToServer(form);
// reset states
setForm({
idProduct: supplyData._id,
description: "",
idProvider: "",
quantity: "",
amount: "",
});
};
//==============================================================================
//services
// add supply
const addNewOrderToServer = async (form) => {
console.log("form VERVER", form);
const response = await addOrder(form);
console.log("response", response);
if (response !== null) {
history.push(`/providersDetail/${supplyData._id}`);
}
};
//PROVIDERS
const socketRef = useRef();
const [providers, setProviders] = useState([]);
const [newProvider, setNewProvider] = useState({
name: "",
});
const handleChangeProviders = (event) => {
setNewProvider({
...newProvider,
[event.target.name]: event.target.value,
});
};
const handleSubmitProvider = (event) => {
event.preventDefault();
console.log(newProvider.name);
addProvider({
name: newProvider.name,
description: "",
});
handleCloseP();
};
//Crea el array de proveedores y queda a la escucha de nuevos posts
useEffect(() => {
socketRef.current = io.connect(`${API}`);
socketRef.current.on("listenProviders", async () => {
loadProviders();
});
const loadProviders = async () => {
const providers = await getProviders();
setProviders(providers);
console.log(providers);
};
loadProviders();
}, []);
return (
<>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-around",
width: "60vw",
background: " #23282C",
padding: "5ch",
}}
>
<div style={{ display: "flex", width: "90%" }}>
<TextField
className="textField"
name="idProvider"
style={{ width: "50%", marginBottom: "15px" }}
select
value={form.idProvider}
onChange={handleChange}
label="Proveedor"
>
{providers.map((item) => (
<MenuItem key={item._id} value={item._id}>
{item.name}
</MenuItem>
))}
</TextField>
<Button
className="btn-hover"
variant="contained"
sx={{ background: "#3082AA !important", marginBottom: "15px" }}
onClick={handleClickOpenP}
>
<AddIcon />
</Button>
</div>
<TextField
className="textField"
name="quantity"
label="Cantidad"
variant="outlined"
onChange={handleChange}
type="number"
style={{ width: "45%", marginBottom: "15px" }}
/>
<TextField
className="textField"
name="amount"
label="Precio"
variant="outlined"
onChange={handleChange}
type="number"
style={{ width: "45%", marginBottom: "15px" }}
/>
<TextField
className="textField"
name="description"
label="Descripcion"
variant="outlined"
onChange={handleChange}
style={{ marginBottom: "15px" }}
multiline
inputProps={{
style: {
height: 80,
},
}}
/>
</div>
<div
style={{
display: "flex",
justifyContent: "flex-end",
marginTop: "3ch",
width: "66vw",
}}
>
<Button
type="submit"
variant="contained"
color="primary"
onClick={handleSubmit}
sx={{ ml: "20px" }}
>
Agregar
</Button>
{/* POP UP DIALOG PROVIDERS */}
<Dialog open={openP} onClose={handleCloseP}>
<DialogTitle>Nuevo Proveedor</DialogTitle>
<DialogContent>
<TextField
autoFocus
name="name"
margin="dense"
id="provider"
label="Proveedor"
onChange={handleChangeProviders}
type="string"
fullWidth
variant="standard"
/>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseP}>Cancelar</Button>
<Button onClick={handleSubmitProvider}>Confirmar</Button>
</DialogActions>
</Dialog>
</div>
</>
);
}

372
frontend/src/components/FormProductModel.jsx

@ -0,0 +1,372 @@
import Box from "@mui/material/Box";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import Paper from "@mui/material/Paper";
import Grid from "@mui/material/Grid";
import List from "@mui/material/List";
import { CssBaseline, Divider, Modal } from "@mui/material";
import React, { useEffect, useState } from "react";
import { createFormData } from "../utils/formUtils";
import { useHistory } from "react-router-dom";
import ModalComponents from "./ModalComponents";
import { useDispatch, useSelector } from "react-redux";
import ItemComponent from "./ItemComponent";
import { getComponents } from "../services/components";
import { getComponentsById } from "../utils/getComponentsUtils";
import { addProduct } from "../services/products";
export default function FormProductModel() {
// TODO move getComponents to home screen
// redux states
const dispatch = useDispatch();
const components = useSelector((state) => state.components);
const componentsProduct = useSelector((state) => state.componentsProduct);
const componentsForModel = useSelector((state) => state.componentsModel);
// local states
const [changeComponentsProduct, setChangeComponentsProduct] = useState(0);
// service for get components
const getComponentsFromServer = async () => {
const data = await getComponents();
if (data !== null) {
dispatch({
type: "GET_COMPONENTS",
payload: data,
});
} else {
console.log("error");
}
};
useEffect(() => {
getComponentsFromServer();
return () => {
dispatch({
type: "CLEAN_COMPONENTS_PRODUCT",
});
};
}, []);
useEffect(() => {
setForm({
...form,
components: componentsProduct,
});
}, [changeComponentsProduct]);
// history
const history = useHistory();
// states
const [modalComponents, setModalComponents] = useState(false);
const [form, setForm] = useState({
name: "",
descript: "",
image: "",
quantity: "",
components: [],
});
const [image, setImage] = useState({ preview: "", raw: "" });
// handlers
const handleChange = (e) => {
setForm({
...form,
[e.target.name]: e.target.value,
});
};
const handleImage = (e) => {
if (e.target.files.length) {
setImage({
preview: URL.createObjectURL(e.target.files[0]),
raw: e.target.files[0],
});
}
setForm({
...form,
image: e.target.files[0],
});
};
const handleSubmit = async (e) => {
e.preventDefault();
const formData = await createFormData(form);
const res = await addProductToServer(formData);
if (res) {
// reset states and redirect
setForm({
name: "",
descript: "",
image: "",
quantity: "",
components: [],
});
dispatch({
type: "RESET_COMPONENTS_PRODUCT",
});
history.push("/");
} else {
console.log("error");
}
};
const handleModal = () => {
setModalComponents(!modalComponents);
};
// dispatch events
const componentsForProduct = (ids) => {
const componentsList = getComponentsById(ids, componentsForModel);
setChangeComponentsProduct(changeComponentsProduct + 1);
dispatch({
type: "ADD_COMPONENTS_PRODUCT",
payload: componentsList,
});
};
const removeComponentForProduct = (id) => {
setChangeComponentsProduct(changeComponentsProduct + 1);
dispatch({
type: "REMOVE_COMPONENTS_PRODUCT",
payload: id,
});
// update form
};
const setQuantity = (value, id) => {
let componentQuantity = {
id: id,
quantity: value,
};
dispatch({
type: "SET_QUANTITY_COMPONENT_PRODUCT",
payload: componentQuantity,
});
};
const addProductToServer = async (formData) => {
const res = await addProduct(formData);
if (res !== null) {
return true;
} else {
return false;
}
};
return (
<>
<ModalComponents
handlerModal={handleModal}
stateModal={modalComponents}
components={components}
addIdComponentsForProduct={componentsForProduct}
/>
<CssBaseline />
{/* LEFT COLUMN */}
<div
style={{
padding: "5%",
marginTop: "5%",
width: "50vw",
height: "90vh",
display: "flex",
justifyContent: "space-between",
flexDirection: "column",
}}
>
<TextField
className="textField"
name="name"
label="Nombre"
variant="outlined"
onChange={handleChange}
style={{ marginBottom: "2ch" }}
/>
<TextField
className="textField"
name="descript"
label="Descripcion"
variant="outlined"
multiline
onChange={handleChange}
style={{ marginBottom: "2ch" }}
inputProps={{
style: {
height: 80,
},
}}
/>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<div
style={{
borderRadius: "100px",
display: "flex",
width: "200px",
height: "200px",
overflow: "hidden",
alignItems: "center",
justifyContent: "center",
}}
>
<Button
href="#"
onClick={(e) => {
e.preventDefault();
document.getElementById("image").click();
}}
>
<img
src={
image.preview
? image.preview
: "https://thealmanian.com/wp-content/uploads/2019/01/product_image_thumbnail_placeholder.png"
}
alt="preview"
style={{ width: "200px", height: "200px" }}
/>
</Button>
<input
type="file"
id="image"
name="image"
accept="image/*"
style={{ display: "none" }}
onChange={handleImage}
/>
</div>
</div>
</div>
{/* RIGHT COLUMN */}
<Grid
item
xs={12}
sm={8}
md={6}
component={Paper}
elevation={0}
sx={{
display: "flex",
justifyContent: "center",
verticalAlign: "center",
backgroundColor: "#11181B",
}}
>
<Box
sx={{
width: "90%",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<h3
style={{
width: "80%",
fontWeight: "400",
color: "#7a7979",
display: "block",
position: "relative",
top: "6%",
}}
>
Lista de componentes
</h3>
<div
style={{
display: "block",
position: "relative",
left: "40%",
top: "5%",
//justifyContent: "end",
}}
>
<Button
style={{
width: "40px",
height: "60px",
borderRadius: "100px",
fontSize: "2rem",
}}
variant="contained"
color="primary"
onClick={() => {
//open modal
handleModal();
}}
>
+
</Button>
</div>
<div
style={{
overflow: "scroll",
minHeight: "60vh",
maxHeight: "60vh",
borderRadius: "1ch",
padding: "5ch",
width: "80%",
background: "#23282C",
}}
>
{componentsProduct && componentsProduct.length > 0
? componentsProduct.map((item) => (
<>
<ItemComponent
key={item.id}
id={item._id}
name={item.name}
setQuantity={setQuantity}
remove={removeComponentForProduct}
/>
<Divider
color="whitesmoke"
style={{
marginBottom: "1em",
}}
/>
</>
))
: null}
</div>
<Button
type="submit"
style={{ width: "50%", margin: "5ch" }}
variant="contained"
color="primary"
onClick={handleSubmit}
>
CONFIRMAR
</Button>
</Box>
</Grid>
</>
);
}

531
frontend/src/components/FormSupply.jsx

@ -0,0 +1,531 @@
import TextField from "@mui/material/TextField";
import MenuItem from "@mui/material/MenuItem";
import Button from "@mui/material/Button";
import { DialogTitle, InputAdornment } from "@mui/material";
import { useState, useEffect, useRef } from "react";
import { createFormData } from "../utils/formUtils";
import { addSupply, sendCsvFile } from "../services/supplies";
import { useHistory } from "react-router-dom";
import { styled } from "@mui/material/styles";
import IconButton from "@mui/material/IconButton";
import UploadFileIcon from "@mui/icons-material/UploadFile";
import AddIcon from "@mui/icons-material/Add";
import {
getProviders,
addPriceToProviderById,
addProvider,
} from "../services/providers";
import { getCategories, addCategory } from "../services/categories";
import { useParams } from "react-router-dom";
//sockets
import io from "socket.io-client";
//pop up
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
const API = process.env.REACT_APP_API;
const Input = styled("input")({
display: "none",
});
export default function FormSupply() {
//popUp
const [openC, setOpenC] = useState(false);
const [openP, setOpenP] = useState(false);
const handleClickOpen = () => {
setOpenC(true);
};
const handleClose = () => {
setOpenC(false);
};
const handleClickOpenP = () => {
setOpenP(true);
};
const handleCloseP = () => {
setOpenP(false);
};
//end PopUp
// history
const history = useHistory();
const [csv, setCsv] = useState([]);
// form state
const [form, setForm] = useState({
name: "",
description: "",
category: "",
providers: "",
unity: "",
imageSupply: "",
code: "",
location: "",
});
const [image, setImage] = useState({ preview: "", raw: "" });
// handlers
const handleChange = (e) => {
setForm({
...form,
[e.target.name]: e.target.value,
});
};
const handleImage = (e) => {
if (e.target.files.length) {
// console.log(e.target.files[0]);
setImage({
preview: URL.createObjectURL(e.target.files[0]),
raw: e.target.files[0],
});
}
setForm({
...form,
imageSupply: e.target.files[0],
});
};
const handleSubmit = async (e) => {
e.preventDefault();
// console.log('form', form);
// console.log('csv', cvs);
// console.log('formProvider', formProvider);
if (!csv.name) {
const formData = createFormData(form);
console.log("formData desde formSupply", formData);
addSupplyToServer(formData);
// reset states
setForm({
name: "",
description: "",
category: "",
providers: "",
imageSupply: "",
code: "",
location: "",
quantity: "",
amount: "",
});
setImage({ preview: "", raw: "" });
} else {
const formData = new FormData();
formData.append("file", csv);
const res = await sendCsvFile(formData);
console.log(res);
if (res !== null) {
history.push("/supplies");
}
}
};
//==============================================================================
//services
// add supply
const addSupplyToServer = async (form) => {
console.log("form VERVER", form);
const response = await addSupply(form);
console.log("response", response);
if (response !== null) {
history.push("/supplies");
}
};
//PROVIDERS
const socketRef = useRef();
const [providers, setProviders] = useState([]);
const [newProvider, setNewProvider] = useState({
name: "",
});
const handleChangeProviders = (event) => {
setNewProvider({
...newProvider,
[event.target.name]: event.target.value,
});
};
const handleSubmitProvider = (event) => {
event.preventDefault();
console.log(newProvider.name);
addProvider({
name: newProvider.name,
description: "",
});
handleCloseP();
};
//Crea el array de proveedores y queda a la escucha de nuevos posts
useEffect(() => {
socketRef.current = io.connect(`${API}`);
socketRef.current.on("listenProviders", async () => {
loadProviders();
});
const loadProviders = async () => {
const providers = await getProviders();
setProviders(providers);
console.log(providers);
};
loadProviders();
}, []);
//CATEGORIES
const [categories, setCategories] = useState([]);
const [newCategory, setNewCategory] = useState({
nombre: "",
});
const handleChangeCategories = (event) => {
setNewCategory({
...newCategory,
[event.target.name]: event.target.value,
});
};
const handleSubmitCategory = (event) => {
event.preventDefault();
console.log(newCategory.nombre);
addCategory({
name: newCategory.nombre,
description: "Es una descripción",
});
handleClose();
};
//Crea el array de categorías y queda a la escucha de nuevos posts
useEffect(() => {
socketRef.current = io.connect(`${API}`);
socketRef.current.on("listenCategories", async () => {
loadCategories();
});
const loadCategories = async () => {
const categories = await getCategories();
setCategories(categories);
console.log(categories);
};
loadCategories();
}, []);
const handleSubmitTest = async (e) => {
e.preventDefault();
console.log("test");
console.log(form);
};
return (
<>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-around",
width: "60vw",
background: " #23282C",
padding: "5ch",
height: "850px",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
height: "400px",
}}
>
<div
style={{
width: "50%",
flexDirection: "column",
height: "100%",
display: "flex",
justifyContent: "space-between",
}}
>
<TextField
name="name"
className="textField"
label="Nombre"
variant="outlined"
onChange={handleChange}
style={{ width: "90%" }}
/>
<TextField
className="textField"
name="location"
label="Ubicación"
variant="outlined"
onChange={handleChange}
type="text"
style={{ width: "90%" }}
/>
<TextField
name="code"
className="textField"
label="Código"
variant="outlined"
onChange={handleChange}
style={{ width: "90%" }}
/>
<div
style={{ display: "flex", flexDirection: "row", width: "100%" }}
>
<TextField
className="textField"
name="category"
select
label="Categoría"
value={form.category}
onChange={handleChange}
style={{ width: "100%" }}
>
{categories.map((option) => (
<MenuItem key={option._id} value={option._id}>
<p> {option.name} </p>
</MenuItem>
))}
</TextField>
<Button
className="btn-hover"
variant="contained"
sx={{ background: "#3082AA !important" }}
onClick={handleClickOpen}
>
<AddIcon />
</Button>
</div>
<TextField
className="textField"
name="unity"
label="Unidad de medida"
type="string"
variant="outlined"
style={{ width: "90%" }}
onChange={handleChange}
/>
</div>
<div
style={{
width: "50%",
display: "flex",
justifyContent: "center",
}}
>
<div
style={{
display: "flex",
justifyContent: "center",
overflow: "hidden",
borderRadius: "50%",
width: "200px",
height: "200px",
}}
>
<Button
style={{
padding: "0",
}}
href="#"
onClick={(e) => {
e.preventDefault();
document.getElementById("imageSupply").click();
}}
>
<img
src={
image.preview
? image.preview
: "https://thealmanian.com/wp-content/uploads/2019/01/product_image_thumbnail_placeholder.png"
}
alt="preview"
style={{
width: "100%",
objectFit: "fill",
}}
/>
</Button>
<input
type="file"
id="imageSupply"
name="imageSupply"
accept="image/*"
style={{ display: "none" }}
onChange={handleImage}
/>
</div>
</div>
</div>
<div style={{ display: "flex", width: "90%" }}>
<TextField
className="textField"
name="providers"
style={{ width: "50%" }}
select
value={form.providers}
onChange={handleChange}
label="Proveedor"
>
{providers.map((item) => (
<MenuItem key={item._id} value={item._id}>
{item.name}
</MenuItem>
))}
</TextField>
<Button
className="btn-hover"
variant="contained"
sx={{ background: "#3082AA !important" }}
onClick={handleClickOpenP}
>
<AddIcon />
</Button>
</div>
<TextField
className="textField"
name="quantity"
label="Cantidad"
variant="outlined"
onChange={handleChange}
type="number"
style={{ width: "45%" }}
/>
<TextField
className="textField"
name="amount"
label="Precio"
variant="outlined"
onChange={handleChange}
type="number"
style={{ width: "45%" }}
/>
<TextField
className="textField"
name="description"
label="Descripcion"
variant="outlined"
onChange={handleChange}
style={{ marginBottom: "2ch" }}
multiline
inputProps={{
style: {
height: 80,
},
}}
/>
</div>
<div
style={{
display: "flex",
justifyContent: "flex-end",
marginTop: "3ch",
width: "66vw",
}}
>
<label htmlFor="icon-button-file">
<Input
id="icon-button-file"
type="file"
name="file"
onChange={(e) => {
const file = e.target.files[0];
setCsv(file);
}}
/>
<IconButton color="primary" aria-label="upload file" component="span">
<UploadFileIcon />
</IconButton>
</label>
<Button onClick={handleSubmitTest}>test</Button>
<Button
type="submit"
variant="contained"
color="primary"
onClick={handleSubmit}
sx={{ ml: "20px" }}
>
Agregar
</Button>
{/* POP UP DIALOG CATEGORY */}
<Dialog open={openC} onClose={handleClose}>
<DialogTitle>Nueva Categoría</DialogTitle>
<DialogContent>
<TextField
autoFocus
name="nombre"
margin="dense"
id="category"
label="Categoría"
onChange={handleChangeCategories}
type="text"
fullWidth
variant="standard"
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancelar</Button>
<Button onClick={handleSubmitCategory}>Confirmar</Button>
</DialogActions>
</Dialog>
{/* POP UP DIALOG PROVIDERS */}
<Dialog open={openP} onClose={handleCloseP}>
<DialogTitle>Nuevo Proveedor</DialogTitle>
<DialogContent>
<TextField
autoFocus
name="name"
margin="dense"
id="provider"
label="Proveedor"
onChange={handleChangeProviders}
type="string"
fullWidth
variant="standard"
/>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseP}>Cancelar</Button>
<Button onClick={handleSubmitProvider}>Confirmar</Button>
</DialogActions>
</Dialog>
</div>
</>
);
}

15
frontend/src/components/GridHome.jsx

@ -0,0 +1,15 @@
import { Button, Grid } from "@mui/material";
import React from "react";
import { Link } from "react-router-dom";
const GridHome = ({ children }) => {
return (
<>
<Grid container justifyContent="center">
{children}
</Grid>
</>
);
};
export default GridHome;

53
frontend/src/components/ItemComponent.jsx

@ -0,0 +1,53 @@
import {
Avatar,
IconButton,
ListItem,
ListItemAvatar,
ListItemText,
TextField,
} from "@mui/material";
import SelectAllIcon from "@mui/icons-material/SelectAll";
import DeleteIcon from "@mui/icons-material/Delete";
import React from "react";
const ItemComponent = ({ name, id, setQuantity, remove }) => {
return (
<ListItem
secondaryAction={
<IconButton edge="end" aria-label="delete" onClick={() => remove(id)}>
<DeleteIcon style={{color: 'grey'}}/>
</IconButton>
}
>
<ListItemAvatar>
<Avatar>
<SelectAllIcon />
</Avatar>
</ListItemAvatar>
<ListItemText className="textField">{name}</ListItemText>
<TextField
className="textField"
key={id}
id="quantity"
label="cantidad"
type="number"
onChange={(e) => {
setQuantity(e.target.value, id);
}}
InputLabelProps={{
shrink: true,
}}
InputProps={{
inputProps: {
defaultValue: 1,
min: 0,
max: 100,
},
}}
/>
</ListItem>
);
};
export default ItemComponent;

105
frontend/src/components/ModalComponents.jsx

@ -0,0 +1,105 @@
//import React from "react";
import Box from "@mui/material/Box";
import TextField from "@mui/material/TextField";
import MenuItem from "@mui/material/MenuItem";
import Button from "@mui/material/Button";
import { Modal } from "@mui/material";
// table
import { DataGrid, renderEditInputCell } from "@mui/x-data-grid";
import { componentsJsonToDataGrid } from "../utils/tableUtils";
import { useEffect, useState } from "react";
let rows = [];
let columns = [];
const ModalComponents = ({
handlerModal,
stateModal,
components,
addIdComponentsForProduct,
}) => {
const [componentsForProduct, setComponentsForProduct] = useState([]);
useEffect(() => {
const dataGrid = componentsJsonToDataGrid(components);
rows = [];
columns = [];
rows.push(...dataGrid.rows);
columns.push(...dataGrid.columns);
}, [components]);
return (
<Modal
open={stateModal}
aria-labelledby="simple-modal-title"
aria-describedby="simple-modal-description"
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
width: "80%",
height: "80%",
backgroundColor: "#23282C",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-around",
alignItems: "center",
width: "100%",
overflow: "auto",
}}
>
<div style={{ height: "50vh", width: "60vw" }}>
<DataGrid
columnVisibilityModel={{
id: false,
name: true,
description: true,
category: true,
}}
sx={{
color: "whitesmoke",
}}
disableSelectionOnClick
rows={rows}
columns={columns}
pageSize={7}
rowsPerPageOptions={[7]}
checkboxSelection={true}
onSelectionModelChange={(selectionModel) => {
setComponentsForProduct(selectionModel);
}}
/>
</div>
</div>
<Button
sx={{
marginTop: "2rem",
}}
variant="contained"
color="primary"
onClick={() => {
addIdComponentsForProduct(componentsForProduct);
handlerModal(false);
}}
>
Agregar componentes
</Button>
</Box>
</Modal>
);
};
export default ModalComponents;

118
frontend/src/components/NavBar.jsx

@ -0,0 +1,118 @@
import { useState } from "react";
import { Link, NavLink } from "react-router-dom";
import AppBar from "@mui/material/AppBar";
import Box from "@mui/material/Box";
import Toolbar from "@mui/material/Toolbar";
import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography";
import Menu from "@mui/material/Menu";
import MenuIcon from "@mui/icons-material/Menu";
import MenuItem from "@mui/material/MenuItem";
const NavBar = () => {
const [anchorElNav, setAnchorElNav] = useState(null);
const handleOpenNavMenu = (event) => {
setAnchorElNav(event.currentTarget);
};
const handleCloseNavMenu = () => {
setAnchorElNav(null);
};
return (
<AppBar position="static" style={{ background: "#23282C" }}>
<Toolbar style={{ minHeight: "0px" }}>
<h2 style={{ color: "#3082aa", paddingRight: "1%" }}>
<span>FAN</span>
<span style={{ color: "#27698a" }}>STOCK</span>
</h2>
<Box sx={{ flexGrow: 1, display: { xs: "flex", md: "none" } }}>
<IconButton
size="large"
aria-label="account of current user"
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={handleOpenNavMenu}
style={{ color: "white" }}
>
<MenuIcon />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorElNav}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
keepMounted
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
open={Boolean(anchorElNav)}
onClose={handleCloseNavMenu}
sx={{
display: { xs: "block", md: "none" },
}}
>
<MenuItem
onClick={handleCloseNavMenu}
style={{ background: "#23282C" }}
>
<Link to="/components">
<Typography>Componentes</Typography>
</Link>
</MenuItem>
<MenuItem
onClick={handleCloseNavMenu}
style={{ background: "#23282C" }}
>
<Link to="/">
<Typography>Productos</Typography>
</Link>
</MenuItem>
<MenuItem
onClick={handleCloseNavMenu}
style={{ background: "#23282C" }}
>
<Link to="/fabricdashboard">
<Typography>Fábrica</Typography>
</Link>
</MenuItem>
</Menu>
</Box>
<Box sx={{ flexGrow: 1, display: { xs: "none", md: "flex" } }}>
<NavLink to="/supplies" activeClassName="active">
<Typography textAlign="center" padding={2}>
Insumos
</Typography>
</NavLink>
<NavLink to="/components" activeClassName="active">
<Typography textAlign="center" padding={2}>
Componentes
</Typography>
</NavLink>
<NavLink exact to="/" activeClassName="active">
<Typography textAlign="center" padding={2}>
Productos
</Typography>
</NavLink>
<NavLink to="/fabricdashboard" activeClassName="active">
<Typography textAlign="center" padding={2}>
Fábrica
</Typography>
</NavLink>
<NavLink to="/calculator" activeClassName="active">
<Typography textAlign="center" padding={2}>
Calculadora
</Typography>
</NavLink>
</Box>
</Toolbar>
</AppBar>
);
};
export default NavBar;

473
frontend/src/components/NewProductFabricComponents.jsx

@ -0,0 +1,473 @@
import { useDispatch, useSelector } from "react-redux";
import { useEffect, useState } from "react";
import Checkbox from "@mui/material/Checkbox";
import { useLocation } from "react-router";
import { ProSidebar, Menu, MenuItem, SubMenu } from "react-pro-sidebar";
import "react-pro-sidebar/dist/css/styles.css";
import { Button, Stack } from "@mui/material";
import {
getProcessProductById,
updateQuantity,
} from "../services/processProduct";
import { getComponentsByIds } from "../services/components";
import { createFormData } from "../utils/formUtils";
import { getProcessProductsThunk } from "../reducers/actionsProcessProducts";
import SaveIcon from "@mui/icons-material/Save";
import UndoIcon from "@mui/icons-material/Backspace";
export default function NewProductFabricComponents({ productId, percentage }) {
const dispatch = useDispatch();
const processProduct = useSelector((state) => state.productSelected);
const componentstoProduct = useSelector((state) => state.componentstoProduct);
const [refresh, setRefresh] = useState(true);
const [categoryCreated, setCategoryCreated] = useState(false);
const [matrixHandlersCategories, setMatrixHandlersCategories] = useState([]);
const [categoryList, setCategoryList] = useState([]);
const [saved, setSaved] = useState(false);
const [finishedComponents, setFinishedComponents] = useState({
finishedQuantityValue: [],
});
const processRequest = useSelector((state) => state.processRequest);
const checkedStates = useSelector((state) => state.processProductsChecked);
let checkedStatesArray = [];
const processRequestThunk = useSelector(
(state) => state.processProductsThunk
);
const saveChanges = async (e) => {
const response = await updateQuantity(
finishedComponents,
processProduct._id
);
setSaved((saved) => true);
await getInfo();
createCategories(processProduct.components);
// reset state
setFinishedComponents({
finishedQuantityValue: [],
});
percentage();
};
const getInfo = async () => {
await dispatch(getProcessProductsThunk(processProduct._id));
};
useEffect(() => {
createCategories(processProduct.components);
}, [categoryList]);
useEffect(() => {
const apiResponse = async () => {
await getInfo();
createCategories(processProduct.components);
};
apiResponse();
}, []);
const createCategories = (components) => {
components.forEach((component) => {
if (
categoryList.findIndex(
(category) => category.label === component.category
) === -1
) {
const category = {
value: component.category,
label: component.category,
};
setCategoryList([...categoryList, category]);
}
});
setCategoryCreated(true);
};
const mappedCategory = (categoriesJson) => {
return categoriesJson.map((category) => {
// verify if category already exist in the matrix state
const categoryExist = matrixHandlersCategories.find(
(matrixHandler) => matrixHandler.label === category.label
);
if (!categoryExist) {
// create a new category
const newCategory = {
label: category.label,
state: false,
};
setMatrixHandlersCategories([...matrixHandlersCategories, newCategory]);
}
const handlerState = matrixHandlersCategories.find(
(matrixHandler) => matrixHandler.label === category.label
);
return (
<SubMenu
title={category.label}
// open={handlerState ? handlerState.state : false}
suffix={
<div
style={{
display: "flex",
flexDirection: "row",
}}
>
<Button
onClick={() => {
handlerState.state = !handlerState.state;
setMatrixHandlersCategories([...matrixHandlersCategories]);
}}
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path
d="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z"
fill="#fff"
/>
<path d="M0-.75h24v24H0z" fill="none" />
</svg>
</Button>
</div>
}
/* prefix={
<Checkbox
onChange={(e) => {
e.currentTarget.checked
? console.log("checked" + category.label)
: console.log("unchecked" + category.label);
}}
/>
} */
>
{processProduct.components
? mappedComponents(processProduct.components, category.value)
: null}
</SubMenu>
);
});
};
const finishedComponentsVerifyExist = (component) => {
const componentExist = finishedComponents.finishedQuantityValue.find(
(finishedComponent) => finishedComponent._id === component._id
);
if (componentExist) {
return true;
} else {
// create a new component
const newComponent = {
_id: component._id,
quantity: 0,
};
// setFinishedComponents([...finishedComponents, newComponent]);
setFinishedComponents({
...finishedComponents,
finishedQuantityValue: [
...finishedComponents.finishedQuantityValue,
newComponent,
],
});
}
};
const finishedComponentsHandler = (component, state) => {
// verify if component already exist in the finishedComponents state
switch (state) {
case "ADD":
const componentExist = finishedComponents.finishedQuantityValue.find(
(finishedComponent) => finishedComponent._id === component._id
);
componentExist.quantity += 1;
setFinishedComponents({
...finishedComponents,
finishedQuantityValue: [...finishedComponents.finishedQuantityValue],
});
break;
case "LESS":
const componentExistLess =
finishedComponents.finishedQuantityValue.find(
(finishedComponent) => finishedComponent._id === component._id
);
componentExistLess.quantity -= 1;
setFinishedComponents({
...finishedComponents,
finishedQuantityValue: [...finishedComponents.finishedQuantityValue],
});
break;
default:
break;
}
};
const undoComponent = (component) => {
const componentExist = finishedComponents.finishedQuantityValue.find(
(finishedComponent) => finishedComponent._id === component._id
);
let quantity = (componentExist.quantity = -1);
let newFinishedComponents = finishedComponents.finishedQuantityValue.map(
(finishedComponent) => {
if (finishedComponent._id === component._id) {
return {
...finishedComponent,
quantity: quantity,
};
} else {
return finishedComponent;
}
}
);
setFinishedComponents({
...finishedComponents,
finishedQuantityValue: newFinishedComponents,
});
saveChanges();
};
const mappedComponents = (components, category) => {
return components.map((component, index) => {
finishedComponentsVerifyExist(component);
return component.category === category ? (
<Stack flexDirection="row">
<SubMenu
style={{
width: "100%",
}}
suffix={
<div
style={{
display: "flex",
justifyContent: "space-between",
flexDirection: "row",
}}
>
<Button>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path
d="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z"
fill="#fff"
/>
<path d="M0-.75h24v24H0z" fill="none" />
</svg>
</Button>
</div>
}
title={
component.name + " " + "(" + component.outstandingQuantity + ")"
}
prefix={
<Checkbox
checked={component.outstandingQuantity <= 0 ? true : false}
color="success"
sx={
component.outstandingQuantity <= 0
? {
color: "green",
}
: {
color: "whitesmoke",
}
}
/>
}
>
{
// create menuItem for quantity in the processProduct.components.quantity
Array.from(
{
length:
component.outstandingQuantity + component.finishedQuantity,
},
(v, i) => i + 1
).map((i) => {
let name = `${component.name}-${i}`;
let value;
if (i <= component.outstandingQuantity) {
value = false;
} else {
value = true;
}
checkedStatesArray.push({
name: name,
value: value,
});
return (
<div
style={{
display: "flex",
flexDirection: "row",
}}
>
<MenuItem
className={
i <= component.finishedQuantity ? "myDIV complete"
: "myDIV"
}
key={component.id}
suffix={
<div
style={{
marginLeft: "50px",
}}
>
{component.finishedQuantity >= i ? (
<Button
onClick={(e) => {
let stateCheck =
e.currentTarget.parentNode.parentNode
.parentNode.children[0].children[0]
.children[0].checked;
if (stateCheck) {
undoComponent(component);
}
let componentState = checkedStatesArray.find(
(state) => state.name === name
);
}}
>
<UndoIcon
sx={{
fontSize: "15px",
color: "whitesmoke",
}}
/>
</Button>
) : null}
</div>
}
prefix={
<Checkbox
sx={{
color:
i <= component.finishedQuantity
? "#7a7979 !important"
: "#3082aa !important",
}}
key={`${component.name}-${i}`}
checked={
component.finishedQuantity >= i ? true : undefined
}
onClick={(e) => {
setSaved((saved) => false);
/* e.currentTarget.childNodes[0].checked
? quantityHanlder++
: quantityHanlder--;
console.log(quantityHanlder); */
}}
onChange={(e) => {
e.currentTarget.checked
? finishedComponentsHandler(component, "ADD")
: finishedComponentsHandler(component, "LESS");
}}
/>
}
>
{name}
</MenuItem>
<MenuItem className="hide">
<div
style={{
marginTop: "10px",
}}
>
<p>
{processProduct.model
? `Stock: ${
componentstoProduct
? componentstoProduct[index].quantity
: 0
} | Necesarios: ${
processProduct.components[index]
.outstandingQuantity
} | Ubicacion: ${
processProduct.components[index].location
}`
: null}
</p>
</div>
</MenuItem>
</div>
);
})
}
</SubMenu>
</Stack>
) : null;
});
};
return (
<>
{/* create sidebar mapped info */}
<div
style={{
display: "flex",
flexDirection: "row",
}}
>
<div
style={{
height: "70vh",
overflow: "auto",
// style scrollbar
backgroundColor: "#fff",
}}
>
<ProSidebar width="50vw" className="styleProSidebar">
<Menu innerSubMenuArrows={false} iconShape="square">
<SubMenu
className="styleProSidebarSM"
title="Components"
open={true}
>
{!processRequestThunk.loading ? (
mappedCategory(categoryList)
) : (
<div>
<p>Cargando...</p>
</div>
)}
</SubMenu>
</Menu>
</ProSidebar>
</div>
<Button
style={{
height: "3em",
marginLeft: "1em",
background: "#3082aa",
}}
variant="contained"
onClick={() => {
saveChanges();
}}
>
<SaveIcon />
</Button>
</div>
</>
);
}

69
frontend/src/components/ProductCard.jsx

@ -0,0 +1,69 @@
import * as React from "react";
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import CardMedia from "@mui/material/CardMedia";
import Typography from "@mui/material/Typography";
import { Box } from "@mui/system";
import { CardActions } from "@mui/material";
export default function ProductCard({ name, img, description }) {
return (
<Card
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
maxWidth: 200,
minWidth: 200,
maxHeight: 300,
minHeight: 300,
mb: 2,
background: "#23282C",
display: "flex",
flexDirection: "column",
borderRadius: "15px",
paddingTop: "10px",
boxShadow: 6,
}}
>
{/* <CardMedia
component="img"
alt="faniot-img"
image={img}
sx={{
borderRadius: "50%",
width: "140px",
alignSelf: "center",
mt: "10px",
mb: "20px",
}}
/> */}
<img
src={img}
alt="faniot-img"
style={{
alignSelf: "center",
width: "150px",
height: "150px",
borderRadius: "50%",
}}
/>
<CardContent
sx={{
background: "#3082AA",
overFlow: "hidden",
}}
>
<Typography variant="h5" component="div" sx={{ color: "whitesmoke" }}>
{name}
</Typography>
<Typography color="text.secondary" sx={{ lineHeight: "30px" }}>
{description}
</Typography>
</CardContent>
</Card>
);
}

74
frontend/src/components/ProductCardFabric.jsx

@ -0,0 +1,74 @@
import * as React from "react";
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import CardMedia from "@mui/material/CardMedia";
import Typography from "@mui/material/Typography";
import { CardActions } from "@mui/material";
import { Link } from "react-router-dom";
export default function ProductCardFabric({
name,
img,
description,
stateProduction,
}) {
return (
<Card
sx={{
maxWidth: 200,
minWidth: 200,
maxHeight: 300,
minHeight: 300,
background: "#23282C",
boxShadow: 6,
display: "flex",
flexDirection: "column",
paddingTop: "10px",
borderRadius: "15px",
}}
>
<CardMedia
component="img"
alt="faniot-img"
height="140"
image={img}
sx={{
borderRadius: "50%",
width: "140px",
alignSelf: "center",
mt: "10px",
mb: "20px",
}}
/>
<CardContent sx={{ background: "#3082AA" }}>
<Typography
gutterBottom
variant="h5"
component="div"
sx={{ color: "whitesmoke" }}
>
{name}
</Typography>
<Typography variant="body2" color="text.secondary">
{description}
</Typography>
</CardContent>
<CardActions
sx={{
display: "flex",
justifyContent: "center",
}}
>
<Typography variant="body2" sx={{paddingTop: '3px'}} >
{stateProduction === "PENDING"
? "EN PROCESO"
: stateProduction === "FINISHED"
? "TERMINADO"
: stateProduction === "ARCHIVED"
? "ARCHIVADO"
: stateProduction}
</Typography>
</CardActions>
</Card>
);
}

209
frontend/src/components/ProductModelInfo.jsx

@ -0,0 +1,209 @@
import { useState } from "react";
import Box from "@mui/material/Box";
import TextField from "@mui/material/TextField";
import Grid from "@mui/material/Grid";
import Button from "@mui/material/Button";
import DeleteIcon from "@mui/icons-material/Delete";
import Typography from "@mui/material/Typography";
import { showImage } from "../utils/imageUtils";
import { addProcessProduct } from "../services/processProduct";
import { useHistory } from "react-router-dom";
import { createFormData } from "../utils/formUtils";
import { deleteProductModel } from "../services/products";
import { CssBaseline } from "@mui/material";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTheme } from "@mui/material/styles";
import 'animate.css';
export default function ProductModelInfo({ product }) {
//popUp
const [open, setOpen] = useState(false);
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("md"));
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
//end PopUp
const [form, setForm] = useState({
modelId: product._id,
name: "",
users: "",
});
// handlers
const handleChange = (e) => {
setForm({
...form,
[e.target.name]: e.target.value,
});
};
const { name, description, imageType, imageData } = product;
const processImage = (imageData, contentType) => {
const img = showImage(imageData, contentType);
return img;
};
// history
const history = useHistory();
// handle addProcessProduct
const handleSubmit = async (e) => {
e.preventDefault();
const formData = await createFormData(form);
const res = await addProcessProduct(formData);
history.push("/fabricdashboard");
};
// handle deleteProduct
const handleSubmitDelete = (e) => {
e.preventDefault();
const response = deleteProductModel(product._id);
if (response !== null) {
console.log("wep");
history.push("/");
}
};
return (
<>
<Grid container component="main" sx={{ height: "100vh" }}>
<Grid
id="left-col"
item
xs={false}
sm={4}
md={6}
sx={{
backgroundColor: "#11181B",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<div id="imageBox" style={{ maxWidth: "50%" }} className='animate__animated animate__fadeIn' >
<img
src={`${processImage(imageData, imageType)}`}
alt="preview"
style={{
maxWidth: "100%",
borderRadius: "50%",
overflow: "hidden",
}}
/>
</div>
</Grid>
<Grid
id="right-col"
item
xs={12}
sm={8}
md={6}
sx={{
backgroundColor: "#11181B",
display: "flex",
alignItems: "center",
}}
>
<Box
sx={{
width: "100%",
my: 10,
mx: 4,
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Typography component="h1" variant="h3" color={"whitesmoke"}>
{name}
</Typography>
<Box sx={{ mt: 10, mb: 10, color: "whitesmoke" }}>
{description}
</Box>
<TextField
className="textField"
name="name"
label="Id"
helperText="Ingrese un identificador del producto a fabricar"
variant="outlined"
onChange={handleChange}
style={{ marginBottom: "2ch" }}
/>
<div
style={{
display: "flex",
flexDirection: "row",
width: "35%",
marginTop: "5%",
}}
>
<Button
color="error"
variant="contained"
sx={{ mt: 3, mb: 2, mr: 5 }}
onClick={handleClickOpen}
>
<DeleteIcon />
</Button>
<Button
className="btn-hover"
variant="contained"
sx={{
mt: 3,
mb: 2,
backgroundColor: "#3082aa",
width: "200px",
}}
onClick={handleSubmit}
endIcon={<ChevronRightIcon />}
>
FABRICAR
</Button>
{/* POP UP DIALOG */}
<Dialog
fullScreen={fullScreen}
open={open}
onClose={handleClose}
aria-labelledby="responsive-dialog-title"
>
<DialogContent>
<h3 style={{ fontWeight: "400", color: "lightgrey " }}>
¿Está seguro/a que desea eliminar el elemento?
</h3>
</DialogContent>
<DialogActions>
<Button className="textGrey" autoFocus onClick={handleClose}>
NO
</Button>
<Button className="textGrey" onClick={handleSubmitDelete} autoFocus>
SI
</Button>
</DialogActions>
</Dialog>
</div>
</Box>
</Grid>
</Grid>
</>
);
}

653
frontend/src/components/SupplyEdit.jsx

@ -0,0 +1,653 @@
import { useState, useEffect, useRef } from "react";
import Button from "@mui/material/Button";
import { showImage } from "../utils/imageUtils";
import EdiText from "react-editext";
import DeleteIcon from "@mui/icons-material/Delete";
import { useSelector } from "react-redux";
import styled from "styled-components";
import MenuItem from "@mui/material/MenuItem";
import TextField from "@mui/material/TextField";
import { deleteSupply } from "../services/supplies";
import { useHistory } from "react-router-dom";
import { createFormData } from "../utils/formUtils";
import { DialogTitle } from "@mui/material";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTheme } from "@mui/material/styles";
import { editSupplyById } from "../services/supplies";
import { getProviders, addProvider } from "../services/providers";
import { getCategories, addCategory } from "../services/categories";
import AddIcon from "@mui/icons-material/Add";
//sockets
import io from "socket.io-client";
const H1Style = styled(EdiText)`
button {
border: none;
background: rgba(0, 0, 0, 0);
border-radius: 50%;
width: 20px;
}
button:hover {
background: #11181b;
}
button[editext="edit-button"] {
transform: scaleX(-1);
font-size: x-large;
color: grey;
width: 50px;
}
button[editext="save-button"] {
}
button[editext="cancel-button"] {
}
input,
textarea {
font-family: "Roboto";
font-size: x-large;
border: none;
}
div[editext="view-container"],
div[editext="edit-container"] {
font-family: "Roboto";
font-size: xx-large;
color: whitesmoke;
}
`;
const H2Style = styled(EdiText)`
button {
border: none;
background: rgba(0, 0, 0, 0);
border-radius: 50%;
width: 20px;
}
button:hover {
background: #11181b;
}
button[editext="edit-button"] {
transform: scaleX(-1);
font-size: x-large;
color: grey;
width: 50px;
}
button[editext="save-button"] {
}
button[editext="cancel-button"] {
}
input,
textarea {
font-family: "Roboto";
font-size: large;
color: grey;
}
div[editext="view-container"],
div[editext="edit-container"] {
font-family: "Roboto";
font-size: large;
color: whitesmoke;
border-radius: 15px;
padding-left: 8px;
border: 0.1px solid rgba(200, 200, 200, 0.1);
margin-top: 10px;
align-self: center;
width: 100%;
}
`;
const API = process.env.REACT_APP_API;
export default function SupplyEdit() {
const socketRef = useRef();
const [categories, setCategories] = useState([{ name: "Loading..." }]);
const [providers, setProviders] = useState([]);
const [newCategory, setNewCategory] = useState({ nombre: "" });
const [newProvider, setNewProvider] = useState({ name: "" });
// PROVIDERS
const handleChangeProviders = (event) => {
setNewProvider({
...newProvider,
[event.target.name]: event.target.value,
});
};
const handleSubmitProvider = (event) => {
event.preventDefault();
console.log(newProvider.name);
addProvider({
name: newProvider.name,
description: "",
});
handleClose();
};
//Crea el array de proveedores y queda a la escucha de nuevos posts
useEffect(() => {
socketRef.current = io.connect(`${API}`);
socketRef.current.on("listenProviders", async () => {
loadProviders();
});
const loadProviders = async () => {
const providers = await getProviders();
setProviders(providers);
console.log(providers);
};
loadProviders();
}, []);
//Crea el array de categorías y queda a la escucha de nuevos posts
useEffect(() => {
socketRef.current = io.connect(`${API}`);
socketRef.current.on("listenCategories", async () => {
loadCategories();
});
const loadCategories = async () => {
const categories = await getCategories();
setCategories(categories);
console.log(categories);
};
loadCategories();
}, []);
console.log(" categories ", categories);
console.log(" providers ", providers);
//popUp
const [open, setOpen] = useState(false);
const [openC, setOpenC] = useState(false);
const [openP, setOpenP] = useState(false);
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("md"));
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
setOpenC(false);
setOpenP(false);
};
const handleAddCategory = () => {
setOpenC(true);
};
const handleAddProvider = () => {
setOpenP(true);
};
//end PopUp
const handleChangeCategories = (event) => {
setNewCategory({
...newCategory,
[event.target.name]: event.target.value,
});
};
const handleSubmitCategory = (event) => {
event.preventDefault();
console.log(newCategory.nombre);
addCategory({
name: newCategory.nombre,
description: "Es una descripción",
});
handleClose();
};
// history
const history = useHistory();
const componentSelected = useSelector((state) => state.componentSelected);
console.log("componentSelected");
console.log(componentSelected);
const [image, setImage] = useState({
preview: componentSelected.imageSupply,
raw: "",
});
console.log("image");
console.log(image);
// form state
const [form, setForm] = useState({
name: componentSelected.name,
description: componentSelected.description,
code: componentSelected.code,
note: componentSelected.note,
unity: componentSelected.unity,
quantity: componentSelected.quantity,
location: componentSelected.location,
imageSupply: componentSelected.imageSupply,
// category: componentSelected.category,
// price: componentSelected.price,
});
//handlers
//delete service
const handleSubmitDelete = (e) => {
e.preventDefault();
console.log("componentSelected._id");
console.log(componentSelected._id);
const response = deleteSupply(componentSelected._id);
if (response) {
history.push("/supplies");
}
};
// handlers for form
const handleForm = (value, inputProp) => {
setForm({
...form,
[inputProp]: value,
});
};
const handleFormCat = (e) => {
setForm({
...form,
[e.target.name]: e.target.value,
});
};
// image
const handleImage = (e) => {
console.log("e.target.files[0]");
console.log(e.target.files[0]);
if (e.target.files.length) {
setImage({
preview: URL.createObjectURL(e.target.files[0]),
raw: e.target.files[0],
});
}
console.log("image 2");
console.log(e.target.files[0]);
setForm({
...form,
imageSupply: e.target.files[0],
});
};
// const handleTest = () => {
// console.log("test");
// console.log(form);
// };
//edit submit hanlder
const handleEditSubmit = (e) => {
e.preventDefault();
const formData = createFormData(form);
console.log("formData desde front" + formData);
editSupplyToServer(componentSelected._id, formData);
};
//services
const editSupplyToServer = async (id, formData) => {
const response = await editSupplyById(componentSelected._id, formData);
if (response !== null) {
history.push("/supplies");
}
};
// process image
const processImage = (imageData, contentType) => {
const img = showImage(imageData, contentType);
return img;
};
let productImage = processImage(
componentSelected.imageData,
componentSelected.imageType
);
useEffect(() => {
const loadProviders = async () => {
const providers = await getProviders();
setProviders(providers);
};
const loadCategories = async () => {
const categories = await getCategories();
setCategories(categories);
};
loadProviders();
loadCategories();
}, []);
return (
<div
id="container-screen"
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100%",
flexDirection: "column",
}}
>
<div
id="card"
style={{
width: "80%",
height: "80%",
marginTop: "5%",
background: "#23282c",
borderRadius: "10px",
display: "flex",
flexDirection: "row",
}}
>
<div
id="left-column"
style={{
display: "flex",
justifyContent: "center",
width: "30%",
alignItems: "center",
// background: "yellow",
}}
>
<div id="imageBox" style={{ maxWidth: "50%" }}>
<Button
href="#"
onClick={(e) => {
e.preventDefault();
document.getElementById("image").click();
}}
>
<img
// src={image.preview ? image.preview : productImage} // TODO
src={image.preview ? image.preview : productImage}
alt="preview"
style={{
maxWidth: "100%",
borderRadius: "50%",
overflow: "hidden",
}}
/>
</Button>
<input
type="file"
id="image"
name="image"
accept="image/*"
style={{ display: "none" }}
onChange={handleImage}
/>
</div>
</div>
<div
id="right-col"
style={{
width: "70%",
display: "flex",
justifyContent: "center",
flexDirection: "column",
// background: 'yellow'
}}
>
<div
style={{
width: "90%",
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
}}
>
<H1Style
inputProps="name"
type="text"
value={form.name}
onSave={handleForm}
/>
<div
style={{ display: "flex", flexDirection: "row", width: "40%" }}
>
<TextField
className="textField"
name="provider"
select
helperText="Seleccione proveedor"
//defaultValue="provider1"
style={{ width: "80%", marginBottom: "30px" }}
onChange={handleFormCat}
>
{providers.map((option) => (
<MenuItem key={option._id} value={option._id}>
{option.name}
</MenuItem>
))}
</TextField>
<Button
className="btn-hover"
variant="contained"
sx={{
background: "#3082AA !important",
width: "55px",
height: "55px",
marginLeft: "10px",
marginRight: "10px",
}}
onClick={handleAddProvider}
>
<AddIcon />
</Button>
</div>
<div
style={{ display: "flex", flexDirection: "row", width: "40%" }}
>
{/* TextField to show Categories */}
<TextField
className="textField"
name="category"
select
helperText="Seleccione categoria"
style={{ width: "80%", marginBottom: "30px" }}
onChange={handleFormCat}
>
{categories.map((option) => (
<MenuItem key={option._id} value={option._id}>
{option.name}
</MenuItem>
))}
</TextField>
<Button
className="btn-hover"
variant="contained"
sx={{
background: "#3082AA !important",
height: "55px",
marginLeft: "10px",
}}
onClick={handleAddCategory}
>
<AddIcon />
</Button>
</div>
</div>
<div style={{ height: "120px", width: "90%" }}>
<p className="textGrey">Código</p>
<H2Style
inputProps="code"
type="text"
value={form.code}
onSave={handleForm}
/>
</div>
<div style={{ height: "120px", width: "90%" }}>
<p className="textGrey">Descripción</p>
<H2Style
inputProps="description"
type="text"
value={form.description}
onSave={handleForm}
/>
</div>
<div
style={{
width: "90%",
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
}}
>
<div style={{ display: "flex", flexDirection: "column" }}>
<p className="textGrey">Ubicacion</p>
<H2Style
inputProps="location"
type="text"
value={form.location}
onSave={handleForm}
/>
</div>
<div style={{ display: "flex", flexDirection: "column" }}>
<p className="textGrey">Cantidad</p>
<H2Style
inputProps="quantity"
type="text"
value={form.quantity}
onSave={handleForm}
/>
</div>
<div style={{ display: "flex", flexDirection: "column" }}>
<p className="textGrey">Unidad de medida</p>
<H2Style
inputProps="unity"
type="text"
value={form.unity}
onSave={handleForm}
/>
</div>
<div style={{ display: "flex", flexDirection: "column" }}>
<p className="textGrey">Precio</p>
<H2Style
inputProps="price"
type="text"
value={form.price}
onSave={handleForm}
/>
</div>
</div>
</div>
</div>
{/* footer */}
<div
style={{
width: "80%",
display: "flex",
justifyContent: "space-between",
marginTop: "1%",
}}
>
{/* <button onClick={handleTest}>TEST</button> */}
<div>
<Button variant="contained" color="error" onClick={handleClickOpen}>
<DeleteIcon />
</Button>
</div>
<Button
className="btn-hover"
variant="contained"
sx={{ background: "#3082AA !important" }}
onClick={handleEditSubmit}
>
CONFIRMAR
</Button>
{/* POP UP DIALOG CATEGORY */}
<Dialog open={openC} onClose={handleClose}>
<DialogTitle>Nueva Categoría</DialogTitle>
<DialogContent>
<TextField
autoFocus
name="nombre"
margin="dense"
id="category"
label="Categoría"
onChange={handleChangeCategories}
type="text"
fullWidth
variant="standard"
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancelar</Button>
<Button onClick={handleSubmitCategory}>Confirmar</Button>
</DialogActions>
</Dialog>
{/* POP UP DIALOG PROVIDERS */}
<Dialog open={openP} onClose={handleClose}>
<DialogTitle>Nuevo Proveedor</DialogTitle>
<DialogContent>
<TextField
autoFocus
name="name"
margin="dense"
id="provider"
label="Proveedor"
onChange={handleChangeProviders}
type="string"
fullWidth
variant="standard"
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancelar</Button>
<Button onClick={handleSubmitProvider}>Confirmar</Button>
</DialogActions>
</Dialog>
{/* POP UP DIALOG */}
<Dialog
fullScreen={fullScreen}
open={open}
onClose={handleClose}
aria-labelledby="responsive-dialog-title"
>
<DialogContent>
<h3 style={{ fontWeight: "400", color: "lightgrey " }}>
¿Está seguro/a que desea eliminar el elemento?
</h3>
</DialogContent>
<DialogActions>
<Button className="textGrey" autoFocus onClick={handleClose}>
NO
</Button>
<Button className="textGrey" onClick={handleSubmitDelete} autoFocus>
SI
</Button>
</DialogActions>
</Dialog>
</div>
</div>
);
}

20
frontend/src/components/emptyBox.jsx

@ -0,0 +1,20 @@
import { useLottie } from "lottie-react";
import emptyBox from "../assets/emptyBox.json";
const EmptyBox = () => {
const options = {
animationData: emptyBox,
loop: true,
autoplay: true,
};
const style = {
height: 150,
};
const { View } = useLottie(options, style);
return <>{View}</>;
};
export default EmptyBox;

30
frontend/src/hooks/useFetch.js

@ -0,0 +1,30 @@
import { useEffect, useState } from "react";
const useFetch = (request) => {
const [data, setData] = useState(null);
const [isFetching, setIsFetching] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const doFetch = async () => {
const fetchData = async () => {
try {
setIsFetching(true);
const data = await request;
setData(data);
} catch (e) {
setError(e);
} finally {
setIsFetching(false);
}
};
fetchData();
};
doFetch();
}, [request]);
return { data, isFetching, error };
};
export default useFetch;

16
frontend/src/index.js

@ -0,0 +1,16 @@
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
//redux
import { Provider } from "react-redux";
import store from "./reducers/store";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);

1
frontend/src/logo.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

373
frontend/src/reducers/actions.js

@ -0,0 +1,373 @@
//actions
const initState = {
// list components full data
componentsFull: [],
// list of components
components: [],
// list of products
products: [],
// List of components without quantity
componentsModel: [],
// info user
user: {},
// components for product
componentsProduct: [],
// product selected
productSelected: {},
// component selected
componentSelected: {},
// List of process products
processProducts: [],
// state of checked process products components
processProductsChecked: [],
// process request
processRequest: {
loading: false,
},
processProductsThunk: {
loading: false,
data: {},
error: null,
},
// c of products
componentstoProduct: [],
// notes
notesThunk: {
loading: false,
data: {},
error: null,
},
// supplies //TODO
suppliesThunk: {
loading: false,
data: {},
error: null,
},
// supplies
supplies: [],
//providers
providers: [],
// select provider
providerSelected: [],
newPrice: 0,
newProviderThunk: {
loading: false,
data: {},
error: null,
},
};
//action types
const ADD_COMPONENT = "ADD_COMPONENT";
const ADD_PRODUCT = "ADD_PRODUCT";
const GET_PROCESS_PRODUCTS = "GET_PROCESS_PRODUCTS";
const GET_PRODUCTS = "GET_PRODUCTS";
const GET_COMPONENTS = "GET_COMPONENTS";
const ADD_COMPONENTS_PRODUCT = "ADD_COMPONENTS_PRODUCT";
const REMOVE_COMPONENTS_PRODUCT = "REMOVE_COMPONENTS_PRODUCT";
const CLEAN_COMPONENTS_PRODUCT = "CLEAN_COMPONENTS_PRODUCT";
const SET_QUANTITY_COMPONENT_PRODUCT = "SET_QUANTITY_COMPONENT_PRODUCT";
const PRODUCT_SELECTED = "PRODUCT_SELECTED";
const COMPONENT_SELECTED = "COMPONENT_SELECTED";
const RESET_COMPONENTS_PRODUCT = "RESET_COMPONENTS_PRODUCT";
const CHECKED_PROCESS_PRODUCTS = "CHECKED_PROCESS_PRODUCTS";
const SET_PROCESS_REQUEST = "SET_PROCESS_REQUEST";
const START_PROCESS_PRODUCTS = "START_PROCESS_PRODUCTS";
const ERROR_PROCESS_PRODUCTS = "ERROR_PROCESS_PRODUCTS";
const SUCCESS_PROCESS_PRODUCTS = "SUCCESS_PROCESS_PRODUCTS";
const START_NOTES = "START_NOTES";
const ERROR_NOTES = "ERROR_NOTES";
const SUCCESS_NOTES = "SUCCESS_NOTES";
const REQUEST_GET_SUPPLIES = "REQUEST_GET_SUPPLIES";
const ERROR_GET_SUPPLIES = "ERROR_GET_SUPPLIES";
const SUCCESS_GET_SUPPLIES = "SUCCESS_GET_SUPPLIES";
//add providers
const ADD_PROVIDER = "ADD_PROVIDER";
const GET_PROVIDERS = "GET_PROVIDERS";
//provider selected
const PROVIDER_SELECTED = "PROVIDER_SELECTED";
//new price
const NEW_PRICE = "NEW_PRICE";
//thunk new price
const REQUEST_NEW_PROVIDER = "REQUEST_NEW_PROVIDER";
const ERROR_NEW_PROVIDER = "ERROR_NEW_PROVIDER";
const SUCCESS_NEW_PROVIDER = "SUCCESS_NEW_PROVIDER";
//reducer
export default function reducer(state = initState, action) {
switch (action.type) {
case ADD_COMPONENT:
return {
...state,
components: [action.payload],
};
case GET_COMPONENTS:
// remove field quantity from components
const compForModel = action.payload.map((component) => {
const { quantity, ...rest } = component;
return rest;
});
return {
...state,
components: action.payload,
componentsModel: compForModel,
componentsFull: action.payload,
};
case ADD_PRODUCT:
return {
...state,
products: [action.payload],
};
case ADD_COMPONENTS_PRODUCT:
//verify if the component is already in the list
let components = action.payload;
let componentsProduct = state.componentsModel;
let prevState = state.componentsProduct;
// add _id from prevState to components
prevState.forEach((component) => {
components.forEach((comp) => {
if (component._id !== comp._id) {
components.push(component);
}
});
});
componentsProduct = componentsProduct.filter((component) => {
return components.find((comp) => {
return comp._id === component._id;
});
});
// add field quantity to the componentsProduct
componentsProduct.forEach((component) => {
// verify if field quantity is already in the component
if (!component.hasOwnProperty("quantity")) {
component.quantity = 1;
}
});
return {
...state,
componentsProduct: componentsProduct,
};
case REMOVE_COMPONENTS_PRODUCT:
let idComponentRemove = action.payload;
let componentsProductRemove = state.componentsProduct;
componentsProductRemove = componentsProductRemove.filter((component) => {
return component._id !== idComponentRemove;
});
return {
...state,
componentsProduct: componentsProductRemove,
};
case CLEAN_COMPONENTS_PRODUCT:
return {
...state,
componentsProduct: [],
};
case SET_QUANTITY_COMPONENT_PRODUCT:
//verify if the component is already in the list
let componentsProduct2 = state.componentsProduct;
let componentQuantity = action.payload;
componentsProduct2.forEach((component) => {
if (component._id === componentQuantity.id) {
component.quantity = parseInt(componentQuantity.quantity);
}
});
return {
...state,
componentsProduct: componentsProduct2,
};
case GET_PRODUCTS:
return {
...state,
products: action.payload,
};
case PRODUCT_SELECTED:
return {
...state,
productSelected: action.payload,
};
case COMPONENT_SELECTED:
return {
...state,
componentSelected: action.payload,
};
case RESET_COMPONENTS_PRODUCT:
return {
...state,
componentsProduct: [],
};
case GET_PROCESS_PRODUCTS:
return {
...state,
processRequest: {
loading: false,
},
processProducts: action.payload,
};
case CHECKED_PROCESS_PRODUCTS:
return {
...state,
processProductsChecked: action.payload,
};
case SET_PROCESS_REQUEST:
return {
...state,
processRequest: action.payload,
};
case START_PROCESS_PRODUCTS:
return {
...state,
processProductsThunk: {
loading: true,
data: {},
error: null,
},
};
case ERROR_PROCESS_PRODUCTS:
return {
...state,
processProductsThunk: {
loading: false,
data: {},
error: action.payload,
},
};
case SUCCESS_PROCESS_PRODUCTS:
return {
...state,
componentstoProduct: action.payload.resComponents,
productSelected: {
...state.productSelected,
components: action.payload.resProduct.components,
},
processProductsThunk: {
loading: false,
data: action.payload.resProduct,
error: null,
},
};
case START_NOTES:
return {
...state,
notesThunk: {
loading: true,
data: {},
error: null,
},
};
case ERROR_NOTES:
return {
...state,
notesThunk: {
loading: false,
data: {},
error: action.payload,
},
};
case SUCCESS_NOTES:
return {
...state,
notesThunk: {
loading: false,
data: action.payload,
error: null,
},
};
case REQUEST_GET_SUPPLIES:
return {
...state,
suppliesThunk: {
loading: true,
data: {},
error: null,
},
};
case ERROR_GET_SUPPLIES:
return {
...state,
suppliesThunk: {
loading: false,
data: {},
error: action.payload,
},
};
case SUCCESS_GET_SUPPLIES:
return {
...state,
suppliesThunk: {
loading: false,
data: action.payload,
error: null,
},
};
case ADD_PROVIDER:
return {
...state,
providers: [action.payload],
};
case GET_PROVIDERS:
return {
...state,
providers: action.payload,
};
case PROVIDER_SELECTED:
return {
...state,
providerSelected: action.payload,
};
case NEW_PRICE:
return {
...state,
newPrice: action.payload,
};
case REQUEST_NEW_PROVIDER:
return {
...state,
newProviderThunk: {
loading: true,
data: {},
error: null,
},
};
case ERROR_NEW_PROVIDER:
return {
...state,
newProviderThunk: {
loading: false,
data: {},
error: action.payload,
},
};
case SUCCESS_NEW_PROVIDER:
return {
...state,
newProviderThunk: {
loading: false,
data: action.payload,
error: null,
},
};
default:
return state;
}
}

34
frontend/src/reducers/actionsProcessProducts.js

@ -0,0 +1,34 @@
import { getComponentsByIds } from "../services/components";
import { getProcessProductById } from "../services/processProduct";
const START_PROCESS_PRODUCTS = "START_PROCESS_PRODUCTS";
const ERROR_PROCESS_PRODUCTS = "ERROR_PROCESS_PRODUCTS";
const SUCCESS_PROCESS_PRODUCTS = "SUCCESS_PROCESS_PRODUCTS";
export const getProcessProductsThunk = (processProductId) => {
return async (dispatch) => {
dispatch({ type: START_PROCESS_PRODUCTS });
try {
const response = await getProcessProductById(processProductId);
// get components ids
let ids = [];
response.components.forEach((component) => {
ids.push(component._id);
});
// get components
const components = await getComponentsByIds(ids);
let finalResponse = {
resProduct: response,
resComponents: components,
};
dispatch({ type: SUCCESS_PROCESS_PRODUCTS, payload: finalResponse });
} catch (error) {
dispatch({ type: ERROR_PROCESS_PRODUCTS, payload: error });
}
};
};

33
frontend/src/reducers/notes.js

@ -0,0 +1,33 @@
import { getNotesByIdSupply } from "../services/supplies";
import { addOrUpdateNotes } from "../services/supplies";
const START_NOTES = "START_NOTES";
const ERROR_NOTES = "ERROR_NOTES";
const SUCCESS_NOTES = "SUCCESS_NOTES";
export const getNotesThunk = (supplyId) => {
return async (dispatch) => {
dispatch({ type: START_NOTES });
try {
const res = await getNotesByIdSupply(supplyId);
dispatch({ type: SUCCESS_NOTES, payload: res });
} catch (error) {
dispatch({ type: ERROR_NOTES, payload: error });
}
};
};
export const addOrUpdateNotesThunk = (supplyId, note) => {
return async (dispatch) => {
dispatch({ type: START_NOTES });
try {
const res = await addOrUpdateNotes(supplyId, note);
dispatch({ type: SUCCESS_NOTES, payload: res });
} catch (error) {
dispatch({ type: ERROR_NOTES, payload: error });
}
};
};

40
frontend/src/reducers/providers.js

@ -0,0 +1,40 @@
import { addProvider } from "../services/providers";
const ADD_PROVIDER = "ADD_PROVIDER";
const PROVIDER_SELECTED = "PROVIDER_SELECTED";
const NEW_PRICE = "NEW_PRICE";
export const addProviderRedux = (formData) => {
return async (dispatch) => {
try {
const res = await addProvider(formData);
dispatch({ type: ADD_PROVIDER, payload: res });
} catch (error) {
dispatch({ type: ADD_PROVIDER, payload: error });
}
};
};
// save selected provider
export const providerSelectedRedux = (provider) => {
console.log("provider desde redux");
console.log(provider);
return async (dispach) => {
try {
dispach({ type: PROVIDER_SELECTED, payload: provider });
} catch (error) {
console.log(error);
}
};
};
//save new price value
export const newPriceRedux = (price) => {
return async (dispach) => {
try {
dispach({ type: NEW_PRICE, payload: price });
} catch (error) {
console.log(error);
}
};
};

10
frontend/src/reducers/store.js

@ -0,0 +1,10 @@
//create store
import thunk from "redux-thunk";
import { createStore, applyMiddleware } from "redux";
import reducer from "./actions";
import { composeWithDevTools} from "redux-devtools-extension";
// export create Store with thunk and devtools
export default createStore(reducer, composeWithDevTools(applyMiddleware(thunk)));

55
frontend/src/reducers/supplies.js

@ -0,0 +1,55 @@
import { getSupplies, assignProviderAndPrice } from "../services/supplies";
const REQUEST_GET_SUPPLIES = "REQUEST_GET_SUPPLIES";
const ERROR_GET_SUPPLIES = "ERROR_GET_SUPPLIES";
const SUCCESS_GET_SUPPLIES = "SUCCESS_GET_SUPPLIES";
const REQUEST_NEW_PROVIDER = "REQUEST_NEW_PROVIDER";
const ERROR_NEW_PROVIDER = "ERROR_NEW_PROVIDER";
const SUCCESS_NEW_PROVIDER = "SUCCESS_NEW_PROVIDER";
export const getSuppliesThunk = () => {
return async (dispatch) => {
dispatch({ type: REQUEST_GET_SUPPLIES });
try {
const res = await getSupplies();
dispatch({ type: SUCCESS_GET_SUPPLIES, payload: res });
} catch (error) {
dispatch({ type: ERROR_GET_SUPPLIES, payload: error });
}
};
};
// save new data in newProviderThunk
export const newProviderThunkRedux = (idSupply, idProvider, price) => {
// console.log("desde supplies reducer");
// console.log(idSupply, idProvider, price);
return async (dispatch) => {
dispatch({ type: REQUEST_NEW_PROVIDER });
try {
const res = await assignProviderAndPrice(idSupply, idProvider, price);
// console.log("res from supplies reducer", res);
dispatch({ type: SUCCESS_NEW_PROVIDER, payload: res});
} catch (error) {
dispatch({ type: ERROR_NEW_PROVIDER, payload: error });
}
};
};
// // save new price value in new_priceThunk
// export const newProviderThunkRedux = (idSupply, idProvider, price) => {
// console.log('desde supplies reducer')
// console.log(idSupply, idProvider, price)
// return async (dispatch) => {
// try {
// dispatch({ type: REQUEST_NEW_PROVIDER });
// const res = await assignProviderAndPrice(idSupply, idProvider, price);
// console.log('res from supplies reducer',)
// dispatch({ type: SUCCESS_NEW_PROVIDER, payload: res });
// } catch (error) {
// dispatch({ type: ERROR_NEW_PROVIDER, payload: error });
// }
// };
// };

28
frontend/src/services/categories.js

@ -0,0 +1,28 @@
import axios from "axios";
const API = process.env.REACT_APP_API;
//get all supplies
export const getCategories = async () => {
const res = await axios.get(`${API}/api/categories`);
if (res.status === 201) {
console.log('desde el service', res.data.body);
return res.data.body;
} else {
return;
}
};
//add new provider
export const addCategory = async (formData) => {
const res = await axios({
method: "post",
url: `${API}/api/categories`,
data: formData,
});
if (res.status === 201) {
return res.data;
} else {
return;
}
};

96
frontend/src/services/components.js

@ -0,0 +1,96 @@
import axios from "axios";
const API = process.env.REACT_APP_API;
//get all components
export const getComponents = async () => {
console.log(API);
const res = await axios.get(
`${API}/api/inventory/components/get`
);
if (res.status === 200) {
return res.data;
} else {
return;
}
};
// add component
export const addComponent = async (formData) => {
const res = await axios({
method: "post",
url: `${API}/api/inventory/components/add`,
data: formData,
headers: {
"Content-Type": "multipart/form-data",
},
});
if (res.status === 200) {
return res.data;
} else {
return;
}
};
// get component by Id
export const getComponentsByIds = async (ids) => {
const res = await axios.post(
`${API}/api/inventory/components/getids`,
{ ids }
);
if (res.status === 200) {
return res.data;
} else {
return;
}
};
export const deleteComponent = async (id) => {
const res = await axios.delete(
`${API}/api/inventory/components/delete/${id}`
);
if (res.status === 200) {
return res.data;
} else {
return;
}
};
// edit component by Id
export const editComponentById = async (id, formData) => {
const res = await axios({
method: "put",
url: `${API}/api/inventory/components/editdata/${id}`,
data: formData,
headers: {
"Content-Type": "multipart/form-data",
},
});
if (res.status === 200) {
return res.data;
} else {
return;
}
};
// send csv file to server
export const sendCsvFile = async (formData) => {
const res = await axios({
method: "post",
url: `${API}/api/inventory/components/csv`,
data: formData,
headers: {
"Content-Type": "multipart/form-data",
},
});
if (res.status === 200) {
return res.data;
} else {
return;
}
};

33
frontend/src/services/notes.js

@ -0,0 +1,33 @@
import axios from "axios";
const API = process.env.REACT_APP_API;
//get notes by id component
export const getNotesByIdComponent = async (ids) => {
const res = await axios.get(
`${API}/api/inventory/notes/${ids}`
);
if (res.status === 200) {
return res.data;
} else {
return;
}
};
// add or update notes
export const addOrUpdateNotes = async (componentId, data) => {
console.log(componentId, data);
const res = await axios({
method: "post",
url: `${API}/api/inventory/notes/`,
data: {
componentId: componentId,
data: data,
},
});
if (res.status === 200) {
return res.data;
} else {
return;
}
};

48
frontend/src/services/orders.js

@ -0,0 +1,48 @@
import axios from "axios";
const API = process.env.REACT_APP_API;
// add new order
// add supply
export const addOrder = async (formData) => {
console.log("NewOrder desde el service");
console.log("formData", formData);
const res = await axios({
method: "post",
url: `${API}/api/orders`,
data: formData,
headers: {
"Content-Type": "application/json",
},
});
if (res.status === 201) {
return res.data;
} else {
return;
}
};
// get list providers by supply Id
export const getListProvidersByIdSupply = async (id) => {
console.log("desde service listProviderByIdSupply", id);
const res = await axios.get(`${API}/api/supplies/listproviders?id=${id}`);
if (res.status === 201) {
console.log("RESPUESTA" + res.data.body);
return res.data.body;
} else {
return;
}
};
// get orders by supply id // TODO! OLD
export const getOrdersBySupplyId = async (supplyId) => {
console.log("supplyId desde el service", supplyId);
const res = await axios.get(`${API}/api/orders?id=${supplyId}`);
if (res.status === 201) {
console.log("exito status 201 .res.data.body: ", res.data.body);
return res.data.body;
} else {
return;
}
};

73
frontend/src/services/processProduct.js

@ -0,0 +1,73 @@
import axios from "axios";
const API = process.env.REACT_APP_API;
export const getProcessProducts = async () => {
const res = await axios.get(`${API}/api/inventory/process`);
if (res.status === 200) {
return res.data;
} else {
return;
}
};
export const getProcessProductById = async (id) => {
const res = await axios.get(
`${API}/api/inventory/process/${id}`
);
console.log(res);
if (res.status === 200) {
return res.data;
} else {
return;
}
};
export const addProcessProduct = async (formData) => {
const res = await axios({
method: "post",
url: `${API}/api/inventory/process/add`,
data: formData,
headers: {
"Content-Type": "multipart/form-data",
},
});
if (res.status === 200) {
return res.data;
} else {
return;
}
};
export const updateQuantity = async (formData, id) => {
const res = await axios({
method: "put",
url: `${API}/api/inventory/process/quantity/${id}`,
data: formData,
});
if (res.status === 200) {
return res.data;
} else {
return;
}
};
export const updateStatus = async (id, status) => {
const res = await axios({
method: "put",
url: `${API}/api/inventory/process/state/${id}`,
data: status,
headers: {
"content-type": "application/json",
},
});
if (res.status === 200) {
return res.data;
} else {
return;
}
};

48
frontend/src/services/products.js

@ -0,0 +1,48 @@
import axios from "axios";
const API = process.env.REACT_APP_API;
export const addProduct = async (formData) => {
const res = await axios({
method: "post",
url: `${API}/api/inventory/products/addmodel`,
data: formData,
headers: {
"Content-Type": "multipart/form-data",
},
});
if (res.status === 200) {
return res.data;
} else {
return;
}
};
export const getProducts = async () => {
const res = await axios({
method: "get",
url: `${API}/api/inventory/products/all`,
});
console.log(res);
if (res.status === 200) {
return res.data;
} else {
return;
}
};
export const deleteProductModel = async (id) => {
console.log("Eliminar");
console.log(id);
const res = await axios.delete(
`http://localhost:5000/api/inventory/products/deleteModel/${id}`
);
if (res.status === 200) {
return res.data;
} else {
return;
}
};

46
frontend/src/services/providers.js

@ -0,0 +1,46 @@
import axios from "axios";
const API = process.env.REACT_APP_API;
//add new provider
export const addProvider = async (formData) => {
const res = await axios({
method: "post",
url: `${API}/api/providers`,
data: formData,
});
if (res.status === 201) {
return res.data;
} else {
return;
}
};
// get all providers
export const getProviders = async () => {
const res = await axios.get(`${API}/api/providers`);
if (res.status === 201) {
return res.data.body;
} else {
return;
}
};
// add price to provider by Id
export const addPriceToProviderById = async (idProvider, price) => {
const res = await axios.put(
`${API}/api/providers/price/${idProvider}`,
{ price: price },
{
headers: {
"Content-Type": "application/json",
},
}
);
if (res.status === 201) {
return res.data;
} else {
return;
}
};

201
frontend/src/services/supplies.js

@ -0,0 +1,201 @@
import axios from "axios";
const API = process.env.REACT_APP_API;
//get all supplies
export const getSupplies = async () => {
const res = await axios.get(`${API}/api/supplies`);
if (res.status === 201) {
console.log("LOS SUPLAIS VIENEN ASI", res.data.body);
return res.data.body;
} else {
return;
}
};
// add supply
export const addSupply = async (formData) => {
console.log("Add Supply desde el service");
console.log("formData", formData);
const res = await axios({
method: "post",
url: `${API}/api/supplies`,
data: formData,
headers: {
"Content-Type": "multipart/form-data",
},
});
if (res.status === 200) {
return res.data;
} else {
return;
}
};
// get supply by Id
export const getSupplyById = async (id) => {
console.log("id en funcion " + id);
const res = await axios.get(`${API}/api/supplies?id=${id}`);
if (res.status === 201) {
return res.data.body[0];
} else {
console.log("no hace el get");
return;
}
};
//delete supply - status DB false
export const deleteSupply = async (id) => {
console.log("desde el service" + id);
const res = await axios.patch(`${API}/api/supplies/totrash/${id}`);
if (res.status === 201) {
return res;
} else {
return;
}
};
//delete supply definitely
export const deleteSupplyDefinitely = async (id) => {
console.log("deleteSupplyDefinitely desde el service " + id);
const res = await axios.delete(`${API}/api/supplies/${id}`);
if (res.status === 201) {
console.log("RES > " + res);
return res;
} else {
return;
}
};
//get trash supplies NUEVO
export const getTrashSupplies = async () => {
console.log("entro al getTrashSupplies");
const res = await axios.get(`${API}/api/supplies/trash`);
if (res.status === 201) {
console.log("LOS ARCHIVED SUPLAIS VIENEN ASI", res.data.body);
return res.data.body;
} else {
return;
}
};
// edit supply by Id
export const editSupplyById = async (id, formData) => {
console.log("Edit Supply desde el service");
console.log("id", id);
console.log("formData", formData);
const res = await axios.patch(`${API}/api/supplies/${id}`, formData, {
headers: {
"Content-Type": "multipart/form-data",
Accept: "application/json",
},
});
if (res.status === 201) {
return res.data;
} else {
return res.error;
}
};
// send csv file to server
export const sendCsvFile = async (formData) => {
const res = await axios({
method: "post",
url: `${API}/api/inventory/supply/csv`,
data: formData,
headers: {
"Content-Type": "multipart/form-data",
},
});
if (res.status === 200) {
return res.data;
} else {
return;
}
};
// assign provider to supply with patch method // TODO? ESTO EL METODO VIEJO.
export const assignProvider = async (idSupply, idProvider) => {
const res = await axios.patch(
`${API}/api/supplies/assign/${idSupply}`,
{ idProvider: idProvider },
{
headers: {
"Content-Type": "application/json",
},
}
);
if (res.status === 200) {
return;
} else {
return;
}
};
//asign provider and price to supply with patch method // TODO ESTO EL METODO NUEVO!!!.
export const assignProviderAndPrice = async (idSupply, idProvider, price) => {
console.log("dsde el service", idSupply, idProvider, price);
const res = await axios.patch(
`${API}/api/supplies/assignPrice/${idSupply}`,
{ provider: idProvider, price: price },
{
headers: {
"Content-Type": "application/json",
},
}
);
if (res.status === 200) {
console.log("res.data.body.providers[0] desde service...... !");
console.log(res.data.body);
return res.data.body;
} else {
return;
}
};
//get notes by supply Id
export const getNotesByIdSupply = async (id) => {
const res = await axios.get(`${API}/api/supplies?id=${id}`);
if (res.status === 201) {
console.log("desde service : ", res.data.body[0].note);
return res.data.body[0].note;
} else {
console.log("no hace el get");
return;
}
};
// add or update note by supply id
export const addOrUpdateNotes = async (id, note) => {
console.log("addOrUpdateNoteByIdSupply desde el service");
console.log("id", id);
console.log("note", note);
const res = await axios.patch(`${API}/api/supplies/updatenote/${id}`, {
note: note,
});
if (res.status === 201) {
return res.data;
} else {
return;
}
};
// // get list providers by supply Id
// export const getListProvidersByIdSupply = async (id) => {
// const res = await axios.get(`${API}/api/supplies/listproviders?id=${id}`);
// if (res.status === 201) {
// return res.data.body;
// } else {
// return;
// }
// };

26
frontend/src/utils/formUtils.js

@ -0,0 +1,26 @@
export const createFormData = (jsonData) => {
const formData = new FormData();
Object.keys(jsonData).forEach((key) => {
switch (typeof jsonData[key]) {
case "object":
if (jsonData[key] instanceof File) {
formData.append(key, jsonData[key]);
} else {
formData.append(key, JSON.stringify(jsonData[key]));
}
break;
case "string":
case "number":
formData.append(key, jsonData[key]);
break;
default:
formData.append(key, JSON.stringify(jsonData[key]));
}
});
return formData;
};

13
frontend/src/utils/getComponentsUtils.js

@ -0,0 +1,13 @@
import { useDispatch, useSelector } from "react-redux";
export const getComponentsById = (ids, componentsList) => {
let components = [];
componentsList.forEach((component) => {
if (ids.includes(component._id)) {
components.push(component);
}
});
return components;
};

4
frontend/src/utils/imageUtils.js

@ -0,0 +1,4 @@
export const showImage = (imageData, imageType) => {
const image = `data:${imageType};base64,${imageData}`;
return image;
};

40
frontend/src/utils/tableUtils.js

@ -0,0 +1,40 @@
import { Button, TextField } from "@mui/material";
import React from "react";
export const componentsJsonToDataGrid = (jsonComponents) => {
let columns = [
{
field: "id",
label: "ID",
},
{
field: "Name",
headerName: "Name",
},
{
field: "Description",
headerName: "Description",
},
{
field: "Category",
headerName: "Category",
},
];
let rows = [];
for (let i = 0; i < jsonComponents.length; i++) {
let row = {
id: jsonComponents[i]._id,
Name: jsonComponents[i].name,
Description: jsonComponents[i].description,
Category: jsonComponents[i].category,
};
rows.push(row);
}
return {
columns,
rows,
};
};

20
frontend/src/views/Calculator_View.jsx

@ -0,0 +1,20 @@
import Calculator from "../components/Calculator";
export default function Calculator_View() {
return (
<>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: 'center',
minHeight: "100vh",
background: "#0d1214",
}}
>
<Calculator />
</div>
</>
);
}

47
frontend/src/views/Components.jsx

@ -0,0 +1,47 @@
import React, { useEffect, useState } from "react";
import DataTableComponents from "../components/DataTableComponents";
import { useDispatch, useSelector } from "react-redux";
import { Button, Container, Grid } from "@mui/material";
import { Link } from "react-router-dom";
import ButtonAdd from "../components/ButtonAdd";
export default function Components() {
const components = useSelector((state) => state.components);
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
minHeight: "100vh",
width: "100vw",
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
minHeight: "100vh",
width: "95vw",
}}
>
<div
style={{
width: '100vw',
display: "flex",
justifyContent: "flex-end",
marginRight: "3%",
marginTop: '1%'
}}
>
<ButtonAdd router={"/newcomponent"} />
</div>
<DataTableComponents components={components} />
</div>
</div>
);
}

11
frontend/src/views/EditComponent.jsx

@ -0,0 +1,11 @@
import ComponentEdit from "../components/ComponentEdit";
export default function EditComponent() {
return (
<>
<div style={{ height: "100vh" }}>
<ComponentEdit />
</div>
</>
);
}

11
frontend/src/views/EditSupply.jsx

@ -0,0 +1,11 @@
import SupplyEdit from "../components/SupplyEdit";
export default function EditSuppy() {
return (
<>
<div style={{ height: "100vh" }}>
<SupplyEdit />
</div>
</>
);
}

40
frontend/src/views/FabricDashboard.jsx

@ -0,0 +1,40 @@
import React, { useEffect, useState } from "react";
import Grid from "@mui/material/Grid";
import { getProcessProducts } from "../services/processProduct";
import { useDispatch, useSelector } from "react-redux";
import CardGridFabric from "../components/CardGridFabric";
export default function FabricDashboard() {
const dispatch = useDispatch();
// redux state
const processProducts = useSelector((state) => state.processProducts);
const [refresh, setRefresh] = useState(false);
const getProcessProductsFromServer = async () => {
const data = await getProcessProducts();
if (data !== null) {
dispatch({ type: "GET_PROCESS_PRODUCTS", payload: data });
}
setRefresh(true);
};
useEffect(() => {
getProcessProductsFromServer();
}, [refresh]);
return (
<>
<Grid
container
spacing={0}
direction="column"
alignItems="center"
justifyContent="center"
style={{ minHeight: "100vh" }}
>
<CardGridFabric />
</Grid>
</>
);
}

69
frontend/src/views/Home.jsx

@ -0,0 +1,69 @@
import React, { useEffect, useState } from "react";
import CardGrid from "../components/CardGrid";
import ButtonAdd from "../components/ButtonAdd";
import { getProducts } from "../services/products";
import { useDispatch, useSelector } from "react-redux";
import GridHome from "../components/GridHome";
import EmptyBox from "../components/emptyBox";
export default function Home() {
const dispatch = useDispatch();
// delete info location state
// redux state
const products = useSelector((state) => state.products);
const getProductsFromServer = async () => {
const data = await getProducts();
if (data !== null) {
dispatch({ type: "GET_PRODUCTS", payload: data });
}
};
useEffect(() => {
getProductsFromServer();
}, []);
return (
<>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
minHeight: "100vh",
background: "#0d1214",
}}
>
<div
style={{
position: "fixed",
right: "2em",
top: "4em",
zIndex: "1000",
}}
>
<ButtonAdd router={"/newproductmodel"} />
</div>
{products.length < 1 ? (
<div style={{ width: "90vw" }}>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100vh",
}}
>
<EmptyBox />
</div>
</div>
) : (
<CardGrid />
)}
</div>
</>
);
}

24
frontend/src/views/Layout.jsx

@ -0,0 +1,24 @@
import React, { Children } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import NavBar from "../components/NavBar";
const Layout = ({ children }) => {
return (
<div className="lightblack">
<div
style={{
position: "absolute",
top: "0",
left: "0",
width: "100%",
zIndex: '1'
}}
>
<NavBar />
</div>
{children}
</div>
);
};
export default Layout;

36
frontend/src/views/NewComponent.jsx

@ -0,0 +1,36 @@
import React from "react";
import FormComponent from "../components/FormComponent";
import Grid from "@mui/material/Grid";
const NewComponent = () => {
return (
<>
{/* form */}
<Grid
container
spacing={0}
direction="column"
alignItems="center"
justifyContent="center"
style={{
minHeight: "100vh",
}}
>
<h2
style={{
color: "lightgrey",
marginBottom: "1%",
fontWeight: "400",
}}
>
AGREGAR COMPONENTE
</h2>
<FormComponent />
</Grid>
</>
);
};
export default NewComponent;

64
frontend/src/views/NewOrder_View.jsx

@ -0,0 +1,64 @@
import { Box } from "@mui/system";
import { useEffect, useState } from "react";
import FormNewOrder from "../components/FormNewOrder";
import { getSupplyById } from "../services/supplies";
import { useParams } from "react-router-dom";
import { Link } from "react-router-dom";
import ArrowBackIosIcon from "@mui/icons-material/ArrowBackIos";
const NewOrder_View = () => {
const { id } = useParams();
const [supply, setSupply] = useState({});
const loadSupply = async () => {
const supply = await getSupplyById(id);
setSupply(supply);
};
// use Effect get Supply by id
useEffect(() => {
loadSupply();
}, []);
console.log("supply", supply);
return (
<>
{/* form */}
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
minHeight: "100vh",
}}
>
<Box style={{ height: "100px" }}></Box>
<div className="wrapper-header-providers">
<Link to={"/supplies"}>
<div className="header-title text-hover">
<ArrowBackIosIcon style={{ fontSize: "15px" }} />
INSUMOS | PROVEEDORES | NUEVA ORDEN
</div>
</Link>
</div>
<div className="header-title-neworder">
<h2>{supply.name}</h2>
<h2
style={{
fontWeight: "400",
}}
>
NUEVA ORDEN
</h2>
</div>
<FormNewOrder supplyData={supply} />
</div>
</>
);
};
export default NewOrder_View;

303
frontend/src/views/NewProductFabric.jsx

@ -0,0 +1,303 @@
import React, { useCallback, useEffect, useState } from "react";
import {
Box,
Button,
MenuItem,
Stack,
TextField,
Typography,
} from "@mui/material";
import NewProductFabricComponents from "../components/NewProductFabricComponents";
import { useHistory } from "react-router-dom";
import LinearProgress, {
linearProgressClasses,
} from "@mui/material/LinearProgress";
import { showImage } from "../utils/imageUtils";
import { styled } from "@mui/material/styles";
import { useSelector } from "react-redux";
import { updateStatus } from "../services/processProduct";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTheme } from "@mui/material/styles";
// progress bar style
const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
height: 10,
borderRadius: 5,
[`&.${linearProgressClasses.colorPrimary}`]: {
backgroundColor:
theme.palette.grey[200],
},
[`& .${linearProgressClasses.bar}`]: {
borderRadius: 5,
backgroundColor: "#3082aa",
},
}));
//status options
const statusOptions = [
{
value: "PENDING",
label: "EN PROCESO",
},
{
value: "FINISHED",
label: "TERMINADO",
},
{
value: "ARCHIVED",
label: "ARCHIVADO",
},
];
export default function NewProductFabric() {
//popUp
const [open, setOpen] = useState(false);
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("md"));
const history = useHistory();
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
//end PopUp
// local state
const [percentage, setPercentage] = useState(0);
//redux
const productSelected = useSelector((state) => state.productSelected);
// process image
const processImage = (imageData, contentType) => {
const img = showImage(imageData, contentType);
return img;
};
let processProductImage = processImage(
productSelected.model.image.data,
productSelected.model.image.contentType
);
let statusNow = productSelected.stateProduction;
// form state
const [form, setForm] = useState(statusNow);
// handler for state
const handleChange = (e) => {
setForm(e.target.value);
};
//service
const editStatus = async () => {
// json object for send
const data = {
stateProduction: form,
};
const res = await updateStatus(productSelected._id, data);
history.push("/fabricdashboard");
return res;
};
useEffect(() => {
const value = progressbarPercent();
setPercentage(value);
}, [percentage]);
const progressbarPercent = () => {
let components = productSelected.components;
let total = components.map(
(component) => component.outstandingQuantity + component.finishedQuantity
);
let totalSum = total.reduce((a, b) => a + b, 0);
let totalFinished = 0;
components.forEach((component) => {
totalFinished += component.finishedQuantity;
});
let percent = (totalFinished * 100) / totalSum;
percent = Math.round(percent);
setPercentage(percent);
return percent;
};
return (
<div id="main-container" style={{ height: "100vh" }}>
<div
id="header"
style={{
backgroundColor: "#0d1214",
paddingTop: "4%",
paddingLeft: 10,
paddingBottom: 10,
paddingRight: 10,
height: "10vh",
display: "flex",
justifyContent: "space-between",
alignItems: "end",
}}
>
<Stack>
<div
style={{
// background: "yellow",
display: "flex",
flexDirection: "row",
alignItems: "end",
height: "100%",
width: 400,
}}
>
<Box id="img-box" width={75}>
<img
src={processProductImage}
alt="preview"
style={{ width: "100%", borderRadius: 50 }}
/>
</Box>
<div style={{ marginLeft: "1em" }}>
<Typography variant="h6" style={{ color: "whitesmoke" }}>
{productSelected.model.name}
</Typography>
<Typography variant="h4" style={{ color: "whitesmoke" }}>
{productSelected.name}
</Typography>
</div>
</div>
</Stack>
<div
id="progressBar"
style={{
display: "flex",
flexDirection: "column",
verticalAlign: "center",
marginBottom: 30,
width: "30%",
marginRight: "2%",
}}
>
<p
style={{
marginLeft: "50%",
marginBottom: "1%",
}}
>
{percentage}%
</p>
<BorderLinearProgress variant="determinate" value={percentage} />
</div>
<Box
style={{
alignSelf: "end",
paddingRight: 30,
}}
>
<TextField
name="stateProduction"
select
value={form}
onChange={handleChange}
style={{
width: "40ch",
border: "2px solid #3082aa",
textAlign: "center",
borderRadius: 50,
paddingRight: 5,
paddingLeft: 5,
marginBottom: 20,
}}
InputProps={{ disableUnderline: true }}
variant="standard"
>
{statusOptions.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
</Box>
</div>
<div
id="body"
style={{
height: "75vh",
padding: 20,
display: "flex",
justifyContent: "center",
}}
>
<div style={{ height: "100%" }}>
<NewProductFabricComponents
Scomponents={productSelected.model.components}
percentage={progressbarPercent}
/>
</div>
<Button
type="submit"
style={{
width: "200",
position: "absolute",
bottom: 50,
right: 100,
backgroundColor: "#3082aa",
}}
variant="contained"
onClick={handleClickOpen}
>
{/* <svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />
</svg> */}
CONFIRMAR
</Button>
<Dialog
fullScreen={fullScreen}
open={open}
onClose={handleClose}
aria-labelledby="responsive-dialog-title"
>
<DialogContent>
<h3 style={{ fontWeight: '400' , color: "lightgrey " }}>
¿Desea confirmar sin guardar los cambios?
</h3>
</DialogContent>
<DialogActions>
<Button className="textGrey" autoFocus onClick={handleClose}>
NO
</Button>
<Button className="textGrey" onClick={editStatus} autoFocus>
SI
</Button>
</DialogActions>
</Dialog>
</div>
</div>
);
}

21
frontend/src/views/NewProductModel.jsx

@ -0,0 +1,21 @@
import React from "react";
import FormProductModel from "../components/FormProductModel";
const NewProductModel = () => {
return (
<>
<div
style={{
height: "100vh",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<FormProductModel />
</div>
</>
);
};
export default NewProductModel;

35
frontend/src/views/NewSupply.jsx

@ -0,0 +1,35 @@
import Grid from "@mui/material/Grid";
import { Box } from "@mui/system";
import FormSupply from "../components/FormSupply";
const NewSupply = () => {
return (
<>
{/* form */}
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
minHeight: "100vh",
}}
>
<Box style={{height: '100px'}}></Box>
<h2
style={{
color: "lightgrey",
marginBottom: "1%",
fontWeight: "400",
}}
>
AGREGAR INSUMO
</h2>
<FormSupply />
</div>
</>
);
};
export default NewSupply;

31
frontend/src/views/ProductModel.jsx

@ -0,0 +1,31 @@
import React from "react";
import ProductModelInfo from "../components/ProductModelInfo";
import ComponentsProductModel from "../components/ComponentsProductModel";
import { useSelector } from "react-redux";
import DownArrow from "../components/DownArrow";
export default function ProductModel() {
// redux state
const productSelected = useSelector((state) => state.productSelected);
return (
<>
<div style={{ height: "100vh", background: "#f2f4f6" }}>
<ProductModelInfo product={productSelected} />
</div>
<div
style={{
width: "100%",
background: "",
display: "fluid",
alignItems: "left",
}}
>
<ComponentsProductModel product={productSelected} />
<div style={{ position: "absolute", bottom: "2%", left: "45%" }}>
<DownArrow />
</div>
</div>
</>
);
}

54
frontend/src/views/ProvidersDetail.jsx

@ -0,0 +1,54 @@
import { useState } from "react";
import { useSelector } from "react-redux";
import { Link } from "react-router-dom";
import ButtonAdd from "../components/ButtonAdd";
import DataTableProvidersDetail from "../components/DataTableProvidersDetail";
import ArrowBackIosIcon from "@mui/icons-material/ArrowBackIos";
export default function ProvidersDetail() {
const selectedSupply = useSelector((state) => state.componentSelected);
console.log("selectedSupply desde providersDetail");
console.log(selectedSupply);
const [total, setTotal] = useState(0);
const pull_total = (total) => {
setTotal(total);
console.log("total: " + total); // LOGS DATA FROM CHILD (My name is Dean Winchester... &)
};
return (
<>
<div className="container-body-providers">
<div className="wrapper-header-providers">
<Link to={"/supplies"}>
<div className="header-title text-hover">
<ArrowBackIosIcon style={{ fontSize: "15px" }} />
INSUMOS | PROVEEDORES
</div>
</Link>
<ButtonAdd router={`/neworder/${selectedSupply._id}`} />
</div>
<div className="header-title-providers">
<h2>{selectedSupply.name}</h2>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "end",
}}
>
<h5>CANTIDAD TOTAL</h5>
<h4>{total}</h4>
</div>
</div>
<DataTableProvidersDetail
selectedSupply={selectedSupply}
funcTotal={pull_total}
/>
</div>
</>
);
}

46
frontend/src/views/Supplies.jsx

@ -0,0 +1,46 @@
import DataTableSupplies from "../components/DataTableSupplies";
import { useDispatch, useSelector } from "react-redux";
import ButtonAdd from "../components/ButtonAdd";
import ButtonArchived from "../components/ButtonArchived";
import ArrowBackIosIcon from "@mui/icons-material/ArrowBackIos";
import { useEffect } from "react";
import { getSuppliesThunk } from "../reducers/supplies";
import { Link } from "react-router-dom";
export default function Supplies() {
const components = useSelector((state) => state.components);
const supplies = useSelector((state) => state.suppliesThunk);
const dispatch = useDispatch();
const fetchSupplies = async () => {
await dispatch(getSuppliesThunk());
};
useEffect(() => {
fetchSupplies();
}, []);
//console.log("supplies desde SuppliesView");
//console.log(supplies);
return (
<div className="container-body">
<div className="container-body-paper">
<div className="wrapper-header">
<Link style={{cursor: 'default'}}>
<div className="header-title text-hover">INSUMOS</div>
</Link>
<ButtonArchived
router={"/archivedsupplies"}
title={"ARCHIVADOS"}
icon={"down"}
/>
<ButtonAdd router={"/newsupply"} />
</div>
<DataTableSupplies suppliesThunk={supplies} />
</div>
</div>
);
}

61
frontend/src/views/SuppliesArchived.jsx

@ -0,0 +1,61 @@
import ButtonAdd from "../components/ButtonAdd";
import ButtonArchived from "../components/ButtonArchived";
import ArrowBackIosIcon from "@mui/icons-material/ArrowBackIos";
import { getTrashSupplies } from "../services/supplies";
import DataTableSuppliesArchived from "../components/DataTableSuppliesArchived";
import { Link } from "react-router-dom";
import { CircularProgress } from "@mui/material";
import useFetch from "../hooks/useFetch";
import { useEffect, useState } from "react";
const request = getTrashSupplies();
export default function SuppliesArchived() {
/* const { data, isFetching, error } = useFetch(request);
if (isFetching) {
return (
<div className="container-body">
<CircularProgress />
</div>
);
}
if (error) {
return <p>{error.message}</p>;
}
if (!data) {
return null;
} */
// get trash supplies
const [trashSupplies, setTrashSupplies] = useState([]);
const getTrashSuppliesToServer = async () => {
const trashSupplies = await getTrashSupplies();
setTrashSupplies(trashSupplies);
};
useEffect(() => {
getTrashSuppliesToServer();
}, []);
return (
<div className="container-body">
<div className="container-body-paper">
<div className="wrapper-header">
<Link to={"/supplies"}>
<div className="header-title text-hover">
<ArrowBackIosIcon style={{ fontSize: "15px" }} />
INSUMOS | ARCHIVADOS
</div>
</Link>
<ButtonArchived router={"/supplies"} title={"INSUMOS"} icon={"up"} />
<ButtonAdd router={"/newsupply"} />
</div>
<DataTableSuppliesArchived suppliesThunk={trashSupplies} />
</div>
</div>
);
}
Loading…
Cancel
Save