feat(): Criação Dashboard clientes

This commit is contained in:
Kenio 2025-11-10 10:32:55 -03:00
parent b6a5dffdec
commit 46ccbd2b53
18 changed files with 1022 additions and 393 deletions

32
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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 (
<p className="p-4 text-gray-500">
Nenhum dado encontrado para o cliente {id}.
</p>
);
}
// Formata data e hora do log do servidor
const { formattedDate, formattedTime, isOutdated } = formatLogDateTime(
logServer?.data,
@ -102,257 +124,266 @@ export default function ClientePage() {
return (
<div className="p-4">
<h1 className="text-2xl font-semibold mb-6">
{logServer.cns} - {logServer.cartorio}
{logServer?.cns ?? 'CNS não disponível'} - {logServer?.cartorio ?? 'Cartório não disponível'}
</h1>
<Tabs defaultValue="server" className="w-full">
<TabsList className="grid w-full grid-cols-4">
<TabsList className="grid w-full grid-cols-5">
<TabsTrigger className='cursor-pointer' value="server">Informações do Servidor</TabsTrigger>
<TabsTrigger className='cursor-pointer' value="database">Banco de Dados</TabsTrigger>
<TabsTrigger className='cursor-pointer' value="ged">GED</TabsTrigger>
<TabsTrigger className='cursor-pointer' value="config">Configurações</TabsTrigger>
<TabsTrigger className='cursor-pointer' value="disk">Unidades de Disco</TabsTrigger>
<TabsTrigger className='cursor-pointer' value="backup">Backup</TabsTrigger>
</TabsList>
{/* ===================================================== */}
{/* Aba: Informação do servidor */}
{/* ===================================================== */}
<TabsContent value="server">
<div className="mt-4 space-y-6">
{/* Badge com data e alerta */}
<div>
<Badge
variant={isOutdated ? "warning" : "outline"}
className={`ml-2 ${
isOutdated
? "bg-yellow-200 text-yellow-800 border-yellow-400"
: ""
}`}
>
Data do log: {formattedDate} às {formattedTime}
{logServer ? (
<div className="mt-4 space-y-6">
{/* Badge com data e alerta */}
<div>
<Badge
variant={isOutdated ? "warning" : "outline"}
className={`ml-2 ${
isOutdated
? "bg-yellow-200 text-yellow-800 border-yellow-400"
: ""
}`}
>
Data do log: {formattedDate} às {formattedTime}
{isOutdated && (
<span>
- Atenção: Log do servidor desatualizado
</span>
)}
{isOutdated && (
<span>
- Atenção: Log do servidor desatualizado
</span>
)}
</Badge>
</Badge>
</div>
</div>
{/* Linha com dois cards lado a lado */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Sistema Operacional */}
{/* Linha com dois cards lado a lado */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Sistema Operacional */}
<Card>
<CardHeader>
<CardTitle>Sistema Operacional</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-1">
<p><strong>SO:</strong> {logServer?.server?.operacional_system?.so}</p>
<p><strong>Versão:</strong> {logServer?.server?.operacional_system?.versao}</p>
<p><strong>Release:</strong> {logServer?.server?.operacional_system?.release}</p>
<p>
<strong>Arquitetura:</strong>
<Badge variant="outline" className="ml-2">
{logServer?.server?.operacional_system?.arquitetura}
</Badge>
</p>
</div>
</CardContent>
</Card>
{/* Memória */}
<Card>
<CardHeader>
<CardTitle>Memória</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-2">
<p><strong>Total:</strong> {logServer?.server?.memory.total_MB} MB</p>
<p><strong>Usada:</strong> {logServer?.server?.memory.usada_MB} MB</p>
<p><strong>Livre:</strong> {logServer?.server?.memory.livre_MB} MB</p>
<p><strong>Buffer:</strong> {logServer?.server?.memory.buffer_MB} MB</p>
<p><strong>Swap Total:</strong> {logServer?.server?.memory.swap_total_MB} MB</p>
<p><strong>Swap Usada:</strong> {logServer?.server?.memory.swap_usada_MB} MB</p>
</div>
</CardContent>
</Card>
</div>
{/* Processadores */}
<Card>
<CardHeader>
<CardTitle>Sistema Operacional</CardTitle>
<CardTitle>Processadores</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-1">
<p><strong>SO:</strong> {logServer.server.operacional_system.so}</p>
<p><strong>Versão:</strong> {logServer.server.operacional_system.versao}</p>
<p><strong>Release:</strong> {logServer.server.operacional_system.release}</p>
<p>
<strong>Arquitetura:</strong>
<Badge variant="outline" className="ml-2">
{logServer.server.operacional_system.arquitetura}
</Badge>
</p>
</div>
</CardContent>
</Card>
{/* Memória */}
<Card>
<CardHeader>
<CardTitle>Memória</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-2">
<p><strong>Total:</strong> {logServer.server.memory.total_MB} MB</p>
<p><strong>Usada:</strong> {logServer.server.memory.usada_MB} MB</p>
<p><strong>Livre:</strong> {logServer.server.memory.livre_MB} MB</p>
<p><strong>Buffer:</strong> {logServer.server.memory.buffer_MB} MB</p>
<p><strong>Swap Total:</strong> {logServer.server.memory.swap_total_MB} MB</p>
<p><strong>Swap Usada:</strong> {logServer.server.memory.swap_usada_MB} MB</p>
</div>
</CardContent>
</Card>
</div>
{/* Processadores */}
<Card>
<CardHeader>
<CardTitle>Processadores</CardTitle>
</CardHeader>
<CardContent>
<ScrollArea className="h-[250px] w-full rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-center">Núcleo</TableHead>
<TableHead className="text-center">Modelo</TableHead>
<TableHead className="text-center">Clock (MHz)</TableHead>
<TableHead className="text-center">Cache</TableHead>
<TableHead className="text-center">Threads</TableHead>
<TableHead className="text-center">Núcleos</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{logServer.server.cpu.map((cpuItem: any, index: number) => (
<TableRow key={index}>
<TableCell className="text-center">{cpuItem.núcleo}</TableCell>
<TableCell className="text-center text-sm">{cpuItem.modelo}</TableCell>
<TableCell className="text-center">{cpuItem.clock_mhz}</TableCell>
<TableCell className="text-center">{cpuItem.cache}</TableCell>
<TableCell className="text-center">{cpuItem.threads}</TableCell>
<TableCell className="text-center">{cpuItem.núcleos}</TableCell>
<ScrollArea className="h-[250px] w-full rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-center">Núcleo</TableHead>
<TableHead className="text-center">Modelo</TableHead>
<TableHead className="text-center">Clock (MHz)</TableHead>
<TableHead className="text-center">Cache</TableHead>
<TableHead className="text-center">Threads</TableHead>
<TableHead className="text-center">Núcleos</TableHead>
</TableRow>
))}
</TableBody>
</Table>
</ScrollArea>
</CardContent>
</Card>
</div>
</TableHeader>
<TableBody>
{logServer?.server?.cpu.map((cpuItem: any, index: number) => (
<TableRow key={index}>
<TableCell className="text-center">{cpuItem.núcleo}</TableCell>
<TableCell className="text-center text-sm">{cpuItem.modelo}</TableCell>
<TableCell className="text-center">{cpuItem.clock_mhz}</TableCell>
<TableCell className="text-center">{cpuItem.cache}</TableCell>
<TableCell className="text-center">{cpuItem.threads}</TableCell>
<TableCell className="text-center">{cpuItem.núcleos}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</ScrollArea>
</CardContent>
</Card>
</div>
) : (
<p className="p-4 text-gray-500 italic">
Nenhum log do servidor disponível para este cliente.
</p>
)}
</TabsContent>
{/* ===================================================== */}
{/* Aba: Banco de Dados */}
{/* ===================================================== */}
<TabsContent value="database">
<div className="mt-4 space-y-6">
{/* Verifica se há dados disponíveis do banco */}
{logDatabase?.data ? (
<>
{/* Badge com data e hora do log do banco */}
<div>
{(() => {
const { formattedDate, formattedTime, isOutdated } =
formatLogDateTime(logDatabase.data.data, logDatabase.data.hora);
{/* Verifica se há dados disponíveis do banco */}
{logDatabase?.data ? (
<div className="mt-4 space-y-6">
{/* Badge com data e hora do log do banco */}
<div>
{(() => {
const { formattedDate, formattedTime, isOutdated } =
formatLogDateTime(logDatabase.data.data, logDatabase.data.hora);
return (
<>
return (
<>
<Badge
variant={isOutdated ? "warning" : "outline"}
className={`ml-2 ${
isOutdated
? "bg-yellow-200 text-yellow-800 border-yellow-400"
: ""
}`}
>
Data do log: {formattedDate} às {formattedTime}
{isOutdated && (
<span>
- Atenção: Log do banco de dados desatualizado
</span>
)}
</Badge>
</>
);
})()}
</div>
{/* Linha de cards principais */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Card: Partição do Banco */}
<Card>
<CardHeader>
<CardTitle>Partição do Banco</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-1">
<p><strong>Unidade:</strong> {logDatabase?.data?.database?.partition.unit}</p>
<p><strong>Espaço Total:</strong> {logDatabase?.data.database?.partition.total_disk_space}</p>
<p><strong>Espaço Usado:</strong> {logDatabase?.data.database?.partition.total_used_disk_space}</p>
<p><strong>Espaço Livre:</strong> {logDatabase?.data.database?.partition.total_free_disk_space}</p>
<p>
<strong>Uso (%):</strong>{" "}
<Badge
variant={isOutdated ? "warning" : "outline"}
variant={
(logDatabase.data.database?.partition?.percent_used || 0) > 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 && (
<span>
- Atenção: Log do banco de dados desatualizado
</span>
)}
{Number(logDatabase.data.database?.partition?.percent_used || 0).toFixed(2)}%
</Badge>
</>
);
})()}
</div>
</p>
</div>
</CardContent>
</Card>
{/* Linha de cards principais */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Card: Partição do Banco */}
<Card>
<CardHeader>
<CardTitle>Partição do Banco</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-1">
<p><strong>Unidade:</strong> {logDatabase?.data?.database?.partition.unit}</p>
<p><strong>Espaço Total:</strong> {logDatabase?.data.database?.partition.total_disk_space}</p>
<p><strong>Espaço Usado:</strong> {logDatabase?.data.database?.partition.total_used_disk_space}</p>
<p><strong>Espaço Livre:</strong> {logDatabase?.data.database?.partition.total_free_disk_space}</p>
<p>
<strong>Uso (%):</strong>{" "}
<Badge
variant={logDatabase.data.database?.partition.percent_used > 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 */}
<Card>
<CardHeader>
<CardTitle>Informações do Arquivo</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-1">
<p><strong>Tamanho:</strong> {convertMBtoGB(logDatabase.data.database?.file_size_mb)}</p>
<p><strong>Acessível:</strong>{" "}
{logDatabase.data.database?.db_accessible ? (
<Badge variant="outline" className="bg-green-100 text-green-800 border-green-400 ml-2">
Sim
</Badge>
</p>
</div>
</CardContent>
</Card>
) : (
<Badge variant="outline" className="bg-red-100 text-red-800 border-red-400 ml-2">
Não
</Badge>
)}
</p>
{/* Card: Informações do Arquivo de Banco */}
<Card>
<CardHeader>
<CardTitle>Informações do Arquivo</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-1">
<p><strong>Tamanho:</strong> {convertMBtoGB(logDatabase.data.database?.file_size_mb)}</p>
<p><strong>Acessível:</strong>{" "}
{logDatabase.data.database?.db_accessible ? (
<Badge variant="outline" className="bg-green-100 text-green-800 border-green-400 ml-2">
Sim
</Badge>
) : (
<Badge variant="outline" className="bg-red-100 text-red-800 border-red-400 ml-2">
Não
</Badge>
)}
</p>
{/* Aqui aplicamos a função */}
{(() => {
const { formattedDate, formattedTime } = formatDateTime(
logDatabase?.data?.database?.last_modified
);
return (
<p>
<strong>Última Modificação:</strong> {formattedDate} às {formattedTime}
</p>
);
})()}
{/* Aqui aplicamos a função */}
{(() => {
const { formattedDate, formattedTime } = formatDateTime(
logDatabase?.data?.database?.last_modified
);
return (
<p>
<strong>Última Modificação:</strong> {formattedDate} às {formattedTime}
</p>
);
})()}
</div>
</CardContent>
</Card>
</div>
</CardContent>
</Card>
{/* Card: Gráfico/Pie de Uso da Partição */}
<Card>
<CardHeader>
<CardTitle>Uso da Partição</CardTitle>
</CardHeader>
<CardContent>
{renderPartitionPieChart(logDatabase.data.database?.partition)}
</CardContent>
</Card>
{/* Card: Gráfico/Pie de Uso da Partição */}
<Card>
<CardHeader>
<CardTitle>Uso da Partição</CardTitle>
</CardHeader>
<CardContent>
{renderPartitionPieChart(logDatabase.data.database?.partition)}
</CardContent>
</Card>
{/* Card: Gráfico/Bar de Uso da Partição */}
<Card>
<CardHeader>
<CardTitle>Uso da Partição</CardTitle>
</CardHeader>
<CardContent>
{(() => {
const partition = logDatabase.data.database?.partition;
if (!partition) return <p className="text-gray-500 italic">Sem dados disponíveis.</p>;
return <PartitionBarChart used={partition.percent_used} free={partition.percent_free} />;
})()}
</CardContent>
</Card>
{/* Card: Gráfico/Bar de Uso da Partição */}
<Card>
<CardHeader>
<CardTitle>Uso da Partição</CardTitle>
</CardHeader>
<CardContent>
{(() => {
const partition = logDatabase.data.database?.partition;
if (!partition) return <p className="text-gray-500 italic">Sem dados disponíveis.</p>;
return <PartitionBarChart used={partition.percent_used} free={partition.percent_free} />;
})()}
</CardContent>
</Card>
</div>
</>
) : (
<p className="text-gray-500 text-sm italic">
Nenhum log de banco de dados disponível para este cliente.
</p>
)}
</div>
</div>
) : (
<p className="p-4 text-gray-500 italic">
Nenhum log de banco de dados disponível para este cliente.
</p>
)}
</TabsContent>
@ -360,135 +391,304 @@ export default function ClientePage() {
{/* Aba: GED */}
{/* ===================================================== */}
<TabsContent value="ged">
<div className="mt-4 space-y-6">
{/* Verifica se há dados disponíveis do banco */}
{logGed?.data ? (
<>
{/* Badge com data e hora do log do banco */}
<div>
{(() => {
const { formattedDate, formattedTime, isOutdated } =
formatLogDateTime(logGed.data.data, logGed.data.hora);
return (
<>
{/* Verifica se há dados disponíveis do banco */}
{logGed?.data ? (
<div className="mt-4 space-y-6">
{/* Badge com data e hora do log do banco */}
<div>
{(() => {
const { formattedDate, formattedTime, isOutdated } =
formatLogDateTime(logGed.data.data, logGed.data.hora);
return (
<>
<Badge
variant={isOutdated ? "warning" : "outline"}
className={`ml-2 ${
isOutdated
? "bg-yellow-200 text-yellow-800 border-yellow-400"
: ""
}`}
>
Data do log: {formattedDate} às {formattedTime}
{isOutdated && (
<span>
- Atenção: Log do GED desatualizado
</span>
)}
</Badge>
</>
);
})()}
</div>
{/* Linha de cards principais */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Card: Partição do Banco */}
<Card>
<CardHeader>
<CardTitle>Partição do GED</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-1">
<p><strong>Unidade:</strong> {logGed?.data?.ged?.partition.unit}</p>
<p><strong>Espaço Total:</strong> {logGed?.data.ged?.partition.total_disk_space}</p>
<p><strong>Espaço Usado:</strong> {logGed?.data.ged?.partition.total_used_disk_space}</p>
<p><strong>Espaço Livre:</strong> {logGed?.data.ged?.partition.total_free_disk_space}</p>
<p>
<strong>Uso (%):</strong>{" "}
<Badge
variant={isOutdated ? "warning" : "outline"}
variant={logGed.data.ged?.partition.percent_used > 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 && (
<span>
- Atenção: Log do GED desatualizado
</span>
)}
{logGed.data.ged?.partition.percent_used.toFixed(2)}%
</Badge>
</>
);
})()}
</div>
</p>
</div>
</CardContent>
</Card>
{/* Linha de cards principais */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Card: Partição do Banco */}
<Card>
<CardHeader>
<CardTitle>Partição do GED</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-1">
<p><strong>Unidade:</strong> {logGed?.data?.ged?.partition.unit}</p>
<p><strong>Espaço Total:</strong> {logGed?.data.ged?.partition.total_disk_space}</p>
<p><strong>Espaço Usado:</strong> {logGed?.data.ged?.partition.total_used_disk_space}</p>
<p><strong>Espaço Livre:</strong> {logGed?.data.ged?.partition.total_free_disk_space}</p>
<p>
<strong>Uso (%):</strong>{" "}
<Badge
variant={logGed.data.ged?.partition.percent_used > 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)}%
</Badge>
</p>
</div>
</CardContent>
</Card>
{/* Pasta de arquivos */}
<Card>
<CardHeader>
<CardTitle>Pasta de Arquivos</CardTitle>
</CardHeader>
<CardContent>
<ScrollArea className="h-[250px] w-full rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-center">Caminho</TableHead>
<TableHead className="text-center">Data</TableHead>
<TableHead className="text-center">Hora</TableHead>
<TableHead className="text-center">Quantidade</TableHead>
{/* Pasta de arquivos */}
<Card>
<CardHeader>
<CardTitle>Pasta de Arquivos</CardTitle>
</CardHeader>
<CardContent>
<ScrollArea className="h-[250px] w-full rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-center">Caminho</TableHead>
<TableHead className="text-center">Data</TableHead>
<TableHead className="text-center">Hora</TableHead>
<TableHead className="text-center">Quantidade</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{logGed?.data.ged?.arquivos.map((gedItem: any, index: number) => (
<TableRow key={index}>
<TableCell>{gedItem.path}</TableCell>
<TableCell className="text-center text-sm">{gedItem.data}</TableCell>
<TableCell className="text-center">{gedItem.hora}</TableCell>
<TableCell className="text-center">{gedItem.quantidade}</TableCell>
</TableRow>
</TableHeader>
<TableBody>
{logGed?.data.ged?.arquivos.map((gedItem: any, index: number) => (
<TableRow key={index}>
<TableCell>{gedItem.path}</TableCell>
<TableCell className="text-center text-sm">{gedItem.data}</TableCell>
<TableCell className="text-center">{gedItem.hora}</TableCell>
<TableCell className="text-center">{gedItem.quantidade}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</ScrollArea>
</CardContent>
</Card>
))}
</TableBody>
</Table>
</ScrollArea>
</CardContent>
</Card>
{/* Card: Gráfico/Pie de Uso da Partição */}
<Card>
<CardHeader>
<CardTitle>Uso da Partição</CardTitle>
</CardHeader>
<CardContent>
{renderPartitionPieChart(logGed.data.ged?.partition)}
</CardContent>
</Card>
{/* Card: Gráfico/Pie de Uso da Partição */}
<Card>
<CardHeader>
<CardTitle>Uso da Partição</CardTitle>
</CardHeader>
<CardContent>
{renderPartitionPieChart(logGed.data.ged?.partition)}
</CardContent>
</Card>
{/* Card: Gráfico/Bar de Uso da Partição */}
<Card>
<CardHeader>
<CardTitle>Uso da Partição</CardTitle>
</CardHeader>
<CardContent>
{(() => {
const partition = logGed.data.ged?.partition;
if (!partition) return <p className="text-gray-500 italic">Sem dados disponíveis.</p>;
return <PartitionBarChart used={partition.percent_used} free={partition.percent_free} />;
})()}
</CardContent>
</Card>
</div>
</>
) : (
<p className="text-gray-500 text-sm italic">
Nenhum log de banco de dados disponível para este cliente.
</p>
)}
{/* Card: Gráfico/Bar de Uso da Partição */}
<Card>
<CardHeader>
<CardTitle>Uso da Partição</CardTitle>
</CardHeader>
<CardContent>
{(() => {
const partition = logGed.data.ged?.partition;
if (!partition) return <p className="text-gray-500 italic">Sem dados disponíveis.</p>;
return <PartitionBarChart used={partition.percent_used} free={partition.percent_free} />;
})()}
</CardContent>
</Card>
</div>
</div>
) : (
<p className="p-4 text-gray-500 italic">
Nenhum log de GED disponível para este cliente.
</p>
)}
</TabsContent>
{/* ===================================================== */}
{/* Aba: Informação do disco do servidor */}
{/* ===================================================== */}
<TabsContent value="disk">
{logDisk?.data ? (
<div className="mt-4 space-y-6">
{/* Badge com data e hora do log do disco */}
<div>
{(() => {
const { formattedDate, formattedTime, isOutdated } =
formatLogDateTime(logDisk.data.data, logDisk.data.hora);
return (
<Badge
variant={isOutdated ? "warning" : "outline"}
className={`ml-2 ${
isOutdated
? "bg-yellow-200 text-yellow-800 border-yellow-400"
: ""
}`}
>
Data do log: {formattedDate} às {formattedTime}
{isOutdated && (
<span> - Atenção: Log do disco desatualizado</span>
)}
</Badge>
);
})()}
</div>
{/* Tabela com as partições/discos encontrados */}
<Card>
<CardHeader>
<CardTitle>Unidades de Disco</CardTitle>
</CardHeader>
<CardContent>
<ScrollArea className="h-[150px] w-full rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-center">Unidade</TableHead>
<TableHead className="text-center">Capacidade</TableHead>
<TableHead className="text-center">Disponível</TableHead>
<TableHead className="text-center">Utilizado</TableHead>
<TableHead className="text-center">Disponível (%)</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{Object.entries(logDisk.data.disk || {}).map(([unit, info]: any, index) => (
<TableRow key={index}>
<TableCell className="text-center font-medium">{unit}</TableCell>
<TableCell className="text-center">{convertMBtoGB(info.capacidade)}</TableCell>
<TableCell className="text-center">{convertMBtoGB(info.disponivel)}</TableCell>
<TableCell className="text-center">{convertMBtoGB(info.utilizados)}</TableCell>
<TableCell className="text-center">
<Badge
variant={
info.disponivel_percentual < 20
? "destructive"
: "outline"
}
className={`${
info.disponivel_percentual < 20
? "bg-red-200 text-red-800 border-red-400"
: "bg-green-100 text-green-800 border-green-400"
}`}
>
{info.disponivel_percentual}%
</Badge>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</ScrollArea>
</CardContent>
</Card>
{Object.entries(logDisk.data.disk || {}).map(([unit, info]: any, index) => (
<div key={index} className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* 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)}
</div>
))}
</div>
) : (
<p className="p-4 text-gray-500 italic">
Nenhum log do disco(s) disponível para este cliente.
</p>
)}
</TabsContent>
{/* ===================================================== */}
{/* Aba: Informação do servidor */}
{/* ===================================================== */}
<TabsContent value="backup">
{logBackup?.data ? (
<div className="mt-4 space-y-6">
{/* Badge com data do backup */}
<div>
{(() => {
const { formattedDate, formattedTime, isOutdated } =
formatLogDateTime(logBackup?.data, logBackup?.hora);
return (
<Badge
variant={isOutdated ? "warning" : "outline"}
className={`ml-2 ${isOutdated ? "bg-yellow-200 text-yellow-800 border-yellow-400" : ""}`}
>
Data do log: {formattedDate} às {formattedTime}
{isOutdated && <span> - Atenção: Log do Backup desatualizado</span>}
</Badge>
);
})()}
</div>
{/* Card com tabela de backups */}
<Card>
<CardHeader>
<CardTitle>Arquivos de Backup</CardTitle>
</CardHeader>
<CardContent>
<ScrollArea className="h-[350px] w-full rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-center">Arquivo</TableHead>
<TableHead className="text-center">Dia</TableHead>
<TableHead className="text-center">Data</TableHead>
<TableHead className="text-center">Hora</TableHead>
<TableHead className="text-center">Caminho</TableHead>
<TableHead className="text-center">Período (dias)</TableHead>
<TableHead className="text-center">Tamanho</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{Object.entries(logBackup?.backup || {}).map(([fileName, details]) => (
<TableRow key={fileName}>
<TableCell className="text-center">{fileName}</TableCell>
<TableCell className="text-center text-sm">{details?.dia}</TableCell>
<TableCell className="text-center">{details?.data}</TableCell>
<TableCell className="text-center">{details?.hora}</TableCell>
<TableCell className="text-center">{details?.caminho}</TableCell>
<TableCell className="text-center">{details?.periodo}</TableCell>
<TableCell className="text-center">{details?.tamanho}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</ScrollArea>
Total de arquivos: {Object.keys(logBackup?.backup || {}).length}
</CardContent>
</Card>
</div>
) : (
<p className="p-4 text-gray-500 italic">
Nenhum log de backup disponível para este cliente.
</p>
)}
</TabsContent>
</Tabs>
</div>
);

View file

@ -58,6 +58,7 @@ const data = {
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const { userAuthenticated } = useGUsuarioGetJWTHook();
console.log('LOGADO', userAuthenticated)
return (
<Sidebar collapsible="icon" {...props}>
<SidebarHeader>

View file

@ -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);

View file

@ -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);

View file

@ -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<LogBackupInterface | null>(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 };
};

View file

@ -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<LogDiskInterface | null>(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 };
};

View file

@ -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")
};
};
};
}

View file

@ -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;
};
};
};
}

View file

@ -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);

View file

@ -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);

