diff --git a/jest.config.js b/jest.config.js index e34f2f27..04356946 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,28 +1,32 @@ +const isCoverageEnabled = process.argv.includes('--coverage'); + module.exports = { setupFilesAfterEnv: ['./src/setupTests.js'], testMatch: ["**/__tests__/**/*.js", "**/?(*.)+(spec|test).js"], testPathIgnorePatterns: ["/node_modules/"], transform: { - "^.+\\.(js|jsx)$": "babel-jest", - "^.+\\.svg$": "/fileTransform.js", // Adicione esta linha + "^.+\\.(js|jsx)$": "babel-jest", + "^.+\\.svg$": "/fileTransform.js", // Adicione esta linha }, moduleFileExtensions: ["js", "json", "jsx", "node"], moduleNameMapper: { - "^@/(.*)$": "/src/$1", - "\\.(css|less|scss)$": "identity-obj-proxy", - '^axios$': require.resolve('axios'), + "^@/(.*)$": "/src/$1", + "\\.(css|less|scss)$": "identity-obj-proxy", + '^axios$': require.resolve('axios'), }, testEnvironment: "jsdom", - + collectCoverage: isCoverageEnabled, + collectCoverageFrom: isCoverageEnabled ? ['src/**/*.{js,jsx}'] : [], + reporters: [ - 'default', - [ - 'jest-sonar', - { - outputDirectory: 'reports', - outputName: 'sonar-report.xml' - } - ] - ] - }; - \ No newline at end of file + 'default', + [ + 'jest-sonar', + { + outputDirectory: 'reports', + outputName: 'sonar-report.xml' + } + ] + ] +}; + diff --git a/package-lock.json b/package-lock.json index 67550788..3d7e04f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "body-parser": "^1.20.2", "dotenv": "^16.3.1", "express": "^4.18.2", + "papaparse": "^5.4.1", "pg": "^8.11.3", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -15681,6 +15682,12 @@ "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==", + "license": "MIT" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", diff --git a/package.json b/package.json index 1338e054..c124593d 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "body-parser": "^1.20.2", "dotenv": "^16.3.1", "express": "^4.18.2", + "papaparse": "^5.4.1", "pg": "^8.11.3", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -60,6 +61,7 @@ "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", "@babel/preset-env": "^7.23.2", "@babel/preset-react": "^7.22.15", + "@testing-library/jest-dom": "^6.4.8", "babel-jest": "^29.7.0", "eslint": "^8.52.0", "eslint-plugin-import": "^2.28.1", @@ -71,8 +73,8 @@ "jest-sonar": "^0.2.16", "jest-sonar-reporter": "^2.0.0", "jsdom": "^22.1.0", + "nodemon": "^2.0.0", "react-router-dom": "^6.17.0", - "react-test-renderer": "^18.2.0", - "nodemon": "^2.0.0" + "react-test-renderer": "^18.2.0" } } diff --git a/src/App.js b/src/App.js index 0a22ed9b..8f87d9fe 100644 --- a/src/App.js +++ b/src/App.js @@ -26,6 +26,7 @@ import ContractForm from "./components/forms/ContractForm"; import EditContractForm from "./components/forms/EditContractForm"; import ContractList from "./pages/ContractList"; import ViewContract from "./pages/ViewContract"; +import AuditPrinter from "./pages/AuditPrinter"; import AddContador from "./pages/AddContador"; function App() { @@ -34,11 +35,12 @@ function App() { }> - }/> - }/> - }/> + } /> + } /> + } /> } /> } /> + } /> } /> } /> } /> @@ -59,7 +61,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> diff --git a/src/__mocks__/fileMock.js b/src/__mocks__/fileMock.js new file mode 100644 index 00000000..36dc734b --- /dev/null +++ b/src/__mocks__/fileMock.js @@ -0,0 +1,2 @@ +// src/__mocks__/fileMock.js +module.exports = ''; diff --git a/src/assets/report.png b/src/assets/report.png new file mode 100644 index 00000000..f177dbec Binary files /dev/null and b/src/assets/report.png differ diff --git a/src/components/Button.js b/src/components/Button.js index 9a103eab..0f360969 100644 --- a/src/components/Button.js +++ b/src/components/Button.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import '../style/components/button.css'; -const Button = ({ type, size, text, onClick, bgColor }) => { +const Button = ({ type, size, text, onClick, bgColor, disabled }) => { const typeClasses = { success: 'button-success', error: 'button-error', @@ -15,13 +15,14 @@ const Button = ({ type, size, text, onClick, bgColor }) => { small: 'button-small', medium: 'button-medium', large: 'button-large', + adaptive: 'button-adaptive', }; - const buttonClass = `button ${typeClasses[type] || 'button-info'} ${sizeClasses[size] || 'button-medium'}`; + const buttonClass = `button ${typeClasses[type] || 'button-info'} ${sizeClasses[size] || 'button-medium'} ${disabled ? "disabled" : ' '}`; return ( - ); @@ -29,15 +30,17 @@ const Button = ({ type, size, text, onClick, bgColor }) => { Button.propTypes = { type: PropTypes.oneOf(['success', 'error', 'info', 'warning', 'icon']), - size: PropTypes.oneOf(['small', 'medium', 'large']), + size: PropTypes.oneOf(['small', 'medium', 'large', 'adaptive']), text: PropTypes.string.isRequired, bgColor: PropTypes.string.isRequired, onClick: PropTypes.func.isRequired, + disabled: PropTypes.bool.isRequired, }; Button.defaultProps = { type: 'info', size: 'medium', + disabled: false }; export default Button; diff --git a/src/components/UploadReport.js b/src/components/UploadReport.js new file mode 100644 index 00000000..a8d1a05b --- /dev/null +++ b/src/components/UploadReport.js @@ -0,0 +1,138 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import '../style/components/UploadReport.css'; +import Button from './Button.js'; +import { toast } from "react-toastify"; +import Papa from 'papaparse'; + +function UploadReport({ isOpen, onClose, onUpload }) { + if (!isOpen) return null; + + const toCamelCase = (str) => { + return str + .toLowerCase() + .replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match, index) => + index === 0 ? match.toLowerCase() : match.toUpperCase() + ).replace(/\s+/g, ''); + }; + + const schema = { + actualColorCounter: "number", + actualMonoCounter: "number", + brand: "string", + customerDescription: "string", + endTotalCounter: "number", + lastUpdateTime: "string", + model: "string", + serialnumber: "string", + }; + + const validateObjectStructure = (obj, schema) => { + for (const key in schema) { + if (!Object.prototype.hasOwnProperty.call(obj, key)) { + console.error(`Erro: Faltando o campo ${key} no objeto.`); + return false; + } + } + return true; + }; + + const handleFileUpload = (event) => { + const file = event.target.files[0]; + if (file) { + if (file.name.split('.').pop() !== 'csv') { + toast.error('Arquivo inválido! Por favor, selecione um arquivo .csv'); + return; + } + + const reader = new FileReader(); + reader.onload = (e) => { + const csvText = e.target.result; + + const parsedData = Papa.parse(csvText, { + header: true, + skipEmptyLines: true, + dynamicTyping: true, + }); + + const cleanedData = parsedData.data.map(item => { + const cleanedItem = {}; + Object.keys(item).forEach(key => { + const cleanedKey = toCamelCase(key.replace(/\[|\]/g, '').trim()); + cleanedItem[cleanedKey] = item[key]; + }); + + if (validateObjectStructure(cleanedItem, schema)) { + return cleanedItem; + } else { + toast.error('Erro na estrutura dos dados do CSV.'); + return null; + } + }).filter(item => item !== null); + + onUpload(cleanedData); + }; + reader.readAsText(file); + } + }; + + const handleDownloadTemplate = async () => { + try { + const response = await fetch('https://raw.githubusercontent.com/fga-eps-mds/2024.1-PrintGo-FrontEnd/dev/public/Modelo_Relatorio.csv'); + if (!response.ok) throw new Error('Erro ao buscar o arquivo'); + + const csvContent = await response.text(); + console.log(csvContent); + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const link = document.createElement('a'); + const url = URL.createObjectURL(blob); + + link.setAttribute('href', url); + link.setAttribute('download', 'Modelo_Relatorio.csv'); + link.style.visibility = 'hidden'; + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } catch (error) { + console.error('Erro ao baixar o arquivo:', error); + } + }; + + return ( +
+
+

Upload do Relatório

+ + +
+
+
+
+ ); +} + +UploadReport.defaultProps = { + isOpen: false, + onClose: () => { }, + onUpload: () => { } +}; + +UploadReport.propTypes = { + isOpen: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + onUpload: PropTypes.func.isRequired +}; + +export default UploadReport; + diff --git a/src/components/containers/AuditBox.js b/src/components/containers/AuditBox.js new file mode 100644 index 00000000..0be5ff5c --- /dev/null +++ b/src/components/containers/AuditBox.js @@ -0,0 +1,54 @@ +import React from "react"; +import PropTypes from "prop-types"; +import "../../style/components/auditBox.css"; +import imageButton from "../../assets/report.png" + +const AuditBox = ({ equipamento, contadorCor, contadorPB, contadorLocPB, contadorLocCor, totPrintgo, totLoc, onClick, marginError }) => { + let errorClass = ""; + if (Math.abs(contadorCor - contadorLocCor) > marginError || Math.abs(contadorPB - contadorLocPB) > marginError || Math.abs(totPrintgo - totLoc) > marginError) { + errorClass = "box-error"; + } + + return ( +
+

{equipamento}

+

{contadorCor}

+

{contadorPB}

+

{contadorLocCor}

+

{contadorLocPB}

+

{totPrintgo}

+

{totLoc}

+
+ +
+
+ + ); +}; + +AuditBox.propTypes = { + equipamento: PropTypes.string, + contadorCor: PropTypes.number, + contadorPB: PropTypes.number, + contadorLocPB: PropTypes.number, + contadorLocCor: PropTypes.number, + totPrintgo: PropTypes.number, + totLoc: PropTypes.number, + ativo: PropTypes.bool, + onClick: PropTypes.func, + marginError: PropTypes.number, +}; + +AuditBox.defaultProps = { + contadorCor: 0, + contadorPB: 0, + contadorLocPB: 0, + contadorLocCor: 0, + totPrintgo: 0, + totLoc: 0, + marginError: 0, +}; + +export default AuditBox; diff --git a/src/components/containers/NumberContainer.js b/src/components/containers/NumberContainer.js index b6579faf..1aafe7d3 100644 --- a/src/components/containers/NumberContainer.js +++ b/src/components/containers/NumberContainer.js @@ -16,6 +16,7 @@ const NumberContainer = ({ id, name, value, onChange, className, label, disabled onChange={onChange} disabled={disabled} placeholder="Insira" + min="0" /> diff --git a/src/components/containers/SelectContainer.js b/src/components/containers/SelectContainer.js index 4912cdd0..ce206e3e 100644 --- a/src/components/containers/SelectContainer.js +++ b/src/components/containers/SelectContainer.js @@ -2,14 +2,15 @@ import React from 'react'; import PropTypes from 'prop-types'; import "../../style/components/SelectContainer.css"; -const SelectContainer = ({ id, name, options, className, label, onChange, value, error }) => { +const SelectContainer = ({ id, name, options, className, label, containerStyle, onChange, value, error }) => { return ( -
- +
+ {label}