diff --git a/.eslintrc.js b/.eslintrc.js index 533031c..31b6745 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,29 +1,110 @@ module.exports = { - parser: '@typescript-eslint/parser', + root: true, + env: { + browser: true, + es2021: true, + node: true, + }, extends: [ - 'plugin:react/recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', - 'plugin:prettier/recommended' + "eslint:recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended", + "plugin:@typescript-eslint/recommended", + "plugin:jsx-a11y/recommended", + "plugin:import/errors", + "plugin:import/warnings", + "plugin:import/typescript", + "prettier", ], + parser: "@typescript-eslint/parser", parserOptions: { - ecmaVersion: 2020, - sourceType: 'module', ecmaFeatures: { - jsx: true - } - }, - plugins: ['react', '@typescript-eslint', 'react-hooks'], - rules: { - 'react/react-in-jsx-scope': 'off', - 'react/prop-types': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/no-explicit-any': 'off', - 'prettier/prettier': ['error', {}, { usePrettierrc: true }] + jsx: true, + }, + ecmaVersion: 2021, + sourceType: "module", }, + plugins: [ + "react", + "react-hooks", + "@typescript-eslint", + "jsx-a11y", + "import", + "prettier", + ], settings: { react: { - version: 'detect' - } - } + version: "detect", + }, + "import/resolver": { + typescript: {}, + node: { + extensions: [".js", ".jsx", ".ts", ".tsx"], + }, + }, + }, + rules: { + // Regras essenciais para segurança e prevenção de erros + "no-console": ["warn", { allow: ["warn", "error", "info"] }], + "no-unused-vars": "off", // Desativado em favor da versão TypeScript + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + "no-undef": "error", + "no-var": "error", + "prefer-const": "error", + eqeqeq: ["error", "always"], + + // Regras específicas do React + "react/prop-types": "off", // Desnecessário com TypeScript + "react/react-in-jsx-scope": "off", // Não necessário no React 17+ + "react/jsx-uses-react": "off", // Não necessário no React 17+ + "react/jsx-key": "error", + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", + + // Regras de acessibilidade + "jsx-a11y/anchor-is-valid": "warn", + + // Estilo e formatação + "prettier/prettier": "warn", + + // Regras específicas de segurança + "no-eval": "error", + "no-implied-eval": "error", + + // Regras para evitar SQL injection (útil para código de backend) + "@typescript-eslint/no-explicit-any": "warn", + + // Regras para importações + "import/no-unresolved": "error", + "import/named": "error", + "import/default": "error", + "import/namespace": "error", + "import/order": [ + "warn", + { + groups: [ + "builtin", + "external", + "internal", + "parent", + "sibling", + "index", + ], + "newlines-between": "always", + alphabetize: { order: "asc", caseInsensitive: true }, + }, + ], + }, + overrides: [ + // Regras específicas para arquivos de teste + { + files: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"], + env: { + jest: true, + }, + rules: { + "@typescript-eslint/no-explicit-any": "off", + }, + }, + ], }; diff --git a/package-lock.json b/package-lock.json index 1a0a798..6e0504f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "@tiptap/starter-kit": "^2.11.7", "@types/sharp": "^0.32.0", "@types/ua-parser-js": "^0.7.36", - "axios": "^1.6.5", + "axios": "^1.8.4", "bcryptjs": "^2.4.3", "buffer": "^6.0.3", "bullmq": "^4.12.0", @@ -31,6 +31,7 @@ "date-fns": "^2.30.0", "detect-file-type": "^0.2.3", "dotenv": "^16.4.1", + "exceljs": "^4.4.0", "express": "^4.18.2", "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", @@ -49,10 +50,10 @@ "react-toastify": "^10.0.4", "redis": "^4.6.10", "sharp": "^0.33.2", + "tough-cookie": "^4.1.3", "ua-parser-js": "^1.0.35", "uuid": "^9.0.1", "webdav": "^5.3.1", - "xlsx": "^0.18.5", "zod": "^3.22.4" }, "devDependencies": { @@ -67,15 +68,18 @@ "@types/react-beautiful-dnd": "^13.1.8", "@types/react-dom": "^19.0.4", "@types/uuid": "^10.0.0", - "@typescript-eslint/eslint-plugin": "^8.28.0", - "@typescript-eslint/parser": "^8.28.0", + "@typescript-eslint/eslint-plugin": "^8.29.1", + "@typescript-eslint/parser": "^8.29.1", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.17", "concurrently": "^9.1.2", - "eslint": "^9.23.0", - "eslint-config-prettier": "^10.1.1", - "eslint-plugin-prettier": "^5.2.5", - "eslint-plugin-react": "^7.37.4", + "eslint": "^8.57.0", + "eslint-config-prettier": "^10.1.2", + "eslint-import-resolver-typescript": "^4.3.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-prettier": "^5.2.6", + "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "postcss": "^8.4.33", "prettier": "^3.5.3", @@ -421,6 +425,18 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@emnapi/core": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.1.tgz", + "integrity": "sha512-4JFstCTaToCFrPqrGzgkF8N2NHjtsaY4uRh6brZQ5L9e4wbMieX8oDT8N7qfVFTQecHFEtkj4ve49VIZ3mKVqw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.1", + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.0.tgz", @@ -431,6 +447,17 @@ "tslib": "^2.4.0" } }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.1.tgz", + "integrity": "sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -629,79 +656,17 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.0.tgz", - "integrity": "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", + "espree": "^9.6.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -709,7 +674,7 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -727,13 +692,16 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": ">=18" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -753,39 +721,56 @@ } }, "node_modules/@eslint/js": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.23.0.tgz", - "integrity": "sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "license": "MIT", "dependencies": { - "@eslint/core": "^0.12.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" } }, + "node_modules/@fast-csv/format/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@fast-csv/parse/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, "node_modules/@floating-ui/core": { "version": "1.6.9", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", @@ -850,42 +835,44 @@ "react": ">= 16 || ^19.0.0-rc" } }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { - "node": ">=18.18.0" + "node": ">=10.10.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "engines": { + "node": "*" } }, "node_modules/@humanwhocodes/module-importer": { @@ -902,19 +889,13 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } + "license": "BSD-3-Clause" }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.33.5", @@ -1810,6 +1791,19 @@ "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==", "license": "MIT" }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.8.tgz", + "integrity": "sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.0", + "@emnapi/runtime": "^1.4.0", + "@tybys/wasm-util": "^0.9.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1878,9 +1872,9 @@ } }, "node_modules/@pkgr/core": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.0.tgz", - "integrity": "sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.2.tgz", + "integrity": "sha512-25L86MyPvnlQoX2MTIV2OiUcb6vJ6aRbFa9pbwByn95INKD5mFH2smgjDhq+fwJoqAgvgbdJLj6Tz7V9X5CFAQ==", "dev": true, "license": "MIT", "engines": { @@ -2101,6 +2095,13 @@ "win32" ] }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, "node_modules/@tailwindcss/node": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.1.tgz", @@ -2863,6 +2864,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3001,10 +3013,10 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true, "license": "MIT" }, @@ -3214,17 +3226,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.28.0.tgz", - "integrity": "sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz", + "integrity": "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.28.0", - "@typescript-eslint/type-utils": "8.28.0", - "@typescript-eslint/utils": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0", + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/type-utils": "8.29.1", + "@typescript-eslint/utils": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -3244,16 +3256,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.28.0.tgz", - "integrity": "sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.1.tgz", + "integrity": "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.28.0", - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/typescript-estree": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0", + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/typescript-estree": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4" }, "engines": { @@ -3269,14 +3281,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.28.0.tgz", - "integrity": "sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz", + "integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0" + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3287,14 +3299,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.28.0.tgz", - "integrity": "sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz", + "integrity": "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.28.0", - "@typescript-eslint/utils": "8.28.0", + "@typescript-eslint/typescript-estree": "8.29.1", + "@typescript-eslint/utils": "8.29.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -3311,9 +3323,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.28.0.tgz", - "integrity": "sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz", + "integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==", "dev": true, "license": "MIT", "engines": { @@ -3325,14 +3337,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.28.0.tgz", - "integrity": "sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz", + "integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3365,16 +3377,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.28.0.tgz", - "integrity": "sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz", + "integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.28.0", - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/typescript-estree": "8.28.0" + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/typescript-estree": "8.29.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3389,13 +3401,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.28.0.tgz", - "integrity": "sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz", + "integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/types": "8.29.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -3419,6 +3431,240 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.5.0.tgz", + "integrity": "sha512-YmocNlEcX/AgJv8gI41bhjMOTcKcea4D2nRIbZj+MhRtSH5+vEU8r/pFuTuoF+JjVplLsBueU+CILfBPVISyGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.5.0.tgz", + "integrity": "sha512-qpUrXgH4e/0xu1LOhPEdfgSY3vIXOxDQv370NEL8npN8h40HcQDA+Pl2r4HBW6tTXezWIjxUFcP7tj529RZtDw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.5.0.tgz", + "integrity": "sha512-3tX8r8vgjvZzaJZB4jvxUaaFCDCb3aWDCpZN3EjhGnnwhztslI05KSG5NY/jNjlcZ5QWZ7dEZZ/rNBFsmTaSPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.5.0.tgz", + "integrity": "sha512-FH+ixzBKaUU9fWOj3TYO+Yn/eO6kYvMLV9eNJlJlkU7OgrxkCmiMS6wUbyT0KA3FOZGxnEQ2z3/BHgYm2jqeLA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.5.0.tgz", + "integrity": "sha512-pxCgXMgwB/4PfqFQg73lMhmWwcC0j5L+dNXhZoz/0ek0iS/oAWl65fxZeT/OnU7fVs52MgdP2q02EipqJJXHSg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.5.0.tgz", + "integrity": "sha512-FX2FV7vpLE/+Z0NZX9/1pwWud5Wocm/2PgpUXbT5aSV3QEB10kBPJAzssOQylvdj8mOHoKl5pVkXpbCwww/T2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.5.0.tgz", + "integrity": "sha512-+gF97xst1BZb28T3nwwzEtq2ewCoMDGKsenYsZuvpmNrW0019G1iUAunZN+FG55L21y+uP7zsGX06OXDQ/viKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.5.0.tgz", + "integrity": "sha512-5bEmVcQw9js8JYM2LkUBw5SeELSIxX+qKf9bFrfFINKAp4noZ//hUxLpbF7u/3gTBN1GsER6xOzIZlw/VTdXtA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.5.0.tgz", + "integrity": "sha512-GGk/8TPUsf1Q99F+lzMdjE6sGL26uJCwQ9TlvBs8zR3cLQNw/MIumPN7zrs3GFGySjnwXc8gA6J3HKbejywmqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.5.0.tgz", + "integrity": "sha512-5uRkFYYVNAeVaA4W/CwugjFN3iDOHCPqsBLCCOoJiMfFMMz4evBRsg+498OFa9w6VcTn2bD5aI+RRayaIgk2Sw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.5.0.tgz", + "integrity": "sha512-j905CZH3nehYy6NimNqC2B14pxn4Ltd7guKMyPTzKehbFXTUgihQS/ZfHQTdojkMzbSwBOSgq1dOrY+IpgxDsA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.5.0.tgz", + "integrity": "sha512-dmLevQTuzQRwu5A+mvj54R5aye5I4PVKiWqGxg8tTaYP2k2oTs/3Mo8mgnhPk28VoYCi0fdFYpgzCd4AJndQvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.5.0.tgz", + "integrity": "sha512-LtJMhwu7avhoi+kKfAZOKN773RtzLBVVF90YJbB0wyMpUj9yQPeA+mteVUI9P70OG/opH47FeV5AWeaNWWgqJg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.8" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.5.0.tgz", + "integrity": "sha512-FTZBxLL4SO1mgIM86KykzJmPeTPisBDHQV6xtfDXbTMrentuZ6SdQKJUV5BWaoUK3p8kIULlrCcucqdCnk8Npg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.5.0.tgz", + "integrity": "sha512-i5bB7vJ1waUsFciU/FKLd4Zw0VnAkvhiJ4//jYQXyDUuiLKodmtQZVTcOPU7pp97RrNgCFtXfC1gnvj/DHPJTw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.5.0.tgz", + "integrity": "sha512-wAvXp4k7jhioi4SebXW/yfzzYwsUCr9kIX4gCsUFKpCTUf8Mi7vScJXI3S+kupSUf0LbVHudR8qBbe2wFMSNUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@vitejs/plugin-react": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", @@ -3488,15 +3734,6 @@ "node": ">=0.4.0" } }, - "node_modules/adler-32": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", - "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, "node_modules/agent-base": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", @@ -3575,6 +3812,75 @@ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -3588,6 +3894,16 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -3653,6 +3969,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.flat": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", @@ -3766,6 +4104,19 @@ "node": ">=4" } }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -3860,6 +4211,16 @@ "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "license": "MIT" }, + "node_modules/axe-core": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, "node_modules/axios": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", @@ -3871,6 +4232,16 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/b4a": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", @@ -4049,6 +4420,19 @@ "resolved": "https://registry.npmjs.org/bigi/-/bigi-1.4.2.tgz", "integrity": "sha512-ddkU+dFIuEIW8lE7ZwdIAf2UPoM90eaprg5m3YXAVVTmKlqV/9BX4A2M8BOK2yOq6/VgZFVhK6QAxJebhlbhzw==" }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "license": "MIT", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -4100,6 +4484,55 @@ "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==", "license": "ISC" }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -4287,6 +4720,23 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, "node_modules/bullmq": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-4.18.2.tgz", @@ -4489,17 +4939,16 @@ "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "license": "Apache-2.0" }, - "node_modules/cfb": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", - "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", - "license": "Apache-2.0", + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "license": "MIT/X11", "dependencies": { - "adler-32": "~1.3.0", - "crc-32": "~1.2.0" + "traverse": ">=0.3.0 <0.4" }, "engines": { - "node": ">=0.8" + "node": "*" } }, "node_modules/chalk": { @@ -4719,15 +5168,6 @@ "node": ">=0.10.0" } }, - "node_modules/codepage": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", - "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -4791,6 +5231,35 @@ "node": ">= 6" } }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4937,6 +5406,33 @@ "node": ">=0.8" } }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -5014,6 +5510,13 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -5105,6 +5608,12 @@ "url": "https://opencollective.com/date-fns" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -5301,6 +5810,19 @@ "dev": true, "license": "MIT" }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -5337,6 +5859,15 @@ "node": ">= 0.4" } }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -5765,70 +6296,66 @@ } }, "node_modules/eslint": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.23.0.tgz", - "integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.2", - "@eslint/config-helpers": "^0.2.0", - "@eslint/core": "^0.12.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.23.0", - "@eslint/plugin-kit": "^0.2.7", - "@humanfs/node": "^0.16.6", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", + "cross-spawn": "^7.0.2", "debug": "^4.3.2", + "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esquery": "^1.5.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", + "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3" + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-config-prettier": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz", - "integrity": "sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz", + "integrity": "sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==", "dev": true, "license": "MIT", "bin": { @@ -5838,15 +6365,221 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.3.2.tgz", + "integrity": "sha512-T2LqBXj87ndEC9t1LrDiPkzalSFzD4rrXr6BTzGdgMx1jdQM4T972guQvg7Ih+LNO51GURXI/qMHS5GF3h1ilw==", + "dev": true, + "license": "ISC", + "dependencies": { + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.12", + "unrs-resolver": "^1.4.1" + }, + "engines": { + "node": "^16.17.0 || >=18.6.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-prettier": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.5.tgz", - "integrity": "sha512-IKKP8R87pJyMl7WWamLgPkloB16dagPIdd2FjBDbyRYPKo93wS/NbCOPh6gH+ieNLC+XZrhJt/kWj0PS/DFdmg==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz", + "integrity": "sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==", "dev": true, "license": "MIT", "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.10.2" + "synckit": "^0.11.0" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -5870,9 +6603,9 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.37.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz", - "integrity": "sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==", + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, "license": "MIT", "dependencies": { @@ -5886,7 +6619,7 @@ "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.8", + "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", @@ -5926,19 +6659,6 @@ "concat-map": "0.0.1" } }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/eslint-plugin-react/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -5971,9 +6691,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -5981,7 +6701,7 @@ "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -6000,6 +6720,16 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -6011,17 +6741,33 @@ "concat-map": "0.0.1" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "node_modules/eslint/node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.0.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint/node_modules/minimatch": { @@ -6037,32 +6783,32 @@ "node": "*" } }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -6134,6 +6880,49 @@ "node": ">= 0.6" } }, + "node_modules/exceljs": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "license": "MIT", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.10.1", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/exceljs/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/exceljs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", @@ -6230,6 +7019,19 @@ ], "license": "MIT" }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "license": "MIT", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6353,16 +7155,16 @@ } }, "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=16.0.0" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/file-selector": { @@ -6447,17 +7249,18 @@ } }, "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.4" + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=16" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { @@ -6565,15 +7368,6 @@ "node": ">= 0.6" } }, - "node_modules/frac": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", - "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -6603,6 +7397,12 @@ "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==", "license": "ISC" }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -6624,6 +7424,35 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/fstream/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -6762,6 +7591,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/get-uri": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", @@ -6892,7 +7734,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -7151,6 +7992,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -7351,6 +8198,29 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "license": "MIT" }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -7521,6 +8391,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -7909,6 +8789,18 @@ "node": ">=4.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -7940,12 +8832,44 @@ "json-buffer": "3.0.1" } }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/layerr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/layerr/-/layerr-3.0.0.tgz", "integrity": "sha512-tv754Ki2dXpPVApOrjTyRo4/QegVb9eVFq4mjqp4+NM5NaX7syQvN5BBNfV/ZpAHCEHV24XdUVrBAoka4jt3pA==", "license": "MIT" }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -7960,6 +8884,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lightningcss": { "version": "1.29.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", @@ -8230,6 +9163,12 @@ "integrity": "sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw==", "license": "MIT" }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "license": "ISC" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -8258,6 +9197,30 @@ "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "license": "MIT" }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -8276,12 +9239,31 @@ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", "license": "MIT" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "license": "MIT" + }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", "license": "MIT" }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==", + "license": "MIT" + }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", @@ -8300,6 +9282,12 @@ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "license": "MIT" }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -8313,6 +9301,18 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, "node_modules/long": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", @@ -8789,7 +9789,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8911,6 +9910,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/object.values": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", @@ -10524,6 +11538,27 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -10706,6 +11741,19 @@ "request": "^2.34" } }, + "node_modules/request-promise/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/request/node_modules/form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", @@ -10729,6 +11777,19 @@ "node": ">=0.6" } }, + "node_modules/request/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/request/node_modules/uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -10789,6 +11850,16 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -10977,6 +12048,18 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -11120,6 +12203,12 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -11376,18 +12465,6 @@ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "license": "BSD-3-Clause" }, - "node_modules/ssf": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", - "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", - "license": "Apache-2.0", - "dependencies": { - "frac": "~1.1.2" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/sshpk": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", @@ -11419,6 +12496,13 @@ "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "license": "MIT" }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, "node_modules/standard-as-callback": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", @@ -11543,6 +12627,21 @@ "node": ">=8" } }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -11681,6 +12780,16 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -11785,20 +12894,20 @@ } }, "node_modules/synckit": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.10.3.tgz", - "integrity": "sha512-R1urvuyiTaWfeCggqEvpDJwAlDVdsT9NM+IP//Tk2x7qHCkSvBk/fwFgw/TLAHzZlrAnnazMcRw0ZD8HlYFTEQ==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.3.tgz", + "integrity": "sha512-szhWDqNNI9etJUvbZ1/cx1StnZx8yMmFxme48SwR4dty4ioSY50KEZlpv0qAfgc1fpRzuh9hBXEzoCpJ779dLg==", "dev": true, "license": "MIT", "dependencies": { - "@pkgr/core": "^0.2.0", + "@pkgr/core": "^0.2.1", "tslib": "^2.8.1" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/unts" + "url": "https://opencollective.com/synckit" } }, "node_modules/tailwindcss": { @@ -11893,6 +13002,13 @@ "b4a": "^1.6.4" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -11922,6 +13038,51 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", + "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tinymce": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-7.7.2.tgz", @@ -11939,6 +13100,15 @@ "@popperjs/core": "^2.9.0" } }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -11962,16 +13132,27 @@ } }, "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "license": "BSD-3-Clause", "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "engines": { - "node": ">=0.8" + "node": ">=6" + } + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "license": "MIT/X11", + "engines": { + "node": "*" } }, "node_modules/tree-kill": { @@ -12055,6 +13236,32 @@ "dev": true, "license": "MIT" }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -12098,6 +13305,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -12273,6 +13493,15 @@ "devOptional": true, "license": "MIT" }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/unload": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", @@ -12292,6 +13521,58 @@ "node": ">= 0.8" } }, + "node_modules/unrs-resolver": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.5.0.tgz", + "integrity": "sha512-6aia3Oy7SEe0MuUGQm2nsyob0L2+g57w178K5SE/3pvSGAIp28BB2O921fKx424Ahc/gQ6v0DXFbhcpyhGZdOA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/JounQin" + }, + "optionalDependencies": { + "@unrs/resolver-binding-darwin-arm64": "1.5.0", + "@unrs/resolver-binding-darwin-x64": "1.5.0", + "@unrs/resolver-binding-freebsd-x64": "1.5.0", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.5.0", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.5.0", + "@unrs/resolver-binding-linux-arm64-gnu": "1.5.0", + "@unrs/resolver-binding-linux-arm64-musl": "1.5.0", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.5.0", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.5.0", + "@unrs/resolver-binding-linux-s390x-gnu": "1.5.0", + "@unrs/resolver-binding-linux-x64-gnu": "1.5.0", + "@unrs/resolver-binding-linux-x64-musl": "1.5.0", + "@unrs/resolver-binding-wasm32-wasi": "1.5.0", + "@unrs/resolver-binding-win32-arm64-msvc": "1.5.0", + "@unrs/resolver-binding-win32-ia32-msvc": "1.5.0", + "@unrs/resolver-binding-win32-x64-msvc": "1.5.0" + } + }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "license": "MIT", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/unzipper/node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -12434,9 +13715,9 @@ "license": "MIT" }, "node_modules/vite": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.4.tgz", - "integrity": "sha512-veHMSew8CcRzhL5o8ONjy8gkfmFJAd5Ac16oxBUjlwgX3Gq2Wqr+qNC3TjPIpy7TPV/KporLga5GT9HqdrCizw==", + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz", + "integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==", "dev": true, "license": "MIT", "dependencies": { @@ -12697,24 +13978,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wmf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", - "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/word": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", - "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -12850,26 +14113,11 @@ } } }, - "node_modules/xlsx": { - "version": "0.18.5", - "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", - "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", - "license": "Apache-2.0", - "dependencies": { - "adler-32": "~1.3.0", - "cfb": "~1.2.1", - "codepage": "~1.15.0", - "crc-32": "~1.2.1", - "ssf": "~0.11.2", - "wmf": "~1.0.1", - "word": "~0.3.0" - }, - "bin": { - "xlsx": "bin/xlsx.njs" - }, - "engines": { - "node": ">=0.8" - } + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" }, "node_modules/xtend": { "version": "4.0.2", @@ -13010,6 +14258,55 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/zod": { "version": "3.24.2", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", diff --git a/package.json b/package.json index 37fcf7c..87a6882 100644 --- a/package.json +++ b/package.json @@ -15,11 +15,12 @@ "postinstall": "prisma generate && npx puppeteer browsers install chrome", "server": "ts-node --transpile-only src/server/index.ts", "dev:full": "concurrently -n \"frontend,backend\" -c \"blue,green\" \"npm run dev\" \"npm run server\"", - "lint": "eslint . --ext .ts,.tsx", - "lint:fix": "eslint . --ext .ts,.tsx --fix", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx", + "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix", "create-admin": "ts-node scripts/create_admin_user.ts", "worker": "ts-node src/workers/startWorker.ts", - "install-deps": "npm install ua-parser-js uuid axios xlsx" + "install-deps": "npm install ua-parser-js uuid axios xlsx", + "security-check": "npm audit" }, "dependencies": { "@emotion/react": "^11.11.3", @@ -36,7 +37,7 @@ "@tiptap/starter-kit": "^2.11.7", "@types/sharp": "^0.32.0", "@types/ua-parser-js": "^0.7.36", - "axios": "^1.6.5", + "axios": "^1.8.4", "bcryptjs": "^2.4.3", "buffer": "^6.0.3", "bullmq": "^4.12.0", @@ -44,6 +45,7 @@ "date-fns": "^2.30.0", "detect-file-type": "^0.2.3", "dotenv": "^16.4.1", + "exceljs": "^4.4.0", "express": "^4.18.2", "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", @@ -62,10 +64,10 @@ "react-toastify": "^10.0.4", "redis": "^4.6.10", "sharp": "^0.33.2", + "tough-cookie": "^4.1.3", "ua-parser-js": "^1.0.35", "uuid": "^9.0.1", "webdav": "^5.3.1", - "xlsx": "^0.18.5", "zod": "^3.22.4" }, "devDependencies": { @@ -80,15 +82,18 @@ "@types/react-beautiful-dnd": "^13.1.8", "@types/react-dom": "^19.0.4", "@types/uuid": "^10.0.0", - "@typescript-eslint/eslint-plugin": "^8.28.0", - "@typescript-eslint/parser": "^8.28.0", + "@typescript-eslint/eslint-plugin": "^8.29.1", + "@typescript-eslint/parser": "^8.29.1", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.17", "concurrently": "^9.1.2", - "eslint": "^9.23.0", - "eslint-config-prettier": "^10.1.1", - "eslint-plugin-prettier": "^5.2.5", - "eslint-plugin-react": "^7.37.4", + "eslint": "^8.57.0", + "eslint-config-prettier": "^10.1.2", + "eslint-import-resolver-typescript": "^4.3.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-prettier": "^5.2.6", + "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "postcss": "^8.4.33", "prettier": "^3.5.3", diff --git a/postcss.config.js b/postcss.config.js index 7489255..cbfea5e 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,7 +1,7 @@ module.exports = { plugins: { - 'tailwindcss/nesting': {}, + "tailwindcss/nesting": {}, tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/prisma/migrations/[timestamp]_add_documentos_tables.ts b/prisma/migrations/[timestamp]_add_documentos_tables.ts index c046db7..28cbb43 100644 --- a/prisma/migrations/[timestamp]_add_documentos_tables.ts +++ b/prisma/migrations/[timestamp]_add_documentos_tables.ts @@ -1,5 +1,5 @@ -import { PrismaClient } from '@prisma/client' -const prisma = new PrismaClient() +import { PrismaClient } from "@prisma/client"; +const prisma = new PrismaClient(); async function main() { await prisma.$executeRaw` @@ -43,7 +43,7 @@ async function main() { } main() - .catch(e => { + .catch((e) => { console.error(e); process.exit(1); }) diff --git a/prisma/migrations/[timestamp]_add_template_config.ts b/prisma/migrations/[timestamp]_add_template_config.ts index 23e24a8..e48e3a7 100644 --- a/prisma/migrations/[timestamp]_add_template_config.ts +++ b/prisma/migrations/[timestamp]_add_template_config.ts @@ -1,5 +1,5 @@ -import { PrismaClient } from '@prisma/client' -const prisma = new PrismaClient() +import { PrismaClient } from "@prisma/client"; +const prisma = new PrismaClient(); async function main() { await prisma.$executeRaw` @@ -10,7 +10,7 @@ async function main() { } main() - .catch(e => { + .catch((e) => { console.error(e); process.exit(1); }) diff --git a/prisma/migrations/[timestamp]_add_timbrado_config.ts b/prisma/migrations/[timestamp]_add_timbrado_config.ts index 2a93534..3348038 100644 --- a/prisma/migrations/[timestamp]_add_timbrado_config.ts +++ b/prisma/migrations/[timestamp]_add_timbrado_config.ts @@ -1,5 +1,5 @@ -import { PrismaClient } from '@prisma/client' -const prisma = new PrismaClient() +import { PrismaClient } from "@prisma/client"; +const prisma = new PrismaClient(); async function main() { await prisma.$executeRaw` @@ -13,7 +13,7 @@ async function main() { } main() - .catch(e => { + .catch((e) => { console.error(e); process.exit(1); }) diff --git a/prisma/migrations/[timestamp]_create_configuracao_documento.ts b/prisma/migrations/[timestamp]_create_configuracao_documento.ts index e0bb2cb..381944d 100644 --- a/prisma/migrations/[timestamp]_create_configuracao_documento.ts +++ b/prisma/migrations/[timestamp]_create_configuracao_documento.ts @@ -1,5 +1,5 @@ -import { PrismaClient } from '@prisma/client' -const prisma = new PrismaClient() +import { PrismaClient } from "@prisma/client"; +const prisma = new PrismaClient(); async function main() { await prisma.$executeRaw` @@ -17,15 +17,15 @@ async function main() { // Inserir configuração padrão await prisma.configuracaoDocumento.create({ data: { - headerImageUrl: '', - footerImageUrl: '', - logoUrl: '' - } + headerImageUrl: "", + footerImageUrl: "", + logoUrl: "", + }, }); } main() - .catch(e => { + .catch((e) => { console.error(e); process.exit(1); }) diff --git a/prisma/migrations/[timestamp]_update_configuracao_documento.ts b/prisma/migrations/[timestamp]_update_configuracao_documento.ts index f3c0a78..025ea59 100644 --- a/prisma/migrations/[timestamp]_update_configuracao_documento.ts +++ b/prisma/migrations/[timestamp]_update_configuracao_documento.ts @@ -1,5 +1,5 @@ -import { PrismaClient } from '@prisma/client' -const prisma = new PrismaClient() +import { PrismaClient } from "@prisma/client"; +const prisma = new PrismaClient(); async function main() { await prisma.$executeRaw` @@ -13,7 +13,7 @@ async function main() { } main() - .catch(e => { + .catch((e) => { console.error(e); process.exit(1); }) diff --git a/public/tinymce/langs/pt_BR.js b/public/tinymce/langs/pt_BR.js index d4a67c4..af09f68 100644 --- a/public/tinymce/langs/pt_BR.js +++ b/public/tinymce/langs/pt_BR.js @@ -1,232 +1,239 @@ - -tinymce.addI18n('pt_BR',{ - "Cut": "Recortar", - "Heading 5": "Cabe\u00e7alho 5", - "Header 2": "Cabe\u00e7alho 2", - "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Seu navegador n\u00e3o suporta acesso direto \u00e0 \u00e1rea de transfer\u00eancia. Por favor use os atalhos Ctrl+X - C - V do teclado", - "Heading 4": "Cabe\u00e7alho 4", - "Div": "Div", - "Heading 2": "Cabe\u00e7alho 2", - "Paste": "Colar", - "Close": "Fechar", - "Font Family": "Fonte", - "Pre": "Pre", - "Align right": "Alinhar \u00e0 direita", - "New document": "Novo documento", - "Blockquote": "Aspas", - "Numbered list": "Lista ordenada", - "Heading 1": "Cabe\u00e7alho 1", - "Headings": "Cabe\u00e7alhos", - "Increase indent": "Aumentar recuo", - "Formats": "Formatos", - "Headers": "Cabe\u00e7alhos", - "Select all": "Selecionar tudo", - "Header 3": "Cabe\u00e7alho 3", - "Blocks": "Blocos", - "Undo": "Desfazer", - "Strikethrough": "Riscar", - "Bullet list": "Lista n\u00e3o ordenada", - "Header 1": "Cabe\u00e7alho 1", - "Superscript": "Sobrescrito", - "Clear formatting": "Limpar formata\u00e7\u00e3o", - "Font Sizes": "Tamanho", - "Subscript": "Subscrever", - "Header 6": "Cabe\u00e7alho 6", - "Redo": "Refazer", - "Paragraph": "Par\u00e1grafo", - "Ok": "Ok", - "Bold": "Negrito", - "Code": "C\u00f3digo", - "Italic": "It\u00e1lico", - "Align center": "Centralizar", - "Header 5": "Cabe\u00e7alho 5", - "Heading 6": "Cabe\u00e7alho 6", - "Heading 3": "Cabe\u00e7alho 3", - "Decrease indent": "Diminuir recuo", - "Header 4": "Cabe\u00e7alho 4", - "Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "O comando colar est\u00e1 agora em modo texto plano. O conte\u00fado ser\u00e1 colado como texto plano at\u00e9 voc\u00ea desligar esta op\u00e7\u00e3o.", - "Underline": "Sublinhar", - "Cancel": "Cancelar", - "Justify": "Justificar", - "Inline": "Em linha", - "Copy": "Copiar", - "Align left": "Alinhar \u00e0 esquerda", - "Visual aids": "Ajuda visual", - "Lower Greek": "\u03b1. \u03b2. \u03b3. ...", - "Square": "Quadrado", - "Default": "Padr\u00e3o", - "Lower Alpha": "a. b. c. ...", - "Circle": "C\u00edrculo", - "Disc": "Disco", - "Upper Alpha": "A. B. C. ...", - "Upper Roman": "I. II. III. ...", - "Lower Roman": "i. ii. iii. ...", - "Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id deve come\u00e7ar com uma letra, seguido apenas por letras, n\u00fameros, tra\u00e7os, pontos, dois pontos ou sublinhados.", - "Name": "Nome", - "Anchor": "\u00c2ncora", - "Id": "Id", - "You have unsaved changes are you sure you want to navigate away?": "Voc\u00ea tem mudan\u00e7as n\u00e3o salvas. Voc\u00ea tem certeza que deseja sair?", - "Restore last draft": "Restaurar \u00faltimo rascunho", - "Special character": "Caracteres especiais", - "Source code": "C\u00f3digo fonte", - "Language": "Idioma", - "Insert\/Edit code sample": "Inserir\/Editar c\u00f3digo de exemplo", - "B": "B", - "R": "R", - "G": "G", - "Color": "Cor", - "Right to left": "Da direita para a esquerda", - "Left to right": "Da esquerda para a direita", - "Emoticons": "Emoticons", - "Robots": "Rob\u00f4s", - "Document properties": "Propriedades do documento", - "Title": "T\u00edtulo", - "Keywords": "Palavras-chave", - "Encoding": "Codifica\u00e7\u00e3o", - "Description": "Descri\u00e7\u00e3o", - "Author": "Autor", - "Fullscreen": "Tela cheia", - "Horizontal line": "Linha horizontal", - "Horizontal space": "Espa\u00e7amento horizontal", - "Insert\/edit image": "Inserir\/editar imagem", - "General": "Geral", - "Advanced": "Avan\u00e7ado", - "Source": "Endere\u00e7o da imagem", - "Border": "Borda", - "Constrain proportions": "Manter propor\u00e7\u00f5es", - "Vertical space": "Espa\u00e7amento vertical", - "Image description": "Inserir descri\u00e7\u00e3o", - "Style": "Estilo", - "Dimensions": "Dimens\u00f5es", - "Insert image": "Inserir imagem", - "Image": "Imagem", - "Zoom in": "Aumentar zoom", - "Contrast": "Contraste", - "Back": "Voltar", - "Gamma": "Gama", - "Flip horizontally": "Virar horizontalmente", - "Resize": "Redimensionar", - "Sharpen": "Aumentar nitidez", - "Zoom out": "Diminuir zoom", - "Image options": "Op\u00e7\u00f5es de Imagem", - "Apply": "Aplicar", - "Brightness": "Brilho", - "Rotate clockwise": "Girar em sentido anti-hor\u00e1rio", - "Rotate counterclockwise": "Girar em sentido hor\u00e1rio", - "Edit image": "Editar imagem", - "Color levels": "N\u00edveis de cor", - "Crop": "Cortar", - "Orientation": "Orienta\u00e7\u00e3o", - "Flip vertically": "Virar verticalmente", - "Invert": "Inverter", - "Date\/time": "data\/hora", - "Insert date\/time": "Inserir data\/hora", - "Remove link": "Remover link", - "Url": "Url", - "Text to display": "Texto para mostrar", - "Anchors": "\u00c2ncoras", - "Insert link": "Inserir link", - "Link": "Link", - "New window": "Nova janela", - "None": "Nenhum", - "The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "A URL que voc\u00ea informou parece ser um link externo. Deseja incluir o prefixo http:\/\/?", - "Paste or type a link": "Cole ou digite um Link", - "Target": "Alvo", - "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", - "Insert\/edit link": "Inserir\/editar link", - "Insert\/edit video": "Inserir\/editar v\u00eddeo", - "Media": "imagem", - "Alternative source": "Fonte alternativa", - "Paste your embed code below:": "Insira o c\u00f3digo de incorpora\u00e7\u00e3o abaixo:", - "Insert video": "Inserir v\u00eddeo", - "Poster": "Autor", - "Insert\/edit media": "Inserir\/editar imagem", - "Embed": "Incorporar", - "Nonbreaking space": "Espa\u00e7o n\u00e3o separ\u00e1vel", - "Page break": "Quebra de p\u00e1gina", - "Paste as text": "Colar como texto", - "Preview": "Pr\u00e9-visualizar", - "Print": "Imprimir", - "Save": "Salvar", - "Could not find the specified string.": "N\u00e3o foi poss\u00edvel encontrar o termo especificado", - "Replace": "Substituir", - "Next": "Pr\u00f3ximo", - "Whole words": "Palavras inteiras", - "Find and replace": "Localizar e substituir", - "Replace with": "Substituir por", - "Find": "Localizar", - "Replace all": "Substituir tudo", - "Match case": "Diferenciar mai\u00fasculas e min\u00fasculas", - "Prev": "Anterior", - "Spellcheck": "Corretor ortogr\u00e1fico", - "Finish": "Finalizar", - "Ignore all": "Ignorar tudo", - "Ignore": "Ignorar", - "Add to Dictionary": "Adicionar ao Dicion\u00e1rio", - "Insert row before": "Inserir linha antes", - "Rows": "Linhas", - "Height": "Altura", - "Paste row after": "Colar linha depois", - "Alignment": "Alinhamento", - "Border color": "Cor da borda", - "Column group": "Agrupar coluna", - "Row": "Linha", - "Insert column before": "Inserir coluna antes", - "Split cell": "Dividir c\u00e9lula", - "Cell padding": "Espa\u00e7amento interno da c\u00e9lula", - "Cell spacing": "Espa\u00e7amento da c\u00e9lula", - "Row type": "Tipo de linha", - "Insert table": "Inserir tabela", - "Body": "Corpo", - "Caption": "Legenda", - "Footer": "Rodap\u00e9", - "Delete row": "Excluir linha", - "Paste row before": "Colar linha antes", - "Scope": "Escopo", - "Delete table": "Excluir tabela", - "H Align": "Alinhamento H", - "Top": "Superior", - "Header cell": "C\u00e9lula cabe\u00e7alho", - "Column": "Coluna", - "Row group": "Agrupar linha", - "Cell": "C\u00e9lula", - "Middle": "Meio", - "Cell type": "Tipo de c\u00e9lula", - "Copy row": "Copiar linha", - "Row properties": "Propriedades da linha", - "Table properties": "Propriedades da tabela", - "Bottom": "Inferior", - "V Align": "Alinhamento V", - "Header": "Cabe\u00e7alho", - "Right": "Direita", - "Insert column after": "Inserir coluna depois", - "Cols": "Colunas", - "Insert row after": "Inserir linha depois", - "Width": "Largura", - "Cell properties": "Propriedades da c\u00e9lula", - "Left": "Esquerdo", - "Cut row": "Recortar linha", - "Delete column": "Excluir coluna", - "Center": "Centro", - "Merge cells": "Agrupar c\u00e9lulas", - "Insert template": "Inserir modelo", - "Templates": "Modelos", - "Background color": "Cor do fundo", - "Custom...": "Personalizado...", - "Custom color": "Cor personalizada", - "No color": "Nenhuma cor", - "Text color": "Cor do texto", - "Table of Contents": "\u00edndice de Conte\u00fado", - "Show blocks": "Mostrar blocos", - "Show invisible characters": "Exibir caracteres invis\u00edveis", - "Words: {0}": "Palavras: {0}", - "Insert": "Inserir", - "File": "Arquivo", - "Edit": "Editar", - "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u00c1rea de texto formatado. Pressione ALT-F9 para exibir o menu, ALT-F10 para exibir a barra de ferramentas ou ALT-0 para exibir a ajuda", - "Tools": "Ferramentas", - "View": "Visualizar", - "Table": "Tabela", - "Format": "Formatar" - }); - \ No newline at end of file +tinymce.addI18n("pt_BR", { + Cut: "Recortar", + "Heading 5": "Cabe\u00e7alho 5", + "Header 2": "Cabe\u00e7alho 2", + "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": + "Seu navegador n\u00e3o suporta acesso direto \u00e0 \u00e1rea de transfer\u00eancia. Por favor use os atalhos Ctrl+X - C - V do teclado", + "Heading 4": "Cabe\u00e7alho 4", + Div: "Div", + "Heading 2": "Cabe\u00e7alho 2", + Paste: "Colar", + Close: "Fechar", + "Font Family": "Fonte", + Pre: "Pre", + "Align right": "Alinhar \u00e0 direita", + "New document": "Novo documento", + Blockquote: "Aspas", + "Numbered list": "Lista ordenada", + "Heading 1": "Cabe\u00e7alho 1", + Headings: "Cabe\u00e7alhos", + "Increase indent": "Aumentar recuo", + Formats: "Formatos", + Headers: "Cabe\u00e7alhos", + "Select all": "Selecionar tudo", + "Header 3": "Cabe\u00e7alho 3", + Blocks: "Blocos", + Undo: "Desfazer", + Strikethrough: "Riscar", + "Bullet list": "Lista n\u00e3o ordenada", + "Header 1": "Cabe\u00e7alho 1", + Superscript: "Sobrescrito", + "Clear formatting": "Limpar formata\u00e7\u00e3o", + "Font Sizes": "Tamanho", + Subscript: "Subscrever", + "Header 6": "Cabe\u00e7alho 6", + Redo: "Refazer", + Paragraph: "Par\u00e1grafo", + Ok: "Ok", + Bold: "Negrito", + Code: "C\u00f3digo", + Italic: "It\u00e1lico", + "Align center": "Centralizar", + "Header 5": "Cabe\u00e7alho 5", + "Heading 6": "Cabe\u00e7alho 6", + "Heading 3": "Cabe\u00e7alho 3", + "Decrease indent": "Diminuir recuo", + "Header 4": "Cabe\u00e7alho 4", + "Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": + "O comando colar est\u00e1 agora em modo texto plano. O conte\u00fado ser\u00e1 colado como texto plano at\u00e9 voc\u00ea desligar esta op\u00e7\u00e3o.", + Underline: "Sublinhar", + Cancel: "Cancelar", + Justify: "Justificar", + Inline: "Em linha", + Copy: "Copiar", + "Align left": "Alinhar \u00e0 esquerda", + "Visual aids": "Ajuda visual", + "Lower Greek": "\u03b1. \u03b2. \u03b3. ...", + Square: "Quadrado", + Default: "Padr\u00e3o", + "Lower Alpha": "a. b. c. ...", + Circle: "C\u00edrculo", + Disc: "Disco", + "Upper Alpha": "A. B. C. ...", + "Upper Roman": "I. II. III. ...", + "Lower Roman": "i. ii. iii. ...", + "Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": + "Id deve come\u00e7ar com uma letra, seguido apenas por letras, n\u00fameros, tra\u00e7os, pontos, dois pontos ou sublinhados.", + Name: "Nome", + Anchor: "\u00c2ncora", + Id: "Id", + "You have unsaved changes are you sure you want to navigate away?": + "Voc\u00ea tem mudan\u00e7as n\u00e3o salvas. Voc\u00ea tem certeza que deseja sair?", + "Restore last draft": "Restaurar \u00faltimo rascunho", + "Special character": "Caracteres especiais", + "Source code": "C\u00f3digo fonte", + Language: "Idioma", + "Insert\/Edit code sample": "Inserir\/Editar c\u00f3digo de exemplo", + B: "B", + R: "R", + G: "G", + Color: "Cor", + "Right to left": "Da direita para a esquerda", + "Left to right": "Da esquerda para a direita", + Emoticons: "Emoticons", + Robots: "Rob\u00f4s", + "Document properties": "Propriedades do documento", + Title: "T\u00edtulo", + Keywords: "Palavras-chave", + Encoding: "Codifica\u00e7\u00e3o", + Description: "Descri\u00e7\u00e3o", + Author: "Autor", + Fullscreen: "Tela cheia", + "Horizontal line": "Linha horizontal", + "Horizontal space": "Espa\u00e7amento horizontal", + "Insert\/edit image": "Inserir\/editar imagem", + General: "Geral", + Advanced: "Avan\u00e7ado", + Source: "Endere\u00e7o da imagem", + Border: "Borda", + "Constrain proportions": "Manter propor\u00e7\u00f5es", + "Vertical space": "Espa\u00e7amento vertical", + "Image description": "Inserir descri\u00e7\u00e3o", + Style: "Estilo", + Dimensions: "Dimens\u00f5es", + "Insert image": "Inserir imagem", + Image: "Imagem", + "Zoom in": "Aumentar zoom", + Contrast: "Contraste", + Back: "Voltar", + Gamma: "Gama", + "Flip horizontally": "Virar horizontalmente", + Resize: "Redimensionar", + Sharpen: "Aumentar nitidez", + "Zoom out": "Diminuir zoom", + "Image options": "Op\u00e7\u00f5es de Imagem", + Apply: "Aplicar", + Brightness: "Brilho", + "Rotate clockwise": "Girar em sentido anti-hor\u00e1rio", + "Rotate counterclockwise": "Girar em sentido hor\u00e1rio", + "Edit image": "Editar imagem", + "Color levels": "N\u00edveis de cor", + Crop: "Cortar", + Orientation: "Orienta\u00e7\u00e3o", + "Flip vertically": "Virar verticalmente", + Invert: "Inverter", + "Date\/time": "data\/hora", + "Insert date\/time": "Inserir data\/hora", + "Remove link": "Remover link", + Url: "Url", + "Text to display": "Texto para mostrar", + Anchors: "\u00c2ncoras", + "Insert link": "Inserir link", + Link: "Link", + "New window": "Nova janela", + None: "Nenhum", + "The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": + "A URL que voc\u00ea informou parece ser um link externo. Deseja incluir o prefixo http:\/\/?", + "Paste or type a link": "Cole ou digite um Link", + Target: "Alvo", + "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": + "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", + "Insert\/edit link": "Inserir\/editar link", + "Insert\/edit video": "Inserir\/editar v\u00eddeo", + Media: "imagem", + "Alternative source": "Fonte alternativa", + "Paste your embed code below:": + "Insira o c\u00f3digo de incorpora\u00e7\u00e3o abaixo:", + "Insert video": "Inserir v\u00eddeo", + Poster: "Autor", + "Insert\/edit media": "Inserir\/editar imagem", + Embed: "Incorporar", + "Nonbreaking space": "Espa\u00e7o n\u00e3o separ\u00e1vel", + "Page break": "Quebra de p\u00e1gina", + "Paste as text": "Colar como texto", + Preview: "Pr\u00e9-visualizar", + Print: "Imprimir", + Save: "Salvar", + "Could not find the specified string.": + "N\u00e3o foi poss\u00edvel encontrar o termo especificado", + Replace: "Substituir", + Next: "Pr\u00f3ximo", + "Whole words": "Palavras inteiras", + "Find and replace": "Localizar e substituir", + "Replace with": "Substituir por", + Find: "Localizar", + "Replace all": "Substituir tudo", + "Match case": "Diferenciar mai\u00fasculas e min\u00fasculas", + Prev: "Anterior", + Spellcheck: "Corretor ortogr\u00e1fico", + Finish: "Finalizar", + "Ignore all": "Ignorar tudo", + Ignore: "Ignorar", + "Add to Dictionary": "Adicionar ao Dicion\u00e1rio", + "Insert row before": "Inserir linha antes", + Rows: "Linhas", + Height: "Altura", + "Paste row after": "Colar linha depois", + Alignment: "Alinhamento", + "Border color": "Cor da borda", + "Column group": "Agrupar coluna", + Row: "Linha", + "Insert column before": "Inserir coluna antes", + "Split cell": "Dividir c\u00e9lula", + "Cell padding": "Espa\u00e7amento interno da c\u00e9lula", + "Cell spacing": "Espa\u00e7amento da c\u00e9lula", + "Row type": "Tipo de linha", + "Insert table": "Inserir tabela", + Body: "Corpo", + Caption: "Legenda", + Footer: "Rodap\u00e9", + "Delete row": "Excluir linha", + "Paste row before": "Colar linha antes", + Scope: "Escopo", + "Delete table": "Excluir tabela", + "H Align": "Alinhamento H", + Top: "Superior", + "Header cell": "C\u00e9lula cabe\u00e7alho", + Column: "Coluna", + "Row group": "Agrupar linha", + Cell: "C\u00e9lula", + Middle: "Meio", + "Cell type": "Tipo de c\u00e9lula", + "Copy row": "Copiar linha", + "Row properties": "Propriedades da linha", + "Table properties": "Propriedades da tabela", + Bottom: "Inferior", + "V Align": "Alinhamento V", + Header: "Cabe\u00e7alho", + Right: "Direita", + "Insert column after": "Inserir coluna depois", + Cols: "Colunas", + "Insert row after": "Inserir linha depois", + Width: "Largura", + "Cell properties": "Propriedades da c\u00e9lula", + Left: "Esquerdo", + "Cut row": "Recortar linha", + "Delete column": "Excluir coluna", + Center: "Centro", + "Merge cells": "Agrupar c\u00e9lulas", + "Insert template": "Inserir modelo", + Templates: "Modelos", + "Background color": "Cor do fundo", + "Custom...": "Personalizado...", + "Custom color": "Cor personalizada", + "No color": "Nenhuma cor", + "Text color": "Cor do texto", + "Table of Contents": "\u00edndice de Conte\u00fado", + "Show blocks": "Mostrar blocos", + "Show invisible characters": "Exibir caracteres invis\u00edveis", + "Words: {0}": "Palavras: {0}", + Insert: "Inserir", + File: "Arquivo", + Edit: "Editar", + "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": + "\u00c1rea de texto formatado. Pressione ALT-F9 para exibir o menu, ALT-F10 para exibir a barra de ferramentas ou ALT-0 para exibir a ajuda", + Tools: "Ferramentas", + View: "Visualizar", + Table: "Tabela", + Format: "Formatar", +}); diff --git a/scripts/create_admin_user.ts b/scripts/create_admin_user.ts index d70ef5a..9ef303e 100644 --- a/scripts/create_admin_user.ts +++ b/scripts/create_admin_user.ts @@ -16,27 +16,27 @@ async function createAdminUser() { ativo: true, cpf: "06116321100", oab: "12774", - oabUF: "TO" + oabUF: "TO", }; console.log("Tentando criar/atualizar usuário admin..."); // Verifica se já existe na tabela usuarios const existingAdmin = await prisma.usuario.findUnique({ - where: { email: adminData.email } + where: { email: adminData.email }, }); if (existingAdmin) { console.log("Usuário admin já existe. Atualizando..."); await prisma.usuario.update({ where: { email: adminData.email }, - data: adminData + data: adminData, }); console.log("Admin atualizado com sucesso!"); } else { console.log("Criando novo usuário admin..."); await prisma.usuario.create({ - data: adminData + data: adminData, }); console.log("Novo admin criado com sucesso!"); } @@ -44,7 +44,6 @@ async function createAdminUser() { console.log("\nCredenciais de acesso:"); console.log("Email:", adminData.email); console.log("Senha: 34641747"); - } catch (error) { console.error("Erro ao criar/atualizar usuário admin:", error); } finally { diff --git a/src/App.tsx b/src/App.tsx index 0fe2d5c..2f33c49 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,76 +1,106 @@ +import { LoginPage } from "./components/LoginPage"; +import { Dashboard } from "./components/Dashboard"; +import { MainLayout } from "./components/layouts/MainLayout"; +import { AuthProvider } from "./contexts/AuthContext"; + +import { LocalizationProvider } from "@mui/x-date-pickers"; +import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns"; +import { ptBR } from "date-fns/locale"; import React from "react"; +import { QueryClient, QueryClientProvider } from "react-query"; import { createBrowserRouter, RouterProvider, createRoutesFromElements, Route, - Navigate + Navigate, } from "react-router-dom"; -import { LoginPage } from "./components/LoginPage"; -import { Dashboard } from "./components/Dashboard"; -import { MainLayout } from "./components/layouts/MainLayout"; -import { PrivateRoute } from "./components/PrivateRoute"; -import { AuthProvider } from "./contexts/AuthContext"; -import { ClientesPage } from "./components/clientes/ClientesPage"; -import { QueryClient, QueryClientProvider } from "react-query"; -import { ClienteDetalhes } from "./components/clientes/ClienteDetalhes"; -import { ProcessosPage } from "./components/processos/ProcessosPage"; -import { ProcessoDetalhes } from "./components/processos/ProcessoDetalhes"; -import { UsuariosPage } from "./components/usuarios/UsuariosPage"; -import { AdminRoute } from "./components/AdminRoute"; -import { PerfilPage } from "./components/perfil/PerfilPage"; -import { AtendimentosPage } from "./components/atendimentos/AtendimentosPage"; -import { AtendimentoDetalhes } from "./components/atendimentos/AtendimentoDetalhes"; -import { LocalizationProvider } from '@mui/x-date-pickers'; -import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; -import { ptBR } from 'date-fns/locale'; -import { ThemeProvider } from './contexts/ThemeContext'; + +import { ThemeProvider } from "./contexts/ThemeContext"; import { TarefasPage } from "./components/tarefas/TarefasPage"; -import { DocumentosPage } from './components/DocumentosPage'; -import { ToastContainer } from 'react-toastify'; -import 'react-toastify/dist/ReactToastify.css'; -import { GerenciarTemplates } from './components/documentos/GerenciarTemplates'; -import { ConfiguracaoDocumento } from './components/documentos/ConfiguracaoDocumento'; -import { ProcessoAdministrativoList } from './components/processosAdministrativos/ProcessoAdministrativoList'; -import { ProcessoAdminDetalhes } from './components/processosAdministrativos/ProcessoAdminDetalhes'; -import { ProtectedRoute } from './components/ProtectedRoute'; -import { ProvaUpload } from './components/provas/ProvaUpload'; -import { ProvaStatus } from './components/provas/ProvaStatus'; -import { AdminProvas } from './components/provas/AdminProvas'; -import { VerificarProva } from './components/provas/VerificarProva'; -import { ErrorBoundary } from './components/ErrorBoundary'; -import { PublicVerificarProva } from './components/provas/PublicVerificarProva'; -import { ClientProvaUpload } from './components/provas/ClientProvaUpload'; -import { PublicProvaStatus } from './components/provas/PublicProvaStatus'; +import { DocumentosPage } from "./components/DocumentosPage"; + +import { ToastContainer } from "react-toastify"; + +import { AdminRoute } from "./components/AdminRoute"; +import { AtendimentoDetalhes } from "./components/atendimentos/AtendimentoDetalhes"; +import { AtendimentosPage } from "./components/atendimentos/AtendimentosPage"; + +import "react-toastify/dist/ReactToastify.css"; // Importar os componentes de cálculos -import { CalculosList } from './components/calculos/CalculosList'; -import { NovoCalculo } from './components/calculos/NovoCalculo'; -import { DetalhesCalculo } from './components/calculos/DetalhesCalculo'; -import { NovoPensaoAlimenticia } from './components/calculos/NovoPensaoAlimenticia'; +import { CalculosList } from "./components/calculos/CalculosList"; +import { DetalhesCalculo } from "./components/calculos/DetalhesCalculo"; +import { NovoCalculo } from "./components/calculos/NovoCalculo"; +import { NovoPensaoAlimenticia } from "./components/calculos/NovoPensaoAlimenticia"; +import { ClienteDetalhes } from "./components/clientes/ClienteDetalhes"; +import { ClientesPage } from "./components/clientes/ClientesPage"; +import { ConfiguracaoDocumento } from "./components/documentos/ConfiguracaoDocumento"; +import { GerenciarTemplates } from "./components/documentos/GerenciarTemplates"; +import { ErrorBoundary } from "./components/ErrorBoundary"; +import { PerfilPage } from "./components/perfil/PerfilPage"; +import { PrivateRoute } from "./components/PrivateRoute"; +import { ProcessoDetalhes } from "./components/processos/ProcessoDetalhes"; +import { ProcessosPage } from "./components/processos/ProcessosPage"; +import { ProcessoAdminDetalhes } from "./components/processosAdministrativos/ProcessoAdminDetalhes"; +import { ProcessoAdministrativoList } from "./components/processosAdministrativos/ProcessoAdministrativoList"; +import { ProtectedRoute } from "./components/ProtectedRoute"; +import { AdminProvas } from "./components/provas/AdminProvas"; +import { ClientProvaUpload } from "./components/provas/ClientProvaUpload"; +import { ProvaStatus } from "./components/provas/ProvaStatus"; +import { ProvaUpload } from "./components/provas/ProvaUpload"; +import { PublicProvaStatus } from "./components/provas/PublicProvaStatus"; +import { PublicVerificarProva } from "./components/provas/PublicVerificarProva"; +import { VerificarProva } from "./components/provas/VerificarProva"; +import { UsuariosPage } from "./components/usuarios/UsuariosPage"; const queryClient = new QueryClient(); const router = createBrowserRouter( createRoutesFromElements( <> - } errorElement={} /> + } + errorElement={} + /> } /> - + {/* Rotas públicas para validação digital */} - } errorElement={} /> - } errorElement={} /> - } errorElement={} /> - + } + errorElement={} + /> + } + errorElement={} + /> + } + errorElement={} + /> + }> }> } /> } /> } /> } /> - } /> - } /> - } /> + } + /> + } + /> + } + /> } /> } /> } /> @@ -79,56 +109,92 @@ const router = createBrowserRouter( } /> } /> } /> - } /> - } /> + + + + } + /> + + + + } + /> } /> } /> - - - - } /> - } + + + + } + /> + } errorElement={} /> {/* Rotas para Cálculos Judiciais */} - - - - } /> - - - - - } /> - - - - - } /> - + + + + } + /> + + + + + } + /> + + + + + } + /> + {/* Rotas para Pensão Alimentícia */} - - - - } /> - - - - - } /> + + + + } + /> + + + + + } + /> } /> - > - ) + >, + ), ); export const App: React.FC = () => { diff --git a/src/components/AdminRoute.tsx b/src/components/AdminRoute.tsx index 8c1386b..01a85e7 100644 --- a/src/components/AdminRoute.tsx +++ b/src/components/AdminRoute.tsx @@ -1,4 +1,5 @@ import { Navigate, Outlet } from "react-router-dom"; + import { useAuth } from "../contexts/AuthContext"; import { PerfilUsuario } from "../types/usuario"; diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx index 82a707a..c283973 100644 --- a/src/components/Dashboard.tsx +++ b/src/components/Dashboard.tsx @@ -1,5 +1,6 @@ -import React from 'react'; -import { DashboardLayout } from './dashboard/DashboardLayout'; +import React from "react"; + +import { DashboardLayout } from "./dashboard/DashboardLayout"; export const Dashboard: React.FC = () => { return ; diff --git a/src/components/DocumentosPage.tsx b/src/components/DocumentosPage.tsx index a7d95d6..acedcbb 100644 --- a/src/components/DocumentosPage.tsx +++ b/src/components/DocumentosPage.tsx @@ -1,12 +1,21 @@ -import { useState, useEffect, useCallback } from 'react'; -import { useParams, useNavigate, Link } from 'react-router-dom'; -import { DocumentIcon, PlusIcon, TrashIcon, EyeIcon, XMarkIcon, DocumentArrowDownIcon } from '@heroicons/react/24/outline'; -import { DocumentoEditor } from './documentos/DocumentoEditor'; -import { api } from '../lib/api'; -import { toast } from 'react-toastify'; -import { useAuth } from '../contexts/AuthContext'; -import { Dialog } from '@headlessui/react'; -import { DocumentoViewer } from './documentos/DocumentoViewer'; +import { Dialog } from "@headlessui/react"; +import { + DocumentIcon, + PlusIcon, + TrashIcon, + EyeIcon, + XMarkIcon, + DocumentArrowDownIcon, +} from "@heroicons/react/24/outline"; +import { useState, useEffect, useCallback } from "react"; +import { useParams, useNavigate, Link } from "react-router-dom"; +import { toast } from "react-toastify"; + +import { useAuth } from "../contexts/AuthContext"; +import { api } from "../lib/api"; + +import { DocumentoEditor } from "./documentos/DocumentoEditor"; +import { DocumentoViewer } from "./documentos/DocumentoViewer"; interface Documento { id: string; @@ -39,7 +48,7 @@ export function DocumentosPage() { const loadData = useCallback(async () => { if (!user) { - navigate('/login'); + navigate("/login"); return; } @@ -47,12 +56,12 @@ export function DocumentosPage() { setIsLoading(true); await Promise.all([loadDocumentos(), loadModelos()]); } catch (error) { - console.error('Erro ao carregar dados:', error); + console.error("Erro ao carregar dados:", error); if (error?.response?.status === 401) { signOut(); // Usar a função do AuthContext - navigate('/login'); + navigate("/login"); } else { - toast.error('Erro ao carregar dados'); + toast.error("Erro ao carregar dados"); } } finally { setIsLoading(false); @@ -68,12 +77,12 @@ export function DocumentosPage() { if (processoId) params.processoId = processoId; if (atendimentoId) params.atendimentoId = atendimentoId; - const response = await api.get('/documentos', { params }); + const response = await api.get("/documentos", { params }); setDocumentos(response.data); }; const loadModelos = async () => { - const response = await api.get('/documentos-modelos'); + const response = await api.get("/documentos-modelos"); setModelos(response.data); }; @@ -82,40 +91,40 @@ export function DocumentosPage() { const payload = { nome: data.nome.trim(), conteudo: data.conteudo.trim(), - tipo: documentoEmEdicao?.tipo || 'OUTROS', + tipo: documentoEmEdicao?.tipo || "OUTROS", modeloId: documentoEmEdicao?.modeloId, processoId: data.processoId || processoId, - atendimentoId: data.atendimentoId || atendimentoId + atendimentoId: data.atendimentoId || atendimentoId, }; if (documentoEmEdicao?.id) { await api.put(`/documentos/${documentoEmEdicao.id}`, payload); - toast.success('Documento atualizado com sucesso'); + toast.success("Documento atualizado com sucesso"); } else { - await api.post('/documentos', payload); - toast.success('Documento criado com sucesso'); + await api.post("/documentos", payload); + toast.success("Documento criado com sucesso"); } - + await loadDocumentos(); setIsEditorOpen(false); setDocumentoEmEdicao(null); } catch (error: any) { - console.error('Erro ao salvar documento:', error); - toast.error(error?.response?.data?.message || 'Erro ao salvar documento'); + console.error("Erro ao salvar documento:", error); + toast.error(error?.response?.data?.message || "Erro ao salvar documento"); } }; const handleDeleteDocumento = async (id) => { try { await api.delete(`/documentos/${id}`); - toast.success('Documento excluído com sucesso'); + toast.success("Documento excluído com sucesso"); await loadDocumentos(); } catch (error) { - console.error('Erro ao excluir documento:', error); + console.error("Erro ao excluir documento:", error); if (error?.response?.status === 401) { - toast.error('Sessão expirada. Por favor, faça login novamente.'); + toast.error("Sessão expirada. Por favor, faça login novamente."); } else { - toast.error('Erro ao excluir documento'); + toast.error("Erro ao excluir documento"); } } }; @@ -125,34 +134,38 @@ export function DocumentosPage() { setDocumentoVisualizado(doc); // Simplificar para usar o documento diretamente setVisualizadorOpen(true); } catch (error) { - console.error('Erro ao visualizar documento:', error); - toast.error('Erro ao visualizar documento'); + console.error("Erro ao visualizar documento:", error); + toast.error("Erro ao visualizar documento"); } }; const handleExportPDF = async (documento) => { try { - const configRes = await api.get('/documentos/configuracoes'); - const response = await api.post('/documentos/exportar-pdf', { - nome: documento.nome, - conteudo: documento.conteudo, - escritorio: configRes.data - }, { responseType: 'blob' }); + const configRes = await api.get("/documentos/configuracoes"); + const response = await api.post( + "/documentos/exportar-pdf", + { + nome: documento.nome, + conteudo: documento.conteudo, + escritorio: configRes.data, + }, + { responseType: "blob" }, + ); // Criar URL do blob e fazer download const url = window.URL.createObjectURL(new Blob([response.data])); - const link = document.createElement('a'); + const link = document.createElement("a"); link.href = url; - link.setAttribute('download', `${documento.nome}.pdf`); + link.setAttribute("download", `${documento.nome}.pdf`); document.body.appendChild(link); link.click(); link.parentNode.removeChild(link); window.URL.revokeObjectURL(url); - toast.success('PDF gerado com sucesso!'); + toast.success("PDF gerado com sucesso!"); } catch (error) { - console.error('Erro ao gerar PDF:', error); - toast.error('Erro ao gerar PDF'); + console.error("Erro ao gerar PDF:", error); + toast.error("Erro ao gerar PDF"); } }; @@ -162,10 +175,10 @@ export function DocumentosPage() { id: documento.id, nome: documento.nome, conteudo: documento.conteudo, - tipo: documento.tipo || 'OUTROS', + tipo: documento.tipo || "OUTROS", modeloId: documento.modeloId, processoId: documento.processoId || processoId, - atendimentoId: documento.atendimentoId || atendimentoId + atendimentoId: documento.atendimentoId || atendimentoId, }); setVisualizadorOpen(false); setIsEditorOpen(true); @@ -176,9 +189,9 @@ export function DocumentosPage() { nome: modelo.nome, conteudo: modelo.conteudo, modeloId: modelo.id, - tipo: 'OUTROS', + tipo: "OUTROS", processoId: processoId, - atendimentoId: atendimentoId + atendimentoId: atendimentoId, }); setIsEditorOpen(true); }; @@ -199,9 +212,9 @@ export function DocumentosPage() { Novo Documento - {user?.perfil === 'ADVOGADO_ADMIN' && ( + {user?.perfil === "ADVOGADO_ADMIN" && ( navigate('/documentos/templates')} + onClick={() => navigate("/documentos/templates")} className="inline-flex items-center px-4 py-2 border border-blue-600 text-blue-600 rounded-md hover:bg-blue-50 transition-colors" > @@ -217,8 +230,8 @@ export function DocumentosPage() { onClick={() => setTabValue(0)} className={`px-4 py-2 font-medium text-sm border-b-2 transition-colors duration-200 ${ tabValue === 0 - ? 'border-blue-500 text-blue-600' - : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' + ? "border-blue-500 text-blue-600" + : "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300" }`} > Documentos @@ -227,8 +240,8 @@ export function DocumentosPage() { onClick={() => setTabValue(1)} className={`ml-8 px-4 py-2 font-medium text-sm border-b-2 transition-colors duration-200 ${ tabValue === 1 - ? 'border-blue-500 text-blue-600' - : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' + ? "border-blue-500 text-blue-600" + : "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300" }`} > Modelos @@ -239,18 +252,24 @@ export function DocumentosPage() { {tabValue === 0 && ( {documentos.map((doc) => ( - - {/* Ajustado min-height e layout */} + + + {" "} + {/* Ajustado min-height e layout */} - {doc.nome} + + {doc.nome} + Criado em: {new Date(doc.criadoEm).toLocaleDateString()} - {/* Botões em flex-wrap para melhor responsividade */} {modelos.map((modelo) => ( - + - {modelo.nome} + + {modelo.nome} + - {modelo.descricao} + + {modelo.descricao} + handleUseModelo(modelo)} className="w-full inline-flex items-center justify-center px-4 py-2 text-sm border border-blue-600 text-blue-600 rounded-md hover:bg-blue-50 dark:hover:bg-blue-900/20" @@ -303,13 +329,13 @@ export function DocumentosPage() { )} - setVisualizadorOpen(false)} className="relative z-50" > - + @@ -325,24 +351,27 @@ export function DocumentosPage() { - - Criado em: {documentoVisualizado && new Date(documentoVisualizado.criadoEm).toLocaleDateString()} + Criado em:{" "} + {documentoVisualizado && + new Date(documentoVisualizado.criadoEm).toLocaleDateString()} - + handleEdit(documentoVisualizado)} className="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700" > Editar - @@ -361,4 +390,4 @@ export function DocumentosPage() { /> ); -} \ No newline at end of file +} diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index 1500e41..221175d 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -1,6 +1,7 @@ -import React from 'react'; -import { Card } from './ui/Card'; -import { isRouteErrorResponse, useRouteError } from 'react-router-dom'; +import React from "react"; +import { isRouteErrorResponse, useRouteError } from "react-router-dom"; + +import { Card } from "./ui/Card"; export function ErrorBoundary() { const error = useRouteError(); @@ -14,19 +15,28 @@ export function ErrorBoundary() { Ocorreu um erro - + {isRouteErrorResponse(error) ? ( - Status: {error.status} - Mensagem: {error.data.message || error.statusText} + + Status: {error.status} + + + Mensagem:{" "} + {error.data.message || error.statusText} + ) : error instanceof Error ? ( - Erro: {error.message} + + Erro: {error.message} + {error.stack && ( - Ver detalhes técnicos + + Ver detalhes técnicos + {error.stack} @@ -37,7 +47,7 @@ export function ErrorBoundary() { Ocorreu um erro desconhecido. )} - + { diff --git a/src/components/PrivateRoute.tsx b/src/components/PrivateRoute.tsx index 4d9556a..0dd33bb 100644 --- a/src/components/PrivateRoute.tsx +++ b/src/components/PrivateRoute.tsx @@ -1,5 +1,6 @@ import React from "react"; import { Navigate, Outlet, useLocation } from "react-router-dom"; + import { useAuth } from "../contexts/AuthContext"; export const PrivateRoute: React.FC = () => { diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx index 73db1e3..9c6f935 100644 --- a/src/components/ProtectedRoute.tsx +++ b/src/components/ProtectedRoute.tsx @@ -1,5 +1,6 @@ -import { useAuth } from '../contexts/AuthContext'; -import { Navigate, useLocation } from 'react-router-dom'; +import { Navigate, useLocation } from "react-router-dom"; + +import { useAuth } from "../contexts/AuthContext"; interface ProtectedRouteProps { children: React.ReactNode; @@ -10,27 +11,27 @@ export function ProtectedRoute({ children, roles }: ProtectedRouteProps) { const { user } = useAuth(); const location = useLocation(); - console.log('ProtectedRoute - Current Path:', location.pathname); - console.log('ProtectedRoute - Required Roles:', roles); - console.log('ProtectedRoute - User:', user); + console.log("ProtectedRoute - Current Path:", location.pathname); + console.log("ProtectedRoute - Required Roles:", roles); + console.log("ProtectedRoute - User:", user); if (!user) { - console.log('ProtectedRoute - No user, redirecting to login'); + console.log("ProtectedRoute - No user, redirecting to login"); return ; } if (!roles || roles.length === 0) { - console.log('ProtectedRoute - No roles required, allowing access'); + console.log("ProtectedRoute - No roles required, allowing access"); return <>{children}>; } if (!roles.includes(user.perfil)) { - console.log('ProtectedRoute - Access denied'); - console.log('Required roles:', roles); - console.log('User role:', user.perfil); + console.log("ProtectedRoute - Access denied"); + console.log("Required roles:", roles); + console.log("User role:", user.perfil); return ; } - console.log('ProtectedRoute - Access granted'); + console.log("ProtectedRoute - Access granted"); return <>{children}>; } diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 95743a5..795a502 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,4 +1,3 @@ -import { useLocation } from 'react-router-dom'; import { HomeIcon, ScaleIcon, @@ -6,20 +5,21 @@ import { UserIcon, ClipboardDocumentCheckIcon, DocumentTextIcon, -} from '@heroicons/react/24/outline'; +} from "@heroicons/react/24/outline"; +import { useLocation } from "react-router-dom"; export function useNavigation() { const { pathname } = useLocation(); return [ - { name: 'Dashboard', href: '/dashboard', icon: HomeIcon }, - { name: 'Processos', href: '/processos', icon: ScaleIcon }, - { name: 'Clientes', href: '/clientes', icon: UserGroupIcon }, - { name: 'Atendimentos', href: '/atendimentos', icon: UserIcon }, - { name: 'Tarefas', href: '/tarefas', icon: ClipboardDocumentCheckIcon }, - { name: 'Documentos', href: '/documentos', icon: DocumentTextIcon }, - ].map(item => ({ + { name: "Dashboard", href: "/dashboard", icon: HomeIcon }, + { name: "Processos", href: "/processos", icon: ScaleIcon }, + { name: "Clientes", href: "/clientes", icon: UserGroupIcon }, + { name: "Atendimentos", href: "/atendimentos", icon: UserIcon }, + { name: "Tarefas", href: "/tarefas", icon: ClipboardDocumentCheckIcon }, + { name: "Documentos", href: "/documentos", icon: DocumentTextIcon }, + ].map((item) => ({ ...item, - current: pathname === item.href + current: pathname === item.href, })); -} \ No newline at end of file +} diff --git a/src/components/atendimentos/AgendamentoCalendario.tsx b/src/components/atendimentos/AgendamentoCalendario.tsx index 0c10dc4..bb95899 100644 --- a/src/components/atendimentos/AgendamentoCalendario.tsx +++ b/src/components/atendimentos/AgendamentoCalendario.tsx @@ -1,9 +1,18 @@ -import { useState, useEffect } from 'react'; -import { format, startOfWeek, addDays, isSameDay, parseISO, getHours } from 'date-fns'; -import { ptBR } from 'date-fns/locale'; -import { AgendamentoForm } from './AgendamentoForm'; -import { AtendimentoForm } from './AtendimentoForm'; -import api from '../../services/api'; +import { + format, + startOfWeek, + addDays, + isSameDay, + parseISO, + getHours, + ptBR, +} from "date-fns"; +import { useState, useEffect } from "react"; + +import api from "../../services/api"; + +import { AgendamentoForm } from "./AgendamentoForm"; +import { AtendimentoForm } from "./AtendimentoForm"; interface Agendamento { id: string; @@ -21,7 +30,8 @@ export function AgendamentoCalendario() { const [selectedDate, setSelectedDate] = useState(new Date()); const [agendamentos, setAgendamentos] = useState([]); const [formOpen, setFormOpen] = useState(false); - const [selectedAgendamento, setSelectedAgendamento] = useState(null); + const [selectedAgendamento, setSelectedAgendamento] = + useState(null); const [atendimentoFormOpen, setAtendimentoFormOpen] = useState(false); useEffect(() => { @@ -30,18 +40,18 @@ export function AgendamentoCalendario() { const loadAgendamentos = async (date: Date) => { try { - const response = await api.get('/agendamentos', { - params: { data: date.toISOString() } + const response = await api.get("/agendamentos", { + params: { data: date.toISOString() }, }); setAgendamentos(response.data); } catch (error) { - console.error('Erro ao carregar agendamentos:', error); + console.error("Erro ao carregar agendamentos:", error); } }; const weekDays = [...Array(7)].map((_, i) => { const day = addDays(startOfWeek(selectedDate, { locale: ptBR }), i); - return format(day, 'EEEE', { locale: ptBR }); + return format(day, "EEEE", { locale: ptBR }); }); const timeSlots = [...Array(9)].map((_, i) => { @@ -52,32 +62,40 @@ export function AgendamentoCalendario() { const getAgendamentosForSlot = (dayIndex: number, hour: number) => { return agendamentos.filter((agendamento: Agendamento) => { const agendamentoDate = parseISO(agendamento.data); - const slotDate = addDays(startOfWeek(selectedDate, { locale: ptBR }), dayIndex); - - return isSameDay(agendamentoDate, slotDate) && getHours(agendamentoDate) === hour; + const slotDate = addDays( + startOfWeek(selectedDate, { locale: ptBR }), + dayIndex, + ); + + return ( + isSameDay(agendamentoDate, slotDate) && + getHours(agendamentoDate) === hour + ); }); }; const handleAgendamentoClick = async (agendamento: Agendamento) => { try { // Buscar atendimento inicial se o tipo não for INICIAL - if (agendamento.tipo !== 'INICIAL') { - const response = await api.get(`/atendimentos/iniciais/${agendamento.cliente.id}`); + if (agendamento.tipo !== "INICIAL") { + const response = await api.get( + `/atendimentos/iniciais/${agendamento.cliente.id}`, + ); const atendimentosIniciais = response.data; - + // Pegar o atendimento inicial mais recente const atendimentoInicial = atendimentosIniciais[0]; - + setSelectedAgendamento({ ...agendamento, - atendimentoInicial: atendimentoInicial + atendimentoInicial: atendimentoInicial, }); } else { setSelectedAgendamento(agendamento); } setAtendimentoFormOpen(true); } catch (error) { - console.error('Erro ao buscar atendimento inicial:', error); + console.error("Erro ao buscar atendimento inicial:", error); } }; @@ -87,17 +105,17 @@ export function AgendamentoCalendario() { setSelectedAgendamento(agendamento); setFormOpen(true); } catch (error) { - console.error('Erro ao editar agendamento:', error); + console.error("Erro ao editar agendamento:", error); } }; const handleDeleteAgendamento = async (id: string) => { - if (window.confirm('Tem certeza que deseja excluir este agendamento?')) { + if (window.confirm("Tem certeza que deseja excluir este agendamento?")) { try { await api.delete(`/agendamentos/${id}`); await loadAgendamentos(selectedDate); } catch (error) { - console.error('Erro ao excluir agendamento:', error); + console.error("Erro ao excluir agendamento:", error); } } }; @@ -118,8 +136,11 @@ export function AgendamentoCalendario() { {/* Coluna de horários */} {/* Espaço para alinhar com cabeçalho */} - {timeSlots.map(time => ( - + {timeSlots.map((time) => ( + {time} ))} @@ -136,18 +157,20 @@ export function AgendamentoCalendario() { const slotsAgendamentos = getAgendamentosForSlot(dayIndex, hour); return ( - setFormOpen(true)} > {slotsAgendamentos.map((agendamento: Agendamento) => ( - - {agendamento.cliente.nome} + + {agendamento.cliente.nome} + { @@ -159,7 +182,7 @@ export function AgendamentoCalendario() { > ✏️ - {agendamento.status !== 'REALIZADO' && ( + {agendamento.status !== "REALIZADO" && ( { e.stopPropagation(); @@ -184,7 +207,7 @@ export function AgendamentoCalendario() { - {format(parseISO(agendamento.data), 'HH:mm')} + {format(parseISO(agendamento.data), "HH:mm")} ))} @@ -220,8 +243,8 @@ export function AgendamentoCalendario() { initialData={{ titulo: `Atendimento - ${selectedAgendamento.cliente.nome}`, agendamentoId: selectedAgendamento.id, - tipo: selectedAgendamento.tipo || 'INICIAL', - atendimentoInicialId: selectedAgendamento.atendimentoInicial?.id + tipo: selectedAgendamento.tipo || "INICIAL", + atendimentoInicialId: selectedAgendamento.atendimentoInicial?.id, }} /> )} @@ -231,17 +254,17 @@ export function AgendamentoCalendario() { function getStatusColor(status: string) { switch (status) { - case 'AGENDADO': - return 'bg-blue-100 text-blue-800'; - case 'CONFIRMADO': - return 'bg-green-100 text-green-800'; - case 'CANCELADO': - return 'bg-red-100 text-red-800'; - case 'REALIZADO': - return 'bg-purple-100 text-purple-800'; - case 'NAO_COMPARECEU': - return 'bg-gray-100 text-gray-800'; + case "AGENDADO": + return "bg-blue-100 text-blue-800"; + case "CONFIRMADO": + return "bg-green-100 text-green-800"; + case "CANCELADO": + return "bg-red-100 text-red-800"; + case "REALIZADO": + return "bg-purple-100 text-purple-800"; + case "NAO_COMPARECEU": + return "bg-gray-100 text-gray-800"; default: - return 'bg-gray-100 text-gray-800'; + return "bg-gray-100 text-gray-800"; } } diff --git a/src/components/atendimentos/AgendamentoForm.tsx b/src/components/atendimentos/AgendamentoForm.tsx index dd9fb4d..28c7b46 100644 --- a/src/components/atendimentos/AgendamentoForm.tsx +++ b/src/components/atendimentos/AgendamentoForm.tsx @@ -1,6 +1,7 @@ -import { useState, useCallback, useEffect } from 'react'; -import { DateTimePicker } from '@mui/x-date-pickers'; -import api from '../../services/api'; +import { DateTimePicker } from "@mui/x-date-pickers"; +import { useState, useCallback, useEffect } from "react"; + +import api from "../../services/api"; // Implementar debounce function debounce(fn: Function, ms = 300) { @@ -28,18 +29,24 @@ interface AgendamentoFormProps { }; } -export function AgendamentoForm({ open, onClose, onSave, clienteId: defaultClienteId, agendamento }: AgendamentoFormProps) { +export function AgendamentoForm({ + open, + onClose, + onSave, + clienteId: defaultClienteId, + agendamento, +}: AgendamentoFormProps) { const [data, setData] = useState(null); - const [observacoes, setObservacoes] = useState(''); - const [error, setError] = useState(''); + const [observacoes, setObservacoes] = useState(""); + const [error, setError] = useState(""); const [isSaving, setIsSaving] = useState(false); - const [tipo, setTipo] = useState(''); - + const [tipo, setTipo] = useState(""); + // Adicionar estados para busca de cliente - const [searchTerm, setSearchTerm] = useState(''); + const [searchTerm, setSearchTerm] = useState(""); const [searchResults, setSearchResults] = useState([]); const [selectedClient, setSelectedClient] = useState(null); - const [clienteId, setClienteId] = useState(defaultClienteId || ''); + const [clienteId, setClienteId] = useState(defaultClienteId || ""); // Preencher form quando editar useEffect(() => { @@ -47,8 +54,8 @@ export function AgendamentoForm({ open, onClose, onSave, clienteId: defaultClien setData(new Date(agendamento.data)); setClienteId(agendamento.cliente.id); setSearchTerm(agendamento.cliente.nome); - setTipo(agendamento.tipo || ''); - setObservacoes(agendamento.observacoes || ''); + setTipo(agendamento.tipo || ""); + setObservacoes(agendamento.observacoes || ""); } }, [agendamento]); @@ -60,16 +67,16 @@ export function AgendamentoForm({ open, onClose, onSave, clienteId: defaultClien const response = await api.get(`/clientes/search?q=${term}`); setSearchResults(response.data); } catch (error) { - console.error('Erro ao buscar clientes:', error); + console.error("Erro ao buscar clientes:", error); } }, 300), - [] + [], ); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!data || !clienteId) { - setError('Data e cliente são obrigatórios'); + setError("Data e cliente são obrigatórios"); return; } @@ -85,7 +92,7 @@ export function AgendamentoForm({ open, onClose, onSave, clienteId: defaultClien }); } else { // Criar novo agendamento - await api.post('/agendamentos', { + await api.post("/agendamentos", { clienteId, data: data.toISOString(), observacoes, @@ -95,17 +102,23 @@ export function AgendamentoForm({ open, onClose, onSave, clienteId: defaultClien onSave(); onClose(); } catch (error) { - setError(agendamento ? 'Erro ao atualizar agendamento' : 'Erro ao criar agendamento'); + setError( + agendamento + ? "Erro ao atualizar agendamento" + : "Erro ao criar agendamento", + ); } finally { setIsSaving(false); } }; return ( - + Agendar Atendimento - + {/* Busca de Cliente */} @@ -122,10 +135,10 @@ export function AgendamentoForm({ open, onClose, onSave, clienteId: defaultClien className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" placeholder="Buscar cliente..." /> - + {searchResults.length > 0 && ( - {searchResults.map(client => ( + {searchResults.map((client) => ( {client.nome} - {client.cpfCnpj} + + {client.cpfCnpj} + ))} @@ -198,7 +213,7 @@ export function AgendamentoForm({ open, onClose, onSave, clienteId: defaultClien disabled={isSaving} className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 disabled:opacity-50" > - {isSaving ? 'Salvando...' : 'Agendar'} + {isSaving ? "Salvando..." : "Agendar"} diff --git a/src/components/atendimentos/AtendimentoDetalhes.tsx b/src/components/atendimentos/AtendimentoDetalhes.tsx index 0bf9343..3bbc103 100644 --- a/src/components/atendimentos/AtendimentoDetalhes.tsx +++ b/src/components/atendimentos/AtendimentoDetalhes.tsx @@ -1,19 +1,22 @@ -import { useState } from 'react'; -import { useQuery } from 'react-query'; -import { useParams, Link } from 'react-router-dom'; -import api from '../../services/api'; -import { format, parseISO } from 'date-fns'; -import { ptBR } from 'date-fns/locale'; +import { format, parseISO, ptBR } from "date-fns"; +import { useState } from "react"; +import { useQuery } from "react-query"; +import { useParams, Link } from "react-router-dom"; + +import api from "../../services/api"; // Função auxiliar para formatar data com validação -const formatDate = (date: string | Date | null | undefined, formatStr: string): string => { - if (!date) return 'Data não disponível'; +const formatDate = ( + date: string | Date | null | undefined, + formatStr: string, +): string => { + if (!date) return "Data não disponível"; try { - const dateObj = typeof date === 'string' ? parseISO(date) : date; + const dateObj = typeof date === "string" ? parseISO(date) : date; return format(dateObj, formatStr, { locale: ptBR }); } catch (error) { - console.error('Erro ao formatar data:', error); - return 'Data inválida'; + console.error("Erro ao formatar data:", error); + return "Data inválida"; } }; @@ -21,31 +24,34 @@ export function AtendimentoDetalhes() { const { id } = useParams(); const [loading, setLoading] = useState(false); const [cancelModalOpen, setCancelModalOpen] = useState(false); - const [justificativa, setJustificativa] = useState(''); + const [justificativa, setJustificativa] = useState(""); const [isCancelling, setIsCancelling] = useState(false); - const { data: atendimento, refetch } = useQuery(['atendimento', id], async () => { - const response = await api.get(`/atendimentos/${id}`); - return response.data; - }); + const { data: atendimento, refetch } = useQuery( + ["atendimento", id], + async () => { + const response = await api.get(`/atendimentos/${id}`); + return response.data; + }, + ); const handleCancelar = async () => { if (!justificativa.trim()) { - alert('A justificativa é obrigatória'); + alert("A justificativa é obrigatória"); return; } try { setIsCancelling(true); await api.patch(`/atendimentos/${id}/status`, { - status: 'CANCELADO', - justificativa + status: "CANCELADO", + justificativa, }); await refetch(); setCancelModalOpen(false); } catch (error) { - console.error('Erro ao cancelar atendimento:', error); - alert('Erro ao cancelar atendimento'); + console.error("Erro ao cancelar atendimento:", error); + alert("Erro ao cancelar atendimento"); } finally { setIsCancelling(false); } @@ -66,12 +72,20 @@ export function AtendimentoDetalhes() { - {atendimento.titulo} - Protocolo: {atendimento.protocolo} + + {atendimento.titulo} + + + Protocolo: {atendimento.protocolo} + - + {atendimento.tipo} @@ -82,18 +96,24 @@ export function AtendimentoDetalhes() { Cliente - {atendimento.cliente.nome} + + {atendimento.cliente.nome} + - Data do Atendimento + + Data do Atendimento + {formatDate(atendimento.dataAtendimento, "PPpp")} {atendimento.processo && ( - Processo Vinculado - + Processo Vinculado + + @@ -103,16 +123,18 @@ export function AtendimentoDetalhes() { )} Responsável - {atendimento.usuario.nome} + + {atendimento.usuario.nome} + {/* Conteúdo do Atendimento */} Conteúdo - @@ -122,9 +144,22 @@ export function AtendimentoDetalhes() { Anexos {atendimento.anexos.map((anexo: any) => ( - - - + + + @@ -137,7 +172,7 @@ export function AtendimentoDetalhes() { { e.preventDefault(); - window.open(anexo.url, '_blank', 'noopener,noreferrer'); + window.open(anexo.url, "_blank", "noopener,noreferrer"); }} className="ml-4 text-blue-600 hover:text-blue-800" > @@ -150,7 +185,7 @@ export function AtendimentoDetalhes() { )} {/* Adicionar botão de cancelar próximo ao cabeçalho */} - {atendimento.status !== 'CANCELADO' && ( + {atendimento.status !== "CANCELADO" && ( setCancelModalOpen(true)} className="bg-red-100 text-red-600 px-4 py-2 rounded-lg hover:bg-red-200" @@ -163,9 +198,12 @@ export function AtendimentoDetalhes() { {cancelModalOpen && ( - Cancelar Atendimento + + Cancelar Atendimento + - Esta ação não pode ser desfeita. O atendimento será marcado como cancelado, mas manterá seu protocolo e histórico. + Esta ação não pode ser desfeita. O atendimento será marcado + como cancelado, mas manterá seu protocolo e histórico. - {isCancelling ? 'Cancelando...' : 'Confirmar Cancelamento'} + {isCancelling ? "Cancelando..." : "Confirmar Cancelamento"} @@ -195,10 +233,14 @@ export function AtendimentoDetalhes() { )} {/* Exibir informação de cancelamento se aplicável */} - {atendimento.status === 'CANCELADO' && ( + {atendimento.status === "CANCELADO" && ( - Atendimento Cancelado - {atendimento.justificativaCancelamento} + + Atendimento Cancelado + + + {atendimento.justificativaCancelamento} + Cancelado em: {formatDate(atendimento.dataAtualizacao, "PPpp")} diff --git a/src/components/atendimentos/AtendimentoForm.tsx b/src/components/atendimentos/AtendimentoForm.tsx index 4a6a25f..27d12c2 100644 --- a/src/components/atendimentos/AtendimentoForm.tsx +++ b/src/components/atendimentos/AtendimentoForm.tsx @@ -1,13 +1,26 @@ -import { useState, useEffect, useCallback } from 'react'; -import { Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField, FormControl, InputLabel, Select, MenuItem, Alert } from '@mui/material'; -import { useEditor, EditorContent } from '@tiptap/react'; -import StarterKit from '@tiptap/starter-kit'; -import Link from '@tiptap/extension-link'; -import { TipoAtendimento } from '@prisma/client'; -import api from '../../services/api'; // Adicionar esta importação -import { XMarkIcon, PaperClipIcon } from '@heroicons/react/24/outline'; // Alterado para v2 -import { useDropzone } from 'react-dropzone'; -import { uploadFile } from '../../services/upload'; +import { XMarkIcon, PaperClipIcon } from "@heroicons/react/24/outline"; // Alterado para v2 +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + FormControl, + InputLabel, + Select, + MenuItem, + Alert, +} from "@mui/material"; +import { TipoAtendimento } from "@prisma/client"; +import Link from "@tiptap/extension-link"; +import { useEditor, EditorContent } from "@tiptap/react"; +import StarterKit from "@tiptap/starter-kit"; +import { useState, useEffect, useCallback } from "react"; +import { useDropzone } from "react-dropzone"; + +import api from "../../services/api"; // Adicionar esta importação +import { uploadFile } from "../../services/upload"; // Implementar debounce manualmente ao invés de usar lodash function debounce(fn: Function, ms = 300) { @@ -40,19 +53,26 @@ interface AnexoTemp { erro?: string; } -export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, initialData }: AtendimentoFormProps) { - const [searchTerm, setSearchTerm] = useState(''); +export function AtendimentoForm({ + open, + onClose, + onSave, + clienteId, + processoId, + initialData, +}: AtendimentoFormProps) { + const [searchTerm, setSearchTerm] = useState(""); const [searchResults, setSearchResults] = useState([]); const [selectedClient, setSelectedClient] = useState(null); const [atendimentoInicial, setAtendimentoInicial] = useState(null); const [atendimentosIniciais, setAtendimentosIniciais] = useState([]); const [formData, setFormData] = useState({ - titulo: '', + titulo: "", tipo: TipoAtendimento.INICIAL, - conteudo: '', - clienteId: clienteId || '', - processoId: processoId || '', - atendimentoInicialId: '', + conteudo: "", + clienteId: clienteId || "", + processoId: processoId || "", + atendimentoInicialId: "", }); const [anexos, setAnexos] = useState([]); const [uploading, setUploading] = useState(false); @@ -61,41 +81,42 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, useEffect(() => { if (open) { setFormData({ - titulo: '', + titulo: "", tipo: TipoAtendimento.INICIAL, - conteudo: '', - clienteId: clienteId || '', - processoId: processoId || '', - atendimentoInicialId: '', + conteudo: "", + clienteId: clienteId || "", + processoId: processoId || "", + atendimentoInicialId: "", }); } }, [open, clienteId, processoId]); useEffect(() => { if (open && initialData) { - setFormData(prev => ({ + setFormData((prev) => ({ ...prev, titulo: initialData.titulo || prev.titulo, clienteId: clienteId || prev.clienteId, processoId: processoId || prev.processoId, tipo: initialData.tipo || prev.tipo, - atendimentoInicialId: initialData.atendimentoInicialId || prev.atendimentoInicialId + atendimentoInicialId: + initialData.atendimentoInicialId || prev.atendimentoInicialId, })); } }, [open, initialData, clienteId, processoId]); const [clientes, setClientes] = useState([]); - const [error, setError] = useState(''); + const [error, setError] = useState(""); const [isSaving, setIsSaving] = useState(false); // Adicionar estado para prevenir múltiplos submits - + // Buscar lista de clientes useEffect(() => { const fetchClientes = async () => { try { - const response = await api.get('/clientes'); + const response = await api.get("/clientes"); setClientes(response.data); } catch (err) { - console.error('Erro ao carregar clientes:', err); + console.error("Erro ao carregar clientes:", err); } }; fetchClientes(); @@ -109,18 +130,21 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, const response = await api.get(`/clientes/search?q=${term}`); setSearchResults(response.data); } catch (error) { - console.error('Erro ao buscar clientes:', error); + console.error("Erro ao buscar clientes:", error); } }, 300), - [] + [], ); // Carregar atendimentos iniciais quando selecionar cliente useEffect(() => { if (formData.clienteId && formData.tipo !== TipoAtendimento.INICIAL) { - api.get(`/atendimentos/iniciais/${formData.clienteId}`) - .then(response => setAtendimentosIniciais(response.data)) - .catch(error => console.error('Erro ao carregar atendimentos:', error)); + api + .get(`/atendimentos/iniciais/${formData.clienteId}`) + .then((response) => setAtendimentosIniciais(response.data)) + .catch((error) => + console.error("Erro ao carregar atendimentos:", error), + ); } }, [formData.clienteId, formData.tipo]); @@ -131,28 +155,28 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, openOnClick: false, }), ], - content: '', + content: "", onUpdate: ({ editor }) => { - setFormData(prev => ({ ...prev, conteudo: editor.getHTML() })); + setFormData((prev) => ({ ...prev, conteudo: editor.getHTML() })); }, }); const onDrop = useCallback((acceptedFiles: File[]) => { - const novosAnexos = acceptedFiles.map(file => ({ + const novosAnexos = acceptedFiles.map((file) => ({ file, nome: file.name, - progresso: 0 + progresso: 0, })); - setAnexos(prev => [...prev, ...novosAnexos]); + setAnexos((prev) => [...prev, ...novosAnexos]); }, []); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, - maxSize: 10485760 // 10MB + maxSize: 10485760, // 10MB }); const handleRemoveAnexo = (index: number) => { - setAnexos(prev => prev.filter((_, i) => i !== index)); + setAnexos((prev) => prev.filter((_, i) => i !== index)); }; const handleSubmit = async (e: React.FormEvent) => { @@ -161,10 +185,10 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, try { setIsSaving(true); - setError(''); + setError(""); if (!formData.clienteId) { - setError('Selecione um cliente'); + setError("Selecione um cliente"); setIsSaving(false); return; } @@ -175,15 +199,15 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, const response = await api.get(`/clientes/${formData.clienteId}`); setSelectedClient(response.data); } catch (error) { - console.error('Erro ao buscar cliente:', error); - setError('Erro ao buscar dados do cliente'); + console.error("Erro ao buscar cliente:", error); + setError("Erro ao buscar dados do cliente"); setIsSaving(false); return; } } // Criar atendimento primeiro para ter o protocolo - const response = await api.post('/atendimentos', { + const response = await api.post("/atendimentos", { ...formData, agendamentoId: initialData?.agendamentoId, }); @@ -195,15 +219,15 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, if (anexos.length > 0) { const anexosPromises = anexos.map(async (anexo) => { try { - const clienteNome = selectedClient?.nome || 'cliente'; + const clienteNome = selectedClient?.nome || "cliente"; return await uploadFile( anexo.file, clienteNome, - 'atendimento', - protocolo + "atendimento", + protocolo, ); } catch (error) { - console.error('Erro no upload:', error); + console.error("Erro no upload:", error); return null; } }); @@ -213,38 +237,42 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, if (anexosSalvos.length > 0) { await api.patch(`/atendimentos/${atendimentoId}`, { - anexos: anexosSalvos + anexos: anexosSalvos, }); } } // Limpar form e fechar modal setFormData({ - titulo: '', + titulo: "", tipo: TipoAtendimento.INICIAL, - conteudo: '', - clienteId: '', - processoId: '', - atendimentoInicialId: '', + conteudo: "", + clienteId: "", + processoId: "", + atendimentoInicialId: "", }); await onSave(response.data); onClose(); - } catch (err) { - console.error('Erro ao salvar atendimento:', err); - setError(err.response?.data?.error || 'Erro ao criar atendimento'); + console.error("Erro ao salvar atendimento:", err); + setError(err.response?.data?.error || "Erro ao criar atendimento"); } finally { setIsSaving(false); } }; return ( - + Novo Atendimento - + {/* Alterado para XMarkIcon */} @@ -265,22 +293,27 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" placeholder="Buscar cliente..." /> - + {searchResults.length > 0 && ( - {searchResults.map(client => ( + {searchResults.map((client) => ( { setSelectedClient(client); - setFormData(prev => ({ ...prev, clienteId: client.id })); + setFormData((prev) => ({ + ...prev, + clienteId: client.id, + })); setSearchTerm(client.nome); setSearchResults([]); }} > {client.nome} - {client.cpfCnpj} + + {client.cpfCnpj} + ))} @@ -294,12 +327,17 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, setFormData({ ...formData, tipo: e.target.value as TipoAtendimento })} + onChange={(e) => + setFormData({ + ...formData, + tipo: e.target.value as TipoAtendimento, + }) + } className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" > {Object.values(TipoAtendimento).map((tipo) => ( - {tipo.replace('_', ' ')} + {tipo.replace("_", " ")} ))} @@ -313,7 +351,12 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, setFormData({ ...formData, atendimentoInicialId: e.target.value })} + onChange={(e) => + setFormData({ + ...formData, + atendimentoInicialId: e.target.value, + }) + } className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" required={formData.tipo !== TipoAtendimento.INICIAL} > @@ -331,7 +374,9 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, fullWidth label="Título do Atendimento" value={formData.titulo} - onChange={(e) => setFormData({ ...formData, titulo: e.target.value })} + onChange={(e) => + setFormData({ ...formData, titulo: e.target.value }) + } required /> @@ -345,14 +390,14 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, editor?.chain().focus().toggleBold().run()} - className={`px-2 py-1 rounded ${editor?.isActive('bold') ? 'bg-gray-200' : 'hover:bg-gray-100'}`} + className={`px-2 py-1 rounded ${editor?.isActive("bold") ? "bg-gray-200" : "hover:bg-gray-100"}`} > Negrito editor?.chain().focus().toggleItalic().run()} - className={`px-2 py-1 rounded ${editor?.isActive('italic') ? 'bg-gray-200' : 'hover:bg-gray-100'}`} + className={`px-2 py-1 rounded ${editor?.isActive("italic") ? "bg-gray-200" : "hover:bg-gray-100"}`} > Itálico @@ -371,14 +416,16 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, - Arraste arquivos aqui ou clique para selecionar + + Arraste arquivos aqui ou clique para selecionar + Máximo 10MB por arquivo @@ -388,17 +435,26 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, {/* Lista de Anexos */} {anexos.length > 0 && ( - Arquivos para upload + + Arquivos para upload + {anexos.map((anexo, index) => ( - + - {anexo.nome} + + {anexo.nome} + {anexo.erro && ( - {anexo.erro} + + {anexo.erro} + )} - {isSaving ? 'Salvando...' : 'Salvar'} + {isSaving ? "Salvando..." : "Salvar"} diff --git a/src/components/atendimentos/AtendimentosPage.tsx b/src/components/atendimentos/AtendimentosPage.tsx index 009c726..31d8dbb 100644 --- a/src/components/atendimentos/AtendimentosPage.tsx +++ b/src/components/atendimentos/AtendimentosPage.tsx @@ -1,46 +1,58 @@ -import { useState } from 'react'; -import { useQuery } from 'react-query'; -import { useNavigate } from 'react-router-dom'; -import api from '../../services/api'; -import { AtendimentoForm } from './AtendimentoForm'; -import { AgendamentoCalendario } from './AgendamentoCalendario'; // Adicionar esta importação -import { formatRelative } from 'date-fns'; -import { ptBR } from 'date-fns/locale'; -import { TextField, FormControl, InputLabel, Select, MenuItem } from '@mui/material'; -import { DatePicker } from '@mui/x-date-pickers'; -import { TipoAtendimento } from '@prisma/client'; +import { + TextField, + FormControl, + InputLabel, + Select, + MenuItem, +} from "@mui/material"; +import { DatePicker } from "@mui/x-date-pickers"; +import { TipoAtendimento } from "@prisma/client"; +import { formatRelative, ptBR } from "date-fns"; +import { useState } from "react"; +import { useQuery } from "react-query"; +import { useNavigate } from "react-router-dom"; + +import api from "../../services/api"; + +import { AgendamentoCalendario } from "./AgendamentoCalendario"; // Adicionar esta importação +import { AtendimentoForm } from "./AtendimentoForm"; export const AtendimentosPage = () => { const navigate = useNavigate(); const [formOpen, setFormOpen] = useState(false); const [filtros, setFiltros] = useState({ - protocolo: '', + protocolo: "", dataInicio: null, dataFim: null, - tipo: '', - clienteId: '', - processoId: '', + tipo: "", + clienteId: "", + processoId: "", }); - const { data: atendimentos, refetch } = useQuery(['atendimentos', filtros], async () => { - const params = new URLSearchParams(); - if (filtros.protocolo) params.append('protocolo', filtros.protocolo); - if (filtros.dataInicio) params.append('dataInicio', filtros.dataInicio.toISOString()); - if (filtros.dataFim) params.append('dataFim', filtros.dataFim.toISOString()); - if (filtros.tipo) params.append('tipo', filtros.tipo); - if (filtros.clienteId) params.append('clienteId', filtros.clienteId); - if (filtros.processoId) params.append('processoId', filtros.processoId); + const { data: atendimentos, refetch } = useQuery( + ["atendimentos", filtros], + async () => { + const params = new URLSearchParams(); + if (filtros.protocolo) params.append("protocolo", filtros.protocolo); + if (filtros.dataInicio) + params.append("dataInicio", filtros.dataInicio.toISOString()); + if (filtros.dataFim) + params.append("dataFim", filtros.dataFim.toISOString()); + if (filtros.tipo) params.append("tipo", filtros.tipo); + if (filtros.clienteId) params.append("clienteId", filtros.clienteId); + if (filtros.processoId) params.append("processoId", filtros.processoId); - const response = await api.get(`/atendimentos?${params}`); - return response.data; - }); + const response = await api.get(`/atendimentos?${params}`); + return response.data; + }, + ); const handleSave = async (data: any) => { try { await refetch(); setFormOpen(false); } catch (error) { - console.error('Erro ao atualizar lista:', error); + console.error("Erro ao atualizar lista:", error); } }; @@ -48,7 +60,9 @@ export const AtendimentosPage = () => { - Atendimentos + + Atendimentos + setFormOpen(true)} className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 dark:hover:bg-blue-500 transition-colors" @@ -68,7 +82,9 @@ export const AtendimentosPage = () => { setFiltros({ ...filtros, protocolo: e.target.value })} + onChange={(e) => + setFiltros({ ...filtros, protocolo: e.target.value }) + } size="small" /> @@ -76,14 +92,14 @@ export const AtendimentosPage = () => { label="Data Início" value={filtros.dataInicio} onChange={(date) => setFiltros({ ...filtros, dataInicio: date })} - slotProps={{ textField: { size: 'small' } }} + slotProps={{ textField: { size: "small" } }} /> setFiltros({ ...filtros, dataFim: date })} - slotProps={{ textField: { size: 'small' } }} + slotProps={{ textField: { size: "small" } }} /> @@ -91,12 +107,14 @@ export const AtendimentosPage = () => { setFiltros({ ...filtros, tipo: e.target.value })} + onChange={(e) => + setFiltros({ ...filtros, tipo: e.target.value }) + } > Todos {Object.values(TipoAtendimento).map((tipo) => ( - {tipo.replace('_', ' ')} + {tipo.replace("_", " ")} ))} @@ -106,18 +124,22 @@ export const AtendimentosPage = () => { {atendimentos?.map((atendimento: any) => ( - navigate(`/atendimentos/${atendimento.id}`)} > - {atendimento.titulo} - {atendimento.status === 'CANCELADO' && ( + + {atendimento.titulo} + + {atendimento.status === "CANCELADO" && ( Cancelado @@ -127,27 +149,48 @@ export const AtendimentosPage = () => { Protocolo: {atendimento.protocolo} - + {atendimento.tipo} - + - {formatRelative(new Date(atendimento.dataAtendimento), new Date(), { locale: ptBR })} + {formatRelative( + new Date(atendimento.dataAtendimento), + new Date(), + { locale: ptBR }, + )} - {atendimento.status === 'CANCELADO' && ( + {atendimento.status === "CANCELADO" && ( Motivo: {atendimento.justificativaCancelamento} )} {atendimento.anexos?.length > 0 && ( - - + + {atendimento.anexos.length} anexos diff --git a/src/components/calculos/CalculosList.tsx b/src/components/calculos/CalculosList.tsx index 080f47e..3444d15 100644 --- a/src/components/calculos/CalculosList.tsx +++ b/src/components/calculos/CalculosList.tsx @@ -1,9 +1,9 @@ -import { useEffect, useState } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; -import { format } from 'date-fns'; -import ptBR from 'date-fns/locale/pt-BR'; -import { toast } from 'react-toastify'; -import { useAuth } from '../../contexts/AuthContext'; +import ptBR, { format } from "date-fns"; +import { useEffect, useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; + +import { useAuth } from "../../contexts/AuthContext"; interface Calculo { id: string; @@ -32,114 +32,119 @@ export function CalculosList() { const [calculos, setCalculos] = useState([]); const [loading, setLoading] = useState(true); const [filtro, setFiltro] = useState({ - titulo: '', - status: '', - processoId: '', - clienteId: '' + titulo: "", + status: "", + processoId: "", + clienteId: "", }); - + useEffect(() => { // Verificar autenticação antes de carregar os dados if (isAuthenticated) { carregarCalculos(); } else { // Redirecionar para login caso não esteja autenticado - navigate('/login'); + navigate("/login"); } }, [isAuthenticated, navigate]); - + const carregarCalculos = async () => { try { setLoading(true); - + // Construir query string baseado nos filtros const queryParams = new URLSearchParams(); - if (filtro.titulo) queryParams.append('titulo', filtro.titulo); - if (filtro.status) queryParams.append('status', filtro.status); - if (filtro.processoId) queryParams.append('processoId', filtro.processoId); - if (filtro.clienteId) queryParams.append('clienteId', filtro.clienteId); - + if (filtro.titulo) queryParams.append("titulo", filtro.titulo); + if (filtro.status) queryParams.append("status", filtro.status); + if (filtro.processoId) + queryParams.append("processoId", filtro.processoId); + if (filtro.clienteId) queryParams.append("clienteId", filtro.clienteId); + // Obter token de autenticação usando o contexto const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado. Faça login novamente.'); + throw new Error("Usuário não autenticado. Faça login novamente."); } - + // Incluir o token no cabeçalho da requisição const response = await fetch(`/api/calculos?${queryParams.toString()}`, { headers: { - 'Authorization': `Bearer ${token}` - } + Authorization: `Bearer ${token}`, + }, }); - + if (!response.ok) { if (response.status === 401) { // Se receber 401, o token pode estar expirado - navigate('/login'); - throw new Error('Sessão expirada. Por favor, faça login novamente.'); + navigate("/login"); + throw new Error("Sessão expirada. Por favor, faça login novamente."); } - throw new Error('Erro ao carregar cálculos'); + throw new Error("Erro ao carregar cálculos"); } - + const data = await response.json(); setCalculos(data); } catch (error) { - console.error('Erro ao carregar cálculos:', error); + console.error("Erro ao carregar cálculos:", error); toast.error(`Falha ao carregar os cálculos: ${error.message}`); } finally { setLoading(false); } }; - - const handleFiltroChange = (e: React.ChangeEvent) => { + + const handleFiltroChange = ( + e: React.ChangeEvent, + ) => { const { name, value } = e.target; - setFiltro(prev => ({ ...prev, [name]: value })); + setFiltro((prev) => ({ ...prev, [name]: value })); }; - + const handleSubmitFiltro = (e: React.FormEvent) => { e.preventDefault(); carregarCalculos(); }; - + // Função para formatar valores monetários const formatarValor = (valor: string) => { - return new Intl.NumberFormat('pt-BR', { - style: 'currency', - currency: 'BRL' + return new Intl.NumberFormat("pt-BR", { + style: "currency", + currency: "BRL", }).format(parseFloat(valor)); }; - + // Função para formatar datas const formatarData = (data: string) => { try { - return format(new Date(data), 'dd/MM/yyyy', { locale: ptBR }); + return format(new Date(data), "dd/MM/yyyy", { locale: ptBR }); } catch (error) { - return 'Data inválida'; + return "Data inválida"; } }; - + // Status Badge const StatusBadge = ({ status }: { status: string }) => { const getStatusColor = () => { switch (status) { - case 'RASCUNHO': - return 'bg-yellow-100 text-yellow-800'; - case 'FINALIZADO': - return 'bg-green-100 text-green-800'; - case 'ARQUIVADO': - return 'bg-gray-100 text-gray-800'; + case "RASCUNHO": + return "bg-yellow-100 text-yellow-800"; + case "FINALIZADO": + return "bg-green-100 text-green-800"; + case "ARQUIVADO": + return "bg-gray-100 text-gray-800"; default: - return 'bg-blue-100 text-blue-800'; + return "bg-blue-100 text-blue-800"; } }; - + return ( - + {status} ); }; - + return ( @@ -151,13 +156,19 @@ export function CalculosList() { Novo Cálculo - + {/* Filtros */} Filtros - + - + Título - + - + Status Arquivado - + - + {/* Lista de Cálculos */} {loading ? ( - + Carregando... ) : calculos.length === 0 ? ( - Nenhum cálculo encontrado + + Nenhum cálculo encontrado + Para criar um novo cálculo, clique no botão "Novo Cálculo". @@ -218,7 +237,10 @@ export function CalculosList() { {calculos.map((calculo) => ( - + @@ -244,14 +266,16 @@ export function CalculosList() { - Criado em: {formatarData(calculo.criadoEm)} por {calculo.usuario.nome} + Criado em: {formatarData(calculo.criadoEm)} por{" "} + {calculo.usuario.nome} {calculo.processo && ( - Processo: {calculo.processo.numero} - {calculo.processo.titulo} + Processo: {calculo.processo.numero} -{" "} + {calculo.processo.titulo} )} diff --git a/src/components/calculos/DetalhePensaoAlimenticia.tsx b/src/components/calculos/DetalhePensaoAlimenticia.tsx index 222b039..6e5f0f8 100644 --- a/src/components/calculos/DetalhePensaoAlimenticia.tsx +++ b/src/components/calculos/DetalhePensaoAlimenticia.tsx @@ -1,7 +1,8 @@ -import React, { useEffect, useState } from 'react'; -import { useParams } from 'react-router-dom'; -import { toast } from 'react-toastify'; -import api from '../../services/api'; +import React, { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import { toast } from "react-toastify"; + +import api from "../../services/api"; interface CalculoPensao { id: string; @@ -36,8 +37,8 @@ export function DetalhePensaoAlimenticia() { const response = await api.get(`/pensao-alimenticia/${id}`); setCalculo(response.data); } catch (error) { - console.error('Erro ao carregar cálculo:', error); - toast.error('Falha ao carregar dados do cálculo'); + console.error("Erro ao carregar cálculo:", error); + toast.error("Falha ao carregar dados do cálculo"); } finally { setLoading(false); } @@ -57,29 +58,53 @@ export function DetalhePensaoAlimenticia() { return ( {calculo.titulo} - + Dados do Cálculo - Data Inicial: {new Date(calculo.dataInicial).toLocaleDateString()} + + Data Inicial:{" "} + {new Date(calculo.dataInicial).toLocaleDateString()} + {calculo.dataFinal && ( - Data Final: {new Date(calculo.dataFinal).toLocaleDateString()} + + Data Final:{" "} + {new Date(calculo.dataFinal).toLocaleDateString()} + )} - Percentual: {calculo.percentualSalarioMinimo}% + + Percentual:{" "} + {calculo.percentualSalarioMinimo}% + {calculo.multaPactuada && ( - Multa: {calculo.multaPactuada}% + + Multa:{" "} + {calculo.multaPactuada}% + )} - Índice: {calculo.tipoAtualizacao} + + Índice:{" "} + {calculo.tipoAtualizacao} + Valores - Valor Base: R$ {calculo.valorCalculado.toFixed(2)} - Valor Multa: R$ {calculo.valorMulta.toFixed(2)} - Valor Total: R$ {calculo.valorTotal.toFixed(2)} + + Valor Base: R${" "} + {calculo.valorCalculado.toFixed(2)} + + + Valor Multa: R${" "} + {calculo.valorMulta.toFixed(2)} + + + Valor Total: R${" "} + {calculo.valorTotal.toFixed(2)} + @@ -92,4 +117,4 @@ export function DetalhePensaoAlimenticia() { )} ); -} \ No newline at end of file +} diff --git a/src/components/calculos/DetalhesCalculo.tsx b/src/components/calculos/DetalhesCalculo.tsx index c376313..ff38836 100644 --- a/src/components/calculos/DetalhesCalculo.tsx +++ b/src/components/calculos/DetalhesCalculo.tsx @@ -1,14 +1,14 @@ -import React, { useState, useEffect } from 'react'; -import { useParams, useNavigate } from 'react-router-dom'; -import { format } from 'date-fns'; -import ptBR from 'date-fns/locale/pt-BR'; -import { toast } from 'react-toastify'; -import { Card, Button, Tabs, TabList, Tab, TabPanel } from '../ui'; -import { useAuth } from '../../contexts/AuthContext'; -import { api } from '../../services/api'; -import axios from 'axios'; -import { formatarData, formatarMoeda } from '../../utils/formatters'; -import { PencilIcon, TrashIcon } from '@heroicons/react/24/outline'; +import { PencilIcon, TrashIcon } from "@heroicons/react/24/outline"; +import axios from "axios"; +import ptBR, { format } from "date-fns"; +import React, { useState, useEffect } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; + +import { useAuth } from "../../contexts/AuthContext"; +import { api } from "../../services/api"; +import { formatarData, formatarMoeda } from "../../utils/formatters"; +import { Card, Button, Tabs, TabList, Tab, TabPanel } from "../ui"; interface Calculo { id: string; @@ -58,12 +58,12 @@ export function DetalhesCalculo() { const [calculo, setCalculo] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [activeTab, setActiveTab] = useState('resumo'); + const [activeTab, setActiveTab] = useState("resumo"); const [formData, setFormData] = useState({ - indice: 'INPC', - dataInicial: '', - dataFinal: '', - valorOriginal: '' + indice: "INPC", + dataInicial: "", + dataFinal: "", + valorOriginal: "", }); const loadCalculo = async () => { @@ -76,9 +76,9 @@ export function DetalhesCalculo() { const response = await api.get(`/calculos/${calculoId}`); setCalculo(response.data); } catch (error) { - console.error('Erro ao carregar cálculo:', error); - setError('Não foi possível carregar os dados do cálculo'); - toast.error('Erro ao carregar dados do cálculo'); + console.error("Erro ao carregar cálculo:", error); + setError("Não foi possível carregar os dados do cálculo"); + toast.error("Erro ao carregar dados do cálculo"); } finally { setLoading(false); } @@ -95,20 +95,24 @@ export function DetalhesCalculo() { // Obter token de autenticação const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado. Faça login novamente.'); + throw new Error("Usuário não autenticado. Faça login novamente."); } // Usar o serviço api em vez de fetch direto - await api.post(`/calculos/${calculoId}/finalizar`, {}, { - headers: { - 'Authorization': `Bearer ${token}` - } - }); + await api.post( + `/calculos/${calculoId}/finalizar`, + {}, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); - toast.success('Cálculo finalizado com sucesso!'); + toast.success("Cálculo finalizado com sucesso!"); loadCalculo(); // Recarregar os dados } catch (error) { - console.error('Erro ao finalizar cálculo:', error); + console.error("Erro ao finalizar cálculo:", error); toast.error(`Falha ao finalizar cálculo: ${error.message}`); } }; @@ -116,33 +120,33 @@ export function DetalhesCalculo() { const gerarRelatorio = async () => { try { const response = await api.get(`/calculos/${calculoId}/relatorio`, { - responseType: 'blob' // Importante para receber o PDF como blob + responseType: "blob", // Importante para receber o PDF como blob }); // Criar URL do blob e abrir em uma nova aba - const blob = new Blob([response.data], { type: 'application/pdf' }); + const blob = new Blob([response.data], { type: "application/pdf" }); const url = window.URL.createObjectURL(blob); - window.open(url, '_blank'); + window.open(url, "_blank"); } catch (error) { - console.error('Erro ao gerar relatório:', error); - toast.error('Erro ao gerar relatório'); + console.error("Erro ao gerar relatório:", error); + toast.error("Erro ao gerar relatório"); } }; const exportarExcel = async () => { try { const response = await api.get(`/calculos/${calculoId}/excel`, { - responseType: 'blob' // Importante para receber o Excel como blob + responseType: "blob", // Importante para receber o Excel como blob }); // Criar URL do blob e forçar download const blob = new Blob([response.data], { - type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", }); const url = window.URL.createObjectURL(blob); // Criar link temporário para download - const link = document.createElement('a'); + const link = document.createElement("a"); link.href = url; link.download = `calculo_${calculoId}.xlsx`; link.click(); @@ -150,25 +154,25 @@ export function DetalhesCalculo() { // Limpar URL window.URL.revokeObjectURL(url); } catch (error) { - console.error('Erro ao exportar Excel:', error); - toast.error('Erro ao exportar para Excel'); + console.error("Erro ao exportar Excel:", error); + toast.error("Erro ao exportar para Excel"); } }; // Função para formatar valores monetários const formatarValor = (valor: string) => { - return new Intl.NumberFormat('pt-BR', { - style: 'currency', - currency: 'BRL' + return new Intl.NumberFormat("pt-BR", { + style: "currency", + currency: "BRL", }).format(parseFloat(valor)); }; // Função para formatar datas const formatarData = (data: string) => { try { - return format(new Date(data), 'dd/MM/yyyy', { locale: ptBR }); + return format(new Date(data), "dd/MM/yyyy", { locale: ptBR }); } catch (error) { - return 'Data inválida'; + return "Data inválida"; } }; @@ -176,19 +180,21 @@ export function DetalhesCalculo() { const StatusBadge = ({ status }: { status: string }) => { const getStatusColor = () => { switch (status) { - case 'RASCUNHO': - return 'bg-yellow-100 text-yellow-800'; - case 'FINALIZADO': - return 'bg-green-100 text-green-800'; - case 'ARQUIVADO': - return 'bg-gray-100 text-gray-800'; + case "RASCUNHO": + return "bg-yellow-100 text-yellow-800"; + case "FINALIZADO": + return "bg-green-100 text-green-800"; + case "ARQUIVADO": + return "bg-gray-100 text-gray-800"; default: - return 'bg-blue-100 text-blue-800'; + return "bg-blue-100 text-blue-800"; } }; return ( - + {status} ); @@ -198,7 +204,10 @@ export function DetalhesCalculo() { return ( - + Carregando... @@ -212,8 +221,12 @@ export function DetalhesCalculo() { Erro - {error || 'Não foi possível carregar os dados do cálculo'} - navigate('/calculos')}>Voltar para Lista + + {error || "Não foi possível carregar os dados do cálculo"} + + navigate("/calculos")}> + Voltar para Lista + @@ -227,17 +240,19 @@ export function DetalhesCalculo() { {calculo.titulo} - Criado em {formatarData(calculo.criadoEm)} + + Criado em {formatarData(calculo.criadoEm)} + - {calculo.status === 'RASCUNHO' && ( + {calculo.status === "RASCUNHO" && ( Finalizar Cálculo )} - navigate('/calculos')} variant="outline"> + navigate("/calculos")} variant="outline"> Voltar @@ -260,23 +275,39 @@ export function DetalhesCalculo() { - Informações Gerais + + Informações Gerais + - Data Base - {formatarData(calculo.dataBase)} + + Data Base + + + {formatarData(calculo.dataBase)} + - Data do Cálculo - {formatarData(calculo.dataCalculo)} + + Data do Cálculo + + + {formatarData(calculo.dataCalculo)} + - Responsável - {calculo.usuario.nome} + + Responsável + + + {calculo.usuario.nome} + {calculo.processo && ( - Processo + + Processo + {calculo.processo.numero} - {calculo.processo.titulo} @@ -284,62 +315,100 @@ export function DetalhesCalculo() { )} {calculo.cliente && ( - Cliente - {calculo.cliente.nome} + + Cliente + + + {calculo.cliente.nome} + )} {calculo.descricao && ( - Descrição - {calculo.descricao} + + Descrição + + + {calculo.descricao} + )} - Valores + + Valores + Valor Base - {formatarValor(calculo.valorBase)} + + {formatarValor(calculo.valorBase)} + Valor Corrigido - {formatarValor(calculo.valorCorrigido)} + + {formatarValor(calculo.valorCorrigido)} + Valor Final - {formatarValor(calculo.valorFinal)} + + {formatarValor(calculo.valorFinal)} + - Resumo de Componentes + + Resumo de Componentes + - Correções Monetárias: - {calculo.correcoes.length} + + Correções Monetárias: + + + {calculo.correcoes.length} + - Juros Aplicados: - {calculo.juros.length} + + Juros Aplicados: + + + {calculo.juros.length} + Multas: - {calculo.multas.length} + + {calculo.multas.length} + - Honorários: - {calculo.honorarios.length} + + Honorários: + + + {calculo.honorarios.length} + Parcelas: - {calculo.parcelas.length} + + {calculo.parcelas.length} + - Descontos: - {calculo.descontos.length} + + Descontos: + + + {calculo.descontos.length} + @@ -347,7 +416,7 @@ export function DetalhesCalculo() { - {calculo.status !== 'FINALIZADO' && ( + {calculo.status !== "FINALIZADO" && ( - + Exportar Excel @@ -376,7 +442,11 @@ export function DetalhesCalculo() { - + @@ -429,28 +499,38 @@ export function DetalhesCalculo() { } // Implementação dos componentes de abas -function CorrecaoMonetariaTab({ calculo, onUpdate, formatarValor }: { calculo: Calculo, onUpdate: () => void, formatarValor: (valor: string) => string }) { +function CorrecaoMonetariaTab({ + calculo, + onUpdate, + formatarValor, +}: { + calculo: Calculo; + onUpdate: () => void; + formatarValor: (valor: string) => string; +}) { const { getToken } = useAuth(); const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ - indice: 'INPC', - dataInicial: new Date().toISOString().split('T')[0], - dataFinal: new Date().toISOString().split('T')[0], - valorOriginal: calculo.valorBase + indice: "INPC", + dataInicial: new Date().toISOString().split("T")[0], + dataFinal: new Date().toISOString().split("T")[0], + valorOriginal: calculo.valorBase, }); // Função para corrigir a formatação do valor const corrigirValorExibicao = (valor: string): string => { // Se o valor for 222.041,00, corrigir para 22.204,10 - if (valor === '222.041,00') { - return 'R$ 22.204,10'; + if (valor === "222.041,00") { + return "R$ 22.204,10"; } return valor; }; - const handleChange = (e: React.ChangeEvent) => { + const handleChange = ( + e: React.ChangeEvent, + ) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); }; const handleSubmit = async (e: React.FormEvent) => { @@ -459,44 +539,51 @@ function CorrecaoMonetariaTab({ calculo, onUpdate, formatarValor }: { calculo: C try { await api.post(`/calculos/${calculo.id}/correcoes`, formData, { - timeout: 60000 // Aumentar timeout para 1 minuto + timeout: 60000, // Aumentar timeout para 1 minuto }); - toast.success('Correção monetária adicionada com sucesso'); + toast.success("Correção monetária adicionada com sucesso"); // Chamar o callback de atualização em vez de referenciar loadCalculo onUpdate(); } catch (error) { - console.error('Erro ao adicionar correção:', error); - toast.error('Erro ao adicionar correção monetária'); + console.error("Erro ao adicionar correção:", error); + toast.error("Erro ao adicionar correção monetária"); } finally { setLoading(false); } }; const handleDelete = async (correcaoId: string) => { - if (!confirm('Deseja realmente excluir esta correção monetária?')) return; + if (!confirm("Deseja realmente excluir esta correção monetária?")) return; try { await api.delete(`/calculos/${calculo.id}/correcoes/${correcaoId}`); - toast.success('Correção monetária removida com sucesso'); + toast.success("Correção monetária removida com sucesso"); onUpdate(); } catch (error) { - console.error('Erro ao excluir correção:', error); - toast.error('Falha ao excluir correção monetária'); + console.error("Erro ao excluir correção:", error); + toast.error("Falha ao excluir correção monetária"); } }; return ( - Correção Monetária + + Correção Monetária + - {calculo.status !== 'FINALIZADO' && ( + {calculo.status !== "FINALIZADO" && ( - Adicionar Correção Monetária + + Adicionar Correção Monetária + - + Índice - + Data Inicial - + Data Final - + Valor Original @@ -569,7 +665,7 @@ function CorrecaoMonetariaTab({ calculo, onUpdate, formatarValor }: { calculo: C className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" disabled={loading} > - {loading ? 'Adicionando...' : 'Adicionar Correção'} + {loading ? "Adicionando..." : "Adicionar Correção"} @@ -577,30 +673,60 @@ function CorrecaoMonetariaTab({ calculo, onUpdate, formatarValor }: { calculo: C )} - Histórico de Correções + + Histórico de Correções + {calculo.correcoes.length === 0 ? ( - Nenhuma correção monetária registrada + + Nenhuma correção monetária registrada + ) : ( - Data Inicial - Data Final - Índice - Valor Original - Valor Corrigido - Ações + + Data Inicial + + + Data Final + + + Índice + + + Valor Original + + + Valor Corrigido + + + Ações + {calculo.correcoes.map((correcao, index) => ( - {formatarData(correcao.dataInicial)} - {correcao.dataFinal ? formatarData(correcao.dataFinal) : '-'} - {correcao.indice} - {formatarMoeda(correcao.valorOriginal)} - {corrigirValorExibicao(formatarMoeda(correcao.valorCorrigido))} + + {formatarData(correcao.dataInicial)} + + + {correcao.dataFinal + ? formatarData(correcao.dataFinal) + : "-"} + + + {correcao.indice} + + + {formatarMoeda(correcao.valorOriginal)} + + + {corrigirValorExibicao( + formatarMoeda(correcao.valorCorrigido), + )} + void; @@ -635,25 +761,27 @@ function JurosTab({ const { getToken } = useAuth(); const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ - tipo: 'SIMPLES', - taxa: '1,00', - periodicidade: 'MENSAL', - natureza: 'MORATORIO', - dataInicial: new Date().toISOString().split('T')[0], - dataFinal: new Date().toISOString().split('T')[0], - valorBase: calculo.valorBase + tipo: "SIMPLES", + taxa: "1,00", + periodicidade: "MENSAL", + natureza: "MORATORIO", + dataInicial: new Date().toISOString().split("T")[0], + dataFinal: new Date().toISOString().split("T")[0], + valorBase: calculo.valorBase, }); - const handleChange = (e: React.ChangeEvent) => { + const handleChange = ( + e: React.ChangeEvent, + ) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); }; const handleTaxaChange = (e: React.ChangeEvent) => { const { value } = e.target; // Permitir apenas dígitos, vírgula, e ponto - const numericValue = value.replace(/[^\d.,]/g, ''); - setFormData(prev => ({ ...prev, taxa: numericValue })); + const numericValue = value.replace(/[^\d.,]/g, ""); + setFormData((prev) => ({ ...prev, taxa: numericValue })); }; const handleSubmit = async (e: React.FormEvent) => { @@ -663,31 +791,37 @@ function JurosTab({ try { const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado. Faça login novamente.'); + throw new Error("Usuário não autenticado. Faça login novamente."); } // Converter taxa para decimal (ex: 1,5% -> 0.015) - const taxaDecimal = parseFloat(formData.taxa.replace(',', '.')) / 100; + const taxaDecimal = parseFloat(formData.taxa.replace(",", ".")) / 100; // Usar o serviço api em vez de fetch direto - await api.post(`/calculos/${calculo.id}/juros`, { - tipo: formData.tipo, - taxa: taxaDecimal, - periodicidade: formData.periodicidade, - natureza: formData.natureza, - dataInicial: formData.dataInicial, - dataFinal: formData.dataFinal, - valorBase: parseFloat(formData.valorBase.replace(/[^\d.,]/g, '').replace(',', '.')) - }, { - headers: { - 'Authorization': `Bearer ${token}` - } - }); + await api.post( + `/calculos/${calculo.id}/juros`, + { + tipo: formData.tipo, + taxa: taxaDecimal, + periodicidade: formData.periodicidade, + natureza: formData.natureza, + dataInicial: formData.dataInicial, + dataFinal: formData.dataFinal, + valorBase: parseFloat( + formData.valorBase.replace(/[^\d.,]/g, "").replace(",", "."), + ), + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); - toast.success('Juros adicionados com sucesso!'); + toast.success("Juros adicionados com sucesso!"); onUpdate(); // Recarregar dados do cálculo } catch (error) { - console.error('Erro ao adicionar juros:', error); + console.error("Erro ao adicionar juros:", error); toast.error(`Falha ao adicionar juros: ${error.message}`); } finally { setLoading(false); @@ -695,15 +829,15 @@ function JurosTab({ }; const handleDelete = async (juroId: string) => { - if (!confirm('Deseja realmente excluir este juro?')) return; + if (!confirm("Deseja realmente excluir este juro?")) return; try { await api.delete(`/calculos/${calculo.id}/juros/${juroId}`); - toast.success('Juro removido com sucesso'); + toast.success("Juro removido com sucesso"); onUpdate(); } catch (error) { - console.error('Erro ao excluir juro:', error); - toast.error('Falha ao excluir juro'); + console.error("Erro ao excluir juro:", error); + toast.error("Falha ao excluir juro"); } }; @@ -711,13 +845,18 @@ function JurosTab({ Juros - {calculo.status !== 'FINALIZADO' && ( + {calculo.status !== "FINALIZADO" && ( - Adicionar Juros + + Adicionar Juros + - + Tipo - + Taxa @@ -754,7 +896,10 @@ function JurosTab({ - + Periodicidade - + Natureza - + Data Inicial - + Data Final - + Valor Base @@ -847,7 +1004,7 @@ function JurosTab({ className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" disabled={loading} > - {loading ? 'Adicionando...' : 'Adicionar Juros'} + {loading ? "Adicionando..." : "Adicionar Juros"} @@ -855,37 +1012,81 @@ function JurosTab({ )} - Juros Aplicados + + Juros Aplicados + {calculo.juros && calculo.juros.length > 0 ? ( - Tipo - Taxa - Período - Natureza - Valor Base - Valor Juros - Ações + + Tipo + + + Taxa + + + Período + + + Natureza + + + Valor Base + + + Valor Juros + + + Ações + {calculo.juros.map((juro, index) => ( - {juro.tipo === 'SIMPLES' ? 'Simples' : 'Composto'} + {juro.tipo === "SIMPLES" ? "Simples" : "Composto"} {(parseFloat(juro.taxa) * 100).toFixed(2)}% - {formatarData(juro.dataInicial)} a {formatarData(juro.dataFinal)} + {formatarData(juro.dataInicial)} a{" "} + {formatarData(juro.dataFinal)} + + + {juro.natureza} + + + {formatarValor(juro.valorBase)} + + + {formatarValor(juro.valorJuros)} - {juro.natureza} - {formatarValor(juro.valorBase)} - {formatarValor(juro.valorJuros)} void; @@ -922,22 +1123,26 @@ function MultasTab({ const { getToken } = useAuth(); const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ - tipo: 'CONTRATUAL', - percentual: '2,00', + tipo: "CONTRATUAL", + percentual: "2,00", valorBase: calculo.valorBase, - fundamento: '' + fundamento: "", }); - const handleChange = (e: React.ChangeEvent) => { + const handleChange = ( + e: React.ChangeEvent< + HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement + >, + ) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); }; const handlePercentualChange = (e: React.ChangeEvent) => { const { value } = e.target; // Permitir apenas dígitos, vírgula, e ponto - const numericValue = value.replace(/[^\d.,]/g, ''); - setFormData(prev => ({ ...prev, percentual: numericValue })); + const numericValue = value.replace(/[^\d.,]/g, ""); + setFormData((prev) => ({ ...prev, percentual: numericValue })); }; const handleSubmit = async (e: React.FormEvent) => { @@ -947,46 +1152,49 @@ function MultasTab({ try { const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado'); + throw new Error("Usuário não autenticado"); } // Converter percentual para decimal (ex: 2,00% -> 0.02) - const percentualDecimal = parseFloat(formData.percentual.replace(',', '.')) / 100; + const percentualDecimal = + parseFloat(formData.percentual.replace(",", ".")) / 100; await api.post(`/calculos/${calculo.id}/multas`, { tipo: formData.tipo, percentual: percentualDecimal, - valorBase: parseFloat(formData.valorBase.replace(/[^\d.,]/g, '').replace(',', '.')), - fundamento: formData.fundamento + valorBase: parseFloat( + formData.valorBase.replace(/[^\d.,]/g, "").replace(",", "."), + ), + fundamento: formData.fundamento, }); - toast.success('Multa adicionada com sucesso!'); + toast.success("Multa adicionada com sucesso!"); onUpdate(); // Limpar formulário - setFormData(prev => ({ + setFormData((prev) => ({ ...prev, - percentual: '', - fundamento: '' + percentual: "", + fundamento: "", })); } catch (error) { - console.error('Erro ao adicionar multa:', error); - toast.error('Falha ao adicionar multa: ' + error.message); + console.error("Erro ao adicionar multa:", error); + toast.error("Falha ao adicionar multa: " + error.message); } finally { setLoading(false); } }; const handleDelete = async (multaId: string) => { - if (!confirm('Deseja realmente excluir esta multa?')) return; + if (!confirm("Deseja realmente excluir esta multa?")) return; try { await api.delete(`/calculos/${calculo.id}/multas/${multaId}`); - toast.success('Multa removida com sucesso'); + toast.success("Multa removida com sucesso"); onUpdate(); } catch (error) { - console.error('Erro ao excluir multa:', error); - toast.error('Falha ao excluir multa'); + console.error("Erro ao excluir multa:", error); + toast.error("Falha ao excluir multa"); } }; @@ -994,13 +1202,18 @@ function MultasTab({ Multas - {calculo.status !== 'FINALIZADO' && ( + {calculo.status !== "FINALIZADO" && ( - Adicionar Multa + + Adicionar Multa + - + Tipo - + Percentual @@ -1037,7 +1253,10 @@ function MultasTab({ - + Valor Base @@ -1057,7 +1276,10 @@ function MultasTab({ - + Fundamento Legal/Contratual - {loading ? 'Adicionando...' : 'Adicionar Multa'} + {loading ? "Adicionando..." : "Adicionar Multa"} @@ -1085,31 +1307,71 @@ function MultasTab({ )} - Multas Aplicadas + + Multas Aplicadas + {calculo.multas && calculo.multas.length > 0 ? ( - Tipo - Percentual - Valor Base - Valor Multa - Fundamento - Ações + + Tipo + + + Percentual + + + Valor Base + + + Valor Multa + + + Fundamento + + + Ações + {calculo.multas.map((multa, index) => ( - {multa.tipo} + + {multa.tipo} + {(parseFloat(multa.percentual) * 100).toFixed(2)}% - {formatarValor(multa.valorBase)} - {formatarValor(multa.valorMulta)} - {multa.fundamento || '-'} + + {formatarValor(multa.valorBase)} + + + {formatarValor(multa.valorMulta)} + + + {multa.fundamento || "-"} + ) : ( - Nenhuma multa aplicada. + + Nenhuma multa aplicada. + )} @@ -1137,7 +1401,7 @@ function MultasTab({ function ParcelasTab({ calculo, onUpdate, - formatarValor = formatarMoeda + formatarValor = formatarMoeda, }: { calculo: Calculo; onUpdate: () => void; @@ -1146,14 +1410,16 @@ function ParcelasTab({ const { getToken } = useAuth(); const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ - numero: '1', - dataVencimento: new Date().toISOString().split('T')[0], - valorOriginal: calculo.valorBase + numero: "1", + dataVencimento: new Date().toISOString().split("T")[0], + valorOriginal: calculo.valorBase, }); - const handleChange = (e: React.ChangeEvent) => { + const handleChange = ( + e: React.ChangeEvent, + ) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); }; const handleSubmit = async (e: React.FormEvent) => { @@ -1163,30 +1429,36 @@ function ParcelasTab({ try { const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado. Faça login novamente.'); + throw new Error("Usuário não autenticado. Faça login novamente."); } // Usar o serviço api em vez de fetch direto - await api.post(`/calculos/${calculo.id}/parcelas`, { - numero: parseInt(formData.numero), - dataVencimento: formData.dataVencimento, - valorOriginal: parseFloat(formData.valorOriginal.replace(/[^\d.,]/g, '').replace(',', '.')) - }, { - headers: { - 'Authorization': `Bearer ${token}` - } - }); + await api.post( + `/calculos/${calculo.id}/parcelas`, + { + numero: parseInt(formData.numero), + dataVencimento: formData.dataVencimento, + valorOriginal: parseFloat( + formData.valorOriginal.replace(/[^\d.,]/g, "").replace(",", "."), + ), + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); - toast.success('Parcela adicionada com sucesso!'); + toast.success("Parcela adicionada com sucesso!"); onUpdate(); // Recarregar dados do cálculo // Limpar o formulário e incrementar o número da parcela - setFormData(prev => ({ + setFormData((prev) => ({ ...prev, - numero: (parseInt(prev.numero) + 1).toString() + numero: (parseInt(prev.numero) + 1).toString(), })); } catch (error) { - console.error('Erro ao adicionar parcela:', error); + console.error("Erro ao adicionar parcela:", error); toast.error(`Falha ao adicionar parcela: ${error.message}`); } finally { setLoading(false); @@ -1197,40 +1469,44 @@ function ParcelasTab({ try { const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado. Faça login novamente.'); + throw new Error("Usuário não autenticado. Faça login novamente."); } // Usar o serviço api em vez de fetch direto - const response = await api.post(`/calculos/${calculo.id}/parcelas/${parcelaId}/pagar`, { - dataPagamento: new Date().toISOString().split('T')[0] - }, { - headers: { - 'Authorization': `Bearer ${token}` - } - }); + const response = await api.post( + `/calculos/${calculo.id}/parcelas/${parcelaId}/pagar`, + { + dataPagamento: new Date().toISOString().split("T")[0], + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); if (!response.ok) { - throw new Error('Erro ao registrar pagamento'); + throw new Error("Erro ao registrar pagamento"); } - toast.success('Pagamento registrado com sucesso!'); + toast.success("Pagamento registrado com sucesso!"); onUpdate(); // Recarregar dados do cálculo } catch (error) { - console.error('Erro ao registrar pagamento:', error); + console.error("Erro ao registrar pagamento:", error); toast.error(`Falha ao registrar pagamento: ${error.message}`); } }; const handleDelete = async (parcelaId: string) => { - if (!confirm('Deseja realmente excluir esta parcela?')) return; + if (!confirm("Deseja realmente excluir esta parcela?")) return; try { await api.delete(`/calculos/${calculo.id}/parcelas/${parcelaId}`); - toast.success('Parcela removida com sucesso'); + toast.success("Parcela removida com sucesso"); onUpdate(); } catch (error) { - console.error('Erro ao excluir parcela:', error); - toast.error('Falha ao excluir parcela'); + console.error("Erro ao excluir parcela:", error); + toast.error("Falha ao excluir parcela"); } }; @@ -1238,13 +1514,18 @@ function ParcelasTab({ Parcelas - {calculo.status !== 'FINALIZADO' && ( + {calculo.status !== "FINALIZADO" && ( - Adicionar Parcela + + Adicionar Parcela + - + Número da Parcela - + Data de Vencimento - + Valor Original @@ -1301,7 +1588,7 @@ function ParcelasTab({ className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" disabled={loading} > - {loading ? 'Adicionando...' : 'Adicionar Parcela'} + {loading ? "Adicionando..." : "Adicionar Parcela"} @@ -1309,49 +1596,111 @@ function ParcelasTab({ )} - Parcelas do Cálculo + + Parcelas do Cálculo + {calculo.parcelas && calculo.parcelas.length > 0 ? ( - Nº - Vencimento - Valor Original - Correção - Juros - Multa - Total - Status - Ações + + Nº + + + Vencimento + + + Valor Original + + + Correção + + + Juros + + + Multa + + + Total + + + Status + + + Ações + {calculo.parcelas.map((parcela, index) => ( - {parcela.numero} - {formatarData(parcela.dataVencimento)} - {formatarValor(parcela.valorOriginal)} - {formatarValor(parseFloat(parcela.valorCorrigido) - parseFloat(parcela.valorOriginal))} + {parcela.numero} + + + {formatarData(parcela.dataVencimento)} + + + {formatarValor(parcela.valorOriginal)} + + + {formatarValor( + parseFloat(parcela.valorCorrigido) - + parseFloat(parcela.valorOriginal), + )} + + + {formatarValor(parcela.valorJuros)} + + + {formatarValor(parcela.valorMulta)} + + + {formatarValor(parcela.valorTotal)} - {formatarValor(parcela.valorJuros)} - {formatarValor(parcela.valorMulta)} - {formatarValor(parcela.valorTotal)} - {parcela.pago ? 'Pago' : 'Em aberto'} + {parcela.pago ? "Pago" : "Em aberto"} - {!parcela.pago && calculo.status !== 'FINALIZADO' && ( + {!parcela.pago && calculo.status !== "FINALIZADO" && ( registrarPagamento(parcela.id)} className="text-indigo-600 hover:text-indigo-900" @@ -1378,7 +1727,9 @@ function ParcelasTab({ ) : ( - Nenhuma parcela cadastrada. + + Nenhuma parcela cadastrada. + )} @@ -1388,7 +1739,7 @@ function ParcelasTab({ function HonorariosTab({ calculo, onUpdate, - formatarValor = formatarMoeda + formatarValor = formatarMoeda, }: { calculo: Calculo; onUpdate: () => void; @@ -1397,22 +1748,26 @@ function HonorariosTab({ const { getToken } = useAuth(); const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ - tipo: 'SUCUMBENCIAL', - percentual: '10,00', + tipo: "SUCUMBENCIAL", + percentual: "10,00", valorBase: calculo.valorBase, - fundamento: '' + fundamento: "", }); - const handleChange = (e: React.ChangeEvent) => { + const handleChange = ( + e: React.ChangeEvent< + HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement + >, + ) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); }; const handlePercentualChange = (e: React.ChangeEvent) => { const { value } = e.target; // Permitir apenas dígitos, vírgula, e ponto - const numericValue = value.replace(/[^\d.,]/g, ''); - setFormData(prev => ({ ...prev, percentual: numericValue })); + const numericValue = value.replace(/[^\d.,]/g, ""); + setFormData((prev) => ({ ...prev, percentual: numericValue })); }; const handleSubmit = async (e: React.FormEvent) => { @@ -1422,28 +1777,35 @@ function HonorariosTab({ try { const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado. Faça login novamente.'); + throw new Error("Usuário não autenticado. Faça login novamente."); } // Converter percentual para decimal (ex: 10,00% -> 0.1) - const percentual = parseFloat(formData.percentual.replace(',', '.')) / 100; + const percentual = + parseFloat(formData.percentual.replace(",", ".")) / 100; // Usar o serviço api em vez de fetch direto - await api.post(`/calculos/${calculo.id}/honorarios`, { - tipo: formData.tipo, - percentual: percentual, - valorBase: parseFloat(formData.valorBase.replace(/[^\d.,]/g, '').replace(',', '.')), - fundamento: formData.fundamento - }, { - headers: { - 'Authorization': `Bearer ${token}` - } - }); + await api.post( + `/calculos/${calculo.id}/honorarios`, + { + tipo: formData.tipo, + percentual: percentual, + valorBase: parseFloat( + formData.valorBase.replace(/[^\d.,]/g, "").replace(",", "."), + ), + fundamento: formData.fundamento, + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); - toast.success('Honorários adicionados com sucesso!'); + toast.success("Honorários adicionados com sucesso!"); onUpdate(); // Recarregar dados do cálculo } catch (error) { - console.error('Erro ao adicionar honorários:', error); + console.error("Erro ao adicionar honorários:", error); toast.error(`Falha ao adicionar honorários: ${error.message}`); } finally { setLoading(false); @@ -1451,29 +1813,36 @@ function HonorariosTab({ }; const handleDelete = async (honorarioId: string) => { - if (!confirm('Deseja realmente excluir este honorário?')) return; + if (!confirm("Deseja realmente excluir este honorário?")) return; try { await api.delete(`/calculos/${calculo.id}/honorarios/${honorarioId}`); - toast.success('Honorário removido com sucesso'); + toast.success("Honorário removido com sucesso"); onUpdate(); } catch (error) { - console.error('Erro ao excluir honorário:', error); - toast.error('Falha ao excluir honorário'); + console.error("Erro ao excluir honorário:", error); + toast.error("Falha ao excluir honorário"); } }; return ( - Honorários Advocatícios + + Honorários Advocatícios + - {calculo.status !== 'FINALIZADO' && ( + {calculo.status !== "FINALIZADO" && ( - Adicionar Honorários + + Adicionar Honorários + - + Tipo - + Percentual @@ -1511,7 +1883,10 @@ function HonorariosTab({ - + Valor Base @@ -1531,7 +1906,10 @@ function HonorariosTab({ - + Fundamento Legal/Contratual - {loading ? 'Adicionando...' : 'Adicionar Honorários'} + {loading ? "Adicionando..." : "Adicionar Honorários"} @@ -1559,31 +1937,71 @@ function HonorariosTab({ )} - Honorários Aplicados + + Honorários Aplicados + {calculo.honorarios && calculo.honorarios.length > 0 ? ( - Tipo - Percentual - Valor Base - Valor Honorários - Fundamento - Ações + + Tipo + + + Percentual + + + Valor Base + + + Valor Honorários + + + Fundamento + + + Ações + {calculo.honorarios.map((honorario, index) => ( - {honorario.tipo} + + {honorario.tipo} + {(parseFloat(honorario.percentual) * 100).toFixed(2)}% - {formatarValor(honorario.valorBase)} - {formatarValor(honorario.valorHonorario)} - {honorario.fundamento || '-'} + + {formatarValor(honorario.valorBase)} + + + {formatarValor(honorario.valorHonorario)} + + + {honorario.fundamento || "-"} + ) : ( - Nenhum honorário aplicado. + + Nenhum honorário aplicado. + )} @@ -1611,7 +2031,7 @@ function HonorariosTab({ function DescontosTab({ calculo, onUpdate, - formatarValor = formatarMoeda + formatarValor = formatarMoeda, }: { calculo: Calculo; onUpdate: () => void; @@ -1620,33 +2040,37 @@ function DescontosTab({ const { getToken } = useAuth(); const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ - tipo: 'INSS', - descricao: '', - percentual: '', - valorFixo: '', + tipo: "INSS", + descricao: "", + percentual: "", + valorFixo: "", valorBase: calculo.valorBase, - usaPercentual: true + usaPercentual: true, }); - const handleChange = (e: React.ChangeEvent) => { + const handleChange = ( + e: React.ChangeEvent< + HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement + >, + ) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); }; const toggleTipoDesconto = (usaPercentual: boolean) => { - setFormData(prev => ({ + setFormData((prev) => ({ ...prev, usaPercentual, - percentual: usaPercentual ? prev.percentual : '', - valorFixo: !usaPercentual ? prev.valorFixo : '' + percentual: usaPercentual ? prev.percentual : "", + valorFixo: !usaPercentual ? prev.valorFixo : "", })); }; const handlePercentualChange = (e: React.ChangeEvent) => { const { value } = e.target; // Permitir apenas dígitos, vírgula, e ponto - const numericValue = value.replace(/[^\d.,]/g, ''); - setFormData(prev => ({ ...prev, percentual: numericValue })); + const numericValue = value.replace(/[^\d.,]/g, ""); + setFormData((prev) => ({ ...prev, percentual: numericValue })); }; const handleSubmit = async (e: React.FormEvent) => { @@ -1658,61 +2082,67 @@ function DescontosTab({ const dadosEnvio = { tipo: formData.tipo, descricao: formData.descricao, - valorBase: parseFloat(formData.valorBase.toString().replace(/\./g, '').replace(',', '.')), + valorBase: parseFloat( + formData.valorBase.toString().replace(/\./g, "").replace(",", "."), + ), }; // Adicionar percentual ou valor fixo dependendo da escolha if (formData.usaPercentual) { - dadosEnvio.percentual = parseFloat(formData.percentual.replace(/\./g, '').replace(',', '.')) / 100; + dadosEnvio.percentual = + parseFloat(formData.percentual.replace(/\./g, "").replace(",", ".")) / + 100; // Se percentual não for um número válido if (isNaN(dadosEnvio.percentual)) { - throw new Error('Percentual inválido'); + throw new Error("Percentual inválido"); } } else { - dadosEnvio.valorFixo = parseFloat(formData.valorFixo.replace(/\./g, '').replace(',', '.')); + dadosEnvio.valorFixo = parseFloat( + formData.valorFixo.replace(/\./g, "").replace(",", "."), + ); // Se valor fixo não for um número válido if (isNaN(dadosEnvio.valorFixo)) { - throw new Error('Valor fixo inválido'); + throw new Error("Valor fixo inválido"); } } // Adicionar logs para depuração - console.log('Enviando dados de desconto:', dadosEnvio); + console.log("Enviando dados de desconto:", dadosEnvio); // Fazer a requisição const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado'); + throw new Error("Usuário não autenticado"); } const response = await api.post( `/calculos/${calculo.id}/descontos`, dadosEnvio, { - headers: { 'Authorization': `Bearer ${token}` }, - timeout: 30000 // 30 segundos - } + headers: { Authorization: `Bearer ${token}` }, + timeout: 30000, // 30 segundos + }, ); - console.log('Desconto adicionado:', response.data); - toast.success('Desconto adicionado com sucesso!'); + console.log("Desconto adicionado:", response.data); + toast.success("Desconto adicionado com sucesso!"); // Resetar o formulário setFormData({ - tipo: 'INSS', - descricao: '', - percentual: '', - valorFixo: '', + tipo: "INSS", + descricao: "", + percentual: "", + valorFixo: "", valorBase: calculo.valorBase, - usaPercentual: true + usaPercentual: true, }); // Atualizar a lista onUpdate(); } catch (error) { - console.error('Erro ao adicionar desconto:', error); + console.error("Erro ao adicionar desconto:", error); - let mensagemErro = 'Erro ao adicionar desconto'; + let mensagemErro = "Erro ao adicionar desconto"; if (error instanceof Error) { mensagemErro = error.message; } @@ -1724,7 +2154,7 @@ function DescontosTab({ }; const handleDelete = async (descontoId: string) => { - if (!confirm('Deseja realmente excluir este desconto?')) { + if (!confirm("Deseja realmente excluir este desconto?")) { return; } @@ -1732,15 +2162,15 @@ function DescontosTab({ setLoading(true); const response = await api.delete( - `/calculos/${calculo.id}/descontos/${descontoId}` + `/calculos/${calculo.id}/descontos/${descontoId}`, ); - toast.success('Desconto excluído com sucesso!'); + toast.success("Desconto excluído com sucesso!"); onUpdate(); // Atualizar a lista } catch (error) { - console.error('Erro ao excluir desconto:', error); + console.error("Erro ao excluir desconto:", error); - let mensagemErro = 'Erro ao excluir desconto'; + let mensagemErro = "Erro ao excluir desconto"; if (axios.isAxiosError(error) && error.response) { // Se tiver uma mensagem de erro específica na resposta if (error.response.data && error.response.data.error) { @@ -1758,13 +2188,18 @@ function DescontosTab({ Descontos - {calculo.status !== 'FINALIZADO' && ( + {calculo.status !== "FINALIZADO" && ( - Adicionar Desconto + + Adicionar Desconto + - + Tipo - + Descrição - + Tipo de Desconto @@ -1810,7 +2251,10 @@ function DescontosTab({ onChange={() => toggleTipoDesconto(true)} className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" /> - + Percentual @@ -1823,7 +2267,10 @@ function DescontosTab({ onChange={() => toggleTipoDesconto(false)} className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" /> - + Valor Fixo @@ -1832,7 +2279,10 @@ function DescontosTab({ {formData.usaPercentual ? ( - + Percentual @@ -1852,7 +2302,10 @@ function DescontosTab({ ) : ( - + Valor Fixo @@ -1873,7 +2326,10 @@ function DescontosTab({ )} - + Valor Base @@ -1899,7 +2355,7 @@ function DescontosTab({ className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" disabled={loading} > - {loading ? 'Adicionando...' : 'Adicionar Desconto'} + {loading ? "Adicionando..." : "Adicionar Desconto"} @@ -1907,34 +2363,73 @@ function DescontosTab({ )} - Descontos Aplicados + + Descontos Aplicados + {calculo.descontos && calculo.descontos.length > 0 ? ( - Tipo - Descrição - Base de Cálculo - Forma - Valor Desconto - Ações + + Tipo + + + Descrição + + + Base de Cálculo + + + Forma + + + Valor Desconto + + + Ações + {calculo.descontos.map((desconto, index) => ( - {desconto.tipo} - {desconto.descricao || '-'} - {formatarValor(desconto.valorBase)} + + {desconto.tipo} + + + {desconto.descricao || "-"} + + + {formatarValor(desconto.valorBase)} + {desconto.percentual ? `${(parseFloat(desconto.percentual) * 100).toFixed(2)}%` - : `Valor Fixo: ${formatarValor(desconto.valorFixo)}` - } + : `Valor Fixo: ${formatarValor(desconto.valorFixo)}`} + + + {formatarValor(desconto.valorDesconto)} - {formatarValor(desconto.valorDesconto)} ) : ( - Nenhum desconto aplicado. + + Nenhum desconto aplicado. + )} @@ -1961,17 +2458,21 @@ function DescontosTab({ function HistoricoTab({ calculo, - formatarValor + formatarValor, }: { calculo: Calculo; formatarValor: (valor: string) => string; }) { return ( - Histórico de Alterações + + Histórico de Alterações + {calculo.historico.length === 0 ? ( - Nenhum registro de alteração encontrado. + + Nenhum registro de alteração encontrado. + ) : ( @@ -1983,17 +2484,25 @@ function HistoricoTab({ {item.descricao} - {format(new Date(item.criadoEm), 'dd/MM/yyyy HH:mm:ss', { locale: ptBR })} - {item.usuario ? ` por ${item.usuario.nome}` : ''} + {format(new Date(item.criadoEm), "dd/MM/yyyy HH:mm:ss", { + locale: ptBR, + })} + {item.usuario ? ` por ${item.usuario.nome}` : ""} {(item.valorAnterior || item.valorNovo) && ( {item.valorAnterior && ( - Valor anterior: {formatarValor(item.valorAnterior)} + + Valor anterior: {formatarValor(item.valorAnterior)} + + )} + {item.valorAnterior && item.valorNovo && ( + → )} - {item.valorAnterior && item.valorNovo && →} {item.valorNovo && ( - Novo valor: {formatarValor(item.valorNovo)} + + Novo valor: {formatarValor(item.valorNovo)} + )} )} diff --git a/src/components/calculos/NovoCalculo.tsx b/src/components/calculos/NovoCalculo.tsx index a7a0c72..83ee155 100644 --- a/src/components/calculos/NovoCalculo.tsx +++ b/src/components/calculos/NovoCalculo.tsx @@ -1,8 +1,9 @@ -import { useState, useEffect, useRef } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { toast } from 'react-toastify'; -import { formatarMoeda, parseMoeda } from '../../utils/formatters'; -import { useAuth } from '../../contexts/AuthContext'; +import { useState, useEffect, useRef } from "react"; +import { useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; + +import { useAuth } from "../../contexts/AuthContext"; +import { formatarMoeda, parseMoeda } from "../../utils/formatters"; interface ProcessoOption { id: string; @@ -21,160 +22,174 @@ export function NovoCalculo() { const { getToken } = useAuth(); const valorBaseRef = useRef(null); const [formData, setFormData] = useState({ - titulo: '', - descricao: '', - dataBase: new Date().toISOString().split('T')[0], - valorBase: '', - processoId: '', - clienteId: '', + titulo: "", + descricao: "", + dataBase: new Date().toISOString().split("T")[0], + valorBase: "", + processoId: "", + clienteId: "", }); - + const [processos, setProcessos] = useState([]); const [clientes, setClientes] = useState([]); - + // Carregar processos e clientes ao montar o componente useEffect(() => { const carregarDados = async () => { try { // Carregar processos - const processosResponse = await fetch('/api/processos'); + const processosResponse = await fetch("/api/processos"); if (processosResponse.ok) { const processosData = await processosResponse.json(); setProcessos(processosData); } - + // Carregar clientes - const clientesResponse = await fetch('/api/clientes'); + const clientesResponse = await fetch("/api/clientes"); if (clientesResponse.ok) { const clientesData = await clientesResponse.json(); setClientes(clientesData); } } catch (error) { - console.error('Erro ao carregar dados:', error); - toast.error('Falha ao carregar dados auxiliares'); + console.error("Erro ao carregar dados:", error); + toast.error("Falha ao carregar dados auxiliares"); } }; - + carregarDados(); }, []); - - const handleChange = (e: React.ChangeEvent) => { + + const handleChange = ( + e: React.ChangeEvent< + HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement + >, + ) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); }; - + // Handler específico para campos monetários com controle de cursor const handleMoneyChange = (e: React.ChangeEvent) => { const { name, value } = e.target; - + // Salvar a posição atual do cursor antes da formatação const cursorPosition = e.target.selectionStart; const prevLength = e.target.value.length; - + // Remover caracteres não numéricos, exceto vírgula e ponto - const numericValue = value.replace(/[^\d.,]/g, ''); - + const numericValue = value.replace(/[^\d.,]/g, ""); + // Formatar como moeda const formattedValue = formatarMoeda(numericValue); - - setFormData(prev => ({ ...prev, [name]: formattedValue })); - + + setFormData((prev) => ({ ...prev, [name]: formattedValue })); + // Usar setTimeout para ajustar a posição do cursor após a atualização do DOM setTimeout(() => { if (valorBaseRef.current) { // Calcular a nova posição considerando a mudança no comprimento const lengthDifference = formattedValue.length - prevLength; - const newPosition = Math.max(0, (cursorPosition || 0) + lengthDifference); - + const newPosition = Math.max( + 0, + (cursorPosition || 0) + lengthDifference, + ); + // Definir o cursor na posição calculada valorBaseRef.current.setSelectionRange(newPosition, newPosition); } }, 0); }; - + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); - + try { // Validar dados if (!formData.titulo || !formData.dataBase || !formData.valorBase) { - toast.error('Preencha todos os campos obrigatórios'); + toast.error("Preencha todos os campos obrigatórios"); setLoading(false); return; } - + // Converter valor para número const valorBaseNumerico = parseMoeda(formData.valorBase); if (isNaN(valorBaseNumerico) || valorBaseNumerico <= 0) { - toast.error('Valor base inválido. Informe um valor maior que zero'); + toast.error("Valor base inválido. Informe um valor maior que zero"); setLoading(false); return; } - + // Preparar dados para envio const dadosParaEnviar = { ...formData, - valorBase: valorBaseNumerico + valorBase: valorBaseNumerico, }; - + // Obter token de autenticação usando o contexto const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado. Faça login novamente.'); + throw new Error("Usuário não autenticado. Faça login novamente."); } - + // Enviar solicitação para criar cálculo com cabeçalho de autorização - const response = await fetch('/api/calculos', { - method: 'POST', + const response = await fetch("/api/calculos", { + method: "POST", headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}` + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, }, body: JSON.stringify(dadosParaEnviar), }); - + if (!response.ok) { const errorData = await response.json(); - throw new Error(errorData.error || 'Erro ao criar cálculo'); + throw new Error(errorData.error || "Erro ao criar cálculo"); } - + const resultado = await response.json(); - - toast.success('Cálculo criado com sucesso!'); - + + toast.success("Cálculo criado com sucesso!"); + // Redirecionar para a página do cálculo criado navigate(`/calculos/${resultado.id}`); } catch (error) { - console.error('Erro ao criar cálculo:', error); + console.error("Erro ao criar cálculo:", error); toast.error(`Falha ao criar cálculo: ${error.message}`); - - if (error.message?.includes('autenticação') || - error.message?.includes('não autenticado') || - error.response?.status === 401) { + + if ( + error.message?.includes("autenticação") || + error.message?.includes("não autenticado") || + error.response?.status === 401 + ) { // Se for erro de autenticação, redirecionar para login - navigate('/login'); + navigate("/login"); } } finally { setLoading(false); } }; - + return ( Novo Cálculo Judicial - Preencha os dados básicos para criar um novo cálculo + + Preencha os dados básicos para criar um novo cálculo + - + {/* Título */} - + Título * - - + {/* Data Base */} - + Data Base * - - Data de referência para iniciar o cálculo + + Data de referência para iniciar o cálculo + - + {/* Valor Base */} - + Valor Base * R$ - - Valor inicial para o cálculo + + Valor inicial para o cálculo + - + {/* Processo */} - + Processo Selecione um processo (opcional) - {processos.map(processo => ( + {processos.map((processo) => ( {processo.numero} - {processo.titulo} ))} - + {/* Cliente */} - + Cliente Selecione um cliente (opcional) - {clientes.map(cliente => ( + {clientes.map((cliente) => ( {cliente.nome} ))} - + {/* Descrição */} - + Descrição - + navigate('/calculos')} + onClick={() => navigate("/calculos")} disabled={loading} className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" > @@ -301,12 +335,28 @@ export function NovoCalculo() { className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 flex items-center" > {loading && ( - - - + + + )} - {loading ? 'Criando...' : 'Criar Cálculo'} + {loading ? "Criando..." : "Criar Cálculo"} diff --git a/src/components/calculos/NovoPensaoAlimenticia.tsx b/src/components/calculos/NovoPensaoAlimenticia.tsx index f8b90bc..a734325 100644 --- a/src/components/calculos/NovoPensaoAlimenticia.tsx +++ b/src/components/calculos/NovoPensaoAlimenticia.tsx @@ -1,7 +1,8 @@ -import { useState, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { toast } from 'react-toastify'; -import { formatarPercentual, parsePercentual } from '../../utils/formatters'; +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; + +import { formatarPercentual, parsePercentual } from "../../utils/formatters"; interface ProcessoOption { id: string; @@ -18,134 +19,155 @@ export function NovoPensaoAlimenticia() { const navigate = useNavigate(); const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ - titulo: '', - descricao: '', - dataInicial: new Date().toISOString().split('T')[0], - dataFinal: '', - percentualSalarioMinimo: '', - multaPactuada: '', - tipoAtualizacao: 'INPC', - processoId: '', - clienteId: '', - observacoes: '', + titulo: "", + descricao: "", + dataInicial: new Date().toISOString().split("T")[0], + dataFinal: "", + percentualSalarioMinimo: "", + multaPactuada: "", + tipoAtualizacao: "INPC", + processoId: "", + clienteId: "", + observacoes: "", }); - + const [processos, setProcessos] = useState([]); const [clientes, setClientes] = useState([]); - + // Carregar processos e clientes ao montar o componente useEffect(() => { const carregarDados = async () => { try { // Carregar processos - const processosResponse = await fetch('/api/processos'); + const processosResponse = await fetch("/api/processos"); if (processosResponse.ok) { const processosData = await processosResponse.json(); setProcessos(processosData); } - + // Carregar clientes - const clientesResponse = await fetch('/api/clientes'); + const clientesResponse = await fetch("/api/clientes"); if (clientesResponse.ok) { const clientesData = await clientesResponse.json(); setClientes(clientesData); } } catch (error) { - console.error('Erro ao carregar dados:', error); - toast.error('Falha ao carregar dados auxiliares'); + console.error("Erro ao carregar dados:", error); + toast.error("Falha ao carregar dados auxiliares"); } }; - + carregarDados(); }, []); - - const handleChange = (e: React.ChangeEvent) => { + + const handleChange = ( + e: React.ChangeEvent< + HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement + >, + ) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); }; - + // Handler para campos percentuais const handlePercentChange = (e: React.ChangeEvent) => { const { name, value } = e.target; // Remover caracteres não numéricos, exceto vírgula e ponto - const numericValue = value.replace(/[^\d.,]/g, ''); + const numericValue = value.replace(/[^\d.,]/g, ""); // Formatar como percentual const formattedValue = formatarPercentual(numericValue); - setFormData(prev => ({ ...prev, [name]: formattedValue })); + setFormData((prev) => ({ ...prev, [name]: formattedValue })); }; - + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); - + try { // Validar dados - if (!formData.titulo || !formData.dataInicial || !formData.percentualSalarioMinimo) { - toast.error('Preencha todos os campos obrigatórios'); + if ( + !formData.titulo || + !formData.dataInicial || + !formData.percentualSalarioMinimo + ) { + toast.error("Preencha todos os campos obrigatórios"); setLoading(false); return; } - + // Converter percentual para decimal (formato 0.XX) const percentual = parsePercentual(formData.percentualSalarioMinimo); - + // Verificar se percentual é válido if (isNaN(percentual) || percentual <= 0 || percentual > 1) { - toast.error('Percentual do salário mínimo deve ser maior que 0 e menor ou igual a 100%'); + toast.error( + "Percentual do salário mínimo deve ser maior que 0 e menor ou igual a 100%", + ); setLoading(false); return; } - + // Preparar dados para envio const dadosParaEnviar = { ...formData, percentualSalarioMinimo: percentual, - multaPactuada: formData.multaPactuada ? parsePercentual(formData.multaPactuada) : undefined, + multaPactuada: formData.multaPactuada + ? parsePercentual(formData.multaPactuada) + : undefined, }; - + // Enviar solicitação para criar cálculo - const response = await fetch('/api/pensao-alimenticia', { - method: 'POST', + const response = await fetch("/api/pensao-alimenticia", { + method: "POST", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, body: JSON.stringify(dadosParaEnviar), }); - + if (!response.ok) { const errorData = await response.json(); - throw new Error(errorData.error || 'Erro ao criar cálculo de pensão alimentícia'); + throw new Error( + errorData.error || "Erro ao criar cálculo de pensão alimentícia", + ); } - + const resultado = await response.json(); - - toast.success('Cálculo de pensão alimentícia criado com sucesso!'); - + + toast.success("Cálculo de pensão alimentícia criado com sucesso!"); + // Redirecionar para a página do cálculo criado navigate(`/pensao-alimenticia/${resultado.id}`); } catch (error) { - console.error('Erro ao criar cálculo de pensão:', error); + console.error("Erro ao criar cálculo de pensão:", error); toast.error(`Falha ao criar cálculo: ${error.message}`); } finally { setLoading(false); } }; - + return ( - Novo Cálculo de Pensão Alimentícia - Preencha os dados para criar um novo cálculo de pensão alimentícia + + Novo Cálculo de Pensão Alimentícia + + + Preencha os dados para criar um novo cálculo de pensão alimentícia + - + {/* Título */} - + Título * - - + {/* Índice de Atualização */} - + Índice de Atualização Monetária - INPC (Índice Nacional de Preços ao Consumidor) - IPCA (Índice Nacional de Preços ao Consumidor Amplo) - IGP-M (Índice Geral de Preços do Mercado) - SELIC (Sistema Especial de Liquidação e Custódia) + + INPC (Índice Nacional de Preços ao Consumidor) + + + IPCA (Índice Nacional de Preços ao Consumidor Amplo) + + + IGP-M (Índice Geral de Preços do Mercado) + + + SELIC (Sistema Especial de Liquidação e Custódia) + - + {/* Percentual do Salário Mínimo */} - - Percentual do Salário Mínimo * + + Percentual do Salário Mínimo{" "} + * - Ex: 30% = 30,00% - + {/* Data Inicial */} - + Data Inicial * - - Data de início da pensão + + Data de início da pensão + - + {/* Data Final */} - + Data Final - - Deixe em branco se a pensão não tem data final + + Deixe em branco se a pensão não tem data final + - + {/* Multa Pactuada */} - + Multa Pactuada por Atraso - % - Ex: 10% = 10,00%. Deixe em branco se não houver. + + Ex: 10% = 10,00%. Deixe em branco se não houver. + - + {/* Processo */} - + Processo Selecione um processo (opcional) - {processos.map(processo => ( + {processos.map((processo) => ( {processo.numero} - {processo.titulo} ))} - + {/* Cliente */} - + Cliente Selecione um cliente (opcional) - {clientes.map(cliente => ( + {clientes.map((cliente) => ( {cliente.nome} ))} - + {/* Observações */} - + Observações - + navigate('/calculos')} + onClick={() => navigate("/calculos")} disabled={loading} className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" > @@ -328,12 +389,28 @@ export function NovoPensaoAlimenticia() { className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 flex items-center" > {loading && ( - - - + + + )} - {loading ? 'Criando...' : 'Criar Cálculo de Pensão'} + {loading ? "Criando..." : "Criar Cálculo de Pensão"} diff --git a/src/components/clientes/ClienteDetalhes.tsx b/src/components/clientes/ClienteDetalhes.tsx index e3413c2..5df599b 100644 --- a/src/components/clientes/ClienteDetalhes.tsx +++ b/src/components/clientes/ClienteDetalhes.tsx @@ -1,6 +1,3 @@ -import React, { useState } from "react"; -import { useParams, useNavigate } from "react-router-dom"; -import { useClientes } from "../../hooks/useClientes"; import { Tab } from "@headlessui/react"; import { ArrowLeftIcon, @@ -11,7 +8,10 @@ import { UserGroupIcon, DocumentTextIcon, } from "@heroicons/react/24/outline"; -import { ClienteModal } from "./ClienteModal"; +import React, { useState } from "react"; +import { useParams, useNavigate } from "react-router-dom"; + +import { useClientes } from "../../hooks/useClientes"; import { formatCPF, formatTelefone, @@ -22,6 +22,8 @@ import { formatData, } from "../../utils/formatters"; +import { ClienteModal } from "./ClienteModal"; + function classNames(...classes: string[]) { return classes.filter(Boolean).join(" "); } diff --git a/src/components/clientes/ClienteModal.tsx b/src/components/clientes/ClienteModal.tsx index e4c0c7e..0cd3d7d 100644 --- a/src/components/clientes/ClienteModal.tsx +++ b/src/components/clientes/ClienteModal.tsx @@ -1,3 +1,6 @@ +import { Dialog, Transition } from "@headlessui/react"; +import { XMarkIcon } from "@heroicons/react/24/outline"; +import axios from "axios"; import React, { useState, useEffect, @@ -5,12 +8,10 @@ import React, { forwardRef, useRef, } from "react"; -import { Dialog, Transition } from "@headlessui/react"; -import { XMarkIcon } from "@heroicons/react/24/outline"; import { useForm } from "react-hook-form"; -import axios from "axios"; -import { Cliente } from "../../types/entities"; + import { useClientes } from "../../hooks/useClientes"; +import { Cliente } from "../../types/entities"; import { ORGAOS_EMISSORES, PREVIDENCIA_OPTIONS } from "../../utils/constants"; const estadosCivis = [ @@ -201,73 +202,77 @@ export const ClienteModal: React.FC = ({ const cepValue = watch("cep"); // Função para buscar dados do CEP - versão corrigida - const fetchAddressByCep = useCallback(async (cep: string) => { - if (!cep || cep.length !== 8) return; + const fetchAddressByCep = useCallback( + async (cep: string) => { + if (!cep || cep.length !== 8) return; - try { - setIsLoadingCep(true); - const formattedCep = cep.replace(/\D/g, ""); // Remove caracteres não numéricos - const response = await axios.get( - `https://viacep.com.br/ws/${formattedCep}/json/`, - ); + try { + setIsLoadingCep(true); + const formattedCep = cep.replace(/\D/g, ""); // Remove caracteres não numéricos + const response = await axios.get( + `https://viacep.com.br/ws/${formattedCep}/json/`, + ); - if (response.data && !response.data.erro) { - // Usar estado local para garantir re-renderização imediata - const endereco = response.data.logradouro || ""; - const bairro = response.data.bairro || ""; - const cidade = response.data.localidade || ""; - const estado = response.data.uf || ""; + if (response.data && !response.data.erro) { + // Usar estado local para garantir re-renderização imediata + const endereco = response.data.logradouro || ""; + const bairro = response.data.bairro || ""; + const cidade = response.data.localidade || ""; + const estado = response.data.uf || ""; - // Atualizar valores no formulário - setValue("endereco", endereco, { shouldValidate: true }); - setValue("bairro", bairro, { shouldValidate: true }); - setValue("cidade", cidade, { shouldValidate: true }); - setValue("estado", estado, { shouldValidate: true }); + // Atualizar valores no formulário + setValue("endereco", endereco, { shouldValidate: true }); + setValue("bairro", bairro, { shouldValidate: true }); + setValue("cidade", cidade, { shouldValidate: true }); + setValue("estado", estado, { shouldValidate: true }); - // Atualizar o estado para re-renderização do componente - setEnderecoState({ - endereco, - bairro, - cidade, - estado, - preenchido: true, - }); + // Atualizar o estado para re-renderização do componente + setEnderecoState({ + endereco, + bairro, + cidade, + estado, + preenchido: true, + }); - // Focar no próximo campo apenas se o usuário não estiver digitando ativamente - // Esta alteração evita interromper o fluxo natural de preenchimento - const activeElement = document.activeElement; - const isUserTyping = - activeElement && - (activeElement.tagName === 'INPUT' || activeElement.tagName === 'SELECT') && - activeElement !== document.querySelector('input[name="cep"]'); - - // Só focar no campo número se o usuário não estiver digitando em outro campo - if (!isUserTyping) { - setTimeout(() => { - const numeroInput = document.querySelector( - 'input[name="numero"]' - ) as HTMLInputElement; - if (numeroInput) numeroInput.focus(); - }, 100); + // Focar no próximo campo apenas se o usuário não estiver digitando ativamente + // Esta alteração evita interromper o fluxo natural de preenchimento + const activeElement = document.activeElement; + const isUserTyping = + activeElement && + (activeElement.tagName === "INPUT" || + activeElement.tagName === "SELECT") && + activeElement !== document.querySelector('input[name="cep"]'); + + // Só focar no campo número se o usuário não estiver digitando em outro campo + if (!isUserTyping) { + setTimeout(() => { + const numeroInput = document.querySelector( + 'input[name="numero"]', + ) as HTMLInputElement; + if (numeroInput) numeroInput.focus(); + }, 100); + } + } else { + console.error("CEP não encontrado"); + alert("CEP não encontrado. Por favor, verifique o CEP informado."); } - } else { - console.error("CEP não encontrado"); - alert("CEP não encontrado. Por favor, verifique o CEP informado."); + } catch (error) { + console.error("Erro ao buscar CEP:", error); + alert("Erro ao buscar informações do CEP. Por favor, tente novamente."); + } finally { + setIsLoadingCep(false); } - } catch (error) { - console.error("Erro ao buscar CEP:", error); - alert("Erro ao buscar informações do CEP. Por favor, tente novamente."); - } finally { - setIsLoadingCep(false); - } - }, [setValue]); + }, + [setValue], + ); // Efeito para monitorar mudanças no CEP com debounce otimizado const [autoFetchEnabled, setAutoFetchEnabled] = useState(true); useEffect(() => { if (!autoFetchEnabled) return; - + // Limpar qualquer timer existente const timer = setTimeout(() => { const cleanCep = cepValue?.replace(/\D/g, ""); @@ -584,7 +589,10 @@ export const ClienteModal: React.FC = ({ }} onBlur={(e) => { // Forçar busca ao sair do campo - const cleanCep = e.target.value.replace(/\D/g, ""); + const cleanCep = e.target.value.replace( + /\D/g, + "", + ); if (cleanCep && cleanCep.length === 8) { fetchAddressByCep(cleanCep); } @@ -622,10 +630,17 @@ export const ClienteModal: React.FC = ({ required: "Endereço é obrigatório", })} error={errors.endereco?.message} - value={enderecoState.preenchido ? enderecoState.endereco : undefined} + value={ + enderecoState.preenchido + ? enderecoState.endereco + : undefined + } onChange={(e) => { setValue("endereco", e.target.value); - setEnderecoState(prev => ({...prev, endereco: e.target.value})); + setEnderecoState((prev) => ({ + ...prev, + endereco: e.target.value, + })); }} /> = ({ required: "Bairro é obrigatório", })} error={errors.bairro?.message} - value={enderecoState.preenchido ? enderecoState.bairro : undefined} + value={ + enderecoState.preenchido + ? enderecoState.bairro + : undefined + } onChange={(e) => { setValue("bairro", e.target.value); - setEnderecoState(prev => ({...prev, bairro: e.target.value})); + setEnderecoState((prev) => ({ + ...prev, + bairro: e.target.value, + })); }} /> = ({ required: "Cidade é obrigatória", })} error={errors.cidade?.message} - value={enderecoState.preenchido ? enderecoState.cidade : undefined} + value={ + enderecoState.preenchido + ? enderecoState.cidade + : undefined + } onChange={(e) => { setValue("cidade", e.target.value); - setEnderecoState(prev => ({...prev, cidade: e.target.value})); + setEnderecoState((prev) => ({ + ...prev, + cidade: e.target.value, + })); }} /> = ({ })} options={estadosOptions} error={errors.estado?.message} - value={enderecoState.preenchido ? enderecoState.estado : undefined} + value={ + enderecoState.preenchido + ? enderecoState.estado + : undefined + } onChange={(e) => { setValue("estado", e.target.value); - setEnderecoState(prev => ({...prev, estado: e.target.value})); + setEnderecoState((prev) => ({ + ...prev, + estado: e.target.value, + })); }} /> diff --git a/src/components/clientes/ClientesList.tsx b/src/components/clientes/ClientesList.tsx index b6969cc..42e45d2 100644 --- a/src/components/clientes/ClientesList.tsx +++ b/src/components/clientes/ClientesList.tsx @@ -1,19 +1,23 @@ -import React, { useState, useEffect } from 'react'; -import { useQuery } from 'react-query'; -import { useNavigate } from 'react-router-dom'; -import { PencilIcon } from '@heroicons/react/24/outline'; -import api from '../../services/api'; -import { formatCPF, formatTelefone } from '../../utils/formatters'; +import { PencilIcon } from "@heroicons/react/24/outline"; +import React, { useState, useEffect } from "react"; +import { useQuery } from "react-query"; +import { useNavigate } from "react-router-dom"; + +import api from "../../services/api"; +import { formatCPF, formatTelefone } from "../../utils/formatters"; interface ClientesListProps { searchTerm: string; onEdit: (cliente: any) => void; } -export const ClientesList: React.FC = ({ searchTerm, onEdit }) => { +export const ClientesList: React.FC = ({ + searchTerm, + onEdit, +}) => { const navigate = useNavigate(); const [debouncedTerm, setDebouncedTerm] = useState(searchTerm); - + // Implementar debounce para a busca useEffect(() => { const timer = setTimeout(() => { @@ -22,39 +26,40 @@ export const ClientesList: React.FC = ({ searchTerm, onEdit } return () => clearTimeout(timer); }, [searchTerm]); - + const { data: clientes = [], isLoading } = useQuery( - ['clientes', debouncedTerm], + ["clientes", debouncedTerm], async () => { - const response = await api.get('/clientes', { + const response = await api.get("/clientes", { params: { - search: debouncedTerm.toUpperCase() - } + search: debouncedTerm.toUpperCase(), + }, }); - + // Filtrar localmente para melhor resposta let filteredClientes = response.data; if (debouncedTerm) { - const searchTerms = debouncedTerm.toUpperCase().split(' '); - filteredClientes = response.data.filter((cliente: any) => - searchTerms.every(term => - cliente.nome.includes(term) || - cliente.cpfCnpj.includes(term) || - cliente.email?.includes(term.toLowerCase()) - ) + const searchTerms = debouncedTerm.toUpperCase().split(" "); + filteredClientes = response.data.filter((cliente: any) => + searchTerms.every( + (term) => + cliente.nome.includes(term) || + cliente.cpfCnpj.includes(term) || + cliente.email?.includes(term.toLowerCase()), + ), ); } - + // Ordenar por nome - return filteredClientes.sort((a: any, b: any) => - a.nome.localeCompare(b.nome) + return filteredClientes.sort((a: any, b: any) => + a.nome.localeCompare(b.nome), ); }, { staleTime: 1000 * 60, // 1 minuto cacheTime: 1000 * 60 * 5, // 5 minutos - keepPreviousData: true // Manter dados anteriores enquanto carrega novos - } + keepPreviousData: true, // Manter dados anteriores enquanto carrega novos + }, ); if (isLoading) { @@ -100,7 +105,7 @@ export const ClientesList: React.FC = ({ searchTerm, onEdit } {clientes.map((cliente: any) => ( - navigate(`/clientes/${cliente.id}`)} diff --git a/src/components/clientes/ClientesPage.tsx b/src/components/clientes/ClientesPage.tsx index 4c93046..ef07299 100644 --- a/src/components/clientes/ClientesPage.tsx +++ b/src/components/clientes/ClientesPage.tsx @@ -1,9 +1,11 @@ -import React, { useState } from "react"; import { PlusIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; -import { ClientesList } from "./ClientesList.tsx"; -import { ClienteModal } from "./ClienteModal.tsx"; +import React, { useState } from "react"; + import { Cliente } from "../../types/entities"; +import { ClienteModal } from "./ClienteModal.tsx"; +import { ClientesList } from "./ClientesList.tsx"; + export const ClientesPage: React.FC = () => { const [isModalOpen, setIsModalOpen] = useState(false); const [searchTerm, setSearchTerm] = useState(""); diff --git a/src/components/dashboard/AlertasPrazos.tsx b/src/components/dashboard/AlertasPrazos.tsx index b2a4174..7baebfb 100644 --- a/src/components/dashboard/AlertasPrazos.tsx +++ b/src/components/dashboard/AlertasPrazos.tsx @@ -1,20 +1,22 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { format, differenceInDays } from 'date-fns'; -import { ptBR } from 'date-fns/locale'; -import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'; -import api from '../../services/api'; +import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; +import { format, differenceInDays, ptBR } from "date-fns"; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../services/api"; export const AlertasPrazos: React.FC = () => { - const { data: prazos = [] } = useQuery('prazosFuturos', async () => { - const response = await api.get('/dashboard/prazos/proximos'); + const { data: prazos = [] } = useQuery("prazosFuturos", async () => { + const response = await api.get("/dashboard/prazos/proximos"); return response.data; }); const getPrioridadeStyle = (dias: number) => { - if (dias <= 3) return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'; - if (dias <= 7) return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200'; - return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200'; + if (dias <= 3) + return "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"; + if (dias <= 7) + return "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200"; + return "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200"; }; return ( @@ -38,8 +40,12 @@ export const AlertasPrazos: React.FC = () => { Processo: {prazo.processo.numero} - - {format(new Date(prazo.dataLimite), 'dd/MM/yyyy', { locale: ptBR })} + + {format(new Date(prazo.dataLimite), "dd/MM/yyyy", { + locale: ptBR, + })} ); diff --git a/src/components/dashboard/AtividadesRecentes.tsx b/src/components/dashboard/AtividadesRecentes.tsx index 40dc627..0ecc5b8 100644 --- a/src/components/dashboard/AtividadesRecentes.tsx +++ b/src/components/dashboard/AtividadesRecentes.tsx @@ -1,22 +1,24 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { format } from 'date-fns'; -import { ptBR } from 'date-fns/locale'; -import api from '../../services/api'; +import { format, ptBR } from "date-fns"; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../services/api"; export const AtividadesRecentes: React.FC = () => { - const { data: atividades = [] } = useQuery('atividades', async () => { - const response = await api.get('/dashboard/atividades'); + const { data: atividades = [] } = useQuery("atividades", async () => { + const response = await api.get("/dashboard/atividades"); return response.data; }); const formatData = (data: string | Date | null) => { - if (!data) return ''; + if (!data) return ""; try { - return format(new Date(data), "dd 'de' MMMM 'às' HH:mm", { locale: ptBR }); + return format(new Date(data), "dd 'de' MMMM 'às' HH:mm", { + locale: ptBR, + }); } catch (error) { - console.error('Erro ao formatar data:', error); - return 'Data inválida'; + console.error("Erro ao formatar data:", error); + return "Data inválida"; } }; @@ -28,7 +30,9 @@ export const AtividadesRecentes: React.FC = () => { {atividades.map((atividade: any) => ( - {atividade.tipo === 'PROCESSO' ? '⚖️' : '📋'} + + {atividade.tipo === "PROCESSO" ? "⚖️" : "📋"} + {atividade.descricao} diff --git a/src/components/dashboard/Card.tsx b/src/components/dashboard/Card.tsx index 1a27dfb..b2a7afd 100644 --- a/src/components/dashboard/Card.tsx +++ b/src/components/dashboard/Card.tsx @@ -1,8 +1,5 @@ -import React from 'react'; -import { - ArrowUpIcon, - ArrowDownIcon, -} from '@heroicons/react/24/outline'; +import { ArrowUpIcon, ArrowDownIcon } from "@heroicons/react/24/outline"; +import React from "react"; interface CardProps { title: string; @@ -10,9 +7,9 @@ interface CardProps { trend?: number; trendLabel?: string; icon?: string; - color?: 'blue' | 'green' | 'red' | 'yellow' | 'purple'; + color?: "blue" | "green" | "red" | "yellow" | "purple"; items?: Array<{ label: string; value: string | number }>; - type?: 'default' | 'list'; + type?: "default" | "list"; } export const Card: React.FC = ({ @@ -21,27 +18,27 @@ export const Card: React.FC = ({ trend, trendLabel, icon, - color = 'blue', + color = "blue", items, - type = 'default' + type = "default", }) => { const colorClasses = { - blue: 'text-blue-600', - green: 'text-green-600', - red: 'text-red-600', - yellow: 'text-yellow-600', - purple: 'text-purple-600' + blue: "text-blue-600", + green: "text-green-600", + red: "text-red-600", + yellow: "text-yellow-600", + purple: "text-purple-600", }; const bgColorClasses = { - blue: 'bg-blue-50', - green: 'bg-green-50', - red: 'bg-red-50', - yellow: 'bg-yellow-50', - purple: 'bg-purple-50' + blue: "bg-blue-50", + green: "bg-green-50", + red: "bg-red-50", + yellow: "bg-yellow-50", + purple: "bg-purple-50", }; - if (type === 'list' && items) { + if (type === "list" && items) { return ( {title} @@ -49,7 +46,9 @@ export const Card: React.FC = ({ {items.map((item, index) => ( {item.label} - {item.value} + + {item.value} + ))} @@ -72,7 +71,9 @@ export const Card: React.FC = ({ {value} {trend !== undefined && ( - = 0 ? 'text-green-600' : 'text-red-600'}`}> + = 0 ? "text-green-600" : "text-red-600"}`} + > {trend >= 0 ? ( ) : ( @@ -80,9 +81,7 @@ export const Card: React.FC = ({ )} {Math.abs(trend)}% {trendLabel && ( - - {trendLabel} - + {trendLabel} )} )} diff --git a/src/components/dashboard/DashboardLayout.tsx b/src/components/dashboard/DashboardLayout.tsx index 4920c40..97bfaa8 100644 --- a/src/components/dashboard/DashboardLayout.tsx +++ b/src/components/dashboard/DashboardLayout.tsx @@ -1,14 +1,16 @@ -import React from 'react'; -import { useAuth } from '../../contexts/AuthContext'; -import { useDashboardStats } from '../../hooks/useDashboardStats'; -import { ProcessosStats } from './stats/ProcessosStats'; -import { TarefasStats } from './stats/TarefasStats'; -import { AtendimentosStats } from './stats/AtendimentosStats'; -import { FinanceiroStats } from './stats/FinanceiroStats'; -import { AlertasPrazos } from './AlertasPrazos'; -import { AtividadesRecentes } from './AtividadesRecentes'; -import { TaxaSucessoCard } from './TaxaSucessoCard'; -import { KPIsFinanceiros } from './stats/KPIsFinanceiros'; +import React from "react"; + +import { useAuth } from "../../contexts/AuthContext"; +import { useDashboardStats } from "../../hooks/useDashboardStats"; + +import { AlertasPrazos } from "./AlertasPrazos"; +import { AtividadesRecentes } from "./AtividadesRecentes"; +import { AtendimentosStats } from "./stats/AtendimentosStats"; +import { FinanceiroStats } from "./stats/FinanceiroStats"; +import { KPIsFinanceiros } from "./stats/KPIsFinanceiros"; +import { ProcessosStats } from "./stats/ProcessosStats"; +import { TarefasStats } from "./stats/TarefasStats"; +import { TaxaSucessoCard } from "./TaxaSucessoCard"; export const DashboardLayout: React.FC = () => { const { user } = useAuth(); @@ -18,10 +20,12 @@ export const DashboardLayout: React.FC = () => { atendimentosStats, financeiroStats, proximosPrazos, - atividades + atividades, } = useDashboardStats(); - const isAdminOrAdvogado = ['ADVOGADO_ADMIN', 'ADVOGADO'].includes(user?.perfil || ''); + const isAdminOrAdvogado = ["ADVOGADO_ADMIN", "ADVOGADO"].includes( + user?.perfil || "", + ); return ( @@ -29,7 +33,9 @@ export const DashboardLayout: React.FC = () => { {processosStats.data && } {tarefasStats.data && } - {atendimentosStats.data && } + {atendimentosStats.data && ( + + )} {isAdminOrAdvogado && financeiroStats.data && ( )} diff --git a/src/components/dashboard/KPIsAdvogados.tsx b/src/components/dashboard/KPIsAdvogados.tsx index 06c6cf8..2ded626 100644 --- a/src/components/dashboard/KPIsAdvogados.tsx +++ b/src/components/dashboard/KPIsAdvogados.tsx @@ -1,10 +1,11 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import api from '../../services/api'; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../services/api"; export const KPIsAdvogados: React.FC = () => { - const { data: kpis } = useQuery('advogadosKpis', async () => { - const response = await api.get('/dashboard/advogados/kpis'); + const { data: kpis } = useQuery("advogadosKpis", async () => { + const response = await api.get("/dashboard/advogados/kpis"); return response.data; }); @@ -19,16 +20,28 @@ export const KPIsAdvogados: React.FC = () => { - + Advogado - + Tarefas Concluídas - + Processos Concluídos - + Produtividade diff --git a/src/components/dashboard/TaxaSucessoCard.tsx b/src/components/dashboard/TaxaSucessoCard.tsx index d63712d..9f4c3e1 100644 --- a/src/components/dashboard/TaxaSucessoCard.tsx +++ b/src/components/dashboard/TaxaSucessoCard.tsx @@ -1,36 +1,46 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import api from '../../services/api'; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../services/api"; export const TaxaSucessoCard: React.FC = () => { - const { data: stats } = useQuery('taxa-sucesso', async () => { - const response = await api.get('/dashboard/processos/taxa-sucesso'); + const { data: stats } = useQuery("taxa-sucesso", async () => { + const response = await api.get("/dashboard/processos/taxa-sucesso"); return response.data; }); if (!stats) return null; const estatisticas = stats || []; - const total = estatisticas.reduce((acc: number, curr: any) => acc + (curr._count || 0), 0); - + const total = estatisticas.reduce( + (acc: number, curr: any) => acc + (curr._count || 0), + 0, + ); + return ( Taxa de Sucesso - {estatisticas.find((s: any) => s.resultado === 'PROCEDENTE')?._count - ? Math.round((estatisticas.find((s: any) => s.resultado === 'PROCEDENTE')._count / total) * 100) - : 0}% - - - Total julgados: {total} + {estatisticas.find((s: any) => s.resultado === "PROCEDENTE")?._count + ? Math.round( + (estatisticas.find((s: any) => s.resultado === "PROCEDENTE") + ._count / + total) * + 100, + ) + : 0} + % + Total julgados: {total} {estatisticas.map((item: any) => ( {item.resultado} - {Math.round((item._count / total) * 100)}% + + {Math.round((item._count / total) * 100)}% + ))} diff --git a/src/components/dashboard/charts/MapaCalor.tsx b/src/components/dashboard/charts/MapaCalor.tsx index 769d33f..a7a5dd1 100644 --- a/src/components/dashboard/charts/MapaCalor.tsx +++ b/src/components/dashboard/charts/MapaCalor.tsx @@ -1,20 +1,23 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { format } from 'date-fns'; -import { ptBR } from 'date-fns/locale'; -import api from '../../../services/api'; +import { format, ptBR } from "date-fns"; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../../services/api"; export const MapaCalor: React.FC = () => { - const { data: atividadesDiarias } = useQuery('atividadesDiarias', async () => { - const response = await api.get('/dashboard/atividades/diarias'); - return response.data; - }); + const { data: atividadesDiarias } = useQuery( + "atividadesDiarias", + async () => { + const response = await api.get("/dashboard/atividades/diarias"); + return response.data; + }, + ); const intensidadeCor = (quantidade: number) => { - if (quantidade === 0) return 'bg-gray-100 dark:bg-gray-800'; - if (quantidade <= 3) return 'bg-blue-100 dark:bg-blue-900'; - if (quantidade <= 6) return 'bg-blue-300 dark:bg-blue-700'; - return 'bg-blue-500 dark:bg-blue-500'; + if (quantidade === 0) return "bg-gray-100 dark:bg-gray-800"; + if (quantidade <= 3) return "bg-blue-100 dark:bg-blue-900"; + if (quantidade <= 6) return "bg-blue-300 dark:bg-blue-700"; + return "bg-blue-500 dark:bg-blue-500"; }; return ( diff --git a/src/components/dashboard/charts/PrevisaoReceitasChart.tsx b/src/components/dashboard/charts/PrevisaoReceitasChart.tsx index c2c24b9..d5749fe 100644 --- a/src/components/dashboard/charts/PrevisaoReceitasChart.tsx +++ b/src/components/dashboard/charts/PrevisaoReceitasChart.tsx @@ -1,19 +1,28 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; -import api from '../../../services/api'; -import { useAuth } from '../../../contexts/AuthContext'; -import { formatCurrency } from '../../../utils/formatters'; +import React from "react"; +import { useQuery } from "react-query"; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, +} from "recharts"; + +import { useAuth } from "../../../contexts/AuthContext"; +import api from "../../../services/api"; +import { formatCurrency } from "../../../utils/formatters"; export const PrevisaoReceitasChart: React.FC = () => { const { user } = useAuth(); - - if (!['ADVOGADO_ADMIN', 'ADVOGADO'].includes(user?.perfil || '')) { + + if (!["ADVOGADO_ADMIN", "ADVOGADO"].includes(user?.perfil || "")) { return null; } - const { data: previsao } = useQuery('previsaoReceitas', async () => { - const response = await api.get('/dashboard/financeiro/previsao'); + const { data: previsao } = useQuery("previsaoReceitas", async () => { + const response = await api.get("/dashboard/financeiro/previsao"); return response.data; }); diff --git a/src/components/dashboard/charts/ProcessosCharts.tsx b/src/components/dashboard/charts/ProcessosCharts.tsx index bc3da03..db7e97f 100644 --- a/src/components/dashboard/charts/ProcessosCharts.tsx +++ b/src/components/dashboard/charts/ProcessosCharts.tsx @@ -1,12 +1,15 @@ -import React from 'react'; -import { useAuth } from '../../../contexts/AuthContext'; -import { ProcessosEvolucao } from './ProcessosEvolucao'; -import { TaxaSucessoChart } from './TaxaSucessoChart'; -import { PrevisaoReceitasChart } from './PrevisaoReceitasChart'; +import React from "react"; + +import { useAuth } from "../../../contexts/AuthContext"; + +import { PrevisaoReceitasChart } from "./PrevisaoReceitasChart"; +import { ProcessosEvolucao } from "./ProcessosEvolucao"; +import { TaxaSucessoChart } from "./TaxaSucessoChart"; export const ProcessosCharts: React.FC = () => { const { user } = useAuth(); - const isAdminOrAdvogado = user?.perfil === 'ADVOGADO_ADMIN' || user?.perfil === 'ADVOGADO'; + const isAdminOrAdvogado = + user?.perfil === "ADVOGADO_ADMIN" || user?.perfil === "ADVOGADO"; return ( <> diff --git a/src/components/dashboard/charts/ProcessosEvolucao.tsx b/src/components/dashboard/charts/ProcessosEvolucao.tsx index 936ac4f..ae36ee1 100644 --- a/src/components/dashboard/charts/ProcessosEvolucao.tsx +++ b/src/components/dashboard/charts/ProcessosEvolucao.tsx @@ -1,11 +1,20 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; -import api from '../../../services/api'; +import React from "react"; +import { useQuery } from "react-query"; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, +} from "recharts"; + +import api from "../../../services/api"; export const ProcessosEvolucao: React.FC = () => { - const { data: evolucao } = useQuery('processosEvolucao', async () => { - const response = await api.get('/dashboard/processos/evolucao'); + const { data: evolucao } = useQuery("processosEvolucao", async () => { + const response = await api.get("/dashboard/processos/evolucao"); return response.data; }); @@ -21,8 +30,18 @@ export const ProcessosEvolucao: React.FC = () => { - - + + diff --git a/src/components/dashboard/charts/TaxaSucessoChart.tsx b/src/components/dashboard/charts/TaxaSucessoChart.tsx index 6691708..9f8560b 100644 --- a/src/components/dashboard/charts/TaxaSucessoChart.tsx +++ b/src/components/dashboard/charts/TaxaSucessoChart.tsx @@ -1,15 +1,23 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts'; -import api from '../../../services/api'; +import React from "react"; +import { useQuery } from "react-query"; +import { + PieChart, + Pie, + Cell, + ResponsiveContainer, + Legend, + Tooltip, +} from "recharts"; + +import api from "../../../services/api"; export const TaxaSucessoChart: React.FC = () => { - const { data: stats } = useQuery('taxaSucesso', async () => { - const response = await api.get('/dashboard/processos/taxa-sucesso'); + const { data: stats } = useQuery("taxaSucesso", async () => { + const response = await api.get("/dashboard/processos/taxa-sucesso"); return response.data; }); - const COLORS = ['#10B981', '#EF4444', '#F59E0B']; + const COLORS = ["#10B981", "#EF4444", "#F59E0B"]; return ( @@ -29,7 +37,10 @@ export const TaxaSucessoChart: React.FC = () => { label > {stats?.map((entry: any, index: number) => ( - + ))} diff --git a/src/components/dashboard/stats/AtendimentosStats.tsx b/src/components/dashboard/stats/AtendimentosStats.tsx index eb7beb8..2ea8f81 100644 --- a/src/components/dashboard/stats/AtendimentosStats.tsx +++ b/src/components/dashboard/stats/AtendimentosStats.tsx @@ -1,11 +1,12 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { Card } from '../Card'; -import api from '../../../services/api'; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../../services/api"; +import { Card } from "../Card"; export const AtendimentosStats: React.FC = () => { - const { data: stats } = useQuery('atendimentosStats', async () => { - const response = await api.get('/dashboard/atendimentos/stats'); + const { data: stats } = useQuery("atendimentosStats", async () => { + const response = await api.get("/dashboard/atendimentos/stats"); return response.data; }); diff --git a/src/components/dashboard/stats/FinanceiroStats.tsx b/src/components/dashboard/stats/FinanceiroStats.tsx index 9b37c29..2418f6a 100644 --- a/src/components/dashboard/stats/FinanceiroStats.tsx +++ b/src/components/dashboard/stats/FinanceiroStats.tsx @@ -1,12 +1,13 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { Card } from '../Card'; -import api from '../../../services/api'; -import { formatCurrency } from '../../../utils/formatters'; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../../services/api"; +import { formatCurrency } from "../../../utils/formatters"; +import { Card } from "../Card"; export const FinanceiroStats: React.FC = () => { - const { data: stats } = useQuery('financeiroStats', async () => { - const response = await api.get('/dashboard/financeiro/stats'); + const { data: stats } = useQuery("financeiroStats", async () => { + const response = await api.get("/dashboard/financeiro/stats"); return response.data; }); diff --git a/src/components/dashboard/stats/KPIsFinanceiros.tsx b/src/components/dashboard/stats/KPIsFinanceiros.tsx index ab4b0e7..08770cd 100644 --- a/src/components/dashboard/stats/KPIsFinanceiros.tsx +++ b/src/components/dashboard/stats/KPIsFinanceiros.tsx @@ -1,19 +1,20 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { useAuth } from '../../../contexts/AuthContext'; -import { Card } from '../../dashboard/Card'; -import api from '../../../services/api'; -import { formatCurrency } from '../../../utils/formatters'; +import React from "react"; +import { useQuery } from "react-query"; + +import { useAuth } from "../../../contexts/AuthContext"; +import api from "../../../services/api"; +import { formatCurrency } from "../../../utils/formatters"; +import { Card } from "../../dashboard/Card"; export const KPIsFinanceiros: React.FC = () => { const { user } = useAuth(); - - if (!['ADVOGADO_ADMIN', 'ADVOGADO'].includes(user?.perfil || '')) { + + if (!["ADVOGADO_ADMIN", "ADVOGADO"].includes(user?.perfil || "")) { return null; } - const { data: stats } = useQuery('financeiroKPIs', async () => { - const response = await api.get('/dashboard/financeiro/stats'); + const { data: stats } = useQuery("financeiroKPIs", async () => { + const response = await api.get("/dashboard/financeiro/stats"); return response.data; }); diff --git a/src/components/dashboard/stats/ProcessosStats.tsx b/src/components/dashboard/stats/ProcessosStats.tsx index 5f7998d..0512378 100644 --- a/src/components/dashboard/stats/ProcessosStats.tsx +++ b/src/components/dashboard/stats/ProcessosStats.tsx @@ -1,11 +1,12 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import api from '../../../services/api'; -import { Card } from '../Card'; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../../services/api"; +import { Card } from "../Card"; export const ProcessosStats: React.FC = () => { - const { data: stats } = useQuery('processosStats', async () => { - const response = await api.get('/dashboard/processos/stats'); + const { data: stats } = useQuery("processosStats", async () => { + const response = await api.get("/dashboard/processos/stats"); return response.data; }); @@ -17,16 +18,8 @@ export const ProcessosStats: React.FC = () => { trend={stats?.crescimento || 0} icon="DocumentText" /> - - + + ); }; diff --git a/src/components/dashboard/stats/TarefasStats.tsx b/src/components/dashboard/stats/TarefasStats.tsx index 3cc17ab..355aee9 100644 --- a/src/components/dashboard/stats/TarefasStats.tsx +++ b/src/components/dashboard/stats/TarefasStats.tsx @@ -1,22 +1,23 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { Card } from '../Card'; -import api from '../../../services/api'; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../../services/api"; +import { Card } from "../Card"; export const TarefasStats: React.FC = () => { - const { data: stats } = useQuery('tarefasStats', async () => { + const { data: stats } = useQuery("tarefasStats", async () => { try { - const response = await api.get('/dashboard/tarefas/stats'); + const response = await api.get("/dashboard/tarefas/stats"); return response.data; } catch (error) { - console.error('Erro ao buscar estatísticas:', error); + console.error("Erro ao buscar estatísticas:", error); return { pendentes: 0, atrasadas: 0, concluidas: 0, total: 0, taxaConclusao: 0, - porStatus: { PENDENTE: 0 } + porStatus: { PENDENTE: 0 }, }; } }); @@ -27,7 +28,7 @@ export const TarefasStats: React.FC = () => { concluidas: 0, total: 0, taxaConclusao: 0, - porStatus: { PENDENTE: 0 } + porStatus: { PENDENTE: 0 }, }; const safeStats = stats || defaultStats; diff --git a/src/components/documentos/ConfiguracaoDocumento.tsx b/src/components/documentos/ConfiguracaoDocumento.tsx index fd29575..dc98eac 100644 --- a/src/components/documentos/ConfiguracaoDocumento.tsx +++ b/src/components/documentos/ConfiguracaoDocumento.tsx @@ -1,10 +1,11 @@ -import { useState, useEffect } from 'react'; -import { api } from '../../lib/api'; -import { toast } from 'react-toastify'; -import { Dialog } from '@headlessui/react'; -import { DocumentArrowUpIcon } from '@heroicons/react/24/outline'; -import { Editor } from '@tinymce/tinymce-react'; -import { DOCUMENT_VARIABLES } from '../../utils/documentVariables'; +import { Dialog } from "@headlessui/react"; +import { DocumentArrowUpIcon } from "@heroicons/react/24/outline"; +import { Editor } from "@tinymce/tinymce-react"; +import { useState, useEffect } from "react"; +import { toast } from "react-toastify"; + +import { api } from "../../lib/api"; +import { DOCUMENT_VARIABLES } from "../../utils/documentVariables"; // Definir interface para o estado interface ConfigState { @@ -24,18 +25,18 @@ interface ConfigState { export function ConfiguracaoDocumento() { const [config, setConfig] = useState({ - timbradoUrl: '', + timbradoUrl: "", timbradoMarginTop: 100, timbradoMarginBottom: 50, timbradoMarginLeft: 50, timbradoMarginRight: 50, - templatePadrao: '', + templatePadrao: "", margensTemplate: { top: 35, bottom: 29, left: 25, - right: 25 - } + right: 25, + }, }); const [isPreviewOpen, setPreviewOpen] = useState(false); @@ -47,27 +48,27 @@ export function ConfiguracaoDocumento() { const loadConfig = async () => { try { - const response = await api.get('/documentos/configuracoes'); - console.log('Configurações carregadas:', response.data); // Debug + const response = await api.get("/documentos/configuracoes"); + console.log("Configurações carregadas:", response.data); // Debug if (response.data) { setConfig({ - timbradoUrl: response.data.timbradoUrl || '', + timbradoUrl: response.data.timbradoUrl || "", timbradoMarginTop: response.data.timbradoMarginTop || 100, timbradoMarginBottom: response.data.timbradoMarginBottom || 50, timbradoMarginLeft: response.data.timbradoMarginLeft || 50, timbradoMarginRight: response.data.timbradoMarginRight || 50, - templatePadrao: response.data.templatePadrao || '', + templatePadrao: response.data.templatePadrao || "", margensTemplate: { top: response.data.margensTemplate?.top || 35, bottom: response.data.margensTemplate?.bottom || 29, left: response.data.margensTemplate?.left || 25, - right: response.data.margensTemplate?.right || 25 - } + right: response.data.margensTemplate?.right || 25, + }, }); } } catch (error) { - console.error('Erro ao carregar configurações:', error); - toast.error('Erro ao carregar configurações'); + console.error("Erro ao carregar configurações:", error); + toast.error("Erro ao carregar configurações"); } }; @@ -76,53 +77,57 @@ export function ConfiguracaoDocumento() { if (!file) return; // Atualizado para aceitar PDF - if (!['image/png', 'image/svg+xml', 'application/pdf'].includes(file.type)) { - toast.error('Por favor, use apenas arquivos PDF, PNG ou SVG'); + if ( + !["image/png", "image/svg+xml", "application/pdf"].includes(file.type) + ) { + toast.error("Por favor, use apenas arquivos PDF, PNG ou SVG"); return; } - if (file.size > 5 * 1024 * 1024) { // Aumentado para 5MB para acomodar PDFs - toast.error('O arquivo deve ter no máximo 5MB'); + if (file.size > 5 * 1024 * 1024) { + // Aumentado para 5MB para acomodar PDFs + toast.error("O arquivo deve ter no máximo 5MB"); return; } try { const formData = new FormData(); - formData.append('timbrado', file); + formData.append("timbrado", file); - const response = await api.post('/documentos/upload-timbrado', formData); - setConfig(prev => ({ ...prev, timbradoUrl: response.data.url })); - toast.success('Timbrado atualizado com sucesso'); + const response = await api.post("/documentos/upload-timbrado", formData); + setConfig((prev) => ({ ...prev, timbradoUrl: response.data.url })); + toast.success("Timbrado atualizado com sucesso"); } catch (error) { - toast.error('Erro ao fazer upload do timbrado'); + toast.error("Erro ao fazer upload do timbrado"); } }; const handleSave = async () => { try { setIsSaving(true); - await api.post('/documentos/configuracoes', config); - toast.success('Configurações salvas com sucesso'); + await api.post("/documentos/configuracoes", config); + toast.success("Configurações salvas com sucesso"); } catch (error) { - toast.error('Erro ao salvar configurações'); + toast.error("Erro ao salvar configurações"); } finally { setIsSaving(false); } }; const handleEditorSetup = (editor: any) => { - editor.ui.registry.addMenuButton('variables', { - text: 'Variáveis', + editor.ui.registry.addMenuButton("variables", { + text: "Variáveis", fetch: (callback: Function) => { - const items = Object.entries(DOCUMENT_VARIABLES).flatMap(([category, vars]) => - Object.entries(vars).map(([name, value]) => ({ - type: 'menuitem', - text: `${category} - ${name.replace(/_/g, ' ')}`, - onAction: () => editor.insertContent(value as string) - })) + const items = Object.entries(DOCUMENT_VARIABLES).flatMap( + ([category, vars]) => + Object.entries(vars).map(([name, value]) => ({ + type: "menuitem", + text: `${category} - ${name.replace(/_/g, " ")}`, + onAction: () => editor.insertContent(value as string), + })), ); callback(items); - } + }, }); }; @@ -131,12 +136,14 @@ export function ConfiguracaoDocumento() { height: 400, menubar: true, plugins: [ - 'code', 'preview' // Removido 'template' + "code", + "preview", // Removido 'template' ], - toolbar: 'variables | undo redo | formatselect | ' + - 'bold italic | alignleft aligncenter alignright | ' + - 'code preview', - setup: handleEditorSetup + toolbar: + "variables | undo redo | formatselect | " + + "bold italic | alignleft aligncenter alignright | " + + "code preview", + setup: handleEditorSetup, }; return ( @@ -179,7 +186,12 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ ...prev, timbradoMarginTop: Number(e.target.value) }))} + onChange={(e) => + setConfig((prev) => ({ + ...prev, + timbradoMarginTop: Number(e.target.value), + })) + } className="w-full p-2 border rounded" /> @@ -188,7 +200,12 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ ...prev, timbradoMarginBottom: Number(e.target.value) }))} + onChange={(e) => + setConfig((prev) => ({ + ...prev, + timbradoMarginBottom: Number(e.target.value), + })) + } className="w-full p-2 border rounded" /> @@ -197,7 +214,12 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ ...prev, timbradoMarginLeft: Number(e.target.value) }))} + onChange={(e) => + setConfig((prev) => ({ + ...prev, + timbradoMarginLeft: Number(e.target.value), + })) + } className="w-full p-2 border rounded" /> @@ -206,7 +228,12 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ ...prev, timbradoMarginRight: Number(e.target.value) }))} + onChange={(e) => + setConfig((prev) => ({ + ...prev, + timbradoMarginRight: Number(e.target.value), + })) + } className="w-full p-2 border rounded" /> @@ -218,7 +245,9 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ ...prev, templatePadrao: content }))} + onEditorChange={(content) => + setConfig((prev) => ({ ...prev, templatePadrao: content })) + } init={editorConfig} /> @@ -226,7 +255,9 @@ export function ConfiguracaoDocumento() { {Object.entries(DOCUMENT_VARIABLES).map(([category, vars]) => ( - {category} + + {category} + {Object.entries(vars).map(([name, value]) => ( @@ -247,13 +278,15 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ - ...prev, - margensTemplate: { - ...prev.margensTemplate, - top: Number(e.target.value) - } - }))} + onChange={(e) => + setConfig((prev) => ({ + ...prev, + margensTemplate: { + ...prev.margensTemplate, + top: Number(e.target.value), + }, + })) + } className="w-full p-2 border rounded" /> @@ -262,13 +295,15 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ - ...prev, - margensTemplate: { - ...prev.margensTemplate, - bottom: Number(e.target.value) - } - }))} + onChange={(e) => + setConfig((prev) => ({ + ...prev, + margensTemplate: { + ...prev.margensTemplate, + bottom: Number(e.target.value), + }, + })) + } className="w-full p-2 border rounded" /> @@ -277,13 +312,15 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ - ...prev, - margensTemplate: { - ...prev.margensTemplate, - left: Number(e.target.value) - } - }))} + onChange={(e) => + setConfig((prev) => ({ + ...prev, + margensTemplate: { + ...prev.margensTemplate, + left: Number(e.target.value), + }, + })) + } className="w-full p-2 border rounded" /> @@ -292,13 +329,15 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ - ...prev, - margensTemplate: { - ...prev.margensTemplate, - right: Number(e.target.value) - } - }))} + onChange={(e) => + setConfig((prev) => ({ + ...prev, + margensTemplate: { + ...prev.margensTemplate, + right: Number(e.target.value), + }, + })) + } className="w-full p-2 border rounded" /> @@ -309,11 +348,11 @@ export function ConfiguracaoDocumento() { disabled={isSaving} className={`px-4 py-2 ${ isSaving - ? 'bg-gray-400 cursor-not-allowed' - : 'bg-blue-600 hover:bg-blue-700' + ? "bg-gray-400 cursor-not-allowed" + : "bg-blue-600 hover:bg-blue-700" } text-white rounded`} > - {isSaving ? 'Salvando...' : 'Salvar Configurações'} + {isSaving ? "Salvando..." : "Salvar Configurações"} diff --git a/src/components/documentos/DocumentoEditor.tsx b/src/components/documentos/DocumentoEditor.tsx index 2316a9c..c58e237 100644 --- a/src/components/documentos/DocumentoEditor.tsx +++ b/src/components/documentos/DocumentoEditor.tsx @@ -1,11 +1,18 @@ -import { useState, useEffect } from 'react'; -import { Editor } from '@tinymce/tinymce-react'; -import { Button, Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; -import { Save as SaveIcon } from '@mui/icons-material'; -import { XMarkIcon } from '@heroicons/react/24/outline'; -import { toast } from 'react-toastify'; // Adicionar esta importação -import { api } from '../../lib/api'; // Adicionar esta importação -import { DocumentoService } from '../../services/DocumentoService'; // Adicionar esta importação +import { XMarkIcon } from "@heroicons/react/24/outline"; +import { Save as SaveIcon } from "@mui/icons-material"; +import { + Button, + Dialog, + DialogTitle, + DialogContent, + DialogActions, +} from "@mui/material"; +import { Editor } from "@tinymce/tinymce-react"; +import { useState, useEffect } from "react"; +import { toast } from "react-toastify"; // Adicionar esta importação + +import { api } from "../../lib/api"; // Adicionar esta importação +import { DocumentoService } from "../../services/DocumentoService"; // Adicionar esta importação interface DocumentoEditorProps { isOpen: boolean; @@ -27,24 +34,24 @@ interface TemplateVariables { const preencherTemplate = (template: string, variables: TemplateVariables) => { let result = template; Object.entries(variables).forEach(([key, value]) => { - const regex = new RegExp(`{{${key}}}`, 'g'); + const regex = new RegExp(`{{${key}}}`, "g"); result = result.replace(regex, value); }); return result; }; -export function DocumentoEditor({ - isOpen, - onClose, - onSave, +export function DocumentoEditor({ + isOpen, + onClose, + onSave, initialData, processoId, atendimentoId, - documento // Recuperar a prop + documento, // Recuperar a prop }: DocumentoEditorProps) { - const [content, setContent] = useState(''); - const [nome, setNome] = useState(''); - const [tipo, setTipo] = useState('OUTROS'); + const [content, setContent] = useState(""); + const [nome, setNome] = useState(""); + const [tipo, setTipo] = useState("OUTROS"); const [selectedTemplate, setSelectedTemplate] = useState(null); const [selectedAtendimento, setSelectedAtendimento] = useState(null); const [atendimentos, setAtendimentos] = useState([]); @@ -54,9 +61,9 @@ export function DocumentoEditor({ useEffect(() => { if (initialData) { - setContent(initialData.conteudo || ''); - setNome(initialData.nome || ''); - setTipo(initialData.tipo || 'OUTROS'); + setContent(initialData.conteudo || ""); + setNome(initialData.nome || ""); + setTipo(initialData.tipo || "OUTROS"); } }, [initialData]); @@ -64,13 +71,13 @@ export function DocumentoEditor({ const loadData = async () => { try { const [atendimentosRes, templatesRes] = await Promise.all([ - api.get('/atendimentos'), - api.get('/documentos-modelos') + api.get("/atendimentos"), + api.get("/documentos-modelos"), ]); setAtendimentos(atendimentosRes.data); setTemplates(templatesRes.data); } catch (error) { - toast.error('Erro ao carregar dados'); + toast.error("Erro ao carregar dados"); } finally { setIsLoading(false); } @@ -80,36 +87,38 @@ export function DocumentoEditor({ useEffect(() => { if (documento) { - setContent(documento.conteudo || ''); - setNome(documento.nome || ''); - setTipo(documento.tipo || 'OUTROS'); + setContent(documento.conteudo || ""); + setNome(documento.nome || ""); + setTipo(documento.tipo || "OUTROS"); } }, [documento]); const handleTemplateSelect = async (templateId) => { try { - const template = templates.find(t => t.id === templateId); + const template = templates.find((t) => t.id === templateId); if (!template) return; - const atendimento = atendimentos.find(a => a.id === selectedAtendimento); + const atendimento = atendimentos.find( + (a) => a.id === selectedAtendimento, + ); if (!atendimento) return; // Buscar dados do atendimento com as relações necessárias const [atendimentoDetalhado, usuarioRes, configRes] = await Promise.all([ api.get(`/atendimentos/${atendimento.id}`), - api.get('/auth/me'), - api.get('/documentos/configuracoes') + api.get("/auth/me"), + api.get("/documentos/configuracoes"), ]); // Formatar endereço completo e outros dados do cliente const cliente = atendimentoDetalhado.data.cliente; - const enderecoCompleto = `${cliente.endereco}, ${cliente.numero}${cliente.complemento ? `, ${cliente.complemento}` : ''}, ${cliente.bairro}, ${cliente.cidade}/${cliente.estado}, CEP: ${cliente.cep}`; + const enderecoCompleto = `${cliente.endereco}, ${cliente.numero}${cliente.complemento ? `, ${cliente.complemento}` : ""}, ${cliente.bairro}, ${cliente.cidade}/${cliente.estado}, CEP: ${cliente.cep}`; // Formatar data extenso corretamente - const formatadorData = new Intl.DateTimeFormat('pt-BR', { - day: 'numeric', - month: 'long', - year: 'numeric' + const formatadorData = new Intl.DateTimeFormat("pt-BR", { + day: "numeric", + month: "long", + year: "numeric", }); // Preparar variáveis com dados formatados @@ -117,65 +126,73 @@ export function DocumentoEditor({ cliente: { ...cliente, enderecoCompleto, - nome: cliente.nome || '', - cpf: cliente.cpfCnpj || '', // Adicionar cpf como alias para cpfCnpj - cpfCnpj: cliente.cpfCnpj || '', // Manter cpfCnpj também - rg: cliente.rg || '', - estadoCivil: cliente.estadoCivil || '', - profissao: cliente.profissao || '', - nacionalidade: cliente.nacionalidade || 'brasileiro(a)', - nomePai: cliente.nomePai || '', - nomeMae: cliente.nomeMae || '' + nome: cliente.nome || "", + cpf: cliente.cpfCnpj || "", // Adicionar cpf como alias para cpfCnpj + cpfCnpj: cliente.cpfCnpj || "", // Manter cpfCnpj também + rg: cliente.rg || "", + estadoCivil: cliente.estadoCivil || "", + profissao: cliente.profissao || "", + nacionalidade: cliente.nacionalidade || "brasileiro(a)", + nomePai: cliente.nomePai || "", + nomeMae: cliente.nomeMae || "", }, atendimento: atendimentoDetalhado.data, processo: atendimentoDetalhado.data.processo, advogado: { ...usuarioRes.data, - oabCompleta: `${usuarioRes.data.oab}/${usuarioRes.data.oabUF}` + oabCompleta: `${usuarioRes.data.oab}/${usuarioRes.data.oabUF}`, }, escritorio: configRes.data, data: { - atual: new Date().toLocaleDateString('pt-BR'), + atual: new Date().toLocaleDateString("pt-BR"), atualExtenso: formatadorData.format(new Date()), // Formatar a data corretamente dia: new Date().getDate().toString(), mes: (new Date().getMonth() + 1).toString(), - mesExtenso: new Intl.DateTimeFormat('pt-BR', { month: 'long' }).format(new Date()), - ano: new Date().getFullYear().toString() - } + mesExtenso: new Intl.DateTimeFormat("pt-BR", { + month: "long", + }).format(new Date()), + ano: new Date().getFullYear().toString(), + }, }; // Teste para debug - console.log('Data por extenso:', variables.data.atualExtenso); + console.log("Data por extenso:", variables.data.atualExtenso); // Preencher template - const conteudoPreenchido = template.conteudo.replace(/\{\{([^}]+)\}\}/g, (match, path) => { - try { - const valor = path.split('.').reduce((obj, key) => obj?.[key], variables); - return valor !== undefined ? valor : match; - } catch (error) { - console.warn(`Erro ao processar variável ${path}:`, error); - return match; - } - }); + const conteudoPreenchido = template.conteudo.replace( + /\{\{([^}]+)\}\}/g, + (match, path) => { + try { + const valor = path + .split(".") + .reduce((obj, key) => obj?.[key], variables); + return valor !== undefined ? valor : match; + } catch (error) { + console.warn(`Erro ao processar variável ${path}:`, error); + return match; + } + }, + ); setContent(conteudoPreenchido); setNome(`${template.nome} - ${cliente.nome}`); setTipo(template.tipo); // Debug para verificar substituições - console.log('Template original:', template.conteudo); - console.log('Conteúdo processado:', conteudoPreenchido); - + console.log("Template original:", template.conteudo); + console.log("Conteúdo processado:", conteudoPreenchido); } catch (error) { - console.error('Erro ao carregar template:', error); - toast.error('Erro ao carregar template. Verifique se o atendimento possui um cliente vinculado.'); + console.error("Erro ao carregar template:", error); + toast.error( + "Erro ao carregar template. Verifique se o atendimento possui um cliente vinculado.", + ); } }; const handleSave = async () => { // Verificação de atendimento apenas para novos documentos if (!documento && !selectedAtendimento) { - toast.error('Selecione um atendimento'); + toast.error("Selecione um atendimento"); return; } @@ -186,12 +203,12 @@ export function DocumentoEditor({ tipo, conteudo: content, atendimentoId: documento?.atendimentoId || selectedAtendimento, - processoId: documento?.processoId || processoId + processoId: documento?.processoId || processoId, }); - toast.success('Documento salvo com sucesso'); + toast.success("Documento salvo com sucesso"); onClose(); } catch (error) { - toast.error('Erro ao salvar documento'); + toast.error("Erro ao salvar documento"); } finally { setIsSaving(false); } @@ -203,12 +220,12 @@ export function DocumentoEditor({ setSelectedAtendimento(e.target.value)} className="flex-1 p-2 border rounded" > Selecione um atendimento - {atendimentos.map(atendimento => ( + {atendimentos.map((atendimento) => ( {atendimento.protocolo} - {atendimento.cliente.nome} @@ -216,12 +233,12 @@ export function DocumentoEditor({ handleTemplateSelect(e.target.value)} className="flex-1 p-2 border rounded" > Selecione um template - {templates.map(template => ( + {templates.map((template) => ( {template.nome} @@ -259,23 +276,39 @@ export function DocumentoEditor({ height: 500, menubar: true, plugins: [ - 'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview', - 'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen', - 'insertdatetime', 'media', 'table', 'help', 'wordcount' + "advlist", + "autolink", + "lists", + "link", + "image", + "charmap", + "preview", + "anchor", + "searchreplace", + "visualblocks", + "code", + "fullscreen", + "insertdatetime", + "media", + "table", + "help", + "wordcount", ], - toolbar: 'undo redo | formatselect | ' + - 'bold italic backcolor | alignleft aligncenter ' + - 'alignright alignjustify | bullist numlist outdent indent | ' + - 'removeformat | help', - language: 'pt_BR', // Adicionar configuração de idioma - language_url: '/tinymce/langs/pt_BR.js', // Adicionar URL do arquivo de idioma - content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }', + toolbar: + "undo redo | formatselect | " + + "bold italic backcolor | alignleft aligncenter " + + "alignright alignjustify | bullist numlist outdent indent | " + + "removeformat | help", + language: "pt_BR", // Adicionar configuração de idioma + language_url: "/tinymce/langs/pt_BR.js", // Adicionar URL do arquivo de idioma + content_style: + "body { font-family:Helvetica,Arial,sans-serif; font-size:14px }", // Adicionar estas configurações promotion: false, branding: false, preview_styles: false, - referrer_policy: 'origin', - disable_stats: true // Desabilitar estatísticas + referrer_policy: "origin", + disable_stats: true, // Desabilitar estatísticas }} value={content} onEditorChange={setContent} @@ -286,7 +319,7 @@ export function DocumentoEditor({ }> Cancelar - } @@ -298,4 +331,3 @@ export function DocumentoEditor({ ); } - diff --git a/src/components/documentos/DocumentoViewer.tsx b/src/components/documentos/DocumentoViewer.tsx index 45652b8..2768daf 100644 --- a/src/components/documentos/DocumentoViewer.tsx +++ b/src/components/documentos/DocumentoViewer.tsx @@ -1,8 +1,13 @@ -import { Dialog } from '@headlessui/react'; -import { XMarkIcon, PrinterIcon, DocumentArrowDownIcon } from '@heroicons/react/24/outline'; -import { api } from '../../lib/api'; -import { toast } from 'react-toastify'; -import { useState } from 'react'; +import { Dialog } from "@headlessui/react"; +import { + XMarkIcon, + PrinterIcon, + DocumentArrowDownIcon, +} from "@heroicons/react/24/outline"; +import { useState } from "react"; +import { toast } from "react-toastify"; + +import { api } from "../../lib/api"; interface DocumentoViewerProps { documento: any; @@ -10,18 +15,22 @@ interface DocumentoViewerProps { onClose: () => void; } -export function DocumentoViewer({ documento, isOpen, onClose }: DocumentoViewerProps) { +export function DocumentoViewer({ + documento, + isOpen, + onClose, +}: DocumentoViewerProps) { const [isExporting, setIsExporting] = useState(false); const handlePrint = () => { - const printWindow = window.open('', '_blank'); + const printWindow = window.open("", "_blank"); if (!printWindow) return; const html = ` - ${documento?.nome || 'Documento'} + ${documento?.nome || "Documento"}
Criado em: {new Date(doc.criadoEm).toLocaleDateString()}
{modelo.descricao}
+ {modelo.descricao} +
Status: {error.status}
Mensagem: {error.data.message || error.statusText}
+ Status: {error.status} +
+ Mensagem:{" "} + {error.data.message || error.statusText} +
Erro: {error.message}
+ Erro: {error.message} +
{error.stack}
Ocorreu um erro desconhecido.
Protocolo: {atendimento.protocolo}
+ Protocolo: {atendimento.protocolo} +
{atendimento.cliente.nome}
+ {atendimento.cliente.nome} +
{formatDate(atendimento.dataAtendimento, "PPpp")}
{atendimento.usuario.nome}
+ {atendimento.usuario.nome} +
@@ -137,7 +172,7 @@ export function AtendimentoDetalhes() { { e.preventDefault(); - window.open(anexo.url, '_blank', 'noopener,noreferrer'); + window.open(anexo.url, "_blank", "noopener,noreferrer"); }} className="ml-4 text-blue-600 hover:text-blue-800" > @@ -150,7 +185,7 @@ export function AtendimentoDetalhes() { )} {/* Adicionar botão de cancelar próximo ao cabeçalho */} - {atendimento.status !== 'CANCELADO' && ( + {atendimento.status !== "CANCELADO" && ( setCancelModalOpen(true)} className="bg-red-100 text-red-600 px-4 py-2 rounded-lg hover:bg-red-200" @@ -163,9 +198,12 @@ export function AtendimentoDetalhes() { {cancelModalOpen && ( - Cancelar Atendimento + + Cancelar Atendimento + - Esta ação não pode ser desfeita. O atendimento será marcado como cancelado, mas manterá seu protocolo e histórico. + Esta ação não pode ser desfeita. O atendimento será marcado + como cancelado, mas manterá seu protocolo e histórico. - {isCancelling ? 'Cancelando...' : 'Confirmar Cancelamento'} + {isCancelling ? "Cancelando..." : "Confirmar Cancelamento"} @@ -195,10 +233,14 @@ export function AtendimentoDetalhes() { )} {/* Exibir informação de cancelamento se aplicável */} - {atendimento.status === 'CANCELADO' && ( + {atendimento.status === "CANCELADO" && ( - Atendimento Cancelado - {atendimento.justificativaCancelamento} + + Atendimento Cancelado + + + {atendimento.justificativaCancelamento} + Cancelado em: {formatDate(atendimento.dataAtualizacao, "PPpp")} diff --git a/src/components/atendimentos/AtendimentoForm.tsx b/src/components/atendimentos/AtendimentoForm.tsx index 4a6a25f..27d12c2 100644 --- a/src/components/atendimentos/AtendimentoForm.tsx +++ b/src/components/atendimentos/AtendimentoForm.tsx @@ -1,13 +1,26 @@ -import { useState, useEffect, useCallback } from 'react'; -import { Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField, FormControl, InputLabel, Select, MenuItem, Alert } from '@mui/material'; -import { useEditor, EditorContent } from '@tiptap/react'; -import StarterKit from '@tiptap/starter-kit'; -import Link from '@tiptap/extension-link'; -import { TipoAtendimento } from '@prisma/client'; -import api from '../../services/api'; // Adicionar esta importação -import { XMarkIcon, PaperClipIcon } from '@heroicons/react/24/outline'; // Alterado para v2 -import { useDropzone } from 'react-dropzone'; -import { uploadFile } from '../../services/upload'; +import { XMarkIcon, PaperClipIcon } from "@heroicons/react/24/outline"; // Alterado para v2 +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + FormControl, + InputLabel, + Select, + MenuItem, + Alert, +} from "@mui/material"; +import { TipoAtendimento } from "@prisma/client"; +import Link from "@tiptap/extension-link"; +import { useEditor, EditorContent } from "@tiptap/react"; +import StarterKit from "@tiptap/starter-kit"; +import { useState, useEffect, useCallback } from "react"; +import { useDropzone } from "react-dropzone"; + +import api from "../../services/api"; // Adicionar esta importação +import { uploadFile } from "../../services/upload"; // Implementar debounce manualmente ao invés de usar lodash function debounce(fn: Function, ms = 300) { @@ -40,19 +53,26 @@ interface AnexoTemp { erro?: string; } -export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, initialData }: AtendimentoFormProps) { - const [searchTerm, setSearchTerm] = useState(''); +export function AtendimentoForm({ + open, + onClose, + onSave, + clienteId, + processoId, + initialData, +}: AtendimentoFormProps) { + const [searchTerm, setSearchTerm] = useState(""); const [searchResults, setSearchResults] = useState([]); const [selectedClient, setSelectedClient] = useState(null); const [atendimentoInicial, setAtendimentoInicial] = useState(null); const [atendimentosIniciais, setAtendimentosIniciais] = useState([]); const [formData, setFormData] = useState({ - titulo: '', + titulo: "", tipo: TipoAtendimento.INICIAL, - conteudo: '', - clienteId: clienteId || '', - processoId: processoId || '', - atendimentoInicialId: '', + conteudo: "", + clienteId: clienteId || "", + processoId: processoId || "", + atendimentoInicialId: "", }); const [anexos, setAnexos] = useState([]); const [uploading, setUploading] = useState(false); @@ -61,41 +81,42 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, useEffect(() => { if (open) { setFormData({ - titulo: '', + titulo: "", tipo: TipoAtendimento.INICIAL, - conteudo: '', - clienteId: clienteId || '', - processoId: processoId || '', - atendimentoInicialId: '', + conteudo: "", + clienteId: clienteId || "", + processoId: processoId || "", + atendimentoInicialId: "", }); } }, [open, clienteId, processoId]); useEffect(() => { if (open && initialData) { - setFormData(prev => ({ + setFormData((prev) => ({ ...prev, titulo: initialData.titulo || prev.titulo, clienteId: clienteId || prev.clienteId, processoId: processoId || prev.processoId, tipo: initialData.tipo || prev.tipo, - atendimentoInicialId: initialData.atendimentoInicialId || prev.atendimentoInicialId + atendimentoInicialId: + initialData.atendimentoInicialId || prev.atendimentoInicialId, })); } }, [open, initialData, clienteId, processoId]); const [clientes, setClientes] = useState([]); - const [error, setError] = useState(''); + const [error, setError] = useState(""); const [isSaving, setIsSaving] = useState(false); // Adicionar estado para prevenir múltiplos submits - + // Buscar lista de clientes useEffect(() => { const fetchClientes = async () => { try { - const response = await api.get('/clientes'); + const response = await api.get("/clientes"); setClientes(response.data); } catch (err) { - console.error('Erro ao carregar clientes:', err); + console.error("Erro ao carregar clientes:", err); } }; fetchClientes(); @@ -109,18 +130,21 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, const response = await api.get(`/clientes/search?q=${term}`); setSearchResults(response.data); } catch (error) { - console.error('Erro ao buscar clientes:', error); + console.error("Erro ao buscar clientes:", error); } }, 300), - [] + [], ); // Carregar atendimentos iniciais quando selecionar cliente useEffect(() => { if (formData.clienteId && formData.tipo !== TipoAtendimento.INICIAL) { - api.get(`/atendimentos/iniciais/${formData.clienteId}`) - .then(response => setAtendimentosIniciais(response.data)) - .catch(error => console.error('Erro ao carregar atendimentos:', error)); + api + .get(`/atendimentos/iniciais/${formData.clienteId}`) + .then((response) => setAtendimentosIniciais(response.data)) + .catch((error) => + console.error("Erro ao carregar atendimentos:", error), + ); } }, [formData.clienteId, formData.tipo]); @@ -131,28 +155,28 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, openOnClick: false, }), ], - content: '', + content: "", onUpdate: ({ editor }) => { - setFormData(prev => ({ ...prev, conteudo: editor.getHTML() })); + setFormData((prev) => ({ ...prev, conteudo: editor.getHTML() })); }, }); const onDrop = useCallback((acceptedFiles: File[]) => { - const novosAnexos = acceptedFiles.map(file => ({ + const novosAnexos = acceptedFiles.map((file) => ({ file, nome: file.name, - progresso: 0 + progresso: 0, })); - setAnexos(prev => [...prev, ...novosAnexos]); + setAnexos((prev) => [...prev, ...novosAnexos]); }, []); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, - maxSize: 10485760 // 10MB + maxSize: 10485760, // 10MB }); const handleRemoveAnexo = (index: number) => { - setAnexos(prev => prev.filter((_, i) => i !== index)); + setAnexos((prev) => prev.filter((_, i) => i !== index)); }; const handleSubmit = async (e: React.FormEvent) => { @@ -161,10 +185,10 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, try { setIsSaving(true); - setError(''); + setError(""); if (!formData.clienteId) { - setError('Selecione um cliente'); + setError("Selecione um cliente"); setIsSaving(false); return; } @@ -175,15 +199,15 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, const response = await api.get(`/clientes/${formData.clienteId}`); setSelectedClient(response.data); } catch (error) { - console.error('Erro ao buscar cliente:', error); - setError('Erro ao buscar dados do cliente'); + console.error("Erro ao buscar cliente:", error); + setError("Erro ao buscar dados do cliente"); setIsSaving(false); return; } } // Criar atendimento primeiro para ter o protocolo - const response = await api.post('/atendimentos', { + const response = await api.post("/atendimentos", { ...formData, agendamentoId: initialData?.agendamentoId, }); @@ -195,15 +219,15 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, if (anexos.length > 0) { const anexosPromises = anexos.map(async (anexo) => { try { - const clienteNome = selectedClient?.nome || 'cliente'; + const clienteNome = selectedClient?.nome || "cliente"; return await uploadFile( anexo.file, clienteNome, - 'atendimento', - protocolo + "atendimento", + protocolo, ); } catch (error) { - console.error('Erro no upload:', error); + console.error("Erro no upload:", error); return null; } }); @@ -213,38 +237,42 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, if (anexosSalvos.length > 0) { await api.patch(`/atendimentos/${atendimentoId}`, { - anexos: anexosSalvos + anexos: anexosSalvos, }); } } // Limpar form e fechar modal setFormData({ - titulo: '', + titulo: "", tipo: TipoAtendimento.INICIAL, - conteudo: '', - clienteId: '', - processoId: '', - atendimentoInicialId: '', + conteudo: "", + clienteId: "", + processoId: "", + atendimentoInicialId: "", }); await onSave(response.data); onClose(); - } catch (err) { - console.error('Erro ao salvar atendimento:', err); - setError(err.response?.data?.error || 'Erro ao criar atendimento'); + console.error("Erro ao salvar atendimento:", err); + setError(err.response?.data?.error || "Erro ao criar atendimento"); } finally { setIsSaving(false); } }; return ( - + Novo Atendimento - + {/* Alterado para XMarkIcon */} @@ -265,22 +293,27 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" placeholder="Buscar cliente..." /> - + {searchResults.length > 0 && ( - {searchResults.map(client => ( + {searchResults.map((client) => ( { setSelectedClient(client); - setFormData(prev => ({ ...prev, clienteId: client.id })); + setFormData((prev) => ({ + ...prev, + clienteId: client.id, + })); setSearchTerm(client.nome); setSearchResults([]); }} > {client.nome} - {client.cpfCnpj} + + {client.cpfCnpj} + ))} @@ -294,12 +327,17 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, setFormData({ ...formData, tipo: e.target.value as TipoAtendimento })} + onChange={(e) => + setFormData({ + ...formData, + tipo: e.target.value as TipoAtendimento, + }) + } className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" > {Object.values(TipoAtendimento).map((tipo) => ( - {tipo.replace('_', ' ')} + {tipo.replace("_", " ")} ))} @@ -313,7 +351,12 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, setFormData({ ...formData, atendimentoInicialId: e.target.value })} + onChange={(e) => + setFormData({ + ...formData, + atendimentoInicialId: e.target.value, + }) + } className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" required={formData.tipo !== TipoAtendimento.INICIAL} > @@ -331,7 +374,9 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, fullWidth label="Título do Atendimento" value={formData.titulo} - onChange={(e) => setFormData({ ...formData, titulo: e.target.value })} + onChange={(e) => + setFormData({ ...formData, titulo: e.target.value }) + } required /> @@ -345,14 +390,14 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, editor?.chain().focus().toggleBold().run()} - className={`px-2 py-1 rounded ${editor?.isActive('bold') ? 'bg-gray-200' : 'hover:bg-gray-100'}`} + className={`px-2 py-1 rounded ${editor?.isActive("bold") ? "bg-gray-200" : "hover:bg-gray-100"}`} > Negrito editor?.chain().focus().toggleItalic().run()} - className={`px-2 py-1 rounded ${editor?.isActive('italic') ? 'bg-gray-200' : 'hover:bg-gray-100'}`} + className={`px-2 py-1 rounded ${editor?.isActive("italic") ? "bg-gray-200" : "hover:bg-gray-100"}`} > Itálico @@ -371,14 +416,16 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, - Arraste arquivos aqui ou clique para selecionar + + Arraste arquivos aqui ou clique para selecionar + Máximo 10MB por arquivo @@ -388,17 +435,26 @@ export function AtendimentoForm({ open, onClose, onSave, clienteId, processoId, {/* Lista de Anexos */} {anexos.length > 0 && ( - Arquivos para upload + + Arquivos para upload + {anexos.map((anexo, index) => ( - + - {anexo.nome} + + {anexo.nome} + {anexo.erro && ( - {anexo.erro} + + {anexo.erro} + )} - {isSaving ? 'Salvando...' : 'Salvar'} + {isSaving ? "Salvando..." : "Salvar"} diff --git a/src/components/atendimentos/AtendimentosPage.tsx b/src/components/atendimentos/AtendimentosPage.tsx index 009c726..31d8dbb 100644 --- a/src/components/atendimentos/AtendimentosPage.tsx +++ b/src/components/atendimentos/AtendimentosPage.tsx @@ -1,46 +1,58 @@ -import { useState } from 'react'; -import { useQuery } from 'react-query'; -import { useNavigate } from 'react-router-dom'; -import api from '../../services/api'; -import { AtendimentoForm } from './AtendimentoForm'; -import { AgendamentoCalendario } from './AgendamentoCalendario'; // Adicionar esta importação -import { formatRelative } from 'date-fns'; -import { ptBR } from 'date-fns/locale'; -import { TextField, FormControl, InputLabel, Select, MenuItem } from '@mui/material'; -import { DatePicker } from '@mui/x-date-pickers'; -import { TipoAtendimento } from '@prisma/client'; +import { + TextField, + FormControl, + InputLabel, + Select, + MenuItem, +} from "@mui/material"; +import { DatePicker } from "@mui/x-date-pickers"; +import { TipoAtendimento } from "@prisma/client"; +import { formatRelative, ptBR } from "date-fns"; +import { useState } from "react"; +import { useQuery } from "react-query"; +import { useNavigate } from "react-router-dom"; + +import api from "../../services/api"; + +import { AgendamentoCalendario } from "./AgendamentoCalendario"; // Adicionar esta importação +import { AtendimentoForm } from "./AtendimentoForm"; export const AtendimentosPage = () => { const navigate = useNavigate(); const [formOpen, setFormOpen] = useState(false); const [filtros, setFiltros] = useState({ - protocolo: '', + protocolo: "", dataInicio: null, dataFim: null, - tipo: '', - clienteId: '', - processoId: '', + tipo: "", + clienteId: "", + processoId: "", }); - const { data: atendimentos, refetch } = useQuery(['atendimentos', filtros], async () => { - const params = new URLSearchParams(); - if (filtros.protocolo) params.append('protocolo', filtros.protocolo); - if (filtros.dataInicio) params.append('dataInicio', filtros.dataInicio.toISOString()); - if (filtros.dataFim) params.append('dataFim', filtros.dataFim.toISOString()); - if (filtros.tipo) params.append('tipo', filtros.tipo); - if (filtros.clienteId) params.append('clienteId', filtros.clienteId); - if (filtros.processoId) params.append('processoId', filtros.processoId); + const { data: atendimentos, refetch } = useQuery( + ["atendimentos", filtros], + async () => { + const params = new URLSearchParams(); + if (filtros.protocolo) params.append("protocolo", filtros.protocolo); + if (filtros.dataInicio) + params.append("dataInicio", filtros.dataInicio.toISOString()); + if (filtros.dataFim) + params.append("dataFim", filtros.dataFim.toISOString()); + if (filtros.tipo) params.append("tipo", filtros.tipo); + if (filtros.clienteId) params.append("clienteId", filtros.clienteId); + if (filtros.processoId) params.append("processoId", filtros.processoId); - const response = await api.get(`/atendimentos?${params}`); - return response.data; - }); + const response = await api.get(`/atendimentos?${params}`); + return response.data; + }, + ); const handleSave = async (data: any) => { try { await refetch(); setFormOpen(false); } catch (error) { - console.error('Erro ao atualizar lista:', error); + console.error("Erro ao atualizar lista:", error); } }; @@ -48,7 +60,9 @@ export const AtendimentosPage = () => { - Atendimentos + + Atendimentos + setFormOpen(true)} className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 dark:hover:bg-blue-500 transition-colors" @@ -68,7 +82,9 @@ export const AtendimentosPage = () => { setFiltros({ ...filtros, protocolo: e.target.value })} + onChange={(e) => + setFiltros({ ...filtros, protocolo: e.target.value }) + } size="small" /> @@ -76,14 +92,14 @@ export const AtendimentosPage = () => { label="Data Início" value={filtros.dataInicio} onChange={(date) => setFiltros({ ...filtros, dataInicio: date })} - slotProps={{ textField: { size: 'small' } }} + slotProps={{ textField: { size: "small" } }} /> setFiltros({ ...filtros, dataFim: date })} - slotProps={{ textField: { size: 'small' } }} + slotProps={{ textField: { size: "small" } }} /> @@ -91,12 +107,14 @@ export const AtendimentosPage = () => { setFiltros({ ...filtros, tipo: e.target.value })} + onChange={(e) => + setFiltros({ ...filtros, tipo: e.target.value }) + } > Todos {Object.values(TipoAtendimento).map((tipo) => ( - {tipo.replace('_', ' ')} + {tipo.replace("_", " ")} ))} @@ -106,18 +124,22 @@ export const AtendimentosPage = () => { {atendimentos?.map((atendimento: any) => ( - navigate(`/atendimentos/${atendimento.id}`)} > - {atendimento.titulo} - {atendimento.status === 'CANCELADO' && ( + + {atendimento.titulo} + + {atendimento.status === "CANCELADO" && ( Cancelado @@ -127,27 +149,48 @@ export const AtendimentosPage = () => { Protocolo: {atendimento.protocolo} - + {atendimento.tipo} - + - {formatRelative(new Date(atendimento.dataAtendimento), new Date(), { locale: ptBR })} + {formatRelative( + new Date(atendimento.dataAtendimento), + new Date(), + { locale: ptBR }, + )} - {atendimento.status === 'CANCELADO' && ( + {atendimento.status === "CANCELADO" && ( Motivo: {atendimento.justificativaCancelamento} )} {atendimento.anexos?.length > 0 && ( - - + + {atendimento.anexos.length} anexos diff --git a/src/components/calculos/CalculosList.tsx b/src/components/calculos/CalculosList.tsx index 080f47e..3444d15 100644 --- a/src/components/calculos/CalculosList.tsx +++ b/src/components/calculos/CalculosList.tsx @@ -1,9 +1,9 @@ -import { useEffect, useState } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; -import { format } from 'date-fns'; -import ptBR from 'date-fns/locale/pt-BR'; -import { toast } from 'react-toastify'; -import { useAuth } from '../../contexts/AuthContext'; +import ptBR, { format } from "date-fns"; +import { useEffect, useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; + +import { useAuth } from "../../contexts/AuthContext"; interface Calculo { id: string; @@ -32,114 +32,119 @@ export function CalculosList() { const [calculos, setCalculos] = useState([]); const [loading, setLoading] = useState(true); const [filtro, setFiltro] = useState({ - titulo: '', - status: '', - processoId: '', - clienteId: '' + titulo: "", + status: "", + processoId: "", + clienteId: "", }); - + useEffect(() => { // Verificar autenticação antes de carregar os dados if (isAuthenticated) { carregarCalculos(); } else { // Redirecionar para login caso não esteja autenticado - navigate('/login'); + navigate("/login"); } }, [isAuthenticated, navigate]); - + const carregarCalculos = async () => { try { setLoading(true); - + // Construir query string baseado nos filtros const queryParams = new URLSearchParams(); - if (filtro.titulo) queryParams.append('titulo', filtro.titulo); - if (filtro.status) queryParams.append('status', filtro.status); - if (filtro.processoId) queryParams.append('processoId', filtro.processoId); - if (filtro.clienteId) queryParams.append('clienteId', filtro.clienteId); - + if (filtro.titulo) queryParams.append("titulo", filtro.titulo); + if (filtro.status) queryParams.append("status", filtro.status); + if (filtro.processoId) + queryParams.append("processoId", filtro.processoId); + if (filtro.clienteId) queryParams.append("clienteId", filtro.clienteId); + // Obter token de autenticação usando o contexto const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado. Faça login novamente.'); + throw new Error("Usuário não autenticado. Faça login novamente."); } - + // Incluir o token no cabeçalho da requisição const response = await fetch(`/api/calculos?${queryParams.toString()}`, { headers: { - 'Authorization': `Bearer ${token}` - } + Authorization: `Bearer ${token}`, + }, }); - + if (!response.ok) { if (response.status === 401) { // Se receber 401, o token pode estar expirado - navigate('/login'); - throw new Error('Sessão expirada. Por favor, faça login novamente.'); + navigate("/login"); + throw new Error("Sessão expirada. Por favor, faça login novamente."); } - throw new Error('Erro ao carregar cálculos'); + throw new Error("Erro ao carregar cálculos"); } - + const data = await response.json(); setCalculos(data); } catch (error) { - console.error('Erro ao carregar cálculos:', error); + console.error("Erro ao carregar cálculos:", error); toast.error(`Falha ao carregar os cálculos: ${error.message}`); } finally { setLoading(false); } }; - - const handleFiltroChange = (e: React.ChangeEvent) => { + + const handleFiltroChange = ( + e: React.ChangeEvent, + ) => { const { name, value } = e.target; - setFiltro(prev => ({ ...prev, [name]: value })); + setFiltro((prev) => ({ ...prev, [name]: value })); }; - + const handleSubmitFiltro = (e: React.FormEvent) => { e.preventDefault(); carregarCalculos(); }; - + // Função para formatar valores monetários const formatarValor = (valor: string) => { - return new Intl.NumberFormat('pt-BR', { - style: 'currency', - currency: 'BRL' + return new Intl.NumberFormat("pt-BR", { + style: "currency", + currency: "BRL", }).format(parseFloat(valor)); }; - + // Função para formatar datas const formatarData = (data: string) => { try { - return format(new Date(data), 'dd/MM/yyyy', { locale: ptBR }); + return format(new Date(data), "dd/MM/yyyy", { locale: ptBR }); } catch (error) { - return 'Data inválida'; + return "Data inválida"; } }; - + // Status Badge const StatusBadge = ({ status }: { status: string }) => { const getStatusColor = () => { switch (status) { - case 'RASCUNHO': - return 'bg-yellow-100 text-yellow-800'; - case 'FINALIZADO': - return 'bg-green-100 text-green-800'; - case 'ARQUIVADO': - return 'bg-gray-100 text-gray-800'; + case "RASCUNHO": + return "bg-yellow-100 text-yellow-800"; + case "FINALIZADO": + return "bg-green-100 text-green-800"; + case "ARQUIVADO": + return "bg-gray-100 text-gray-800"; default: - return 'bg-blue-100 text-blue-800'; + return "bg-blue-100 text-blue-800"; } }; - + return ( - + {status} ); }; - + return ( @@ -151,13 +156,19 @@ export function CalculosList() { Novo Cálculo - + {/* Filtros */} Filtros - + - + Título - + - + Status Arquivado - + - + {/* Lista de Cálculos */} {loading ? ( - + Carregando... ) : calculos.length === 0 ? ( - Nenhum cálculo encontrado + + Nenhum cálculo encontrado + Para criar um novo cálculo, clique no botão "Novo Cálculo". @@ -218,7 +237,10 @@ export function CalculosList() { {calculos.map((calculo) => ( - + @@ -244,14 +266,16 @@ export function CalculosList() { - Criado em: {formatarData(calculo.criadoEm)} por {calculo.usuario.nome} + Criado em: {formatarData(calculo.criadoEm)} por{" "} + {calculo.usuario.nome} {calculo.processo && ( - Processo: {calculo.processo.numero} - {calculo.processo.titulo} + Processo: {calculo.processo.numero} -{" "} + {calculo.processo.titulo} )} diff --git a/src/components/calculos/DetalhePensaoAlimenticia.tsx b/src/components/calculos/DetalhePensaoAlimenticia.tsx index 222b039..6e5f0f8 100644 --- a/src/components/calculos/DetalhePensaoAlimenticia.tsx +++ b/src/components/calculos/DetalhePensaoAlimenticia.tsx @@ -1,7 +1,8 @@ -import React, { useEffect, useState } from 'react'; -import { useParams } from 'react-router-dom'; -import { toast } from 'react-toastify'; -import api from '../../services/api'; +import React, { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import { toast } from "react-toastify"; + +import api from "../../services/api"; interface CalculoPensao { id: string; @@ -36,8 +37,8 @@ export function DetalhePensaoAlimenticia() { const response = await api.get(`/pensao-alimenticia/${id}`); setCalculo(response.data); } catch (error) { - console.error('Erro ao carregar cálculo:', error); - toast.error('Falha ao carregar dados do cálculo'); + console.error("Erro ao carregar cálculo:", error); + toast.error("Falha ao carregar dados do cálculo"); } finally { setLoading(false); } @@ -57,29 +58,53 @@ export function DetalhePensaoAlimenticia() { return ( {calculo.titulo} - + Dados do Cálculo - Data Inicial: {new Date(calculo.dataInicial).toLocaleDateString()} + + Data Inicial:{" "} + {new Date(calculo.dataInicial).toLocaleDateString()} + {calculo.dataFinal && ( - Data Final: {new Date(calculo.dataFinal).toLocaleDateString()} + + Data Final:{" "} + {new Date(calculo.dataFinal).toLocaleDateString()} + )} - Percentual: {calculo.percentualSalarioMinimo}% + + Percentual:{" "} + {calculo.percentualSalarioMinimo}% + {calculo.multaPactuada && ( - Multa: {calculo.multaPactuada}% + + Multa:{" "} + {calculo.multaPactuada}% + )} - Índice: {calculo.tipoAtualizacao} + + Índice:{" "} + {calculo.tipoAtualizacao} + Valores - Valor Base: R$ {calculo.valorCalculado.toFixed(2)} - Valor Multa: R$ {calculo.valorMulta.toFixed(2)} - Valor Total: R$ {calculo.valorTotal.toFixed(2)} + + Valor Base: R${" "} + {calculo.valorCalculado.toFixed(2)} + + + Valor Multa: R${" "} + {calculo.valorMulta.toFixed(2)} + + + Valor Total: R${" "} + {calculo.valorTotal.toFixed(2)} + @@ -92,4 +117,4 @@ export function DetalhePensaoAlimenticia() { )} ); -} \ No newline at end of file +} diff --git a/src/components/calculos/DetalhesCalculo.tsx b/src/components/calculos/DetalhesCalculo.tsx index c376313..ff38836 100644 --- a/src/components/calculos/DetalhesCalculo.tsx +++ b/src/components/calculos/DetalhesCalculo.tsx @@ -1,14 +1,14 @@ -import React, { useState, useEffect } from 'react'; -import { useParams, useNavigate } from 'react-router-dom'; -import { format } from 'date-fns'; -import ptBR from 'date-fns/locale/pt-BR'; -import { toast } from 'react-toastify'; -import { Card, Button, Tabs, TabList, Tab, TabPanel } from '../ui'; -import { useAuth } from '../../contexts/AuthContext'; -import { api } from '../../services/api'; -import axios from 'axios'; -import { formatarData, formatarMoeda } from '../../utils/formatters'; -import { PencilIcon, TrashIcon } from '@heroicons/react/24/outline'; +import { PencilIcon, TrashIcon } from "@heroicons/react/24/outline"; +import axios from "axios"; +import ptBR, { format } from "date-fns"; +import React, { useState, useEffect } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; + +import { useAuth } from "../../contexts/AuthContext"; +import { api } from "../../services/api"; +import { formatarData, formatarMoeda } from "../../utils/formatters"; +import { Card, Button, Tabs, TabList, Tab, TabPanel } from "../ui"; interface Calculo { id: string; @@ -58,12 +58,12 @@ export function DetalhesCalculo() { const [calculo, setCalculo] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [activeTab, setActiveTab] = useState('resumo'); + const [activeTab, setActiveTab] = useState("resumo"); const [formData, setFormData] = useState({ - indice: 'INPC', - dataInicial: '', - dataFinal: '', - valorOriginal: '' + indice: "INPC", + dataInicial: "", + dataFinal: "", + valorOriginal: "", }); const loadCalculo = async () => { @@ -76,9 +76,9 @@ export function DetalhesCalculo() { const response = await api.get(`/calculos/${calculoId}`); setCalculo(response.data); } catch (error) { - console.error('Erro ao carregar cálculo:', error); - setError('Não foi possível carregar os dados do cálculo'); - toast.error('Erro ao carregar dados do cálculo'); + console.error("Erro ao carregar cálculo:", error); + setError("Não foi possível carregar os dados do cálculo"); + toast.error("Erro ao carregar dados do cálculo"); } finally { setLoading(false); } @@ -95,20 +95,24 @@ export function DetalhesCalculo() { // Obter token de autenticação const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado. Faça login novamente.'); + throw new Error("Usuário não autenticado. Faça login novamente."); } // Usar o serviço api em vez de fetch direto - await api.post(`/calculos/${calculoId}/finalizar`, {}, { - headers: { - 'Authorization': `Bearer ${token}` - } - }); + await api.post( + `/calculos/${calculoId}/finalizar`, + {}, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); - toast.success('Cálculo finalizado com sucesso!'); + toast.success("Cálculo finalizado com sucesso!"); loadCalculo(); // Recarregar os dados } catch (error) { - console.error('Erro ao finalizar cálculo:', error); + console.error("Erro ao finalizar cálculo:", error); toast.error(`Falha ao finalizar cálculo: ${error.message}`); } }; @@ -116,33 +120,33 @@ export function DetalhesCalculo() { const gerarRelatorio = async () => { try { const response = await api.get(`/calculos/${calculoId}/relatorio`, { - responseType: 'blob' // Importante para receber o PDF como blob + responseType: "blob", // Importante para receber o PDF como blob }); // Criar URL do blob e abrir em uma nova aba - const blob = new Blob([response.data], { type: 'application/pdf' }); + const blob = new Blob([response.data], { type: "application/pdf" }); const url = window.URL.createObjectURL(blob); - window.open(url, '_blank'); + window.open(url, "_blank"); } catch (error) { - console.error('Erro ao gerar relatório:', error); - toast.error('Erro ao gerar relatório'); + console.error("Erro ao gerar relatório:", error); + toast.error("Erro ao gerar relatório"); } }; const exportarExcel = async () => { try { const response = await api.get(`/calculos/${calculoId}/excel`, { - responseType: 'blob' // Importante para receber o Excel como blob + responseType: "blob", // Importante para receber o Excel como blob }); // Criar URL do blob e forçar download const blob = new Blob([response.data], { - type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", }); const url = window.URL.createObjectURL(blob); // Criar link temporário para download - const link = document.createElement('a'); + const link = document.createElement("a"); link.href = url; link.download = `calculo_${calculoId}.xlsx`; link.click(); @@ -150,25 +154,25 @@ export function DetalhesCalculo() { // Limpar URL window.URL.revokeObjectURL(url); } catch (error) { - console.error('Erro ao exportar Excel:', error); - toast.error('Erro ao exportar para Excel'); + console.error("Erro ao exportar Excel:", error); + toast.error("Erro ao exportar para Excel"); } }; // Função para formatar valores monetários const formatarValor = (valor: string) => { - return new Intl.NumberFormat('pt-BR', { - style: 'currency', - currency: 'BRL' + return new Intl.NumberFormat("pt-BR", { + style: "currency", + currency: "BRL", }).format(parseFloat(valor)); }; // Função para formatar datas const formatarData = (data: string) => { try { - return format(new Date(data), 'dd/MM/yyyy', { locale: ptBR }); + return format(new Date(data), "dd/MM/yyyy", { locale: ptBR }); } catch (error) { - return 'Data inválida'; + return "Data inválida"; } }; @@ -176,19 +180,21 @@ export function DetalhesCalculo() { const StatusBadge = ({ status }: { status: string }) => { const getStatusColor = () => { switch (status) { - case 'RASCUNHO': - return 'bg-yellow-100 text-yellow-800'; - case 'FINALIZADO': - return 'bg-green-100 text-green-800'; - case 'ARQUIVADO': - return 'bg-gray-100 text-gray-800'; + case "RASCUNHO": + return "bg-yellow-100 text-yellow-800"; + case "FINALIZADO": + return "bg-green-100 text-green-800"; + case "ARQUIVADO": + return "bg-gray-100 text-gray-800"; default: - return 'bg-blue-100 text-blue-800'; + return "bg-blue-100 text-blue-800"; } }; return ( - + {status} ); @@ -198,7 +204,10 @@ export function DetalhesCalculo() { return ( - + Carregando... @@ -212,8 +221,12 @@ export function DetalhesCalculo() { Erro - {error || 'Não foi possível carregar os dados do cálculo'} - navigate('/calculos')}>Voltar para Lista + + {error || "Não foi possível carregar os dados do cálculo"} + + navigate("/calculos")}> + Voltar para Lista + @@ -227,17 +240,19 @@ export function DetalhesCalculo() { {calculo.titulo} - Criado em {formatarData(calculo.criadoEm)} + + Criado em {formatarData(calculo.criadoEm)} + - {calculo.status === 'RASCUNHO' && ( + {calculo.status === "RASCUNHO" && ( Finalizar Cálculo )} - navigate('/calculos')} variant="outline"> + navigate("/calculos")} variant="outline"> Voltar @@ -260,23 +275,39 @@ export function DetalhesCalculo() { - Informações Gerais + + Informações Gerais + - Data Base - {formatarData(calculo.dataBase)} + + Data Base + + + {formatarData(calculo.dataBase)} + - Data do Cálculo - {formatarData(calculo.dataCalculo)} + + Data do Cálculo + + + {formatarData(calculo.dataCalculo)} + - Responsável - {calculo.usuario.nome} + + Responsável + + + {calculo.usuario.nome} + {calculo.processo && ( - Processo + + Processo + {calculo.processo.numero} - {calculo.processo.titulo} @@ -284,62 +315,100 @@ export function DetalhesCalculo() { )} {calculo.cliente && ( - Cliente - {calculo.cliente.nome} + + Cliente + + + {calculo.cliente.nome} + )} {calculo.descricao && ( - Descrição - {calculo.descricao} + + Descrição + + + {calculo.descricao} + )} - Valores + + Valores + Valor Base - {formatarValor(calculo.valorBase)} + + {formatarValor(calculo.valorBase)} + Valor Corrigido - {formatarValor(calculo.valorCorrigido)} + + {formatarValor(calculo.valorCorrigido)} + Valor Final - {formatarValor(calculo.valorFinal)} + + {formatarValor(calculo.valorFinal)} + - Resumo de Componentes + + Resumo de Componentes + - Correções Monetárias: - {calculo.correcoes.length} + + Correções Monetárias: + + + {calculo.correcoes.length} + - Juros Aplicados: - {calculo.juros.length} + + Juros Aplicados: + + + {calculo.juros.length} + Multas: - {calculo.multas.length} + + {calculo.multas.length} + - Honorários: - {calculo.honorarios.length} + + Honorários: + + + {calculo.honorarios.length} + Parcelas: - {calculo.parcelas.length} + + {calculo.parcelas.length} + - Descontos: - {calculo.descontos.length} + + Descontos: + + + {calculo.descontos.length} + @@ -347,7 +416,7 @@ export function DetalhesCalculo() { - {calculo.status !== 'FINALIZADO' && ( + {calculo.status !== "FINALIZADO" && ( - + Exportar Excel @@ -376,7 +442,11 @@ export function DetalhesCalculo() { - + @@ -429,28 +499,38 @@ export function DetalhesCalculo() { } // Implementação dos componentes de abas -function CorrecaoMonetariaTab({ calculo, onUpdate, formatarValor }: { calculo: Calculo, onUpdate: () => void, formatarValor: (valor: string) => string }) { +function CorrecaoMonetariaTab({ + calculo, + onUpdate, + formatarValor, +}: { + calculo: Calculo; + onUpdate: () => void; + formatarValor: (valor: string) => string; +}) { const { getToken } = useAuth(); const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ - indice: 'INPC', - dataInicial: new Date().toISOString().split('T')[0], - dataFinal: new Date().toISOString().split('T')[0], - valorOriginal: calculo.valorBase + indice: "INPC", + dataInicial: new Date().toISOString().split("T")[0], + dataFinal: new Date().toISOString().split("T")[0], + valorOriginal: calculo.valorBase, }); // Função para corrigir a formatação do valor const corrigirValorExibicao = (valor: string): string => { // Se o valor for 222.041,00, corrigir para 22.204,10 - if (valor === '222.041,00') { - return 'R$ 22.204,10'; + if (valor === "222.041,00") { + return "R$ 22.204,10"; } return valor; }; - const handleChange = (e: React.ChangeEvent) => { + const handleChange = ( + e: React.ChangeEvent, + ) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); }; const handleSubmit = async (e: React.FormEvent) => { @@ -459,44 +539,51 @@ function CorrecaoMonetariaTab({ calculo, onUpdate, formatarValor }: { calculo: C try { await api.post(`/calculos/${calculo.id}/correcoes`, formData, { - timeout: 60000 // Aumentar timeout para 1 minuto + timeout: 60000, // Aumentar timeout para 1 minuto }); - toast.success('Correção monetária adicionada com sucesso'); + toast.success("Correção monetária adicionada com sucesso"); // Chamar o callback de atualização em vez de referenciar loadCalculo onUpdate(); } catch (error) { - console.error('Erro ao adicionar correção:', error); - toast.error('Erro ao adicionar correção monetária'); + console.error("Erro ao adicionar correção:", error); + toast.error("Erro ao adicionar correção monetária"); } finally { setLoading(false); } }; const handleDelete = async (correcaoId: string) => { - if (!confirm('Deseja realmente excluir esta correção monetária?')) return; + if (!confirm("Deseja realmente excluir esta correção monetária?")) return; try { await api.delete(`/calculos/${calculo.id}/correcoes/${correcaoId}`); - toast.success('Correção monetária removida com sucesso'); + toast.success("Correção monetária removida com sucesso"); onUpdate(); } catch (error) { - console.error('Erro ao excluir correção:', error); - toast.error('Falha ao excluir correção monetária'); + console.error("Erro ao excluir correção:", error); + toast.error("Falha ao excluir correção monetária"); } }; return ( - Correção Monetária + + Correção Monetária + - {calculo.status !== 'FINALIZADO' && ( + {calculo.status !== "FINALIZADO" && ( - Adicionar Correção Monetária + + Adicionar Correção Monetária + - + Índice - + Data Inicial - + Data Final - + Valor Original @@ -569,7 +665,7 @@ function CorrecaoMonetariaTab({ calculo, onUpdate, formatarValor }: { calculo: C className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" disabled={loading} > - {loading ? 'Adicionando...' : 'Adicionar Correção'} + {loading ? "Adicionando..." : "Adicionar Correção"} @@ -577,30 +673,60 @@ function CorrecaoMonetariaTab({ calculo, onUpdate, formatarValor }: { calculo: C )} - Histórico de Correções + + Histórico de Correções + {calculo.correcoes.length === 0 ? ( - Nenhuma correção monetária registrada + + Nenhuma correção monetária registrada + ) : ( - Data Inicial - Data Final - Índice - Valor Original - Valor Corrigido - Ações + + Data Inicial + + + Data Final + + + Índice + + + Valor Original + + + Valor Corrigido + + + Ações + {calculo.correcoes.map((correcao, index) => ( - {formatarData(correcao.dataInicial)} - {correcao.dataFinal ? formatarData(correcao.dataFinal) : '-'} - {correcao.indice} - {formatarMoeda(correcao.valorOriginal)} - {corrigirValorExibicao(formatarMoeda(correcao.valorCorrigido))} + + {formatarData(correcao.dataInicial)} + + + {correcao.dataFinal + ? formatarData(correcao.dataFinal) + : "-"} + + + {correcao.indice} + + + {formatarMoeda(correcao.valorOriginal)} + + + {corrigirValorExibicao( + formatarMoeda(correcao.valorCorrigido), + )} + void; @@ -635,25 +761,27 @@ function JurosTab({ const { getToken } = useAuth(); const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ - tipo: 'SIMPLES', - taxa: '1,00', - periodicidade: 'MENSAL', - natureza: 'MORATORIO', - dataInicial: new Date().toISOString().split('T')[0], - dataFinal: new Date().toISOString().split('T')[0], - valorBase: calculo.valorBase + tipo: "SIMPLES", + taxa: "1,00", + periodicidade: "MENSAL", + natureza: "MORATORIO", + dataInicial: new Date().toISOString().split("T")[0], + dataFinal: new Date().toISOString().split("T")[0], + valorBase: calculo.valorBase, }); - const handleChange = (e: React.ChangeEvent) => { + const handleChange = ( + e: React.ChangeEvent, + ) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); }; const handleTaxaChange = (e: React.ChangeEvent) => { const { value } = e.target; // Permitir apenas dígitos, vírgula, e ponto - const numericValue = value.replace(/[^\d.,]/g, ''); - setFormData(prev => ({ ...prev, taxa: numericValue })); + const numericValue = value.replace(/[^\d.,]/g, ""); + setFormData((prev) => ({ ...prev, taxa: numericValue })); }; const handleSubmit = async (e: React.FormEvent) => { @@ -663,31 +791,37 @@ function JurosTab({ try { const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado. Faça login novamente.'); + throw new Error("Usuário não autenticado. Faça login novamente."); } // Converter taxa para decimal (ex: 1,5% -> 0.015) - const taxaDecimal = parseFloat(formData.taxa.replace(',', '.')) / 100; + const taxaDecimal = parseFloat(formData.taxa.replace(",", ".")) / 100; // Usar o serviço api em vez de fetch direto - await api.post(`/calculos/${calculo.id}/juros`, { - tipo: formData.tipo, - taxa: taxaDecimal, - periodicidade: formData.periodicidade, - natureza: formData.natureza, - dataInicial: formData.dataInicial, - dataFinal: formData.dataFinal, - valorBase: parseFloat(formData.valorBase.replace(/[^\d.,]/g, '').replace(',', '.')) - }, { - headers: { - 'Authorization': `Bearer ${token}` - } - }); + await api.post( + `/calculos/${calculo.id}/juros`, + { + tipo: formData.tipo, + taxa: taxaDecimal, + periodicidade: formData.periodicidade, + natureza: formData.natureza, + dataInicial: formData.dataInicial, + dataFinal: formData.dataFinal, + valorBase: parseFloat( + formData.valorBase.replace(/[^\d.,]/g, "").replace(",", "."), + ), + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); - toast.success('Juros adicionados com sucesso!'); + toast.success("Juros adicionados com sucesso!"); onUpdate(); // Recarregar dados do cálculo } catch (error) { - console.error('Erro ao adicionar juros:', error); + console.error("Erro ao adicionar juros:", error); toast.error(`Falha ao adicionar juros: ${error.message}`); } finally { setLoading(false); @@ -695,15 +829,15 @@ function JurosTab({ }; const handleDelete = async (juroId: string) => { - if (!confirm('Deseja realmente excluir este juro?')) return; + if (!confirm("Deseja realmente excluir este juro?")) return; try { await api.delete(`/calculos/${calculo.id}/juros/${juroId}`); - toast.success('Juro removido com sucesso'); + toast.success("Juro removido com sucesso"); onUpdate(); } catch (error) { - console.error('Erro ao excluir juro:', error); - toast.error('Falha ao excluir juro'); + console.error("Erro ao excluir juro:", error); + toast.error("Falha ao excluir juro"); } }; @@ -711,13 +845,18 @@ function JurosTab({ Juros - {calculo.status !== 'FINALIZADO' && ( + {calculo.status !== "FINALIZADO" && ( - Adicionar Juros + + Adicionar Juros + - + Tipo - + Taxa @@ -754,7 +896,10 @@ function JurosTab({ - + Periodicidade - + Natureza - + Data Inicial - + Data Final - + Valor Base @@ -847,7 +1004,7 @@ function JurosTab({ className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" disabled={loading} > - {loading ? 'Adicionando...' : 'Adicionar Juros'} + {loading ? "Adicionando..." : "Adicionar Juros"} @@ -855,37 +1012,81 @@ function JurosTab({ )} - Juros Aplicados + + Juros Aplicados + {calculo.juros && calculo.juros.length > 0 ? ( - Tipo - Taxa - Período - Natureza - Valor Base - Valor Juros - Ações + + Tipo + + + Taxa + + + Período + + + Natureza + + + Valor Base + + + Valor Juros + + + Ações + {calculo.juros.map((juro, index) => ( - {juro.tipo === 'SIMPLES' ? 'Simples' : 'Composto'} + {juro.tipo === "SIMPLES" ? "Simples" : "Composto"} {(parseFloat(juro.taxa) * 100).toFixed(2)}% - {formatarData(juro.dataInicial)} a {formatarData(juro.dataFinal)} + {formatarData(juro.dataInicial)} a{" "} + {formatarData(juro.dataFinal)} + + + {juro.natureza} + + + {formatarValor(juro.valorBase)} + + + {formatarValor(juro.valorJuros)} - {juro.natureza} - {formatarValor(juro.valorBase)} - {formatarValor(juro.valorJuros)} void; @@ -922,22 +1123,26 @@ function MultasTab({ const { getToken } = useAuth(); const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ - tipo: 'CONTRATUAL', - percentual: '2,00', + tipo: "CONTRATUAL", + percentual: "2,00", valorBase: calculo.valorBase, - fundamento: '' + fundamento: "", }); - const handleChange = (e: React.ChangeEvent) => { + const handleChange = ( + e: React.ChangeEvent< + HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement + >, + ) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); }; const handlePercentualChange = (e: React.ChangeEvent) => { const { value } = e.target; // Permitir apenas dígitos, vírgula, e ponto - const numericValue = value.replace(/[^\d.,]/g, ''); - setFormData(prev => ({ ...prev, percentual: numericValue })); + const numericValue = value.replace(/[^\d.,]/g, ""); + setFormData((prev) => ({ ...prev, percentual: numericValue })); }; const handleSubmit = async (e: React.FormEvent) => { @@ -947,46 +1152,49 @@ function MultasTab({ try { const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado'); + throw new Error("Usuário não autenticado"); } // Converter percentual para decimal (ex: 2,00% -> 0.02) - const percentualDecimal = parseFloat(formData.percentual.replace(',', '.')) / 100; + const percentualDecimal = + parseFloat(formData.percentual.replace(",", ".")) / 100; await api.post(`/calculos/${calculo.id}/multas`, { tipo: formData.tipo, percentual: percentualDecimal, - valorBase: parseFloat(formData.valorBase.replace(/[^\d.,]/g, '').replace(',', '.')), - fundamento: formData.fundamento + valorBase: parseFloat( + formData.valorBase.replace(/[^\d.,]/g, "").replace(",", "."), + ), + fundamento: formData.fundamento, }); - toast.success('Multa adicionada com sucesso!'); + toast.success("Multa adicionada com sucesso!"); onUpdate(); // Limpar formulário - setFormData(prev => ({ + setFormData((prev) => ({ ...prev, - percentual: '', - fundamento: '' + percentual: "", + fundamento: "", })); } catch (error) { - console.error('Erro ao adicionar multa:', error); - toast.error('Falha ao adicionar multa: ' + error.message); + console.error("Erro ao adicionar multa:", error); + toast.error("Falha ao adicionar multa: " + error.message); } finally { setLoading(false); } }; const handleDelete = async (multaId: string) => { - if (!confirm('Deseja realmente excluir esta multa?')) return; + if (!confirm("Deseja realmente excluir esta multa?")) return; try { await api.delete(`/calculos/${calculo.id}/multas/${multaId}`); - toast.success('Multa removida com sucesso'); + toast.success("Multa removida com sucesso"); onUpdate(); } catch (error) { - console.error('Erro ao excluir multa:', error); - toast.error('Falha ao excluir multa'); + console.error("Erro ao excluir multa:", error); + toast.error("Falha ao excluir multa"); } }; @@ -994,13 +1202,18 @@ function MultasTab({ Multas - {calculo.status !== 'FINALIZADO' && ( + {calculo.status !== "FINALIZADO" && ( - Adicionar Multa + + Adicionar Multa + - + Tipo - + Percentual @@ -1037,7 +1253,10 @@ function MultasTab({ - + Valor Base @@ -1057,7 +1276,10 @@ function MultasTab({ - + Fundamento Legal/Contratual - {loading ? 'Adicionando...' : 'Adicionar Multa'} + {loading ? "Adicionando..." : "Adicionar Multa"} @@ -1085,31 +1307,71 @@ function MultasTab({ )} - Multas Aplicadas + + Multas Aplicadas + {calculo.multas && calculo.multas.length > 0 ? ( - Tipo - Percentual - Valor Base - Valor Multa - Fundamento - Ações + + Tipo + + + Percentual + + + Valor Base + + + Valor Multa + + + Fundamento + + + Ações + {calculo.multas.map((multa, index) => ( - {multa.tipo} + + {multa.tipo} + {(parseFloat(multa.percentual) * 100).toFixed(2)}% - {formatarValor(multa.valorBase)} - {formatarValor(multa.valorMulta)} - {multa.fundamento || '-'} + + {formatarValor(multa.valorBase)} + + + {formatarValor(multa.valorMulta)} + + + {multa.fundamento || "-"} + ) : ( - Nenhuma multa aplicada. + + Nenhuma multa aplicada. + )} @@ -1137,7 +1401,7 @@ function MultasTab({ function ParcelasTab({ calculo, onUpdate, - formatarValor = formatarMoeda + formatarValor = formatarMoeda, }: { calculo: Calculo; onUpdate: () => void; @@ -1146,14 +1410,16 @@ function ParcelasTab({ const { getToken } = useAuth(); const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ - numero: '1', - dataVencimento: new Date().toISOString().split('T')[0], - valorOriginal: calculo.valorBase + numero: "1", + dataVencimento: new Date().toISOString().split("T")[0], + valorOriginal: calculo.valorBase, }); - const handleChange = (e: React.ChangeEvent) => { + const handleChange = ( + e: React.ChangeEvent, + ) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); }; const handleSubmit = async (e: React.FormEvent) => { @@ -1163,30 +1429,36 @@ function ParcelasTab({ try { const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado. Faça login novamente.'); + throw new Error("Usuário não autenticado. Faça login novamente."); } // Usar o serviço api em vez de fetch direto - await api.post(`/calculos/${calculo.id}/parcelas`, { - numero: parseInt(formData.numero), - dataVencimento: formData.dataVencimento, - valorOriginal: parseFloat(formData.valorOriginal.replace(/[^\d.,]/g, '').replace(',', '.')) - }, { - headers: { - 'Authorization': `Bearer ${token}` - } - }); + await api.post( + `/calculos/${calculo.id}/parcelas`, + { + numero: parseInt(formData.numero), + dataVencimento: formData.dataVencimento, + valorOriginal: parseFloat( + formData.valorOriginal.replace(/[^\d.,]/g, "").replace(",", "."), + ), + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); - toast.success('Parcela adicionada com sucesso!'); + toast.success("Parcela adicionada com sucesso!"); onUpdate(); // Recarregar dados do cálculo // Limpar o formulário e incrementar o número da parcela - setFormData(prev => ({ + setFormData((prev) => ({ ...prev, - numero: (parseInt(prev.numero) + 1).toString() + numero: (parseInt(prev.numero) + 1).toString(), })); } catch (error) { - console.error('Erro ao adicionar parcela:', error); + console.error("Erro ao adicionar parcela:", error); toast.error(`Falha ao adicionar parcela: ${error.message}`); } finally { setLoading(false); @@ -1197,40 +1469,44 @@ function ParcelasTab({ try { const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado. Faça login novamente.'); + throw new Error("Usuário não autenticado. Faça login novamente."); } // Usar o serviço api em vez de fetch direto - const response = await api.post(`/calculos/${calculo.id}/parcelas/${parcelaId}/pagar`, { - dataPagamento: new Date().toISOString().split('T')[0] - }, { - headers: { - 'Authorization': `Bearer ${token}` - } - }); + const response = await api.post( + `/calculos/${calculo.id}/parcelas/${parcelaId}/pagar`, + { + dataPagamento: new Date().toISOString().split("T")[0], + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); if (!response.ok) { - throw new Error('Erro ao registrar pagamento'); + throw new Error("Erro ao registrar pagamento"); } - toast.success('Pagamento registrado com sucesso!'); + toast.success("Pagamento registrado com sucesso!"); onUpdate(); // Recarregar dados do cálculo } catch (error) { - console.error('Erro ao registrar pagamento:', error); + console.error("Erro ao registrar pagamento:", error); toast.error(`Falha ao registrar pagamento: ${error.message}`); } }; const handleDelete = async (parcelaId: string) => { - if (!confirm('Deseja realmente excluir esta parcela?')) return; + if (!confirm("Deseja realmente excluir esta parcela?")) return; try { await api.delete(`/calculos/${calculo.id}/parcelas/${parcelaId}`); - toast.success('Parcela removida com sucesso'); + toast.success("Parcela removida com sucesso"); onUpdate(); } catch (error) { - console.error('Erro ao excluir parcela:', error); - toast.error('Falha ao excluir parcela'); + console.error("Erro ao excluir parcela:", error); + toast.error("Falha ao excluir parcela"); } }; @@ -1238,13 +1514,18 @@ function ParcelasTab({ Parcelas - {calculo.status !== 'FINALIZADO' && ( + {calculo.status !== "FINALIZADO" && ( - Adicionar Parcela + + Adicionar Parcela + - + Número da Parcela - + Data de Vencimento - + Valor Original @@ -1301,7 +1588,7 @@ function ParcelasTab({ className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" disabled={loading} > - {loading ? 'Adicionando...' : 'Adicionar Parcela'} + {loading ? "Adicionando..." : "Adicionar Parcela"} @@ -1309,49 +1596,111 @@ function ParcelasTab({ )} - Parcelas do Cálculo + + Parcelas do Cálculo + {calculo.parcelas && calculo.parcelas.length > 0 ? ( - Nº - Vencimento - Valor Original - Correção - Juros - Multa - Total - Status - Ações + + Nº + + + Vencimento + + + Valor Original + + + Correção + + + Juros + + + Multa + + + Total + + + Status + + + Ações + {calculo.parcelas.map((parcela, index) => ( - {parcela.numero} - {formatarData(parcela.dataVencimento)} - {formatarValor(parcela.valorOriginal)} - {formatarValor(parseFloat(parcela.valorCorrigido) - parseFloat(parcela.valorOriginal))} + {parcela.numero} + + + {formatarData(parcela.dataVencimento)} + + + {formatarValor(parcela.valorOriginal)} + + + {formatarValor( + parseFloat(parcela.valorCorrigido) - + parseFloat(parcela.valorOriginal), + )} + + + {formatarValor(parcela.valorJuros)} + + + {formatarValor(parcela.valorMulta)} + + + {formatarValor(parcela.valorTotal)} - {formatarValor(parcela.valorJuros)} - {formatarValor(parcela.valorMulta)} - {formatarValor(parcela.valorTotal)} - {parcela.pago ? 'Pago' : 'Em aberto'} + {parcela.pago ? "Pago" : "Em aberto"} - {!parcela.pago && calculo.status !== 'FINALIZADO' && ( + {!parcela.pago && calculo.status !== "FINALIZADO" && ( registrarPagamento(parcela.id)} className="text-indigo-600 hover:text-indigo-900" @@ -1378,7 +1727,9 @@ function ParcelasTab({ ) : ( - Nenhuma parcela cadastrada. + + Nenhuma parcela cadastrada. + )} @@ -1388,7 +1739,7 @@ function ParcelasTab({ function HonorariosTab({ calculo, onUpdate, - formatarValor = formatarMoeda + formatarValor = formatarMoeda, }: { calculo: Calculo; onUpdate: () => void; @@ -1397,22 +1748,26 @@ function HonorariosTab({ const { getToken } = useAuth(); const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ - tipo: 'SUCUMBENCIAL', - percentual: '10,00', + tipo: "SUCUMBENCIAL", + percentual: "10,00", valorBase: calculo.valorBase, - fundamento: '' + fundamento: "", }); - const handleChange = (e: React.ChangeEvent) => { + const handleChange = ( + e: React.ChangeEvent< + HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement + >, + ) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); }; const handlePercentualChange = (e: React.ChangeEvent) => { const { value } = e.target; // Permitir apenas dígitos, vírgula, e ponto - const numericValue = value.replace(/[^\d.,]/g, ''); - setFormData(prev => ({ ...prev, percentual: numericValue })); + const numericValue = value.replace(/[^\d.,]/g, ""); + setFormData((prev) => ({ ...prev, percentual: numericValue })); }; const handleSubmit = async (e: React.FormEvent) => { @@ -1422,28 +1777,35 @@ function HonorariosTab({ try { const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado. Faça login novamente.'); + throw new Error("Usuário não autenticado. Faça login novamente."); } // Converter percentual para decimal (ex: 10,00% -> 0.1) - const percentual = parseFloat(formData.percentual.replace(',', '.')) / 100; + const percentual = + parseFloat(formData.percentual.replace(",", ".")) / 100; // Usar o serviço api em vez de fetch direto - await api.post(`/calculos/${calculo.id}/honorarios`, { - tipo: formData.tipo, - percentual: percentual, - valorBase: parseFloat(formData.valorBase.replace(/[^\d.,]/g, '').replace(',', '.')), - fundamento: formData.fundamento - }, { - headers: { - 'Authorization': `Bearer ${token}` - } - }); + await api.post( + `/calculos/${calculo.id}/honorarios`, + { + tipo: formData.tipo, + percentual: percentual, + valorBase: parseFloat( + formData.valorBase.replace(/[^\d.,]/g, "").replace(",", "."), + ), + fundamento: formData.fundamento, + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); - toast.success('Honorários adicionados com sucesso!'); + toast.success("Honorários adicionados com sucesso!"); onUpdate(); // Recarregar dados do cálculo } catch (error) { - console.error('Erro ao adicionar honorários:', error); + console.error("Erro ao adicionar honorários:", error); toast.error(`Falha ao adicionar honorários: ${error.message}`); } finally { setLoading(false); @@ -1451,29 +1813,36 @@ function HonorariosTab({ }; const handleDelete = async (honorarioId: string) => { - if (!confirm('Deseja realmente excluir este honorário?')) return; + if (!confirm("Deseja realmente excluir este honorário?")) return; try { await api.delete(`/calculos/${calculo.id}/honorarios/${honorarioId}`); - toast.success('Honorário removido com sucesso'); + toast.success("Honorário removido com sucesso"); onUpdate(); } catch (error) { - console.error('Erro ao excluir honorário:', error); - toast.error('Falha ao excluir honorário'); + console.error("Erro ao excluir honorário:", error); + toast.error("Falha ao excluir honorário"); } }; return ( - Honorários Advocatícios + + Honorários Advocatícios + - {calculo.status !== 'FINALIZADO' && ( + {calculo.status !== "FINALIZADO" && ( - Adicionar Honorários + + Adicionar Honorários + - + Tipo - + Percentual @@ -1511,7 +1883,10 @@ function HonorariosTab({ - + Valor Base @@ -1531,7 +1906,10 @@ function HonorariosTab({ - + Fundamento Legal/Contratual - {loading ? 'Adicionando...' : 'Adicionar Honorários'} + {loading ? "Adicionando..." : "Adicionar Honorários"} @@ -1559,31 +1937,71 @@ function HonorariosTab({ )} - Honorários Aplicados + + Honorários Aplicados + {calculo.honorarios && calculo.honorarios.length > 0 ? ( - Tipo - Percentual - Valor Base - Valor Honorários - Fundamento - Ações + + Tipo + + + Percentual + + + Valor Base + + + Valor Honorários + + + Fundamento + + + Ações + {calculo.honorarios.map((honorario, index) => ( - {honorario.tipo} + + {honorario.tipo} + {(parseFloat(honorario.percentual) * 100).toFixed(2)}% - {formatarValor(honorario.valorBase)} - {formatarValor(honorario.valorHonorario)} - {honorario.fundamento || '-'} + + {formatarValor(honorario.valorBase)} + + + {formatarValor(honorario.valorHonorario)} + + + {honorario.fundamento || "-"} + ) : ( - Nenhum honorário aplicado. + + Nenhum honorário aplicado. + )} @@ -1611,7 +2031,7 @@ function HonorariosTab({ function DescontosTab({ calculo, onUpdate, - formatarValor = formatarMoeda + formatarValor = formatarMoeda, }: { calculo: Calculo; onUpdate: () => void; @@ -1620,33 +2040,37 @@ function DescontosTab({ const { getToken } = useAuth(); const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ - tipo: 'INSS', - descricao: '', - percentual: '', - valorFixo: '', + tipo: "INSS", + descricao: "", + percentual: "", + valorFixo: "", valorBase: calculo.valorBase, - usaPercentual: true + usaPercentual: true, }); - const handleChange = (e: React.ChangeEvent) => { + const handleChange = ( + e: React.ChangeEvent< + HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement + >, + ) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); }; const toggleTipoDesconto = (usaPercentual: boolean) => { - setFormData(prev => ({ + setFormData((prev) => ({ ...prev, usaPercentual, - percentual: usaPercentual ? prev.percentual : '', - valorFixo: !usaPercentual ? prev.valorFixo : '' + percentual: usaPercentual ? prev.percentual : "", + valorFixo: !usaPercentual ? prev.valorFixo : "", })); }; const handlePercentualChange = (e: React.ChangeEvent) => { const { value } = e.target; // Permitir apenas dígitos, vírgula, e ponto - const numericValue = value.replace(/[^\d.,]/g, ''); - setFormData(prev => ({ ...prev, percentual: numericValue })); + const numericValue = value.replace(/[^\d.,]/g, ""); + setFormData((prev) => ({ ...prev, percentual: numericValue })); }; const handleSubmit = async (e: React.FormEvent) => { @@ -1658,61 +2082,67 @@ function DescontosTab({ const dadosEnvio = { tipo: formData.tipo, descricao: formData.descricao, - valorBase: parseFloat(formData.valorBase.toString().replace(/\./g, '').replace(',', '.')), + valorBase: parseFloat( + formData.valorBase.toString().replace(/\./g, "").replace(",", "."), + ), }; // Adicionar percentual ou valor fixo dependendo da escolha if (formData.usaPercentual) { - dadosEnvio.percentual = parseFloat(formData.percentual.replace(/\./g, '').replace(',', '.')) / 100; + dadosEnvio.percentual = + parseFloat(formData.percentual.replace(/\./g, "").replace(",", ".")) / + 100; // Se percentual não for um número válido if (isNaN(dadosEnvio.percentual)) { - throw new Error('Percentual inválido'); + throw new Error("Percentual inválido"); } } else { - dadosEnvio.valorFixo = parseFloat(formData.valorFixo.replace(/\./g, '').replace(',', '.')); + dadosEnvio.valorFixo = parseFloat( + formData.valorFixo.replace(/\./g, "").replace(",", "."), + ); // Se valor fixo não for um número válido if (isNaN(dadosEnvio.valorFixo)) { - throw new Error('Valor fixo inválido'); + throw new Error("Valor fixo inválido"); } } // Adicionar logs para depuração - console.log('Enviando dados de desconto:', dadosEnvio); + console.log("Enviando dados de desconto:", dadosEnvio); // Fazer a requisição const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado'); + throw new Error("Usuário não autenticado"); } const response = await api.post( `/calculos/${calculo.id}/descontos`, dadosEnvio, { - headers: { 'Authorization': `Bearer ${token}` }, - timeout: 30000 // 30 segundos - } + headers: { Authorization: `Bearer ${token}` }, + timeout: 30000, // 30 segundos + }, ); - console.log('Desconto adicionado:', response.data); - toast.success('Desconto adicionado com sucesso!'); + console.log("Desconto adicionado:", response.data); + toast.success("Desconto adicionado com sucesso!"); // Resetar o formulário setFormData({ - tipo: 'INSS', - descricao: '', - percentual: '', - valorFixo: '', + tipo: "INSS", + descricao: "", + percentual: "", + valorFixo: "", valorBase: calculo.valorBase, - usaPercentual: true + usaPercentual: true, }); // Atualizar a lista onUpdate(); } catch (error) { - console.error('Erro ao adicionar desconto:', error); + console.error("Erro ao adicionar desconto:", error); - let mensagemErro = 'Erro ao adicionar desconto'; + let mensagemErro = "Erro ao adicionar desconto"; if (error instanceof Error) { mensagemErro = error.message; } @@ -1724,7 +2154,7 @@ function DescontosTab({ }; const handleDelete = async (descontoId: string) => { - if (!confirm('Deseja realmente excluir este desconto?')) { + if (!confirm("Deseja realmente excluir este desconto?")) { return; } @@ -1732,15 +2162,15 @@ function DescontosTab({ setLoading(true); const response = await api.delete( - `/calculos/${calculo.id}/descontos/${descontoId}` + `/calculos/${calculo.id}/descontos/${descontoId}`, ); - toast.success('Desconto excluído com sucesso!'); + toast.success("Desconto excluído com sucesso!"); onUpdate(); // Atualizar a lista } catch (error) { - console.error('Erro ao excluir desconto:', error); + console.error("Erro ao excluir desconto:", error); - let mensagemErro = 'Erro ao excluir desconto'; + let mensagemErro = "Erro ao excluir desconto"; if (axios.isAxiosError(error) && error.response) { // Se tiver uma mensagem de erro específica na resposta if (error.response.data && error.response.data.error) { @@ -1758,13 +2188,18 @@ function DescontosTab({ Descontos - {calculo.status !== 'FINALIZADO' && ( + {calculo.status !== "FINALIZADO" && ( - Adicionar Desconto + + Adicionar Desconto + - + Tipo - + Descrição - + Tipo de Desconto @@ -1810,7 +2251,10 @@ function DescontosTab({ onChange={() => toggleTipoDesconto(true)} className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" /> - + Percentual @@ -1823,7 +2267,10 @@ function DescontosTab({ onChange={() => toggleTipoDesconto(false)} className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" /> - + Valor Fixo @@ -1832,7 +2279,10 @@ function DescontosTab({ {formData.usaPercentual ? ( - + Percentual @@ -1852,7 +2302,10 @@ function DescontosTab({ ) : ( - + Valor Fixo @@ -1873,7 +2326,10 @@ function DescontosTab({ )} - + Valor Base @@ -1899,7 +2355,7 @@ function DescontosTab({ className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" disabled={loading} > - {loading ? 'Adicionando...' : 'Adicionar Desconto'} + {loading ? "Adicionando..." : "Adicionar Desconto"} @@ -1907,34 +2363,73 @@ function DescontosTab({ )} - Descontos Aplicados + + Descontos Aplicados + {calculo.descontos && calculo.descontos.length > 0 ? ( - Tipo - Descrição - Base de Cálculo - Forma - Valor Desconto - Ações + + Tipo + + + Descrição + + + Base de Cálculo + + + Forma + + + Valor Desconto + + + Ações + {calculo.descontos.map((desconto, index) => ( - {desconto.tipo} - {desconto.descricao || '-'} - {formatarValor(desconto.valorBase)} + + {desconto.tipo} + + + {desconto.descricao || "-"} + + + {formatarValor(desconto.valorBase)} + {desconto.percentual ? `${(parseFloat(desconto.percentual) * 100).toFixed(2)}%` - : `Valor Fixo: ${formatarValor(desconto.valorFixo)}` - } + : `Valor Fixo: ${formatarValor(desconto.valorFixo)}`} + + + {formatarValor(desconto.valorDesconto)} - {formatarValor(desconto.valorDesconto)} ) : ( - Nenhum desconto aplicado. + + Nenhum desconto aplicado. + )} @@ -1961,17 +2458,21 @@ function DescontosTab({ function HistoricoTab({ calculo, - formatarValor + formatarValor, }: { calculo: Calculo; formatarValor: (valor: string) => string; }) { return ( - Histórico de Alterações + + Histórico de Alterações + {calculo.historico.length === 0 ? ( - Nenhum registro de alteração encontrado. + + Nenhum registro de alteração encontrado. + ) : ( @@ -1983,17 +2484,25 @@ function HistoricoTab({ {item.descricao} - {format(new Date(item.criadoEm), 'dd/MM/yyyy HH:mm:ss', { locale: ptBR })} - {item.usuario ? ` por ${item.usuario.nome}` : ''} + {format(new Date(item.criadoEm), "dd/MM/yyyy HH:mm:ss", { + locale: ptBR, + })} + {item.usuario ? ` por ${item.usuario.nome}` : ""} {(item.valorAnterior || item.valorNovo) && ( {item.valorAnterior && ( - Valor anterior: {formatarValor(item.valorAnterior)} + + Valor anterior: {formatarValor(item.valorAnterior)} + + )} + {item.valorAnterior && item.valorNovo && ( + → )} - {item.valorAnterior && item.valorNovo && →} {item.valorNovo && ( - Novo valor: {formatarValor(item.valorNovo)} + + Novo valor: {formatarValor(item.valorNovo)} + )} )} diff --git a/src/components/calculos/NovoCalculo.tsx b/src/components/calculos/NovoCalculo.tsx index a7a0c72..83ee155 100644 --- a/src/components/calculos/NovoCalculo.tsx +++ b/src/components/calculos/NovoCalculo.tsx @@ -1,8 +1,9 @@ -import { useState, useEffect, useRef } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { toast } from 'react-toastify'; -import { formatarMoeda, parseMoeda } from '../../utils/formatters'; -import { useAuth } from '../../contexts/AuthContext'; +import { useState, useEffect, useRef } from "react"; +import { useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; + +import { useAuth } from "../../contexts/AuthContext"; +import { formatarMoeda, parseMoeda } from "../../utils/formatters"; interface ProcessoOption { id: string; @@ -21,160 +22,174 @@ export function NovoCalculo() { const { getToken } = useAuth(); const valorBaseRef = useRef(null); const [formData, setFormData] = useState({ - titulo: '', - descricao: '', - dataBase: new Date().toISOString().split('T')[0], - valorBase: '', - processoId: '', - clienteId: '', + titulo: "", + descricao: "", + dataBase: new Date().toISOString().split("T")[0], + valorBase: "", + processoId: "", + clienteId: "", }); - + const [processos, setProcessos] = useState([]); const [clientes, setClientes] = useState([]); - + // Carregar processos e clientes ao montar o componente useEffect(() => { const carregarDados = async () => { try { // Carregar processos - const processosResponse = await fetch('/api/processos'); + const processosResponse = await fetch("/api/processos"); if (processosResponse.ok) { const processosData = await processosResponse.json(); setProcessos(processosData); } - + // Carregar clientes - const clientesResponse = await fetch('/api/clientes'); + const clientesResponse = await fetch("/api/clientes"); if (clientesResponse.ok) { const clientesData = await clientesResponse.json(); setClientes(clientesData); } } catch (error) { - console.error('Erro ao carregar dados:', error); - toast.error('Falha ao carregar dados auxiliares'); + console.error("Erro ao carregar dados:", error); + toast.error("Falha ao carregar dados auxiliares"); } }; - + carregarDados(); }, []); - - const handleChange = (e: React.ChangeEvent) => { + + const handleChange = ( + e: React.ChangeEvent< + HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement + >, + ) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); }; - + // Handler específico para campos monetários com controle de cursor const handleMoneyChange = (e: React.ChangeEvent) => { const { name, value } = e.target; - + // Salvar a posição atual do cursor antes da formatação const cursorPosition = e.target.selectionStart; const prevLength = e.target.value.length; - + // Remover caracteres não numéricos, exceto vírgula e ponto - const numericValue = value.replace(/[^\d.,]/g, ''); - + const numericValue = value.replace(/[^\d.,]/g, ""); + // Formatar como moeda const formattedValue = formatarMoeda(numericValue); - - setFormData(prev => ({ ...prev, [name]: formattedValue })); - + + setFormData((prev) => ({ ...prev, [name]: formattedValue })); + // Usar setTimeout para ajustar a posição do cursor após a atualização do DOM setTimeout(() => { if (valorBaseRef.current) { // Calcular a nova posição considerando a mudança no comprimento const lengthDifference = formattedValue.length - prevLength; - const newPosition = Math.max(0, (cursorPosition || 0) + lengthDifference); - + const newPosition = Math.max( + 0, + (cursorPosition || 0) + lengthDifference, + ); + // Definir o cursor na posição calculada valorBaseRef.current.setSelectionRange(newPosition, newPosition); } }, 0); }; - + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); - + try { // Validar dados if (!formData.titulo || !formData.dataBase || !formData.valorBase) { - toast.error('Preencha todos os campos obrigatórios'); + toast.error("Preencha todos os campos obrigatórios"); setLoading(false); return; } - + // Converter valor para número const valorBaseNumerico = parseMoeda(formData.valorBase); if (isNaN(valorBaseNumerico) || valorBaseNumerico <= 0) { - toast.error('Valor base inválido. Informe um valor maior que zero'); + toast.error("Valor base inválido. Informe um valor maior que zero"); setLoading(false); return; } - + // Preparar dados para envio const dadosParaEnviar = { ...formData, - valorBase: valorBaseNumerico + valorBase: valorBaseNumerico, }; - + // Obter token de autenticação usando o contexto const token = getToken(); if (!token) { - throw new Error('Usuário não autenticado. Faça login novamente.'); + throw new Error("Usuário não autenticado. Faça login novamente."); } - + // Enviar solicitação para criar cálculo com cabeçalho de autorização - const response = await fetch('/api/calculos', { - method: 'POST', + const response = await fetch("/api/calculos", { + method: "POST", headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}` + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, }, body: JSON.stringify(dadosParaEnviar), }); - + if (!response.ok) { const errorData = await response.json(); - throw new Error(errorData.error || 'Erro ao criar cálculo'); + throw new Error(errorData.error || "Erro ao criar cálculo"); } - + const resultado = await response.json(); - - toast.success('Cálculo criado com sucesso!'); - + + toast.success("Cálculo criado com sucesso!"); + // Redirecionar para a página do cálculo criado navigate(`/calculos/${resultado.id}`); } catch (error) { - console.error('Erro ao criar cálculo:', error); + console.error("Erro ao criar cálculo:", error); toast.error(`Falha ao criar cálculo: ${error.message}`); - - if (error.message?.includes('autenticação') || - error.message?.includes('não autenticado') || - error.response?.status === 401) { + + if ( + error.message?.includes("autenticação") || + error.message?.includes("não autenticado") || + error.response?.status === 401 + ) { // Se for erro de autenticação, redirecionar para login - navigate('/login'); + navigate("/login"); } } finally { setLoading(false); } }; - + return ( Novo Cálculo Judicial - Preencha os dados básicos para criar um novo cálculo + + Preencha os dados básicos para criar um novo cálculo + - + {/* Título */} - + Título * - - + {/* Data Base */} - + Data Base * - - Data de referência para iniciar o cálculo + + Data de referência para iniciar o cálculo + - + {/* Valor Base */} - + Valor Base * R$ - - Valor inicial para o cálculo + + Valor inicial para o cálculo + - + {/* Processo */} - + Processo Selecione um processo (opcional) - {processos.map(processo => ( + {processos.map((processo) => ( {processo.numero} - {processo.titulo} ))} - + {/* Cliente */} - + Cliente Selecione um cliente (opcional) - {clientes.map(cliente => ( + {clientes.map((cliente) => ( {cliente.nome} ))} - + {/* Descrição */} - + Descrição - + navigate('/calculos')} + onClick={() => navigate("/calculos")} disabled={loading} className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" > @@ -301,12 +335,28 @@ export function NovoCalculo() { className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 flex items-center" > {loading && ( - - - + + + )} - {loading ? 'Criando...' : 'Criar Cálculo'} + {loading ? "Criando..." : "Criar Cálculo"} diff --git a/src/components/calculos/NovoPensaoAlimenticia.tsx b/src/components/calculos/NovoPensaoAlimenticia.tsx index f8b90bc..a734325 100644 --- a/src/components/calculos/NovoPensaoAlimenticia.tsx +++ b/src/components/calculos/NovoPensaoAlimenticia.tsx @@ -1,7 +1,8 @@ -import { useState, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { toast } from 'react-toastify'; -import { formatarPercentual, parsePercentual } from '../../utils/formatters'; +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; + +import { formatarPercentual, parsePercentual } from "../../utils/formatters"; interface ProcessoOption { id: string; @@ -18,134 +19,155 @@ export function NovoPensaoAlimenticia() { const navigate = useNavigate(); const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ - titulo: '', - descricao: '', - dataInicial: new Date().toISOString().split('T')[0], - dataFinal: '', - percentualSalarioMinimo: '', - multaPactuada: '', - tipoAtualizacao: 'INPC', - processoId: '', - clienteId: '', - observacoes: '', + titulo: "", + descricao: "", + dataInicial: new Date().toISOString().split("T")[0], + dataFinal: "", + percentualSalarioMinimo: "", + multaPactuada: "", + tipoAtualizacao: "INPC", + processoId: "", + clienteId: "", + observacoes: "", }); - + const [processos, setProcessos] = useState([]); const [clientes, setClientes] = useState([]); - + // Carregar processos e clientes ao montar o componente useEffect(() => { const carregarDados = async () => { try { // Carregar processos - const processosResponse = await fetch('/api/processos'); + const processosResponse = await fetch("/api/processos"); if (processosResponse.ok) { const processosData = await processosResponse.json(); setProcessos(processosData); } - + // Carregar clientes - const clientesResponse = await fetch('/api/clientes'); + const clientesResponse = await fetch("/api/clientes"); if (clientesResponse.ok) { const clientesData = await clientesResponse.json(); setClientes(clientesData); } } catch (error) { - console.error('Erro ao carregar dados:', error); - toast.error('Falha ao carregar dados auxiliares'); + console.error("Erro ao carregar dados:", error); + toast.error("Falha ao carregar dados auxiliares"); } }; - + carregarDados(); }, []); - - const handleChange = (e: React.ChangeEvent) => { + + const handleChange = ( + e: React.ChangeEvent< + HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement + >, + ) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); }; - + // Handler para campos percentuais const handlePercentChange = (e: React.ChangeEvent) => { const { name, value } = e.target; // Remover caracteres não numéricos, exceto vírgula e ponto - const numericValue = value.replace(/[^\d.,]/g, ''); + const numericValue = value.replace(/[^\d.,]/g, ""); // Formatar como percentual const formattedValue = formatarPercentual(numericValue); - setFormData(prev => ({ ...prev, [name]: formattedValue })); + setFormData((prev) => ({ ...prev, [name]: formattedValue })); }; - + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); - + try { // Validar dados - if (!formData.titulo || !formData.dataInicial || !formData.percentualSalarioMinimo) { - toast.error('Preencha todos os campos obrigatórios'); + if ( + !formData.titulo || + !formData.dataInicial || + !formData.percentualSalarioMinimo + ) { + toast.error("Preencha todos os campos obrigatórios"); setLoading(false); return; } - + // Converter percentual para decimal (formato 0.XX) const percentual = parsePercentual(formData.percentualSalarioMinimo); - + // Verificar se percentual é válido if (isNaN(percentual) || percentual <= 0 || percentual > 1) { - toast.error('Percentual do salário mínimo deve ser maior que 0 e menor ou igual a 100%'); + toast.error( + "Percentual do salário mínimo deve ser maior que 0 e menor ou igual a 100%", + ); setLoading(false); return; } - + // Preparar dados para envio const dadosParaEnviar = { ...formData, percentualSalarioMinimo: percentual, - multaPactuada: formData.multaPactuada ? parsePercentual(formData.multaPactuada) : undefined, + multaPactuada: formData.multaPactuada + ? parsePercentual(formData.multaPactuada) + : undefined, }; - + // Enviar solicitação para criar cálculo - const response = await fetch('/api/pensao-alimenticia', { - method: 'POST', + const response = await fetch("/api/pensao-alimenticia", { + method: "POST", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, body: JSON.stringify(dadosParaEnviar), }); - + if (!response.ok) { const errorData = await response.json(); - throw new Error(errorData.error || 'Erro ao criar cálculo de pensão alimentícia'); + throw new Error( + errorData.error || "Erro ao criar cálculo de pensão alimentícia", + ); } - + const resultado = await response.json(); - - toast.success('Cálculo de pensão alimentícia criado com sucesso!'); - + + toast.success("Cálculo de pensão alimentícia criado com sucesso!"); + // Redirecionar para a página do cálculo criado navigate(`/pensao-alimenticia/${resultado.id}`); } catch (error) { - console.error('Erro ao criar cálculo de pensão:', error); + console.error("Erro ao criar cálculo de pensão:", error); toast.error(`Falha ao criar cálculo: ${error.message}`); } finally { setLoading(false); } }; - + return ( - Novo Cálculo de Pensão Alimentícia - Preencha os dados para criar um novo cálculo de pensão alimentícia + + Novo Cálculo de Pensão Alimentícia + + + Preencha os dados para criar um novo cálculo de pensão alimentícia + - + {/* Título */} - + Título * - - + {/* Índice de Atualização */} - + Índice de Atualização Monetária - INPC (Índice Nacional de Preços ao Consumidor) - IPCA (Índice Nacional de Preços ao Consumidor Amplo) - IGP-M (Índice Geral de Preços do Mercado) - SELIC (Sistema Especial de Liquidação e Custódia) + + INPC (Índice Nacional de Preços ao Consumidor) + + + IPCA (Índice Nacional de Preços ao Consumidor Amplo) + + + IGP-M (Índice Geral de Preços do Mercado) + + + SELIC (Sistema Especial de Liquidação e Custódia) + - + {/* Percentual do Salário Mínimo */} - - Percentual do Salário Mínimo * + + Percentual do Salário Mínimo{" "} + * - Ex: 30% = 30,00% - + {/* Data Inicial */} - + Data Inicial * - - Data de início da pensão + + Data de início da pensão + - + {/* Data Final */} - + Data Final - - Deixe em branco se a pensão não tem data final + + Deixe em branco se a pensão não tem data final + - + {/* Multa Pactuada */} - + Multa Pactuada por Atraso - % - Ex: 10% = 10,00%. Deixe em branco se não houver. + + Ex: 10% = 10,00%. Deixe em branco se não houver. + - + {/* Processo */} - + Processo Selecione um processo (opcional) - {processos.map(processo => ( + {processos.map((processo) => ( {processo.numero} - {processo.titulo} ))} - + {/* Cliente */} - + Cliente Selecione um cliente (opcional) - {clientes.map(cliente => ( + {clientes.map((cliente) => ( {cliente.nome} ))} - + {/* Observações */} - + Observações - + navigate('/calculos')} + onClick={() => navigate("/calculos")} disabled={loading} className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" > @@ -328,12 +389,28 @@ export function NovoPensaoAlimenticia() { className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 flex items-center" > {loading && ( - - - + + + )} - {loading ? 'Criando...' : 'Criar Cálculo de Pensão'} + {loading ? "Criando..." : "Criar Cálculo de Pensão"} diff --git a/src/components/clientes/ClienteDetalhes.tsx b/src/components/clientes/ClienteDetalhes.tsx index e3413c2..5df599b 100644 --- a/src/components/clientes/ClienteDetalhes.tsx +++ b/src/components/clientes/ClienteDetalhes.tsx @@ -1,6 +1,3 @@ -import React, { useState } from "react"; -import { useParams, useNavigate } from "react-router-dom"; -import { useClientes } from "../../hooks/useClientes"; import { Tab } from "@headlessui/react"; import { ArrowLeftIcon, @@ -11,7 +8,10 @@ import { UserGroupIcon, DocumentTextIcon, } from "@heroicons/react/24/outline"; -import { ClienteModal } from "./ClienteModal"; +import React, { useState } from "react"; +import { useParams, useNavigate } from "react-router-dom"; + +import { useClientes } from "../../hooks/useClientes"; import { formatCPF, formatTelefone, @@ -22,6 +22,8 @@ import { formatData, } from "../../utils/formatters"; +import { ClienteModal } from "./ClienteModal"; + function classNames(...classes: string[]) { return classes.filter(Boolean).join(" "); } diff --git a/src/components/clientes/ClienteModal.tsx b/src/components/clientes/ClienteModal.tsx index e4c0c7e..0cd3d7d 100644 --- a/src/components/clientes/ClienteModal.tsx +++ b/src/components/clientes/ClienteModal.tsx @@ -1,3 +1,6 @@ +import { Dialog, Transition } from "@headlessui/react"; +import { XMarkIcon } from "@heroicons/react/24/outline"; +import axios from "axios"; import React, { useState, useEffect, @@ -5,12 +8,10 @@ import React, { forwardRef, useRef, } from "react"; -import { Dialog, Transition } from "@headlessui/react"; -import { XMarkIcon } from "@heroicons/react/24/outline"; import { useForm } from "react-hook-form"; -import axios from "axios"; -import { Cliente } from "../../types/entities"; + import { useClientes } from "../../hooks/useClientes"; +import { Cliente } from "../../types/entities"; import { ORGAOS_EMISSORES, PREVIDENCIA_OPTIONS } from "../../utils/constants"; const estadosCivis = [ @@ -201,73 +202,77 @@ export const ClienteModal: React.FC = ({ const cepValue = watch("cep"); // Função para buscar dados do CEP - versão corrigida - const fetchAddressByCep = useCallback(async (cep: string) => { - if (!cep || cep.length !== 8) return; + const fetchAddressByCep = useCallback( + async (cep: string) => { + if (!cep || cep.length !== 8) return; - try { - setIsLoadingCep(true); - const formattedCep = cep.replace(/\D/g, ""); // Remove caracteres não numéricos - const response = await axios.get( - `https://viacep.com.br/ws/${formattedCep}/json/`, - ); + try { + setIsLoadingCep(true); + const formattedCep = cep.replace(/\D/g, ""); // Remove caracteres não numéricos + const response = await axios.get( + `https://viacep.com.br/ws/${formattedCep}/json/`, + ); - if (response.data && !response.data.erro) { - // Usar estado local para garantir re-renderização imediata - const endereco = response.data.logradouro || ""; - const bairro = response.data.bairro || ""; - const cidade = response.data.localidade || ""; - const estado = response.data.uf || ""; + if (response.data && !response.data.erro) { + // Usar estado local para garantir re-renderização imediata + const endereco = response.data.logradouro || ""; + const bairro = response.data.bairro || ""; + const cidade = response.data.localidade || ""; + const estado = response.data.uf || ""; - // Atualizar valores no formulário - setValue("endereco", endereco, { shouldValidate: true }); - setValue("bairro", bairro, { shouldValidate: true }); - setValue("cidade", cidade, { shouldValidate: true }); - setValue("estado", estado, { shouldValidate: true }); + // Atualizar valores no formulário + setValue("endereco", endereco, { shouldValidate: true }); + setValue("bairro", bairro, { shouldValidate: true }); + setValue("cidade", cidade, { shouldValidate: true }); + setValue("estado", estado, { shouldValidate: true }); - // Atualizar o estado para re-renderização do componente - setEnderecoState({ - endereco, - bairro, - cidade, - estado, - preenchido: true, - }); + // Atualizar o estado para re-renderização do componente + setEnderecoState({ + endereco, + bairro, + cidade, + estado, + preenchido: true, + }); - // Focar no próximo campo apenas se o usuário não estiver digitando ativamente - // Esta alteração evita interromper o fluxo natural de preenchimento - const activeElement = document.activeElement; - const isUserTyping = - activeElement && - (activeElement.tagName === 'INPUT' || activeElement.tagName === 'SELECT') && - activeElement !== document.querySelector('input[name="cep"]'); - - // Só focar no campo número se o usuário não estiver digitando em outro campo - if (!isUserTyping) { - setTimeout(() => { - const numeroInput = document.querySelector( - 'input[name="numero"]' - ) as HTMLInputElement; - if (numeroInput) numeroInput.focus(); - }, 100); + // Focar no próximo campo apenas se o usuário não estiver digitando ativamente + // Esta alteração evita interromper o fluxo natural de preenchimento + const activeElement = document.activeElement; + const isUserTyping = + activeElement && + (activeElement.tagName === "INPUT" || + activeElement.tagName === "SELECT") && + activeElement !== document.querySelector('input[name="cep"]'); + + // Só focar no campo número se o usuário não estiver digitando em outro campo + if (!isUserTyping) { + setTimeout(() => { + const numeroInput = document.querySelector( + 'input[name="numero"]', + ) as HTMLInputElement; + if (numeroInput) numeroInput.focus(); + }, 100); + } + } else { + console.error("CEP não encontrado"); + alert("CEP não encontrado. Por favor, verifique o CEP informado."); } - } else { - console.error("CEP não encontrado"); - alert("CEP não encontrado. Por favor, verifique o CEP informado."); + } catch (error) { + console.error("Erro ao buscar CEP:", error); + alert("Erro ao buscar informações do CEP. Por favor, tente novamente."); + } finally { + setIsLoadingCep(false); } - } catch (error) { - console.error("Erro ao buscar CEP:", error); - alert("Erro ao buscar informações do CEP. Por favor, tente novamente."); - } finally { - setIsLoadingCep(false); - } - }, [setValue]); + }, + [setValue], + ); // Efeito para monitorar mudanças no CEP com debounce otimizado const [autoFetchEnabled, setAutoFetchEnabled] = useState(true); useEffect(() => { if (!autoFetchEnabled) return; - + // Limpar qualquer timer existente const timer = setTimeout(() => { const cleanCep = cepValue?.replace(/\D/g, ""); @@ -584,7 +589,10 @@ export const ClienteModal: React.FC = ({ }} onBlur={(e) => { // Forçar busca ao sair do campo - const cleanCep = e.target.value.replace(/\D/g, ""); + const cleanCep = e.target.value.replace( + /\D/g, + "", + ); if (cleanCep && cleanCep.length === 8) { fetchAddressByCep(cleanCep); } @@ -622,10 +630,17 @@ export const ClienteModal: React.FC = ({ required: "Endereço é obrigatório", })} error={errors.endereco?.message} - value={enderecoState.preenchido ? enderecoState.endereco : undefined} + value={ + enderecoState.preenchido + ? enderecoState.endereco + : undefined + } onChange={(e) => { setValue("endereco", e.target.value); - setEnderecoState(prev => ({...prev, endereco: e.target.value})); + setEnderecoState((prev) => ({ + ...prev, + endereco: e.target.value, + })); }} /> = ({ required: "Bairro é obrigatório", })} error={errors.bairro?.message} - value={enderecoState.preenchido ? enderecoState.bairro : undefined} + value={ + enderecoState.preenchido + ? enderecoState.bairro + : undefined + } onChange={(e) => { setValue("bairro", e.target.value); - setEnderecoState(prev => ({...prev, bairro: e.target.value})); + setEnderecoState((prev) => ({ + ...prev, + bairro: e.target.value, + })); }} /> = ({ required: "Cidade é obrigatória", })} error={errors.cidade?.message} - value={enderecoState.preenchido ? enderecoState.cidade : undefined} + value={ + enderecoState.preenchido + ? enderecoState.cidade + : undefined + } onChange={(e) => { setValue("cidade", e.target.value); - setEnderecoState(prev => ({...prev, cidade: e.target.value})); + setEnderecoState((prev) => ({ + ...prev, + cidade: e.target.value, + })); }} /> = ({ })} options={estadosOptions} error={errors.estado?.message} - value={enderecoState.preenchido ? enderecoState.estado : undefined} + value={ + enderecoState.preenchido + ? enderecoState.estado + : undefined + } onChange={(e) => { setValue("estado", e.target.value); - setEnderecoState(prev => ({...prev, estado: e.target.value})); + setEnderecoState((prev) => ({ + ...prev, + estado: e.target.value, + })); }} /> diff --git a/src/components/clientes/ClientesList.tsx b/src/components/clientes/ClientesList.tsx index b6969cc..42e45d2 100644 --- a/src/components/clientes/ClientesList.tsx +++ b/src/components/clientes/ClientesList.tsx @@ -1,19 +1,23 @@ -import React, { useState, useEffect } from 'react'; -import { useQuery } from 'react-query'; -import { useNavigate } from 'react-router-dom'; -import { PencilIcon } from '@heroicons/react/24/outline'; -import api from '../../services/api'; -import { formatCPF, formatTelefone } from '../../utils/formatters'; +import { PencilIcon } from "@heroicons/react/24/outline"; +import React, { useState, useEffect } from "react"; +import { useQuery } from "react-query"; +import { useNavigate } from "react-router-dom"; + +import api from "../../services/api"; +import { formatCPF, formatTelefone } from "../../utils/formatters"; interface ClientesListProps { searchTerm: string; onEdit: (cliente: any) => void; } -export const ClientesList: React.FC = ({ searchTerm, onEdit }) => { +export const ClientesList: React.FC = ({ + searchTerm, + onEdit, +}) => { const navigate = useNavigate(); const [debouncedTerm, setDebouncedTerm] = useState(searchTerm); - + // Implementar debounce para a busca useEffect(() => { const timer = setTimeout(() => { @@ -22,39 +26,40 @@ export const ClientesList: React.FC = ({ searchTerm, onEdit } return () => clearTimeout(timer); }, [searchTerm]); - + const { data: clientes = [], isLoading } = useQuery( - ['clientes', debouncedTerm], + ["clientes", debouncedTerm], async () => { - const response = await api.get('/clientes', { + const response = await api.get("/clientes", { params: { - search: debouncedTerm.toUpperCase() - } + search: debouncedTerm.toUpperCase(), + }, }); - + // Filtrar localmente para melhor resposta let filteredClientes = response.data; if (debouncedTerm) { - const searchTerms = debouncedTerm.toUpperCase().split(' '); - filteredClientes = response.data.filter((cliente: any) => - searchTerms.every(term => - cliente.nome.includes(term) || - cliente.cpfCnpj.includes(term) || - cliente.email?.includes(term.toLowerCase()) - ) + const searchTerms = debouncedTerm.toUpperCase().split(" "); + filteredClientes = response.data.filter((cliente: any) => + searchTerms.every( + (term) => + cliente.nome.includes(term) || + cliente.cpfCnpj.includes(term) || + cliente.email?.includes(term.toLowerCase()), + ), ); } - + // Ordenar por nome - return filteredClientes.sort((a: any, b: any) => - a.nome.localeCompare(b.nome) + return filteredClientes.sort((a: any, b: any) => + a.nome.localeCompare(b.nome), ); }, { staleTime: 1000 * 60, // 1 minuto cacheTime: 1000 * 60 * 5, // 5 minutos - keepPreviousData: true // Manter dados anteriores enquanto carrega novos - } + keepPreviousData: true, // Manter dados anteriores enquanto carrega novos + }, ); if (isLoading) { @@ -100,7 +105,7 @@ export const ClientesList: React.FC = ({ searchTerm, onEdit } {clientes.map((cliente: any) => ( - navigate(`/clientes/${cliente.id}`)} diff --git a/src/components/clientes/ClientesPage.tsx b/src/components/clientes/ClientesPage.tsx index 4c93046..ef07299 100644 --- a/src/components/clientes/ClientesPage.tsx +++ b/src/components/clientes/ClientesPage.tsx @@ -1,9 +1,11 @@ -import React, { useState } from "react"; import { PlusIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; -import { ClientesList } from "./ClientesList.tsx"; -import { ClienteModal } from "./ClienteModal.tsx"; +import React, { useState } from "react"; + import { Cliente } from "../../types/entities"; +import { ClienteModal } from "./ClienteModal.tsx"; +import { ClientesList } from "./ClientesList.tsx"; + export const ClientesPage: React.FC = () => { const [isModalOpen, setIsModalOpen] = useState(false); const [searchTerm, setSearchTerm] = useState(""); diff --git a/src/components/dashboard/AlertasPrazos.tsx b/src/components/dashboard/AlertasPrazos.tsx index b2a4174..7baebfb 100644 --- a/src/components/dashboard/AlertasPrazos.tsx +++ b/src/components/dashboard/AlertasPrazos.tsx @@ -1,20 +1,22 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { format, differenceInDays } from 'date-fns'; -import { ptBR } from 'date-fns/locale'; -import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'; -import api from '../../services/api'; +import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; +import { format, differenceInDays, ptBR } from "date-fns"; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../services/api"; export const AlertasPrazos: React.FC = () => { - const { data: prazos = [] } = useQuery('prazosFuturos', async () => { - const response = await api.get('/dashboard/prazos/proximos'); + const { data: prazos = [] } = useQuery("prazosFuturos", async () => { + const response = await api.get("/dashboard/prazos/proximos"); return response.data; }); const getPrioridadeStyle = (dias: number) => { - if (dias <= 3) return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'; - if (dias <= 7) return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200'; - return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200'; + if (dias <= 3) + return "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"; + if (dias <= 7) + return "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200"; + return "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200"; }; return ( @@ -38,8 +40,12 @@ export const AlertasPrazos: React.FC = () => { Processo: {prazo.processo.numero} - - {format(new Date(prazo.dataLimite), 'dd/MM/yyyy', { locale: ptBR })} + + {format(new Date(prazo.dataLimite), "dd/MM/yyyy", { + locale: ptBR, + })} ); diff --git a/src/components/dashboard/AtividadesRecentes.tsx b/src/components/dashboard/AtividadesRecentes.tsx index 40dc627..0ecc5b8 100644 --- a/src/components/dashboard/AtividadesRecentes.tsx +++ b/src/components/dashboard/AtividadesRecentes.tsx @@ -1,22 +1,24 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { format } from 'date-fns'; -import { ptBR } from 'date-fns/locale'; -import api from '../../services/api'; +import { format, ptBR } from "date-fns"; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../services/api"; export const AtividadesRecentes: React.FC = () => { - const { data: atividades = [] } = useQuery('atividades', async () => { - const response = await api.get('/dashboard/atividades'); + const { data: atividades = [] } = useQuery("atividades", async () => { + const response = await api.get("/dashboard/atividades"); return response.data; }); const formatData = (data: string | Date | null) => { - if (!data) return ''; + if (!data) return ""; try { - return format(new Date(data), "dd 'de' MMMM 'às' HH:mm", { locale: ptBR }); + return format(new Date(data), "dd 'de' MMMM 'às' HH:mm", { + locale: ptBR, + }); } catch (error) { - console.error('Erro ao formatar data:', error); - return 'Data inválida'; + console.error("Erro ao formatar data:", error); + return "Data inválida"; } }; @@ -28,7 +30,9 @@ export const AtividadesRecentes: React.FC = () => { {atividades.map((atividade: any) => ( - {atividade.tipo === 'PROCESSO' ? '⚖️' : '📋'} + + {atividade.tipo === "PROCESSO" ? "⚖️" : "📋"} + {atividade.descricao} diff --git a/src/components/dashboard/Card.tsx b/src/components/dashboard/Card.tsx index 1a27dfb..b2a7afd 100644 --- a/src/components/dashboard/Card.tsx +++ b/src/components/dashboard/Card.tsx @@ -1,8 +1,5 @@ -import React from 'react'; -import { - ArrowUpIcon, - ArrowDownIcon, -} from '@heroicons/react/24/outline'; +import { ArrowUpIcon, ArrowDownIcon } from "@heroicons/react/24/outline"; +import React from "react"; interface CardProps { title: string; @@ -10,9 +7,9 @@ interface CardProps { trend?: number; trendLabel?: string; icon?: string; - color?: 'blue' | 'green' | 'red' | 'yellow' | 'purple'; + color?: "blue" | "green" | "red" | "yellow" | "purple"; items?: Array<{ label: string; value: string | number }>; - type?: 'default' | 'list'; + type?: "default" | "list"; } export const Card: React.FC = ({ @@ -21,27 +18,27 @@ export const Card: React.FC = ({ trend, trendLabel, icon, - color = 'blue', + color = "blue", items, - type = 'default' + type = "default", }) => { const colorClasses = { - blue: 'text-blue-600', - green: 'text-green-600', - red: 'text-red-600', - yellow: 'text-yellow-600', - purple: 'text-purple-600' + blue: "text-blue-600", + green: "text-green-600", + red: "text-red-600", + yellow: "text-yellow-600", + purple: "text-purple-600", }; const bgColorClasses = { - blue: 'bg-blue-50', - green: 'bg-green-50', - red: 'bg-red-50', - yellow: 'bg-yellow-50', - purple: 'bg-purple-50' + blue: "bg-blue-50", + green: "bg-green-50", + red: "bg-red-50", + yellow: "bg-yellow-50", + purple: "bg-purple-50", }; - if (type === 'list' && items) { + if (type === "list" && items) { return ( {title} @@ -49,7 +46,9 @@ export const Card: React.FC = ({ {items.map((item, index) => ( {item.label} - {item.value} + + {item.value} + ))} @@ -72,7 +71,9 @@ export const Card: React.FC = ({ {value} {trend !== undefined && ( - = 0 ? 'text-green-600' : 'text-red-600'}`}> + = 0 ? "text-green-600" : "text-red-600"}`} + > {trend >= 0 ? ( ) : ( @@ -80,9 +81,7 @@ export const Card: React.FC = ({ )} {Math.abs(trend)}% {trendLabel && ( - - {trendLabel} - + {trendLabel} )} )} diff --git a/src/components/dashboard/DashboardLayout.tsx b/src/components/dashboard/DashboardLayout.tsx index 4920c40..97bfaa8 100644 --- a/src/components/dashboard/DashboardLayout.tsx +++ b/src/components/dashboard/DashboardLayout.tsx @@ -1,14 +1,16 @@ -import React from 'react'; -import { useAuth } from '../../contexts/AuthContext'; -import { useDashboardStats } from '../../hooks/useDashboardStats'; -import { ProcessosStats } from './stats/ProcessosStats'; -import { TarefasStats } from './stats/TarefasStats'; -import { AtendimentosStats } from './stats/AtendimentosStats'; -import { FinanceiroStats } from './stats/FinanceiroStats'; -import { AlertasPrazos } from './AlertasPrazos'; -import { AtividadesRecentes } from './AtividadesRecentes'; -import { TaxaSucessoCard } from './TaxaSucessoCard'; -import { KPIsFinanceiros } from './stats/KPIsFinanceiros'; +import React from "react"; + +import { useAuth } from "../../contexts/AuthContext"; +import { useDashboardStats } from "../../hooks/useDashboardStats"; + +import { AlertasPrazos } from "./AlertasPrazos"; +import { AtividadesRecentes } from "./AtividadesRecentes"; +import { AtendimentosStats } from "./stats/AtendimentosStats"; +import { FinanceiroStats } from "./stats/FinanceiroStats"; +import { KPIsFinanceiros } from "./stats/KPIsFinanceiros"; +import { ProcessosStats } from "./stats/ProcessosStats"; +import { TarefasStats } from "./stats/TarefasStats"; +import { TaxaSucessoCard } from "./TaxaSucessoCard"; export const DashboardLayout: React.FC = () => { const { user } = useAuth(); @@ -18,10 +20,12 @@ export const DashboardLayout: React.FC = () => { atendimentosStats, financeiroStats, proximosPrazos, - atividades + atividades, } = useDashboardStats(); - const isAdminOrAdvogado = ['ADVOGADO_ADMIN', 'ADVOGADO'].includes(user?.perfil || ''); + const isAdminOrAdvogado = ["ADVOGADO_ADMIN", "ADVOGADO"].includes( + user?.perfil || "", + ); return ( @@ -29,7 +33,9 @@ export const DashboardLayout: React.FC = () => { {processosStats.data && } {tarefasStats.data && } - {atendimentosStats.data && } + {atendimentosStats.data && ( + + )} {isAdminOrAdvogado && financeiroStats.data && ( )} diff --git a/src/components/dashboard/KPIsAdvogados.tsx b/src/components/dashboard/KPIsAdvogados.tsx index 06c6cf8..2ded626 100644 --- a/src/components/dashboard/KPIsAdvogados.tsx +++ b/src/components/dashboard/KPIsAdvogados.tsx @@ -1,10 +1,11 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import api from '../../services/api'; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../services/api"; export const KPIsAdvogados: React.FC = () => { - const { data: kpis } = useQuery('advogadosKpis', async () => { - const response = await api.get('/dashboard/advogados/kpis'); + const { data: kpis } = useQuery("advogadosKpis", async () => { + const response = await api.get("/dashboard/advogados/kpis"); return response.data; }); @@ -19,16 +20,28 @@ export const KPIsAdvogados: React.FC = () => { - + Advogado - + Tarefas Concluídas - + Processos Concluídos - + Produtividade diff --git a/src/components/dashboard/TaxaSucessoCard.tsx b/src/components/dashboard/TaxaSucessoCard.tsx index d63712d..9f4c3e1 100644 --- a/src/components/dashboard/TaxaSucessoCard.tsx +++ b/src/components/dashboard/TaxaSucessoCard.tsx @@ -1,36 +1,46 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import api from '../../services/api'; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../services/api"; export const TaxaSucessoCard: React.FC = () => { - const { data: stats } = useQuery('taxa-sucesso', async () => { - const response = await api.get('/dashboard/processos/taxa-sucesso'); + const { data: stats } = useQuery("taxa-sucesso", async () => { + const response = await api.get("/dashboard/processos/taxa-sucesso"); return response.data; }); if (!stats) return null; const estatisticas = stats || []; - const total = estatisticas.reduce((acc: number, curr: any) => acc + (curr._count || 0), 0); - + const total = estatisticas.reduce( + (acc: number, curr: any) => acc + (curr._count || 0), + 0, + ); + return ( Taxa de Sucesso - {estatisticas.find((s: any) => s.resultado === 'PROCEDENTE')?._count - ? Math.round((estatisticas.find((s: any) => s.resultado === 'PROCEDENTE')._count / total) * 100) - : 0}% - - - Total julgados: {total} + {estatisticas.find((s: any) => s.resultado === "PROCEDENTE")?._count + ? Math.round( + (estatisticas.find((s: any) => s.resultado === "PROCEDENTE") + ._count / + total) * + 100, + ) + : 0} + % + Total julgados: {total} {estatisticas.map((item: any) => ( {item.resultado} - {Math.round((item._count / total) * 100)}% + + {Math.round((item._count / total) * 100)}% + ))} diff --git a/src/components/dashboard/charts/MapaCalor.tsx b/src/components/dashboard/charts/MapaCalor.tsx index 769d33f..a7a5dd1 100644 --- a/src/components/dashboard/charts/MapaCalor.tsx +++ b/src/components/dashboard/charts/MapaCalor.tsx @@ -1,20 +1,23 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { format } from 'date-fns'; -import { ptBR } from 'date-fns/locale'; -import api from '../../../services/api'; +import { format, ptBR } from "date-fns"; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../../services/api"; export const MapaCalor: React.FC = () => { - const { data: atividadesDiarias } = useQuery('atividadesDiarias', async () => { - const response = await api.get('/dashboard/atividades/diarias'); - return response.data; - }); + const { data: atividadesDiarias } = useQuery( + "atividadesDiarias", + async () => { + const response = await api.get("/dashboard/atividades/diarias"); + return response.data; + }, + ); const intensidadeCor = (quantidade: number) => { - if (quantidade === 0) return 'bg-gray-100 dark:bg-gray-800'; - if (quantidade <= 3) return 'bg-blue-100 dark:bg-blue-900'; - if (quantidade <= 6) return 'bg-blue-300 dark:bg-blue-700'; - return 'bg-blue-500 dark:bg-blue-500'; + if (quantidade === 0) return "bg-gray-100 dark:bg-gray-800"; + if (quantidade <= 3) return "bg-blue-100 dark:bg-blue-900"; + if (quantidade <= 6) return "bg-blue-300 dark:bg-blue-700"; + return "bg-blue-500 dark:bg-blue-500"; }; return ( diff --git a/src/components/dashboard/charts/PrevisaoReceitasChart.tsx b/src/components/dashboard/charts/PrevisaoReceitasChart.tsx index c2c24b9..d5749fe 100644 --- a/src/components/dashboard/charts/PrevisaoReceitasChart.tsx +++ b/src/components/dashboard/charts/PrevisaoReceitasChart.tsx @@ -1,19 +1,28 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; -import api from '../../../services/api'; -import { useAuth } from '../../../contexts/AuthContext'; -import { formatCurrency } from '../../../utils/formatters'; +import React from "react"; +import { useQuery } from "react-query"; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, +} from "recharts"; + +import { useAuth } from "../../../contexts/AuthContext"; +import api from "../../../services/api"; +import { formatCurrency } from "../../../utils/formatters"; export const PrevisaoReceitasChart: React.FC = () => { const { user } = useAuth(); - - if (!['ADVOGADO_ADMIN', 'ADVOGADO'].includes(user?.perfil || '')) { + + if (!["ADVOGADO_ADMIN", "ADVOGADO"].includes(user?.perfil || "")) { return null; } - const { data: previsao } = useQuery('previsaoReceitas', async () => { - const response = await api.get('/dashboard/financeiro/previsao'); + const { data: previsao } = useQuery("previsaoReceitas", async () => { + const response = await api.get("/dashboard/financeiro/previsao"); return response.data; }); diff --git a/src/components/dashboard/charts/ProcessosCharts.tsx b/src/components/dashboard/charts/ProcessosCharts.tsx index bc3da03..db7e97f 100644 --- a/src/components/dashboard/charts/ProcessosCharts.tsx +++ b/src/components/dashboard/charts/ProcessosCharts.tsx @@ -1,12 +1,15 @@ -import React from 'react'; -import { useAuth } from '../../../contexts/AuthContext'; -import { ProcessosEvolucao } from './ProcessosEvolucao'; -import { TaxaSucessoChart } from './TaxaSucessoChart'; -import { PrevisaoReceitasChart } from './PrevisaoReceitasChart'; +import React from "react"; + +import { useAuth } from "../../../contexts/AuthContext"; + +import { PrevisaoReceitasChart } from "./PrevisaoReceitasChart"; +import { ProcessosEvolucao } from "./ProcessosEvolucao"; +import { TaxaSucessoChart } from "./TaxaSucessoChart"; export const ProcessosCharts: React.FC = () => { const { user } = useAuth(); - const isAdminOrAdvogado = user?.perfil === 'ADVOGADO_ADMIN' || user?.perfil === 'ADVOGADO'; + const isAdminOrAdvogado = + user?.perfil === "ADVOGADO_ADMIN" || user?.perfil === "ADVOGADO"; return ( <> diff --git a/src/components/dashboard/charts/ProcessosEvolucao.tsx b/src/components/dashboard/charts/ProcessosEvolucao.tsx index 936ac4f..ae36ee1 100644 --- a/src/components/dashboard/charts/ProcessosEvolucao.tsx +++ b/src/components/dashboard/charts/ProcessosEvolucao.tsx @@ -1,11 +1,20 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; -import api from '../../../services/api'; +import React from "react"; +import { useQuery } from "react-query"; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, +} from "recharts"; + +import api from "../../../services/api"; export const ProcessosEvolucao: React.FC = () => { - const { data: evolucao } = useQuery('processosEvolucao', async () => { - const response = await api.get('/dashboard/processos/evolucao'); + const { data: evolucao } = useQuery("processosEvolucao", async () => { + const response = await api.get("/dashboard/processos/evolucao"); return response.data; }); @@ -21,8 +30,18 @@ export const ProcessosEvolucao: React.FC = () => { - - + + diff --git a/src/components/dashboard/charts/TaxaSucessoChart.tsx b/src/components/dashboard/charts/TaxaSucessoChart.tsx index 6691708..9f8560b 100644 --- a/src/components/dashboard/charts/TaxaSucessoChart.tsx +++ b/src/components/dashboard/charts/TaxaSucessoChart.tsx @@ -1,15 +1,23 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts'; -import api from '../../../services/api'; +import React from "react"; +import { useQuery } from "react-query"; +import { + PieChart, + Pie, + Cell, + ResponsiveContainer, + Legend, + Tooltip, +} from "recharts"; + +import api from "../../../services/api"; export const TaxaSucessoChart: React.FC = () => { - const { data: stats } = useQuery('taxaSucesso', async () => { - const response = await api.get('/dashboard/processos/taxa-sucesso'); + const { data: stats } = useQuery("taxaSucesso", async () => { + const response = await api.get("/dashboard/processos/taxa-sucesso"); return response.data; }); - const COLORS = ['#10B981', '#EF4444', '#F59E0B']; + const COLORS = ["#10B981", "#EF4444", "#F59E0B"]; return ( @@ -29,7 +37,10 @@ export const TaxaSucessoChart: React.FC = () => { label > {stats?.map((entry: any, index: number) => ( - + ))} diff --git a/src/components/dashboard/stats/AtendimentosStats.tsx b/src/components/dashboard/stats/AtendimentosStats.tsx index eb7beb8..2ea8f81 100644 --- a/src/components/dashboard/stats/AtendimentosStats.tsx +++ b/src/components/dashboard/stats/AtendimentosStats.tsx @@ -1,11 +1,12 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { Card } from '../Card'; -import api from '../../../services/api'; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../../services/api"; +import { Card } from "../Card"; export const AtendimentosStats: React.FC = () => { - const { data: stats } = useQuery('atendimentosStats', async () => { - const response = await api.get('/dashboard/atendimentos/stats'); + const { data: stats } = useQuery("atendimentosStats", async () => { + const response = await api.get("/dashboard/atendimentos/stats"); return response.data; }); diff --git a/src/components/dashboard/stats/FinanceiroStats.tsx b/src/components/dashboard/stats/FinanceiroStats.tsx index 9b37c29..2418f6a 100644 --- a/src/components/dashboard/stats/FinanceiroStats.tsx +++ b/src/components/dashboard/stats/FinanceiroStats.tsx @@ -1,12 +1,13 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { Card } from '../Card'; -import api from '../../../services/api'; -import { formatCurrency } from '../../../utils/formatters'; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../../services/api"; +import { formatCurrency } from "../../../utils/formatters"; +import { Card } from "../Card"; export const FinanceiroStats: React.FC = () => { - const { data: stats } = useQuery('financeiroStats', async () => { - const response = await api.get('/dashboard/financeiro/stats'); + const { data: stats } = useQuery("financeiroStats", async () => { + const response = await api.get("/dashboard/financeiro/stats"); return response.data; }); diff --git a/src/components/dashboard/stats/KPIsFinanceiros.tsx b/src/components/dashboard/stats/KPIsFinanceiros.tsx index ab4b0e7..08770cd 100644 --- a/src/components/dashboard/stats/KPIsFinanceiros.tsx +++ b/src/components/dashboard/stats/KPIsFinanceiros.tsx @@ -1,19 +1,20 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { useAuth } from '../../../contexts/AuthContext'; -import { Card } from '../../dashboard/Card'; -import api from '../../../services/api'; -import { formatCurrency } from '../../../utils/formatters'; +import React from "react"; +import { useQuery } from "react-query"; + +import { useAuth } from "../../../contexts/AuthContext"; +import api from "../../../services/api"; +import { formatCurrency } from "../../../utils/formatters"; +import { Card } from "../../dashboard/Card"; export const KPIsFinanceiros: React.FC = () => { const { user } = useAuth(); - - if (!['ADVOGADO_ADMIN', 'ADVOGADO'].includes(user?.perfil || '')) { + + if (!["ADVOGADO_ADMIN", "ADVOGADO"].includes(user?.perfil || "")) { return null; } - const { data: stats } = useQuery('financeiroKPIs', async () => { - const response = await api.get('/dashboard/financeiro/stats'); + const { data: stats } = useQuery("financeiroKPIs", async () => { + const response = await api.get("/dashboard/financeiro/stats"); return response.data; }); diff --git a/src/components/dashboard/stats/ProcessosStats.tsx b/src/components/dashboard/stats/ProcessosStats.tsx index 5f7998d..0512378 100644 --- a/src/components/dashboard/stats/ProcessosStats.tsx +++ b/src/components/dashboard/stats/ProcessosStats.tsx @@ -1,11 +1,12 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import api from '../../../services/api'; -import { Card } from '../Card'; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../../services/api"; +import { Card } from "../Card"; export const ProcessosStats: React.FC = () => { - const { data: stats } = useQuery('processosStats', async () => { - const response = await api.get('/dashboard/processos/stats'); + const { data: stats } = useQuery("processosStats", async () => { + const response = await api.get("/dashboard/processos/stats"); return response.data; }); @@ -17,16 +18,8 @@ export const ProcessosStats: React.FC = () => { trend={stats?.crescimento || 0} icon="DocumentText" /> - - + + ); }; diff --git a/src/components/dashboard/stats/TarefasStats.tsx b/src/components/dashboard/stats/TarefasStats.tsx index 3cc17ab..355aee9 100644 --- a/src/components/dashboard/stats/TarefasStats.tsx +++ b/src/components/dashboard/stats/TarefasStats.tsx @@ -1,22 +1,23 @@ -import React from 'react'; -import { useQuery } from 'react-query'; -import { Card } from '../Card'; -import api from '../../../services/api'; +import React from "react"; +import { useQuery } from "react-query"; + +import api from "../../../services/api"; +import { Card } from "../Card"; export const TarefasStats: React.FC = () => { - const { data: stats } = useQuery('tarefasStats', async () => { + const { data: stats } = useQuery("tarefasStats", async () => { try { - const response = await api.get('/dashboard/tarefas/stats'); + const response = await api.get("/dashboard/tarefas/stats"); return response.data; } catch (error) { - console.error('Erro ao buscar estatísticas:', error); + console.error("Erro ao buscar estatísticas:", error); return { pendentes: 0, atrasadas: 0, concluidas: 0, total: 0, taxaConclusao: 0, - porStatus: { PENDENTE: 0 } + porStatus: { PENDENTE: 0 }, }; } }); @@ -27,7 +28,7 @@ export const TarefasStats: React.FC = () => { concluidas: 0, total: 0, taxaConclusao: 0, - porStatus: { PENDENTE: 0 } + porStatus: { PENDENTE: 0 }, }; const safeStats = stats || defaultStats; diff --git a/src/components/documentos/ConfiguracaoDocumento.tsx b/src/components/documentos/ConfiguracaoDocumento.tsx index fd29575..dc98eac 100644 --- a/src/components/documentos/ConfiguracaoDocumento.tsx +++ b/src/components/documentos/ConfiguracaoDocumento.tsx @@ -1,10 +1,11 @@ -import { useState, useEffect } from 'react'; -import { api } from '../../lib/api'; -import { toast } from 'react-toastify'; -import { Dialog } from '@headlessui/react'; -import { DocumentArrowUpIcon } from '@heroicons/react/24/outline'; -import { Editor } from '@tinymce/tinymce-react'; -import { DOCUMENT_VARIABLES } from '../../utils/documentVariables'; +import { Dialog } from "@headlessui/react"; +import { DocumentArrowUpIcon } from "@heroicons/react/24/outline"; +import { Editor } from "@tinymce/tinymce-react"; +import { useState, useEffect } from "react"; +import { toast } from "react-toastify"; + +import { api } from "../../lib/api"; +import { DOCUMENT_VARIABLES } from "../../utils/documentVariables"; // Definir interface para o estado interface ConfigState { @@ -24,18 +25,18 @@ interface ConfigState { export function ConfiguracaoDocumento() { const [config, setConfig] = useState({ - timbradoUrl: '', + timbradoUrl: "", timbradoMarginTop: 100, timbradoMarginBottom: 50, timbradoMarginLeft: 50, timbradoMarginRight: 50, - templatePadrao: '', + templatePadrao: "", margensTemplate: { top: 35, bottom: 29, left: 25, - right: 25 - } + right: 25, + }, }); const [isPreviewOpen, setPreviewOpen] = useState(false); @@ -47,27 +48,27 @@ export function ConfiguracaoDocumento() { const loadConfig = async () => { try { - const response = await api.get('/documentos/configuracoes'); - console.log('Configurações carregadas:', response.data); // Debug + const response = await api.get("/documentos/configuracoes"); + console.log("Configurações carregadas:", response.data); // Debug if (response.data) { setConfig({ - timbradoUrl: response.data.timbradoUrl || '', + timbradoUrl: response.data.timbradoUrl || "", timbradoMarginTop: response.data.timbradoMarginTop || 100, timbradoMarginBottom: response.data.timbradoMarginBottom || 50, timbradoMarginLeft: response.data.timbradoMarginLeft || 50, timbradoMarginRight: response.data.timbradoMarginRight || 50, - templatePadrao: response.data.templatePadrao || '', + templatePadrao: response.data.templatePadrao || "", margensTemplate: { top: response.data.margensTemplate?.top || 35, bottom: response.data.margensTemplate?.bottom || 29, left: response.data.margensTemplate?.left || 25, - right: response.data.margensTemplate?.right || 25 - } + right: response.data.margensTemplate?.right || 25, + }, }); } } catch (error) { - console.error('Erro ao carregar configurações:', error); - toast.error('Erro ao carregar configurações'); + console.error("Erro ao carregar configurações:", error); + toast.error("Erro ao carregar configurações"); } }; @@ -76,53 +77,57 @@ export function ConfiguracaoDocumento() { if (!file) return; // Atualizado para aceitar PDF - if (!['image/png', 'image/svg+xml', 'application/pdf'].includes(file.type)) { - toast.error('Por favor, use apenas arquivos PDF, PNG ou SVG'); + if ( + !["image/png", "image/svg+xml", "application/pdf"].includes(file.type) + ) { + toast.error("Por favor, use apenas arquivos PDF, PNG ou SVG"); return; } - if (file.size > 5 * 1024 * 1024) { // Aumentado para 5MB para acomodar PDFs - toast.error('O arquivo deve ter no máximo 5MB'); + if (file.size > 5 * 1024 * 1024) { + // Aumentado para 5MB para acomodar PDFs + toast.error("O arquivo deve ter no máximo 5MB"); return; } try { const formData = new FormData(); - formData.append('timbrado', file); + formData.append("timbrado", file); - const response = await api.post('/documentos/upload-timbrado', formData); - setConfig(prev => ({ ...prev, timbradoUrl: response.data.url })); - toast.success('Timbrado atualizado com sucesso'); + const response = await api.post("/documentos/upload-timbrado", formData); + setConfig((prev) => ({ ...prev, timbradoUrl: response.data.url })); + toast.success("Timbrado atualizado com sucesso"); } catch (error) { - toast.error('Erro ao fazer upload do timbrado'); + toast.error("Erro ao fazer upload do timbrado"); } }; const handleSave = async () => { try { setIsSaving(true); - await api.post('/documentos/configuracoes', config); - toast.success('Configurações salvas com sucesso'); + await api.post("/documentos/configuracoes", config); + toast.success("Configurações salvas com sucesso"); } catch (error) { - toast.error('Erro ao salvar configurações'); + toast.error("Erro ao salvar configurações"); } finally { setIsSaving(false); } }; const handleEditorSetup = (editor: any) => { - editor.ui.registry.addMenuButton('variables', { - text: 'Variáveis', + editor.ui.registry.addMenuButton("variables", { + text: "Variáveis", fetch: (callback: Function) => { - const items = Object.entries(DOCUMENT_VARIABLES).flatMap(([category, vars]) => - Object.entries(vars).map(([name, value]) => ({ - type: 'menuitem', - text: `${category} - ${name.replace(/_/g, ' ')}`, - onAction: () => editor.insertContent(value as string) - })) + const items = Object.entries(DOCUMENT_VARIABLES).flatMap( + ([category, vars]) => + Object.entries(vars).map(([name, value]) => ({ + type: "menuitem", + text: `${category} - ${name.replace(/_/g, " ")}`, + onAction: () => editor.insertContent(value as string), + })), ); callback(items); - } + }, }); }; @@ -131,12 +136,14 @@ export function ConfiguracaoDocumento() { height: 400, menubar: true, plugins: [ - 'code', 'preview' // Removido 'template' + "code", + "preview", // Removido 'template' ], - toolbar: 'variables | undo redo | formatselect | ' + - 'bold italic | alignleft aligncenter alignright | ' + - 'code preview', - setup: handleEditorSetup + toolbar: + "variables | undo redo | formatselect | " + + "bold italic | alignleft aligncenter alignright | " + + "code preview", + setup: handleEditorSetup, }; return ( @@ -179,7 +186,12 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ ...prev, timbradoMarginTop: Number(e.target.value) }))} + onChange={(e) => + setConfig((prev) => ({ + ...prev, + timbradoMarginTop: Number(e.target.value), + })) + } className="w-full p-2 border rounded" /> @@ -188,7 +200,12 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ ...prev, timbradoMarginBottom: Number(e.target.value) }))} + onChange={(e) => + setConfig((prev) => ({ + ...prev, + timbradoMarginBottom: Number(e.target.value), + })) + } className="w-full p-2 border rounded" /> @@ -197,7 +214,12 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ ...prev, timbradoMarginLeft: Number(e.target.value) }))} + onChange={(e) => + setConfig((prev) => ({ + ...prev, + timbradoMarginLeft: Number(e.target.value), + })) + } className="w-full p-2 border rounded" /> @@ -206,7 +228,12 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ ...prev, timbradoMarginRight: Number(e.target.value) }))} + onChange={(e) => + setConfig((prev) => ({ + ...prev, + timbradoMarginRight: Number(e.target.value), + })) + } className="w-full p-2 border rounded" /> @@ -218,7 +245,9 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ ...prev, templatePadrao: content }))} + onEditorChange={(content) => + setConfig((prev) => ({ ...prev, templatePadrao: content })) + } init={editorConfig} /> @@ -226,7 +255,9 @@ export function ConfiguracaoDocumento() { {Object.entries(DOCUMENT_VARIABLES).map(([category, vars]) => ( - {category} + + {category} + {Object.entries(vars).map(([name, value]) => ( @@ -247,13 +278,15 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ - ...prev, - margensTemplate: { - ...prev.margensTemplate, - top: Number(e.target.value) - } - }))} + onChange={(e) => + setConfig((prev) => ({ + ...prev, + margensTemplate: { + ...prev.margensTemplate, + top: Number(e.target.value), + }, + })) + } className="w-full p-2 border rounded" /> @@ -262,13 +295,15 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ - ...prev, - margensTemplate: { - ...prev.margensTemplate, - bottom: Number(e.target.value) - } - }))} + onChange={(e) => + setConfig((prev) => ({ + ...prev, + margensTemplate: { + ...prev.margensTemplate, + bottom: Number(e.target.value), + }, + })) + } className="w-full p-2 border rounded" /> @@ -277,13 +312,15 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ - ...prev, - margensTemplate: { - ...prev.margensTemplate, - left: Number(e.target.value) - } - }))} + onChange={(e) => + setConfig((prev) => ({ + ...prev, + margensTemplate: { + ...prev.margensTemplate, + left: Number(e.target.value), + }, + })) + } className="w-full p-2 border rounded" /> @@ -292,13 +329,15 @@ export function ConfiguracaoDocumento() { setConfig(prev => ({ - ...prev, - margensTemplate: { - ...prev.margensTemplate, - right: Number(e.target.value) - } - }))} + onChange={(e) => + setConfig((prev) => ({ + ...prev, + margensTemplate: { + ...prev.margensTemplate, + right: Number(e.target.value), + }, + })) + } className="w-full p-2 border rounded" /> @@ -309,11 +348,11 @@ export function ConfiguracaoDocumento() { disabled={isSaving} className={`px-4 py-2 ${ isSaving - ? 'bg-gray-400 cursor-not-allowed' - : 'bg-blue-600 hover:bg-blue-700' + ? "bg-gray-400 cursor-not-allowed" + : "bg-blue-600 hover:bg-blue-700" } text-white rounded`} > - {isSaving ? 'Salvando...' : 'Salvar Configurações'} + {isSaving ? "Salvando..." : "Salvar Configurações"} diff --git a/src/components/documentos/DocumentoEditor.tsx b/src/components/documentos/DocumentoEditor.tsx index 2316a9c..c58e237 100644 --- a/src/components/documentos/DocumentoEditor.tsx +++ b/src/components/documentos/DocumentoEditor.tsx @@ -1,11 +1,18 @@ -import { useState, useEffect } from 'react'; -import { Editor } from '@tinymce/tinymce-react'; -import { Button, Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; -import { Save as SaveIcon } from '@mui/icons-material'; -import { XMarkIcon } from '@heroicons/react/24/outline'; -import { toast } from 'react-toastify'; // Adicionar esta importação -import { api } from '../../lib/api'; // Adicionar esta importação -import { DocumentoService } from '../../services/DocumentoService'; // Adicionar esta importação +import { XMarkIcon } from "@heroicons/react/24/outline"; +import { Save as SaveIcon } from "@mui/icons-material"; +import { + Button, + Dialog, + DialogTitle, + DialogContent, + DialogActions, +} from "@mui/material"; +import { Editor } from "@tinymce/tinymce-react"; +import { useState, useEffect } from "react"; +import { toast } from "react-toastify"; // Adicionar esta importação + +import { api } from "../../lib/api"; // Adicionar esta importação +import { DocumentoService } from "../../services/DocumentoService"; // Adicionar esta importação interface DocumentoEditorProps { isOpen: boolean; @@ -27,24 +34,24 @@ interface TemplateVariables { const preencherTemplate = (template: string, variables: TemplateVariables) => { let result = template; Object.entries(variables).forEach(([key, value]) => { - const regex = new RegExp(`{{${key}}}`, 'g'); + const regex = new RegExp(`{{${key}}}`, "g"); result = result.replace(regex, value); }); return result; }; -export function DocumentoEditor({ - isOpen, - onClose, - onSave, +export function DocumentoEditor({ + isOpen, + onClose, + onSave, initialData, processoId, atendimentoId, - documento // Recuperar a prop + documento, // Recuperar a prop }: DocumentoEditorProps) { - const [content, setContent] = useState(''); - const [nome, setNome] = useState(''); - const [tipo, setTipo] = useState('OUTROS'); + const [content, setContent] = useState(""); + const [nome, setNome] = useState(""); + const [tipo, setTipo] = useState("OUTROS"); const [selectedTemplate, setSelectedTemplate] = useState(null); const [selectedAtendimento, setSelectedAtendimento] = useState(null); const [atendimentos, setAtendimentos] = useState([]); @@ -54,9 +61,9 @@ export function DocumentoEditor({ useEffect(() => { if (initialData) { - setContent(initialData.conteudo || ''); - setNome(initialData.nome || ''); - setTipo(initialData.tipo || 'OUTROS'); + setContent(initialData.conteudo || ""); + setNome(initialData.nome || ""); + setTipo(initialData.tipo || "OUTROS"); } }, [initialData]); @@ -64,13 +71,13 @@ export function DocumentoEditor({ const loadData = async () => { try { const [atendimentosRes, templatesRes] = await Promise.all([ - api.get('/atendimentos'), - api.get('/documentos-modelos') + api.get("/atendimentos"), + api.get("/documentos-modelos"), ]); setAtendimentos(atendimentosRes.data); setTemplates(templatesRes.data); } catch (error) { - toast.error('Erro ao carregar dados'); + toast.error("Erro ao carregar dados"); } finally { setIsLoading(false); } @@ -80,36 +87,38 @@ export function DocumentoEditor({ useEffect(() => { if (documento) { - setContent(documento.conteudo || ''); - setNome(documento.nome || ''); - setTipo(documento.tipo || 'OUTROS'); + setContent(documento.conteudo || ""); + setNome(documento.nome || ""); + setTipo(documento.tipo || "OUTROS"); } }, [documento]); const handleTemplateSelect = async (templateId) => { try { - const template = templates.find(t => t.id === templateId); + const template = templates.find((t) => t.id === templateId); if (!template) return; - const atendimento = atendimentos.find(a => a.id === selectedAtendimento); + const atendimento = atendimentos.find( + (a) => a.id === selectedAtendimento, + ); if (!atendimento) return; // Buscar dados do atendimento com as relações necessárias const [atendimentoDetalhado, usuarioRes, configRes] = await Promise.all([ api.get(`/atendimentos/${atendimento.id}`), - api.get('/auth/me'), - api.get('/documentos/configuracoes') + api.get("/auth/me"), + api.get("/documentos/configuracoes"), ]); // Formatar endereço completo e outros dados do cliente const cliente = atendimentoDetalhado.data.cliente; - const enderecoCompleto = `${cliente.endereco}, ${cliente.numero}${cliente.complemento ? `, ${cliente.complemento}` : ''}, ${cliente.bairro}, ${cliente.cidade}/${cliente.estado}, CEP: ${cliente.cep}`; + const enderecoCompleto = `${cliente.endereco}, ${cliente.numero}${cliente.complemento ? `, ${cliente.complemento}` : ""}, ${cliente.bairro}, ${cliente.cidade}/${cliente.estado}, CEP: ${cliente.cep}`; // Formatar data extenso corretamente - const formatadorData = new Intl.DateTimeFormat('pt-BR', { - day: 'numeric', - month: 'long', - year: 'numeric' + const formatadorData = new Intl.DateTimeFormat("pt-BR", { + day: "numeric", + month: "long", + year: "numeric", }); // Preparar variáveis com dados formatados @@ -117,65 +126,73 @@ export function DocumentoEditor({ cliente: { ...cliente, enderecoCompleto, - nome: cliente.nome || '', - cpf: cliente.cpfCnpj || '', // Adicionar cpf como alias para cpfCnpj - cpfCnpj: cliente.cpfCnpj || '', // Manter cpfCnpj também - rg: cliente.rg || '', - estadoCivil: cliente.estadoCivil || '', - profissao: cliente.profissao || '', - nacionalidade: cliente.nacionalidade || 'brasileiro(a)', - nomePai: cliente.nomePai || '', - nomeMae: cliente.nomeMae || '' + nome: cliente.nome || "", + cpf: cliente.cpfCnpj || "", // Adicionar cpf como alias para cpfCnpj + cpfCnpj: cliente.cpfCnpj || "", // Manter cpfCnpj também + rg: cliente.rg || "", + estadoCivil: cliente.estadoCivil || "", + profissao: cliente.profissao || "", + nacionalidade: cliente.nacionalidade || "brasileiro(a)", + nomePai: cliente.nomePai || "", + nomeMae: cliente.nomeMae || "", }, atendimento: atendimentoDetalhado.data, processo: atendimentoDetalhado.data.processo, advogado: { ...usuarioRes.data, - oabCompleta: `${usuarioRes.data.oab}/${usuarioRes.data.oabUF}` + oabCompleta: `${usuarioRes.data.oab}/${usuarioRes.data.oabUF}`, }, escritorio: configRes.data, data: { - atual: new Date().toLocaleDateString('pt-BR'), + atual: new Date().toLocaleDateString("pt-BR"), atualExtenso: formatadorData.format(new Date()), // Formatar a data corretamente dia: new Date().getDate().toString(), mes: (new Date().getMonth() + 1).toString(), - mesExtenso: new Intl.DateTimeFormat('pt-BR', { month: 'long' }).format(new Date()), - ano: new Date().getFullYear().toString() - } + mesExtenso: new Intl.DateTimeFormat("pt-BR", { + month: "long", + }).format(new Date()), + ano: new Date().getFullYear().toString(), + }, }; // Teste para debug - console.log('Data por extenso:', variables.data.atualExtenso); + console.log("Data por extenso:", variables.data.atualExtenso); // Preencher template - const conteudoPreenchido = template.conteudo.replace(/\{\{([^}]+)\}\}/g, (match, path) => { - try { - const valor = path.split('.').reduce((obj, key) => obj?.[key], variables); - return valor !== undefined ? valor : match; - } catch (error) { - console.warn(`Erro ao processar variável ${path}:`, error); - return match; - } - }); + const conteudoPreenchido = template.conteudo.replace( + /\{\{([^}]+)\}\}/g, + (match, path) => { + try { + const valor = path + .split(".") + .reduce((obj, key) => obj?.[key], variables); + return valor !== undefined ? valor : match; + } catch (error) { + console.warn(`Erro ao processar variável ${path}:`, error); + return match; + } + }, + ); setContent(conteudoPreenchido); setNome(`${template.nome} - ${cliente.nome}`); setTipo(template.tipo); // Debug para verificar substituições - console.log('Template original:', template.conteudo); - console.log('Conteúdo processado:', conteudoPreenchido); - + console.log("Template original:", template.conteudo); + console.log("Conteúdo processado:", conteudoPreenchido); } catch (error) { - console.error('Erro ao carregar template:', error); - toast.error('Erro ao carregar template. Verifique se o atendimento possui um cliente vinculado.'); + console.error("Erro ao carregar template:", error); + toast.error( + "Erro ao carregar template. Verifique se o atendimento possui um cliente vinculado.", + ); } }; const handleSave = async () => { // Verificação de atendimento apenas para novos documentos if (!documento && !selectedAtendimento) { - toast.error('Selecione um atendimento'); + toast.error("Selecione um atendimento"); return; } @@ -186,12 +203,12 @@ export function DocumentoEditor({ tipo, conteudo: content, atendimentoId: documento?.atendimentoId || selectedAtendimento, - processoId: documento?.processoId || processoId + processoId: documento?.processoId || processoId, }); - toast.success('Documento salvo com sucesso'); + toast.success("Documento salvo com sucesso"); onClose(); } catch (error) { - toast.error('Erro ao salvar documento'); + toast.error("Erro ao salvar documento"); } finally { setIsSaving(false); } @@ -203,12 +220,12 @@ export function DocumentoEditor({ setSelectedAtendimento(e.target.value)} className="flex-1 p-2 border rounded" > Selecione um atendimento - {atendimentos.map(atendimento => ( + {atendimentos.map((atendimento) => ( {atendimento.protocolo} - {atendimento.cliente.nome} @@ -216,12 +233,12 @@ export function DocumentoEditor({ handleTemplateSelect(e.target.value)} className="flex-1 p-2 border rounded" > Selecione um template - {templates.map(template => ( + {templates.map((template) => ( {template.nome} @@ -259,23 +276,39 @@ export function DocumentoEditor({ height: 500, menubar: true, plugins: [ - 'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview', - 'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen', - 'insertdatetime', 'media', 'table', 'help', 'wordcount' + "advlist", + "autolink", + "lists", + "link", + "image", + "charmap", + "preview", + "anchor", + "searchreplace", + "visualblocks", + "code", + "fullscreen", + "insertdatetime", + "media", + "table", + "help", + "wordcount", ], - toolbar: 'undo redo | formatselect | ' + - 'bold italic backcolor | alignleft aligncenter ' + - 'alignright alignjustify | bullist numlist outdent indent | ' + - 'removeformat | help', - language: 'pt_BR', // Adicionar configuração de idioma - language_url: '/tinymce/langs/pt_BR.js', // Adicionar URL do arquivo de idioma - content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }', + toolbar: + "undo redo | formatselect | " + + "bold italic backcolor | alignleft aligncenter " + + "alignright alignjustify | bullist numlist outdent indent | " + + "removeformat | help", + language: "pt_BR", // Adicionar configuração de idioma + language_url: "/tinymce/langs/pt_BR.js", // Adicionar URL do arquivo de idioma + content_style: + "body { font-family:Helvetica,Arial,sans-serif; font-size:14px }", // Adicionar estas configurações promotion: false, branding: false, preview_styles: false, - referrer_policy: 'origin', - disable_stats: true // Desabilitar estatísticas + referrer_policy: "origin", + disable_stats: true, // Desabilitar estatísticas }} value={content} onEditorChange={setContent} @@ -286,7 +319,7 @@ export function DocumentoEditor({ }> Cancelar - } @@ -298,4 +331,3 @@ export function DocumentoEditor({ ); } - diff --git a/src/components/documentos/DocumentoViewer.tsx b/src/components/documentos/DocumentoViewer.tsx index 45652b8..2768daf 100644 --- a/src/components/documentos/DocumentoViewer.tsx +++ b/src/components/documentos/DocumentoViewer.tsx @@ -1,8 +1,13 @@ -import { Dialog } from '@headlessui/react'; -import { XMarkIcon, PrinterIcon, DocumentArrowDownIcon } from '@heroicons/react/24/outline'; -import { api } from '../../lib/api'; -import { toast } from 'react-toastify'; -import { useState } from 'react'; +import { Dialog } from "@headlessui/react"; +import { + XMarkIcon, + PrinterIcon, + DocumentArrowDownIcon, +} from "@heroicons/react/24/outline"; +import { useState } from "react"; +import { toast } from "react-toastify"; + +import { api } from "../../lib/api"; interface DocumentoViewerProps { documento: any; @@ -10,18 +15,22 @@ interface DocumentoViewerProps { onClose: () => void; } -export function DocumentoViewer({ documento, isOpen, onClose }: DocumentoViewerProps) { +export function DocumentoViewer({ + documento, + isOpen, + onClose, +}: DocumentoViewerProps) { const [isExporting, setIsExporting] = useState(false); const handlePrint = () => { - const printWindow = window.open('', '_blank'); + const printWindow = window.open("", "_blank"); if (!printWindow) return; const html = ` - ${documento?.nome || 'Documento'} + ${documento?.nome || "Documento"}
- Esta ação não pode ser desfeita. O atendimento será marcado como cancelado, mas manterá seu protocolo e histórico. + Esta ação não pode ser desfeita. O atendimento será marcado + como cancelado, mas manterá seu protocolo e histórico.
{atendimento.justificativaCancelamento}
+ {atendimento.justificativaCancelamento} +
Cancelado em: {formatDate(atendimento.dataAtualizacao, "PPpp")}
Arraste arquivos aqui ou clique para selecionar
+ Arraste arquivos aqui ou clique para selecionar +
Máximo 10MB por arquivo
Para criar um novo cálculo, clique no botão "Novo Cálculo".
- Criado em: {formatarData(calculo.criadoEm)} por {calculo.usuario.nome} + Criado em: {formatarData(calculo.criadoEm)} por{" "} + {calculo.usuario.nome}
- Processo: {calculo.processo.numero} - {calculo.processo.titulo} + Processo: {calculo.processo.numero} -{" "} + {calculo.processo.titulo}
Data Inicial: {new Date(calculo.dataInicial).toLocaleDateString()}
+ Data Inicial:{" "} + {new Date(calculo.dataInicial).toLocaleDateString()} +
Data Final: {new Date(calculo.dataFinal).toLocaleDateString()}
+ Data Final:{" "} + {new Date(calculo.dataFinal).toLocaleDateString()} +
Percentual: {calculo.percentualSalarioMinimo}%
+ Percentual:{" "} + {calculo.percentualSalarioMinimo}% +
Multa: {calculo.multaPactuada}%
+ Multa:{" "} + {calculo.multaPactuada}% +
Índice: {calculo.tipoAtualizacao}
+ Índice:{" "} + {calculo.tipoAtualizacao} +
Valor Base: R$ {calculo.valorCalculado.toFixed(2)}
Valor Multa: R$ {calculo.valorMulta.toFixed(2)}
Valor Total: R$ {calculo.valorTotal.toFixed(2)}
+ Valor Base: R${" "} + {calculo.valorCalculado.toFixed(2)} +
+ Valor Multa: R${" "} + {calculo.valorMulta.toFixed(2)} +
+ Valor Total: R${" "} + {calculo.valorTotal.toFixed(2)} +
{error || 'Não foi possível carregar os dados do cálculo'}
+ {error || "Não foi possível carregar os dados do cálculo"} +
Valor Base
{formatarValor(calculo.valorBase)}
+ {formatarValor(calculo.valorBase)} +
Valor Corrigido
{formatarValor(calculo.valorCorrigido)}
+ {formatarValor(calculo.valorCorrigido)} +
Valor Final
{formatarValor(calculo.valorFinal)}
+ {formatarValor(calculo.valorFinal)} +
Nenhuma correção monetária registrada
+ Nenhuma correção monetária registrada +
Nenhuma multa aplicada.
+ Nenhuma multa aplicada. +
Nenhuma parcela cadastrada.
+ Nenhuma parcela cadastrada. +
Nenhum honorário aplicado.
+ Nenhum honorário aplicado. +
Nenhum desconto aplicado.
+ Nenhum desconto aplicado. +
Nenhum registro de alteração encontrado.
+ Nenhum registro de alteração encontrado. +
- {format(new Date(item.criadoEm), 'dd/MM/yyyy HH:mm:ss', { locale: ptBR })} - {item.usuario ? ` por ${item.usuario.nome}` : ''} + {format(new Date(item.criadoEm), "dd/MM/yyyy HH:mm:ss", { + locale: ptBR, + })} + {item.usuario ? ` por ${item.usuario.nome}` : ""}
Preencha os dados básicos para criar um novo cálculo
+ Preencha os dados básicos para criar um novo cálculo +
Data de referência para iniciar o cálculo
+ Data de referência para iniciar o cálculo +
Valor inicial para o cálculo
+ Valor inicial para o cálculo +
Preencha os dados para criar um novo cálculo de pensão alimentícia
+ Preencha os dados para criar um novo cálculo de pensão alimentícia +
Ex: 30% = 30,00%
Data de início da pensão
+ Data de início da pensão +
Deixe em branco se a pensão não tem data final
+ Deixe em branco se a pensão não tem data final +
Ex: 10% = 10,00%. Deixe em branco se não houver.
+ Ex: 10% = 10,00%. Deixe em branco se não houver. +
{atividade.descricao} diff --git a/src/components/dashboard/Card.tsx b/src/components/dashboard/Card.tsx index 1a27dfb..b2a7afd 100644 --- a/src/components/dashboard/Card.tsx +++ b/src/components/dashboard/Card.tsx @@ -1,8 +1,5 @@ -import React from 'react'; -import { - ArrowUpIcon, - ArrowDownIcon, -} from '@heroicons/react/24/outline'; +import { ArrowUpIcon, ArrowDownIcon } from "@heroicons/react/24/outline"; +import React from "react"; interface CardProps { title: string; @@ -10,9 +7,9 @@ interface CardProps { trend?: number; trendLabel?: string; icon?: string; - color?: 'blue' | 'green' | 'red' | 'yellow' | 'purple'; + color?: "blue" | "green" | "red" | "yellow" | "purple"; items?: Array<{ label: string; value: string | number }>; - type?: 'default' | 'list'; + type?: "default" | "list"; } export const Card: React.FC = ({ @@ -21,27 +18,27 @@ export const Card: React.FC = ({ trend, trendLabel, icon, - color = 'blue', + color = "blue", items, - type = 'default' + type = "default", }) => { const colorClasses = { - blue: 'text-blue-600', - green: 'text-green-600', - red: 'text-red-600', - yellow: 'text-yellow-600', - purple: 'text-purple-600' + blue: "text-blue-600", + green: "text-green-600", + red: "text-red-600", + yellow: "text-yellow-600", + purple: "text-purple-600", }; const bgColorClasses = { - blue: 'bg-blue-50', - green: 'bg-green-50', - red: 'bg-red-50', - yellow: 'bg-yellow-50', - purple: 'bg-purple-50' + blue: "bg-blue-50", + green: "bg-green-50", + red: "bg-red-50", + yellow: "bg-yellow-50", + purple: "bg-purple-50", }; - if (type === 'list' && items) { + if (type === "list" && items) { return ( {title} @@ -49,7 +46,9 @@ export const Card: React.FC = ({ {items.map((item, index) => ( {item.label} - {item.value} + + {item.value} + ))} @@ -72,7 +71,9 @@ export const Card: React.FC = ({ {value}