From b6a5dffdec0a19c9542bb4e81338b31bfd6d78d3 Mon Sep 17 00:00:00 2001 From: Kenio Date: Sun, 9 Nov 2025 09:44:13 -0300 Subject: [PATCH] =?UTF-8?q?feat():=20Crian=C3=A7=C3=A3o=20da=20consuma?= =?UTF-8?q?=C3=A7=C3=A3o=20dos=20seguintes=20endpoints,=20Log=20Server,=20?= =?UTF-8?q?Log=20Ged=20e=20Log=20Database?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 578 +++++++++++++++++- package.json | 5 +- .../(client)/clientes/[id]/page.tsx | 492 ++++++++++++++- src/components/ui/badge.tsx | 46 ++ src/components/ui/scroll-area.tsx | 58 ++ .../data/Log/LogDatabaseData.ts | 31 + .../administrativo/data/Log/LogGedData.ts | 31 + .../hooks/Log/useLogDatabaseHook.ts | 23 + .../administrativo/hooks/Log/useLogGedHook.ts | 23 + .../hooks/Log/useLogServerHook.ts | 7 +- .../interfaces/Log/LogDatabaseInterface.ts | 25 + .../interfaces/Log/LogGedInterface.ts | 28 + .../interfaces/Log/LogInterface.ts | 5 + .../interfaces/Log/LogServerInterface.ts | 36 ++ .../services/Log/LogDatabaseService.ts | 22 + .../services/Log/LogGedService.ts | 22 + .../components/charts/PartitionBarChart.tsx | 40 ++ .../components/charts/PartitionPieChart.tsx | 71 +++ src/shared/utils/convertMBtoGB.ts | 21 + src/shared/utils/formatDateTime.ts | 16 + src/shared/utils/formatLogDateTime.ts | 39 ++ 21 files changed, 1589 insertions(+), 30 deletions(-) create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/scroll-area.tsx create mode 100644 src/packages/administrativo/data/Log/LogDatabaseData.ts create mode 100644 src/packages/administrativo/data/Log/LogGedData.ts create mode 100644 src/packages/administrativo/hooks/Log/useLogDatabaseHook.ts create mode 100644 src/packages/administrativo/hooks/Log/useLogGedHook.ts create mode 100644 src/packages/administrativo/interfaces/Log/LogDatabaseInterface.ts create mode 100644 src/packages/administrativo/interfaces/Log/LogGedInterface.ts create mode 100644 src/packages/administrativo/interfaces/Log/LogServerInterface.ts create mode 100644 src/packages/administrativo/services/Log/LogDatabaseService.ts create mode 100644 src/packages/administrativo/services/Log/LogGedService.ts create mode 100644 src/shared/components/charts/PartitionBarChart.tsx create mode 100644 src/shared/components/charts/PartitionPieChart.tsx create mode 100644 src/shared/utils/convertMBtoGB.ts create mode 100644 src/shared/utils/formatDateTime.ts create mode 100644 src/shared/utils/formatLogDateTime.ts diff --git a/package-lock.json b/package-lock.json index 4c9f71c..8dc307a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,9 +18,10 @@ "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-table": "^8.21.3", @@ -29,6 +30,7 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "cookies-next": "^6.1.0", + "date-fns": "^4.1.0", "faker-js": "^1.0.0", "input-otp": "^1.4.2", "js-cookie": "^3.0.5", @@ -41,6 +43,7 @@ "react-dom": "19.1.0", "react-hook-form": "^7.62.0", "react-masked-text": "^1.0.5", + "recharts": "^3.3.0", "sonner": "^2.0.7", "tailwind-merge": "^3.3.1", "tinymce": "^8.1.2", @@ -1119,6 +1122,24 @@ } } }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", @@ -1255,6 +1276,24 @@ } } }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", @@ -1321,6 +1360,24 @@ } } }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", @@ -1513,6 +1570,24 @@ } } }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popover": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", @@ -1550,6 +1625,24 @@ } } }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", @@ -1653,6 +1746,24 @@ } } }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", @@ -1684,6 +1795,37 @@ } } }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-select": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", @@ -1727,6 +1869,24 @@ } } }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-separator": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", @@ -1751,9 +1911,9 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -1832,6 +1992,24 @@ } } }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", @@ -2015,6 +2193,32 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, + "node_modules/@reduxjs/toolkit": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.10.1.tgz", + "integrity": "sha512-/U17EXQ9Do9Yx4DlNGU6eVNfZvFJfYpUtRRdLf19PbPjdWBxNlxGZXywQZ1p1Nz8nMkWplTI7iD/23m07nolDA==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^10.2.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -2029,6 +2233,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, "node_modules/@standard-schema/utils": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", @@ -2383,6 +2593,69 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/js-cookie": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", @@ -2431,6 +2704,7 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -2441,10 +2715,17 @@ "integrity": "sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.46.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz", @@ -2962,6 +3243,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3521,6 +3803,127 @@ "devOptional": true, "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -3582,6 +3985,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -3600,6 +4013,12 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3894,6 +4313,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-toolkit": { + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.41.0.tgz", + "integrity": "sha512-bDd3oRmbVgqZCJS6WmeQieOrzpl3URcWBUVDXxOELlUW2FuW+0glPOz1n0KnRie+PdyvUZcXz2sOn00c6pPRIA==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3914,6 +4343,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3998,6 +4428,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -4099,6 +4530,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -4524,6 +4956,12 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/faker-js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/faker-js/-/faker-js-1.0.0.tgz", @@ -5039,6 +5477,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -5110,6 +5558,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -6208,6 +6665,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-15.5.4.tgz", "integrity": "sha512-xH4Yjhb82sFYQfY3vbkJfgSDgXvBB6a8xPs9i35k6oZJRoQRihZH+4s9Yo2qsWpzBmZ3lPXaJ2KPXLfkvW4LnA==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "15.5.4", "@swc/helpers": "0.5.15", @@ -6617,6 +7075,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -6774,6 +7233,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -6783,6 +7243,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -6795,6 +7256,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.64.0.tgz", "integrity": "sha512-fnN+vvTiMLnRqKNTVhDysdrUay0kUUAymQnFIznmgDvapjveUWOOPqMNzPg+A+0yf9DuE2h6xzBjN1s+Qx8wcg==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -6810,7 +7272,8 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-masked-text": { "version": "1.0.5", @@ -6818,6 +7281,30 @@ "integrity": "sha512-WichrlCXehL0apIfIgOdi2mjBE03tdMi8wXF+DhHe2ySWYxXCkP88aqDBaJZWUMa3Jp8p2h71u7TpC7EzEjXYw==", "license": "ISC" }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-remove-scroll": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", @@ -6887,6 +7374,49 @@ } } }, + "node_modules/recharts": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.3.0.tgz", + "integrity": "sha512-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg==", + "license": "MIT", + "dependencies": { + "@reduxjs/toolkit": "1.x.x || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT", + "peer": true + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -6931,6 +7461,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -7617,6 +8153,12 @@ "dev": true, "license": "MIT" }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -7658,6 +8200,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -7669,7 +8212,8 @@ "version": "8.1.2", "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-8.1.2.tgz", "integrity": "sha512-KITxHEEHRlxC5xOnxA123eAJ67NgsWxNphtItWt9TRu07DiTZrWIqJeIKRX9euE51/l3kJO4WQiqoBXKTJJGsA==", - "license": "GPL-2.0-or-later" + "license": "GPL-2.0-or-later", + "peer": true }, "node_modules/to-regex-range": { "version": "5.0.1", @@ -7967,6 +8511,28 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 64e68ae..ffc9aaf 100644 --- a/package.json +++ b/package.json @@ -22,9 +22,10 @@ "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-table": "^8.21.3", @@ -33,6 +34,7 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "cookies-next": "^6.1.0", + "date-fns": "^4.1.0", "faker-js": "^1.0.0", "input-otp": "^1.4.2", "js-cookie": "^3.0.5", @@ -45,6 +47,7 @@ "react-dom": "19.1.0", "react-hook-form": "^7.62.0", "react-masked-text": "^1.0.5", + "recharts": "^3.3.0", "sonner": "^2.0.7", "tailwind-merge": "^3.3.1", "tinymce": "^8.1.2", diff --git a/src/app/(protected)/administrativo/(client)/clientes/[id]/page.tsx b/src/app/(protected)/administrativo/(client)/clientes/[id]/page.tsx index 4a199b0..e23d318 100644 --- a/src/app/(protected)/administrativo/(client)/clientes/[id]/page.tsx +++ b/src/app/(protected)/administrativo/(client)/clientes/[id]/page.tsx @@ -1,39 +1,495 @@ 'use client'; import { useParams } from 'next/navigation'; -import { useLogServerHook } from '@/packages/administrativo/hooks/Log/useLogServerHook'; // importa seu hook customizado import { useEffect } from 'react'; +// Helpers e hooks customizados +import { formatLogDateTime } from "@/shared/utils/formatLogDateTime"; +import { formatDateTime } from '@/shared/utils/formatDateTime'; +import { convertMBtoGB } from '@/shared/utils/convertMBtoGB'; + +// Hooks responsaveis em consumir os endpoint's +import { useLogServerHook } from '@/packages/administrativo/hooks/Log/useLogServerHook'; +import { useLogDatabaseHook } from '@/packages/administrativo/hooks/Log/useLogDatabaseHook'; +import { useLogGedHook } from '@/packages/administrativo/hooks/Log/useLogGedHook'; + +// Componentes do Shadcn/UI +import { + Card, CardHeader, CardTitle, CardContent +} from "@/components/ui/card"; +import { + Table, TableHeader, TableRow, TableHead, TableCell, TableBody +} from "@/components/ui/table"; +import { Badge } from "@/components/ui/badge"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { + Tabs, TabsContent, TabsList, TabsTrigger +} from "@/components/ui/tabs"; +import { PartitionBarChart } from '@/shared/components/charts/PartitionBarChart'; +import { renderPartitionPieChart } from '@/shared/components/charts/PartitionPieChart'; + export default function ClientePage() { - // Captura o ID da rota (ex: /administrativo/clientes/6) const { id } = useParams(); - // Hook customizado para carregar dados do log/server - const { log, fetchLogServer } = useLogServerHook(); + // Hooks de logs (server e database) + const { logServer, fetchLogServer } = useLogServerHook(); + const { logDatabase, fetchLogDatabase } = useLogDatabaseHook(); + const { logGed, fetchLogGed } = useLogGedHook(); - // Quando o componente montar, busca os dados do servidor/log + // Efeito responsável por buscar logs de forma sequencial useEffect(() => { - if (id) fetchLogServer(Number(id)); + + // Só executa se houver um ID válido + if (!id) return; + + const fetchSequentially = async () => { + + try { + + // Primeiro: tenta buscar o log do servidor + await fetchLogServer(Number(id)); + + } catch (error) { + + console.error("Erro ao buscar log do servidor:", error); + + } finally { + + // Após terminar a busca do servidor (com erro ou não), + // tenta buscar o log do banco + try { + + await fetchLogDatabase(Number(id)); + } catch (error) { + + console.error("Erro ao buscar log do banco:", error); + + } finally { + // E SOMENTE após a conclusão da busca do banco + // (mesmo que dê erro ou traga 0 registros), + // executa a busca no GED + try { + + await fetchLogGed(Number(id)); + } catch (error) { + console.error("Erro ao buscar log do GED:", error); + } + } + } + }; + + fetchSequentially(); }, [id]); - // Caso não exista log - if (!log) { - return

Nenhum dado encontrado para o cliente {id}.

; + // Caso não haja log do servidor, mostra mensagem simples + if (!logServer) { + return ( +

+ Nenhum dado encontrado para o cliente {id}. +

+ ); } + // Formata data e hora do log do servidor + const { formattedDate, formattedTime, isOutdated } = formatLogDateTime( + logServer?.data, + logServer?.hora + ); + + // ============================================================ + // Renderização principal da página + // ============================================================ return (
-