View file

@ -35,6 +35,23 @@ export function PartitionBarChart({ used, free }: PartitionBarChartProps) {
</Bar>
</BarChart>
</ResponsiveContainer>
{/* Legenda personalizada com os valores reais */}
<div className="flex justify-center gap-6 mt-4 text-sm">
<div className="flex items-center gap-2">
<span className="w-3 h-3 rounded-full bg-red-500"></span>
<span>
Usado {`${used?.toFixed(1)}%`}
</span>
</div>
<div className="flex items-center gap-2">
<span className="w-3 h-3 rounded-full bg-green-500"></span>
<span>
Livre ({`${free?.toFixed(1)}%`})
</span>
</div>
</div>
</div>
);
}

View file

@ -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 (
<p className="text-gray-500 italic text-sm">
Nenhum dado de disco disponível.
</p>
);
}
// 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 (
<Card key={index}>
<CardHeader>
<CardTitle>Uso da Partição ({unit})</CardTitle>
</CardHeader>
<CardContent>
<div className="w-full h-64">
<ResponsiveContainer>
<BarChart
data={barData}
margin={{ top: 10, right: 20, left: 0, bottom: 0 }}
>
<XAxis dataKey="name" />
<YAxis domain={[0, 100]} tickFormatter={(v) => `${v}%`} />
<Tooltip formatter={(value: number) => `${value.toFixed(1)}%`} />
<Legend />
<Bar dataKey="Usado" radius={[8, 8, 0, 0]} fill="#ef4444" />
<Bar dataKey="Livre" radius={[8, 8, 0, 0]} fill="#22c55e" />
</BarChart>
</ResponsiveContainer>
</div>
{/* Legenda personalizada com os valores reais */}
<div className="flex justify-center gap-6 mt-4 text-sm">
<div className="flex items-center gap-2">
<span className="w-3 h-3 rounded-full bg-red-500"></span>
<span>
Usado ({info.utilizados || `${usedPercent.toFixed(1)}%`})
</span>
</div>
<div className="flex items-center gap-2">
<span className="w-3 h-3 rounded-full bg-green-500"></span>
<span>
Livre ({info.disponivel || `${freePercent.toFixed(1)}%`})
</span>
</div>
</div>
</CardContent>
</Card>
);
})}
</>
);
}

