);
diff --git a/src/app/(protected)/layout.tsx b/src/app/(protected)/layout.tsx
index 49d55c9..a14024c 100644
--- a/src/app/(protected)/layout.tsx
+++ b/src/app/(protected)/layout.tsx
@@ -56,7 +56,7 @@ export default function RootLayout({
-
+
Orius Tecnologia
diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx
index a0361a8..a57a24a 100644
--- a/src/components/app-sidebar.tsx
+++ b/src/components/app-sidebar.tsx
@@ -8,7 +8,6 @@ import {
} from 'lucide-react';
import { NavMain } from '@/components/nav-main';
-import { NavProjects } from '@/components/nav-projects';
import { NavUser } from '@/components/nav-user';
import {
Sidebar,
diff --git a/src/components/ui/spinner.tsx b/src/components/ui/spinner.tsx
new file mode 100644
index 0000000..a70e713
--- /dev/null
+++ b/src/components/ui/spinner.tsx
@@ -0,0 +1,16 @@
+import { Loader2Icon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
+ return (
+
+ )
+}
+
+export { Spinner }
diff --git a/src/config/app.json b/src/config/app.json
index 6a28307..aa128cf 100644
--- a/src/config/app.json
+++ b/src/config/app.json
@@ -4,5 +4,6 @@
"url": "https://monitoring-api.oriustecnologia.com/",
"prefix": "api/v1/",
"content_type": "application/json"
- }
+ },
+ "api_debit": "https://admin.oriustecnologia.com/router.php"
}
\ No newline at end of file
diff --git a/src/packages/administrativo/components/Client/ClientTable.tsx b/src/packages/administrativo/components/Client/ClientTable.tsx
index 54325f8..c233636 100644
--- a/src/packages/administrativo/components/Client/ClientTable.tsx
+++ b/src/packages/administrativo/components/Client/ClientTable.tsx
@@ -1,6 +1,9 @@
-'use client';
+'use client'; // Indica que este componente é executado no lado do cliente (Next.js)
+import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
+
+// Componentes de UI
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
@@ -20,92 +23,190 @@ import {
TableRow,
} from '@/components/ui/table';
-import { ChartPie, EllipsisIcon, PencilIcon, Trash2Icon } from 'lucide-react';
+// Ícones da biblioteca Lucide
+import { ChartPie, EllipsisIcon, PencilIcon } from 'lucide-react';
+import { Spinner } from "@/components/ui/spinner"
+
+// Interfaces e componentes auxiliares
import { ClientTableInterface } from '../../interfaces/Client/ClienteTableInterface';
import { StatusBadge } from '@/components/StatusBadge';
+import { CheckLiberationHelper } from '@/shared/utils/CheckLiberationHelper';
+import FinanceStatusDialog from '@/shared/components/finance/FinanceStatusDialog';
+
+// Tipagem do retorno financeiro do helper
+interface FinancialStatus {
+ status: string;
+ message: string;
+ expired_count: number;
+ next_due_count?: number;
+ details: {
+ type: string;
+ description: string;
+ reference: string;
+ days: number;
+ }[];
+}
export default function ClientTable({ data, pagination, onEdit, onDelete }: ClientTableInterface) {
-
- // Inicializa o roteador
- const router = useRouter();
+ const router = useRouter(); // Hook do Next.js para navegação
+
+ // Estado que armazena o status financeiro de cada cliente, indexado pelo ID
+ const [financialStatus, setFinancialStatus] = useState>({});
+
+ // Efeito que busca o status financeiro de todos os clientes ao carregar ou atualizar a tabela
+ useEffect(() => {
+ // Se não houver dados válidos, encerra a execução
+ if (!Array.isArray(data)) return;
+
+ // Função assíncrona responsável por consultar os status financeiros
+ const fetchStatus = async () => {
+ const newStatuses: Record = {}; // Armazena os resultados temporariamente
+
+ // Faz as requisições em paralelo para cada cliente
+ await Promise.all(
+ data.map(async (client) => {
+ const res = await CheckLiberationHelper(client.cns); // Chama o helper externo
+
+ // Normaliza o retorno e armazena no objeto temporário
+ newStatuses[client.client_id] = {
+ status: res.status || 'indefinido',
+ message: res.message || '',
+ expired_count: res.expired_count || 0,
+ next_due_count: res.next_due_count || 0,
+ details: res.details || [],
+ };
+ })
+ );
+
+ // Atualiza o estado global com todos os resultados de uma vez
+ setFinancialStatus(newStatuses);
+ };
+
+ // Executa a função
+ fetchStatus();
+ }, [data]); // Dependência: roda novamente se o array de clientes mudar
return (
+ {/* Cabeçalho da tabela */}
#Status
+ FinanceiroCNS
- Nome
+ NomeAções
+ {/* Corpo da tabela */}
- {Array.isArray(data) && data.length > 0 ? ( data.map((client) => (
-
- {client.client_id}
-
-
-
- {client.cns}
- {client.name}
-
-
-
-
-
+ {Array.isArray(data) && data.length > 0 ? (
+ data.map((client) => (
+
+ {/* Coluna: ID do cliente */}
+ {client.client_id}
-
-
- onEdit(client)}
- >
-
- Editar
-
-
- router.push(`/administrativo/clientes/${client.client_id}`)}
- >
-
- Dashboard
-
- {/*
- onDelete(client)}
- >
-
- Remover
- */}
+ {/* Coluna: Status cadastral (ativo, inativo, etc) */}
+
+
+
-
-
-
-
-
- ))
+ {/* Coluna: Status financeiro retornado da API */}
+
+ {financialStatus[client.client_id] ? (
+ garante que o elemento seja interativo e receba handlers clonados
+
+ }
+ status={financialStatus[client.client_id].status}
+ message={financialStatus[client.client_id].message}
+ details={financialStatus[client.client_id].details}
+ expired_count={financialStatus[client.client_id].expired_count}
+ />
+ ) : (
+
+ )}
+
+
+ {/* Coluna: CNS do cliente */}
+ {client.cns}
+
+ {/* Coluna: Nome do cliente */}
+ {client.name}
+
+ {/* Coluna: Ações (menu suspenso) */}
+
+
+ {/* Botão principal do menu */}
+
+
+
+
+ {/* Conteúdo do menu suspenso */}
+
+
+ {/* Ação: Editar cliente */}
+ onEdit(client)}
+ >
+
+ Editar
+
+
+
+
+ {/* Ação: Ir para dashboard do cliente */}
+ router.push(`/administrativo/clientes/${client.client_id}`)}
+ >
+
+ Dashboard
+
+
+
+
+
+
+ ))
) : (
+ // Caso não existam registros
-
+
Nenhum cliente encontrado.
- )}
+ )}
+
+ {/* Rodapé da tabela com totalização */}
-
+
- {/* Se existir paginação, mostra o total de registros; senão, usa o tamanho da lista */}
Total de clientes: {pagination?.total_records ?? data?.length}
);
-}
\ No newline at end of file
+}
diff --git a/src/packages/administrativo/data/Client/ClientDeleteData.ts b/src/packages/administrativo/data/Client/ClientDeleteData.ts
index b4b599e..8a34770 100644
--- a/src/packages/administrativo/data/Client/ClientDeleteData.ts
+++ b/src/packages/administrativo/data/Client/ClientDeleteData.ts
@@ -11,7 +11,7 @@ import { withClientErrorHandler } from '@/withClientErrorHandler/withClientError
// Importa o decorador que adiciona tratamento global de erros às funções assíncronas
// Função principal responsável por enviar a requisição de exclusão de um cliente
-async function executeClientDeleteData(usuarioId: number) {
+async function executeClientDeleteData(client_id: number) {
// Cria uma nova instância da classe API, que gerencia headers, baseURL e envio das requisições
const api = new API();
@@ -19,7 +19,7 @@ async function executeClientDeleteData(usuarioId: number) {
// Envia a requisição DELETE para o endpoint correspondente ao cliente informado
const response = await api.send({
'method': Methods.DELETE, // Define o método HTTP como DELETE
- 'endpoint': `administrativo/client/${usuarioId}` // Define o endpoint incluindo o ID do cliente
+ 'endpoint': `administrativo/client/${client_id}` // Define o endpoint incluindo o ID do cliente
});
// Retorna a resposta da API, contendo status, mensagem e possíveis dados adicionais
diff --git a/src/packages/administrativo/data/Log/LogWarningData.ts b/src/packages/administrativo/data/Log/LogWarningData.ts
new file mode 100644
index 0000000..2e9c8dc
--- /dev/null
+++ b/src/packages/administrativo/data/Log/LogWarningData.ts
@@ -0,0 +1,31 @@
+'use server'
+// Indica que este módulo será executado no lado do servidor (Warning 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 executeLogWarningData(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/warning/${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 LogWarningData = withClientErrorHandler(executeLogWarningData);
diff --git a/src/packages/administrativo/hooks/Log/useLogWarningHook.ts b/src/packages/administrativo/hooks/Log/useLogWarningHook.ts
new file mode 100644
index 0000000..bc3e502
--- /dev/null
+++ b/src/packages/administrativo/hooks/Log/useLogWarningHook.ts
@@ -0,0 +1,23 @@
+'use client';
+
+import { useState } from 'react';
+import { LogWarningInterface } from '../../interfaces/Log/LogWarningInterface';
+import { LogWarningService } from '../../services/Log/LogWarningService';
+import { useResponse } from '@/shared/components/response/ResponseContext';
+
+export const useLogWarningHook = () => {
+ const { setResponse } = useResponse();
+ const [logWarning, setLog] = useState(null);
+
+ const fetchLogWarning = async (client_id: number) => {
+ try {
+ const response = await LogWarningService(client_id);
+ setLog(response as LogWarningInterface);
+ setResponse(response);
+ } catch (error) {
+ console.error("Erro ao buscar informação do banco de dados:", error);
+ }
+ };
+
+ return { logWarning, fetchLogWarning };
+};
diff --git a/src/packages/administrativo/interfaces/Log/LogWarningInterface.ts b/src/packages/administrativo/interfaces/Log/LogWarningInterface.ts
new file mode 100644
index 0000000..f6861c1
--- /dev/null
+++ b/src/packages/administrativo/interfaces/Log/LogWarningInterface.ts
@@ -0,0 +1,13 @@
+/**
+ * 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;
+ warning: []
+ };
+}
diff --git a/src/packages/administrativo/services/Log/LogWarningService.ts b/src/packages/administrativo/services/Log/LogWarningService.ts
new file mode 100644
index 0000000..30c335f
--- /dev/null
+++ b/src/packages/administrativo/services/Log/LogWarningService.ts
@@ -0,0 +1,22 @@
+'use server'
+// Indica que este arquivo é um "Warning 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 { LogWarningData } from "../../data/Log/LogWarningData";
+// 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 executeLogWarningService(client_id: number) {
+
+ // Executa a função de busca de usuário, passando o ID recebido como parâmetro
+ const response = await LogWarningData(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 LogWarningService = withClientErrorHandler(executeLogWarningService);
diff --git a/src/shared/components/finance/FinanceStatusDialog.tsx b/src/shared/components/finance/FinanceStatusDialog.tsx
new file mode 100644
index 0000000..1f5899a
--- /dev/null
+++ b/src/shared/components/finance/FinanceStatusDialog.tsx
@@ -0,0 +1,70 @@
+// src/shared/components/finance/FinanceStatusDialog.tsx
+'use client';
+
+import React from 'react';
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
+import { ScrollArea } from '@/components/ui/scroll-area';
+import { format } from 'date-fns';
+import { ptBR } from 'date-fns/locale';
+
+interface FinanceDetail { type: string; description: string; reference: string; days: number; }
+
+interface Props {
+ status: string;
+ message?: string;
+ details?: FinanceDetail[];
+ expired_count?: number;
+ // Aceitar apenas ReactElement garante que as props clonadas pelo DialogTrigger funcionem
+ triggerElement?: React.ReactElement | null;
+}
+
+export default function FinanceStatusDialog({
+ status,
+ message,
+ details = [],
+ expired_count = 0,
+ triggerElement = null,
+}: Props) {
+ const isLate = status?.toLowerCase() === 'em atraso';
+
+ return (
+
+ );
+}
diff --git a/src/shared/utils/CheckLiberationHelper.ts b/src/shared/utils/CheckLiberationHelper.ts
new file mode 100644
index 0000000..e517cc7
--- /dev/null
+++ b/src/shared/utils/CheckLiberationHelper.ts
@@ -0,0 +1,88 @@
+// src/shared/helpers/CheckLiberationHelper.ts
+'use server';
+
+import appConfig from '@/config/app.json';
+
+/**
+ * Consulta o status financeiro de um cliente a partir do CNS.
+ * Faz POST x-www-form-urlencoded para a URL definida em app.json (api_debit).
+ * Interpreta dinamicamente os campos retornados, como expired, next_due_date etc.
+ */
+export async function CheckLiberationHelper(cns: string) {
+ try {
+ const apiUrl = appConfig.api_debit;
+
+ // Monta o corpo no formato x-www-form-urlencoded
+ const body = new URLSearchParams({
+ TABLE: 'liberation',
+ ACTION: 'liberation',
+ FOLDER: 'action',
+ hash: 'DOa62FKFI9yjcRi/k/n3fSzz67CjrWRW6qCl8IbC3RjQEQHgAPc6Wz0fjXSUlJKdPnRVw/Ejwj3DKTOFNuPxXX4oFzfCUo1Eqqc8tl7sJ1slbRjwL6XSahaXxxboWHiiltirhr/nu5FDeOH1oLxH2w==',
+ cns: cns,
+ });
+
+ // Requisição POST
+ const response = await fetch(apiUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: body.toString(),
+ cache: 'no-store',
+ });
+
+ if (!response.ok) {
+ return { status: 'erro', message: 'Falha ao consultar status financeiro' };
+ }
+
+ const json = await response.json();
+
+ // Se não existir campo "data", retorna mensagem padrão
+ if (!json?.data) {
+ return { status: 'indefinido', message: 'Sem informações financeiras' };
+ }
+
+ // Extrai dinamicamente as listas retornadas
+ const data = json.data;
+ const expired = Array.isArray(data.expired) ? data.expired : [];
+ const next = Array.isArray(data.next_due_date) ? data.next_due_date : [];
+
+ // Determina status geral com base nas listas
+ let status = 'em dia';
+ if (expired.length > 0) {
+ status = 'em atraso';
+ } else if (next.length > 0) {
+ const nextDays = next[0]?.days ?? 0;
+ status = nextDays === 0 ? 'vence hoje' : 'em dia';
+ }
+
+ // Cria um resumo com detalhes de cada item (útil para exibir tooltip, modal, etc.)
+ const details = [
+ ...expired.map((item) => ({
+ type: 'vencido',
+ description: item.description,
+ reference: item.reference,
+ days: item.days,
+ })),
+ ...next.map((item) => ({
+ type: 'a_vencer',
+ description: item.description,
+ reference: item.reference,
+ days: item.days,
+ })),
+ ];
+
+ // Retorna objeto completo
+ return {
+ code: json.code ?? 200,
+ status, // "em atraso", "vence hoje", "em dia", etc.
+ message: json.message ?? 'Sem mensagem',
+ expired_count: expired.length,
+ next_due_count: next.length,
+ details,
+ };
+ } catch (error) {
+ console.error('Erro ao consultar API externa:', error);
+ return { status: 'erro', message: 'Erro de comunicação com servidor externo' };
+ }
+}