- Detalhes do Log do Cliente #{id} +

+ {logServer.cns} - {logServer.cartorio}

-
-

Nome: {log.name}

-

CNS: {log.cns}

-

Status: {log.status}

-

Email: {log.email}

- {/* outros campos conforme LogInterface */} -
+ + + Informações do Servidor + Banco de Dados + GED + Configurações + + + {/* ===================================================== */} + {/* Aba: Informação do servidor */} + {/* ===================================================== */} + +
+ {/* Badge com data e alerta */} +
+ + Data do log: {formattedDate} às {formattedTime} + + {isOutdated && ( + + - Atenção: Log do servidor desatualizado + + )} + + + +
+ + {/* Linha com dois cards lado a lado */} +
+ {/* Sistema Operacional */} + + + Sistema Operacional + + +
+

SO: {logServer.server.operacional_system.so}

+

Versão: {logServer.server.operacional_system.versao}

+

Release: {logServer.server.operacional_system.release}

+

+ Arquitetura: + + {logServer.server.operacional_system.arquitetura} + +

+
+
+
+ + {/* Memória */} + + + Memória + + +
+

Total: {logServer.server.memory.total_MB} MB

+

Usada: {logServer.server.memory.usada_MB} MB

+

Livre: {logServer.server.memory.livre_MB} MB