View file

@ -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 (
<p className="text-gray-500 italic text-sm">
Nenhum dado de disco disponível.
</p>
);
}
// 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 (
<Card key={index}>
<CardHeader>
<CardTitle>Uso da Partição ({unit})</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-col items-center">
{/* Container do gráfico */}
<div className="w-full h-64">
<ResponsiveContainer>
<PieChart>
<Pie
data={pieData}
dataKey="value"
nameKey="name"
innerRadius={60}
outerRadius={100}
paddingAngle={3}
label={({ name, value }) => `${name}: ${value.toFixed(1)}%`}
>
{pieData.map((entry, idx) => (
<Cell key={`cell-${idx}`} fill={COLORS[idx]} />
))}
</Pie>
<Tooltip
formatter={(value: number, name: string) => [`${value.toFixed(1)}%`, name]}
/>
</PieChart>
</ResponsiveContainer>
</div>
{/* Legenda personalizada com valores reais */}
<div className="flex justify-center gap-6 mt-4 text-sm">
<div className="flex items-center gap-2">
<span className="w-3 h-3 rounded-full bg-red-500"></span>
<span>
Usado ({info.utilizados || `${usedPercent.toFixed(1)}%`})
</span>
</div>
<div className="flex items-center gap-2">
<span className="w-3 h-3 rounded-full bg-green-500"></span>
<span>
Livre ({info.disponivel || `${freePercent.toFixed(1)}%`})
</span>
</div>
</div>
</div>
</CardContent>
</Card>
);
})}
</>
);
}

View file

@ -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<JwtPayload>(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();
}, []);

View file

@ -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) : '-'
};
}

View file

@ -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
};
}