Compare commits

...
Sign in to create a new pull request.

36 commits

Author SHA1 Message Date
81be526148 fix(): Ajuste page.tsx cliente 2025-12-18 09:52:34 -03:00
30b2052f5a fix(): Higienização do codigo 2025-11-14 19:15:11 -03:00
2599c8fdbf Debug 2025-11-14 17:37:16 -03:00
fa802e3e55 fix(): Ajuste no carregamento dos conteúdo das abas 2025-11-14 17:31:12 -03:00
5215180249 Debug 2025-11-14 17:20:30 -03:00
c6ac3e3fc9 Debug 2025-11-14 17:10:56 -03:00
2f5d01dfe8 Debug 2025-11-14 16:45:07 -03:00
3ea3778642 fix(): Ajuste na interface 2025-11-14 16:44:33 -03:00
1d1aca0a88 feat(): Criação do endpoint Schema Database para verificação de integridade de banco de dados 2025-11-14 16:17:19 -03:00
da3fdee03f feat(): Criação do endpoint Schema Database para verificação de integridade de banco de dados 2025-11-14 16:14:22 -03:00
134e730f90 feat(): Criação do endpoint Schema Database para verificação de integridade de banco de dados 2025-11-14 16:06:24 -03:00
c1ee420741 feat(): Criação do endpoint Schema Database para verificação de integridade de banco de dados 2025-11-14 15:59:14 -03:00
6c1371ccd9 feat(): Redirecionamento para a tela de clientes na página inicial 2025-11-12 10:01:44 -03:00
0c91e9ed39 fix(): Ajuste no badge da data do log warning 2025-11-11 18:43:28 -03:00
76efa712ee feat(): Criado listagem do backup mais atual 2025-11-11 14:47:48 -03:00
30b5d29c37 feat(): Criado a visualização de pendência por cliente e criado o serviço que acesso o endpoit de warning 2025-11-11 11:33:51 -03:00
588f3b3d06 Removendo todas as pastas TImovel do versionamento 2025-11-11 10:28:32 -03:00
2a4c759c16 fix(): Ajuste no bloqueio de telas 2025-11-10 15:34:30 -03:00
0d5d285786 fix(): Ajuste no bloqueio de telas 2025-11-10 15:24:39 -03:00
3114dbb6d7 fix(): Ajuste gitignore 2025-11-10 10:44:28 -03:00
48a46d8a14 Volta a rastrear app.json 2025-11-10 10:43:30 -03:00
dbbd0291d8 feat(): Ajustes para deploy 2025-11-10 10:40:20 -03:00
46ccbd2b53 feat(): Criação Dashboard clientes 2025-11-10 10:32:55 -03:00
b6a5dffdec feat(): Crianção da consumação dos seguintes endpoints, Log Server, Log Ged e Log Database 2025-11-09 09:44:13 -03:00
bee7779e39 feat(): Refatoração de código, e preparação para criação do dashboard do cliente 2025-11-08 10:46:48 -03:00
=
bed3989eda [VDU-9] feat(CURD):implementando listagem, cadastro, editar e excluir usuario 2025-10-16 17:00:01 -03:00
=
cefd6fd561 [VDU-9] fix(CREATE): correção do create porém agora o update que não está funcionando 2025-10-16 10:47:35 -03:00
=
a1d08b9be0 [VDU-9] feat(implementação) ainda na fase de implementação, mas não está completo 2025-10-14 15:26:47 -03:00
=
cb51e0ed19 Merge branch 'VDU-4' into release/Sprint1 2025-10-13 10:23:28 -03:00
=
15b4338ada Merge branch 'VDU-3' into release/Sprint1 2025-10-13 10:22:16 -03:00
=
ef8c499f37 [VDU-6] feat(implementação): implementando endpoint para buscar usuario por ID 2025-10-13 10:06:25 -03:00
=
e33bb452cf [VDU-5] feat(implementação): implementando endpoint para buscar usuario por email 2025-10-13 10:05:10 -03:00
=
5351fd4be8 [VDU-4] feat(implementando): implementando endpoint para cadastrar/atualizar usuario 2025-10-13 10:03:58 -03:00
=
c92e903c3c [VDU-4] feat(implementando): endpoint para buscar usuário por email 2025-10-13 09:59:43 -03:00
=
a8eb911832 [VDU-2] feat(implementando): endpoint para obter dados do usuário logado 2025-10-13 09:32:01 -03:00
=
3ff500b515 [VDU-1] feat(autenticação): implementando endpoint de autenticação de usuário 2025-10-13 08:33:31 -03:00
146 changed files with 6435 additions and 1237 deletions

10
.dockerignore Normal file
View file

@ -0,0 +1,10 @@
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
.env
*.md
.vscode
dist

2
.gitignore vendored
View file

@ -40,4 +40,4 @@ yarn-error.log*
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
/src/config/app.json # /src/config/app.json

View file

@ -1,41 +1,42 @@
# Etapa 1: Construir a aplicação # ========================================================
# Etapa 1: Build da aplicação
# ========================================================
FROM node:20-alpine AS builder FROM node:20-alpine AS builder
# Define o diretório de trabalho # Define diretório de trabalho
WORKDIR /app WORKDIR /app
# Copia os arquivos de configuração do pacote # Copia arquivos de configuração do pacote
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
# Instala as dependências do projeto # Instala dependências de produção e desenvolvimento
RUN npm install RUN npm ci
# Copia todo o código da aplicação para o container # Copia todo o código fonte
COPY . . COPY . .
# Constrói a aplicação com o output 'standalone' # Gera build standalone
RUN npm run build RUN npm run build
# Etapa 2: Executar a aplicação # ========================================================
# Usa uma imagem Node.js leve # Etapa 2: Runner (produção)
# ========================================================
FROM node:20-alpine AS runner FROM node:20-alpine AS runner
# Define o diretório de trabalho # Define diretório de trabalho
WORKDIR /app WORKDIR /app
# Copia o diretório 'standalone' da etapa de build, que já contém o servidor e as dependências # Copia diretório standalone gerado pelo build
# O diretório 'standalone' é a pasta .next/standalone gerada pela configuração 'output: standalone'
COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/standalone ./
# Copia os arquivos públicos # Copia pasta public
COPY --from=builder /app/public ./public COPY --from=builder /app/public ./public
# Copia os arquivos estáticos gerados pelo build. É aqui que os arquivos CSS e JS ficam. # Copia arquivos estáticos do build
COPY --from=builder /app/.next/static ./.next/static COPY --from=builder /app/.next/static ./.next/static
# Expõe a porta padrão do Next.js # Expondo porta padrão do Next.js
EXPOSE 3000 EXPOSE 3000
# Define o comando para iniciar a aplicação # Comando de inicialização do servidor standalone
# O 'start' do package.json não é necessário, o próprio servidor standalone já está no container
CMD ["node", "server.js"] CMD ["node", "server.js"]

File diff suppressed because it is too large Load diff

568
package-lock.json generated
View file

