From 46ccbd2b5303acde2f13b7f611edb832eb81a224 Mon Sep 17 00:00:00 2001 From: Kenio de Souza Date: Mon, 10 Nov 2025 10:32:55 -0300 Subject: [PATCH] =?UTF-8?q?feat():=20Cria=C3=A7=C3=A3o=20Dashboard=20clien?= =?UTF-8?q?tes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 32 +- package.json | 1 + .../(client)/clientes/[id]/page.tsx | 870 +++++++++++------- src/components/app-sidebar.tsx | 1 + .../administrativo/data/Log/LogBackupData.ts | 31 + .../administrativo/data/Log/LogDiskData.ts | 31 + .../hooks/Log/useLogBackupHook.ts | 35 + .../hooks/Log/useLogDiskHook.ts | 23 + .../interfaces/Log/LogBackupInterface.ts | 23 + .../interfaces/Log/LogDiskInterface.ts | 20 + .../services/Log/LogBackupService.ts | 22 + .../services/Log/LogDiskService.ts | 22 + .../components/charts/PartitionBarChart.tsx | 17 + .../charts/PartitionBarChartDisk.tsx | 82 ++ .../charts/PartitionPieChartDisk.tsx | 92 ++ .../hooks/auth/useGUsuarioGetJWTHook.ts | 33 +- src/shared/utils/formatDateTime.ts | 46 +- src/shared/utils/formatLogDateTime.ts | 34 +- 18 files changed, 1022 insertions(+), 393 deletions(-) create mode 100644 src/packages/administrativo/data/Log/LogBackupData.ts create mode 100644 src/packages/administrativo/data/Log/LogDiskData.ts create mode 100644 src/packages/administrativo/hooks/Log/useLogBackupHook.ts create mode 100644 src/packages/administrativo/hooks/Log/useLogDiskHook.ts create mode 100644 src/packages/administrativo/interfaces/Log/LogBackupInterface.ts create mode 100644 src/packages/administrativo/interfaces/Log/LogDiskInterface.ts create mode 100644 src/packages/administrativo/services/Log/LogBackupService.ts create mode 100644 src/packages/administrativo/services/Log/LogDiskService.ts create mode 100644 src/shared/components/charts/PartitionBarChartDisk.tsx create mode 100644 src/shared/components/charts/PartitionPieChartDisk.tsx diff --git a/package-lock.json b/package-lock.json index 8dc307a..13e751c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "date-fns": "^4.1.0", "faker-js": "^1.0.0", "input-otp": "^1.4.2", + "jose": "^6.1.1", "js-cookie": "^3.0.5", "jsonwebtoken": "^9.0.2", "jwt-decode": "^4.0.0", @@ -2704,7 +2705,6 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -2715,7 +2715,6 @@ "integrity": "sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3243,7 +3242,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4343,7 +4341,6 @@ "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", @@ -4428,7 +4425,6 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -4530,7 +4526,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -6021,6 +6016,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.1.tgz", + "integrity": "sha512-GWSqjfOPf4cWOkBzw5THBjtGPhXKqYnfRBzh4Ni+ArTrQQ9unvmsA3oFLqaYKoKe5sjWmGu5wVKg9Ft1i+LQfg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-cookie": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", @@ -6665,7 +6669,6 @@ "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", @@ -7075,7 +7078,6 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -7233,7 +7235,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -7243,7 +7244,6 @@ "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" }, @@ -7256,7 +7256,6 @@ "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" }, @@ -7272,8 +7271,7 @@ "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", - "peer": true + "license": "MIT" }, "node_modules/react-masked-text": { "version": "1.0.5", @@ -7286,7 +7284,6 @@ "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" @@ -7405,8 +7402,7 @@ "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 + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -8200,7 +8196,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8212,8 +8207,7 @@ "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", - "peer": true + "license": "GPL-2.0-or-later" }, "node_modules/to-regex-range": { "version": "5.0.1", diff --git a/package.json b/package.json index ffc9aaf..5042a7c 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "date-fns": "^4.1.0", "faker-js": "^1.0.0", "input-otp": "^1.4.2", + "jose": "^6.1.1", "js-cookie": "^3.0.5", "jsonwebtoken": "^9.0.2", "jwt-decode": "^4.0.0", diff --git a/src/app/(protected)/administrativo/(client)/clientes/[id]/page.tsx b/src/app/(protected)/administrativo/(client)/clientes/[id]/page.tsx index e23d318..818ea63 100644 --- a/src/app/(protected)/administrativo/(client)/clientes/[id]/page.tsx +++ b/src/app/(protected)/administrativo/(client)/clientes/[id]/page.tsx @@ -1,5 +1,6 @@ 'use client'; +import React from "react"; import { useParams } from 'next/navigation'; import { useEffect } from 'react'; @@ -12,13 +13,16 @@ import { convertMBtoGB } from '@/shared/utils/convertMBtoGB'; import { useLogServerHook } from '@/packages/administrativo/hooks/Log/useLogServerHook'; import { useLogDatabaseHook } from '@/packages/administrativo/hooks/Log/useLogDatabaseHook'; import { useLogGedHook } from '@/packages/administrativo/hooks/Log/useLogGedHook'; +import { useLogDiskHook } from '@/packages/administrativo/hooks/Log/useLogDiskHook'; +import { useLogBackupHook } from '@/packages/administrativo/hooks/Log/useLogBackupHook'; // Componentes do Shadcn/UI import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; import { - Table, TableHeader, TableRow, TableHead, TableCell, TableBody + Table, TableHeader, TableRow, TableHead, TableCell, TableBody, + TableFooter } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; import { ScrollArea } from "@/components/ui/scroll-area"; @@ -27,6 +31,8 @@ import { } from "@/components/ui/tabs"; import { PartitionBarChart } from '@/shared/components/charts/PartitionBarChart'; import { renderPartitionPieChart } from '@/shared/components/charts/PartitionPieChart'; +import { renderDiskPieChartsFromJson } from '@/shared/components/charts/PartitionPieChartDisk'; +import { renderDiskBarChartsFromJson } from '@/shared/components/charts/PartitionBarChartDisk'; export default function ClientePage() { const { id } = useParams(); @@ -35,6 +41,8 @@ export default function ClientePage() { const { logServer, fetchLogServer } = useLogServerHook(); const { logDatabase, fetchLogDatabase } = useLogDatabaseHook(); const { logGed, fetchLogGed } = useLogGedHook(); + const { logDisk, fetchLogDisk } = useLogDiskHook(); + const { logBackup, fetchLogBackup } = useLogBackupHook(); // Efeito responsável por buscar logs de forma sequencial useEffect(() => { @@ -73,6 +81,29 @@ export default function ClientePage() { await fetchLogGed(Number(id)); } catch (error) { console.error("Erro ao buscar log do GED:", error); + + } finally { + // E SOMENTE após a conclusão da busca do GED + // (mesmo que dê erro ou traga 0 registros), + // executa a busca no Disk + try { + + await fetchLogDisk(Number(id)); + } catch (error) { + console.error("Erro ao buscar log do Disk:", error); + + } finally { + // E SOMENTE após a conclusão da busca do Disk + // (mesmo que dê erro ou traga 0 registros), + // executa a busca no Backup + try { + + await fetchLogBackup(Number(id)); + } catch (error) { + console.error("Erro ao buscar log do Backup:", error); + + } + } } } } @@ -81,15 +112,6 @@ export default function ClientePage() { fetchSequentially(); }, [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, @@ -102,257 +124,266 @@ export default function ClientePage() { return (

- {logServer.cns} - {logServer.cartorio} + {logServer?.cns ?? 'CNS não disponível'} - {logServer?.cartorio ?? 'Cartório não disponível'}

- + Informações do Servidor Banco de Dados GED - Configurações + Unidades de Disco + Backup {/* ===================================================== */} {/* Aba: Informação do servidor */} {/* ===================================================== */} -
- {/* Badge com data e alerta */} -
- - Data do log: {formattedDate} às {formattedTime} + {logServer ? ( +
+ {/* Badge com data e alerta */} +
+ + Data do log: {formattedDate} às {formattedTime} - {isOutdated && ( - - - Atenção: Log do servidor desatualizado - - )} + {isOutdated && ( + + - Atenção: Log do servidor desatualizado + + )} - + -
+
- {/* Linha com dois cards lado a lado */} -
- {/* Sistema Operacional */} + {/* 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 */} - Sistema Operacional + Processadores -
-

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} + +
+ + + 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} + + ))} + + + + + +
+ ) : ( +

+ Nenhum log do servidor disponível para este cliente. +

+ )}
{/* ===================================================== */} {/* 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); + {/* 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 ( - <> + 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 ${ - isOutdated - ? "bg-yellow-200 text-yellow-800 border-yellow-400" - : "" + (logDatabase.data.database?.partition?.percent_used || 0) > 80 + ? "bg-red-200 text-red-800 border-red-400" + : "bg-green-100 text-green-800 border-green-400" }`} > - Data do log: {formattedDate} às {formattedTime} - - {isOutdated && ( - - - Atenção: Log do banco de dados desatualizado - - )} + {Number(logDatabase.data.database?.partition?.percent_used || 0).toFixed(2)}% - - ); - })()} -

+

+
+ + - {/* 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 + + )} +

- {/* 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} +

+ ); + })()} - {/* 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/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 ; + })()} +
+
- {/* 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. -

- )} +
- + ) : ( +

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

+ )} @@ -360,135 +391,304 @@ export default function ClientePage() { {/* 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 ( - <> + {/* 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 ${ - isOutdated - ? "bg-yellow-200 text-yellow-800 border-yellow-400" - : "" + 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" }`} > - Data do log: {formattedDate} às {formattedTime} - - {isOutdated && ( - - - Atenção: Log do GED desatualizado - - )} + {logGed.data.ged?.partition.percent_used.toFixed(2)}% - - ); - })()} -

+

+
+ + - {/* 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 + {/* 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} - - - {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/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. -

- )} + {/* 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 GED disponível para este cliente. +

+ )} + + {/* ===================================================== */} + {/* Aba: Informação do disco do servidor */} + {/* ===================================================== */} + + {logDisk?.data ? ( +
+ + {/* Badge com data e hora do log do disco */} +
+ {(() => { + const { formattedDate, formattedTime, isOutdated } = + formatLogDateTime(logDisk.data.data, logDisk.data.hora); + + return ( + + Data do log: {formattedDate} às {formattedTime} + {isOutdated && ( + - Atenção: Log do disco desatualizado + )} + + ); + })()} +
+ + {/* Tabela com as partições/discos encontrados */} + + + Unidades de Disco + + + + + + + Unidade + Capacidade + Disponível + Utilizado + Disponível (%) + + + + {Object.entries(logDisk.data.disk || {}).map(([unit, info]: any, index) => ( + + {unit} + {convertMBtoGB(info.capacidade)} + {convertMBtoGB(info.disponivel)} + {convertMBtoGB(info.utilizados)} + + + {info.disponivel_percentual}% + + + + ))} + +
+
+
+
+ + {Object.entries(logDisk.data.disk || {}).map(([unit, info]: any, index) => ( +
+ {/* Card: Gráfico de Pizza mostrando espaço usado vs livre */} + {renderDiskPieChartsFromJson(logDisk.data.disk)} + + {/* Card: Gráfico de Barras mostrando uso da partição */} + {renderDiskBarChartsFromJson(logDisk.data.disk)} +
+ ))} + + +
+ ) : ( +

+ Nenhum log do disco(s) disponível para este cliente. +

+ )} +
+ + + {/* ===================================================== */} + {/* Aba: Informação do servidor */} + {/* ===================================================== */} + + {logBackup?.data ? ( +
+ {/* Badge com data do backup */} +
+ {(() => { + const { formattedDate, formattedTime, isOutdated } = + formatLogDateTime(logBackup?.data, logBackup?.hora); + + return ( + + Data do log: {formattedDate} às {formattedTime} + {isOutdated && - Atenção: Log do Backup desatualizado} + + ); + })()} +
+ + {/* Card com tabela de backups */} + + + Arquivos de Backup + + + + + + + Arquivo + Dia + Data + Hora + Caminho + Período (dias) + Tamanho + + + + {Object.entries(logBackup?.backup || {}).map(([fileName, details]) => ( + + {fileName} + {details?.dia} + {details?.data} + {details?.hora} + {details?.caminho} + {details?.periodo} + {details?.tamanho} + + ))} + +
+
+ Total de arquivos: {Object.keys(logBackup?.backup || {}).length} +
+
+
+ ) : ( +

+ Nenhum log de backup disponível para este cliente. +

+ )} +
+ +
); diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx index 34f3495..d2fa314 100644 --- a/src/components/app-sidebar.tsx +++ b/src/components/app-sidebar.tsx @@ -58,6 +58,7 @@ const data = { export function AppSidebar({ ...props }: React.ComponentProps) { const { userAuthenticated } = useGUsuarioGetJWTHook(); + console.log('LOGADO', userAuthenticated) return ( diff --git a/src/packages/administrativo/data/Log/LogBackupData.ts b/src/packages/administrativo/data/Log/LogBackupData.ts new file mode 100644 index 0000000..a2e3d69 --- /dev/null +++ b/src/packages/administrativo/data/Log/LogBackupData.ts @@ -0,0 +1,31 @@ +'use server' +// Indica que este módulo será executado no lado do servidor (Backup 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 executeLogBackupData(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/backup/${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 LogBackupData = withClientErrorHandler(executeLogBackupData); diff --git a/src/packages/administrativo/data/Log/LogDiskData.ts b/src/packages/administrativo/data/Log/LogDiskData.ts new file mode 100644 index 0000000..bb4a2cb --- /dev/null +++ b/src/packages/administrativo/data/Log/LogDiskData.ts @@ -0,0 +1,31 @@ +'use server' +// Indica que este módulo será executado no lado do servidor (Disk 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 executeLogDiskData(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/disk/${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 LogDiskData = withClientErrorHandler(executeLogDiskData); diff --git a/src/packages/administrativo/hooks/Log/useLogBackupHook.ts b/src/packages/administrativo/hooks/Log/useLogBackupHook.ts new file mode 100644 index 0000000..3b4af10 --- /dev/null +++ b/src/packages/administrativo/hooks/Log/useLogBackupHook.ts @@ -0,0 +1,35 @@ +'use client'; + +import { useState } from 'react'; +import { LogBackupInterface } from '../../interfaces/Log/LogBackupInterface'; +import { LogBackupService } from '../../services/Log/LogBackupService'; +import { useResponse } from '@/shared/components/response/ResponseContext'; + +export const useLogBackupHook = () => { + const { setResponse } = useResponse(); + + // Estado tipado para armazenar apenas os dados reais de log + const [logBackup, setLog] = useState(null); + + const fetchLogBackup = async (client_id: number) => { + try { + const response = await LogBackupService(client_id); + + // Verifica se a API retorna no formato esperado + //console.log(' Resposta bruta do LogBackupService:', response); + + // Se a estrutura for { success, message, data }, use response.data + const logData = + response?.data && response.data.backup ? response.data : response; + + setLog(logData); // Armazena só a parte relevante + setResponse(response); // Mantém o contexto global + + //console.log(' LogBackup armazenado no estado:', logData); + } catch (error) { + console.error(' Erro ao buscar informação do servidor por ID:', error); + } + }; + + return { logBackup, fetchLogBackup }; +}; diff --git a/src/packages/administrativo/hooks/Log/useLogDiskHook.ts b/src/packages/administrativo/hooks/Log/useLogDiskHook.ts new file mode 100644 index 0000000..b1eed4a --- /dev/null +++ b/src/packages/administrativo/hooks/Log/useLogDiskHook.ts @@ -0,0 +1,23 @@ +'use client'; + +import { useState } from 'react'; +import { LogDiskInterface } from '../../interfaces/Log/LogDiskInterface'; +import { LogDiskService } from '../../services/Log/LogDiskService'; +import { useResponse } from '@/shared/components/response/ResponseContext'; + +export const useLogDiskHook = () => { + const { setResponse } = useResponse(); + const [logDisk, setLog] = useState(null); + + const fetchLogDisk = async (client_id: number) => { + try { + const response = await LogDiskService(client_id); + setLog(response as LogDiskInterface); + setResponse(response); + } catch (error) { + console.error("Erro ao buscar informação do banco de dados:", error); + } + }; + + return { logDisk, fetchLogDisk }; +}; diff --git a/src/packages/administrativo/interfaces/Log/LogBackupInterface.ts b/src/packages/administrativo/interfaces/Log/LogBackupInterface.ts new file mode 100644 index 0000000..92a11f6 --- /dev/null +++ b/src/packages/administrativo/interfaces/Log/LogBackupInterface.ts @@ -0,0 +1,23 @@ +/** + * Interface que representa o log de backup retornado pelo endpoint /log/backup. + */ +export interface LogBackupInterface { + message?: string; // Mensagem geral de status + data: { + cns: string; // Código CNS do cartório + cartorio: string; // Nome do cartório + data: string; // Data do log + hora: string; // Hora do log + backup: { + // Cada chave do objeto backup representa o nome de um arquivo ZIP + [arquivo: string]: { + dia: string; // Dia da semana em que o backup foi feito + data: string; // Data completa do backup (formato dd/mm/yyyy) + hora: string; // Hora da execução do backup + caminho: string; // Caminho onde o arquivo está armazenado + periodo: number; // Quantidade de dias desde o backup + tamanho: string; // Tamanho do arquivo com unidade (ex: "240.388 MB") + }; + }; + }; +} diff --git a/src/packages/administrativo/interfaces/Log/LogDiskInterface.ts b/src/packages/administrativo/interfaces/Log/LogDiskInterface.ts new file mode 100644 index 0000000..c8d3636 --- /dev/null +++ b/src/packages/administrativo/interfaces/Log/LogDiskInterface.ts @@ -0,0 +1,20 @@ +/** + * Interface que representa o log de banco de dados retornado pelo endpoint /log/database. + */ +export interface LogDiskInterface { + message?: string; + data: { + cns: string; + cartorio: string; + data: string; + hora: string; + disk: { + [diskName: string]: { // cada chave é o identificador do disco, ex: "//", "C:", "D:" + capacidade: string; + disponivel: string; + utilizados: string; + disponivel_percentual: number; + }; + }; + }; +} diff --git a/src/packages/administrativo/services/Log/LogBackupService.ts b/src/packages/administrativo/services/Log/LogBackupService.ts new file mode 100644 index 0000000..d43e951 --- /dev/null +++ b/src/packages/administrativo/services/Log/LogBackupService.ts @@ -0,0 +1,22 @@ +'use server' +// Indica que este arquivo é um "Backup 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 { LogBackupData } from "../../data/Log/LogBackupData"; +// 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 executeLogBackupService(client_id: number) { + + // Executa a função de busca de usuário, passando o ID recebido como parâmetro + const response = await LogBackupData(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 LogBackupService = withClientErrorHandler(executeLogBackupService); diff --git a/src/packages/administrativo/services/Log/LogDiskService.ts b/src/packages/administrativo/services/Log/LogDiskService.ts new file mode 100644 index 0000000..975124b --- /dev/null +++ b/src/packages/administrativo/services/Log/LogDiskService.ts @@ -0,0 +1,22 @@ +'use server' +// Indica que este arquivo é um "disk 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 { LogDiskData } from "../../data/Log/LogDiskData"; +// 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 executeLogDiskService(client_id: number) { + + // Executa a função de busca de usuário, passando o ID recebido como parâmetro + const response = await LogDiskData(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 LogDiskService = withClientErrorHandler(executeLogDiskService); diff --git a/src/shared/components/charts/PartitionBarChart.tsx b/src/shared/components/charts/PartitionBarChart.tsx index 1c0b4f7..830f725 100644 --- a/src/shared/components/charts/PartitionBarChart.tsx +++ b/src/shared/components/charts/PartitionBarChart.tsx @@ -35,6 +35,23 @@ export function PartitionBarChart({ used, free }: PartitionBarChartProps) { + + {/* Legenda personalizada com os valores reais */} +
+
+ + + Usado {`${used?.toFixed(1)}%`} + +
+
+ + + Livre ({`${free?.toFixed(1)}%`}) + +
+
+
); } diff --git a/src/shared/components/charts/PartitionBarChartDisk.tsx b/src/shared/components/charts/PartitionBarChartDisk.tsx new file mode 100644 index 0000000..13208ce --- /dev/null +++ b/src/shared/components/charts/PartitionBarChartDisk.tsx @@ -0,0 +1,82 @@ +'use client'; + +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; +import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip, Legend } from "recharts"; + +/** + * Função responsável por renderizar um gráfico de barras para cada unidade de disco + * retornada pelo JSON do log. + * + * @param diskData - Objeto contendo as partições (ex: logDisk.data.disk) + */ +export function renderDiskBarChartsFromJson(diskData: any) { + // Caso não exista dado algum + if (!diskData || Object.keys(diskData).length === 0) { + return ( +

+ Nenhum dado de disco disponível. +

+ ); + } + + // Loop sobre cada unidade do JSON (ex: "C:/", "D:/", "//") + return ( + <> + {Object.entries(diskData).map(([unit, info]: [string, any], index) => { + // Calcula percentual usado e livre + const usedPercent = 100 - Number(info.disponivel_percentual); + const freePercent = Number(info.disponivel_percentual); + + // Prepara dados para o gráfico + const barData = [ + { + name: "Uso (%)", + Usado: usedPercent, + Livre: freePercent, + }, + ]; + + return ( + + + Uso da Partição ({unit}) + + +
+ + + + `${v}%`} /> + `${value.toFixed(1)}%`} /> + + + + + +
+ + {/* Legenda personalizada com os valores reais */} +
+
+ + + Usado ({info.utilizados || `${usedPercent.toFixed(1)}%`}) + +
+
+ + + Livre ({info.disponivel || `${freePercent.toFixed(1)}%`}) + +
+
+
+
+ ); + })} + + ); +} diff --git a/src/shared/components/charts/PartitionPieChartDisk.tsx b/src/shared/components/charts/PartitionPieChartDisk.tsx new file mode 100644 index 0000000..08afa56 --- /dev/null +++ b/src/shared/components/charts/PartitionPieChartDisk.tsx @@ -0,0 +1,92 @@ +'use client'; + +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; +import { ResponsiveContainer, PieChart, Pie, Tooltip, Cell } from "recharts"; + +/** + * Função responsável por renderizar um gráfico de pizza para cada unidade de disco + * retornada pelo JSON do log. + * + * @param diskData - Objeto contendo as partições (ex: logDisk.data.disk) + */ +export function renderDiskPieChartsFromJson(diskData: any) { + // Caso não exista dado algum + if (!diskData || Object.keys(diskData).length === 0) { + return ( +

+ Nenhum dado de disco disponível. +

+ ); + } + + // Paleta de cores padrão + const COLORS = ['#ef4444', '#22c55e']; + + // Loop sobre cada unidade do JSON (ex: "C:/", "D:/", "//") + return ( + <> + {Object.entries(diskData).map(([unit, info]: [string, any], index) => { + // Calcula percentual usado (baseado no percentual livre) + const usedPercent = 100 - Number(info.disponivel_percentual); + const freePercent = Number(info.disponivel_percentual); + + // Prepara dados para o gráfico + const pieData = [ + { name: 'Usado', value: usedPercent }, + { name: 'Livre', value: freePercent }, + ]; + + return ( + + + Uso da Partição ({unit}) + + +
+ {/* Container do gráfico */} +
+ + + `${name}: ${value.toFixed(1)}%`} + > + {pieData.map((entry, idx) => ( + + ))} + + [`${value.toFixed(1)}%`, name]} + /> + + +
+ + {/* Legenda personalizada com valores reais */} +
+
+ + + Usado ({info.utilizados || `${usedPercent.toFixed(1)}%`}) + +
+
+ + + Livre ({info.disponivel || `${freePercent.toFixed(1)}%`}) + +
+
+
+
+
+ ); + })} + + ); +} diff --git a/src/shared/hooks/auth/useGUsuarioGetJWTHook.ts b/src/shared/hooks/auth/useGUsuarioGetJWTHook.ts index dbcf567..ee8eed9 100644 --- a/src/shared/hooks/auth/useGUsuarioGetJWTHook.ts +++ b/src/shared/hooks/auth/useGUsuarioGetJWTHook.ts @@ -2,7 +2,6 @@ import { useEffect, useState } from 'react'; import { jwtDecode } from 'jwt-decode'; -import CookiesGet from '../../actions/cookies/CookiesGet'; import GetSigla from '@/shared/actions/text/GetSigla'; import GUsuarioAuthenticatedInterface from '@/shared/interfaces/UserAuthenticatedInterface'; @@ -19,39 +18,37 @@ export default function useGUsuarioGetJWTHook() { useEffect(() => { async function fetchToken() { try { - // Executa a serve action para obtem o token, salvo em cookies - const token = await CookiesGet('access_token'); + // Lê o token diretamente do cookie (lado do cliente) + const token = document.cookie + .split('; ') + .find(row => row.startsWith('access_token=')) + ?.split('=')[1]; - // Verifica se o token esta preenchido + // Se não houver token, apenas sai (sem erro nem redirecionamento) if (!token) { - console.error('Não foi localizado dados dentro do token'); - - // Encerra a aplicação + console.warn('Token ausente ou inválido no cookie.'); return; } - // Decodifica os dados do JWT + // Decodifica o JWT const decoded = jwtDecode(token); - // Se existir campo data e for string, corrige aspas simples + // Se o campo data for string JSON, converte e adiciona sigla if (decoded.data && typeof decoded.data === 'string') { - // Decodifica os dados enviado via json decoded.data = JSON.parse(decoded.data); - - if (decoded.data) { - // Gera Sigla para o nome - decoded.data.sigla = GetSigla(decoded.data.nome || ''); - } } - // Armazena os dados decodificados + if (decoded.data) { + decoded.data.sigla = GetSigla(decoded.data.nome || ''); + } + + // Salva no estado global/local setUserAuthenticated(decoded); } catch (error) { - console.error('Erro ao buscar token', error); + console.error('Erro ao buscar token:', error); } } - // Busca o TOken fetchToken(); }, []); diff --git a/src/shared/utils/formatDateTime.ts b/src/shared/utils/formatDateTime.ts index ec38518..a40e861 100644 --- a/src/shared/utils/formatDateTime.ts +++ b/src/shared/utils/formatDateTime.ts @@ -1,16 +1,52 @@ /** * Formata uma string no formato "YYYY-MM-DD HH:mm:ss" * para "DD/MM/YYYY" e "HH:mm". + * + * Inclui validação para entradas inválidas, nulas ou em formatos diferentes. */ -export function formatDateTime(datetimeString: string) { - if (!datetimeString) return { formattedDate: '-', formattedTime: '-' }; +export function formatDateTime(datetimeString: unknown) { + // 1 Verifica se o valor foi fornecido + if (!datetimeString) { + return { formattedDate: '-', formattedTime: '-' }; + } - const [datePart, timePart] = datetimeString.split(' '); + // 2 Converte para string de forma segura (caso venha como Date, número, etc.) + const value = String(datetimeString).trim(); + + // 3 Se for uma string vazia, retorna padrão + if (value.length === 0) { + return { formattedDate: '-', formattedTime: '-' }; + } + + // 4 Caso o valor seja uma data ISO (ex: "2025-11-09T12:30:00Z") + // ou algo que o construtor Date entenda, tenta converter + if (!value.includes(' ') && value.includes('T')) { + const dateObj = new Date(value); + if (!isNaN(dateObj.getTime())) { + return { + formattedDate: dateObj.toLocaleDateString('pt-BR'), + formattedTime: dateObj.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }) + }; + } + } + + // 5 Tenta separar a data e a hora no formato esperado "YYYY-MM-DD HH:mm:ss" + const parts = value.split(' '); + if (parts.length < 1 || parts[0].split('-').length !== 3) { + return { formattedDate: '-', formattedTime: '-' }; // formato inválido + } + + const [datePart, timePart] = parts; const [year, month, day] = datePart.split('-'); - // Exemplo: 2025-11-04 09:00:01 → 04/11/2025 às 09:00 + // 6 Verifica se os componentes da data são válidos + if (!year || !month || !day || year.length !== 4) { + return { formattedDate: '-', formattedTime: '-' }; + } + + // 7 Retorna a data e hora formatadas return { formattedDate: `${day}/${month}/${year}`, - formattedTime: timePart ? timePart.substring(0, 5) : '' + formattedTime: timePart ? timePart.substring(0, 5) : '-' }; } diff --git a/src/shared/utils/formatLogDateTime.ts b/src/shared/utils/formatLogDateTime.ts index d663c57..8a63659 100644 --- a/src/shared/utils/formatLogDateTime.ts +++ b/src/shared/utils/formatLogDateTime.ts @@ -2,38 +2,40 @@ 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. + * Formata o campo "data" de um log no padrão brasileiro + * e indica se o log está desatualizado em relação à data atual. * * @param data - Data no formato "YYYY-MM-DD" - * @param hora - Hora no formato "HH:mm:ss" + * @param hora - Hora no formato "HH:mm:ss" (opcional, usado apenas para exibir) * @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; + // Cria um objeto Date apenas com a data (ignora a hora) + const logDate = data ? new Date(`${data}T00:00:00`) : 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) + // Formata data no padrão brasileiro (dd/MM/yyyy) const formattedDate = isValid ? format(logDate!, "dd/MM/yyyy", { locale: ptBR }) : "Data inválida"; - const formattedTime = isValid - ? format(logDate!, "HH:mm", { locale: ptBR }) + // Formata hora apenas para exibição (sem influência lógica) + const formattedTime = hora + ? hora.slice(0, 5) // ex: "13:45" : "--:--"; - // Verifica se o log está desatualizado (anterior à data/hora atual) - const isOutdated = isValid ? logDate! < new Date() : false; + // Pega a data atual (zerando a hora para comparar só o dia) + const today = new Date(); + today.setHours(0, 0, 0, 0); + + // Verifica se a data do log é anterior à data atual + const isOutdated = isValid ? logDate! < today : false; return { - formattedDate, // Ex: "04/11/2025" - formattedTime, // Ex: "09:00" - isOutdated, // true se a data/hora for anterior à atual + formattedDate, // Ex: "09/11/2025" + formattedTime, // Ex: "13:00" + isOutdated, // true se a data for anterior à data atual }; }