+

Buffer: {logServer.server.memory.buffer_MB} MB

+

Swap Total: {logServer.server.memory.swap_total_MB} MB

+

Swap Usada: {logServer.server.memory.swap_usada_MB} MB

+
+
+
+
+ + {/* Processadores */} + + + Processadores + + + + + + + Núcleo + Modelo + Clock (MHz) + Cache + Threads + Núcleos + + + + {logServer.server.cpu.map((cpuItem: any, index: number) => ( + + {cpuItem.núcleo} + {cpuItem.modelo} + {cpuItem.clock_mhz} + {cpuItem.cache} + {cpuItem.threads} + {cpuItem.núcleos} + + ))} + +
+
+
+
+
+
+ + {/* ===================================================== */} + {/* Aba: Banco de Dados */} + {/* ===================================================== */} + +
+ {/* Verifica se há dados disponíveis do banco */} + {logDatabase?.data ? ( + <> + {/* Badge com data e hora do log do banco */} +
+ {(() => { + const { formattedDate, formattedTime, isOutdated } = + formatLogDateTime(logDatabase.data.data, logDatabase.data.hora); + + return ( + <> + + Data do log: {formattedDate} às {formattedTime} + + {isOutdated && ( + + - Atenção: Log do banco de dados desatualizado + + )} + + + ); + })()} +
+ + {/* Linha de cards principais */} +
+ {/* Card: Partição do Banco */} + + + Partição do Banco + + +
+

Unidade: {logDatabase?.data?.database?.partition.unit}

+

Espaço Total: {logDatabase?.data.database?.partition.total_disk_space}

+

Espaço Usado: {logDatabase?.data.database?.partition.total_used_disk_space}

+

Espaço Livre: {logDatabase?.data.database?.partition.total_free_disk_space}

+

+ Uso (%):{" "} + 80 ? "destructive" : "outline"} + className={`ml-2 ${ + logDatabase.data.database?.partition.percent_used > 80 + ? "bg-red-200 text-red-800 border-red-400" + : "bg-green-100 text-green-800 border-green-400" + }`} + > + {logDatabase.data.database?.partition.percent_used.toFixed(2)}% + +

+
+
+
+ + {/* Card: Informações do Arquivo de Banco */} + + + Informações do Arquivo + + +
+

Tamanho: {convertMBtoGB(logDatabase.data.database?.file_size_mb)}

+

Acessível:{" "} + {logDatabase.data.database?.db_accessible ? ( + + Sim + + ) : ( + + Não + + )} +

+ + {/* Aqui aplicamos a função */} + {(() => { + const { formattedDate, formattedTime } = formatDateTime( + logDatabase?.data?.database?.last_modified + ); + return ( +

+ Última Modificação: {formattedDate} às {formattedTime} +

+ ); + })()} + +
+
+
+ + {/* Card: Gráfico/Pie de Uso da Partição */} + + + Uso da Partição + + + {renderPartitionPieChart(logDatabase.data.database?.partition)} + + + + {/* Card: Gráfico/Bar de Uso da Partição */} + + + Uso da Partição + + + {(() => { + const partition = logDatabase.data.database?.partition; + if (!partition) return

Sem dados disponíveis.

; + return ; + })()} +
+
+ +
+ + ) : ( +

+ Nenhum log de banco de dados disponível para este cliente. +

+ )} +
+ +
+ + + {/* ===================================================== */} + {/* Aba: GED */} + {/* ===================================================== */} + +
+ {/* Verifica se há dados disponíveis do banco */} + {logGed?.data ? ( + <> + {/* Badge com data e hora do log do banco */} +
+ {(() => { + const { formattedDate, formattedTime, isOutdated } = + formatLogDateTime(logGed.data.data, logGed.data.hora); + + return ( + <> + + Data do log: {formattedDate} às {formattedTime} + + {isOutdated && ( + + - Atenção: Log do GED desatualizado + + )} + + + ); + })()} +
+ + {/* Linha de cards principais */} +
+ {/* Card: Partição do Banco */} + + + Partição do GED + + +
+

Unidade: {logGed?.data?.ged?.partition.unit}

+

Espaço Total: {logGed?.data.ged?.partition.total_disk_space}

+

Espaço Usado: {logGed?.data.ged?.partition.total_used_disk_space}

+

Espaço Livre: {logGed?.data.ged?.partition.total_free_disk_space}

+

+ Uso (%):{" "} + 80 ? "destructive" : "outline"} + className={`ml-2 ${ + logGed.data.ged?.partition.percent_used > 80 + ? "bg-red-200 text-red-800 border-red-400" + : "bg-green-100 text-green-800 border-green-400" + }`} + > + {logGed.data.ged?.partition.percent_used.toFixed(2)}% + +

+
+
+
+ + {/* Pasta de arquivos */} + + + Pasta de Arquivos + + + + + + + Caminho + Data + Hora + Quantidade + + + + {logGed?.data.ged?.arquivos.map((gedItem: any, index: number) => ( + + {gedItem.path} + {gedItem.data} + {gedItem.hora} + {gedItem.quantidade} + + ))} + +
+
+
+
+ + + {/* Card: Gráfico/Pie de Uso da Partição */} + + + Uso da Partição + + + {renderPartitionPieChart(logGed.data.ged?.partition)} + + + + {/* Card: Gráfico/Bar de Uso da Partição */} + + + Uso da Partição + + + {(() => { + const partition = logGed.data.ged?.partition; + if (!partition) return

Sem dados disponíveis.

; + return ; + })()} +
+
+
+ + ) : ( +

+ Nenhum log de banco de dados disponível para este cliente. +

+ )} +
+ +
+ +
); } diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..fd3a406 --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,46 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: + "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : "span" + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx new file mode 100644 index 0000000..8e4fa13 --- /dev/null +++ b/src/components/ui/scroll-area.tsx @@ -0,0 +1,58 @@ +"use client" + +import * as React from "react" +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" + +import { cn } from "@/lib/utils" + +function ScrollArea({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + + ) +} + +function ScrollBar({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { ScrollArea, ScrollBar } diff --git a/src/packages/administrativo/data/Log/LogDatabaseData.ts b/src/packages/administrativo/data/Log/LogDatabaseData.ts new file mode 100644 index 0000000..1185c3b --- /dev/null +++ b/src/packages/administrativo/data/Log/LogDatabaseData.ts @@ -0,0 +1,31 @@ +'use server' +// Indica que este módulo será executado no lado do servidor (Database Action do Next.js) + +import { Methods } from '@/shared/services/api/enums/ApiMethodEnum'; +// Importa o enumerador que contém os métodos HTTP padronizados (GET, POST, PUT, DELETE) + +import API from '@/shared/services/api/Api'; +// Importa a classe responsável por realizar requisições HTTP à API backend + +import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler'; +// Importa o wrapper que padroniza o tratamento de erros e respostas para o cliente + +// Função principal responsável por buscar um usuário específico pelo seu ID +async function executeLogDatabaseData(client_id: number) { + + // Cria uma nova instância da classe de comunicação com a API + const api = new API(); + + // Envia uma requisição GET ao endpoint que retorna os dados de um usuário específico + const response = await api.send({ + 'method': Methods.GET, // Define o método HTTP da requisição + 'endpoint': `administrativo/log/database/${client_id}` // Monta dinamicamente o endpoint com o ID do usuário + }); + + // Retorna a resposta recebida da API (dados do usuário ou erro) + return response; +} + +// Exporta a função encapsulada com o handler de erro +// Isso garante que exceções sejam tratadas de forma padronizada na camada superior +export const LogDatabaseData = withClientErrorHandler(executeLogDatabaseData); diff --git a/src/packages/administrativo/data/Log/LogGedData.ts b/src/packages/administrativo/data/Log/LogGedData.ts new file mode 100644 index 0000000..24bbb3e --- /dev/null +++ b/src/packages/administrativo/data/Log/LogGedData.ts @@ -0,0 +1,31 @@ +'use server' +// Indica que este módulo será executado no lado do servidor (Ged Action do Next.js) + +import { Methods } from '@/shared/services/api/enums/ApiMethodEnum'; +// Importa o enumerador que contém os métodos HTTP padronizados (GET, POST, PUT, DELETE) + +import API from '@/shared/services/api/Api'; +// Importa a classe responsável por realizar requisições HTTP à API backend + +import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler'; +// Importa o wrapper que padroniza o tratamento de erros e respostas para o cliente + +// Função principal responsável por buscar um usuário específico pelo seu ID +async function executeLogGedData(client_id: number) { + + // Cria uma nova instância da classe de comunicação com a API + const api = new API(); + + // Envia uma requisição GET ao endpoint que retorna os dados de um usuário específico + const response = await api.send({ + 'method': Methods.GET, // Define o método HTTP da requisição + 'endpoint': `administrativo/log/ged/${client_id}` // Monta dinamicamente o endpoint com o ID do usuário + }); + + // Retorna a resposta recebida da API (dados do usuário ou erro) + return response; +} + +// Exporta a função encapsulada com o handler de erro +// Isso garante que exceções sejam tratadas de forma padronizada na camada superior +export const LogGedData = withClientErrorHandler(executeLogGedData); diff --git a/src/packages/administrativo/hooks/Log/useLogDatabaseHook.ts b/src/packages/administrativo/hooks/Log/useLogDatabaseHook.ts new file mode 100644 index 0000000..bba0cb9 --- /dev/null +++ b/src/packages/administrativo/hooks/Log/useLogDatabaseHook.ts @@ -0,0 +1,23 @@ +'use client'; + +import { useState } from 'react'; +import { LogDatabaseInterface } from '../../interfaces/Log/LogDatabaseInterface'; +import { LogDatabaseService } from '../../services/Log/LogDatabaseService'; +import { useResponse } from '@/shared/components/response/ResponseContext'; + +export const useLogDatabaseHook = () => { + const { setResponse } = useResponse(); + const [logDatabase, setLog] = useState(null); + + const fetchLogDatabase = async (client_id: number) => { + try { + const response = await LogDatabaseService(client_id); + setLog(response as LogDatabaseInterface); + setResponse(response); + } catch (error) { + console.error("Erro ao buscar informação do banco de dados:", error); + } + }; + + return { logDatabase, fetchLogDatabase }; +}; diff --git a/src/packages/administrativo/hooks/Log/useLogGedHook.ts b/src/packages/administrativo/hooks/Log/useLogGedHook.ts new file mode 100644 index 0000000..c9fd794 --- /dev/null +++ b/src/packages/administrativo/hooks/Log/useLogGedHook.ts @@ -0,0 +1,23 @@ +'use client'; + +import { useState } from 'react'; +import { LogGedInterface } from '../../interfaces/Log/LogGedInterface'; +import { LogGedService } from '../../services/Log/LogGedService'; +import { useResponse } from '@/shared/components/response/ResponseContext'; + +export const useLogGedHook = () => { + const { setResponse } = useResponse(); + const [logGed, setLog] = useState(null); + + const fetchLogGed = async (client_id: number) => { + try { + const response = await LogGedService(client_id); + setLog(response as LogGedInterface); + setResponse(response); + } catch (error) { + console.error("Erro ao buscar informação do banco de dados:", error); + } + }; + + return { logGed, fetchLogGed }; +}; diff --git a/src/packages/administrativo/hooks/Log/useLogServerHook.ts b/src/packages/administrativo/hooks/Log/useLogServerHook.ts index c7a031c..b9f8710 100644 --- a/src/packages/administrativo/hooks/Log/useLogServerHook.ts +++ b/src/packages/administrativo/hooks/Log/useLogServerHook.ts @@ -8,15 +8,12 @@ import { useResponse } from '@/shared/components/response/ResponseContext'; export const useLogServerHook = () => { const { setResponse } = useResponse(); - const [log, setLog] = useState(null); + const [logServer, setLog] = useState(null); const fetchLogServer = async (client_id: number) => { try { const response = await LogServerService(client_id); - - console.log(response) - setLog(response.data); setResponse(response); } catch (error) { @@ -26,5 +23,5 @@ export const useLogServerHook = () => { } }; - return { log, fetchLogServer }; + return { logServer, fetchLogServer }; }; \ No newline at end of file diff --git a/src/packages/administrativo/interfaces/Log/LogDatabaseInterface.ts b/src/packages/administrativo/interfaces/Log/LogDatabaseInterface.ts new file mode 100644 index 0000000..5d6b8ee --- /dev/null +++ b/src/packages/administrativo/interfaces/Log/LogDatabaseInterface.ts @@ -0,0 +1,25 @@ +/** + * Interface que representa o log de banco de dados retornado pelo endpoint /log/database. + */ +export interface LogDatabaseInterface { + message?: string; + data: { + cns: string; + cartorio: string; + data: string; + hora: string; + database: { + partition: { + unit: string; + percent_free: number; + percent_used: number; + total_disk_space: string; + total_free_disk_space: string; + total_used_disk_space: string; + }; + file_size_mb: string; + db_accessible: boolean; + last_modified: string; + }; + }; +} diff --git a/src/packages/administrativo/interfaces/Log/LogGedInterface.ts b/src/packages/administrativo/interfaces/Log/LogGedInterface.ts new file mode 100644 index 0000000..ac3f54a --- /dev/null +++ b/src/packages/administrativo/interfaces/Log/LogGedInterface.ts @@ -0,0 +1,28 @@ +/** + * Interface que representa o log de banco de dados retornado pelo endpoint /log/database. + */ +export interface LogDatabaseInterface { + message?: string; + data: { + cns: string; + cartorio: string; + data: string; + hora: string; + ged: { + partition: { + unit: string; + percent_free: number; + percent_used: number; + total_disk_space: string; + total_free_disk_space: string; + total_used_disk_space: string; + }; + arquivos: [{ + path: string; + data: string; + hora: string; + quantidade: number; + }] + }; + }; +} diff --git a/src/packages/administrativo/interfaces/Log/LogInterface.ts b/src/packages/administrativo/interfaces/Log/LogInterface.ts index 62ee0eb..8c0057a 100644 --- a/src/packages/administrativo/interfaces/Log/LogInterface.ts +++ b/src/packages/administrativo/interfaces/Log/LogInterface.ts @@ -7,4 +7,9 @@ export interface LogInterface { client_id: number; // ID do cliente relacionado (chave estrangeira obrigatória) date_post?: string; // Data e hora do registro (gerada automaticamente pelo banco) file: object; // Dados em formato JSON (ex: informações do arquivo ou operação) + cartorio?: string; + server?: string; + cns?: string; + data?: string; + hora?: string; } diff --git a/src/packages/administrativo/interfaces/Log/LogServerInterface.ts b/src/packages/administrativo/interfaces/Log/LogServerInterface.ts new file mode 100644 index 0000000..1877e8b --- /dev/null +++ b/src/packages/administrativo/interfaces/Log/LogServerInterface.ts @@ -0,0 +1,36 @@ +/** + * Interface que representa o log de servidor retornado pelo endpoint /log/server. + */ +export interface LogServerInterface { + message?: string; + data: { + cns: string; + cartorio: string; + data: string; + hora: string; + server: { + operacional_system: { + so: string; + versao: string; + release: string; + arquitetura: string; + }; + memory: { + total_MB: number; + usada_MB: number; + livre_MB: number; + buffer_MB: number; + swap_total_MB: number; + swap_usada_MB: number; + }; + cpu: Array<{ + núcleo: string; + modelo: string; + clock_mhz: number; + cache: string; + threads: number; + núcleos: number; + }>; + }; + }; +} diff --git a/src/packages/administrativo/services/Log/LogDatabaseService.ts b/src/packages/administrativo/services/Log/LogDatabaseService.ts new file mode 100644 index 0000000..e9f9ac6 --- /dev/null +++ b/src/packages/administrativo/services/Log/LogDatabaseService.ts @@ -0,0 +1,22 @@ +'use server' +// Indica que este arquivo é um "Database Action", executado no lado do servidor pelo Next.js + +import { withClientErrorHandler } from "@/withClientErrorHandler/withClientErrorHandler"; +// Importa o wrapper responsável por padronizar o tratamento de erros nas requisições do Loge + +import { LogDatabaseData } from "../../data/Log/LogDatabaseData"; +// Importa a função que acessa a camada de dados e retorna as informações do usuário a partir do ID + +// Função assíncrona principal responsável por buscar um usuário pelo seu ID +async function executeLogDatabaseService(client_id: number) { + + // Executa a função de busca de usuário, passando o ID recebido como parâmetro + const response = await LogDatabaseData(client_id); + + // Retorna a resposta vinda da camada de dados (usuário encontrado ou erro) + return response; +} + +// Exporta o serviço com o tratamento de erros encapsulado +// O wrapper "withClientErrorHandler" assegura respostas consistentes em caso de falhas +export const LogDatabaseService = withClientErrorHandler(executeLogDatabaseService); diff --git a/src/packages/administrativo/services/Log/LogGedService.ts b/src/packages/administrativo/services/Log/LogGedService.ts new file mode 100644 index 0000000..d938859 --- /dev/null +++ b/src/packages/administrativo/services/Log/LogGedService.ts @@ -0,0 +1,22 @@ +'use server' +// Indica que este arquivo é um "Ged Action", executado no lado do servidor pelo Next.js + +import { withClientErrorHandler } from "@/withClientErrorHandler/withClientErrorHandler"; +// Importa o wrapper responsável por padronizar o tratamento de erros nas requisições do Loge + +import { LogGedData } from "../../data/Log/LogGedData"; +// Importa a função que acessa a camada de dados e retorna as informações do usuário a partir do ID + +// Função assíncrona principal responsável por buscar um usuário pelo seu ID +async function executeLogGedService(client_id: number) { + + // Executa a função de busca de usuário, passando o ID recebido como parâmetro + const response = await LogGedData(client_id); + + // Retorna a resposta vinda da camada de dados (usuário encontrado ou erro) + return response; +} + +// Exporta o serviço com o tratamento de erros encapsulado +// O wrapper "withClientErrorHandler" assegura respostas consistentes em caso de falhas +export const LogGedService = withClientErrorHandler(executeLogGedService); diff --git a/src/shared/components/charts/PartitionBarChart.tsx b/src/shared/components/charts/PartitionBarChart.tsx new file mode 100644 index 0000000..1c0b4f7 --- /dev/null +++ b/src/shared/components/charts/PartitionBarChart.tsx @@ -0,0 +1,40 @@ +'use client'; + +import { ResponsiveContainer, BarChart, Bar, XAxis, Tooltip, Cell } from 'recharts'; + +interface PartitionBarChartProps { + used: number; + free: number; +} + +export function PartitionBarChart({ used, free }: PartitionBarChartProps) { + // Estrutura dos dados do gráfico + const barData = [ + { name: 'Usado', value: used }, + { name: 'Livre', value: free }, + ]; + + return ( +
+ + + {/* Eixo X com rótulos (Usado / Livre) */} + + + {/* Tooltip com formatação percentual */} + `${value.toFixed(2)}%`} /> + + {/* Barras com cores diferentes para "Usado" e "Livre" */} + + {barData.map((entry, index) => ( + + ))} + + + +
+ ); +} diff --git a/src/shared/components/charts/PartitionPieChart.tsx b/src/shared/components/charts/PartitionPieChart.tsx new file mode 100644 index 0000000..be8af2f --- /dev/null +++ b/src/shared/components/charts/PartitionPieChart.tsx @@ -0,0 +1,71 @@ +'use client'; + +import { ResponsiveContainer, PieChart, Pie, Tooltip, Cell } from 'recharts'; + +/** + * Função auxiliar que retorna um gráfico de pizza pronto + * para exibição no CardContent. + * + * @param partition - Objeto contendo os dados da partição + * (percent_used, percent_free, total_used_disk_space, total_free_disk_space) + */ +export function renderPartitionPieChart(partition: any) { + // Verifica se há dados válidos + if (!partition) { + return ( +

+ Nenhuma informação de partição disponível. +

+ ); + } + + // Estrutura de dados para o gráfico de pizza + const pieData = [ + { name: 'Usado', value: partition.percent_used }, + { name: 'Livre', value: partition.percent_free }, + ]; + + // Paleta de cores das fatias (vermelho e verde) + const COLORS = ['#ef4444', '#22c55e']; + + // Retorna o gráfico renderizado pronto para o CardContent + return ( +
+ {/* Container responsivo do gráfico */} +
+ + + `${name}: ${value.toFixed(1)}%`} + > + {pieData.map((entry, index) => ( + + ))} + + [`${value.toFixed(2)}%`, name]} + /> + + +
+ + {/* Legenda abaixo do gráfico */} +
+
+ + Usado ({partition.total_used_disk_space}) +
+
+ + Livre ({partition.total_free_disk_space}) +
+
+
+ ); +} diff --git a/src/shared/utils/convertMBtoGB.ts b/src/shared/utils/convertMBtoGB.ts new file mode 100644 index 0000000..744197e --- /dev/null +++ b/src/shared/utils/convertMBtoGB.ts @@ -0,0 +1,21 @@ +/** + * Converte valor de megabytes (MB) para gigabytes (GB). + * Aceita valores numéricos ou strings com separadores e unidades. + * Exemplo: "22.380.281 GB" ou "22380281 MB" + */ +export function convertMBtoGB(value: string | number): string { + if (!value) return '-'; + + // Extrai apenas dígitos e pontos da string + const numericValue = parseFloat( + String(value).replace(/[^\d.]/g, '').replace(/\.(?=.*\.)/g, '') + ); + + if (isNaN(numericValue)) return '-'; + + // Converte MB → GB (1 GB = 1024 MB) + const gbValue = numericValue / 1024; + + // Arredonda para 2 casas decimais + return `${gbValue.toFixed(2)} GB`; +} diff --git a/src/shared/utils/formatDateTime.ts b/src/shared/utils/formatDateTime.ts new file mode 100644 index 0000000..ec38518 --- /dev/null +++ b/src/shared/utils/formatDateTime.ts @@ -0,0 +1,16 @@ +/** + * Formata uma string no formato "YYYY-MM-DD HH:mm:ss" + * para "DD/MM/YYYY" e "HH:mm". + */ +export function formatDateTime(datetimeString: string) { + if (!datetimeString) return { formattedDate: '-', formattedTime: '-' }; + + const [datePart, timePart] = datetimeString.split(' '); + const [year, month, day] = datePart.split('-'); + + // Exemplo: 2025-11-04 09:00:01 → 04/11/2025 às 09:00 + return { + formattedDate: `${day}/${month}/${year}`, + formattedTime: timePart ? timePart.substring(0, 5) : '' + }; +} diff --git a/src/shared/utils/formatLogDateTime.ts b/src/shared/utils/formatLogDateTime.ts new file mode 100644 index 0000000..d663c57 --- /dev/null +++ b/src/shared/utils/formatLogDateTime.ts @@ -0,0 +1,39 @@ +import { format } from "date-fns"; +import { ptBR } from "date-fns/locale"; + +/** + * Formata os campos "data" e "hora" de um log no padrão brasileiro + * e indica se o log está desatualizado em relação à data/hora atual. + * + * @param data - Data no formato "YYYY-MM-DD" + * @param hora - Hora no formato "HH:mm:ss" + * @returns Objeto com data/hora formatadas e flag de desatualização + */ +export function formatLogDateTime(data?: string, hora?: string) { + // Combina data e hora em um formato ISO válido (ex: "2025-11-04T09:00:01") + const combinedDateTime = data && hora ? `${data}T${hora}` : null; + + // Cria o objeto Date com base no datetime combinado + const logDate = combinedDateTime ? new Date(combinedDateTime) : null; + + // Evita erros se a data for inválida + const isValid = logDate && !isNaN(logDate.getTime()); + + // Formata data e hora no padrão brasileiro (dd/MM/yyyy e HH:mm) + const formattedDate = isValid + ? format(logDate!, "dd/MM/yyyy", { locale: ptBR }) + : "Data inválida"; + + const formattedTime = isValid + ? format(logDate!, "HH:mm", { locale: ptBR }) + : "--:--"; + + // Verifica se o log está desatualizado (anterior à data/hora atual) + const isOutdated = isValid ? logDate! < new Date() : false; + + return { + formattedDate, // Ex: "04/11/2025" + formattedTime, // Ex: "09:00" + isOutdated, // true se a data/hora for anterior à atual + }; +}