From 8fc8a1303f3c2f66d8bb733f63b5c14b6f56e15f Mon Sep 17 00:00:00 2001 From: Eduardo Lopes Date: Thu, 5 Mar 2026 18:34:20 -0300 Subject: [PATCH 1/2] =?UTF-8?q?Feat:=20Exporta=C3=A7=C3=A3o=20por=20p?= =?UTF-8?q?=C3=A1gina=20e=20Bloquio=20e=20Desbloqueio=20de=20linhas=20em?= =?UTF-8?q?=20lote?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 864 +++++++++++++++++- package.json | 1 + .../chips-controle-recebidos.html | 8 + .../chips-controle-recebidos.ts | 129 ++- .../pages/dados-usuarios/dados-usuarios.html | 5 +- .../pages/dados-usuarios/dados-usuarios.ts | 112 ++- src/app/pages/faturamento/faturamento.html | 8 +- src/app/pages/faturamento/faturamento.ts | 103 ++- src/app/pages/geral/geral.html | 166 +++- src/app/pages/geral/geral.scss | 157 ++++ src/app/pages/geral/geral.ts | 443 ++++++++- src/app/pages/historico/historico.html | 4 + src/app/pages/historico/historico.ts | 106 ++- src/app/pages/mureg/mureg.html | 4 + src/app/pages/mureg/mureg.ts | 167 +++- .../parcelamentos-table.scss | 14 +- .../pages/parcelamentos/parcelamentos.html | 14 + src/app/pages/parcelamentos/parcelamentos.ts | 184 +++- src/app/pages/resumo/resumo.html | 22 +- src/app/pages/resumo/resumo.ts | 131 ++- src/app/pages/troca-numero/troca-numero.html | 5 +- src/app/pages/troca-numero/troca-numero.ts | 99 +- src/app/pages/vigencia/vigencia.html | 4 + src/app/pages/vigencia/vigencia.ts | 108 ++- src/app/services/table-export.service.ts | 462 ++++++++++ 25 files changed, 3188 insertions(+), 132 deletions(-) create mode 100644 src/app/services/table-export.service.ts diff --git a/package-lock.json b/package-lock.json index bcb1605..d41a1fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "bootstrap": "^5.3.8", "bootstrap-icons": "^1.13.1", "chart.js": "^4.5.1", + "exceljs": "^4.4.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" @@ -1354,6 +1355,47 @@ "node": ">=18" } }, + "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": { + "@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/@hono/node-server": { "version": "1.19.9", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", @@ -3715,11 +3757,105 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "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-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "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/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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" }, "node_modules/base64id": { @@ -3765,6 +3901,28 @@ "node": ">=14.0.0" } }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "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", @@ -3778,6 +3936,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", @@ -3849,7 +4024,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -3904,6 +4078,39 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "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/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3911,6 +4118,23 @@ "dev": true, "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/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -4040,6 +4264,18 @@ ], "license": "CC-BY-4.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": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/chalk": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", @@ -4214,11 +4450,25 @@ "dev": true, "license": "MIT" }, + "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/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, "node_modules/connect": { @@ -4357,6 +4607,12 @@ "node": ">=6.6.0" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -4371,6 +4627,31 @@ "node": ">= 0.10" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "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/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4433,6 +4714,12 @@ "node": ">=4.0" } }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -4577,6 +4864,45 @@ "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/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4633,6 +4959,15 @@ "node": ">=0.10.0" } }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/engine.io": { "version": "6.6.4", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", @@ -4920,6 +5255,26 @@ "node": ">=18.0.0" } }, + "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/exponential-backoff": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", @@ -4998,6 +5353,19 @@ "dev": true, "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", @@ -5119,6 +5487,12 @@ "node": ">= 0.8" } }, + "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-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -5151,7 +5525,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -5169,6 +5542,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", @@ -5256,7 +5658,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -5310,7 +5711,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/has-flag": { @@ -5527,6 +5927,26 @@ "url": "https://opencollective.com/express" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, "node_modules/ignore-walk": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", @@ -5556,6 +5976,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "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/immutable": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", @@ -5578,7 +6004,6 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -5589,7 +6014,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/ini": { @@ -5749,6 +6173,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/isbinaryfile": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", @@ -5952,6 +6382,48 @@ ], "license": "MIT" }, + "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/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/karma": { "version": "6.4.4", "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", @@ -6404,6 +6876,63 @@ "node": ">=10" } }, + "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/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.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/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/listr2": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.1.tgz", @@ -6483,6 +7012,85 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "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.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "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.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.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "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.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/log-symbols": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", @@ -6789,7 +7397,6 @@ "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" @@ -6802,7 +7409,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6945,7 +7551,6 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, "license": "MIT", "dependencies": { "minimist": "^1.2.6" @@ -7156,7 +7761,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" @@ -7337,7 +7941,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -7446,6 +8049,12 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parse5": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", @@ -7527,7 +8136,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7677,6 +8285,12 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", @@ -7764,6 +8378,50 @@ "node": ">= 0.10" } }, + "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/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/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -7953,6 +8611,26 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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" + }, "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", @@ -8000,6 +8678,18 @@ "@parcel/watcher": "^2.4.1" } }, + "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/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -8052,6 +8742,12 @@ "node": ">= 18" } }, + "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", @@ -8520,6 +9216,15 @@ "node": ">=8.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -8597,6 +9302,22 @@ "node": ">=18" } }, + "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/tar/node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", @@ -8628,7 +9349,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", - "dev": true, "license": "MIT", "engines": { "node": ">=14.14" @@ -8657,6 +9377,15 @@ "node": ">=0.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/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -8789,6 +9518,54 @@ "node": ">= 0.8" } }, + "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/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/unzipper/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/unzipper/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", @@ -8820,6 +9597,12 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -8830,6 +9613,15 @@ "node": ">= 0.4.0" } }, + "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/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -9092,7 +9884,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/ws": { @@ -9117,6 +9908,12 @@ } } }, + "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/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -9175,6 +9972,41 @@ "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/zod": { "version": "4.1.13", "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", diff --git a/package.json b/package.json index 46bb015..b7e9f3b 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "bootstrap": "^5.3.8", "bootstrap-icons": "^1.13.1", "chart.js": "^4.5.1", + "exceljs": "^4.4.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" diff --git a/src/app/pages/chips-controle-recebidos/chips-controle-recebidos.html b/src/app/pages/chips-controle-recebidos/chips-controle-recebidos.html index 513d6e6..5f3da29 100644 --- a/src/app/pages/chips-controle-recebidos/chips-controle-recebidos.html +++ b/src/app/pages/chips-controle-recebidos/chips-controle-recebidos.html @@ -35,6 +35,14 @@
+ + @@ -88,7 +92,6 @@ Itens por pág:
-
diff --git a/src/app/pages/dados-usuarios/dados-usuarios.ts b/src/app/pages/dados-usuarios/dados-usuarios.ts index b94a1c9..3526def 100644 --- a/src/app/pages/dados-usuarios/dados-usuarios.ts +++ b/src/app/pages/dados-usuarios/dados-usuarios.ts @@ -2,7 +2,9 @@ import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { HttpErrorResponse } from '@angular/common/http'; +import { firstValueFrom } from 'rxjs'; import { CustomSelectComponent } from '../../components/custom-select/custom-select'; +import { TableExportService } from '../../services/table-export.service'; import { DadosUsuariosService, @@ -45,6 +47,7 @@ export class DadosUsuarios implements OnInit { @ViewChild('successToast', { static: false }) successToast!: ElementRef; loading = false; + exporting = false; errorMsg = ''; // Filtros @@ -116,7 +119,8 @@ export class DadosUsuarios implements OnInit { constructor( private service: DadosUsuariosService, private authService: AuthService, - private linesService: LinesService + private linesService: LinesService, + private tableExportService: TableExportService ) {} ngOnInit(): void { @@ -256,6 +260,112 @@ export class DadosUsuarios implements OnInit { } clearFilters() { this.search = ''; this.fetch(1); } + + async onExport(): Promise { + if (this.exporting) return; + this.exporting = true; + + try { + const baseRows = await this.fetchAllRowsForExport(); + const rows = await this.fetchDetailedRowsForExport(baseRows); + if (!rows.length) { + this.showToast('Nenhum registro encontrado para exportar.', 'danger'); + return; + } + + const timestamp = this.tableExportService.buildTimestamp(); + const fileName = `dados_usuarios_${this.tipoFilter.toLowerCase()}_${timestamp}`; + + await this.tableExportService.exportAsXlsx({ + fileName, + sheetName: 'DadosUsuarios', + rows, + columns: [ + { header: 'ID', value: (row) => row.id ?? '' }, + { header: 'Tipo', value: (row) => this.normalizeTipo(row) }, + { header: 'Cliente', value: (row) => row.cliente ?? '' }, + { + header: this.tipoFilter === 'PJ' ? 'Razao Social' : 'Nome', + value: (row) => (this.normalizeTipo(row) === 'PJ' ? (row.razaoSocial ?? row.cliente ?? '') : (row.nome ?? row.cliente ?? '')), + }, + { header: 'Item', type: 'number', value: (row) => this.toNullableNumber(row.item) ?? 0 }, + { header: 'Linha', value: (row) => row.linha ?? '' }, + { header: 'CPF', value: (row) => row.cpf ?? '' }, + { header: 'CNPJ', value: (row) => row.cnpj ?? '' }, + { header: 'E-mail', value: (row) => row.email ?? '' }, + { header: 'Celular', value: (row) => row.celular ?? '' }, + { header: 'Telefone Fixo', value: (row) => row.telefoneFixo ?? '' }, + { header: 'RG', value: (row) => row.rg ?? '' }, + { header: 'Endereco', value: (row) => row.endereco ?? '' }, + { header: 'Data de Nascimento', type: 'date', value: (row) => row.dataNascimento ?? '' }, + ], + }); + + this.showToast(`Planilha exportada com ${rows.length} registro(s).`, 'success'); + } catch { + this.showToast('Erro ao exportar planilha.', 'danger'); + } finally { + this.exporting = false; + } + } + + private async fetchAllRowsForExport(): Promise { + const pageSize = 500; + let page = 1; + let expectedTotal = 0; + const all: UserDataRow[] = []; + + while (page <= 500) { + const response = await firstValueFrom( + this.service.getRows({ + search: this.search?.trim(), + tipo: this.tipoFilter, + page, + pageSize, + sortBy: 'item', + sortDir: 'asc', + }) + ); + + const items = response?.items ?? []; + expectedTotal = response?.total ?? 0; + all.push(...items); + + if (items.length === 0) break; + if (items.length < pageSize) break; + if (expectedTotal > 0 && all.length >= expectedTotal) break; + page += 1; + } + + return all.sort((a, b) => { + const byClient = (a.cliente ?? '').localeCompare(b.cliente ?? '', 'pt-BR', { sensitivity: 'base' }); + if (byClient !== 0) return byClient; + return (this.toNullableNumber(a.item) ?? 0) - (this.toNullableNumber(b.item) ?? 0); + }); + } + + private async fetchDetailedRowsForExport(rows: UserDataRow[]): Promise { + if (!rows.length) return []; + + const detailed: UserDataRow[] = []; + const chunkSize = 10; + + for (let i = 0; i < rows.length; i += chunkSize) { + const chunk = rows.slice(i, i + chunkSize); + const resolved = await Promise.all( + chunk.map(async (row) => { + try { + return await firstValueFrom(this.service.getById(row.id)); + } catch { + return row; + } + }) + ); + detailed.push(...resolved); + } + + return detailed; + } onPageSizeChange() { this.page = 1; diff --git a/src/app/pages/faturamento/faturamento.html b/src/app/pages/faturamento/faturamento.html index b8dc60b..0fdbf91 100644 --- a/src/app/pages/faturamento/faturamento.html +++ b/src/app/pages/faturamento/faturamento.html @@ -33,7 +33,12 @@ Totais, lucro e comparativo Vivo x Line -
+
+ +
@@ -184,7 +189,6 @@
-
diff --git a/src/app/pages/faturamento/faturamento.ts b/src/app/pages/faturamento/faturamento.ts index 0c46cbb..7046ebc 100644 --- a/src/app/pages/faturamento/faturamento.ts +++ b/src/app/pages/faturamento/faturamento.ts @@ -25,7 +25,9 @@ import { } from '../../services/billing'; import { AuthService } from '../../services/auth.service'; import { LinesService } from '../../services/lines.service'; +import { TableExportService } from '../../services/table-export.service'; import { confirmDeletionWithTyping } from '../../utils/destructive-confirmation'; +import { firstValueFrom } from 'rxjs'; interface BillingClientGroup { cliente: string; @@ -54,10 +56,12 @@ export class Faturamento implements AfterViewInit, OnDestroy { private billing: BillingService, private linesService: LinesService, private cdr: ChangeDetectorRef, - private authService: AuthService + private authService: AuthService, + private tableExportService: TableExportService ) {} loading = false; + exporting = false; // filtros searchTerm = ''; @@ -415,6 +419,85 @@ export class Faturamento implements AfterViewInit, OnDestroy { this.loadAllAndApply(forceReloadAll); } + async onExport(): Promise { + if (this.exporting) return; + this.exporting = true; + + try { + const baseRows = this.getRowsForExport(); + const rows = await this.fetchDetailedRowsForExport(baseRows); + if (!rows.length) { + await this.showToast('Nenhum registro encontrado para exportar.'); + return; + } + + const timestamp = this.tableExportService.buildTimestamp(); + const suffix = this.filterTipo === 'ALL' ? 'todos' : this.filterTipo.toLowerCase(); + await this.tableExportService.exportAsXlsx({ + fileName: `faturamento_${suffix}_${timestamp}`, + sheetName: 'Faturamento', + rows, + columns: [ + { header: 'ID', value: (row) => row.id ?? '' }, + { header: 'Tipo', value: (row) => row.tipo ?? '' }, + { header: 'Item', type: 'number', value: (row) => this.toNullableNumber(row.item) ?? 0 }, + { header: 'Cliente', value: (row) => row.cliente ?? '' }, + { header: 'Qtd Linhas', type: 'number', value: (row) => this.toNullableNumber(row.qtdLinhas) ?? 0 }, + { header: 'Franquia Vivo', type: 'number', value: (row) => this.toNullableNumber(row.franquiaVivo) ?? 0 }, + { header: 'Valor Contrato Vivo', type: 'currency', value: (row) => this.toNullableNumber(row.valorContratoVivo) ?? 0 }, + { header: 'Franquia Line', type: 'number', value: (row) => this.toNullableNumber(row.franquiaLine) ?? 0 }, + { header: 'Valor Contrato Line', type: 'currency', value: (row) => this.toNullableNumber(row.valorContratoLine) ?? 0 }, + { header: 'Lucro', type: 'currency', value: (row) => this.toNullableNumber(row.lucro) ?? 0 }, + { header: 'Aparelho', value: (row) => row.aparelho ?? '' }, + { header: 'Forma de Pagamento', value: (row) => row.formaPagamento ?? '' }, + { header: 'Observacao', value: (row) => this.getObservacao(row) }, + { header: 'Criado Em', type: 'datetime', value: (row) => row.createdAt ?? '' }, + { header: 'Atualizado Em', type: 'datetime', value: (row) => row.updatedAt ?? '' }, + ], + }); + + await this.showToast(`Planilha exportada com ${rows.length} registro(s).`); + } catch { + await this.showToast('Erro ao exportar planilha.'); + } finally { + this.exporting = false; + } + } + + private getRowsForExport(): BillingItem[] { + const rows: BillingItem[] = []; + this.rowsByClient.forEach((items) => rows.push(...items)); + + return rows.sort((a, b) => { + const byClient = (a.cliente ?? '').localeCompare(b.cliente ?? '', 'pt-BR', { sensitivity: 'base' }); + if (byClient !== 0) return byClient; + return (this.toNullableNumber(a.item) ?? 0) - (this.toNullableNumber(b.item) ?? 0); + }); + } + + private async fetchDetailedRowsForExport(rows: BillingItem[]): Promise { + if (!rows.length) return []; + + const detailed: BillingItem[] = []; + const chunkSize = 10; + + for (let i = 0; i < rows.length; i += chunkSize) { + const chunk = rows.slice(i, i + chunkSize); + const resolved = await Promise.all( + chunk.map(async (row) => { + try { + return await firstValueFrom(this.billing.getById(row.id)); + } catch { + return row; + } + }) + ); + detailed.push(...resolved); + } + + return detailed; + } + private getAllItems(force = false): Promise { const now = Date.now(); @@ -795,4 +878,22 @@ export class Faturamento implements AfterViewInit, OnDestroy { const n = Number(value); return Number.isNaN(n) ? null : n; } + + private async showToast(message: string): Promise { + if (!isPlatformBrowser(this.platformId)) return; + this.toastMessage = message; + this.cdr.detectChanges(); + if (!this.successToast?.nativeElement) return; + + try { + const bs = await import('bootstrap'); + const toastInstance = bs.Toast.getOrCreateInstance(this.successToast.nativeElement, { + autohide: true, + delay: 3000, + }); + toastInstance.show(); + } catch (error) { + console.error(error); + } + } } diff --git a/src/app/pages/geral/geral.html b/src/app/pages/geral/geral.html index 6ba0955..f34ddaf 100644 --- a/src/app/pages/geral/geral.html +++ b/src/app/pages/geral/geral.html @@ -31,22 +31,42 @@ Tabela de linhas e dados de telefonia -
- +
+
+ + + +
+ + +
+ +
+ + Selecionadas: {{ batchStatusSelectionCount }} + + + +
@@ -312,6 +352,20 @@ {{ reservaSelectedCount > 0 && reservaSelectedCount === groupLines.length ? 'Limpar seleção' : 'Selecionar todas' }} + + + + + + + + +
{ @@ -303,6 +338,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { return `${apiBase}/templates`; })(); loading = false; + exporting = false; isSysAdmin = false; isGestor = false; isClientRestricted = false; @@ -378,6 +414,12 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { }; reservaTransferLastResult: AssignReservaLinesResultDto | null = null; moveToReservaLastResult: AssignReservaLinesResultDto | null = null; + batchStatusOpen = false; + batchStatusSaving = false; + batchStatusAction: BatchStatusAction = 'BLOCK'; + batchStatusType = ''; + batchStatusUsuario = ''; + batchStatusLastResult: BatchLineStatusUpdateResultDto | null = null; detailData: any = null; financeData: any = null; @@ -609,6 +651,46 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { return this.hasGroupLineSelectionTools && !this.isReservaExpandedGroup && !this.isExpandedGroupNamedReserva; } + get blockedStatusOptions(): string[] { + return this.statusOptions.filter((status) => !this.isActiveStatus(status)); + } + + get batchStatusSelectionCount(): number { + return this.reservaSelectedCount; + } + + get canOpenBatchStatusModal(): boolean { + if (this.isClientRestricted) return false; + if (this.loading || this.batchStatusSaving) return false; + return this.batchStatusSelectionCount > 0; + } + + get canSubmitBatchStatusModal(): boolean { + if (this.batchStatusSaving) return false; + if (this.batchStatusSelectionCount <= 0) return false; + if (this.batchStatusAction === 'BLOCK' && !String(this.batchStatusType ?? '').trim()) return false; + return true; + } + + get batchStatusActionLabel(): string { + return this.batchStatusAction === 'BLOCK' ? 'Bloquear' : 'Desbloquear'; + } + + get batchStatusTargetDescription(): string { + return `${this.batchStatusSelectionCount} linha(s) selecionada(s)`; + } + + get batchStatusUserOptions(): string[] { + const users = (this.groupLines ?? []) + .map((x) => (x.usuario ?? '').toString().trim()) + .filter((x) => !!x); + + const current = (this.batchStatusUsuario ?? '').toString().trim(); + if (current) users.push(current); + + return Array.from(new Set(users)).sort((a, b) => a.localeCompare(b, 'pt-BR', { sensitivity: 'base' })); + } + get reservaSelectedCount(): number { return this.reservaSelectedLineIds.length; } @@ -823,7 +905,15 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { // ✅ FIX PRINCIPAL: limpeza forçada de backdrops/scroll lock // ============================================================ private anyModalOpen(): boolean { - return !!(this.detailOpen || this.financeOpen || this.editOpen || this.createOpen || this.reservaTransferOpen || this.moveToReservaOpen); + return !!( + this.detailOpen || + this.financeOpen || + this.editOpen || + this.createOpen || + this.reservaTransferOpen || + this.moveToReservaOpen || + this.batchStatusOpen + ); } private cleanupModalArtifacts() { @@ -851,6 +941,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { this.createOpen = false; this.reservaTransferOpen = false; this.moveToReservaOpen = false; + this.batchStatusOpen = false; this.detailData = null; this.financeData = null; @@ -869,8 +960,11 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { this.batchExcelTemplateDownloading = false; this.reservaTransferSaving = false; this.moveToReservaSaving = false; + this.batchStatusSaving = false; this.reservaTransferLastResult = null; this.moveToReservaLastResult = null; + this.batchStatusLastResult = null; + this.batchStatusUsuario = ''; // Limpa overlays/locks residuais this.cleanupModalArtifacts(); @@ -1800,6 +1894,225 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { this.refreshData(); } + async onExport(): Promise { + if (this.exporting) return; + this.exporting = true; + + try { + const baseRows = await this.getRowsForExport(); + const rows = await this.getDetailedRowsForExport(baseRows); + if (!rows.length) { + await this.showToast('Nenhum registro encontrado para exportar.'); + return; + } + + const suffix = this.getExportFilterSuffix(); + const timestamp = this.tableExportService.buildTimestamp(); + const fileName = `geral_${suffix}_${timestamp}`; + const templateBuffer = await this.getGeralTemplateBuffer(); + + await this.tableExportService.exportAsXlsx({ + fileName, + sheetName: 'Geral', + templateBuffer, + rows, + columns: [ + { header: 'ID', value: (row) => row.id }, + { header: 'Item', type: 'number', value: (row) => this.toInt(row.item) }, + { header: 'Empresa (Conta)', value: (row) => this.findEmpresaByConta(row.conta) }, + { header: 'Conta', value: (row) => row.conta ?? '' }, + { header: 'Linha', value: (row) => row.linha ?? '' }, + { header: 'Chip', value: (row) => row.chip ?? '' }, + { header: 'Tipo de Chip', value: (row) => row.tipoDeChip ?? '' }, + { header: 'Cliente', value: (row) => row.cliente ?? '' }, + { header: 'Usuario', value: (row) => row.usuario ?? '' }, + { header: 'Centro de Custos', value: (row) => row.centroDeCustos ?? '' }, + { header: 'Setor ID', value: (row) => row.setorId ?? '' }, + { header: 'Setor', value: (row) => row.setorNome ?? '' }, + { header: 'Aparelho ID', value: (row) => row.aparelhoId ?? '' }, + { header: 'Aparelho', value: (row) => row.aparelhoNome ?? '' }, + { header: 'Cor do Aparelho', value: (row) => row.aparelhoCor ?? '' }, + { header: 'IMEI do Aparelho', value: (row) => row.aparelhoImei ?? '' }, + { header: 'NF do Aparelho (anexo)', type: 'boolean', value: (row) => !!row.aparelhoNotaFiscalTemArquivo }, + { header: 'Recibo do Aparelho (anexo)', type: 'boolean', value: (row) => !!row.aparelhoReciboTemArquivo }, + { header: 'Plano Contrato', value: (row) => row.planoContrato ?? '' }, + { header: 'Status', value: (row) => row.status ?? '' }, + { header: 'Tipo (Skil)', value: (row) => row.skil ?? '' }, + { header: 'Modalidade', value: (row) => row.modalidade ?? '' }, + { header: 'Cedente', value: (row) => row.cedente ?? '' }, + { header: 'Solicitante', value: (row) => row.solicitante ?? '' }, + { header: 'Data de Bloqueio', type: 'date', value: (row) => row.dataBloqueio ?? '' }, + { header: 'Data Entrega Operadora', type: 'date', value: (row) => row.dataEntregaOpera ?? '' }, + { header: 'Data Entrega Cliente', type: 'date', value: (row) => row.dataEntregaCliente ?? '' }, + { header: 'Dt. Efetivacao Servico', type: 'date', value: (row) => row.dtEfetivacaoServico ?? '' }, + { header: 'Dt. Termino Fidelizacao', type: 'date', value: (row) => row.dtTerminoFidelizacao ?? '' }, + { header: 'Vencimento da Conta', value: (row) => row.vencConta ?? '' }, + { header: 'Franquia Vivo', type: 'number', value: (row) => this.toNullableNumber(row.franquiaVivo) ?? 0 }, + { header: 'Valor Plano Vivo', type: 'currency', value: (row) => this.toNullableNumber(row.valorPlanoVivo) ?? 0 }, + { header: 'Gestao Voz e Dados', type: 'currency', value: (row) => this.toNullableNumber(row.gestaoVozDados) ?? 0 }, + { header: 'Skeelo', type: 'currency', value: (row) => this.toNullableNumber(row.skeelo) ?? 0 }, + { header: 'Vivo News Plus', type: 'currency', value: (row) => this.toNullableNumber(row.vivoNewsPlus) ?? 0 }, + { header: 'Vivo Travel Mundo', type: 'currency', value: (row) => this.toNullableNumber(row.vivoTravelMundo) ?? 0 }, + { header: 'Vivo Gestao Dispositivo', type: 'currency', value: (row) => this.toNullableNumber(row.vivoGestaoDispositivo) ?? 0 }, + { header: 'Vivo Sync', type: 'currency', value: (row) => this.toNullableNumber(row.vivoSync) ?? 0 }, + { header: 'Valor Contrato Vivo', type: 'currency', value: (row) => this.toNullableNumber(row.valorContratoVivo) ?? 0 }, + { header: 'Franquia Line', type: 'number', value: (row) => this.toNullableNumber(row.franquiaLine) ?? 0 }, + { header: 'Franquia Gestao', type: 'number', value: (row) => this.toNullableNumber(row.franquiaGestao) ?? 0 }, + { header: 'Locacao AP', type: 'currency', value: (row) => this.toNullableNumber(row.locacaoAp) ?? 0 }, + { header: 'Valor Contrato Line', type: 'currency', value: (row) => this.toNullableNumber(row.valorContratoLine) ?? 0 }, + { header: 'Desconto', type: 'currency', value: (row) => this.toNullableNumber(row.desconto) ?? 0 }, + { header: 'Lucro', type: 'currency', value: (row) => this.toNullableNumber(row.lucro) ?? 0 }, + { header: 'Criado Em', type: 'datetime', value: (row) => this.getAnyField(row, ['createdAt', 'CreatedAt']) ?? '' }, + { header: 'Atualizado Em', type: 'datetime', value: (row) => this.getAnyField(row, ['updatedAt', 'UpdatedAt']) ?? '' }, + ], + }); + + await this.showToast(`Planilha exportada com ${rows.length} registro(s).`); + } catch { + await this.showToast('Erro ao exportar a planilha.'); + } finally { + this.exporting = false; + } + } + + private async getRowsForExport(): Promise { + let lines = await this.fetchLinesForGrouping(); + + if (this.selectedClients.length > 0) { + const selected = new Set( + this.selectedClients.map((client) => (client ?? '').toString().trim().toUpperCase()) + ); + lines = lines.filter((line) => selected.has((line.cliente ?? '').toString().trim().toUpperCase())); + } + + const fallbackClient = this.filterSkil === 'RESERVA' ? 'RESERVA' : 'SEM CLIENTE'; + const mapped = lines.map((line) => ({ + id: (line.id ?? '').toString(), + item: String(line.item ?? ''), + linha: line.linha ?? '', + chip: line.chip ?? '', + cliente: ((line.cliente ?? '').toString().trim()) || fallbackClient, + usuario: line.usuario ?? '', + centroDeCustos: line.centroDeCustos ?? '', + setorNome: line.setorNome ?? '', + aparelhoNome: line.aparelhoNome ?? '', + aparelhoCor: line.aparelhoCor ?? '', + status: line.status ?? '', + skil: line.skil ?? '', + contrato: line.vencConta ?? '', + })); + + return mapped.sort((a, b) => { + const byClient = (a.cliente ?? '').localeCompare(b.cliente ?? '', 'pt-BR', { sensitivity: 'base' }); + if (byClient !== 0) return byClient; + + const byItem = this.toInt(a.item) - this.toInt(b.item); + if (byItem !== 0) return byItem; + + return (a.linha ?? '').localeCompare(b.linha ?? '', 'pt-BR', { sensitivity: 'base' }); + }); + } + + private async getDetailedRowsForExport(baseRows: LineRow[]): Promise { + if (!baseRows.length) return []; + + const result: ApiLineDetail[] = []; + const chunkSize = 8; + + for (let i = 0; i < baseRows.length; i += chunkSize) { + const chunk = baseRows.slice(i, i + chunkSize); + const fetched = await Promise.all( + chunk.map(async (row) => { + try { + return await firstValueFrom( + this.http.get(`${this.apiBase}/${row.id}`, { + params: this.withNoCache(new HttpParams()), + }) + ); + } catch { + return this.toDetailFallback(row); + } + }) + ); + + result.push(...fetched); + } + + return result; + } + + private toDetailFallback(row: LineRow): ApiLineDetail { + return { + id: row.id, + item: this.toInt(row.item), + qtdLinhas: null, + conta: row.contrato ?? null, + linha: row.linha ?? null, + chip: row.chip ?? null, + tipoDeChip: null, + cliente: row.cliente ?? null, + usuario: row.usuario ?? null, + centroDeCustos: row.centroDeCustos ?? null, + setorId: null, + setorNome: row.setorNome ?? null, + aparelhoId: null, + aparelhoNome: row.aparelhoNome ?? null, + aparelhoCor: row.aparelhoCor ?? null, + aparelhoImei: null, + aparelhoNotaFiscalTemArquivo: false, + aparelhoReciboTemArquivo: false, + planoContrato: null, + status: row.status ?? null, + skil: row.skil ?? null, + modalidade: null, + dataBloqueio: null, + cedente: null, + solicitante: null, + dataEntregaOpera: null, + dataEntregaCliente: null, + dtEfetivacaoServico: null, + dtTerminoFidelizacao: null, + vencConta: row.contrato ?? null, + franquiaVivo: null, + valorPlanoVivo: null, + gestaoVozDados: null, + skeelo: null, + vivoNewsPlus: null, + vivoTravelMundo: null, + vivoGestaoDispositivo: null, + vivoSync: null, + valorContratoVivo: null, + franquiaLine: null, + franquiaGestao: null, + locacaoAp: null, + valorContratoLine: null, + desconto: null, + lucro: null, + }; + } + + private async getGeralTemplateBuffer(): Promise { + try { + const params = new HttpParams().set('_', `${Date.now()}`); + const blob = await firstValueFrom( + this.http.get(`${this.templatesApiBase}/planilha-geral`, { + params, + responseType: 'blob', + }) + ); + return await blob.arrayBuffer(); + } catch { + return null; + } + } + + private getExportFilterSuffix(): string { + if (this.filterSkil === 'PF') return 'pf'; + if (this.filterSkil === 'PJ') return 'pj'; + if (this.filterSkil === 'RESERVA') return 'reserva'; + return 'todas'; + } + async onImportExcel() { if (!this.isSysAdmin) { await this.showToast('Você não tem permissão para importar planilha.'); @@ -3167,6 +3480,116 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { this.reservaSelectedLineIds = []; } + async openBatchStatusModal(action: BatchStatusAction) { + if (this.isClientRestricted) { + await this.showToast('Você não tem permissão para bloquear/desbloquear em lote.'); + return; + } + + if (this.batchStatusSelectionCount <= 0) { + await this.showToast('Selecione ao menos uma linha para processar.'); + return; + } + + this.batchStatusAction = action; + this.batchStatusSaving = false; + this.batchStatusLastResult = null; + this.batchStatusUsuario = ''; + + if (action === 'BLOCK') { + const current = (this.batchStatusType ?? '').toString().trim(); + const options = this.blockedStatusOptions; + if (!current || !options.some((x) => x === current)) { + this.batchStatusType = options[0] ?? ''; + } + } else { + this.batchStatusType = ''; + } + + this.batchStatusOpen = true; + this.cdr.detectChanges(); + } + + async submitBatchStatusUpdate() { + if (this.batchStatusSaving) return; + if (!this.canSubmitBatchStatusModal) return; + + const payload = this.buildBatchStatusPayload(); + this.batchStatusSaving = true; + + this.http.post(`${this.apiBase}/batch-status-update`, payload).subscribe({ + next: async (res) => { + this.batchStatusSaving = false; + this.batchStatusLastResult = res; + + const ok = Number(res?.updated ?? 0) || 0; + const failed = Number(res?.failed ?? 0) || 0; + + this.batchStatusOpen = false; + this.clearReservaSelection(); + this.batchStatusUsuario = ''; + + await this.showToast( + failed > 0 + ? `${this.batchStatusActionLabel} em lote concluído com pendências: ${ok} linha(s) processada(s), ${failed} falha(s).` + : `${this.batchStatusActionLabel} em lote concluído: ${ok} linha(s) processada(s).` + ); + + if (this.expandedGroup) { + const term = (this.searchTerm ?? '').trim(); + const useTerm = term && this.isSpecificSearchTerm(term) ? term : undefined; + this.fetchGroupLines(this.expandedGroup, useTerm); + } + + this.loadGroups(); + this.loadKpis(); + }, + error: async (err: HttpErrorResponse) => { + this.batchStatusSaving = false; + const msg = (err.error as any)?.message || 'Erro ao processar bloqueio/desbloqueio em lote.'; + await this.showToast(msg); + } + }); + } + + private buildBatchStatusPayload(): BatchLineStatusUpdateRequestDto { + const clients = this.searchResolvedClient + ? [this.searchResolvedClient] + : [...this.selectedClients]; + + const normalizedClients = clients + .map((x) => (x ?? '').toString().trim()) + .filter((x) => !!x); + + const userFilter = (this.batchStatusUsuario ?? '').toString().trim(); + + return { + action: this.batchStatusAction === 'BLOCK' ? 'block' : 'unblock', + blockStatus: this.batchStatusAction === 'BLOCK' ? (this.batchStatusType || null) : null, + applyToAllFiltered: false, + lineIds: [...this.reservaSelectedLineIds], + search: (this.searchTerm ?? '').toString().trim() || null, + skil: this.resolveFilterSkilForApi(), + clients: normalizedClients, + additionalMode: this.resolveAdditionalModeForApi(), + additionalServices: this.selectedAdditionalServices.length > 0 ? this.selectedAdditionalServices.join(',') : null, + usuario: userFilter || null + }; + } + + private resolveFilterSkilForApi(): string | null { + if (this.filterSkil === 'PF') return 'PESSOA FÍSICA'; + if (this.filterSkil === 'PJ') return 'PESSOA JURÍDICA'; + if (this.filterSkil === 'RESERVA') return 'RESERVA'; + return null; + } + + private resolveAdditionalModeForApi(): string | null { + if (this.additionalMode === 'WITH') return 'with'; + if (this.additionalMode === 'WITHOUT') return 'without'; + return null; + } + async openReservaTransferModal() { if (!this.isReservaExpandedGroup) { await this.showToast('Abra um grupo no filtro Reserva para selecionar e atribuir linhas.'); @@ -3588,6 +4011,12 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { return v || '-'; } + private isActiveStatus(status: string | null | undefined): boolean { + const normalized = (status ?? '').toString().trim().toLowerCase(); + if (!normalized) return false; + return normalized.includes('ativo'); + } + private toEditModel(d: ApiLineDetail): any { return { ...d, @@ -3660,6 +4089,16 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { return Number.isNaN(n) ? null : n; } + private getAnyField(row: unknown, keys: string[]): unknown { + const source = row as Record; + for (const key of keys) { + if (source && source[key] !== undefined && source[key] !== null && source[key] !== '') { + return source[key]; + } + } + return null; + } + private mergeOption(current: any, list: string[]): string[] { const v = (current ?? '').toString().trim(); if (!v) return list; diff --git a/src/app/pages/historico/historico.html b/src/app/pages/historico/historico.html index e01f1e8..9cc9940 100644 --- a/src/app/pages/historico/historico.html +++ b/src/app/pages/historico/historico.html @@ -33,6 +33,10 @@ +
diff --git a/src/app/pages/historico/historico.ts b/src/app/pages/historico/historico.ts index 5ce35cf..16fe62c 100644 --- a/src/app/pages/historico/historico.ts +++ b/src/app/pages/historico/historico.ts @@ -2,9 +2,11 @@ import { Component, OnInit, ElementRef, ViewChild, ChangeDetectorRef, Inject, PL import { CommonModule, isPlatformBrowser } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { HttpErrorResponse } from '@angular/common/http'; +import { firstValueFrom } from 'rxjs'; import { CustomSelectComponent } from '../../components/custom-select/custom-select'; import { HistoricoService, AuditLogDto, AuditChangeType, HistoricoQuery } from '../../services/historico.service'; +import { TableExportService } from '../../services/table-export.service'; interface SelectOption { value: string; @@ -23,6 +25,7 @@ export class Historico implements OnInit { logs: AuditLogDto[] = []; loading = false; + exporting = false; error = false; errorMsg = ''; toastMessage = ''; @@ -65,7 +68,8 @@ export class Historico implements OnInit { constructor( private historicoService: HistoricoService, private cdr: ChangeDetectorRef, - @Inject(PLATFORM_ID) private platformId: object + @Inject(PLATFORM_ID) private platformId: object, + private tableExportService: TableExportService ) {} ngOnInit(): void { @@ -111,6 +115,47 @@ export class Historico implements OnInit { this.fetch(); } + async onExport(): Promise { + if (this.exporting) return; + this.exporting = true; + + try { + const logs = await this.fetchAllLogsForExport(); + if (!logs.length) { + await this.showToast('Nenhum registro encontrado para exportar.'); + return; + } + + const timestamp = this.tableExportService.buildTimestamp(); + await this.tableExportService.exportAsXlsx({ + fileName: `historico_${timestamp}`, + sheetName: 'Historico', + rows: logs, + columns: [ + { header: 'ID', value: (log) => log.id ?? '' }, + { header: 'Data/Hora', type: 'datetime', value: (log) => log.occurredAtUtc ?? '' }, + { header: 'Usuario', value: (log) => this.displayUserName(log) }, + { header: 'E-mail', value: (log) => log.userEmail ?? '' }, + { header: 'Pagina', value: (log) => log.page ?? '' }, + { header: 'Acao', value: (log) => this.formatAction(log.action) }, + { header: 'Entidade', value: (log) => this.displayEntity(log) }, + { header: 'Id Entidade', value: (log) => log.entityId ?? '' }, + { header: 'Metodo HTTP', value: (log) => log.requestMethod ?? '' }, + { header: 'Endpoint', value: (log) => log.requestPath ?? '' }, + { header: 'IP', value: (log) => log.ipAddress ?? '' }, + { header: 'Mudancas', value: (log) => this.formatChangesSummary(log) }, + { header: 'Qtd Mudancas', type: 'number', value: (log) => log.changes?.length ?? 0 }, + ], + }); + + await this.showToast(`Planilha exportada com ${logs.length} registro(s).`); + } catch { + await this.showToast('Erro ao exportar planilha.'); + } finally { + this.exporting = false; + } + } + goToPage(p: number): void { this.page = Math.max(1, Math.min(this.totalPages, p)); this.fetch(); @@ -217,14 +262,9 @@ export class Historico implements OnInit { this.expandedLogId = null; const query: HistoricoQuery = { + ...this.buildBaseQuery(), page: this.page, pageSize: this.pageSize, - pageName: this.filterPageName || undefined, - action: this.filterAction || undefined, - user: this.filterUser?.trim() || undefined, - search: this.filterSearch?.trim() || undefined, - dateFrom: this.toIsoDate(this.dateFrom, false) || undefined, - dateTo: this.toIsoDate(this.dateTo, true) || undefined, }; this.historicoService.list(query).subscribe({ @@ -247,6 +287,58 @@ export class Historico implements OnInit { }); } + private async fetchAllLogsForExport(): Promise { + const pageSize = 500; + let page = 1; + let expectedTotal = 0; + const all: AuditLogDto[] = []; + + while (page <= 500) { + const response = await firstValueFrom( + this.historicoService.list({ + ...this.buildBaseQuery(), + page, + pageSize, + }) + ); + + const items = response?.items ?? []; + expectedTotal = response?.total ?? 0; + all.push(...items); + + if (items.length === 0) break; + if (items.length < pageSize) break; + if (expectedTotal > 0 && all.length >= expectedTotal) break; + page += 1; + } + + return all; + } + + private buildBaseQuery(): Omit { + return { + pageName: this.filterPageName || undefined, + action: this.filterAction || undefined, + user: this.filterUser?.trim() || undefined, + search: this.filterSearch?.trim() || undefined, + dateFrom: this.toIsoDate(this.dateFrom, false) || undefined, + dateTo: this.toIsoDate(this.dateTo, true) || undefined, + }; + } + + private formatChangesSummary(log: AuditLogDto): string { + const changes = log?.changes ?? []; + if (!changes.length) return ''; + return changes + .map((change) => { + const field = change?.field ?? 'campo'; + const oldValue = this.formatChangeValue(change?.oldValue); + const newValue = this.formatChangeValue(change?.newValue); + return `${field}: ${oldValue} -> ${newValue}`; + }) + .join(' | '); + } + private toIsoDate(value: string, endOfDay: boolean): string | null { if (!value) return null; const time = endOfDay ? '23:59:59' : '00:00:00'; diff --git a/src/app/pages/mureg/mureg.html b/src/app/pages/mureg/mureg.html index 58c0764..9a4cfa7 100644 --- a/src/app/pages/mureg/mureg.html +++ b/src/app/pages/mureg/mureg.html @@ -31,6 +31,10 @@
+ diff --git a/src/app/pages/mureg/mureg.ts b/src/app/pages/mureg/mureg.ts index a09f8f8..86fdc7b 100644 --- a/src/app/pages/mureg/mureg.ts +++ b/src/app/pages/mureg/mureg.ts @@ -10,8 +10,10 @@ import { import { isPlatformBrowser, CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { HttpClient, HttpParams } from '@angular/common/http'; +import { firstValueFrom } from 'rxjs'; import { LinesService } from '../../services/lines.service'; import { CustomSelectComponent } from '../../components/custom-select/custom-select'; +import { TableExportService } from '../../services/table-export.service'; import { environment } from '../../../environments/environment'; import { confirmDeletionWithTyping } from '../../utils/destructive-confirmation'; @@ -75,6 +77,17 @@ interface MuregDetailDto { statusNaGeral: string | null; } +type MuregExportRow = MuregRow & { + usuario?: string | null; + skil?: string | null; + linhaAtualNaGeral?: string | null; + chipNaGeral?: string | null; + contaNaGeral?: string | null; + statusNaGeral?: string | null; + createdAt?: string | null; + updatedAt?: string | null; +}; + @Component({ standalone: true, imports: [CommonModule, FormsModule, CustomSelectComponent], @@ -84,6 +97,7 @@ interface MuregDetailDto { export class Mureg implements AfterViewInit { toastMessage = ''; loading = false; + exporting = false; @ViewChild('successToast', { static: false }) successToast!: ElementRef; @@ -91,7 +105,8 @@ export class Mureg implements AfterViewInit { @Inject(PLATFORM_ID) private platformId: object, private http: HttpClient, private cdr: ChangeDetectorRef, - private linesService: LinesService + private linesService: LinesService, + private tableExportService: TableExportService ) {} private readonly apiBase = (() => { @@ -184,6 +199,147 @@ export class Mureg implements AfterViewInit { this.loadForGroups(); } + async onExport(): Promise { + if (this.exporting) return; + this.exporting = true; + + try { + const baseRows = await this.fetchAllRowsForExport(); + const rows = await this.fetchDetailedRowsForExport(baseRows); + if (!rows.length) { + await this.showToast('Nenhum registro encontrado para exportar.'); + return; + } + + const timestamp = this.tableExportService.buildTimestamp(); + await this.tableExportService.exportAsXlsx({ + fileName: `mureg_${timestamp}`, + sheetName: 'Mureg', + rows, + columns: [ + { header: 'ID', value: (row) => row.id ?? '' }, + { header: 'Cliente', value: (row) => row.cliente }, + { header: 'Usuario', value: (row) => row.usuario ?? '' }, + { header: 'Skil', value: (row) => row.skil ?? '' }, + { header: 'Item', type: 'number', value: (row) => this.toIntOrZero(row.item) }, + { header: 'Linha Antiga', value: (row) => row.linhaAntiga }, + { header: 'Linha Nova', value: (row) => row.linhaNova }, + { header: 'ICCID', value: (row) => row.iccid }, + { header: 'Data da Mureg', type: 'date', value: (row) => row.dataDaMureg }, + { header: 'Situacao', value: (row) => (this.isTroca(row) ? 'TROCA' : 'SEM TROCA') }, + { header: 'Linha ID (Geral)', value: (row) => row.mobileLineId ?? '' }, + { header: 'Linha Atual na Geral', value: (row) => row.linhaAtualNaGeral ?? '' }, + { header: 'Chip na Geral', value: (row) => row.chipNaGeral ?? '' }, + { header: 'Conta na Geral', value: (row) => row.contaNaGeral ?? '' }, + { header: 'Status na Geral', value: (row) => row.statusNaGeral ?? '' }, + { header: 'Criado Em', type: 'datetime', value: (row) => row.createdAt ?? '' }, + { header: 'Atualizado Em', type: 'datetime', value: (row) => row.updatedAt ?? '' }, + ], + }); + + await this.showToast(`Planilha exportada com ${rows.length} registro(s).`); + } catch { + await this.showToast('Erro ao exportar planilha.'); + } finally { + this.exporting = false; + } + } + + private async fetchAllRowsForExport(): Promise { + const pageSize = 2000; + let page = 1; + let expectedTotal = 0; + const rows: MuregRow[] = []; + + while (page <= 500) { + const params = new HttpParams() + .set('page', String(page)) + .set('pageSize', String(pageSize)) + .set('search', (this.searchTerm ?? '').trim()) + .set('sortBy', 'cliente') + .set('sortDir', 'asc'); + + const response = await firstValueFrom( + this.http.get | any[]>(this.apiBase, { params }) + ); + + const items = Array.isArray(response) ? response : (response.items ?? []); + const normalized = items.map((item: any, idx: number) => this.normalizeRow(item, rows.length + idx)); + rows.push(...normalized); + expectedTotal = Array.isArray(response) ? 0 : Number(response.total ?? 0); + + if (Array.isArray(response)) break; + if (items.length === 0) break; + if (items.length < pageSize) break; + if (expectedTotal > 0 && rows.length >= expectedTotal) break; + page += 1; + } + + return rows.sort((a, b) => { + const byClient = (a.cliente ?? '').localeCompare(b.cliente ?? '', 'pt-BR', { sensitivity: 'base' }); + if (byClient !== 0) return byClient; + + const byItem = this.toIntOrZero(a.item) - this.toIntOrZero(b.item); + if (byItem !== 0) return byItem; + + return (a.linhaNova ?? '').localeCompare(b.linhaNova ?? '', 'pt-BR', { sensitivity: 'base' }); + }); + } + + private async fetchDetailedRowsForExport(rows: MuregRow[]): Promise { + if (!rows.length) return []; + + const result: MuregExportRow[] = []; + const chunkSize = 10; + + for (let i = 0; i < rows.length; i += chunkSize) { + const chunk = rows.slice(i, i + chunkSize); + const detailedChunk = await Promise.all( + chunk.map(async (row) => { + try { + const detail = await firstValueFrom(this.http.get(`${this.apiBase}/${row.id}`)); + const merged: MuregExportRow = { + ...row, + item: detail.item !== undefined && detail.item !== null ? String(detail.item) : row.item, + linhaAntiga: detail.linhaAntiga ?? row.linhaAntiga, + linhaNova: detail.linhaNova ?? row.linhaNova, + iccid: detail.iccid ?? row.iccid, + dataDaMureg: detail.dataDaMureg ?? row.dataDaMureg, + cliente: detail.cliente ?? row.cliente, + mobileLineId: detail.mobileLineId ?? row.mobileLineId, + usuario: detail.usuario ?? null, + skil: detail.skil ?? null, + linhaAtualNaGeral: detail.linhaAtualNaGeral ?? null, + chipNaGeral: detail.chipNaGeral ?? null, + contaNaGeral: detail.contaNaGeral ?? null, + statusNaGeral: detail.statusNaGeral ?? null, + createdAt: this.getRawField(detail, ['createdAt', 'CreatedAt']) ?? this.getRawField(row.raw, ['createdAt', 'CreatedAt']), + updatedAt: this.getRawField(detail, ['updatedAt', 'UpdatedAt']) ?? this.getRawField(row.raw, ['updatedAt', 'UpdatedAt']), + }; + + return merged; + } catch { + return { + ...row, + usuario: this.getRawField(row.raw, ['usuario', 'Usuario']), + skil: this.getRawField(row.raw, ['skil', 'Skil']), + linhaAtualNaGeral: this.getRawField(row.raw, ['linhaAtualNaGeral', 'LinhaAtualNaGeral']), + chipNaGeral: this.getRawField(row.raw, ['chipNaGeral', 'ChipNaGeral']), + contaNaGeral: this.getRawField(row.raw, ['contaNaGeral', 'ContaNaGeral']), + statusNaGeral: this.getRawField(row.raw, ['statusNaGeral', 'StatusNaGeral']), + createdAt: this.getRawField(row.raw, ['createdAt', 'CreatedAt']), + updatedAt: this.getRawField(row.raw, ['updatedAt', 'UpdatedAt']), + }; + } + }) + ); + + result.push(...detailedChunk); + } + + return result; + } + onSearch() { if (this.searchTimer) clearTimeout(this.searchTimer); this.searchTimer = setTimeout(() => { @@ -770,6 +926,15 @@ export class Mureg implements AfterViewInit { } } + private getRawField(source: any, keys: string[]): string | null { + for (const key of keys) { + const value = source?.[key]; + if (value === undefined || value === null || String(value).trim() === '') continue; + return String(value); + } + return null; + } + displayValue(key: MuregKey, v: any): string { if (v === null || v === undefined || String(v).trim() === '') return '-'; diff --git a/src/app/pages/parcelamentos/components/parcelamentos-table/parcelamentos-table.scss b/src/app/pages/parcelamentos/components/parcelamentos-table/parcelamentos-table.scss index f1c8de2..cd072b7 100644 --- a/src/app/pages/parcelamentos/components/parcelamentos-table/parcelamentos-table.scss +++ b/src/app/pages/parcelamentos/components/parcelamentos-table/parcelamentos-table.scss @@ -429,14 +429,14 @@ display: inline-flex; align-items: center; gap: 8px; +} - span { - color: var(--pg-text-soft, #64748b); - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.05em; - font-weight: 800; - } +.page-size span { + color: var(--pg-text-soft, #64748b); + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.05em; + font-weight: 800; } .select-glass { diff --git a/src/app/pages/parcelamentos/parcelamentos.html b/src/app/pages/parcelamentos/parcelamentos.html index 1697b20..78ba26f 100644 --- a/src/app/pages/parcelamentos/parcelamentos.html +++ b/src/app/pages/parcelamentos/parcelamentos.html @@ -1,4 +1,14 @@
+
+
+
+ LineGestao + +
+
{{ toastMessage }}
+
+
+
diff --git a/src/app/pages/resumo/resumo.ts b/src/app/pages/resumo/resumo.ts index 3a7706d..a2c04d2 100644 --- a/src/app/pages/resumo/resumo.ts +++ b/src/app/pages/resumo/resumo.ts @@ -31,6 +31,7 @@ import { ReservaPorDdd, ReservaTotal } from '../../services/resumo.service'; +import { TableExportService, type ExportCellType } from '../../services/table-export.service'; import { environment } from '../../../environments/environment'; type ResumoTab = 'planos' | 'clientes' | 'totais' | 'reserva'; @@ -85,6 +86,11 @@ export class Resumo implements OnInit, AfterViewInit, OnDestroy { loading = false; errorMessage = ''; + toastOpen = false; + toastMessage = ''; + toastType: 'success' | 'danger' = 'success'; + private toastTimer: ReturnType | null = null; + private exportingKeys = new Set(); resumo: ResumoResponse | null = null; activeTab: ResumoTab = 'planos'; @@ -139,7 +145,8 @@ export class Resumo implements OnInit, AfterViewInit, OnDestroy { private resumoService: ResumoService, private route: ActivatedRoute, private router: Router, - private cdr: ChangeDetectorRef + private cdr: ChangeDetectorRef, + private tableExportService: TableExportService ) { const raw = (environment.apiUrl || '').replace(/\/+$/, ''); this.baseApi = raw.toLowerCase().endsWith('/api') ? raw : `${raw}/api`; @@ -172,6 +179,7 @@ export class Resumo implements OnInit, AfterViewInit, OnDestroy { ngOnDestroy(): void { Object.values(this.charts).forEach(c => c?.destroy()); + if (this.toastTimer) clearTimeout(this.toastTimer); } setTab(tab: ResumoTab): void { @@ -644,7 +652,7 @@ export class Resumo implements OnInit, AfterViewInit, OnDestroy { this.updateGroupView(g); } toggleGroupedCompact(g: GroupedTableState) { g.compact = !g.compact; } - exportGroupedCsv(g: GroupedTableState, file: string) { this.exportCsv(g.table, file); } + exportGroupedCsv(g: GroupedTableState, file: string) { void this.exportTableAsXlsx(g.table, file); } isGroupedOpen(g: GroupedTableState, key: string) { return g.open.has(key); } toggleGroupedOpen(g: GroupedTableState, key: string) { if (g.open.has(key)) g.open.delete(key); else g.open.add(key); } openGroupedDetail(g: GroupedTableState, item: GroupItem) { g.detailGroup = item; g.detailOpen = true; } @@ -677,6 +685,10 @@ export class Resumo implements OnInit, AfterViewInit, OnDestroy { return normalized === 'true' || normalized === '1' || normalized === 'sim'; } + isExporting(key: string): boolean { + return this.exportingKeys.has(key); + } + private initTables() { const hideMoneyColumns = (cols: TableColumn[]) => this.showFinancial ? cols : cols.filter((c) => c.type !== 'money'); @@ -1214,78 +1226,59 @@ export class Resumo implements OnInit, AfterViewInit, OnDestroy { return Number.isNaN(parsed) ? null : parsed; } - private exportCsv(table: TableState, filename: string) { + private async exportTableAsXlsx(table: TableState, fileKey: string): Promise { if (!isPlatformBrowser(this.platformId)) return; + if (this.exportingKeys.has(fileKey)) return; + const rows = table.data ?? []; - const columns = table.columns ?? []; - const generatedAt = new Date().toLocaleString('pt-BR'); - const escapeHtml = (value: string) => - value - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); + if (!rows.length) { + this.showToast('Nenhum registro encontrado para exportar.', 'danger'); + return; + } - const headerHtml = columns - .map((column) => `${escapeHtml(column.label)}`) - .join(''); + this.exportingKeys.add(fileKey); + try { + const timestamp = this.tableExportService.buildTimestamp(); + await this.tableExportService.exportAsXlsx({ + fileName: `${fileKey}_${timestamp}`, + sheetName: table.label || 'Resumo', + rows, + columns: (table.columns ?? []).map((column) => ({ + header: column.label, + type: this.mapColumnType(column.type), + value: (row: T) => this.getExportColumnValue(column, row), + })), + }); - const bodyHtml = rows - .map((row, index) => { - const cells = columns - .map((column) => { - const value = this.formatCell(column, row); - const toneClass = column.tone ? this.getToneClass(column.value(row)) : ''; - const alignClass = column.align === 'right' ? 'text-right' : column.align === 'center' ? 'text-center' : ''; - const classes = [alignClass, toneClass].filter(Boolean).join(' '); - return `${escapeHtml(String(value))}`; - }) - .join(''); - return `${cells}`; - }) - .join(''); + this.showToast(`Planilha exportada com ${rows.length} registro(s).`, 'success'); + } catch { + this.showToast('Erro ao exportar planilha.', 'danger'); + } finally { + this.exportingKeys.delete(fileKey); + } + } - const html = ` - - - - - - -
${escapeHtml(table.label || 'Resumo')}
-
Exportado em ${escapeHtml(generatedAt)} | Total de linhas: ${rows.length}
- - - ${headerHtml} - - - ${bodyHtml} - -
- -`; + private getExportColumnValue(column: TableColumn, row: T): unknown { + const rawValue = column.value(row); + if (column.type === 'money' || column.type === 'number' || column.type === 'gb') { + const numeric = this.toNumber(rawValue); + if (numeric !== null) return numeric; + } + return this.formatCell(column, row); + } - const blob = new Blob([`\uFEFF${html}`], { type: 'application/vnd.ms-excel;charset=utf-8;' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `${filename}.xls`; - a.click(); - URL.revokeObjectURL(url); + private mapColumnType(type: TableColumn['type']): ExportCellType { + if (type === 'money') return 'currency'; + if (type === 'number' || type === 'gb') return 'number'; + return 'text'; + } + + private showToast(message: string, type: 'success' | 'danger'): void { + this.toastMessage = message; + this.toastType = type; + this.toastOpen = true; + if (this.toastTimer) clearTimeout(this.toastTimer); + this.toastTimer = setTimeout(() => (this.toastOpen = false), 3000); } private getReservaPorDddChartData(): Array<{ label: string; totalLinhas: number }> { @@ -1310,7 +1303,7 @@ export class Resumo implements OnInit, AfterViewInit, OnDestroy { return Array.from(map.entries()).map(([label, totalLinhas]) => ({ label, totalLinhas })); } - exportMacrophonyCsv() { this.exportCsv(this.tableMacrophony, 'macrophony-planos'); } + exportMacrophonyCsv() { void this.exportTableAsXlsx(this.tableMacrophony, 'macrophony-planos'); } findLineTotal(k: string[]): LineTotal | null { const keys = k.map((item) => item.toUpperCase()); const list = this.getEffectiveLineTotais(); diff --git a/src/app/pages/troca-numero/troca-numero.html b/src/app/pages/troca-numero/troca-numero.html index 6e31ffb..0edff36 100644 --- a/src/app/pages/troca-numero/troca-numero.html +++ b/src/app/pages/troca-numero/troca-numero.html @@ -31,6 +31,10 @@
+ @@ -86,7 +90,6 @@ Itens por pág:
-
diff --git a/src/app/pages/troca-numero/troca-numero.ts b/src/app/pages/troca-numero/troca-numero.ts index b27b637..8eda5bc 100644 --- a/src/app/pages/troca-numero/troca-numero.ts +++ b/src/app/pages/troca-numero/troca-numero.ts @@ -10,7 +10,9 @@ import { import { isPlatformBrowser, CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { HttpClient, HttpParams } from '@angular/common/http'; +import { firstValueFrom } from 'rxjs'; import { CustomSelectComponent } from '../../components/custom-select/custom-select'; +import { TableExportService } from '../../services/table-export.service'; import { environment } from '../../../environments/environment'; type TrocaKey = 'item' | 'linhaAntiga' | 'linhaNova' | 'iccid' | 'dataTroca' | 'motivo' | 'observacao'; @@ -63,13 +65,15 @@ interface LineOptionDto { export class TrocaNumero implements AfterViewInit { toastMessage = ''; loading = false; + exporting = false; @ViewChild('successToast', { static: false }) successToast!: ElementRef; constructor( @Inject(PLATFORM_ID) private platformId: object, private http: HttpClient, - private cdr: ChangeDetectorRef + private cdr: ChangeDetectorRef, + private tableExportService: TableExportService ) {} private readonly apiBase = (() => { @@ -151,6 +155,90 @@ export class TrocaNumero implements AfterViewInit { this.loadForGroups(); } + async onExport(): Promise { + if (this.exporting) return; + this.exporting = true; + + try { + const rows = await this.fetchAllRowsForExport(); + if (!rows.length) { + await this.showToast('Nenhum registro encontrado para exportar.'); + return; + } + + const timestamp = this.tableExportService.buildTimestamp(); + await this.tableExportService.exportAsXlsx({ + fileName: `troca_numero_${timestamp}`, + sheetName: 'TrocaNumero', + rows, + columns: [ + { header: 'ID', value: (row) => row.id ?? '' }, + { header: 'Motivo', value: (row) => row.motivo }, + { header: 'Cliente', value: (row) => this.getRawField(row, ['cliente', 'Cliente']) ?? '' }, + { header: 'Usuario', value: (row) => this.getRawField(row, ['usuario', 'Usuario']) ?? '' }, + { header: 'Skil', value: (row) => this.getRawField(row, ['skil', 'Skil']) ?? '' }, + { header: 'Item', type: 'number', value: (row) => this.toNumberOrNull(row.item) ?? 0 }, + { header: 'Linha Antiga', value: (row) => row.linhaAntiga }, + { header: 'Linha Nova', value: (row) => row.linhaNova }, + { header: 'ICCID', value: (row) => row.iccid }, + { header: 'Data da Troca', type: 'date', value: (row) => row.dataTroca }, + { header: 'Observacao', value: (row) => row.observacao }, + { header: 'Situacao', value: (row) => (this.isTroca(row) ? 'TROCA' : 'SEM TROCA') }, + { header: 'Linha ID (Geral)', value: (row) => this.getRawField(row, ['mobileLineId', 'MobileLineId']) ?? '' }, + { header: 'Criado Em', type: 'datetime', value: (row) => this.getRawField(row, ['createdAt', 'CreatedAt']) ?? '' }, + { header: 'Atualizado Em', type: 'datetime', value: (row) => this.getRawField(row, ['updatedAt', 'UpdatedAt']) ?? '' }, + ], + }); + + await this.showToast(`Planilha exportada com ${rows.length} registro(s).`); + } catch { + await this.showToast('Erro ao exportar planilha.'); + } finally { + this.exporting = false; + } + } + + private async fetchAllRowsForExport(): Promise { + const pageSize = 2000; + let page = 1; + let expectedTotal = 0; + const rows: TrocaRow[] = []; + + while (page <= 500) { + const params = new HttpParams() + .set('page', String(page)) + .set('pageSize', String(pageSize)) + .set('search', (this.searchTerm ?? '').trim()) + .set('sortBy', 'motivo') + .set('sortDir', 'asc'); + + const response = await firstValueFrom( + this.http.get | any[]>(this.apiBase, { params }) + ); + + const items = Array.isArray(response) ? response : (response.items ?? []); + const normalized = items.map((item: any, idx: number) => this.normalizeRow(item, rows.length + idx)); + rows.push(...normalized); + expectedTotal = Array.isArray(response) ? 0 : Number(response.total ?? 0); + + if (Array.isArray(response)) break; + if (items.length === 0) break; + if (items.length < pageSize) break; + if (expectedTotal > 0 && rows.length >= expectedTotal) break; + page += 1; + } + + return rows.sort((a, b) => { + const byMotivo = (a.motivo ?? '').localeCompare(b.motivo ?? '', 'pt-BR', { sensitivity: 'base' }); + if (byMotivo !== 0) return byMotivo; + + const byItem = (this.toNumberOrNull(a.item) ?? 0) - (this.toNumberOrNull(b.item) ?? 0); + if (byItem !== 0) return byItem; + + return (a.linhaNova ?? '').localeCompare(b.linhaNova ?? '', 'pt-BR', { sensitivity: 'base' }); + }); + } + onSearch() { if (this.searchTimer) clearTimeout(this.searchTimer); this.searchTimer = setTimeout(() => { @@ -542,6 +630,15 @@ export class TrocaNumero implements AfterViewInit { return Number.isFinite(n) ? n : null; } + private getRawField(row: TrocaRow, keys: string[]): string | null { + for (const key of keys) { + const value = row?.raw?.[key]; + if (value === undefined || value === null || String(value).trim() === '') continue; + return String(value); + } + return null; + } + private isoToDateInput(iso: string | null | undefined): string { if (!iso) return ''; const dt = new Date(iso); diff --git a/src/app/pages/vigencia/vigencia.html b/src/app/pages/vigencia/vigencia.html index 1ae2d25..1b92ea9 100644 --- a/src/app/pages/vigencia/vigencia.html +++ b/src/app/pages/vigencia/vigencia.html @@ -24,6 +24,10 @@ Controle de contratos e fidelização
+ diff --git a/src/app/pages/vigencia/vigencia.ts b/src/app/pages/vigencia/vigencia.ts index 31d1148..5b10bbe 100644 --- a/src/app/pages/vigencia/vigencia.ts +++ b/src/app/pages/vigencia/vigencia.ts @@ -3,12 +3,13 @@ import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { HttpErrorResponse } from '@angular/common/http'; import { ActivatedRoute } from '@angular/router'; -import { Subscription } from 'rxjs'; +import { Subscription, firstValueFrom } from 'rxjs'; import { VigenciaService, VigenciaRow, VigenciaClientGroup, PagedResult, UpdateVigenciaRequest } from '../../services/vigencia.service'; import { CustomSelectComponent } from '../../components/custom-select/custom-select'; import { AuthService } from '../../services/auth.service'; import { LinesService, MobileLineDetail } from '../../services/lines.service'; import { PlanAutoFillService } from '../../services/plan-autofill.service'; +import { TableExportService } from '../../services/table-export.service'; import { confirmDeletionWithTyping } from '../../utils/destructive-confirmation'; type SortDir = 'asc' | 'desc'; @@ -32,6 +33,7 @@ interface LineOptionDto { }) export class VigenciaComponent implements OnInit, OnDestroy { loading = false; + exporting = false; errorMsg = ''; // Filtros @@ -113,7 +115,8 @@ export class VigenciaComponent implements OnInit, OnDestroy { private authService: AuthService, private linesService: LinesService, private planAutoFill: PlanAutoFillService, - private route: ActivatedRoute + private route: ActivatedRoute, + private tableExportService: TableExportService ) {} ngOnInit(): void { @@ -295,6 +298,107 @@ export class VigenciaComponent implements OnInit, OnDestroy { this.fetch(1); } + async onExport(): Promise { + if (this.exporting) return; + this.exporting = true; + + try { + const baseRows = await this.fetchAllRowsForExport(); + const rows = await this.fetchDetailedRowsForExport(baseRows); + if (!rows.length) { + this.showToast('Nenhum registro encontrado para exportar.', 'danger'); + return; + } + + const timestamp = this.tableExportService.buildTimestamp(); + await this.tableExportService.exportAsXlsx({ + fileName: `vigencia_${timestamp}`, + sheetName: 'Vigencia', + rows, + columns: [ + { header: 'ID', value: (row) => row.id ?? '' }, + { header: 'Item', type: 'number', value: (row) => this.toNullableNumber(row.item) ?? 0 }, + { header: 'Linha', value: (row) => row.linha ?? '' }, + { header: 'Conta', value: (row) => row.conta ?? '' }, + { header: 'Cliente', value: (row) => row.cliente ?? '' }, + { header: 'Usuario', value: (row) => row.usuario ?? '' }, + { header: 'Plano', value: (row) => row.planoContrato ?? '' }, + { header: 'Efetivacao', type: 'date', value: (row) => row.dtEfetivacaoServico ?? '' }, + { header: 'Termino Fidelizacao', type: 'date', value: (row) => row.dtTerminoFidelizacao ?? '' }, + { header: 'Status', value: (row) => (this.isVencido(row.dtTerminoFidelizacao) ? 'Vencido' : 'Ativo') }, + { header: 'Auto Renovacao (anos)', type: 'number', value: (row) => this.toNullableNumber(row.autoRenewYears) ?? 0 }, + { header: 'Auto Renovacao Referencia', type: 'date', value: (row) => row.autoRenewReferenceEndDate ?? '' }, + { header: 'Auto Renovacao Configurada Em', type: 'datetime', value: (row) => row.autoRenewConfiguredAt ?? '' }, + { header: 'Ultima Auto Renovacao', type: 'datetime', value: (row) => row.lastAutoRenewedAt ?? '' }, + { header: 'Total', type: 'currency', value: (row) => this.toNullableNumber(row.total) ?? 0 }, + { header: 'Criado Em', type: 'datetime', value: (row) => row.createdAt ?? '' }, + { header: 'Atualizado Em', type: 'datetime', value: (row) => row.updatedAt ?? '' }, + ], + }); + + this.showToast(`Planilha exportada com ${rows.length} registro(s).`, 'success'); + } catch { + this.showToast('Erro ao exportar planilha.', 'danger'); + } finally { + this.exporting = false; + } + } + + private async fetchAllRowsForExport(): Promise { + const pageSize = 500; + let page = 1; + let expectedTotal = 0; + const all: VigenciaRow[] = []; + + while (page <= 500) { + const response = await firstValueFrom( + this.vigenciaService.getVigencia({ + search: this.search?.trim(), + client: this.client?.trim(), + page, + pageSize, + sortBy: 'item', + sortDir: 'asc', + }) + ); + + const items = response?.items ?? []; + expectedTotal = response?.total ?? 0; + all.push(...items); + + if (items.length === 0) break; + if (items.length < pageSize) break; + if (expectedTotal > 0 && all.length >= expectedTotal) break; + page += 1; + } + + return all; + } + + private async fetchDetailedRowsForExport(rows: VigenciaRow[]): Promise { + if (!rows.length) return []; + + const detailedRows: VigenciaRow[] = []; + const chunkSize = 10; + + for (let i = 0; i < rows.length; i += chunkSize) { + const chunk = rows.slice(i, i + chunkSize); + const resolved = await Promise.all( + chunk.map(async (row) => { + try { + return await firstValueFrom(this.vigenciaService.getById(row.id)); + } catch { + return row; + } + }) + ); + + detailedRows.push(...resolved); + } + + return detailedRows; + } + scheduleAutoRenew(row: VigenciaRow): void { if (!row?.id) return; const years = 2; diff --git a/src/app/services/table-export.service.ts b/src/app/services/table-export.service.ts new file mode 100644 index 0000000..285ed51 --- /dev/null +++ b/src/app/services/table-export.service.ts @@ -0,0 +1,462 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { firstValueFrom } from 'rxjs'; +import { environment } from '../../environments/environment'; + +export type ExportCellType = 'text' | 'number' | 'currency' | 'date' | 'datetime' | 'boolean'; + +export interface TableExportColumn { + header: string; + key?: string; + type?: ExportCellType; + width?: number; + value: (row: T, index: number) => unknown; +} + +export interface TableExportRequest { + fileName: string; + sheetName?: string; + columns: TableExportColumn[]; + rows: T[]; + templateBuffer?: ArrayBuffer | null; +} + +type CellStyleSnapshot = { + font?: Partial; + fill?: import('exceljs').Fill; + border?: Partial; + alignment?: Partial; +}; + +type TemplateStyleSnapshot = { + headerStyles: CellStyleSnapshot[]; + bodyStyle?: CellStyleSnapshot; + bodyAltStyle?: CellStyleSnapshot; + columnWidths: Array; +}; + +@Injectable({ providedIn: 'root' }) +export class TableExportService { + private readonly templatesApiBase = (() => { + const raw = (environment.apiUrl || '').replace(/\/+$/, ''); + const apiBase = raw.toLowerCase().endsWith('/api') ? raw : `${raw}/api`; + return `${apiBase}/templates`; + })(); + private defaultTemplateBufferPromise: Promise | null = null; + private cachedDefaultTemplateStyle?: TemplateStyleSnapshot; + + constructor(private readonly http: HttpClient) {} + + async exportAsXlsx(request: TableExportRequest): Promise { + const ExcelJS = await import('exceljs'); + const templateBuffer = request.templateBuffer ?? (await this.getDefaultTemplateBuffer()); + const templateStyle = await this.resolveTemplateStyle(ExcelJS, templateBuffer); + const workbook = new ExcelJS.Workbook(); + const sheet = workbook.addWorksheet(this.sanitizeSheetName(request.sheetName || 'Dados')); + + const rawColumns = request.columns ?? []; + const columns = rawColumns.filter((column) => !this.shouldExcludeColumnByHeader(column.header)); + const rows = request.rows ?? []; + + if (!columns.length) { + throw new Error('Nenhuma coluna exportavel apos remover ITEM/ID.'); + } + + const headerValues = columns.map((c) => c.header ?? ''); + sheet.addRow(headerValues); + + rows.forEach((row, rowIndex) => { + const values = columns.map((column) => this.normalizeValue(column.value(row, rowIndex), column.type)); + sheet.addRow(values); + }); + + this.applyHeaderStyle(sheet, columns.length, templateStyle); + this.applyBodyStyle(sheet, columns, rows.length, templateStyle); + this.applyColumnWidths(sheet, columns, rows, templateStyle); + this.applyAutoFilter(sheet, columns.length); + sheet.views = [{ state: 'frozen', ySplit: 1 }]; + + const extensionSafeName = this.ensureXlsxExtension(request.fileName); + const buffer = await workbook.xlsx.writeBuffer(); + this.downloadBuffer(buffer, extensionSafeName); + } + + buildTimestamp(date: Date = new Date()): string { + const year = date.getFullYear(); + const month = this.pad2(date.getMonth() + 1); + const day = this.pad2(date.getDate()); + const hour = this.pad2(date.getHours()); + const minute = this.pad2(date.getMinutes()); + return `${year}-${month}-${day}_${hour}-${minute}`; + } + + private applyHeaderStyle( + sheet: import('exceljs').Worksheet, + columnCount: number, + templateStyle?: TemplateStyleSnapshot, + ): void { + const headerRow = sheet.getRow(1); + headerRow.height = 24; + + for (let col = 1; col <= columnCount; col += 1) { + const cell = headerRow.getCell(col); + const templateCell = this.getTemplateStyleByIndex(templateStyle, col - 1); + cell.font = this.cloneStyle(templateCell?.font) || { bold: true, color: { argb: 'FFFFFFFF' }, name: 'Calibri', size: 11 }; + cell.fill = this.cloneStyle(templateCell?.fill) || { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FF0A58CA' }, + }; + cell.alignment = this.cloneStyle(templateCell?.alignment) || { vertical: 'middle', horizontal: 'center', wrapText: true }; + cell.border = this.cloneStyle(templateCell?.border) || this.getDefaultBorder(); + } + } + + private applyBodyStyle( + sheet: import('exceljs').Worksheet, + columns: TableExportColumn[], + rowCount: number, + templateStyle?: TemplateStyleSnapshot, + ): void { + for (let rowIndex = 2; rowIndex <= rowCount + 1; rowIndex += 1) { + const row = sheet.getRow(rowIndex); + const isEven = (rowIndex - 1) % 2 === 0; + const templateRowStyle = isEven + ? (templateStyle?.bodyAltStyle ?? templateStyle?.bodyStyle) + : (templateStyle?.bodyStyle ?? templateStyle?.bodyAltStyle); + + columns.forEach((column, columnIndex) => { + const cell = row.getCell(columnIndex + 1); + cell.font = this.cloneStyle(templateRowStyle?.font) || { name: 'Calibri', size: 11, color: { argb: 'FF1F2937' } }; + cell.border = this.cloneStyle(templateRowStyle?.border) || this.getDefaultBorder(); + cell.alignment = this.cloneStyle(templateRowStyle?.alignment) || this.getAlignment(column.type); + + if (templateRowStyle?.fill) { + const fill = this.cloneStyle(templateRowStyle.fill); + if (fill) cell.fill = fill; + } else if (isEven) { + cell.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFF7FAFF' }, + }; + } + + if (column.type === 'number') cell.numFmt = '#,##0.00'; + if (column.type === 'currency') cell.numFmt = '"R$" #,##0.00'; + if (column.type === 'date') cell.numFmt = 'dd/mm/yyyy'; + if (column.type === 'datetime') cell.numFmt = 'dd/mm/yyyy hh:mm'; + }); + } + } + + private applyColumnWidths( + sheet: import('exceljs').Worksheet, + columns: TableExportColumn[], + rows: T[], + templateStyle?: TemplateStyleSnapshot, + ): void { + columns.forEach((column, columnIndex) => { + if (column.width && column.width > 0) { + sheet.getColumn(columnIndex + 1).width = column.width; + return; + } + + const templateWidth = templateStyle?.columnWidths?.[columnIndex]; + if (templateWidth && templateWidth > 0) { + sheet.getColumn(columnIndex + 1).width = templateWidth; + return; + } + + const headerLength = (column.header ?? '').length; + let maxLength = headerLength; + + rows.forEach((row, rowIndex) => { + const value = column.value(row, rowIndex); + const printable = this.toPrintableValue(value, column.type); + if (printable.length > maxLength) maxLength = printable.length; + }); + + const target = Math.max(12, Math.min(maxLength + 3, 48)); + sheet.getColumn(columnIndex + 1).width = target; + }); + } + + private applyAutoFilter(sheet: import('exceljs').Worksheet, columnCount: number): void { + if (columnCount <= 0) return; + sheet.autoFilter = { + from: { row: 1, column: 1 }, + to: { row: 1, column: columnCount }, + }; + } + + private normalizeValue(value: unknown, type?: ExportCellType): string | number | Date | boolean | null { + if (value === null || value === undefined || value === '') return null; + + if (type === 'number' || type === 'currency') { + const numeric = this.toNumber(value); + return numeric ?? String(value); + } + + if (type === 'date' || type === 'datetime') { + const parsedDate = this.toDate(value); + return parsedDate ?? String(value); + } + + if (type === 'boolean') { + if (typeof value === 'boolean') return value; + return this.normalizeBoolean(value); + } + + return String(value); + } + + private toPrintableValue(value: unknown, type?: ExportCellType): string { + if (value === null || value === undefined || value === '') return ''; + + if (type === 'date' || type === 'datetime') { + const parsedDate = this.toDate(value); + if (!parsedDate) return String(value); + const datePart = `${this.pad2(parsedDate.getDate())}/${this.pad2(parsedDate.getMonth() + 1)}/${parsedDate.getFullYear()}`; + if (type === 'date') return datePart; + return `${datePart} ${this.pad2(parsedDate.getHours())}:${this.pad2(parsedDate.getMinutes())}`; + } + + if (type === 'number' || type === 'currency') { + const numeric = this.toNumber(value); + if (numeric === null) return String(value); + if (type === 'currency') { + return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(numeric); + } + return new Intl.NumberFormat('pt-BR').format(numeric); + } + + if (type === 'boolean') { + return this.normalizeBoolean(value) ? 'Sim' : 'Nao'; + } + + return String(value); + } + + private toNumber(value: unknown): number | null { + if (typeof value === 'number') { + return Number.isFinite(value) ? value : null; + } + + if (typeof value === 'string') { + const trimmed = value.trim(); + if (!trimmed) return null; + const normalized = trimmed + .replace(/[^\d,.-]/g, '') + .replace(/\.(?=\d{3}(\D|$))/g, '') + .replace(',', '.'); + const parsed = Number(normalized); + return Number.isFinite(parsed) ? parsed : null; + } + + return null; + } + + private toDate(value: unknown): Date | null { + if (value instanceof Date) { + return Number.isNaN(value.getTime()) ? null : value; + } + + if (typeof value === 'string') { + const trimmed = value.trim(); + if (!trimmed) return null; + + const brDate = trimmed.match(/^(\d{2})\/(\d{2})\/(\d{4})(?:\s+(\d{2}):(\d{2}))?$/); + if (brDate) { + const day = Number(brDate[1]); + const month = Number(brDate[2]) - 1; + const year = Number(brDate[3]); + const hour = Number(brDate[4] ?? '0'); + const minute = Number(brDate[5] ?? '0'); + const parsedBr = new Date(year, month, day, hour, minute); + return Number.isNaN(parsedBr.getTime()) ? null : parsedBr; + } + + const parsed = new Date(trimmed); + return Number.isNaN(parsed.getTime()) ? null : parsed; + } + + return null; + } + + private normalizeBoolean(value: unknown): boolean { + if (typeof value === 'boolean') return value; + const normalized = String(value ?? '') + .trim() + .toLowerCase(); + return normalized === 'true' || normalized === '1' || normalized === 'sim' || normalized === 'yes'; + } + + private ensureXlsxExtension(fileName: string): string { + const safe = (fileName ?? 'export').trim() || 'export'; + return safe.toLowerCase().endsWith('.xlsx') ? safe : `${safe}.xlsx`; + } + + private sanitizeSheetName(name: string): string { + const safe = (name ?? 'Dados').replace(/[\\/*?:[\]]/g, '').trim(); + return (safe || 'Dados').slice(0, 31); + } + + private shouldExcludeColumnByHeader(header: string | undefined): boolean { + const normalized = this.normalizeHeader(header); + if (!normalized) return false; + + const tokens = normalized.split(/[^a-z0-9]+/).filter(Boolean); + if (!tokens.length) return false; + + return tokens.includes('id') || tokens.includes('item'); + } + + private normalizeHeader(value: string | undefined): string { + return (value ?? '') + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .toLowerCase() + .trim(); + } + + private downloadBuffer(buffer: ArrayBuffer, fileName: string): void { + const blob = new Blob([buffer], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }); + const url = URL.createObjectURL(blob); + const anchor = document.createElement('a'); + anchor.href = url; + anchor.download = fileName; + document.body.appendChild(anchor); + anchor.click(); + document.body.removeChild(anchor); + URL.revokeObjectURL(url); + } + + private getAlignment(type?: ExportCellType): Partial { + if (type === 'number' || type === 'currency') { + return { vertical: 'middle', horizontal: 'right' }; + } + if (type === 'boolean') { + return { vertical: 'middle', horizontal: 'center' }; + } + return { vertical: 'middle', horizontal: 'left', wrapText: true }; + } + + private getDefaultBorder(): Partial { + return { + top: { style: 'thin', color: { argb: 'FFD6DCE8' } }, + left: { style: 'thin', color: { argb: 'FFD6DCE8' } }, + right: { style: 'thin', color: { argb: 'FFD6DCE8' } }, + bottom: { style: 'thin', color: { argb: 'FFD6DCE8' } }, + }; + } + + private pad2(value: number): string { + return value.toString().padStart(2, '0'); + } + + private async extractTemplateStyle( + excelJsModule: typeof import('exceljs'), + templateBuffer: ArrayBuffer | null, + ): Promise { + if (!templateBuffer) return undefined; + + try { + const workbook = new excelJsModule.Workbook(); + await workbook.xlsx.load(templateBuffer); + const sheet = workbook.getWorksheet(1); + if (!sheet) return undefined; + + const headerRow = sheet.getRow(1); + const headerCount = Math.max(headerRow.actualCellCount, 1); + const headerStyles: CellStyleSnapshot[] = []; + for (let col = 1; col <= headerCount; col += 1) { + headerStyles.push(this.captureCellStyle(headerRow.getCell(col))); + } + + const bodyStyle = this.captureFirstStyledCellRow(sheet.getRow(2)); + const bodyAltStyle = this.captureFirstStyledCellRow(sheet.getRow(3)); + const columnWidths = (sheet.columns ?? []).map((column) => column.width); + + return { headerStyles, bodyStyle, bodyAltStyle, columnWidths }; + } catch { + return undefined; + } + } + + private async resolveTemplateStyle( + excelJsModule: typeof import('exceljs'), + templateBuffer: ArrayBuffer | null, + ): Promise { + if (templateBuffer) { + const style = await this.extractTemplateStyle(excelJsModule, templateBuffer); + if (style) this.cachedDefaultTemplateStyle = style; + return style; + } + + return this.cachedDefaultTemplateStyle; + } + + private async getDefaultTemplateBuffer(): Promise { + if (this.defaultTemplateBufferPromise) { + return this.defaultTemplateBufferPromise; + } + + this.defaultTemplateBufferPromise = this.fetchDefaultTemplateBuffer(); + const buffer = await this.defaultTemplateBufferPromise; + if (!buffer) this.defaultTemplateBufferPromise = null; + return buffer; + } + + private async fetchDefaultTemplateBuffer(): Promise { + try { + const params = new HttpParams().set('_', `${Date.now()}`); + const blob = await firstValueFrom( + this.http.get(`${this.templatesApiBase}/planilha-geral`, { + params, + responseType: 'blob', + }) + ); + return await blob.arrayBuffer(); + } catch { + return null; + } + } + + private captureFirstStyledCellRow(row: import('exceljs').Row): CellStyleSnapshot | undefined { + if (!row) return undefined; + const cellCount = Math.max(row.actualCellCount, 1); + for (let col = 1; col <= cellCount; col += 1) { + const captured = this.captureCellStyle(row.getCell(col)); + if (captured.font || captured.fill || captured.border || captured.alignment) { + return captured; + } + } + return undefined; + } + + private captureCellStyle(cell: import('exceljs').Cell): CellStyleSnapshot { + return { + font: this.cloneStyle(cell.font), + fill: this.cloneStyle(cell.fill), + border: this.cloneStyle(cell.border), + alignment: this.cloneStyle(cell.alignment), + }; + } + + private getTemplateStyleByIndex(style: TemplateStyleSnapshot | undefined, index: number): CellStyleSnapshot | undefined { + if (!style || !style.headerStyles.length) return undefined; + return style.headerStyles[index] ?? style.headerStyles[style.headerStyles.length - 1]; + } + + private cloneStyle(value: T | undefined): T | undefined { + if (!value) return undefined; + try { + return JSON.parse(JSON.stringify(value)) as T; + } catch { + return value; + } + } +} From 4b7c74195e5d11adf754b36598311348efcfc95c Mon Sep 17 00:00:00 2001 From: Eduardo Lopes Date: Fri, 6 Mar 2026 13:09:34 -0300 Subject: [PATCH 2/2] =?UTF-8?q?Feat:=20Novas=20Implementa=C3=A7=C3=B5es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 1064 ++++++++--------- src/app/app.routes.ts | 7 +- src/app/app.ts | 1 + src/app/components/header/header.html | 7 +- src/app/components/header/header.ts | 18 +- .../guards/sysadmin-or-financeiro.guard.ts | 27 + src/app/guards/sysadmin-or-gestor.guard.ts | 5 +- src/app/pages/dashboard/dashboard.ts | 3 +- src/app/pages/geral/geral.html | 10 +- src/app/pages/geral/geral.ts | 28 +- .../historico-linhas/historico-linhas.html | 278 +++++ .../historico-linhas/historico-linhas.scss | 648 ++++++++++ .../historico-linhas/historico-linhas.ts | 598 +++++++++ src/app/pages/mureg/mureg.html | 10 +- src/app/pages/mureg/mureg.ts | 55 +- .../parcelamentos-table.html | 3 +- .../parcelamentos-table.ts | 3 +- .../pages/parcelamentos/parcelamentos.html | 5 +- src/app/pages/parcelamentos/parcelamentos.ts | 28 + src/app/pages/troca-numero/troca-numero.html | 4 +- src/app/pages/troca-numero/troca-numero.ts | 33 + src/app/services/historico.service.ts | 28 + src/app/services/users.service.ts | 2 +- 23 files changed, 2258 insertions(+), 607 deletions(-) create mode 100644 src/app/guards/sysadmin-or-financeiro.guard.ts create mode 100644 src/app/pages/historico-linhas/historico-linhas.html create mode 100644 src/app/pages/historico-linhas/historico-linhas.scss create mode 100644 src/app/pages/historico-linhas/historico-linhas.ts diff --git a/package-lock.json b/package-lock.json index d41a1fd..c495cf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -610,13 +610,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -625,9 +625,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -640,7 +640,6 @@ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -684,14 +683,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -714,13 +713,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -751,29 +750,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -826,27 +825,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -856,33 +855,33 @@ } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -890,9 +889,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { @@ -1396,10 +1395,23 @@ "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", "license": "MIT" }, + "node_modules/@gar/promise-retry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.2.tgz", + "integrity": "sha512-Lm/ZLhDZcBECta3TmCQSngiQykFdfw+QtI1/GYMsZd4l3nG+P8WLB16XuS7WaBGLQ+9E+cOcWQsth9cayuGt8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "retry": "^0.13.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/@hono/node-server": { - "version": "1.19.9", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", - "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", + "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", "dev": true, "license": "MIT", "engines": { @@ -1760,29 +1772,6 @@ } } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", - "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -2432,9 +2421,9 @@ } }, "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "11.2.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", - "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -2455,18 +2444,18 @@ } }, "node_modules/@npmcli/git": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.1.tgz", - "integrity": "sha512-+XTFxK2jJF/EJJ5SoAzXk3qwIDfvFc5/g+bD274LZ7uY7LE8sTfG6Z8rOanPl2ZEvZWqNvmEdtXC25cE54VcoA==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz", + "integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==", "dev": true, "license": "ISC", "dependencies": { + "@gar/promise-retry": "^1.0.0", "@npmcli/promise-spawn": "^9.0.0", "ini": "^6.0.0", "lru-cache": "^11.2.1", "npm-pick-manifest": "^11.0.1", "proc-log": "^6.0.0", - "promise-retry": "^2.0.1", "semver": "^7.3.5", "which": "^6.0.0" }, @@ -2485,19 +2474,19 @@ } }, "node_modules/@npmcli/git/node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "11.2.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", - "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -2515,13 +2504,13 @@ } }, "node_modules/@npmcli/git/node_modules/which": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", - "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" + "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" @@ -2558,9 +2547,9 @@ } }, "node_modules/@npmcli/package-json": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.4.tgz", - "integrity": "sha512-0wInJG3j/K40OJt/33ax47WfWMzZTm6OQxB9cDhTt5huCP2a9g2GnlsxmfN+PulItNPIpPrZ+kfwwUil7eHcZQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.5.tgz", + "integrity": "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==", "dev": true, "license": "ISC", "dependencies": { @@ -2570,41 +2559,64 @@ "json-parse-even-better-errors": "^5.0.0", "proc-log": "^6.0.0", "semver": "^7.5.3", - "validate-npm-package-license": "^3.0.4" + "spdx-expression-parse": "^4.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/@npmcli/package-json/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@npmcli/package-json/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/@npmcli/package-json/node_modules/glob": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.1.tgz", - "integrity": "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "minimatch": "^10.1.2", - "minipass": "^7.1.2", - "path-scurry": "^2.0.0" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@npmcli/package-json/node_modules/minimatch": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", - "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2634,23 +2646,23 @@ } }, "node_modules/@npmcli/promise-spawn/node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", - "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" + "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" @@ -2670,9 +2682,9 @@ } }, "node_modules/@npmcli/run-script": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.3.tgz", - "integrity": "sha512-ER2N6itRkzWbbtVmZ9WKaWxVlKlOeBFF1/7xx+KA5J1xKa4JjUwBdb6tDpk0v1qA+d+VDwHI9qmLcXSWcmi+Rw==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.4.tgz", + "integrity": "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==", "dev": true, "license": "ISC", "dependencies": { @@ -2680,23 +2692,12 @@ "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "node-gyp": "^12.1.0", - "proc-log": "^6.0.0", - "which": "^6.0.0" + "proc-log": "^6.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/run-script/node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/@npmcli/run-script/node_modules/proc-log": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", @@ -2707,35 +2708,19 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/run-script/node_modules/which": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", - "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/@parcel/watcher": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", - "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, "dependencies": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.3", "is-glob": "^4.0.3", - "micromatch": "^4.0.5", - "node-addon-api": "^7.0.0" + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">= 10.0.0" @@ -2745,25 +2730,25 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.1", - "@parcel/watcher-darwin-arm64": "2.5.1", - "@parcel/watcher-darwin-x64": "2.5.1", - "@parcel/watcher-freebsd-x64": "2.5.1", - "@parcel/watcher-linux-arm-glibc": "2.5.1", - "@parcel/watcher-linux-arm-musl": "2.5.1", - "@parcel/watcher-linux-arm64-glibc": "2.5.1", - "@parcel/watcher-linux-arm64-musl": "2.5.1", - "@parcel/watcher-linux-x64-glibc": "2.5.1", - "@parcel/watcher-linux-x64-musl": "2.5.1", - "@parcel/watcher-win32-arm64": "2.5.1", - "@parcel/watcher-win32-ia32": "2.5.1", - "@parcel/watcher-win32-x64": "2.5.1" + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" } }, "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", - "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", "cpu": [ "arm64" ], @@ -2782,9 +2767,9 @@ } }, "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", - "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", "cpu": [ "arm64" ], @@ -2803,9 +2788,9 @@ } }, "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", - "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", "cpu": [ "x64" ], @@ -2824,9 +2809,9 @@ } }, "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", - "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", "cpu": [ "x64" ], @@ -2845,9 +2830,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", - "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", "cpu": [ "arm" ], @@ -2866,9 +2851,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", - "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", "cpu": [ "arm" ], @@ -2887,9 +2872,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", - "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", "cpu": [ "arm64" ], @@ -2908,9 +2893,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", - "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", "cpu": [ "arm64" ], @@ -2929,9 +2914,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", - "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", "cpu": [ "x64" ], @@ -2950,9 +2935,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", - "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", "cpu": [ "x64" ], @@ -2971,9 +2956,9 @@ } }, "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", - "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", "cpu": [ "arm64" ], @@ -2992,9 +2977,9 @@ } }, "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", - "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", "cpu": [ "ia32" ], @@ -3013,9 +2998,9 @@ } }, "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", - "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", "cpu": [ "x64" ], @@ -3033,20 +3018,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/@parcel/watcher/node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/@parcel/watcher/node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -3512,17 +3483,40 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/@tufjs/models/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@tufjs/models/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", - "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3556,16 +3550,16 @@ "license": "MIT" }, "node_modules/@types/jasmine": { - "version": "5.1.13", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.13.tgz", - "integrity": "sha512-MYCcDkruFc92LeYZux5BC0dmqo2jk+M5UIZ4/oFnAPCXN9mCcQhLyj7F3/Za7rocVyt5YRr1MmqJqFlvQ9LVcg==", + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.15.tgz", + "integrity": "sha512-ZAC8KjmV2MJxbNTrwXFN+HKeajpXQZp6KpPiR6Aa4XvaEnjP6qh23lL/Rqb7AYzlp3h/rcwDrQ7Gg7q28cQTQg==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", - "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", "dev": true, "license": "MIT", "peer": true, @@ -3689,9 +3683,9 @@ } }, "node_modules/ansi-escapes": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", - "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "dev": true, "license": "MIT", "dependencies": { @@ -3954,9 +3948,9 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", - "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "dev": true, "license": "MIT", "dependencies": { @@ -3966,7 +3960,7 @@ "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.14.0", + "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" }, @@ -4044,9 +4038,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -4065,11 +4059,11 @@ "license": "MIT", "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", - "update-browserslist-db": "^1.1.4" + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -4168,28 +4162,51 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/cacache/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/cacache/node_modules/glob": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.1.tgz", - "integrity": "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "minimatch": "^10.1.2", - "minipass": "^7.1.2", - "path-scurry": "^2.0.0" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/cacache/node_modules/lru-cache": { - "version": "11.2.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", - "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -4197,16 +4214,16 @@ } }, "node_modules/cacache/node_modules/minimatch": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", - "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4244,9 +4261,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001757", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", - "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", + "version": "1.0.30001777", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", + "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", "dev": true, "funding": [ { @@ -4614,9 +4631,9 @@ "license": "MIT" }, "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "dev": true, "license": "MIT", "dependencies": { @@ -4625,6 +4642,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/crc-32": { @@ -4911,9 +4932,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.262", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", - "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==", + "version": "1.5.307", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", + "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", "dev": true, "license": "ISC" }, @@ -4934,31 +4955,6 @@ "node": ">= 0.8" } }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -4969,9 +4965,9 @@ } }, "node_modules/engine.io": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", - "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz", + "integrity": "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==", "dev": true, "license": "MIT", "dependencies": { @@ -4981,9 +4977,9 @@ "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", - "debug": "~4.3.1", + "debug": "~4.4.1", "engine.io-parser": "~5.2.1", - "ws": "~8.17.1" + "ws": "~8.18.3" }, "engines": { "node": ">=10.2.0" @@ -5013,24 +5009,6 @@ "node": ">= 0.6" } }, - "node_modules/engine.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/engine.io/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -5328,13 +5306,13 @@ } }, "node_modules/express-rate-limit": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", - "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.0.tgz", + "integrity": "sha512-KJzBawY6fB9FiZGdE/0aftepZ91YlaGIrV8vgblRM3J8X+dHx/aiowJWwkx6LIGyuqGiANsjSwwrbb8mifOJ4Q==", "dev": true, "license": "MIT", "dependencies": { - "ip-address": "10.0.1" + "ip-address": "10.1.0" }, "engines": { "node": ">= 16" @@ -5422,9 +5400,9 @@ } }, "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "dev": true, "license": "MIT", "dependencies": { @@ -5436,13 +5414,17 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", + "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", "dev": true, "license": "ISC" }, @@ -5602,9 +5584,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", "dev": true, "license": "MIT", "engines": { @@ -5657,7 +5639,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -5766,9 +5748,9 @@ } }, "node_modules/hono": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", - "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz", + "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==", "dev": true, "license": "MIT", "peer": true, @@ -5790,9 +5772,9 @@ } }, "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -5807,9 +5789,9 @@ "license": "MIT" }, "node_modules/htmlparser2": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", - "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", @@ -5822,14 +5804,14 @@ "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.2.1", - "entities": "^6.0.0" + "domutils": "^3.2.2", + "entities": "^7.0.1" } }, "node_modules/htmlparser2/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -5911,9 +5893,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "dev": true, "license": "MIT", "dependencies": { @@ -5960,17 +5942,40 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/ignore-walk/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/ignore-walk/node_modules/minimatch": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", - "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5983,9 +5988,9 @@ "license": "MIT" }, "node_modules/immutable": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", - "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", "dev": true, "license": "MIT" }, @@ -6027,9 +6032,9 @@ } }, "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "dev": true, "license": "MIT", "engines": { @@ -6289,9 +6294,9 @@ "peer": true }, "node_modules/jose": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", - "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.0.tgz", + "integrity": "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ==", "dev": true, "license": "MIT", "funding": { @@ -6939,7 +6944,6 @@ "integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", @@ -6953,9 +6957,9 @@ } }, "node_modules/listr2/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "dev": true, "license": "MIT" }, @@ -7246,12 +7250,13 @@ } }, "node_modules/make-fetch-happen": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.3.tgz", - "integrity": "sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==", + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.4.tgz", + "integrity": "sha512-vM2sG+wbVeVGYcCm16mM3d5fuem9oC28n436HjsGO3LcxoTI8LNVa4rwZDn3f76+cWyT4GGJDxjTYU1I2nr6zw==", "dev": true, "license": "ISC", "dependencies": { + "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", @@ -7261,7 +7266,6 @@ "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", - "promise-retry": "^2.0.1", "ssri": "^13.0.0" }, "engines": { @@ -7311,35 +7315,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", @@ -7394,9 +7369,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -7415,11 +7390,11 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -7438,9 +7413,9 @@ } }, "node_modules/minipass-fetch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.1.tgz", - "integrity": "sha512-yHK8pb0iCGat0lDrs/D6RZmCdaBT64tULXjdxjSMAqoDi18Q3qKEUTHypHQZQd9+FYpIS+lkvpq6C/R6SbUeRw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", + "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7452,7 +7427,7 @@ "node": "^20.17.0 || >=22.9.0" }, "optionalDependencies": { - "encoding": "^0.1.13" + "iconv-lite": "^0.7.2" } }, "node_modules/minipass-flush": { @@ -7577,9 +7552,9 @@ "license": "MIT" }, "node_modules/msgpackr": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", - "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.8.tgz", + "integrity": "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA==", "dev": true, "license": "MIT", "optional": true, @@ -7699,13 +7674,13 @@ } }, "node_modules/node-gyp/node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/node-gyp/node_modules/proc-log": { @@ -7719,13 +7694,13 @@ } }, "node_modules/node-gyp/node_modules/which": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", - "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" + "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" @@ -7735,9 +7710,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", "dev": true, "license": "MIT" }, @@ -7819,9 +7794,9 @@ } }, "node_modules/npm-packlist": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.3.tgz", - "integrity": "sha512-zPukTwJMOu5X5uvm0fztwS5Zxyvmk38H/LfidkOMt3gbZVCyro2cD/ETzwzVPcWZA3JOyPznfUN/nkyFiyUbxg==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.4.tgz", + "integrity": "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==", "dev": true, "license": "ISC", "dependencies": { @@ -7987,9 +7962,9 @@ } }, "node_modules/ordered-binary": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.0.tgz", - "integrity": "sha512-IQh2aMfMIDbPjI/8a3Edr+PiOpcsB7yo8NdW7aHWVaoR/pcDldunMvnnwbk/auPGqmKeAdxtZl7MHX/QmPwhvQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.1.tgz", + "integrity": "sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w==", "dev": true, "license": "MIT", "optional": true @@ -8159,9 +8134,9 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", - "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -8169,16 +8144,16 @@ "minipass": "^7.1.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", - "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -8240,9 +8215,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "dev": true, "funding": [ { @@ -8305,6 +8280,16 @@ "node": ">=10" } }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -8337,9 +8322,9 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -8509,9 +8494,9 @@ } }, "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "dev": true, "license": "MIT", "engines": { @@ -8704,32 +8689,36 @@ } }, "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.3.5", + "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "statuses": "^2.0.2" }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "dev": true, "license": "MIT", "dependencies": { @@ -8740,6 +8729,10 @@ }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setimmediate": { @@ -8914,16 +8907,16 @@ } }, "node_modules/socket.io": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", - "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", - "debug": "~4.3.2", + "debug": "~4.4.1", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" @@ -8933,66 +8926,30 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", - "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz", + "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==", "dev": true, "license": "MIT", "dependencies": { - "debug": "~4.3.4", - "ws": "~8.17.1" - } - }, - "node_modules/socket.io-adapter/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "debug": "~4.4.1", + "ws": "~8.18.3" } }, "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", + "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", "dev": true, "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" + "debug": "~4.4.1" }, "engines": { "node": ">=10.0.0" } }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/socket.io/node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -9007,24 +8964,6 @@ "node": ">= 0.6" } }, - "node_modules/socket.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/socket.io/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -9129,17 +9068,6 @@ "node": ">=0.10.0" } }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, "node_modules/spdx-exceptions": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", @@ -9148,9 +9076,9 @@ "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9159,16 +9087,16 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.22", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", - "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", "dev": true, "license": "CC0-1.0" }, "node_modules/ssri": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.0.tgz", - "integrity": "sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", + "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", "dev": true, "license": "ISC", "dependencies": { @@ -9244,13 +9172,13 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -9286,9 +9214,9 @@ } }, "node_modules/tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.10.tgz", + "integrity": "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -9567,9 +9495,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -9622,17 +9550,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, "node_modules/validate-npm-package-name": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", @@ -9659,7 +9576,6 @@ "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -9887,9 +9803,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "license": "MIT", "engines": { diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 3c39d3d..7a30473 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -9,6 +9,7 @@ import { Faturamento } from './pages/faturamento/faturamento'; import { authGuard } from './guards/auth.guard'; import { sysadminOrGestorGuard } from './guards/sysadmin-or-gestor.guard'; +import { sysadminOrFinanceiroGuard } from './guards/sysadmin-or-financeiro.guard'; import { sysadminOnlyGuard } from './guards/sysadmin-only.guard'; import { DadosUsuarios } from './pages/dados-usuarios/dados-usuarios'; import { VigenciaComponent } from './pages/vigencia/vigencia'; @@ -19,6 +20,7 @@ import { ChipsControleRecebidos } from './pages/chips-controle-recebidos/chips-c import { Resumo } from './pages/resumo/resumo'; import { Parcelamentos } from './pages/parcelamentos/parcelamentos'; import { Historico } from './pages/historico/historico'; +import { HistoricoLinhas } from './pages/historico-linhas/historico-linhas'; import { Perfil } from './pages/perfil/perfil'; import { SystemProvisionUserPage } from './pages/system-provision-user/system-provision-user'; @@ -29,15 +31,16 @@ export const routes: Routes = [ { path: 'geral', component: Geral, canActivate: [authGuard], title: 'Geral' }, { path: 'mureg', component: Mureg, canActivate: [authGuard], title: 'Mureg' }, - { path: 'faturamento', component: Faturamento, canActivate: [authGuard, sysadminOrGestorGuard], title: 'Faturamento' }, + { path: 'faturamento', component: Faturamento, canActivate: [authGuard, sysadminOrFinanceiroGuard], title: 'Faturamento' }, { path: 'dadosusuarios', component: DadosUsuarios, canActivate: [authGuard], title: 'Dados dos Usuários' }, { path: 'vigencia', component: VigenciaComponent, canActivate: [authGuard], title: 'Vigência' }, { path: 'trocanumero', component: TrocaNumero, canActivate: [authGuard], title: 'Troca de Número' }, { path: 'notificacoes', component: Notificacoes, canActivate: [authGuard], title: 'Notificações' }, { path: 'chips-controle-recebidos', component: ChipsControleRecebidos, canActivate: [authGuard, sysadminOrGestorGuard], title: 'Chips Controle Recebidos' }, { path: 'resumo', component: Resumo, canActivate: [authGuard], title: 'Resumo' }, - { path: 'parcelamentos', component: Parcelamentos, canActivate: [authGuard, sysadminOrGestorGuard], title: 'Parcelamentos' }, + { path: 'parcelamentos', component: Parcelamentos, canActivate: [authGuard, sysadminOrFinanceiroGuard], title: 'Parcelamentos' }, { path: 'historico', component: Historico, canActivate: [authGuard, sysadminOrGestorGuard], title: 'Histórico' }, + { path: 'historico-linhas', component: HistoricoLinhas, canActivate: [authGuard, sysadminOrGestorGuard], title: 'Histórico de Linhas' }, { path: 'perfil', component: Perfil, canActivate: [authGuard], title: 'Perfil' }, { path: 'system/fornecer-usuario', diff --git a/src/app/app.ts b/src/app/app.ts index 11b8f61..3534422 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -40,6 +40,7 @@ export class AppComponent { '/resumo', '/parcelamentos', '/historico', + '/historico-linhas', '/perfil', '/system', ]; diff --git a/src/app/components/header/header.html b/src/app/components/header/header.html index cc76c2f..44206a8 100644 --- a/src/app/components/header/header.html +++ b/src/app/components/header/header.html @@ -539,15 +539,18 @@ Mureg - + Faturamento - + Parcelamentos Histórico + + Histórico de Linhas + Dados PF/PJ diff --git a/src/app/components/header/header.ts b/src/app/components/header/header.ts index 0d78ae7..b0608bf 100644 --- a/src/app/components/header/header.ts +++ b/src/app/components/header/header.ts @@ -34,7 +34,10 @@ export class Header implements AfterViewInit, OnDestroy { isLoggedHeader = false; isHome = false; isSysAdmin = false; + isGestor = false; + isFinanceiro = false; canViewAll = false; + canViewFinancialPages = false; clientTenantDisplayName = ''; private clientTenantNameTenantId: string | null = null; private readonly baseApi: string; @@ -60,7 +63,7 @@ export class Header implements AfterViewInit, OnDestroy { readonly permissionOptions = [ { value: 'sysadmin', label: 'SysAdmin' }, { value: 'gestor', label: 'Gestor' }, - { value: 'cliente', label: 'Cliente' }, + { value: 'financeiro', label: 'Financeiro' }, ]; manageUsersLoading = false; @@ -93,6 +96,7 @@ export class Header implements AfterViewInit, OnDestroy { '/resumo', '/parcelamentos', '/historico', + '/historico-linhas', '/perfil', '/system', ]; @@ -213,15 +217,22 @@ export class Header implements AfterViewInit, OnDestroy { private syncPermissions() { if (!isPlatformBrowser(this.platformId)) { this.isSysAdmin = false; + this.isGestor = false; + this.isFinanceiro = false; this.canViewAll = false; + this.canViewFinancialPages = false; this.clientTenantDisplayName = ''; this.clientTenantNameTenantId = null; return; } const isSysAdmin = this.authService.hasRole('sysadmin'); const isGestor = this.authService.hasRole('gestor'); + const isFinanceiro = this.authService.hasRole('financeiro'); this.isSysAdmin = isSysAdmin; - this.canViewAll = isSysAdmin || isGestor; + this.isGestor = isGestor; + this.isFinanceiro = isFinanceiro; + this.canViewAll = isSysAdmin || isGestor || isFinanceiro; + this.canViewFinancialPages = isSysAdmin || isFinanceiro; if (!this.isClientHeader) { this.clientTenantDisplayName = ''; @@ -497,7 +508,10 @@ export class Header implements AfterViewInit, OnDestroy { this.optionsOpen = false; this.notificationsOpen = false; this.isSysAdmin = false; + this.isGestor = false; + this.isFinanceiro = false; this.canViewAll = false; + this.canViewFinancialPages = false; this.router.navigate(['/']); } diff --git a/src/app/guards/sysadmin-or-financeiro.guard.ts b/src/app/guards/sysadmin-or-financeiro.guard.ts new file mode 100644 index 0000000..8ce5e8b --- /dev/null +++ b/src/app/guards/sysadmin-or-financeiro.guard.ts @@ -0,0 +1,27 @@ +import { inject, PLATFORM_ID } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; +import { isPlatformBrowser } from '@angular/common'; +import { AuthService } from '../services/auth.service'; + +export const sysadminOrFinanceiroGuard: CanActivateFn = () => { + const router = inject(Router); + const platformId = inject(PLATFORM_ID); + const authService = inject(AuthService); + + if (!isPlatformBrowser(platformId)) { + // Em SSR não há storage do usuário para validar sessão/perfil. + return true; + } + + const token = authService.token; + if (!token) { + return router.parseUrl('/login'); + } + + const hasAccess = authService.hasRole('sysadmin') || authService.hasRole('financeiro'); + if (!hasAccess) { + return router.parseUrl('/dashboard'); + } + + return true; +}; diff --git a/src/app/guards/sysadmin-or-gestor.guard.ts b/src/app/guards/sysadmin-or-gestor.guard.ts index 252bae1..61b19a5 100644 --- a/src/app/guards/sysadmin-or-gestor.guard.ts +++ b/src/app/guards/sysadmin-or-gestor.guard.ts @@ -18,7 +18,10 @@ export const sysadminOrGestorGuard: CanActivateFn = () => { return router.parseUrl('/login'); } - const hasAccess = authService.hasRole('sysadmin') || authService.hasRole('gestor'); + const hasAccess = + authService.hasRole('sysadmin') || + authService.hasRole('gestor') || + authService.hasRole('financeiro'); if (!hasAccess) { return router.parseUrl('/dashboard'); } diff --git a/src/app/pages/dashboard/dashboard.ts b/src/app/pages/dashboard/dashboard.ts index d734fed..943d530 100644 --- a/src/app/pages/dashboard/dashboard.ts +++ b/src/app/pages/dashboard/dashboard.ts @@ -370,7 +370,8 @@ export class Dashboard implements OnInit, AfterViewInit, OnDestroy { const isSysAdmin = this.authService.hasRole('sysadmin'); const isGestor = this.authService.hasRole('gestor'); - this.isCliente = !(isSysAdmin || isGestor); + const isFinanceiro = this.authService.hasRole('financeiro'); + this.isCliente = !(isSysAdmin || isGestor || isFinanceiro); if (this.isCliente) { this.loadClientDashboardData(); diff --git a/src/app/pages/geral/geral.html b/src/app/pages/geral/geral.html index f34ddaf..4953a8f 100644 --- a/src/app/pages/geral/geral.html +++ b/src/app/pages/geral/geral.html @@ -67,7 +67,7 @@
-
+
Selecionadas: {{ batchStatusSelectionCount }} @@ -387,7 +387,7 @@ Enviar p/ Reserva ({{ reservaSelectedCount }}) -
@@ -452,7 +452,7 @@
- + @@ -571,7 +571,7 @@
- + diff --git a/src/app/pages/geral/geral.ts b/src/app/pages/geral/geral.ts index b10e06c..3c6c305 100644 --- a/src/app/pages/geral/geral.ts +++ b/src/app/pages/geral/geral.ts @@ -341,6 +341,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { exporting = false; isSysAdmin = false; isGestor = false; + isFinanceiro = false; isClientRestricted = false; rows: LineRow[] = []; @@ -644,7 +645,11 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { } get hasGroupLineSelectionTools(): boolean { - return !this.isClientRestricted && !!(this.expandedGroup ?? '').trim(); + return this.canManageLines && !!(this.expandedGroup ?? '').trim(); + } + + get canManageLines(): boolean { + return this.isSysAdmin || this.isGestor; } get canMoveSelectedLinesToReserva(): boolean { @@ -660,7 +665,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { } get canOpenBatchStatusModal(): boolean { - if (this.isClientRestricted) return false; + if (!this.canManageLines) return false; if (this.loading || this.batchStatusSaving) return false; return this.batchStatusSelectionCount > 0; } @@ -810,7 +815,8 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { if (!isPlatformBrowser(this.platformId)) return; this.isSysAdmin = this.authService.hasRole('sysadmin'); this.isGestor = this.authService.hasRole('gestor'); - this.isClientRestricted = !(this.isSysAdmin || this.isGestor); + this.isFinanceiro = this.authService.hasRole('financeiro'); + this.isClientRestricted = !(this.isSysAdmin || this.isGestor || this.isFinanceiro); if (this.isClientRestricted) { this.filterSkil = 'ALL'; @@ -2176,6 +2182,11 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { } async onEditar(r: LineRow) { + if (this.isFinanceiro) { + await this.showToast('Perfil Financeiro possui acesso somente leitura.'); + return; + } + this.editOpen = true; this.editSaving = false; this.editModel = null; @@ -2255,6 +2266,11 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { } async saveEdit() { + if (this.isFinanceiro) { + await this.showToast('Perfil Financeiro possui acesso somente leitura.'); + return; + } + if (!this.editingId || !this.editModel) return; this.editSaving = true; @@ -2485,7 +2501,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { } async onCadastrarLinha() { - if (this.isClientRestricted) { + if (!this.canManageLines) { await this.showToast('Você não tem permissão para cadastrar novos clientes.'); return; } @@ -2498,7 +2514,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { } async onAddLineToGroup(clientName: string) { - if (this.isClientRestricted) { + if (!this.canManageLines) { await this.showToast('Você não tem permissão para adicionar linhas.'); return; } @@ -3481,7 +3497,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { } async openBatchStatusModal(action: BatchStatusAction) { - if (this.isClientRestricted) { + if (!this.canManageLines) { await this.showToast('Você não tem permissão para bloquear/desbloquear em lote.'); return; } diff --git a/src/app/pages/historico-linhas/historico-linhas.html b/src/app/pages/historico-linhas/historico-linhas.html new file mode 100644 index 0000000..eb67ad6 --- /dev/null +++ b/src/app/pages/historico-linhas/historico-linhas.html @@ -0,0 +1,278 @@ +
+ +
+ +
+ + + + + +
+
+
+
+
+ Linha +
+ +
+
Histórico de Linhas
+ Timeline completa das alterações feitas em uma linha específica. +
+ +
+ + +
+
+ +
+
+
+ + Filtros +
+
+ + +
+
+ +
+
+ + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ +
+
+ Eventos (filtro) + {{ total }} +
+
+ Status (página) + {{ statusCountInPage }} +
+
+ Trocas de Número (página) + {{ trocaCountInPage }} +
+
+ Mureg (página) + {{ muregCountInPage }} +
+
+
+ +
+
+
+ Informe a linha no filtro para carregar o histórico detalhado. +
+ +
+ +
+ + + +
+ Nenhuma alteração encontrada para a linha informada. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Data/HoraUsuárioOrigemAçãoResumo da alteraçãoDetalhes
{{ formatDateTime(log.occurredAtUtc) }} +
+ {{ displayUserName(log) }} + {{ log.userEmail || '-' }} +
+
+ {{ log.page || '-' }} + + {{ formatAction(log.action) }} + + +
{{ summary.title }}
+
{{ summary.description }}
+
+ {{ formatChangeValue(summary.before) }} + + {{ formatChangeValue(summary.after) }} +
+
+ DDD: {{ formatChangeValue(summary.beforeDdd) }} {{ formatChangeValue(summary.afterDdd) }} +
+
+
+ +
+
+
+
+ Mudanças de campos +
+ +
+
+
+ {{ change.field }} + + {{ changeTypeLabel(change.changeType) }} + +
+
+ {{ formatChangeValue(change.oldValue) }} + + {{ formatChangeValue(change.newValue) }} +
+
+
+
+ +
Sem mudanças detalhadas nesse evento.
+
+
+
+
+
+
+ + +
+
+
diff --git a/src/app/pages/historico-linhas/historico-linhas.scss b/src/app/pages/historico-linhas/historico-linhas.scss new file mode 100644 index 0000000..1fe3352 --- /dev/null +++ b/src/app/pages/historico-linhas/historico-linhas.scss @@ -0,0 +1,648 @@ +:host { + --brand: #e33dcf; + --brand-soft: rgba(227, 61, 207, 0.12); + --blue: #030faa; + --text: #111214; + --muted: rgba(17, 18, 20, 0.64); + --surface: rgba(255, 255, 255, 0.9); + --surface-strong: #ffffff; + --line: rgba(15, 23, 42, 0.11); + --radius-xl: 22px; + --radius-lg: 16px; + --shadow-card: 0 20px 44px rgba(17, 18, 20, 0.1); + + display: block; + font-family: 'Inter', sans-serif; + color: var(--text); + box-sizing: border-box; +} + +.historico-linhas-page { + min-height: 100vh; + padding: 0 12px; + display: flex; + align-items: flex-start; + justify-content: center; + position: relative; + overflow-y: auto; + background: + radial-gradient(900px 420px at 20% 10%, rgba(227, 61, 207, 0.14), transparent 60%), + radial-gradient(820px 380px at 80% 30%, rgba(227, 61, 207, 0.08), transparent 60%), + linear-gradient(180deg, #ffffff 0%, #f5f5f7 70%); + + &::after { + content: ''; + position: absolute; + inset: 0; + pointer-events: none; + background: rgba(255, 255, 255, 0.25); + } +} + +.page-blob { + position: fixed; + pointer-events: none; + border-radius: 999px; + filter: blur(34px); + opacity: 0.55; + z-index: 0; + background: radial-gradient(circle at 30% 30%, rgba(227, 61, 207, 0.55), rgba(227, 61, 207, 0.06)); + animation: floaty 10s ease-in-out infinite; + + &.blob-1 { width: 420px; height: 420px; top: -140px; left: -140px; } + &.blob-2 { width: 520px; height: 520px; top: -220px; right: -240px; animation-duration: 12s; } + &.blob-3 { width: 360px; height: 360px; bottom: -180px; left: 25%; animation-duration: 14s; } + &.blob-4 { width: 520px; height: 520px; bottom: -260px; right: -260px; animation-duration: 16s; opacity: 0.45; } +} + +@keyframes floaty { + 0% { transform: translate(0, 0) scale(1); } + 50% { transform: translate(18px, 10px) scale(1.03); } + 100% { transform: translate(0, 0) scale(1); } +} + +.container-geral-responsive { + width: 98% !important; + max-width: 1500px !important; + position: relative; + z-index: 1; + margin-top: 40px; + margin-bottom: 200px; +} + +.geral-card { + border-radius: var(--radius-xl); + overflow: hidden; + background: var(--surface); + border: 1px solid rgba(227, 61, 207, 0.16); + backdrop-filter: blur(12px); + box-shadow: var(--shadow-card); + position: relative; + display: flex; + flex-direction: column; + min-height: 80vh; + + &::before { + content: ''; + position: absolute; + inset: 1px; + border-radius: calc(var(--radius-xl) - 1px); + pointer-events: none; + border: 1px solid rgba(255, 255, 255, 0.65); + opacity: 0.75; + } +} + +.geral-header { + padding: 16px 24px; + border-bottom: 1px solid rgba(17, 18, 20, 0.06); + background: linear-gradient(180deg, rgba(227, 61, 207, 0.06), rgba(255, 255, 255, 0.2)); + flex-shrink: 0; +} + +.header-row-top { + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + gap: 12px; + + @media (max-width: 900px) { + grid-template-columns: 1fr; + text-align: center; + gap: 14px; + + .title-badge { justify-self: center; } + .header-actions { justify-self: center; } + } +} + +.title-badge { + justify-self: start; + display: inline-flex; + align-items: center; + gap: 10px; + padding: 6px 12px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.82); + border: 1px solid rgba(227, 61, 207, 0.22); + color: var(--text); + font-size: 13px; + font-weight: 800; + + i { color: var(--brand); } +} + +.header-title { + justify-self: center; + text-align: center; +} + +.title { + font-size: 26px; + font-weight: 950; + letter-spacing: -0.3px; + color: var(--text); + margin-top: 10px; +} + +.subtitle { + color: var(--muted); + font-weight: 700; +} + +.header-actions { + justify-self: end; +} + +.btn-brand { + background-color: var(--brand); + border-color: var(--brand); + color: #fff; + font-weight: 900; + border-radius: 12px; + transition: transform 0.2s, box-shadow 0.2s; + + &:hover { + transform: translateY(-2px); + box-shadow: 0 10px 20px rgba(227, 61, 207, 0.25); + filter: brightness(1.05); + } +} + +.btn-glass { + background: rgba(255, 255, 255, 0.9); + border: 1px solid rgba(17, 18, 20, 0.16); + color: rgba(17, 18, 20, 0.85); + border-radius: 12px; + font-weight: 700; +} + +.filters-card { + background: rgba(255, 255, 255, 0.92); + border: 1px solid rgba(17, 18, 20, 0.08); + border-radius: 16px; + padding: 16px; + display: grid; + gap: 14px; + box-shadow: 0 14px 28px rgba(17, 18, 20, 0.08); +} + +.filters-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; +} + +.filters-title { + display: inline-flex; + align-items: center; + gap: 8px; + font-weight: 900; + font-size: 14px; + color: rgba(17, 18, 20, 0.82); +} + +.filters-actions { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.filters-grid { + display: grid; + grid-template-columns: repeat(12, minmax(0, 1fr)); + gap: 12px; +} + +.filter-field { + display: grid; + gap: 6px; + grid-column: span 2; + min-width: 0; + + label { + font-size: 11px; + font-weight: 800; + color: rgba(17, 18, 20, 0.6); + text-transform: uppercase; + letter-spacing: 0.05em; + } + + input { + width: 100%; + height: 40px; + border-radius: 10px; + border: 1px solid rgba(15, 23, 42, 0.15); + padding: 0 12px; + font-size: 14px; + background: #fff; + transition: border-color 0.2s ease, box-shadow 0.2s ease; + } + + input:focus { + outline: none; + border-color: var(--brand); + box-shadow: 0 0 0 3px rgba(227, 61, 207, 0.12); + } +} + +.line-field { + grid-column: span 4; +} + +.period-field { + grid-column: span 3; +} + +.btn-primary, +.btn-ghost { + height: 38px; + border-radius: 10px; + border: none; + font-weight: 700; + font-size: 12px; + display: inline-flex; + align-items: center; + gap: 8px; + padding: 0 14px; +} + +.btn-primary { + background: linear-gradient(135deg, var(--brand), #bc30ac); + color: #fff; + box-shadow: 0 8px 16px rgba(227, 61, 207, 0.24); +} + +.btn-ghost { + background: rgba(15, 23, 42, 0.06); + color: rgba(15, 23, 42, 0.85); +} + +.kpi-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; +} + +.kpi-card { + background: var(--surface-strong); + border: 1px solid rgba(17, 18, 20, 0.08); + border-radius: 14px; + padding: 12px 14px; + display: grid; + gap: 6px; + box-shadow: 0 8px 16px rgba(17, 18, 20, 0.06); +} + +.kpi-label { + font-size: 12px; + font-weight: 700; + color: rgba(17, 18, 20, 0.62); + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.kpi-value { + font-size: 22px; + line-height: 1; + font-weight: 900; + color: var(--blue); +} + +.geral-body { + padding: 18px 24px; + flex: 1; +} + +.table-wrap { + width: 100%; + background: rgba(255, 255, 255, 0.84); + border: 1px solid rgba(15, 23, 42, 0.1); + border-radius: 14px; + overflow: hidden; +} + +.table-modern { + margin: 0; + min-width: 980px; + + thead th { + background: linear-gradient(180deg, rgba(3, 15, 170, 0.92), rgba(3, 15, 170, 0.82)); + color: #fff; + font-size: 12px; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.05em; + border: none; + padding: 12px; + white-space: nowrap; + } + + tbody td { + border-top: 1px solid rgba(15, 23, 42, 0.08); + vertical-align: middle; + padding: 12px; + background: rgba(255, 255, 255, 0.92); + } + + tbody tr.table-row-item:hover td { + background: rgba(227, 61, 207, 0.05); + } + + tbody tr.table-row-item.expanded td { + background: rgba(227, 61, 207, 0.08); + } +} + +.user-cell { + display: grid; + line-height: 1.2; +} + +.user-name { + font-weight: 800; +} + +.user-email { + color: rgba(17, 18, 20, 0.55); +} + +.origin-pill { + display: inline-flex; + align-items: center; + border-radius: 999px; + padding: 4px 10px; + background: rgba(3, 15, 170, 0.1); + border: 1px solid rgba(3, 15, 170, 0.2); + color: rgba(3, 15, 170, 0.88); + font-size: 12px; + font-weight: 700; +} + +.badge-action { + display: inline-flex; + align-items: center; + border-radius: 999px; + padding: 5px 10px; + font-size: 12px; + font-weight: 800; + border: 1px solid transparent; + + &.action-create { + color: #157347; + background: rgba(25, 135, 84, 0.12); + border-color: rgba(25, 135, 84, 0.24); + } + + &.action-update { + color: #0a58ca; + background: rgba(13, 110, 253, 0.12); + border-color: rgba(13, 110, 253, 0.24); + } + + &.action-delete { + color: #b02a37; + background: rgba(220, 53, 69, 0.12); + border-color: rgba(220, 53, 69, 0.24); + } + + &.action-default { + color: #495057; + background: rgba(108, 117, 125, 0.12); + border-color: rgba(108, 117, 125, 0.24); + } +} + +.summary-col { + min-width: 360px; +} + +.summary-title { + font-size: 13px; + font-weight: 900; + margin-bottom: 2px; +} + +.summary-description { + font-size: 12px; + color: rgba(17, 18, 20, 0.66); +} + +.summary-diff { + margin-top: 6px; + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 12px; + border-radius: 10px; + background: rgba(15, 23, 42, 0.05); + padding: 4px 8px; + + .old { + color: #b02a37; + font-weight: 700; + } + + .new { + color: #157347; + font-weight: 700; + } +} + +.summary-ddd { + margin-top: 5px; + font-size: 11px; + color: rgba(17, 18, 20, 0.62); + font-weight: 700; + display: inline-flex; + align-items: center; + gap: 6px; +} + +.tone-mureg { color: #005f73; } +.tone-troca { color: #6f42c1; } +.tone-status { color: #0a58ca; } +.tone-linha { color: #0d6efd; } +.tone-chip { color: #198754; } +.tone-generic { color: #495057; } + +.actions-col { + width: 84px; + text-align: center; +} + +.expand-btn { + width: 34px; + height: 34px; + border-radius: 10px; + border: 1px solid rgba(15, 23, 42, 0.15); + background: #fff; + color: rgba(15, 23, 42, 0.85); +} + +.details-row td { + background: rgba(255, 255, 255, 0.94); +} + +.details-panel { + display: grid; + grid-template-columns: 1fr; + gap: 12px; +} + +.details-section { + border: 1px solid rgba(15, 23, 42, 0.1); + border-radius: 12px; + background: #fff; + padding: 10px 12px; +} + +.section-title { + display: inline-flex; + align-items: center; + gap: 7px; + font-weight: 800; + color: rgba(17, 18, 20, 0.84); + margin-bottom: 8px; +} + +.changes-list { + display: grid; + gap: 8px; +} + +.change-item { + border: 1px solid rgba(15, 23, 42, 0.08); + border-radius: 10px; + padding: 8px; + background: rgba(248, 249, 250, 0.85); +} + +.change-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 4px; +} + +.change-field { + font-size: 12px; + font-weight: 800; + color: #111; +} + +.change-type { + border-radius: 999px; + padding: 2px 8px; + font-size: 11px; + font-weight: 800; + border: 1px solid transparent; + + &.change-added { + background: rgba(25, 135, 84, 0.12); + color: #157347; + border-color: rgba(25, 135, 84, 0.24); + } + + &.change-removed { + background: rgba(220, 53, 69, 0.12); + color: #b02a37; + border-color: rgba(220, 53, 69, 0.24); + } + + &.change-modified { + background: rgba(13, 110, 253, 0.12); + color: #0a58ca; + border-color: rgba(13, 110, 253, 0.24); + } +} + +.change-values { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 12px; + + .old { + color: #b02a37; + font-weight: 700; + } + + .new { + color: #157347; + font-weight: 700; + } +} + +.empty-state { + font-size: 13px; + color: rgba(17, 18, 20, 0.62); + font-weight: 600; +} + +.empty-group { + text-align: center; + padding: 28px; + color: rgba(17, 18, 20, 0.65); + font-weight: 700; +} + +.empty-group.helper { + background: rgba(3, 15, 170, 0.05); + border-bottom: 1px solid rgba(3, 15, 170, 0.12); +} + +.geral-footer { + display: none; +} + +.footer-meta { + display: flex; + align-items: center; + gap: 14px; + flex-wrap: wrap; +} + +.pagination-modern .page-link { + border-radius: 10px; + border: 1px solid rgba(15, 23, 42, 0.15); + color: rgba(15, 23, 42, 0.85); + margin: 0 2px; +} + +.pagination-modern .page-item.active .page-link { + background: var(--brand); + border-color: var(--brand); + color: #fff; +} + +@media (max-width: 1200px) { + .filters-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); } + .filter-field { grid-column: span 2; } + .line-field { grid-column: span 3; } + .period-field { grid-column: span 3; } + .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .details-panel { grid-template-columns: 1fr; } +} + +@media (max-width: 768px) { + .geral-header, + .geral-body, + .geral-footer { + padding-left: 14px; + padding-right: 14px; + } + + .filters-grid { grid-template-columns: repeat(1, minmax(0, 1fr)); } + .filter-field, + .line-field { + grid-column: span 1; + } + + .kpi-grid { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } +} diff --git a/src/app/pages/historico-linhas/historico-linhas.ts b/src/app/pages/historico-linhas/historico-linhas.ts new file mode 100644 index 0000000..c9f4bf6 --- /dev/null +++ b/src/app/pages/historico-linhas/historico-linhas.ts @@ -0,0 +1,598 @@ +import { Component, OnInit, ElementRef, ViewChild, ChangeDetectorRef, Inject, PLATFORM_ID } from '@angular/core'; +import { CommonModule, isPlatformBrowser } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { HttpErrorResponse } from '@angular/common/http'; +import { firstValueFrom } from 'rxjs'; + +import { CustomSelectComponent } from '../../components/custom-select/custom-select'; +import { + HistoricoService, + AuditLogDto, + AuditChangeType, + AuditFieldChangeDto, + LineHistoricoQuery +} from '../../services/historico.service'; +import { TableExportService } from '../../services/table-export.service'; + +interface SelectOption { + value: string; + label: string; +} + +type EventTone = 'mureg' | 'troca' | 'status' | 'linha' | 'chip' | 'generic'; + +interface EventSummary { + title: string; + description: string; + before?: string | null; + after?: string | null; + beforeDdd?: string | null; + afterDdd?: string | null; + tone: EventTone; +} + +@Component({ + selector: 'app-historico-linhas', + standalone: true, + imports: [CommonModule, FormsModule, CustomSelectComponent], + templateUrl: './historico-linhas.html', + styleUrls: ['./historico-linhas.scss'], +}) +export class HistoricoLinhas implements OnInit { + @ViewChild('successToast', { static: false }) successToast!: ElementRef; + + logs: AuditLogDto[] = []; + loading = false; + exporting = false; + error = false; + errorMsg = ''; + toastMessage = ''; + + expandedLogId: string | null = null; + + page = 1; + pageSize = 10; + pageSizeOptions = [10, 20, 50, 100]; + total = 0; + + filterLine = ''; + filterPageName = ''; + filterAction = ''; + filterUser = ''; + dateFrom = ''; + dateTo = ''; + + readonly pageOptions: SelectOption[] = [ + { value: '', label: 'Todas as origens' }, + { value: 'Geral', label: 'Geral' }, + { value: 'Mureg', label: 'Mureg' }, + { value: 'Troca de número', label: 'Troca de número' }, + { value: 'Vigência', label: 'Vigência' }, + { value: 'Parcelamentos', label: 'Parcelamentos' }, + ]; + + readonly actionOptions: SelectOption[] = [ + { value: '', label: 'Todas as ações' }, + { value: 'CREATE', label: 'Criação' }, + { value: 'UPDATE', label: 'Atualização' }, + { value: 'DELETE', label: 'Exclusão' }, + ]; + + private readonly summaryCache = new Map(); + private readonly idFieldExceptions = new Set(['iccid']); + + constructor( + private readonly historicoService: HistoricoService, + private readonly cdr: ChangeDetectorRef, + @Inject(PLATFORM_ID) private readonly platformId: object, + private readonly tableExportService: TableExportService + ) {} + + ngOnInit(): void { + // Tela inicia aguardando o usuário informar a linha. + } + + applyFilters(): void { + this.page = 1; + this.fetch(); + } + + refresh(): void { + this.fetch(); + } + + clearFilters(): void { + this.filterLine = ''; + this.filterPageName = ''; + this.filterAction = ''; + this.filterUser = ''; + this.dateFrom = ''; + this.dateTo = ''; + this.page = 1; + this.logs = []; + this.total = 0; + this.error = false; + this.errorMsg = ''; + this.summaryCache.clear(); + } + + onPageSizeChange(): void { + this.page = 1; + this.fetch(); + } + + goToPage(target: number): void { + this.page = Math.max(1, Math.min(this.totalPages, target)); + this.fetch(); + } + + toggleDetails(log: AuditLogDto, event?: Event): void { + if (event) event.stopPropagation(); + this.expandedLogId = this.expandedLogId === log.id ? null : log.id; + } + + async onExport(): Promise { + if (this.exporting) return; + + const lineTerm = this.normalizedLineTerm; + if (!lineTerm) { + await this.showToast('Informe a linha para exportar.'); + return; + } + + this.exporting = true; + try { + const allLogs = await this.fetchAllLogsForExport(); + if (!allLogs.length) { + await this.showToast('Nenhum evento encontrado para exportar.'); + return; + } + + const timestamp = this.tableExportService.buildTimestamp(); + await this.tableExportService.exportAsXlsx({ + fileName: `historico_linhas_${timestamp}`, + sheetName: 'HistoricoLinhas', + rows: allLogs, + columns: [ + { header: 'Data/Hora', type: 'datetime', value: (log) => log.occurredAtUtc ?? '' }, + { header: 'Usuario', value: (log) => this.displayUserName(log) }, + { header: 'E-mail', value: (log) => log.userEmail ?? '' }, + { header: 'Origem', value: (log) => log.page ?? '' }, + { header: 'Acao', value: (log) => this.formatAction(log.action) }, + { header: 'Evento', value: (log) => this.summaryFor(log).title }, + { header: 'Resumo', value: (log) => this.summaryFor(log).description }, + { header: 'Valor Anterior', value: (log) => this.summaryFor(log).before ?? '' }, + { header: 'Valor Novo', value: (log) => this.summaryFor(log).after ?? '' }, + { header: 'DDD Anterior', value: (log) => this.summaryFor(log).beforeDdd ?? '' }, + { header: 'DDD Novo', value: (log) => this.summaryFor(log).afterDdd ?? '' }, + { header: 'Mudancas', value: (log) => this.formatChangesSummary(log) }, + ], + }); + + await this.showToast(`Planilha exportada com ${allLogs.length} evento(s).`); + } catch { + await this.showToast('Erro ao exportar histórico de linhas.'); + } finally { + this.exporting = false; + } + } + + formatDateTime(value?: string | null): string { + if (!value) return '-'; + const dt = new Date(value); + if (Number.isNaN(dt.getTime())) return '-'; + return dt.toLocaleString('pt-BR'); + } + + displayUserName(log: AuditLogDto): string { + const name = (log.userName || '').trim(); + return name ? name : 'SISTEMA'; + } + + formatAction(action?: string | null): string { + const value = (action || '').toUpperCase(); + if (!value) return '-'; + if (value === 'CREATE') return 'Criação'; + if (value === 'UPDATE') return 'Atualização'; + if (value === 'DELETE') return 'Exclusão'; + return 'Outro'; + } + + actionClass(action?: string | null): string { + const value = (action || '').toUpperCase(); + if (value === 'CREATE') return 'action-create'; + if (value === 'UPDATE') return 'action-update'; + if (value === 'DELETE') return 'action-delete'; + return 'action-default'; + } + + changeTypeLabel(type?: AuditChangeType | string | null): string { + if (!type) return 'Alterado'; + if (type === 'added') return 'Adicionado'; + if (type === 'removed') return 'Removido'; + return 'Alterado'; + } + + changeTypeClass(type?: AuditChangeType | string | null): string { + if (type === 'added') return 'change-added'; + if (type === 'removed') return 'change-removed'; + return 'change-modified'; + } + + formatChangeValue(value?: string | null): string { + if (value === undefined || value === null || value === '') return '-'; + return String(value); + } + + summaryFor(log: AuditLogDto): EventSummary { + const cached = this.summaryCache.get(log.id); + if (cached) return cached; + const summary = this.buildEventSummary(log); + this.summaryCache.set(log.id, summary); + return summary; + } + + toneClass(tone: EventTone): string { + return `tone-${tone}`; + } + + trackByLog(_: number, log: AuditLogDto): string { + return log.id; + } + + trackByField(_: number, change: AuditFieldChangeDto): string { + return `${change.field}-${change.oldValue ?? ''}-${change.newValue ?? ''}`; + } + + visibleChanges(log: AuditLogDto): AuditFieldChangeDto[] { + return this.publicChanges(log); + } + + get normalizedLineTerm(): string { + return (this.filterLine || '').trim(); + } + + get hasLineFilter(): boolean { + return !!this.normalizedLineTerm; + } + + get totalPages(): number { + return Math.ceil((this.total || 0) / this.pageSize) || 1; + } + + get pageNumbers(): number[] { + const total = this.totalPages; + const current = this.page; + const max = 5; + let start = Math.max(1, current - 2); + let end = Math.min(total, start + (max - 1)); + start = Math.max(1, end - (max - 1)); + const pages: number[] = []; + for (let i = start; i <= end; i += 1) pages.push(i); + return pages; + } + + get pageStart(): number { + return this.total === 0 ? 0 : (this.page - 1) * this.pageSize + 1; + } + + get pageEnd(): number { + if (this.total === 0) return 0; + return Math.min(this.page * this.pageSize, this.total); + } + + get statusCountInPage(): number { + return this.logs.filter((log) => this.summaryFor(log).tone === 'status').length; + } + + get trocaCountInPage(): number { + return this.logs.filter((log) => this.summaryFor(log).tone === 'troca').length; + } + + get muregCountInPage(): number { + return this.logs.filter((log) => this.summaryFor(log).tone === 'mureg').length; + } + + private fetch(): void { + const lineTerm = this.normalizedLineTerm; + if (!lineTerm) { + this.logs = []; + this.total = 0; + this.error = true; + this.errorMsg = 'Informe a linha para consultar o histórico.'; + this.loading = false; + this.summaryCache.clear(); + return; + } + + this.loading = true; + this.error = false; + this.errorMsg = ''; + this.expandedLogId = null; + + const query: LineHistoricoQuery = { + ...this.buildBaseQuery(), + line: lineTerm, + page: this.page, + pageSize: this.pageSize, + }; + + this.historicoService.listByLine(query).subscribe({ + next: (res) => { + this.logs = res.items || []; + this.total = res.total || 0; + this.page = res.page || this.page; + this.pageSize = res.pageSize || this.pageSize; + this.loading = false; + this.rebuildSummaryCache(); + }, + error: (err: HttpErrorResponse) => { + this.loading = false; + this.error = true; + this.logs = []; + this.total = 0; + this.summaryCache.clear(); + if (err?.status === 400) { + this.errorMsg = err?.error?.message || 'Informe uma linha válida.'; + return; + } + if (err?.status === 403) { + this.errorMsg = 'Acesso restrito.'; + return; + } + this.errorMsg = 'Erro ao carregar histórico da linha. Tente novamente.'; + } + }); + } + + private async fetchAllLogsForExport(): Promise { + const lineTerm = this.normalizedLineTerm; + if (!lineTerm) return []; + + const pageSize = 500; + let page = 1; + let expectedTotal = 0; + const all: AuditLogDto[] = []; + + while (page <= 500) { + const response = await firstValueFrom( + this.historicoService.listByLine({ + ...this.buildBaseQuery(), + line: lineTerm, + page, + pageSize, + }) + ); + + const items = response?.items ?? []; + expectedTotal = response?.total ?? 0; + all.push(...items); + + if (items.length === 0) break; + if (items.length < pageSize) break; + if (expectedTotal > 0 && all.length >= expectedTotal) break; + page += 1; + } + + return all; + } + + private buildBaseQuery(): Omit { + return { + pageName: this.filterPageName || undefined, + action: this.filterAction || undefined, + user: this.filterUser?.trim() || undefined, + dateFrom: this.toIsoDate(this.dateFrom, false) || undefined, + dateTo: this.toIsoDate(this.dateTo, true) || undefined, + }; + } + + private rebuildSummaryCache(): void { + this.summaryCache.clear(); + this.logs.forEach((log) => { + this.summaryCache.set(log.id, this.buildEventSummary(log)); + }); + } + + private buildEventSummary(log: AuditLogDto): EventSummary { + const page = (log.page || '').toLowerCase(); + const entity = (log.entityName || '').toLowerCase(); + + const linhaChange = this.findChange(log, 'linha'); + const statusChange = this.findChange(log, 'status'); + const chipChange = this.findChange(log, 'chip', 'iccid'); + const linhaAntiga = this.findChange(log, 'linhaantiga'); + const linhaNova = this.findChange(log, 'linhanova'); + + const muregLike = entity === 'muregline' || page.includes('mureg'); + if (muregLike) { + const before = this.firstFilled(linhaAntiga?.newValue, linhaAntiga?.oldValue, linhaChange?.oldValue); + const after = this.firstFilled(linhaNova?.newValue, linhaNova?.oldValue, linhaChange?.newValue); + return { + title: 'Troca de Mureg', + description: 'Linha alterada no fluxo de Mureg.', + before, + after, + beforeDdd: this.extractDdd(before), + afterDdd: this.extractDdd(after), + tone: 'mureg', + }; + } + + const trocaLike = entity === 'trocanumeroline' || page.includes('troca'); + if (trocaLike) { + const before = this.firstFilled(linhaAntiga?.newValue, linhaAntiga?.oldValue, linhaChange?.oldValue); + const after = this.firstFilled(linhaNova?.newValue, linhaNova?.oldValue, linhaChange?.newValue); + return { + title: 'Troca de Número', + description: 'Linha antiga substituída por uma nova.', + before, + after, + beforeDdd: this.extractDdd(before), + afterDdd: this.extractDdd(after), + tone: 'troca', + }; + } + + if (statusChange) { + const oldStatus = this.firstFilled(statusChange.oldValue); + const newStatus = this.firstFilled(statusChange.newValue); + const wasBlocked = this.isBlockedStatus(oldStatus); + const isBlocked = this.isBlockedStatus(newStatus); + let description = 'Status da linha atualizado.'; + if (!wasBlocked && isBlocked) description = 'Linha foi bloqueada.'; + if (wasBlocked && !isBlocked) description = 'Linha foi desbloqueada.'; + return { + title: 'Status da Linha', + description, + before: oldStatus, + after: newStatus, + tone: 'status', + }; + } + + if (linhaChange) { + return { + title: 'Alteração da Linha', + description: 'Número da linha foi atualizado.', + before: this.firstFilled(linhaChange.oldValue), + after: this.firstFilled(linhaChange.newValue), + beforeDdd: this.extractDdd(linhaChange.oldValue), + afterDdd: this.extractDdd(linhaChange.newValue), + tone: 'linha', + }; + } + + if (chipChange) { + return { + title: 'Alteração de Chip', + description: 'ICCID/chip atualizado na linha.', + before: this.firstFilled(chipChange.oldValue), + after: this.firstFilled(chipChange.newValue), + tone: 'chip', + }; + } + + const first = this.publicChanges(log)[0]; + if (first) { + return { + title: 'Outras alterações', + description: `Campo ${first.field} foi atualizado.`, + before: this.firstFilled(first.oldValue), + after: this.firstFilled(first.newValue), + tone: 'generic', + }; + } + + return { + title: 'Sem detalhes', + description: 'Não há mudanças detalhadas registradas para este evento.', + tone: 'generic', + }; + } + + private findChange(log: AuditLogDto, ...fields: string[]): AuditFieldChangeDto | null { + if (!fields.length) return null; + const normalizedTargets = new Set(fields.map((field) => this.normalizeField(field))); + return (log.changes || []).find((change) => normalizedTargets.has(this.normalizeField(change.field))) || null; + } + + private normalizeField(value?: string | null): string { + return (value ?? '') + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .replace(/[^a-zA-Z0-9]/g, '') + .toLowerCase() + .trim(); + } + + private firstFilled(...values: Array): string | null { + for (const value of values) { + const normalized = (value ?? '').toString().trim(); + if (normalized) return normalized; + } + return null; + } + + private formatChangesSummary(log: AuditLogDto): string { + const changes = this.publicChanges(log); + if (!changes.length) return ''; + return changes + .map((change) => { + const field = change?.field ?? 'campo'; + const oldValue = this.formatChangeValue(change?.oldValue); + const newValue = this.formatChangeValue(change?.newValue); + return `${field}: ${oldValue} -> ${newValue}`; + }) + .join(' | '); + } + + private publicChanges(log: AuditLogDto): AuditFieldChangeDto[] { + return (log?.changes ?? []).filter((change) => !this.isHiddenIdField(change?.field)); + } + + private isHiddenIdField(field?: string | null): boolean { + const normalized = this.normalizeField(field); + if (!normalized) return false; + if (this.idFieldExceptions.has(normalized)) return false; + if (normalized === 'id') return true; + return normalized.endsWith('id'); + } + + private isBlockedStatus(status?: string | null): boolean { + const normalized = (status ?? '').toLowerCase().trim(); + if (!normalized) return false; + return ( + normalized.includes('bloque') || + normalized.includes('perda') || + normalized.includes('roubo') || + normalized.includes('suspens') + ); + } + + private extractDdd(value?: string | null): string | null { + const digits = this.digitsOnly(value); + if (!digits) return null; + + if (digits.startsWith('55') && digits.length >= 12) { + return digits.slice(2, 4); + } + if (digits.length >= 10) { + return digits.slice(0, 2); + } + if (digits.length >= 2) { + return digits.slice(0, 2); + } + return null; + } + + private digitsOnly(value?: string | null): string { + return (value ?? '').replace(/\D/g, ''); + } + + private toIsoDate(value: string, endOfDay: boolean): string | null { + if (!value) return null; + const time = endOfDay ? '23:59:59' : '00:00:00'; + const date = new Date(`${value}T${time}`); + if (isNaN(date.getTime())) return null; + return date.toISOString(); + } + + private async showToast(message: string): Promise { + if (!isPlatformBrowser(this.platformId)) return; + this.toastMessage = message; + this.cdr.detectChanges(); + if (!this.successToast?.nativeElement) return; + + try { + const bs = await import('bootstrap'); + const toastInstance = bs.Toast.getOrCreateInstance(this.successToast.nativeElement, { + autohide: true, + delay: 3000 + }); + toastInstance.show(); + } catch (error) { + console.error(error); + } + } +} diff --git a/src/app/pages/mureg/mureg.html b/src/app/pages/mureg/mureg.html index 9a4cfa7..c742410 100644 --- a/src/app/pages/mureg/mureg.html +++ b/src/app/pages/mureg/mureg.html @@ -35,7 +35,7 @@ Exportar Exportando... -
@@ -177,10 +177,10 @@ - -
@@ -391,8 +391,8 @@
- - + +
diff --git a/src/app/pages/mureg/mureg.ts b/src/app/pages/mureg/mureg.ts index 86fdc7b..8ba13ea 100644 --- a/src/app/pages/mureg/mureg.ts +++ b/src/app/pages/mureg/mureg.ts @@ -11,6 +11,7 @@ import { isPlatformBrowser, CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { HttpClient, HttpParams } from '@angular/common/http'; import { firstValueFrom } from 'rxjs'; +import { AuthService } from '../../services/auth.service'; import { LinesService } from '../../services/lines.service'; import { CustomSelectComponent } from '../../components/custom-select/custom-select'; import { TableExportService } from '../../services/table-export.service'; @@ -105,6 +106,7 @@ export class Mureg implements AfterViewInit { @Inject(PLATFORM_ID) private platformId: object, private http: HttpClient, private cdr: ChangeDetectorRef, + private authService: AuthService, private linesService: LinesService, private tableExportService: TableExportService ) {} @@ -177,9 +179,20 @@ export class Mureg implements AfterViewInit { clienteInfo: '' }; + isSysAdmin = false; + isGestor = false; + isFinanceiro = false; + + get canManageRecords(): boolean { + return this.isSysAdmin || this.isGestor; + } + async ngAfterViewInit() { if (!isPlatformBrowser(this.platformId)) return; this.initAnimations(); + this.isSysAdmin = this.authService.hasRole('sysadmin'); + this.isGestor = this.authService.hasRole('gestor'); + this.isFinanceiro = this.authService.hasRole('financeiro'); setTimeout(() => { this.preloadClients(); // ✅ já deixa o select pronto this.refresh(); @@ -624,6 +637,11 @@ export class Mureg implements AfterViewInit { // CREATE MODAL // ======================================================================= onCreate() { + if (!this.canManageRecords) { + this.showToast('Perfil Financeiro possui acesso somente leitura.'); + return; + } + this.preloadClients(); this.createOpen = true; @@ -636,7 +654,7 @@ export class Mureg implements AfterViewInit { linhaAntiga: '', linhaNova: '', iccid: '', - dataDaMureg: '', + dataDaMureg: this.nowDateInput(), clienteInfo: '' }; @@ -663,6 +681,11 @@ export class Mureg implements AfterViewInit { } saveCreate() { + if (!this.canManageRecords) { + this.showToast('Perfil Financeiro possui acesso somente leitura.'); + return; + } + const mobileLineId = String(this.createModel.mobileLineId ?? '').trim(); const linhaNova = String(this.createModel.linhaNova ?? '').trim(); @@ -679,7 +702,7 @@ export class Mureg implements AfterViewInit { linhaAntiga: (this.createModel.linhaAntiga ?? '') || null, linhaNova: (this.createModel.linhaNova ?? '') || null, iccid: (this.createModel.iccid ?? '') || null, - dataDaMureg: this.dateInputToIso(this.createModel.dataDaMureg) + dataDaMureg: new Date().toISOString() }; if (!payload.item || payload.item <= 0) delete payload.item; @@ -703,6 +726,11 @@ export class Mureg implements AfterViewInit { // EDIT MODAL // ======================================================================= onEditar(r: MuregRow) { + if (!this.canManageRecords) { + this.showToast('Perfil Financeiro possui acesso somente leitura.'); + return; + } + this.preloadClients(); this.editOpen = true; @@ -770,6 +798,11 @@ export class Mureg implements AfterViewInit { } saveEdit() { + if (!this.canManageRecords) { + this.showToast('Perfil Financeiro possui acesso somente leitura.'); + return; + } + if (!this.editModel || !this.editModel.id) return; const mobileLineId = String(this.editModel.mobileLineId ?? '').trim(); @@ -844,6 +877,11 @@ export class Mureg implements AfterViewInit { // DELETE MODAL // ======================================================================= onDelete(row: MuregRow) { + if (!this.canManageRecords) { + this.showToast('Perfil Financeiro possui acesso somente leitura.'); + return; + } + this.deleteTarget = row; this.deleteOpen = true; this.deleteSaving = false; @@ -856,6 +894,11 @@ export class Mureg implements AfterViewInit { } async confirmDelete() { + if (!this.canManageRecords) { + await this.showToast('Perfil Financeiro possui acesso somente leitura.'); + return; + } + if (!this.deleteTarget?.id) return; if (!(await confirmDeletionWithTyping('esta Mureg'))) return; @@ -914,6 +957,14 @@ export class Mureg implements AfterViewInit { return dt.toISOString(); } + private nowDateInput(): string { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + } + private extractApiMessage(err: any): string | null { try { const m1 = err?.error?.message; diff --git a/src/app/pages/parcelamentos/components/parcelamentos-table/parcelamentos-table.html b/src/app/pages/parcelamentos/components/parcelamentos-table/parcelamentos-table.html index fb8c03c..3b3c311 100644 --- a/src/app/pages/parcelamentos/components/parcelamentos-table/parcelamentos-table.html +++ b/src/app/pages/parcelamentos/components/parcelamentos-table/parcelamentos-table.html @@ -100,6 +100,7 @@ diff --git a/src/app/pages/parcelamentos/components/parcelamentos-table/parcelamentos-table.ts b/src/app/pages/parcelamentos/components/parcelamentos-table/parcelamentos-table.ts index 45a2629..17861bd 100644 --- a/src/app/pages/parcelamentos/components/parcelamentos-table/parcelamentos-table.ts +++ b/src/app/pages/parcelamentos/components/parcelamentos-table/parcelamentos-table.ts @@ -26,7 +26,8 @@ export class ParcelamentosTableComponent { @Input() items: ParcelamentoViewItem[] = []; @Input() loading = false; @Input() errorMessage = ''; - @Input() isSysAdmin = false; + @Input() canEdit = false; + @Input() canDelete = false; @Input() segment: ParcelamentoSegment = 'todos'; @Input() segmentCounts: Record = { diff --git a/src/app/pages/parcelamentos/parcelamentos.html b/src/app/pages/parcelamentos/parcelamentos.html index 78ba26f..538debb 100644 --- a/src/app/pages/parcelamentos/parcelamentos.html +++ b/src/app/pages/parcelamentos/parcelamentos.html @@ -29,7 +29,7 @@ Exportar Exportando... - @@ -79,7 +79,8 @@ [total]="total" [pageSize]="pageSize" [pageSizeOptions]="pageSizeOptions" - [isSysAdmin]="isSysAdmin" + [canEdit]="canManageRecords" + [canDelete]="isSysAdmin" (segmentChange)="setSegment($event)" (detail)="openDetails($event)" (edit)="openEdit($event)" diff --git a/src/app/pages/parcelamentos/parcelamentos.ts b/src/app/pages/parcelamentos/parcelamentos.ts index ef6ce4d..d60e17a 100644 --- a/src/app/pages/parcelamentos/parcelamentos.ts +++ b/src/app/pages/parcelamentos/parcelamentos.ts @@ -95,6 +95,12 @@ export class Parcelamentos implements OnInit, OnDestroy { activeChips: FilterChip[] = []; isSysAdmin = false; + isGestor = false; + isFinanceiro = false; + + get canManageRecords(): boolean { + return this.isSysAdmin || this.isGestor; + } detailOpen = false; detailLoading = false; @@ -168,6 +174,8 @@ export class Parcelamentos implements OnInit, OnDestroy { private syncPermissions(): void { this.isSysAdmin = this.authService.hasRole('sysadmin'); + this.isGestor = this.authService.hasRole('gestor'); + this.isFinanceiro = this.authService.hasRole('financeiro'); } get totalPages(): number { @@ -409,6 +417,11 @@ export class Parcelamentos implements OnInit, OnDestroy { } openCreateModal(): void { + if (!this.canManageRecords) { + this.showToast('Perfil Financeiro possui acesso somente leitura.', 'danger'); + return; + } + this.createModel = this.buildCreateModel(); this.createError = ''; this.createOpen = true; @@ -421,6 +434,11 @@ export class Parcelamentos implements OnInit, OnDestroy { } saveNewParcelamento(model: ParcelamentoCreateModel): void { + if (!this.canManageRecords) { + this.showToast('Perfil Financeiro possui acesso somente leitura.', 'danger'); + return; + } + if (this.createSaving) return; this.createSaving = true; this.createError = ''; @@ -439,6 +457,11 @@ export class Parcelamentos implements OnInit, OnDestroy { } openEdit(item: ParcelamentoListItem): void { + if (!this.canManageRecords) { + this.showToast('Perfil Financeiro possui acesso somente leitura.', 'danger'); + return; + } + const id = this.getItemId(item); if (!id) return; this.editOpen = true; @@ -474,6 +497,11 @@ export class Parcelamentos implements OnInit, OnDestroy { } saveEditParcelamento(model: ParcelamentoCreateModel): void { + if (!this.canManageRecords) { + this.showToast('Perfil Financeiro possui acesso somente leitura.', 'danger'); + return; + } + if (this.editSaving || !this.editModel || !this.editId) return; this.editSaving = true; this.editError = ''; diff --git a/src/app/pages/troca-numero/troca-numero.html b/src/app/pages/troca-numero/troca-numero.html index 0edff36..0461aa6 100644 --- a/src/app/pages/troca-numero/troca-numero.html +++ b/src/app/pages/troca-numero/troca-numero.html @@ -35,7 +35,7 @@ Exportar Exportando... - @@ -156,7 +156,7 @@ {{ r.observacao || '-' }}
-
diff --git a/src/app/pages/troca-numero/troca-numero.ts b/src/app/pages/troca-numero/troca-numero.ts index 8eda5bc..673a9ce 100644 --- a/src/app/pages/troca-numero/troca-numero.ts +++ b/src/app/pages/troca-numero/troca-numero.ts @@ -11,6 +11,7 @@ import { isPlatformBrowser, CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { HttpClient, HttpParams } from '@angular/common/http'; import { firstValueFrom } from 'rxjs'; +import { AuthService } from '../../services/auth.service'; import { CustomSelectComponent } from '../../components/custom-select/custom-select'; import { TableExportService } from '../../services/table-export.service'; import { environment } from '../../../environments/environment'; @@ -73,6 +74,7 @@ export class TrocaNumero implements AfterViewInit { @Inject(PLATFORM_ID) private platformId: object, private http: HttpClient, private cdr: ChangeDetectorRef, + private authService: AuthService, private tableExportService: TableExportService ) {} @@ -136,9 +138,20 @@ export class TrocaNumero implements AfterViewInit { loadingClients = false; loadingLines = false; + isSysAdmin = false; + isGestor = false; + isFinanceiro = false; + + get canManageRecords(): boolean { + return this.isSysAdmin || this.isGestor; + } + async ngAfterViewInit() { if (!isPlatformBrowser(this.platformId)) return; this.initAnimations(); + this.isSysAdmin = this.authService.hasRole('sysadmin'); + this.isGestor = this.authService.hasRole('gestor'); + this.isFinanceiro = this.authService.hasRole('financeiro'); setTimeout(() => this.refresh()); } @@ -502,6 +515,11 @@ export class TrocaNumero implements AfterViewInit { // ====== MODAL EDIÇÃO ====== onEditar(r: TrocaRow) { + if (!this.canManageRecords) { + this.showToast('Perfil Financeiro possui acesso somente leitura.'); + return; + } + this.editOpen = true; this.editSaving = false; @@ -524,6 +542,11 @@ export class TrocaNumero implements AfterViewInit { } saveEdit() { + if (!this.canManageRecords) { + this.showToast('Perfil Financeiro possui acesso somente leitura.'); + return; + } + if (!this.editModel || !this.editModel.id) return; this.editSaving = true; @@ -555,6 +578,11 @@ export class TrocaNumero implements AfterViewInit { // ====== MODAL CRIAÇÃO ====== onCreate() { + if (!this.canManageRecords) { + this.showToast('Perfil Financeiro possui acesso somente leitura.'); + return; + } + this.createOpen = true; this.createSaving = false; @@ -584,6 +612,11 @@ export class TrocaNumero implements AfterViewInit { } saveCreate() { + if (!this.canManageRecords) { + this.showToast('Perfil Financeiro possui acesso somente leitura.'); + return; + } + // ✅ validações do "beber do GERAL" if (!String(this.selectedCliente ?? '').trim()) { this.showToast('Selecione um Cliente do GERAL.'); diff --git a/src/app/services/historico.service.ts b/src/app/services/historico.service.ts index 2fb0c5c..f6ab9b3 100644 --- a/src/app/services/historico.service.ts +++ b/src/app/services/historico.service.ts @@ -50,6 +50,18 @@ export interface HistoricoQuery { pageSize?: number; } +export interface LineHistoricoQuery { + line: string; + pageName?: string; + action?: AuditAction | string; + user?: string; + search?: string; + dateFrom?: string; + dateTo?: string; + page?: number; + pageSize?: number; +} + @Injectable({ providedIn: 'root' }) export class HistoricoService { private readonly baseApi: string; @@ -74,4 +86,20 @@ export class HistoricoService { return this.http.get>(`${this.baseApi}/historico`, { params: httpParams }); } + + listByLine(params: LineHistoricoQuery): Observable> { + let httpParams = new HttpParams(); + if (params.line) httpParams = httpParams.set('line', params.line); + if (params.pageName) httpParams = httpParams.set('pageName', params.pageName); + if (params.action) httpParams = httpParams.set('action', params.action); + if (params.user) httpParams = httpParams.set('user', params.user); + if (params.search) httpParams = httpParams.set('search', params.search); + if (params.dateFrom) httpParams = httpParams.set('dateFrom', params.dateFrom); + if (params.dateTo) httpParams = httpParams.set('dateTo', params.dateTo); + + httpParams = httpParams.set('page', String(params.page || 1)); + httpParams = httpParams.set('pageSize', String(params.pageSize || 10)); + + return this.http.get>(`${this.baseApi}/historico/linhas`, { params: httpParams }); + } } diff --git a/src/app/services/users.service.ts b/src/app/services/users.service.ts index 115f3ce..4d7bb8b 100644 --- a/src/app/services/users.service.ts +++ b/src/app/services/users.service.ts @@ -4,7 +4,7 @@ import { Observable } from 'rxjs'; import { environment } from '../../environments/environment'; -export type UserPermission = 'sysadmin' | 'gestor' | 'cliente'; +export type UserPermission = 'sysadmin' | 'gestor' | 'financeiro' | 'cliente'; export type UserDto = { id: string;