@ -18,9 +18,10 @@
"@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.7", "@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6", "@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-table": "^8.21.3", "@tanstack/react-table": "^8.21.3",
@ -29,8 +30,10 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"cookies-next": "^6.1.0", "cookies-next": "^6.1.0",
"date-fns": "^4.1.0",
"faker-js": "^1.0.0", "faker-js": "^1.0.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"jose": "^6.1.1",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
@ -41,6 +44,7 @@
"react-dom": "19.1.0", "react-dom": "19.1.0",
"react-hook-form": "^7.62.0", "react-hook-form": "^7.62.0",
"react-masked-text": "^1.0.5", "react-masked-text": "^1.0.5",
"recharts": "^3.3.0",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"tinymce": "^8.1.2", "tinymce": "^8.1.2",
@ -1119,6 +1123,24 @@
} }
} }
}, },
"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-arrow": { "node_modules/@radix-ui/react-arrow": {
"version": "1.1.7", "version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
@ -1255,6 +1277,24 @@
} }
} }
}, },
"node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-compose-refs": { "node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
@ -1321,6 +1361,24 @@
} }
} }
}, },
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-direction": { "node_modules/@radix-ui/react-direction": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
@ -1513,6 +1571,24 @@
} }
} }
}, },
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover": { "node_modules/@radix-ui/react-popover": {
"version": "1.1.15", "version": "1.1.15",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz",
@ -1550,6 +1626,24 @@
} }
} }
}, },
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popper": { "node_modules/@radix-ui/react-popper": {
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
@ -1653,6 +1747,24 @@
} }
} }
}, },
"node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-roving-focus": { "node_modules/@radix-ui/react-roving-focus": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
@ -1684,6 +1796,37 @@
} }
} }
}, },
"node_modules/@radix-ui/react-scroll-area": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz",
"integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==",
"license": "MIT",
"dependencies": {
"@radix-ui/number": "1.1.1",
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-direction": "1.1.1",
"@radix-ui/react-presence": "1.1.5",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-callback-ref": "1.1.1",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-select": { "node_modules/@radix-ui/react-select": {
"version": "2.2.6", "version": "2.2.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz",
@ -1727,6 +1870,24 @@
} }
} }
}, },
"node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-separator": { "node_modules/@radix-ui/react-separator": {
"version": "1.1.7", "version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz",
@ -1751,9 +1912,9 @@
} }
}, },
"node_modules/@radix-ui/react-slot": { "node_modules/@radix-ui/react-slot": {
"version": "1.2.3", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@radix-ui/react-compose-refs": "1.1.2" "@radix-ui/react-compose-refs": "1.1.2"
@ -1832,6 +1993,24 @@
} }
} }
}, },
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": { "node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
@ -2015,6 +2194,32 @@
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@reduxjs/toolkit": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.10.1.tgz",
"integrity": "sha512-/U17EXQ9Do9Yx4DlNGU6eVNfZvFJfYpUtRRdLf19PbPjdWBxNlxGZXywQZ1p1Nz8nMkWplTI7iD/23m07nolDA==",
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@standard-schema/utils": "^0.3.0",
"immer": "^10.2.0",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"reselect": "^5.1.0"
},
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-redux": {
"optional": true
}
}
},
"node_modules/@rtsao/scc": { "node_modules/@rtsao/scc": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@ -2029,6 +2234,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@standard-schema/spec": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
"license": "MIT"
},
"node_modules/@standard-schema/utils": { "node_modules/@standard-schema/utils": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
@ -2383,6 +2594,69 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@types/d3-array": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
"integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
"license": "MIT"
},
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
"license": "MIT"
},
"node_modules/@types/d3-ease": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
"license": "MIT"
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
"license": "MIT",
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
"license": "MIT"
},
"node_modules/@types/d3-scale": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
"license": "MIT",
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-shape": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
"license": "MIT",
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
"license": "MIT"
},
"node_modules/@types/d3-timer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
"license": "MIT"
},
"node_modules/@types/js-cookie": { "node_modules/@types/js-cookie": {
"version": "3.0.6", "version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
@ -2445,6 +2719,12 @@
"@types/react": "^19.2.0" "@types/react": "^19.2.0"
} }
}, },
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.46.0", "version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz",
@ -3521,6 +3801,127 @@
"devOptional": true, "devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-format": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
"license": "ISC",
"dependencies": {
"d3-array": "2.10.0 - 3",
"d3-format": "1 - 3",
"d3-interpolate": "1.2.0 - 3",
"d3-time": "2.1.1 - 3",
"d3-time-format": "2 - 4"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-shape": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
"license": "ISC",
"dependencies": {
"d3-path": "^3.1.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
"license": "ISC",
"dependencies": {
"d3-array": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time-format": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
"license": "ISC",
"dependencies": {
"d3-time": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/damerau-levenshtein": { "node_modules/damerau-levenshtein": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@ -3582,6 +3983,16 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/date-fns": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@ -3600,6 +4011,12 @@
} }
} }
}, },
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
"license": "MIT"
},
"node_modules/deep-is": { "node_modules/deep-is": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@ -3894,6 +4311,16 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/es-toolkit": {
"version": "1.41.0",
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.41.0.tgz",
"integrity": "sha512-bDd3oRmbVgqZCJS6WmeQieOrzpl3URcWBUVDXxOELlUW2FuW+0glPOz1n0KnRie+PdyvUZcXz2sOn00c6pPRIA==",
"license": "MIT",
"workspaces": [
"docs",
"benchmarks"
]
},
"node_modules/escape-string-regexp": { "node_modules/escape-string-regexp": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@ -4524,6 +4951,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"license": "MIT"
},
"node_modules/faker-js": { "node_modules/faker-js": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/faker-js/-/faker-js-1.0.0.tgz", "resolved": "https://registry.npmjs.org/faker-js/-/faker-js-1.0.0.tgz",
@ -5039,6 +5472,16 @@
"node": ">= 4" "node": ">= 4"
} }
}, },
"node_modules/immer": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
"integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/import-fresh": { "node_modules/import-fresh": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@ -5110,6 +5553,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/is-array-buffer": { "node_modules/is-array-buffer": {
"version": "3.0.5", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@ -5564,6 +6016,15 @@
"jiti": "lib/jiti-cli.mjs" "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": { "node_modules/js-cookie": {
"version": "3.0.5", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
@ -6818,6 +7279,29 @@
"integrity": "sha512-WichrlCXehL0apIfIgOdi2mjBE03tdMi8wXF+DhHe2ySWYxXCkP88aqDBaJZWUMa3Jp8p2h71u7TpC7EzEjXYw==", "integrity": "sha512-WichrlCXehL0apIfIgOdi2mjBE03tdMi8wXF+DhHe2ySWYxXCkP88aqDBaJZWUMa3Jp8p2h71u7TpC7EzEjXYw==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/react-redux": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
"@types/react": "^18.2.25 || ^19",
"react": "^18.0 || ^19",
"redux": "^5.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"redux": {
"optional": true
}
}
},
"node_modules/react-remove-scroll": { "node_modules/react-remove-scroll": {
"version": "2.7.1", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz",
@ -6887,6 +7371,48 @@
} }
} }
}, },
"node_modules/recharts": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.3.0.tgz",
"integrity": "sha512-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg==",
"license": "MIT",
"dependencies": {
"@reduxjs/toolkit": "1.x.x || 2.x.x",
"clsx": "^2.1.1",
"decimal.js-light": "^2.5.1",
"es-toolkit": "^1.39.3",
"eventemitter3": "^5.0.1",
"immer": "^10.1.1",
"react-redux": "8.x.x || 9.x.x",
"reselect": "5.1.1",
"tiny-invariant": "^1.3.3",
"use-sync-external-store": "^1.2.2",
"victory-vendor": "^37.0.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
},
"node_modules/redux-thunk": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
"license": "MIT",
"peerDependencies": {
"redux": "^5.0.0"
}
},
"node_modules/reflect.getprototypeof": { "node_modules/reflect.getprototypeof": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@ -6931,6 +7457,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
"license": "MIT"
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.10", "version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@ -7617,6 +8149,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
"license": "MIT"
},
"node_modules/tinyglobby": { "node_modules/tinyglobby": {
"version": "0.2.15", "version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@ -7967,6 +8505,28 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
} }
}, },
"node_modules/victory-vendor": {
"version": "37.3.6",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
"integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
"license": "MIT AND ISC",
"dependencies": {
"@types/d3-array": "^3.0.3",
"@types/d3-ease": "^3.0.0",
"@types/d3-interpolate": "^3.0.1",
"@types/d3-scale": "^4.0.2",
"@types/d3-shape": "^3.1.0",
"@types/d3-time": "^3.0.0",
"@types/d3-timer": "^3.0.0",
"d3-array": "^3.1.6",
"d3-ease": "^3.0.1",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-shape": "^3.1.0",
"d3-time": "^3.0.0",
"d3-timer": "^3.0.1"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View file

@ -22,9 +22,10 @@
"@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.7", "@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6", "@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-table": "^8.21.3", "@tanstack/react-table": "^8.21.3",
@ -33,8 +34,10 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"cookies-next": "^6.1.0", "cookies-next": "^6.1.0",
"date-fns": "^4.1.0",
"faker-js": "^1.0.0", "faker-js": "^1.0.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"jose": "^6.1.1",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
@ -45,6 +48,7 @@
"react-dom": "19.1.0", "react-dom": "19.1.0",
"react-hook-form": "^7.62.0", "react-hook-form": "^7.62.0",
"react-masked-text": "^1.0.5", "react-masked-text": "^1.0.5",
"recharts": "^3.3.0",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"tinymce": "^8.1.2", "tinymce": "^8.1.2",

View file

@ -0,0 +1,762 @@
'use client';
import React from "react";
import { useParams } from 'next/navigation';
import { useEffect } from 'react';
import Link from 'next/link';
// Helpers e hooks customizados
import { formatLogDateTime } from "@/shared/utils/formatLogDateTime";
import { formatDateTime } from '@/shared/utils/formatDateTime';
import { convertMBtoGB } from '@/shared/utils/convertMBtoGB';
// Hooks responsaveis em consumir os endpoint's
import { useLogServerHook } from '@/packages/administrativo/hooks/Log/useLogServerHook';
import { useLogDatabaseHook } from '@/packages/administrativo/hooks/Log/useLogDatabaseHook';
import { useLogGedHook } from '@/packages/administrativo/hooks/Log/useLogGedHook';
import { useLogDiskHook } from '@/packages/administrativo/hooks/Log/useLogDiskHook';
import { useLogBackupHook } from '@/packages/administrativo/hooks/Log/useLogBackupHook';
import { useLogWarningHook } from '@/packages/administrativo/hooks/Log/useLogWarningHook';
// Componentes do Shadcn/UI
import {
Card, CardHeader, CardTitle, CardContent
} from "@/components/ui/card";
import {
Table, TableHeader, TableRow, TableHead, TableCell, TableBody,
TableFooter
} from "@/components/ui/table";
import { Badge } from "@/components/ui/badge";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Tabs, TabsContent, TabsList, TabsTrigger
} from "@/components/ui/tabs";
import { PartitionBarChart } from '@/shared/components/charts/PartitionBarChart';
import { renderPartitionPieChart } from '@/shared/components/charts/PartitionPieChart';
import { renderDiskPieChartsFromJson } from '@/shared/components/charts/PartitionPieChartDisk';
import { renderDiskBarChartsFromJson } from '@/shared/components/charts/PartitionBarChartDisk';
export default function ClientePage() {
const { id } = useParams();
// Hooks de logs (server e database)
const { logServer, fetchLogServer } = useLogServerHook();
const { logDatabase, fetchLogDatabase } = useLogDatabaseHook();
const { logGed, fetchLogGed } = useLogGedHook();
const { logDisk, fetchLogDisk } = useLogDiskHook();
const { logBackup, fetchLogBackup } = useLogBackupHook();
const { logWarning, fetchLogWarning } = useLogWarningHook();
// Efeito responsável por buscar logs de forma sequencial
useEffect(() => {
if (!id) return;
// Helper: executa função e ignora erros (logando no console)
const safeRun = async (label: string, fn: () => Promise<any>) => {
try {
await fn();
} catch (error) {
console.error(`Erro ao executar ${label}:`, error);
}
};
const fetchSequentially = async () => {
await safeRun("log do servidor", () => fetchLogServer(Number(id)));
await safeRun("log do banco", () => fetchLogDatabase(Number(id)));
await safeRun("log do GED", () => fetchLogGed(Number(id)));
await safeRun("log do Disk", () => fetchLogDisk(Number(id)));
await safeRun("log do Backup", () => fetchLogBackup(Number(id)));
await safeRun("log do Warning", () => fetchLogWarning(Number(id)));
};
fetchSequentially();
}, [id]);
// Formata data e hora do log do servidor
const { formattedDate, formattedTime, isOutdated } = formatLogDateTime(
logServer?.data,
logServer?.hora
);
// ============================================================
// Renderização principal da página
// ============================================================
return (
<div className="p-4">
<h1 className="text-2xl font-semibold mb-6">
{logServer?.cns ?? 'CNS não disponível'} - {logServer?.cartorio ?? 'Cartório não disponível'}
<span className="m-4 text-gray-500 "> :: </span>
<Link href={`/administrativo/clientes`} className="ml-2 text-gray-500 hover:underline">
Voltar
</Link>
</h1>
<Tabs defaultValue="server" className="w-full">
<TabsList className="grid w-full grid-cols-6">
<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="disk">Unidades de Disco</TabsTrigger>
<TabsTrigger className='cursor-pointer' value="backup">Backup</TabsTrigger>
<TabsTrigger className='cursor-pointer' value="warning">Avisos</TabsTrigger>
</TabsList>
{/* ===================================================== */}
{/* Aba: Informação do servidor */}
{/* ===================================================== */}
<TabsContent value="server">
{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>
)}
</Badge>
</div>
{/* 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>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>
</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">
{/* 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 (
<>
<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={
(logDatabase.data.database?.partition?.percent_used || 0) > 80
? "destructive"
: "outline"
}
className={`ml-2 ${
(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"
}`}
>
{Number(logDatabase.data.database?.partition?.percent_used || 0).toFixed(2)}%
</Badge>
</p>
</div>
</CardContent>
</Card>
{/* 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>
);
})()}
</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/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>
</div>
) : (
<p className="p-4 text-gray-500 italic">
Nenhum log de banco de dados disponível para este cliente.
</p>
)}
</TabsContent>
{/* ===================================================== */}
{/* Aba: GED */}
{/* ===================================================== */}
<TabsContent value="ged">
{/* 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={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>
</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>
{/* 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>
</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 Backup */}
{/* ===================================================== */}
<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>
{/** Lista os backups mais recentes */}
{logBackup?.latest_backups && (
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg mb-4">
<h2 className="text-lg font-semibold text-blue-800">
Backup mais recente ({logBackup.latest_backups.data})
</h2>
<table className="w-full text-sm mt-2 border">
<thead className="bg-blue-100">
<tr>
<th className="px-3 py-2 text-left">Arquivo</th>
<th className="px-3 py-2 text-left">Hora</th>
<th className="px-3 py-2 text-left">Tamanho</th>
<th className="px-3 py-2 text-left">Caminho</th>
</tr>
</thead>
<tbody>
{logBackup.latest_backups.arquivos.map((item) => (
<tr key={item.nome_arquivo} className="border-t">
<td className="px-3 py-1">{item.nome_arquivo}</td>
<td className="px-3 py-1">{item.dados.hora}</td>
<td className="px-3 py-1">{item.dados.tamanho}</td>
<td className="px-3 py-1">{item.dados.caminho}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
<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>
{/* ===================================================== */}
{/* Aba: Avisos (Warnings) */}
{/* ===================================================== */}
<TabsContent value="warning">
{logWarning?.data?.warning && logWarning.data.warning.length > 0 ? (
<div className="mt-4 space-y-6">
{/* Cabeçalho com data e hora */}
<div>
{(() => {
const { formattedDate, formattedTime, isOutdated } =
formatLogDateTime(logWarning?.data.data, logWarning?.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 de Avisos desatualizado</span>}
</Badge>
);
})()}
</div>
{/* Card de lista de avisos */}
<Card>
<CardHeader>
<CardTitle>Lista de Avisos</CardTitle>
</CardHeader>
<CardContent>
<ScrollArea className="h-[400px] w-full rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-center w-[80px]">#</TableHead>
<TableHead>Mensagem</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{logWarning.data.warning.map((msg: string, index: number) => (
<TableRow key={index}>
<TableCell className="text-center">{index + 1}</TableCell>
<TableCell className="text-sm text-gray-800">{msg}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</ScrollArea>
</CardContent>
</Card>
</div>
) : (
<p className="p-4 text-gray-500 italic">
Nenhum aviso disponível para este cliente.
</p>
)}
</TabsContent>
</Tabs>
</div>
);
}

View file

@ -0,0 +1,156 @@
'use client';
import { useEffect, useState, useCallback } from 'react';
// Componentes de UI Genéricos
import z from 'zod';
import { Card, CardContent } from '@/components/ui/card';
import Loading from '@/shared/components/loading/loading';
import Header from '@/shared/components/structure/Header';
import ConfirmDialog from '@/shared/components/confirmDialog/ConfirmDialog';
import { useConfirmDialog } from '@/shared/components/confirmDialog/useConfirmDialog';
// Componentes de UI Específicos de cliente
import ClientTable from '@/packages/administrativo/components/Client/ClientTable';
import ClientForm from '@/packages/administrativo/components/Client/ClientForm';
// Hooks Específicos de cliente
import { useClientIndexHook } from '@/packages/administrativo/hooks/Client/useClientIndexHook';
import { useClientSaveHook } from '@/packages/administrativo/hooks/Client/useClientSaveHook';
import { useClientDeleteHook } from '@/packages/administrativo/hooks/Client/useClientDeleteHook';
// Interface
import { ClientInterface } from '@/packages/administrativo/interfaces/Client/ClientInterface';
import { ClientSchema } from '@/packages/administrativo/schemas/Client/ClientSchema';
import { SituacoesEnum } from '@/shared/enums/SituacoesEnum';
const initialClient: ClientInterface = {
client_id: 0, // ID inicial padrão (auto incremento no banco)
cns: '', // Código opcional de 10 caracteres
name: '', // Nome obrigatório
date_register: new Date().toISOString(), // Data atual no formato ISO
state: '', // UF (2 caracteres)
city: '', // Nome da cidade
responsible: '', // Responsável pelo cliente
consultant: '', // Consultor associado
type_contract: '', // Tipo de contrato (ex: A, M, S...)
}
export default function ClientsPage() {
const [buttonIsLoading, setButtonIsLoading] = useState(false);
// 1. Hooks de dados para cliente
const { clients, fetchClients } = useClientIndexHook();
const { saveClient } = useClientSaveHook();
const { removeClient } = useClientDeleteHook(); // Presumindo que o hook existe e expõe `removeclient`
// 2. Estados da página
const [selectedClient, setSelectedClient] = useState<ClientInterface | null>(null);
const [isFormOpen, setIsFormOpen] = useState(false);
const [clientToDelete, setClientToDelete] = useState<ClientInterface | null>(null);
// Hook do modal de confirmação
const {
isOpen: isConfirmOpen,
openDialog: openConfirmDialog,
handleCancel,
} = useConfirmDialog();
// 3. Funções de manipulação do formulário
const handleOpenForm = useCallback((client: ClientInterface | null) => {
setSelectedClient(client);
setIsFormOpen(true);
}, []);
const handleCloseForm = useCallback(() => {
setSelectedClient(null);
setIsFormOpen(false);
}, []);
// 4. Função para salvar (criar ou editar)
const handleSave = useCallback(
async (formData: ClientInterface) => {
setButtonIsLoading(true);
await saveClient(formData);
setButtonIsLoading(false);
setIsFormOpen(false);
fetchClients(); // Atualiza a lista de clientes
},
[saveClient, fetchClients, handleCloseForm],
);
// 5. Funções para exclusão
const handleConfirmDelete = useCallback(
(client: ClientInterface) => {
setClientToDelete(client);
openConfirmDialog();
},
[openConfirmDialog],
);
const handleDelete = useCallback(async () => {
if (!clientToDelete) return;
await removeClient(clientToDelete); // Chama o hook de remoção
await fetchClients(); // Atualiza a lista
setClientToDelete(null); // Limpa o estado
handleCancel(); // Fecha o modal de confirmação
}, [clientToDelete, fetchClients, handleCancel]);
// 6. Busca inicial dos dados
useEffect(() => {
fetchClients();
}, []);
// 7. Renderização condicional de loading
if (clients?.length == 0) {
return <Loading type={2} />;
}
// 8. Renderização da página
return (
<div>
<Header
title={'Clientes'}
description={'Gerenciamento de Clientes do Sistema'}
buttonText={'Novo Cliente'}
buttonAction={(data) => handleOpenForm(data = initialClient)}
/>
<Card>
<CardContent>
<ClientTable
data={clients}
onEdit={handleOpenForm}
onDelete={handleConfirmDelete}
/>
</CardContent>
</Card>
<ConfirmDialog
isOpen={isConfirmOpen}
title="Confirmar exclusão"
description="Esta ação não pode ser desfeita."
message={`Deseja realmente excluir o Cliente "${clientToDelete?.name}"?`}
confirmText="Sim, excluir"
cancelText="Cancelar"
onConfirm={handleDelete}
onCancel={handleCancel}
/>
<ClientForm
isOpen={isFormOpen}
data={selectedClient}
onClose={handleCloseForm}
onSave={handleSave}
buttonIsLoading={buttonIsLoading}
/>
</div>
);
}

View file

@ -0,0 +1,155 @@
'use client';
import { useEffect, useState, useCallback } from 'react';
// Componentes de UI Genéricos
import z from 'zod';
import { Card, CardContent } from '@/components/ui/card';
import Loading from '@/shared/components/loading/loading';
import Header from '@/shared/components/structure/Header';
import ConfirmDialog from '@/shared/components/confirmDialog/ConfirmDialog';
import { useConfirmDialog } from '@/shared/components/confirmDialog/useConfirmDialog';
// Componentes de UI Específicos de Usuário
import UserTable from '@/packages/administrativo/components/User/UserTable';
import UserForm from '@/packages/administrativo/components/User/UserForm';
// Hooks Específicos de Usuário
import { useUserIndexHook } from '@/packages/administrativo/hooks/User/useUserIndexHook';
import { useUserSaveHook } from '@/packages/administrativo/hooks/User/useUserSaveHook';
import { useUserDeleteHook } from '@/packages/administrativo/hooks/User/useUserDeleteHook';
// Interface
import { UserInterface } from '@/packages/administrativo/interfaces/User/UserInterface';
import { UserSchema } from '@/packages/administrativo/schemas/User/UserSchema';
import { SituacoesEnum } from '@/shared/enums/SituacoesEnum';
const initialUser: UserInterface = {
user_id: 0,
name: '',
email: '',
password: '',
team: '',
position: 'string',
status: SituacoesEnum.ATIVO,
user_id_create: null,
user_id_update: null,
}
export default function UsersPage() {
const [buttonIsLoading, setButtonIsLoading] = useState(false);
// 1. Hooks de dados para Usuário
const { usuarios, fetchUsuarios } = useUserIndexHook();
const { saveUser } = useUserSaveHook();
const { removeUser } = useUserDeleteHook(); // Presumindo que o hook existe e expõe `removeUser`
// 2. Estados da página
const [selectedUser, setSelectedUser] = useState<UserInterface | null>(null);
const [isFormOpen, setIsFormOpen] = useState(false);
const [userToDelete, setUserToDelete] = useState<UserInterface | null>(null);
// Hook do modal de confirmação
const {
isOpen: isConfirmOpen,
openDialog: openConfirmDialog,
handleCancel,
} = useConfirmDialog();
// 3. Funções de manipulação do formulário
const handleOpenForm = useCallback((user: UserInterface | null) => {
setSelectedUser(user);
setIsFormOpen(true);
}, []);
const handleCloseForm = useCallback(() => {
setSelectedUser(null);
setIsFormOpen(false);
}, []);
// 4. Função para salvar (criar ou editar)
const handleSave = useCallback(
async (formData: UserInterface) => {
setButtonIsLoading(true);
await saveUser(formData);
setButtonIsLoading(false);
setIsFormOpen(false);
fetchUsuarios(); // Atualiza a lista de usuários
},
[saveUser, fetchUsuarios, handleCloseForm],
);
// 5. Funções para exclusão
const handleConfirmDelete = useCallback(
(user: UserInterface) => {
setUserToDelete(user);
openConfirmDialog();
},
[openConfirmDialog],
);
const handleDelete = useCallback(async () => {
if (!userToDelete) return;
await removeUser(userToDelete); // Chama o hook de remoção
await fetchUsuarios(); // Atualiza a lista
setUserToDelete(null); // Limpa o estado
handleCancel(); // Fecha o modal de confirmação
}, [userToDelete, fetchUsuarios, handleCancel]);
// 6. Busca inicial dos dados
useEffect(() => {
fetchUsuarios();
}, []);
// 7. Renderização condicional de loading
if (usuarios?.length == 0) {
return <Loading type={2} />;
}
// 8. Renderização da página
return (
<div>
<Header
title={'Usuários'}
description={'Gerenciamento de Usuários do Sistema'}
buttonText={'Novo Usuário'}
buttonAction={(data) => handleOpenForm(data = initialUser)}
/>
<Card>
<CardContent>
<UserTable
data={usuarios}
onEdit={handleOpenForm}
onDelete={handleConfirmDelete}
/>
</CardContent>
</Card>
<ConfirmDialog
isOpen={isConfirmOpen}
title="Confirmar exclusão"
description="Esta ação não pode ser desfeita."
message={`Deseja realmente excluir o usuário "${userToDelete?.name}"?`}
confirmText="Sim, excluir"
cancelText="Cancelar"
onConfirm={handleDelete}
onCancel={handleCancel}
/>
<UserForm
isOpen={isFormOpen}
data={selectedUser}
onClose={handleCloseForm}
onSave={handleSave}
buttonIsLoading={buttonIsLoading}
/>
</div>
);
}

View file

@ -28,7 +28,7 @@ const geistMono = Geist_Mono({
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'SAAS - Orius Tecnologia', title: 'Monitoring App - Orius Tecnologia',
description: 'Evolução tecnológica com toque humano', description: 'Evolução tecnológica com toque humano',
icons: { icons: {
icon: '/images/favicon.ico', icon: '/images/favicon.ico',
@ -56,14 +56,14 @@ export default function RootLayout({
<Breadcrumb> <Breadcrumb>
<BreadcrumbList> <BreadcrumbList>
<BreadcrumbItem className="hidden md:block"> <BreadcrumbItem className="hidden md:block">
<BreadcrumbLink href="#"> <BreadcrumbLink href="/">
Building Your Application Orius Tecnologia
</BreadcrumbLink> </BreadcrumbLink>
</BreadcrumbItem> </BreadcrumbItem>
<BreadcrumbSeparator className="hidden md:block" /> <BreadcrumbSeparator className="hidden md:block" />
<BreadcrumbItem> <BreadcrumbItem>
<BreadcrumbPage> <BreadcrumbPage>
Data Fetching Monitoramento de Servidores
</BreadcrumbPage> </BreadcrumbPage>
</BreadcrumbItem> </BreadcrumbItem>
</BreadcrumbList> </BreadcrumbList>

View file

@ -1,3 +1,17 @@
'use client'; // Necessário, pois usaremos hooks do React
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
export default function Page() { export default function Page() {
return <div></div>; const router = useRouter();
}
useEffect(() => {
//Redireciona para a tela de clientes
router.replace('/administrativo/clientes');
}, [router]);
return null; // Página em branco (não renderiza nada)
}

View file

@ -1,4 +1,4 @@
import { LoginForm } from '@/packages/administrativo/components/GUsuario/GUsuarioLoginForm'; import { LoginForm } from '@/packages/administrativo/components/User/UserLoginForm';
export default function LoginPage() { export default function LoginPage() {
return ( return (

View file

@ -0,0 +1,30 @@
import { NextRequest, NextResponse } from 'next/server';
import { jwtDecode } from 'jwt-decode';
import GetSigla from '@/shared/actions/text/GetSigla';
import UserAuthenticatedInterface from '@/shared/interfaces/UserAuthenticatedInterface';
interface JwtPayload {
id: string;
iat: number;
exp: number;
data?: UserAuthenticatedInterface;
}
export async function GET(req: NextRequest) {
try {
const token = req.cookies.get('access_token')?.value;
if (!token) return NextResponse.json(null);
const decoded: JwtPayload = jwtDecode(token);
if (decoded.data && typeof decoded.data === 'string') {
decoded.data = JSON.parse(decoded.data);
}
if (decoded.data) {
decoded.data.sigla = GetSigla(decoded.data.nome || '');
}
return NextResponse.json(decoded);
} catch (err) {
return NextResponse.json(null);
}
}

View file

@ -0,0 +1,17 @@
import { SituacoesEnum } from '@/shared/enums/SituacoesEnum';
/**
* Exibe um selo (badge) visual para status do usuário: Ativo / Inativo
*/
export function StatusBadge({ status }: { status: SituacoesEnum }) {
const isActive = status === 'A';
const baseClasses = 'text-xs font-medium px-2.5 py-0.5 rounded-sm me-2';
const activeClasses = 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300';
const inactiveClasses = 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300';
return (
<span className={`${baseClasses} ${isActive ? activeClasses : inactiveClasses}`}>
{isActive ? 'Ativo' : 'Inativo'}
</span>
);
}

View file

@ -8,7 +8,6 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { NavMain } from '@/components/nav-main'; import { NavMain } from '@/components/nav-main';
import { NavProjects } from '@/components/nav-projects';
import { NavUser } from '@/components/nav-user'; import { NavUser } from '@/components/nav-user';
import { import {
Sidebar, Sidebar,
@ -21,7 +20,7 @@ import {
SidebarRail, SidebarRail,
} from '@/components/ui/sidebar'; } from '@/components/ui/sidebar';
import useGUsuarioGetJWTHook from '@/shared/hooks/auth/useGUsuarioGetJWTHook'; import useUserGetJWTHook from '@/shared/hooks/auth/useUserGetJWTHook';
import Image from 'next/image'; import Image from 'next/image';
// This is sample data. // This is sample data.
@ -35,8 +34,8 @@ const data = {
isActive: false, isActive: false,
items: [ items: [
{ {
title: 'Dashboard', title: 'Clientes',
url: '/dashboard/', url: '/administrativo/clientes/',
}, },
], ],
}, },
@ -48,16 +47,15 @@ const data = {
items: [ items: [
{ {
title: 'Usuários', title: 'Usuários',
url: '/usuarios/', url: '/administrativo/usuarios/',
}, },
], ],
}, },
], ]
projects: [],
}; };
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) { export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const { userAuthenticated } = useGUsuarioGetJWTHook(); const { userAuthenticated } = useUserGetJWTHook();
return ( return (
<Sidebar collapsible="icon" {...props}> <Sidebar collapsible="icon" {...props}>
<SidebarHeader> <SidebarHeader>
@ -77,10 +75,10 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
</div> </div>
<div className="flex flex-col gap-0.5 leading-none"> <div className="flex flex-col gap-0.5 leading-none">
<span className="font-semibold"> <span className="font-semibold">
Orius Tecnologia Monitoring App
</span> </span>
<span className=""> <span className="">
25.9.1 v1.0
</span> </span>
</div> </div>
</a> </a>
@ -90,7 +88,6 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
</SidebarHeader> </SidebarHeader>
<SidebarContent> <SidebarContent>
<NavMain items={data.navMain} /> <NavMain items={data.navMain} />
<NavProjects projects={data.projects} />
</SidebarContent> </SidebarContent>
<SidebarFooter> <SidebarFooter>
{userAuthenticated?.data ? <NavUser user={userAuthenticated.data} /> : 'Carregando...'} {userAuthenticated?.data ? <NavUser user={userAuthenticated.data} /> : 'Carregando...'}

View file

@ -43,7 +43,7 @@ export function NavMain({
> >
<SidebarMenuItem> <SidebarMenuItem>
<CollapsibleTrigger asChild> <CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}> <SidebarMenuButton tooltip={item.title} className='cursor-pointer'>
{item.icon && <item.icon />} {item.icon && <item.icon />}
<span>{item.title}</span> <span>{item.title}</span>

View file

@ -18,14 +18,14 @@ import {
useSidebar, useSidebar,
} from '@/components/ui/sidebar'; } from '@/components/ui/sidebar';
import GUsuarioAuthenticatedInterface from '@/shared/interfaces/GUsuarioAuthenticatedInterface'; import UserAuthenticatedInterface from '@/shared/interfaces/UserAuthenticatedInterface';
import ConfirmDialog from '@/shared/components/confirmDialog/ConfirmDialog'; import ConfirmDialog from '@/shared/components/confirmDialog/ConfirmDialog';
import { useGUsuarioLogoutHook } from '@/packages/administrativo/hooks/GUsuario/useGUsuarioLogoutHook'; import { useUserLogoutHook } from '@/packages/administrativo/hooks/User/useUserLogoutHook';
import { use, useCallback, useState } from 'react'; import { use, useCallback, useState } from 'react';
export function NavUser({ user }: { user: GUsuarioAuthenticatedInterface }) { export function NavUser({ user }: { user: UserAuthenticatedInterface }) {
// Hook para encerrar sessão // Hook para encerrar sessão
const { logoutUsuario } = useGUsuarioLogoutHook(); const { logoutUsuario } = useUserLogoutHook();
// Controle de exibição do formulário de confirmação // Controle de exibição do formulário de confirmação
const [isConfirmOpen, setIsConfirmOpen] = useState(false); const [isConfirmOpen, setIsConfirmOpen] = useState(false);
@ -37,10 +37,12 @@ export function NavUser({ user }: { user: GUsuarioAuthenticatedInterface }) {
setIsConfirmOpen(true); setIsConfirmOpen(true);
}, []); }, []);
// Consifrma ação de log-out
const handleLogoutConfirm = useCallback(async () => { const handleLogoutConfirm = useCallback(async () => {
logoutUsuario(); logoutUsuario();
}, []); }, []);
// Cancela ação de log-out
const handleLogoutCancel = useCallback(async () => { const handleLogoutCancel = useCallback(async () => {
setIsConfirmOpen(false); setIsConfirmOpen(false);
}, []); }, []);
@ -99,18 +101,18 @@ export function NavUser({ user }: { user: GUsuarioAuthenticatedInterface }) {
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuGroup> {/* <DropdownMenuGroup>
<DropdownMenuItem className="cursor-pointer"> <DropdownMenuItem className="cursor-pointer">
<Sparkles /> <Sparkles />
Configurações Configurações
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
<DropdownMenuSeparator /> <DropdownMenuSeparator /> */}
<DropdownMenuItem className="cursor-pointer" onClick={() => handleConfirmOpen()}> <DropdownMenuItem className="cursor-pointer" onClick={() => handleConfirmOpen()}>
<LogOut /> <LogOut />
Log out Sair do sistema
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>

View file

@ -0,0 +1,46 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
secondary:
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
function Badge({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "span"
return (
<Comp
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
}
export { Badge, badgeVariants }

View file

@ -0,0 +1,58 @@
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
function ScrollArea({
className,
children,
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
return (
<ScrollAreaPrimitive.Root
data-slot="scroll-area"
className={cn("relative", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
)
}
function ScrollBar({
className,
orientation = "vertical",
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return (
<ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar"
orientation={orientation}
className={cn(
"flex touch-none p-px transition-colors select-none",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb
data-slot="scroll-area-thumb"
className="bg-border relative flex-1 rounded-full"
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar>
)
}
export { ScrollArea, ScrollBar }

View file

@ -0,0 +1,16 @@
import { Loader2Icon } from "lucide-react"
import { cn } from "@/lib/utils"
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
return (
<Loader2Icon
role="status"
aria-label="Loading"
className={cn("size-4 animate-spin", className)}
{...props}
/>
)
}
export { Spinner }

9
src/config/app.json Normal file
View file

@ -0,0 +1,9 @@
{
"state": "go",
"api": {
"url": "https://monitoring-api.oriustecnologia.com/",
"prefix": "api/v1/",
"content_type": "application/json"
},
"api_debit": "https://admin.oriustecnologia.com/router.php"
}

View file

@ -4,7 +4,6 @@ import { NextResponse, type MiddlewareConfig, type NextRequest } from 'next/serv
const publicRoutes = [ const publicRoutes = [
{ path: '/login', whenAuthenticated: 'redirect' }, { path: '/login', whenAuthenticated: 'redirect' },
{ path: '/register', whenAuthenticated: 'redirect' }, { path: '/register', whenAuthenticated: 'redirect' },
{ path: '/', whenAuthenticated: 'next' },
] as const; ] as const;
const ROOT_PATH = '/login'; // url raiz const ROOT_PATH = '/login'; // url raiz
@ -14,37 +13,44 @@ export function middleware(request: NextRequest) {
const publicRoute = publicRoutes.find((route) => route.path === path); const publicRoute = publicRoutes.find((route) => route.path === path);
const authToken = request.cookies.get('access_token'); const authToken = request.cookies.get('access_token');
// 1. Não autenticado e rota pública → segue normal // Evita loop — se já estiver na página de login, não redireciona novamente
if (path === ROOT_PATH) {
return NextResponse.next();
}
// 1 Não autenticado e rota pública → segue normal
if (!authToken && publicRoute) { if (!authToken && publicRoute) {
return NextResponse.next(); return NextResponse.next();
} }
// 2. Não autenticado e rota privada → redireciona para raiz // 2 Não autenticado e rota privada → redireciona para login
if (!authToken && !publicRoute) { if (!authToken && !publicRoute) {
return NextResponse.redirect(new URL(ROOT_PATH, request.url)); return NextResponse.redirect(new URL(ROOT_PATH, request.url));
} }
// 3. Autenticado em rota pública com flag "redirect" → vai para raiz // 3 Autenticado em rota pública com flag "redirect" → vai para raiz
if (authToken && publicRoute && publicRoute.whenAuthenticated === 'redirect') { if (authToken && publicRoute && publicRoute.whenAuthenticated === 'redirect') {
return NextResponse.redirect(new URL(ROOT_PATH, request.url)); return NextResponse.redirect(new URL(ROOT_PATH, request.url));
} }
// 4. Autenticado em rota privada → valida token // 4 Autenticado em rota privada → valida token
if (authToken && !publicRoute) { if (authToken && !publicRoute) {
const decoded: any = jwt.decode(authToken.value); const decoded: any = jwt.decode(authToken.value);
// Token inválido → redireciona (uma vez)
if (!decoded || !decoded.exp) { if (!decoded || !decoded.exp) {
console.log('Token inválido'); console.log('Token inválido');
return NextResponse.redirect(new URL(ROOT_PATH, request.url)); return NextResponse.redirect(new URL(ROOT_PATH, request.url));
} }
// Verifica expiração
const currentTime = Math.floor(Date.now() / 1000); const currentTime = Math.floor(Date.now() / 1000);
if (decoded.exp <= currentTime) { if (decoded.exp <= currentTime) {
console.log('Token expirado'); console.log('Token expirado');
return NextResponse.redirect(new URL(ROOT_PATH, request.url)); return NextResponse.redirect(new URL(ROOT_PATH, request.url));
} }
// Token válido → segue
return NextResponse.next(); return NextResponse.next();
} }

View file

@ -0,0 +1,195 @@
'use client'; // Define que este componente será executado no lado do cliente (Client Component)
import z from 'zod';
import { useEffect } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
// Importação dos componentes da UI (botões, inputs, dialogs, etc.)
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
// Componente de botão com estado de carregamento
import LoadingButton from '@/shared/components/loadingButton/LoadingButton';
// Importa a interface tipada separadamente
import { UserFormInterface, FormValues } from '../../interfaces/User/UserFormInterface';
// Importa o hook que gerencia o formulário
import { useUserFormHook } from '../../hooks/User/useUserFormHook';
// Componente principal do formulário de usuário
export default function UserForm({ isOpen, data, onClose, onSave, buttonIsLoading }: UserFormInterface) {
// Usa o hook que centraliza toda a lógica do form
const form = useUserFormHook(data);
// Callback de erro da submissão do formulário
function onError(error: any) {
console.log("error", error);
}
return (
// Componente de diálogo (modal)
<Dialog
open={isOpen} // Define se o diálogo está aberto
onOpenChange={(open) => {
if (!open) onClose(null, false); // Fecha o diálogo quando necessário
}}
>
{/* Conteúdo do modal */}
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>{data?.user_id ? 'Editar Usuário' : 'Novo Usuário'}</DialogTitle>
<DialogDescription>Gerencie os dados do usuário aqui.</DialogDescription>
</DialogHeader>
{/* Wrapper do formulário com react-hook-form */}
<Form {...form}>
<form onSubmit={form.handleSubmit(onSave, onError)} className="space-y-4">
{/* Campo: Nome */}
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Nome</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite o nome completo" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Campo: Email */}
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" {...field} placeholder="exemplo@email.com" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Campo: Senha */}
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Senha</FormLabel>
<FormControl>
<Input
type="password"
{...field}
placeholder={data ? 'Deixe em branco para não alterar' : 'Digite a senha'}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Campo: Equipe */}
<FormField
control={form.control}
name="team"
render={({ field }) => (
<FormItem>
<FormLabel>Equipe</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite o nome da equipe" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Campo: Cargo */}
<FormField
control={form.control}
name="cargo"
render={({ field }) => (
<FormItem>
<FormLabel>Cargo</FormLabel>
<FormControl>
<Input {...field} value={field.value ?? ''} placeholder="Digite o cargo do usuário" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Campo: Status (Ativo/Inativo) */}
<Controller
name="status"
control={form.control}
render={({ field }) => (
<div className="flex items-center space-x-2">
<Checkbox
checked={field.value === 'A'}
onCheckedChange={(checked) => field.onChange(checked ? 'A' : 'I')}
/>
<Label>Ativo</Label>
</div>
)}
/>
{/* Rodapé do formulário (botões) */}
<DialogFooter className="mt-4">
<DialogClose asChild>
<Button
variant="outline"
type="button"
onClick={() => onClose(null, false)}
className="cursor-pointer"
>
Cancelar
</Button>
</DialogClose>
{/* Botão de salvar com loading */}
<LoadingButton
text={data?.user_id ? 'Salvar' : 'Cadastrar'}
textLoading="Aguarde..."
type="submit"
loading={buttonIsLoading}
/>
</DialogFooter>
{/* Campos ocultos para dados técnicos */}
<input type="hidden" {...form.register('user_id')} />
<input type="hidden" {...form.register('date_register')} />
<input type="hidden" {...form.register('date_update')} />
</form>
</Form>
</DialogContent>
</Dialog>
);
}

View file

@ -0,0 +1,212 @@
'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,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
// Í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) {
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<Record<number, FinancialStatus>>({});
// 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<number, FinancialStatus> = {}; // 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 (
<Table>
{/* Cabeçalho da tabela */}
<TableHeader>
<TableRow>
<TableHead>#</TableHead>
<TableHead>Status</TableHead>
<TableHead>Financeiro</TableHead>
<TableHead>CNS</TableHead>
<TableHead>Nome</TableHead>
<TableHead className="text-right">Ações</TableHead>
</TableRow>
</TableHeader>
{/* Corpo da tabela */}
<TableBody>
{Array.isArray(data) && data.length > 0 ? (
data.map((client) => (
<TableRow key={client.client_id}>
{/* Coluna: ID do cliente */}
<TableCell className="font-medium">{client.client_id}</TableCell>
{/* Coluna: Status cadastral (ativo, inativo, etc) */}
<TableCell>
<StatusBadge status={client.status as any} />
</TableCell>
{/* Coluna: Status financeiro retornado da API */}
<TableCell>
{financialStatus[client.client_id] ? (
<FinanceStatusDialog
triggerElement={
// Usar <button> garante que o elemento seja interativo e receba handlers clonados
<button
type="button"
className={
financialStatus[client.client_id].status === 'em atraso'
? 'text-red-600 font-semibold cursor-pointer underline underline-offset-2 bg-transparent border-0 p-0'
: 'text-green-600 font-semibold cursor-pointer underline underline-offset-2 bg-transparent border-0 p-0'
}
title={financialStatus[client.client_id].message}
>
{financialStatus[client.client_id].status.toUpperCase()}
{financialStatus[client.client_id].expired_count > 0 &&
` (${financialStatus[client.client_id].expired_count} pendência${
financialStatus[client.client_id].expired_count > 1 ? 's' : ''
})`}
</button>
}
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}
/>
) : (
<span className="text-gray-400 italic"><Spinner /></span>
)}
</TableCell>
{/* Coluna: CNS do cliente */}
<TableCell>{client.cns}</TableCell>
{/* Coluna: Nome do cliente */}
<TableCell>{client.name}</TableCell>
{/* Coluna: Ações (menu suspenso) */}
<TableCell className="text-right">
<DropdownMenu>
{/* Botão principal do menu */}
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" className="cursor-pointer">
<EllipsisIcon />
</Button>
</DropdownMenuTrigger>
{/* Conteúdo do menu suspenso */}
<DropdownMenuContent side="left" align="start">
<DropdownMenuGroup>
{/* Ação: Editar cliente */}
<DropdownMenuItem
className="cursor-pointer"
onSelect={() => onEdit(client)}
>
<PencilIcon className="mr-2 h-4 w-4" />
Editar
</DropdownMenuItem>
<DropdownMenuSeparator />
{/* Ação: Ir para dashboard do cliente */}
<DropdownMenuItem
className="cursor-pointer"
onSelect={() => router.push(`/administrativo/clientes/${client.client_id}`)}
>
<ChartPie className="mr-2 h-4 w-4" />
Dashboard
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))
) : (
// Caso não existam registros
<TableRow>
<TableCell colSpan={6} className="text-center text-muted-foreground">
Nenhum cliente encontrado.
</TableCell>
</TableRow>
)}
</TableBody>
{/* Rodapé da tabela com totalização */}
<TableFooter>
<TableRow>
<TableCell colSpan={5}>
Total de clientes: {pagination?.total_records ?? data?.length}
</TableCell>
</TableRow>
</TableFooter>
</Table>
);
}

View file

@ -1,126 +0,0 @@
import { ColumnDef } from "@tanstack/react-table";
import TImovelInterface from "../../interfaces/TImovel/TImovelInterface";
import { Button } from "@/components/ui/button";
import {
EllipsisIcon,
PencilIcon,
Trash2Icon,
} from "lucide-react";
import { FormatDateTime } from "@/shared/actions/dateTime/FormatDateTime";
import { FormatCEP } from "@/shared/actions/CEP/FormatCEP";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ImovelTipoRegistro } from "@/shared/enums/ImovelTipoRegistro";
import { SortableHeader } from "@/shared/components/dataTable/SortableHeader";
import GetCapitalize from "@/shared/actions/text/GetCapitalize";
export default function TImovelColumns(
onEdit: (item: TImovelInterface, isEditingFormStatus: boolean) => void,
onDelete: (item: TImovelInterface, isEditingFormStatus: boolean) => void
): ColumnDef<TImovelInterface>[] {
return [
// ID
{
accessorKey: "imovel_id",
header: ({ column }) => SortableHeader("#", column),
cell: ({ row }) => Number(row.getValue("imovel_id")),
enableSorting: false,
},
// Tipo Registro
{
accessorKey: "tipo_registro",
header: ({ column }) => SortableHeader("Tipo Registro", column),
cell: ({ row }) => {
const value = row.getValue("tipo_registro") as keyof typeof ImovelTipoRegistro;
return ImovelTipoRegistro[value] ?? value;
},
},
// Número
{
accessorKey: "numero",
header: ({ column }) => SortableHeader("Número", column),
cell: ({ row }) => row.getValue("numero"),
},
// UF / Cidade / Bairro
{
id: "uf_cidade_bairro",
accessorFn: (row) => row,
header: ({ column }) => SortableHeader("Cidade / UF / Bairro", column),
cell: ({ row }) => {
const imovel = row.original;
return (
<div className="flex flex-col">
<span className="font-semibold text-gray-900 capitalize">
{GetCapitalize(imovel.cidade)}/{imovel.uf}
</span>
<span className="text-sm text-gray-500">{GetCapitalize(imovel.gtbb_descricao)}</span>
</div>
);
},
sortingFn: (a, b) =>
(a.original.cartorio?.toLowerCase() || "").localeCompare(
b.original.cartorio?.toLowerCase() || ""
),
},
// CEP
{
accessorKey: "cep",
header: ({ column }) => SortableHeader("CEP", column),
cell: ({ row }) => FormatCEP(row.getValue("cep")),
},
// Data de Registro
{
accessorKey: "data_registro",
header: ({ column }) => SortableHeader("Cadastro", column),
cell: ({ row }) => FormatDateTime(row.getValue("data_registro")),
sortingFn: "datetime",
},
// Ações
{
id: "actions",
header: "Ações",
cell: ({ row }) => {
const imovel = row.original;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<EllipsisIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="left" align="start">
<DropdownMenuGroup>
<DropdownMenuItem onSelect={() => onEdit(imovel, true)}>
<PencilIcon className="mr-2 h-4 w-4" />
Editar
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="text-red-600"
onSelect={() => onDelete(imovel, true)}
>
<Trash2Icon className="mr-2 h-4 w-4" />
Remover
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
},
enableSorting: false,
enableHiding: false,
},
];
}

View file

@ -1,385 +0,0 @@
'use client';
import React, { useEffect } from 'react';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import LoadingButton from '@/shared/components/loadingButton/LoadingButton';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { CheckIcon, ChevronsUpDownIcon, HouseIcon, IdCardIcon } from 'lucide-react';
import { Select, SelectContent, SelectItem, SelectTrigger } from '@/components/ui/select';
import { ImovelTipoRegistro } from '@/shared/enums/ImovelTipoRegistro';
import { ImovelTipoClasseEnum } from '@/shared/enums/ImovelTipoClasseEnum';
import { ResetFormIfData } from '@/shared/actions/form/ResetFormIfData';
import { useGTBBairroReadHook } from '../../../../app/(protected)/(cadastros)/cadastros/_hooks/g_tb_bairro/useGTBBairroReadHook';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command';
import { cn } from '@/lib/utils';
import GetCapitalize from '@/shared/actions/text/GetCapitalize';
import { TImovelFormInterface } from '../../interfaces/TImovel/TImovelFormInterface';
import { useTImovelFormHook } from '../../hooks/TImovel/useTImovelFormHook';
import TImovelUnidadeUrbanoPage from '../TImovelUnidade/TImovelUnidadeUrbano/TImovelUnidadeUrbanoPage';
import TImovelUnidadeRuralPage from '../TImovelUnidade/TImovelUnidadeRural/TImovelUnidadeRuralPage';
import { parseNumberInput } from '@/shared/actions/form/parseNumberInput';
export default function TImovelForm({ isOpen, data, onClose, onSave, buttonIsLoading, tipoClasse }: TImovelFormInterface) {
const { gTBBairro, fetchGTBBairro } = useGTBBairroReadHook();
// Inicializa o react-hook-form com schema zod
const form = useTImovelFormHook();
// Atualiza o formulário quando recebe dados para edição
useEffect(() => {
// Se existir dados, reseta o formulário com os mesmos
ResetFormIfData(form, data);
// Função sincrona para carregamento de dados
async function loadData() {
// Busca os bairros
await fetchGTBBairro();
}
// Executa a função
loadData();
}, [data, form]);
return (
<Dialog
open={isOpen}
onOpenChange={(open) => {
if (!open) onClose(null, false);
}}
>
<DialogContent className="w-full max-w-full p-6 sm:max-w-3xl md:max-w-4xl lg:max-w-5xl">
<DialogHeader>
<DialogTitle>
Imóvel Urbano
</DialogTitle>
<DialogDescription>
Cadastro de imóvel urbano
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSave)} className="space-y-6">
{/* Tabs */}
<Tabs defaultValue="dadosDoImovel" className="space-y-4">
<TabsList className="flex w-full">
<TabsTrigger className="flex-1 text-center cursor-pointer" value="dadosDoImovel">
<HouseIcon className="me-1 inline" />
Dados do Imóvel
</TabsTrigger>
<TabsTrigger className="flex-1 text-center cursor-pointer" value="unidades">
<IdCardIcon className="inline" />
Unidades
</TabsTrigger>
</TabsList>
{/* Dados do Imóvel */}
<TabsContent value="dadosDoImovel" className="space-y-4">
<div className="grid w-full grid-cols-12 gap-4">
{/* UF */}
<div className="col-span-12 sm:col-span-6 md:col-span-2">
<FormField
control={form.control}
name="uf"
render={({ field }) => (
<FormItem>
<FormLabel>UF</FormLabel>
<FormControl>
<Input {...field} type='text' placeholder="UF" maxLength={2} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* CEP */}
<div className="col-span-12 sm:col-span-6 md:col-span-4">
<FormField
control={form.control}
name="cep"
render={({ field }) => (
<FormItem>
<FormLabel>CEP</FormLabel>
<FormControl>
<Input {...field} type='text' placeholder="Digite o CEP" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Cidade */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="cidade"
render={({ field }) => (
<FormItem>
<FormLabel>Cidade</FormLabel>
<FormControl>
<Input {...field} type="text" placeholder="Digite a cidade" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Bairro */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="tb_bairro_id"
render={({ field }) => {
const [open, setOpen] = React.useState(false);
return (
<FormItem>
<FormLabel>Bairro</FormLabel>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<FormControl className="w-full">
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="justify-between"
>
{field.value
? gTBBairro.find(
(item) =>
String(item.tb_bairro_id) === String(field.value),
)?.descricao
: 'Selecione...'}
<ChevronsUpDownIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="Buscar tipo logradouro..." />
<CommandList>
<CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
<CommandGroup>
{gTBBairro?.map((item) => (
<CommandItem
key={item.tb_bairro_id}
value={(item.descricao ?? '').toLowerCase()}
onSelect={() => {
field.onChange(Number(item.tb_bairro_id));
setOpen(false);
}}
>
<CheckIcon
className={cn(
'mr-2 h-4 w-4',
String(field.value) === String(item.descricao)
? 'opacity-100'
: 'opacity-0',
)}
/>
{GetCapitalize(item.descricao)}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<FormMessage />
</FormItem>
);
}}
/>
</div>
{/* Cartório */}
<div className="col-span-12 sm:col-span-6 md:col-span-2">
<FormField
control={form.control}
name="cartorio"
render={({ field }) => (
<FormItem>
<FormLabel>Cartório</FormLabel>
<FormControl>
<Input {...field} type='number' placeholder="Digite o cartório" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* CNS */}
<div className="col-span-12 sm:col-span-6 md:col-span-5">
<FormField
control={form.control}
name="cns"
render={({ field }) => (
<FormItem>
<FormLabel>CNS</FormLabel>
<FormControl>
<Input {...field} type="number" placeholder="Digite o CNS" onChange={e => field.onChange(parseNumberInput(e))} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Livro */}
<div className="col-span-12 sm:col-span-6 md:col-span-5">
<FormField
control={form.control}
name="livro"
render={({ field }) => (
<FormItem>
<FormLabel>Livro</FormLabel>
<FormControl>
<Input {...field} type='text' placeholder="Digite o livro" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Tipo Registro */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="tipo_registro"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Tipo Registro</FormLabel>
<FormControl>
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger className="w-full">
{field.value
? ImovelTipoRegistro[field.value as keyof typeof ImovelTipoRegistro]
: "Selecione"}
</SelectTrigger>
<SelectContent>
{Object.entries(ImovelTipoRegistro).map(([key, label]) => (
<SelectItem key={key} value={key} className="cursor-pointer">
{label}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Número */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="numero"
render={({ field }) => (
<FormItem>
<FormLabel>Número</FormLabel>
<FormControl>
<Input {...field} type="number" placeholder="Digite o número" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Número Letra */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="numero_letra"
render={({ field }) => (
<FormItem>
<FormLabel>Número Letra</FormLabel>
<FormControl>
<Input {...field} type='text' placeholder="Digite a letra" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Tipo Registro */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="tipo_classe"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Tipo Classe</FormLabel>
<FormControl>
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger className="w-full">
{field.value
? ImovelTipoClasseEnum[Number(field.value) as keyof typeof ImovelTipoClasseEnum]
: "Selecione"}
</SelectTrigger>
<SelectContent>
{Object.entries(ImovelTipoClasseEnum).map(([key, label]) => (
<SelectItem key={key} value={key} className="cursor-pointer">
{label}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
</TabsContent>
{/* Unidades */}
<TabsContent value="unidades" className="space-y-4">
{/* Conteúdo das unidades */}
{tipoClasse === 1 ? <TImovelUnidadeUrbanoPage imovel_id={data?.imovel_id} /> : null}
{/* Conteúdo das unidades */}
{tipoClasse === 3 ? <TImovelUnidadeRuralPage /> : null}
</TabsContent>
</Tabs>
{/* Rodapé do Dialog */}
<DialogFooter className="mt-4 flex flex-col sm:flex-row gap-2 justify-end">
<DialogClose asChild>
<Button variant="outline" type="button" onClick={() => onClose(null, false)}>
Cancelar
</Button>
</DialogClose>
<LoadingButton
text="Salvar"
textLoading="Aguarde..."
type="submit"
loading={buttonIsLoading}
/>
</DialogFooter>
{/* Campo oculto */}
<input type="hidden" {...form.register("imovel_id")} />
</form>
</Form>
</DialogContent>
</Dialog>
);
}

View file

@ -1,170 +0,0 @@
'use client';
import { useEffect, useState, useCallback } from 'react';
import Loading from '@/shared/components/loading/loading';
import { useTImovelIndexHook } from '@/packages/administrativo/hooks/TImovel/useTImovelIndexHook';
import { useTImovelSaveHook } from '@/packages/administrativo/hooks/TImovel/useTImovelSaveHook';
import { useTImovelDeleteHook } from '@/packages/administrativo/hooks/TImovel/useTImovelDeleteHook';
import ConfirmDialog from '@/shared/components/confirmDialog/ConfirmDialog';
import { useConfirmDialog } from '@/shared/components/confirmDialog/useConfirmDialog';
import TImovelInterface from '@/packages/administrativo/interfaces/TImovel/TImovelInterface';
import Header from '@/shared/components/structure/Header';
import { TImovelIndexInterface } from '../../interfaces/TImovel/TImovelIndexInterface';
import TImovelTable from './TImovelTable';
import TImovelForm from './TImovelForm';
export default function TImovelIndex({ pageTitle, pageDescription, tipoClasse }: TImovelIndexInterface) {
// Controle de estado do botão
const [buttonIsLoading, setButtonIsLoading] = useState(false);
// Hooks para leitura e salvamento
const { tImovel, indexTImovel } = useTImovelIndexHook();
const { saveTImovel } = useTImovelSaveHook();
const { deleteTImovel } = useTImovelDeleteHook();
// Estados
const [selectedAndamento, setSelectedAndamento] = useState<TImovelInterface | null>(null);
const [isFormOpen, setIsFormOpen] = useState(false);
// Estado para saber qual item será deletado
const [itemToDelete, setItemToDelete] = useState<TImovelInterface | null>(null);
/**
* Hook do modal de confirmação
*/
const {
isOpen: isConfirmOpen,
openDialog: openConfirmDialog,
handleConfirm,
handleCancel,
} = useConfirmDialog();
/**
* Abre o formulário no modo de edição ou criação
*/
const handleOpenForm = useCallback((data: TImovelInterface | null) => {
setSelectedAndamento(data);
setIsFormOpen(true);
}, []);
/**
* Fecha o formulário e limpa o andamento selecionado
*/
const handleCloseForm = useCallback(() => {
setSelectedAndamento(null);
setIsFormOpen(false);
}, []);
/**
* Salva os dados do formulário
*/
const handleSave = useCallback(
async (formData: TImovelInterface) => {
// Coloca o botão em estado de loading
setButtonIsLoading(true);
// Aguarda salvar o registro
await saveTImovel(formData);
// Remove o botão em estado de loading
setButtonIsLoading(false);
// Atualiza a lista de dados
indexTImovel();
},
[saveTImovel, indexTImovel, handleCloseForm],
);
/**
* Quando o usuário clica em "remover" na tabela
*/
const handleConfirmDelete = useCallback(
(item: TImovelInterface) => {
// Define o item atual para remoção
setItemToDelete(item);
// Abre o modal de confirmação
openConfirmDialog();
},
[openConfirmDialog],
);
/**
* Executa a exclusão de fato quando o usuário confirma
*/
const handleDelete = useCallback(async () => {
// Protege contra null
if (!itemToDelete) return;
// Executa o Hook de remoção
await deleteTImovel(itemToDelete);
// Atualiza a lista
await indexTImovel();
// Limpa o item selecionado
setItemToDelete(null);
// Fecha o modal
handleCancel();
}, [itemToDelete, indexTImovel, handleCancel]);
/**
* Busca inicial dos dados
*/
useEffect(() => {
indexTImovel();
}, []);
/**
* Tela de loading enquanto carrega os dados
*/
if (tImovel?.length == 0) {
return <Loading type={2} />;
}
return (
<div>
{/* Cabeçalho */}
<Header
title={pageTitle}
description={pageDescription}
buttonText={'Novo imóvel'}
buttonAction={() => {
handleOpenForm(null);
}}
/>
{/* Tabela de andamentos */}
<TImovelTable
data={tImovel}
onEdit={handleOpenForm}
onDelete={handleConfirmDelete}
/>
{/* Modal de confirmação */}
<ConfirmDialog
isOpen={isConfirmOpen}
title="Confirmar exclusão"
description="Atenção"
message={`Deseja realmente excluir o imóvel "${itemToDelete?.cidade}"?`}
confirmText="Sim, excluir"
cancelText="Cancelar"
onConfirm={handleDelete}
onCancel={handleCancel}
/>
{/* Formulário de criação/edição */}
<TImovelForm
isOpen={isFormOpen}
data={selectedAndamento}
onClose={handleCloseForm}
onSave={handleSave}
buttonIsLoading={buttonIsLoading}
tipoClasse={tipoClasse}
/>
</div>
);
}

View file

@ -1,22 +0,0 @@
'use client';
import { DataTable } from '@/shared/components/dataTable/DataTable';
import TImovelColumns from './TImovelColumns';
import TImovelTableInterface from '../../interfaces/TImovel/TImovelTabelInterface';
/**
* Componente principal da tabela
*/
export default function TImovelTable({ data, onEdit, onDelete }: TImovelTableInterface) {
const columns = TImovelColumns(onEdit, onDelete);
return (
<div>
<DataTable
data={data}
columns={columns}
filterColumn="numero"
filterPlaceholder="Buscar pelo numero de transcrição, matricula etc..."
/>
</div>
);
}

View file

@ -0,0 +1,195 @@
'use client'; // Define que este componente será executado no lado do cliente (Client Component)
import z from 'zod';
import { useEffect } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
// Importação dos componentes da UI (botões, inputs, dialogs, etc.)
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
// Componente de botão com estado de carregamento
import LoadingButton from '@/shared/components/loadingButton/LoadingButton';
// Importa a interface tipada separadamente
import { UserFormInterface, FormValues } from '../../interfaces/User/UserFormInterface';
// Importa o hook que gerencia o formulário
import { useUserFormHook } from '../../hooks/User/useUserFormHook';
// Componente principal do formulário de usuário
export default function UserForm({ isOpen, data, onClose, onSave, buttonIsLoading }: UserFormInterface) {
// Usa o hook que centraliza toda a lógica do form
const form = useUserFormHook(data);
// Callback de erro da submissão do formulário
function onError(error: any) {
console.log("error", error);
}
return (
// Componente de diálogo (modal)
<Dialog
open={isOpen} // Define se o diálogo está aberto
onOpenChange={(open) => {
if (!open) onClose(null, false); // Fecha o diálogo quando necessário
}}
>
{/* Conteúdo do modal */}
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>{data?.user_id ? 'Editar Usuário' : 'Novo Usuário'}</DialogTitle>
<DialogDescription>Gerencie os dados do usuário aqui.</DialogDescription>
</DialogHeader>
{/* Wrapper do formulário com react-hook-form */}
<Form {...form}>
<form onSubmit={form.handleSubmit(onSave, onError)} className="space-y-4">
{/* Campo: Nome */}
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Nome</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite o nome completo" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Campo: Email */}
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" {...field} placeholder="exemplo@email.com" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Campo: Senha */}
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Senha</FormLabel>
<FormControl>
<Input
type="password"
{...field}
placeholder={data ? 'Deixe em branco para não alterar' : 'Digite a senha'}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Campo: Equipe */}
<FormField
control={form.control}
name="team"
render={({ field }) => (
<FormItem>
<FormLabel>Equipe</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite o nome da equipe" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Campo: Cargo */}
<FormField
control={form.control}
name="position"
render={({ field }) => (
<FormItem>
<FormLabel>Cargo</FormLabel>
<FormControl>
<Input {...field} value={field.value ?? ''} placeholder="Digite o cargo do usuário" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Campo: Status (Ativo/Inativo) */}
<Controller
name="status"
control={form.control}
render={({ field }) => (
<div className="flex items-center space-x-2">
<Checkbox
checked={field.value === 'A'}
onCheckedChange={(checked) => field.onChange(checked ? 'A' : 'I')}
/>
<Label>Ativo</Label>
</div>
)}
/>
{/* Rodapé do formulário (botões) */}
<DialogFooter className="mt-4">
<DialogClose asChild>
<Button
variant="outline"
type="button"
onClick={() => onClose(null, false)}
className="cursor-pointer"
>
Cancelar
</Button>
</DialogClose>
{/* Botão de salvar com loading */}
<LoadingButton
text={data?.user_id ? 'Salvar' : 'Cadastrar'}
textLoading="Aguarde..."
type="submit"
loading={buttonIsLoading}
/>
</DialogFooter>
{/* Campos ocultos para dados técnicos */}
<input type="hidden" {...form.register('user_id')} />
<input type="hidden" {...form.register('date_register')} />
<input type="hidden" {...form.register('date_update')} />
</form>
</Form>
</DialogContent>
</Dialog>
);
}

View file

@ -4,40 +4,17 @@ import Image from 'next/image';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import z from 'zod'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { zodResolver } from '@hookform/resolvers/zod'; import { Checkbox } from '@/components/ui/checkbox';
import GUsuarioLoginService from '@/packages/administrativo/services/GUsuario/GUsuarioLoginService';
import { useForm } from 'react-hook-form';
import { useState } from 'react';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../../../../components/ui/form';
import LoadingButton from '@/shared/components/loadingButton/LoadingButton'; import LoadingButton from '@/shared/components/loadingButton/LoadingButton';
import { Button } from '../../../../components/ui/button'; import { useUserFormLoginHook } from '../../hooks/User/useUserFormLoginHook';
import { GUsuarioLoginSchema } from '@/packages/administrativo/schemas/GUsuario/GUsuarioLoginSchema'; import { UserFormLoginInterface } from '../../interfaces/User/UserFormLoginInterface';
type FormValues = z.infer<typeof GUsuarioLoginSchema>; /**
* Componente de login utiliza hook para gerenciar estado e lógica.
export function LoginForm({ className, ...props }: React.ComponentProps<'div'>) { */
const [loading, setLoading] = useState(false); export function LoginForm({ className, ...props }: UserFormLoginInterface) {
const { form, onSubmit, loading } = useUserFormLoginHook(); // Hook customizado
const form = useForm<FormValues>({
resolver: zodResolver(GUsuarioLoginSchema),
defaultValues: {
login: '',
senha_api: '',
},
});
// onSubmit agora recebe o evento do form através do handleSubmit
const onSubmit = async (values: FormValues) => {
// Ativa o estado de loading do botão
setLoading(true);
// Realiza o login
await GUsuarioLoginService(values);
// Removo o estado de loading do botão
setLoading(false);
};
return ( return (
<div className={cn('flex flex-col gap-6', className)} {...props}> <div className={cn('flex flex-col gap-6', className)} {...props}>
@ -45,15 +22,18 @@ export function LoginForm({ className, ...props }: React.ComponentProps<'div'>)
<CardContent className="grid p-0 md:grid-cols-2"> <CardContent className="grid p-0 md:grid-cols-2">
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-3 p-6 md:p-8"> <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-3 p-6 md:p-8">
{/* Cabeçalho */}
<div className="mb-6 flex flex-col items-center text-center"> <div className="mb-6 flex flex-col items-center text-center">
<h1 className="text-2xl font-bold">Bem vindo de volta!</h1> <h1 className="text-2xl font-bold">Bem-vindo de volta!</h1>
<p className="text-muted-foreground text-balance"> <p className="text-muted-foreground text-balance">
Entre na sua conta Orius Tecnologia. Entre na sua conta Orius Tecnologia.
</p> </p>
</div> </div>
{/* Campo: Login */}
<FormField <FormField
control={form.control} control={form.control}
name="login" name="email"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Login</FormLabel> <FormLabel>Login</FormLabel>
@ -64,9 +44,11 @@ export function LoginForm({ className, ...props }: React.ComponentProps<'div'>)
</FormItem> </FormItem>
)} )}
/> />
{/* Campo: Senha */}
<FormField <FormField
control={form.control} control={form.control}
name="senha_api" name="password"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Senha</FormLabel> <FormLabel>Senha</FormLabel>
@ -77,28 +59,35 @@ export function LoginForm({ className, ...props }: React.ComponentProps<'div'>)
</FormItem> </FormItem>
)} )}
/> />
{/* Botão de loading */}
{/* Checkbox: Lembrar acesso */}
<FormField
control={form.control}
name="rememberMe"
render={({ field }) => (
<FormItem className="flex items-center space-x-2 cursor-pointer">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel className="text-sm font-normal cursor-pointer">Lembrar acesso</FormLabel>
</FormItem>
)}
/>
{/* Botão de envio com loading */}
<LoadingButton <LoadingButton
text="Entrar" text="Entrar"
textLoading="Aguarde..." textLoading="Aguarde..."
type="submit" type="submit"
loading={loading} loading={loading}
/> />
<div className="after:border-border relative my-4 text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t">
<span className="bg-card text-muted-foreground relative z-10 px-2">
Ou entre em contato
</span>
</div>
<div className="grid grid-cols-2 gap-4">
<Button variant="outline" type="button" className="w-full">
Chamar no Whatsapp
</Button>
<Button variant="outline" type="button" className="w-full">
Chamar no Local
</Button>
</div>
</form> </form>
</Form> </Form>
{/* Lado direito (imagem) */}
<div className="bg-brand relative hidden items-center justify-center md:flex"> <div className="bg-brand relative hidden items-center justify-center md:flex">
<Image <Image
src="/images/logo-login.svg" src="/images/logo-login.svg"
@ -110,11 +99,12 @@ export function LoginForm({ className, ...props }: React.ComponentProps<'div'>)
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
{/* Rodapé */}
<div className="text-muted-foreground text-center text-xs"> <div className="text-muted-foreground text-center text-xs">
Ao clicar você concorda com <a href="#">Nossos termos de serviços</a> e{' '} Ao clicar você concorda com{' '}
<a href="#"> <a href="#">Nossos termos de serviços</a> e{' '}
Políticas de Privacidade <a href="#">Políticas de Privacidade</a>.
</a>.
</div> </div>
</div> </div>
); );

View file

@ -0,0 +1,83 @@
'use client';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { EllipsisIcon, PencilIcon, Trash2Icon } from 'lucide-react';
import { UserTableInterface } from '../../interfaces/User/UserTableInterface';
import { StatusBadge } from '@/components/StatusBadge';
export default function UserTable({ data, onEdit, onDelete }: UserTableInterface) {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>#</TableHead>
<TableHead>Status</TableHead>
<TableHead>Nome</TableHead>
<TableHead>Email</TableHead>
<TableHead>Equipe</TableHead>
<TableHead className="text-right">Ações</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((user) => (
<TableRow key={user.user_id}>
<TableCell className="font-medium">{user.user_id}</TableCell>
<TableCell>
<StatusBadge status={user.status} />
</TableCell>
<TableCell>{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>{user.team}</TableCell>
<TableCell className="text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" className="cursor-pointer">
<EllipsisIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="left" align="start">
<DropdownMenuGroup>
<DropdownMenuItem
className="cursor-pointer"
onSelect={() => onEdit(user)}
>
<PencilIcon className="mr-2 h-4 w-4" />
Editar
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="cursor-pointer"
onSelect={() => onDelete(user)}
>
<Trash2Icon className="mr-2 h-4 w-4" />
Remover
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
}

View file

@ -0,0 +1,32 @@
'use server'
// Define que este módulo será executado no lado do servidor (Server Action do Next.js)
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
// Importa o enumerador que contém os métodos HTTP disponíveis (GET, POST, PUT, DELETE)
import API from '@/shared/services/api/Api';
// Importa a classe responsável por centralizar e padronizar as chamadas HTTP na aplicação
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
// 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(client_id: number) {
// Cria uma nova instância da classe API, que gerencia headers, baseURL e envio das requisições
const api = new API();
// 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/${client_id}` // Define o endpoint incluindo o ID do cliente
});
// Retorna a resposta da API, contendo status, mensagem e possíveis dados adicionais
return response;
}
// Exporta a função encapsulada com o tratador global de erros
export const ClientDeleteData = withClientErrorHandler(executeClientDeleteData);
// `withClientErrorHandler` garante que qualquer exceção durante a execução da função seja capturada
// Isso permite um tratamento padronizado de erros e evita quebra no fluxo de execução do app

View file

@ -0,0 +1,39 @@
'use server';
// Indica que este módulo será executado no lado do servidor (Server Action do Next.js)
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
// Importa o enum com os tipos de métodos HTTP disponíveis (GET, POST, PUT, DELETE...)
import API from '@/shared/services/api/Api';
// Importa a classe responsável por centralizar chamadas à API (wrapper de fetch ou axios)
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
// Importa um decorador/função HOC que trata erros de forma padronizada nas requisições
/**
* Função principal responsável por buscar a lista de clientes na API.
* Executa uma requisição HTTP GET para o endpoint administrativo/client.
*/
async function executeClientIndexData() {
// Instancia o serviço de API para uso nesta função
const api = new API();
// Executa uma requisição GET para o endpoint administrativo/client/
// - Usa o método 'send' da classe API
// - Passa o método HTTP e o endpoint como parâmetros
const response = await api.send({
method: Methods.GET, // Método HTTP GET
endpoint: `administrativo/client/`, // Rota da API que retorna a lista de clientes
});
// Retorna a resposta obtida da API
return response;
}
/**
* Exporta a função encapsulada com o handler de erro.
* Caso ocorra falha na requisição, o withClientErrorHandler
* intercepta o erro e o trata de forma uniforme (ex: logging, toast, etc.)
*/
export const ClientIndexData = withClientErrorHandler(executeClientIndexData);

View file

@ -0,0 +1,42 @@
'use server'
// Define que este módulo será executado no lado do servidor (Server Action do Next.js)
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
// Importa o enumerador que define os métodos HTTP disponíveis (GET, POST, PUT, DELETE)
import API from '@/shared/services/api/Api';
// Importa a classe responsável por realizar as chamadas HTTP centralizadas da aplicação
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
// Importa o decorador que adiciona tratamento global de erros à função principal
import { ClientInterface } from '../../interfaces/Client/ClientInterface';
// Importa a tipagem do objeto de cliente, garantindo consistência nos dados enviados
// Função principal responsável por salvar (criar ou atualizar) os dados de um cliente
async function executeClientSaveData(form: ClientInterface) {
// Verifica se existe um `client_id`; se sim, trata-se de uma atualização (PUT), caso contrário, é um novo cadastro (POST)
const isUpdate = Boolean(form.client_id);
// Cria uma nova instância da classe API para enviar a requisição
const api = new API();
// Envia a requisição para o endpoint responsável por salvar os dados do cliente
const response = await api.send({
// Define o método HTTP dinamicamente com base no tipo de operação (POST ou PUT)
'method': isUpdate ? Methods.PUT : Methods.POST,
// Define o endpoint, incluindo o `client_id` se for atualização
'endpoint': `administrativo/client/${form.client_id || ''}`,
// Corpo da requisição contendo os dados do formulário
'body': form
});
// Retorna a resposta da API (pode conter status, dados ou mensagens)
return response;
}
// Exporta a função encapsulada com o tratador global de erros
export const ClientSaveData = withClientErrorHandler(executeClientSaveData);
// `withClientErrorHandler` assegura que qualquer erro durante a execução será capturado e tratado de forma padronizada

View file

@ -1,16 +0,0 @@
'use server';
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import API from '@/shared/services/api/Api';
export default async function GUsuarioLoginData(form: any) {
const api = new API();
// Realiza o envio dos dados
const response = await api.send({
method: Methods.POST,
endpoint: `administrativo/g_usuario/authenticate`,
body: form,
});
return response;
}

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 (Database Action do Next.js)
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
// Importa o enumerador que contém os métodos HTTP padronizados (GET, POST, PUT, DELETE)
import API from '@/shared/services/api/Api';
// Importa a classe responsável por realizar requisições HTTP à API backend
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
// Importa o wrapper que padroniza o tratamento de erros e respostas para o cliente
// Função principal responsável por buscar um usuário específico pelo seu ID
async function executeLogDatabaseData(client_id: number) {
// Cria uma nova instância da classe de comunicação com a API
const api = new API();
// Envia uma requisição GET ao endpoint que retorna os dados de um usuário específico
const response = await api.send({
'method': Methods.GET, // Define o método HTTP da requisição
'endpoint': `administrativo/log/database/${client_id}` // Monta dinamicamente o endpoint com o ID do usuário
});
// Retorna a resposta recebida da API (dados do usuário ou erro)
return response;
}
// Exporta a função encapsulada com o handler de erro
// Isso garante que exceções sejam tratadas de forma padronizada na camada superior
export const LogDatabaseData = withClientErrorHandler(executeLogDatabaseData);

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,31 @@
'use server'
// Indica que este módulo será executado no lado do servidor (Ged Action do Next.js)
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
// Importa o enumerador que contém os métodos HTTP padronizados (GET, POST, PUT, DELETE)
import API from '@/shared/services/api/Api';
// Importa a classe responsável por realizar requisições HTTP à API backend
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
// Importa o wrapper que padroniza o tratamento de erros e respostas para o cliente
// Função principal responsável por buscar um usuário específico pelo seu ID
async function executeLogGedData(client_id: number) {
// Cria uma nova instância da classe de comunicação com a API
const api = new API();
// Envia uma requisição GET ao endpoint que retorna os dados de um usuário específico
const response = await api.send({
'method': Methods.GET, // Define o método HTTP da requisição
'endpoint': `administrativo/log/ged/${client_id}` // Monta dinamicamente o endpoint com o ID do usuário
});
// Retorna a resposta recebida da API (dados do usuário ou erro)
return response;
}
// Exporta a função encapsulada com o handler de erro
// Isso garante que exceções sejam tratadas de forma padronizada na camada superior
export const LogGedData = withClientErrorHandler(executeLogGedData);

View file

@ -0,0 +1,31 @@
'use server'
// Indica que este módulo será executado no lado do servidor (Server 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 executeLogIndexByIDData(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/${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 LogIndexByClientIDData = withClientErrorHandler(executeLogIndexByIDData);

View file

@ -0,0 +1,39 @@
'use server';
// Indica que este módulo será executado no lado do servidor (Server Action do Next.js)
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
// Importa o enum com os tipos de métodos HTTP disponíveis (GET, POST, PUT, DELETE...)
import API from '@/shared/services/api/Api';
// Importa a classe responsável por centralizar chamadas à API (wrapper de fetch ou axios)
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
// Importa um decorador/função HOC que trata erros de forma padronizada nas requisições
/**
* Função principal responsável por buscar a lista de loges na API.
* Executa uma requisição HTTP GET para o endpoint administrativo/log.
*/
async function executeLogIndexData() {
// Instancia o serviço de API para uso nesta função
const api = new API();
// Executa uma requisição GET para o endpoint administrativo/log/
// - Usa o método 'send' da classe API
// - Passa o método HTTP e o endpoint como parâmetros
const response = await api.send({
method: Methods.GET, // Método HTTP GET
endpoint: `administrativo/log/`, // Rota da API que retorna a lista de loges
});
// Retorna a resposta obtida da API
return response;
}
/**
* Exporta a função encapsulada com o handler de erro.
* Caso ocorra falha na requisição, o withlogErrorHandler
* intercepta o erro e o trata de forma uniforme (ex: logging, toast, etc.)
*/
export const LogIndexData = withClientErrorHandler(executeLogIndexData);

View file

@ -0,0 +1,31 @@
'use server'
// Indica que este módulo será executado no lado do servidor (Server 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 executeLogServerData(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/server/${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 LogServerData = withClientErrorHandler(executeLogServerData);

View file

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

View file

@ -1,18 +0,0 @@
import { withClientErrorHandler } from "@/shared/actions/withClientErrorHandler/withClientErrorHandler";
import ApiResponseInterface from "@/shared/services/api/interfaces/ApiResponseInterface";
import API from "@/shared/services/api/Api";
import { Methods } from "@/shared/services/api/enums/ApiMethodEnum";
import TImovelInterface from "../../interfaces/TImovel/TImovelInterface";
async function executeTImovelDeleteData(data: TImovelInterface): Promise<ApiResponseInterface> {
const api = new API();
return await api.send({
method: Methods.DELETE,
endpoint: `administrativo/t_imovel/${data.imovel_id}`
});
}
export const TImovelDeleteData = withClientErrorHandler(executeTImovelDeleteData);

View file

@ -1,17 +0,0 @@
import { withClientErrorHandler } from "@/shared/actions/withClientErrorHandler/withClientErrorHandler";
import API from "@/shared/services/api/Api";
import { Methods } from "@/shared/services/api/enums/ApiMethodEnum";
import ApiResponseInterface from "@/shared/services/api/interfaces/ApiResponseInterface";
async function executeTImovelIndexData(): Promise<ApiResponseInterface> {
const api = new API();
return api.send({
method: Methods.GET,
endpoint: `administrativo/t_imovel/classe/1`
});
}
export const TImovelIndexData = withClientErrorHandler(executeTImovelIndexData);

View file

@ -1,24 +0,0 @@
import { withClientErrorHandler } from "@/shared/actions/withClientErrorHandler/withClientErrorHandler";
import TImovelInterface from "../../interfaces/TImovel/TImovelInterface";
import ApiResponseInterface from "@/shared/services/api/interfaces/ApiResponseInterface";
import API from "@/shared/services/api/Api";
import { Methods } from "@/shared/services/api/enums/ApiMethodEnum";
async function executeTImovelSaveData(data: TImovelInterface): Promise<ApiResponseInterface> {
// Verifica se existe ID da cidade para decidir se é atualização (PUT) ou criação (POST)
const isUpdate = Boolean(data.imovel_id);
// Instancia o cliente da API para enviar a requisição
const api = new API();
// Executa a requisição para a API com o método apropriado e envia os dados no corpo
return await api.send({
method: isUpdate ? Methods.PUT : Methods.POST, // PUT se atualizar, POST se criar
endpoint: `administrativo/t_imovel/${data.imovel_id || ''}`, // endpoint dinâmico
body: data, // payload enviado para a API
});
}
export const TImovelSaveData = withClientErrorHandler(executeTImovelSaveData);

View file

@ -0,0 +1,20 @@
'use server'
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import API from '@/shared/services/api/Api';
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
async function executeUserDeleteData(usuarioId: number) {
const api = new API();
const response = await api.send({
'method': Methods.DELETE,
'endpoint': `administrativo/user/${usuarioId}`
});
return response;
}
export const UserDeleteData = withClientErrorHandler(executeUserDeleteData)

View file

@ -0,0 +1,19 @@
'use server'
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import API from '@/shared/services/api/Api';
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
async function executeUserIndexByEmailData(email: string) {
const api = new API();
const response = await api.send({
'method': Methods.GET,
'endpoint': `administrativo/user/email?email=${email}`
});
return response;
}
export const UserIndexByEmailData = withClientErrorHandler(executeUserIndexByEmailData)

View file

@ -0,0 +1,31 @@
'use server'
// Indica que este módulo será executado no lado do servidor (Server 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 executeUserIndexByIDData(user_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/user/${user_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 UserIndexByIDData = withClientErrorHandler(executeUserIndexByIDData);

View file

@ -0,0 +1,19 @@
'use server'
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import API from '@/shared/services/api/Api';
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
async function executeUserIndexData() {
const api = new API();
const response = await api.send({
'method': Methods.GET,
'endpoint': `administrativo/user/`
});
return response;
}
export const UserIndexData = withClientErrorHandler(executeUserIndexData)

View file

@ -0,0 +1,19 @@
'use server'
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import API from '@/shared/services/api/Api';
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
async function executeUserLoggedIndexData() {
const api = new API();
const response = await api.send({
'method': Methods.GET,
'endpoint': `administrativo/user/me`
});
return response;
}
export const UserLoggedIndexData = withClientErrorHandler(executeUserLoggedIndexData)

View file

@ -0,0 +1,22 @@
'use server';
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import API from '@/shared/services/api/Api';
import { AuthenticateUserInterface } from '@/shared/interfaces/AuthenticateUserInterface';
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
export default async function UserLoginData(form: any) {
const api = new API();
const response = await api.send({
method: Methods.POST,
endpoint: `administrativo/user/authenticate`,
body: form,
});
console.log("resposta da api",response)
return response;
}

View file

@ -0,0 +1,20 @@
'use server'
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import API from '@/shared/services/api/Api';
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
async function executeUserReadData(usuarioId: number) {
const api = new API();
const response = await api.send({
'method': Methods.GET,
'endpoint': `administrativo/user/${usuarioId}`
});
return response
}
export const UserReadData = withClientErrorHandler(executeUserReadData)

View file

@ -0,0 +1,23 @@
'use server'
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import API from '@/shared/services/api/Api';
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
import { UserInterface } from '../../interfaces/User/UserInterface';
async function executeUserSaveData(form: UserInterface) {
const isUpdate = Boolean(form.user_id)
const api = new API();
const response = await api.send({
'method': isUpdate ? Methods.PUT : Methods.POST,
'endpoint': `administrativo/user/${form.user_id || ''}`,
'body': form
});
return response;
}
export const UserSaveData = withClientErrorHandler(executeUserSaveData)

View file

@ -0,0 +1,37 @@
'use client';
// Define que este módulo será executado no lado do cliente (Client Component do Next.js)
import { useState } from 'react';
// Importa o hook `useState` do React (embora não esteja sendo usado aqui, pode ser útil em versões futuras)
import { useResponse } from '@/shared/components/response/ResponseContext';
// Importa o hook de contexto responsável por exibir feedbacks globais (como toasts, alerts ou modais)
import { ClientDeleteService } from '../../services/Client/ClientDeleteService';
// Importa o serviço responsável por realizar a exclusão do cliente via API
import { ClientInterface } from '../../interfaces/Client/ClientInterface';
// Importa a tipagem do objeto `ClientInterface` para garantir segurança de tipo e padronização dos dados
// Hook personalizado responsável por encapsular a lógica de exclusão de clientes
export const useClientDeleteHook = () => {
// Obtém a função `setResponse` do contexto global, usada para exibir feedbacks ao usuário
const { setResponse } = useResponse();
// Função assíncrona que executa a exclusão de um cliente específico
const removeClient = async (client: ClientInterface) => {
try {
// Chama o serviço de exclusão, enviando o ID do cliente como parâmetro
const response = await ClientDeleteService(client.client_id);
// Define a resposta no contexto global, permitindo exibir mensagem de sucesso/erro na interface
setResponse(response);
} catch (error) {
// Captura e exibe o erro no console (embora o handler global já trate exceções)
console.error('Erro ao remover usuário:', error);
}
};
// Retorna a função principal de exclusão, permitindo que o componente que usa este hook a invoque
return { removeClient };
};

View file

@ -0,0 +1,43 @@
'use client';
// Define que este arquivo será executado no lado do cliente (Next.js Client Component)
import { useState } from 'react';
import { ClientInterface } from '../../interfaces/Client/ClientInterface';
import { ClientIndexService } from '../../services/Client/ClientIndexService';
import { useResponse } from '@/shared/components/response/ResponseContext';
// Hook personalizado responsável por gerenciar a listagem de clientes
export const useClientIndexHook = () => {
// Obtém a função para definir mensagens globais de resposta (toast, modal, etc.)
const { setResponse } = useResponse();
// Estado local que armazena a lista de clientes retornados pela API
const [clients, setClients] = useState<ClientInterface[]>([]);
// Função responsável por buscar os clientes da API
const fetchClients = async () => {
try {
// Chama o serviço que faz a requisição HTTP
const response = await ClientIndexService();
// Atualiza o estado local com os dados retornados
setClients(response.data);
// Define a resposta global (útil para exibir mensagens de sucesso/erro)
setResponse(response);
} catch (error) {
// Caso ocorra erro na requisição, registra no console
console.error('Erro ao buscar clientes:', error);
// Atualiza o contexto de resposta com erro
setResponse({
status: 'error',
message: 'Falha ao carregar a lista de clientes.',
data: [],
});
}
};
// Retorna as variáveis e funções que o componente poderá utilizar
return { clients, fetchClients };
};

View file

@ -0,0 +1,43 @@
'use client';
// Indica que este arquivo será executado no lado do cliente (Client Component do Next.js)
import { useState } from 'react';
import { ClientInterface } from '../../interfaces/Client/ClientInterface';
import { ClientSaveService } from '../../services/Client/ClientSaveService';
import { useResponse } from '@/shared/components/response/ResponseContext'; // 🔧 corrigido nome de import (clientesponse → clientResponse)
// Hook personalizado responsável por salvar (criar ou atualizar) clientes
export const useClientSaveHook = () => {
// Obtém a função global para definir mensagens de resposta (toast, modal, etc.)
const { setResponse } = useResponse();
// Estado local para armazenar o cliente salvo/retornado pela API
const [cliente, setCliente] = useState<ClientInterface>();
// Função responsável por enviar os dados do cliente para a API
const saveClient = async (clientData: ClientInterface) => {
try {
// Faz a chamada ao serviço que salva os dados do cliente
const response = await ClientSaveService(clientData);
// Atualiza o estado com o cliente retornado (pode conter ID ou dados processados)
setCliente(response.data);
// Define a resposta global (útil para feedback de sucesso)
setResponse(response);
} catch (error) {
// Em caso de erro, exibe no console para depuração
console.error('Erro ao salvar cliente:', error);
// Define resposta de erro global
setResponse({
status: 'error',
message: 'Falha ao salvar o cliente.',
data: null,
});
}
};
// Retorna o cliente salvo e a função responsável por salvar
return { cliente, saveClient };
};

View file

@ -1,11 +0,0 @@
'use client';
import GUsuarioLogoutService from '../../services/GUsuario/GUsuarioLogoutService';
export const useGUsuarioLogoutHook = () => {
const logoutUsuario = async () => {
await GUsuarioLogoutService('access_token');
};
return { logoutUsuario };
};

View file

@ -0,0 +1,58 @@
'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);
// Se a estrutura for { success, message, data }, use response.data
const logData =
response?.data && response.data.backup ? response.data : response;
// --- ETAPA 1: ordenar backups por data ---
if (logData.backup) {
// Converte o objeto de backups em array [nomeArquivo, dados]
const sortedBackups = Object.entries(logData.backup).sort(([, a], [, b]) => {
const dateA = a.data.split('/').reverse().join('-');
const dateB = b.data.split('/').reverse().join('-');
return new Date(dateB).getTime() - new Date(dateA).getTime(); // Mais recente primeiro
});
// Reconstrói o objeto ordenado
logData.backup = Object.fromEntries(sortedBackups);
// --- ETAPA 2: obter a data mais recente ---
const latestDate = sortedBackups[0][1].data;
// --- ETAPA 3: filtrar todos os backups dessa data ---
const latestBackups = sortedBackups
.filter(([, dados]) => dados.data === latestDate)
.map(([nome_arquivo, dados]) => ({ nome_arquivo, dados }));
// --- ETAPA 4: adicionar campo customizado no JSON ---
logData.latest_backups = {
data: latestDate,
arquivos: latestBackups,
};
}
// Armazena os dados atualizados no estado
setLog(logData);
setResponse(response);
} 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 { LogDatabaseInterface } from '../../interfaces/Log/LogDatabaseInterface';
import { LogDatabaseService } from '../../services/Log/LogDatabaseService';
import { useResponse } from '@/shared/components/response/ResponseContext';
export const useLogDatabaseHook = () => {
const { setResponse } = useResponse();
const [logDatabase, setLog] = useState<LogDatabaseInterface | null>(null);
const fetchLogDatabase = async (client_id: number) => {
try {
const response = await LogDatabaseService(client_id);
setLog(response as LogDatabaseInterface);
setResponse(response);
} catch (error) {
console.error("Erro ao buscar informação do banco de dados:", error);
}
};
return { logDatabase, fetchLogDatabase };
};

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 @@
'use client';
import { useState } from 'react';
import { LogGedInterface } from '../../interfaces/Log/LogGedInterface';
import { LogGedService } from '../../services/Log/LogGedService';
import { useResponse } from '@/shared/components/response/ResponseContext';
export const useLogGedHook = () => {
const { setResponse } = useResponse();
const [logGed, setLog] = useState<LogGedInterface | null>(null);
const fetchLogGed = async (client_id: number) => {
try {
const response = await LogGedService(client_id);
setLog(response as LogGedInterface);
setResponse(response);
} catch (error) {
console.error("Erro ao buscar informação do banco de dados:", error);
}
};
return { logGed, fetchLogGed };
};

View file

@ -0,0 +1,27 @@
'use client';
import { useState } from 'react';
import { LogInterface } from '../../interfaces/Log/LogInterface';
import { LogServerService } from '../../services/Log/LogServerService';
import { useResponse } from '@/shared/components/response/ResponseContext';
export const useLogServerHook = () => {
const { setResponse } = useResponse();
const [logServer, setLog] = useState<LogInterface | null>(null);
const fetchLogServer = async (client_id: number) => {
try {
const response = await LogServerService(client_id);
setLog(response.data);
setResponse(response);
} catch (error) {
// O withClientErrorHandler já deve tratar o erro e formatar a 'response',
// mas um catch local pode ser útil para lógicas adicionais se necessário.
console.error("Erro ao buscar informação do servidor por ID:", error);
}
};
return { logServer, fetchLogServer };
};

View file

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

View file

@ -1,20 +0,0 @@
import { useResponse } from '@/shared/components/response/ResponseContext';
import { useState } from 'react';
import TImovelInterface from '../../interfaces/TImovel/TImovelInterface';
import { TImovelDeleteService } from '../../services/TImovel/TImovelDeleteService';
export const useTImovelDeleteHook = () => {
const { setResponse } = useResponse();
const [tImovel, setTImovel] = useState<TImovelInterface>();
const deleteTImovel = async (data: TImovelInterface) => {
const response = await TImovelDeleteService(data);
setTImovel(data);
setResponse(response);
};
return { tImovel, deleteTImovel };
};

View file

@ -1,12 +0,0 @@
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { TImovelFormValues, TImovelSchema } from "../../schemas/TImovel/TImovelSchema";
export function useTImovelFormHook(defaults?: Partial<TImovelFormValues>) {
return useForm<TImovelFormValues>({
resolver: zodResolver(TImovelSchema),
defaultValues: {
...defaults,
},
});
}

View file

@ -1,27 +0,0 @@
'use client';
import { useResponse } from '@/shared/components/response/ResponseContext';
import { useState } from 'react';
import TImovelInterface from '../../interfaces/TImovel/TImovelInterface';
import { TImovelIndexData } from '../../data/TImovel/TImovelIndexData';
export const useTImovelIndexHook = () => {
const { setResponse } = useResponse();
const [tImovel, setTImovel] = useState<TImovelInterface[]>([]);
const indexTImovel = async () => {
const response = await TImovelIndexData();
// Armazena os dados consultados
setTImovel(response.data);
// Define os dados do componente de resposta (toast, modal, etc)
setResponse(response);
};
return {
tImovel,
indexTImovel
};
};

View file

@ -1,33 +0,0 @@
'use client';
import { useResponse } from '@/shared/components/response/ResponseContext';
import { useState } from 'react';
import TImovelInterface from '../../interfaces/TImovel/TImovelInterface';
import { TImovelSaveService } from '../../services/TImovel/TImovelSaveService';
export const useTImovelSaveHook = () => {
const { setResponse } = useResponse();
const [tImovel, setTImovel] = useState<TImovelInterface>();
// controla se o formulário está aberto ou fechado
const [isOpen, setIsOpen] = useState(false);
const saveTImovel = async (data: TImovelInterface) => {
const response = await TImovelSaveService(data);
// Armazena os dados da repsota
setTImovel(response.data);
// Define os dados da respota(toast, modal, etc)
setResponse(response);
// Fecha o formulário automaticamente após salvar
setIsOpen(false);
// Retorna os valores de forma imediata
return response.data;
};
return { tImovel, saveTImovel };
};

View file

@ -0,0 +1,28 @@
'use client';
import { useState } from 'react';
import { useResponse } from '@/shared/components/response/ResponseContext';
import { UserDeleteService } from '../../services/User/UserDeleteService'; // Ajuste o caminho conforme necessário
import { UserInterface } from '../../interfaces/User/UserInterface'; // Ajuste o caminho
export const useUserDeleteHook = () => {
// Hook de contexto para fornecer feedback (toast, modal, etc.)
const { setResponse } = useResponse();
const removeUser = async (user: UserInterface) => {
try {
// Chama o serviço de exclusão, passando apenas o ID do usuário
const response = await UserDeleteService(user.user_id);
// Define a resposta para que o ResponseContext possa exibir um feedback
setResponse(response);
} catch (error) {
// O withClientErrorHandler já trata o erro, mas um log pode ser útil
console.error('Erro ao remover usuário:', error);
}
};
// Retorna a função de remoção e o estado de carregamento
return { removeUser };
};

View file

@ -0,0 +1,71 @@
'use client';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
// Importa o schema de validação (Zod)
import { UserSchema } from '../../schemas/User/UserSchema';
// Importa o enum com o status
import { SituacoesEnum } from '@/shared/enums/SituacoesEnum';
// Tipagem do formulário (interface compartilhada)
import { FormValues } from '../../interfaces/User/UserFormInterface';
/**
* Hook responsável por inicializar e gerenciar o estado do formulário de usuários.
* Centraliza a lógica de criação, reset e carregamento de dados.
*/
export function useUserFormHook(data: FormValues | null) {
// Inicializa o React Hook Form com validação baseada no Zod
const form = useForm<FormValues>({
resolver: zodResolver(UserSchema), // Aplica o schema para validação automática
defaultValues: {
user_id: 0,
name: '',
email: '',
password: '',
team: '',
cargo: '',
status: SituacoesEnum.ATIVO,
user_id_create: null,
user_id_update: null,
date_register: new Date().toISOString(),
date_update: null,
} as FormValues,
});
/**
* Efeito responsável por atualizar o formulário
* sempre que houver dados (modo edição) ou resetar (modo criação).
*/
useEffect(() => {
if (data) {
// Modo edição → carrega dados do usuário
form.reset({
...data,
password: '',
});
console.log('Form carregado com dados:', data);
} else {
// Modo criação → limpa o formulário
form.reset({
user_id: 0,
name: '',
email: '',
password: '',
team: '',
position: '',
status: SituacoesEnum.ATIVO,
user_id_create: null,
user_id_update: null,
date_register: new Date().toISOString(),
date_update: null,
});
}
}, [data]); // Atualiza sempre que "data" mudar
// Retorna o objeto form para uso no componente
return form;
}

View file

@ -0,0 +1,65 @@
'use client';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { UserLoginSchema } from '../../schemas/User/UserLoginSchema';
import UserLoginService from '../../services/User/UserLoginService';
import z from 'zod';
/**
* Tipagem dos valores do formulário.
* Inclui o campo "rememberMe" que não está no schema Zod original.
*/
export type LoginFormValues = z.infer<typeof UserLoginSchema> & {
rememberMe: boolean;
};
/**
* Hook responsável por gerenciar o formulário de login e o comportamento
* de lembrar acesso (localStorage + API).
*/
export function useUserFormLoginHook() {
const [loading, setLoading] = useState(false);
// Inicializa o formulário com validação Zod
const form = useForm<LoginFormValues>({
resolver: zodResolver(UserLoginSchema),
defaultValues: {
email: '',
password: '',
rememberMe: false,
},
});
// Carrega o e-mail salvo no localStorage, se existir
useEffect(() => {
const savedEmail = localStorage.getItem('remembered_email');
if (savedEmail) {
form.setValue('email', savedEmail);
form.setValue('rememberMe', true);
}
}, [form]);
/**
* Função de envio do formulário autentica o usuário e
* salva o e-mail no localStorage se o "Lembrar acesso" estiver marcado.
*/
const onSubmit = async (values: LoginFormValues) => {
try {
setLoading(true);
await UserLoginService(values);
if (values.rememberMe) {
localStorage.setItem('remembered_email', values.email);
} else {
localStorage.removeItem('remembered_email');
}
} finally {
setLoading(false);
}
};
// Retorna o formulário e os estados necessários para o componente
return { form, onSubmit, loading };
}

View file

@ -0,0 +1,25 @@
'use client';
import { useState } from 'react';
import { UserInterface } from '../../interfaces/User/UserInterface';
import { UserIndexByEmailService } from '../../services/User/UserIndexByEmailService';
import { useResponse } from '@/shared/components/response/ResponseContext';
export const useUserIndexByEmailHook = () => {
const { setResponse } = useResponse();
const [user, setUser] = useState<UserInterface | null>(null);
const fetchUserByEmail = async (email: string) => {
try {
const response = await UserIndexByEmailService(email);
setUser(response.data);
setResponse(response);
} catch (error) {
console.error("Erro ao buscar usuário por Email:", error);
}
};
return { user, fetchUserByEmail };
};

View file

@ -0,0 +1,27 @@
'use client';
import { useState } from 'react';
import { UserInterface } from '../../interfaces/User/UserInterface';
import { UserIndexByIDService } from '../../services/User/UserIndexByIDService';
import { useResponse } from '@/shared/components/response/ResponseContext';
export const useUserIndexByIdHook = () => {
const { setResponse } = useResponse();
const [user, setUser] = useState<UserInterface | null>(null);
const fetchUserById = async (userId: number) => {
try {
const response = await UserIndexByIDService(userId);
setUser(response.data);
setResponse(response);
} catch (error) {
// O withClientErrorHandler já deve tratar o erro e formatar a 'response',
// mas um catch local pode ser útil para lógicas adicionais se necessário.
console.error("Erro ao buscar usuário por ID:", error);
}
};
return { user, fetchUserById };
};

View file

@ -0,0 +1,23 @@
'use client';
import { useState } from 'react';
import { UserInterface } from '../../interfaces/User/UserInterface';
import { UserIndexService } from '../../services/User/UserIndexService';
import { useResponse } from '@/shared/components/response/ResponseContext';
export const useUserIndexHook = () => {
const { setResponse } = useResponse();
const [usuarios, setUsuarios] = useState<UserInterface[]>([]);
const fetchUsuarios = async () => {
const response = await UserIndexService();
setUsuarios(response.data);
// Define os dados do componente de resposta (toast, modal, etc)
setResponse(response);
};
return { usuarios, fetchUsuarios };
};

View file

@ -0,0 +1,26 @@
'use client'; // Indica que este código roda no lado do cliente (Next.js)
import { useRouter } from 'next/navigation'; // Hook do Next.js para redirecionamentos no cliente
import { UserLogoutService } from '../../services/User/UserLogoutService'; // Importa o serviço de logout
// Hook customizado responsável por encapsular a lógica de logout
export const useUserLogoutHook = () => {
const router = useRouter(); // Inicializa o roteador do Next.js
// Função assíncrona responsável por executar o logout
const logoutUsuario = async () => {
try {
// Chama o serviço no servidor para apagar o cookie do token
await UserLogoutService('access_token');
// Redireciona o usuário para a tela de login após o logout
router.push('/login');
} catch (error) {
// Captura e exibe eventuais erros no processo de logout
console.error('Erro ao fazer logout:', error);
}
};
// Retorna a função de logout para ser usada em qualquer componente
return { logoutUsuario };
};

View file

@ -0,0 +1,22 @@
'use client';
import { useState } from 'react';
import { UserInterface } from '../../interfaces/User/UserInterface';
import { UserReadService } from '../../services/User/UserReadService';
import { useResponse } from '@/shared/components/response/ResponseContext';
export const useUserReadHooks = () => {
const { setResponse } = useResponse();
const [User, setUser] = useState<UserInterface>();
const fetchUser = async (User: UserInterface) => {
const response = await UserReadService(User.user_id);
setUser(response.data);
setResponse(response);
};
return { User, fetchUser };
};

View file

@ -0,0 +1,22 @@
'use client';
import { useState } from 'react';
import { UserInterface } from '../../interfaces/User/UserInterface';
import { UserSaveService } from '../../services/User/UserSaveService';
import { useResponse } from '@/shared/components/response/ResponseContext';
export const useUserSaveHook = () => {
const { setResponse } = useResponse();
const [usuarios, setUser] = useState<UserInterface>();
const saveUser = async (User: any) => {
const response = await UserSaveService(User);
setUser(response.data);
setResponse(response);
};
return { usuarios, saveUser };
};

View file

@ -0,0 +1,16 @@
/**
* Interface que representa a tabela `client` do banco de dados `monitoring`.
* Cada campo reflete a tipagem e as restrições da DDL original.
*/
export interface ClientInterface {
client_id?: number; // ID único do cliente (chave primária, gerada automaticamente)
cns?: string | null; // Código CNS (campo opcional)
name: string; // Nome do cliente (campo obrigatório)
date_register?: string | null; // Data/hora do registro (timestamp gerado automaticamente)
state?: string | null; // Sigla do estado (ex: 'SP', 'RJ', etc.)
city?: string | null; // Nome da cidade
responsible?: string | null; // Responsável principal pelo cliente
consultant?: string | null; // Nome do consultor associado
type_contract?: string | null; // Tipo de contrato (ex: 'A' = anual, 'M' = mensal, etc.)
status?: string | null; // Tipo de contrato (ex: 'A' = Ativo, 'I' = Inativo, etc.)
}

View file

@ -0,0 +1,21 @@
import { ClientInterface } from './ClientInterface';
/**
* Interface que define as propriedades esperadas pelo componente ClientTable.
*/
export interface ClientTableInterface {
data: ClientInterface[]; // Lista de clientes a exibir
// Objeto opcional de paginação retornado pela API
pagination?: {
total_records: number; // Total de registros encontrados
total_pages: number; // Total de páginas disponíveis
current_page: number; // Página atual
next_page?: number | null; // Próxima página (opcional)
first?: number; // Quantidade padrão por página
skip?: number; // Offset inicial
};
onEdit: (client: ClientInterface) => void; // Ação ao clicar em editar
onDelete: (client: ClientInterface) => void; // Ação ao clicar em remover
}

View file

@ -1,34 +0,0 @@
export default interface GUsuario {
usuario_id: number;
trocarsenha: string;
login: string;
senha: string;
situacao: string;
nome_completo: string;
funcao: string;
assina: string;
sigla: string;
usuario_tab: string;
ultimo_login: string;
ultimo_login_regs: string;
data_expiracao: string;
senha_anterior: string;
andamento_padrao: string;
lembrete_pergunta: string;
lembrete_resposta: string;
andamento_padrao2: string;
receber_mensagem_arrolamento: string;
email: string;
assina_certidao: string;
receber_email_penhora: string;
foto: string;
nao_receber_chat_todos: string;
pode_alterar_caixa: string;
receber_chat_certidao_online: string;
receber_chat_cancelamento: string;
cpf: string;
somente_leitura: string;
receber_chat_envio_onr: string;
tipo_usuario: string;
senha_api: string;
}

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,25 @@
/**
* Interface que representa o log de banco de dados retornado pelo endpoint /log/database.
*/
export interface LogDatabaseInterface {
message?: string;
data: {
cns: string;
cartorio: string;
data: string;
hora: string;
database: {
partition: {
unit: string;
percent_free: number;
percent_used: number;
total_disk_space: string;
total_free_disk_space: string;
total_used_disk_space: string;
};
file_size_mb: string;
db_accessible: boolean;
last_modified: string;
};
};
}

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,28 @@
/**
* Interface que representa o log de banco de dados retornado pelo endpoint /log/database.
*/
export interface LogDatabaseInterface {
message?: string;
data: {
cns: string;
cartorio: string;
data: string;
hora: string;
ged: {
partition: {
unit: string;
percent_free: number;
percent_used: number;
total_disk_space: string;
total_free_disk_space: string;
total_used_disk_space: string;
};
arquivos: [{
path: string;
data: string;
hora: string;
quantidade: number;
}]
};
};
}

View file

@ -0,0 +1,15 @@
/**
* Interface que representa a tabela `log` do banco de dados `monitoring`.
* Cada campo reflete a tipagem e as restrições definidas na DDL original.
*/
export interface LogInterface {
log_id?: number; // ID único do log (chave primária, gerada automaticamente pelo banco)
client_id: number; // ID do cliente relacionado (chave estrangeira obrigatória)
date_post?: string; // Data e hora do registro (gerada automaticamente pelo banco)
file: object; // Dados em formato JSON (ex: informações do arquivo ou operação)
cartorio?: string;
server?: string;
cns?: string;
data?: string;
hora?: string;
}

View file

@ -0,0 +1,36 @@
/**
* Interface que representa o log de servidor retornado pelo endpoint /log/server.
*/
export interface LogServerInterface {
message?: string;
data: {
cns: string;
cartorio: string;
data: string;
hora: string;
server: {
operacional_system: {
so: string;
versao: string;
release: string;
arquitetura: string;
};
memory: {
total_MB: number;
usada_MB: number;
livre_MB: number;
buffer_MB: number;
swap_total_MB: number;
swap_usada_MB: number;
};
cpu: Array<{
núcleo: string;
modelo: string;
clock_mhz: number;
cache: string;
threads: number;
núcleos: number;
}>;
};
};
}

View file

@ -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: []
};
}

View file

@ -1,10 +0,0 @@
import { TImovelFormValues } from "../../schemas/TImovel/TImovelSchema";
export interface TImovelFormInterface {
isOpen: boolean;
data: TImovelFormValues | null;
onClose: (item: null, isFormStatus: boolean) => void;
onSave: (data: TImovelFormValues) => void;
buttonIsLoading: boolean;
tipoClasse: number;
}

View file

@ -1,5 +0,0 @@
export interface TImovelIndexInterface {
pageTitle: string,
pageDescription: string,
tipoClasse: number
}

View file

@ -1,16 +0,0 @@
export default interface TImovelInterface {
imovel_id?: number,
tipo_classe?: string,
tipo_registro?: string,
data_registro?: string,
numero?: number,
numero_letra?: string,
cidade?: string,
cep?: string,
uf?: string,
tb_bairro_id?: number,
cartorio?: string,
livro?: string,
cns?: number,
gtbb_descricao?: string,
}

View file

@ -1,7 +0,0 @@
import TImovelInterface from "./TImovelInterface";
export default interface TImovelTableInterface {
data?: TImovelInterface[];
onEdit: (item: TImovelInterface, isEditingFormStatus: boolean) => void;
onDelete: (item: TImovelInterface, isEditingFormStatus: boolean) => void;
}

View file

@ -0,0 +1,18 @@
// Define o tipo base dos valores do formulário (importa do schema do usuário)
import { z } from 'zod';
import { UserSchema } from '../../schemas/User/UserSchema';
// Cria o tipo inferido a partir do schema do usuário
export type FormValues = z.infer<typeof UserSchema>;
/**
* Interface com as propriedades aceitas pelo componente UserForm.
* Isso facilita a reutilização e deixa o código mais limpo.
*/
export interface UserFormInterface {
isOpen: boolean; // Controla se o diálogo está aberto
data: FormValues | null; // Dados do usuário para edição (ou null no modo de criação)
onClose: (item: null, isFormStatus: boolean) => void; // Função executada ao fechar o diálogo
onSave: (data: FormValues) => void; // Função executada ao salvar o formulário
buttonIsLoading: boolean; // Define se o botão de envio está em modo de carregamento
}

View file

@ -0,0 +1,8 @@
import React from 'react';
/**
* Interface de tipagem para o componente LoginForm.
*/
export interface UserFormLoginInterface extends React.ComponentProps<'div'> {
className?: string; // Classe CSS opcional
}

View file

@ -0,0 +1,16 @@
import { SituacoesEnum } from "@/shared/enums/SituacoesEnum";
export interface UserInterface {
user_id?: number;
name: string;
email: string;
password: string;
position: string;
team: string;
status: SituacoesEnum; // 'A' ou 'I'
date_register?: string;
date_update?: string | null;
user_id_create: number | null,
user_id_update: number | null
}

View file

@ -0,0 +1,10 @@
import { UserInterface } from './UserInterface';
/**
* Interface que define as propriedades esperadas pelo componente UserTable.
*/
export interface UserTableInterface {
data: UserInterface[]; // Lista de usuários a exibir
onEdit: (user: UserInterface) => void; // Ação ao clicar em editar
onDelete: (user: UserInterface) => void; // Ação ao clicar em remover
}

View file

@ -0,0 +1,67 @@
import { z } from "zod";
/**
* Schema de validação para a tabela `client`
* Baseado na DDL do banco de dados `monitoring.client`
*/
export const ClientSchema = z.object({
client_id: z.number().optional(), // ID gerado automaticamente (AUTO_INCREMENT)
cns: z
.string()
.max(10, { message: "O campo CNS deve ter no máximo 10 caracteres." })
.nullable()
.optional(), // Campo opcional e pode ser nulo
name: z
.string()
.min(3, { message: "O nome deve ter no mínimo 3 caracteres." })
.max(550, { message: "O nome deve ter no máximo 550 caracteres." }), // Campo obrigatório
date_register: z
.string()
.nullable()
.optional(), // Timestamp gerado automaticamente pelo banco
state: z
.string()
.length(2, { message: "O estado deve conter exatamente 2 caracteres (ex: 'SP')." })
.nullable()
.optional(), // Campo opcional
city: z
.string()
.max(160, { message: "O nome da cidade deve ter no máximo 160 caracteres." })
.nullable()
.optional(),
responsible: z
.string()
.max(160, { message: "O nome do responsável deve ter no máximo 160 caracteres." })
.nullable()
.optional(),
consultant: z
.string()
.max(160, { message: "O nome do consultor deve ter no máximo 160 caracteres." })
.nullable()
.optional(),
type_contract: z
.string()
.length(1, { message: "O tipo de contrato deve conter apenas 1 caractere." })
.nullable()
.optional(), // Pode representar tipo de contrato (ex: 'A' = anual, 'M' = mensal)
status: z
.string()
.length(1, { message: "O status de deve conter apenas 1 caractere." })
.nullable()
.optional(), // Pode representar tipo de status (ex: 'A' = ativo, 'I' = inativo)
});
/**
* Tipo TypeScript inferido automaticamente a partir do schema.
* Permite utilizar a tipagem do Zod em qualquer lugar do código.
*/
export type ClientSchemaType = z.infer<typeof ClientSchema>;

View file

@ -1,6 +0,0 @@
import { z } from 'zod';
export const GUsuarioLoginSchema = z.object({
login: z.string().min(1, 'O campo deve ser preenchido'),
senha_api: z.string().min(1, 'O campo deve ser preenchido'),
});

View file

@ -0,0 +1,43 @@
import { z } from "zod";
/**
* Schema de validação para a tabela `log`
* Baseado na DDL do banco de dados `monitoring.log`
*/
export const LogSchema = z.object({
// ID do log, gerado automaticamente pelo banco (AUTO_INCREMENT)
log_id: z.number().optional(),
// ID do cliente relacionado — campo obrigatório
client_id: z
.number({
message: "O campo client_id deve ser um número.",
})
.int()
.positive({ message: "O client_id deve ser um número positivo." }),
// Data e hora da inserção — gerada automaticamente pelo banco (CURRENT_TIMESTAMP)
date_post: z
.string()
.datetime({ message: "O campo date_post deve ser uma data/hora válida (ISO 8601)." })
.optional()
.nullable(),
// Campo JSON que armazena informações sobre o arquivo
// Pode ser um objeto com qualquer estrutura válida em JSON
file: z
.any()
.refine((val) => val !== undefined, {
message: "O campo file é obrigatório e deve conter um JSON válido.",
})
.refine(
(val) => typeof val === "object" && val !== null,
{ message: "O campo file deve ser um objeto JSON válido." }
),
});
/**
* Tipo TypeScript inferido automaticamente a partir do schema.
* Permite utilizar a tipagem do Zod em qualquer parte do código.
*/
export type LogSchemaType = z.infer<typeof LogSchema>;

View file

@ -1,19 +0,0 @@
import z from "zod";
export const TImovelSchema = z.object({
imovel_id: z.number().optional(),
tipo_classe: z.string().optional(),
tipo_registro: z.string().optional(),
data_registro: z.string().optional(),
numero: z.number().optional(),
numero_letra: z.string().optional(),
cidade: z.string().optional(),
cep: z.string().optional(),
uf: z.string().optional(),
tb_bairro_id: z.number().optional(),
cartorio: z.string().optional(),
livro: z.string().optional(),
cns: z.number().optional(),
});
export type TImovelFormValues = z.infer<typeof TImovelSchema>;

View file

@ -0,0 +1,12 @@
import { z } from 'zod';
/**
* Schema de validação do login do usuário
* - Garante que email e senha sejam obrigatórios
* - `rememberMe` é opcional e booleano
*/
export const UserLoginSchema = z.object({
email: z.string().min(1, 'O campo deve ser preenchido'),
password: z.string().min(1, 'O campo deve ser preenchido'),
rememberMe: z.boolean().optional(),
});

Some files were not shown because too many files have changed in this diff Show more