Merge remote-tracking branch 'origin/release(MVP/Sprint6)' into release(MVP)

This commit is contained in:
Keven Willian Pereira de Souza 2025-09-26 15:45:48 -03:00
commit 931fb7e158
16 changed files with 749 additions and 8 deletions

12
package-lock.json generated
View file

@ -2255,12 +2255,6 @@
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
@ -2270,6 +2264,12 @@
"node": ">=14"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",

View file

@ -0,0 +1,164 @@
'use client';
import { useEffect, useState, useCallback } from "react";
import { Card, CardContent } from "@/components/ui/card";
import Loading from "@/app/_components/loading/loading";
import GCidadeTable from "../../_components/g_cidade/GCidadeTable";
import GCidadeForm from "../../_components/g_cidade/GCidadeForm";
import { useGCidadeReadHook } from "../../_hooks/g_cidade/useGCidadeReadHook";
import { useGCidadeSaveHook } from "../../_hooks/g_cidade/useGCidadeSaveHook";
import { useGCidadeRemoveHook } from "../../_hooks/g_cidade/useGCidadeRemoveHook";
import ConfirmDialog from "@/app/_components/confirm_dialog/ConfirmDialog";
import { useConfirmDialog } from "@/app/_components/confirm_dialog/useConfirmDialog";
import GCidadeInterface from "../../_interfaces/GCidadeInterface";
import Header from "@/app/_components/structure/Header";
export default function GCidadePage() {
// Hooks para leitura e salvamento
const { gCidade, fetchGCidade } = useGCidadeReadHook();
const { saveGCidade } = useGCidadeSaveHook();
const { removeGCidade } = useGCidadeRemoveHook();
// Estados
const [selectedCidade, setSelectedCidade] = useState<GCidadeInterface | null>(null);
const [isFormOpen, setIsFormOpen] = useState(false);
// Estado para saber qual item será deletado
const [itemToDelete, setItemToDelete] = useState<GCidadeInterface | 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: GCidadeInterface | null) => {
setSelectedCidade(data);
setIsFormOpen(true);
}, []);
/**
* Fecha o formulário e limpa o andamento selecionado
*/
const handleCloseForm = useCallback(() => {
setSelectedCidade(null);
setIsFormOpen(false);
}, []);
/**
* Salva os dados do formulário
*/
const handleSave = useCallback(async (formData: GCidadeInterface) => {
// Aguarda salvar o registro
await saveGCidade(formData);
// Atualiza a lista de dados
fetchGCidade();
}, [saveGCidade, fetchGCidade]);
/**
* Quando o usuário clica em "remover" na tabela
*/
const handleConfirmDelete = useCallback((item: GCidadeInterface) => {
// 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 removeGCidade(itemToDelete);
// Atualiza a lista
await fetchGCidade();
// Limpa o item selecionado
setItemToDelete(null);
// Fecha o modal
handleCancel();
}, [itemToDelete, fetchGCidade, handleCancel]);
/**
* Busca inicial dos dados
*/
useEffect(() => {
fetchGCidade();
}, []);
/**
* Tela de loading enquanto carrega os dados
*/
if (!gCidade) {
return <Loading type={2} />;
}
return (
<div>
{/* Cabeçalho */}
<Header
title={"Cidades"}
description={"Gerenciamento de Cidades"}
buttonText={"Nova Cidade"}
buttonAction={() => { handleOpenForm(null) }}
/>
{/* Tabela de andamentos */}
<Card>
<CardContent>
<GCidadeTable
data={gCidade}
onEdit={handleOpenForm}
onDelete={handleConfirmDelete}
/>
</CardContent>
</Card>
{/* Modal de confirmação */}
<ConfirmDialog
isOpen={isConfirmOpen}
title="Confirmar exclusão"
description="Atenção"
message={`Deseja realmente excluir a cidade "${itemToDelete?.cidade_nome}"?`}
confirmText="Sim, excluir"
cancelText="Cancelar"
onConfirm={handleDelete}
onCancel={handleCancel}
/>
{/* Formulário de criação/edição */}
<GCidadeForm
isOpen={isFormOpen}
data={selectedCidade}
onClose={handleCloseForm}
onSave={handleSave}
/>
</div>
); 4
}

View file

@ -0,0 +1,152 @@
'use client';
import z from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
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 { GCidadeSchema } from "../../_schemas/GCidadeSchema";
import { useEffect } from "react";
// Define o tipo do formulário com base no schema Zod
type FormValues = z.infer<typeof GCidadeSchema>;
// Propriedades esperadas pelo componente
interface Props {
isOpen: boolean; // controla se o Dialog está aberto
data: FormValues | null; // dados para edição (se existirem)
onClose: (item: null, isFormStatus: boolean) => void; // callback para fechar
onSave: (data: FormValues) => void; // callback para salvar
}
// Componente principal do formulário
export default function GCidadeForm({ isOpen, data, onClose, onSave }: Props) {
// Inicializa o react-hook-form integrado ao Zod para validação
const form = useForm<FormValues>({
resolver: zodResolver(GCidadeSchema),
defaultValues: {
cidade_id: 0,
uf: "",
cidade_nome: "",
codigo_ibge: "",
codigo_gyn: ""
},
});
// Quando recebe dados para edição, atualiza os valores do formulário
useEffect(() => {
if (data) form.reset(data);
}, [data, form]);
return (
<Dialog
open={isOpen}
// Fecha o diálogo quando alterado para "false"
onOpenChange={(open) => {
if (!open) onClose(null, false);
}}
>
<DialogContent className="sm:max-w-[425px]">
{/* Cabeçalho do diálogo */}
<DialogHeader>
<DialogTitle>Cidades</DialogTitle>
<DialogDescription>Controle de Cidades</DialogDescription>
</DialogHeader>
{/* Estrutura do formulário */}
<Form {...form}>
<form onSubmit={form.handleSubmit(onSave)} className="space-y-6">
{/* Campo: Nome da cidade */}
<FormField
control={form.control}
name="cidade_nome"
render={({ field }) => (
<FormItem>
<FormLabel>Descrição</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite a descrição" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Campo: Código IBGE */}
<FormField
name="codigo_ibge"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Código IBGE</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite o código" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Campo: UF (Estado) */}
<FormField
name="uf"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Estado</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite a UF" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Rodapé do diálogo com botões */}
<DialogFooter className="mt-4">
{/* Botão de cancelar */}
<DialogClose asChild>
<Button
variant="outline"
type="button"
onClick={() => onClose(null, false)}
className="cursor-pointer"
>
Cancelar
</Button>
</DialogClose>
{/* Botão de salvar */}
<Button type="submit" className="cursor-pointer">
Salvar
</Button>
</DialogFooter>
{/* Campo oculto: ID da cidade */}
<input type="hidden" {...form.register("cidade_id")} />
</form>
</Form>
</DialogContent>
</Dialog>
);
}

View file

@ -0,0 +1,150 @@
'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 GCidadeInterface from "../../_interfaces/GCidadeInterface";
// Tipagem das props do componente da tabela
interface GCidadeTableProps {
data: GCidadeInterface[]; // lista de cidades
onEdit: (item: GCidadeInterface, isEditingFormStatus: boolean) => void; // callback para edição
onDelete: (item: GCidadeInterface, isEditingFormStatus: boolean) => void; // callback para exclusão
}
/**
* Renderiza o "badge" de status da cidade (Ativo/Inativo)
*/
function StatusBadge({ situacao }: { situacao: string }) {
const isActive = situacao === "A"; // define se está ativo ou inativo
// Estilos base
const baseClasses =
"text-xs font-medium px-2.5 py-0.5 rounded-sm me-2";
// Estilo para ativo
const activeClasses =
"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300";
// Estilo para inativo
const inactiveClasses =
"bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300";
// Retorna o badge com classe condicional
return (
<span className={`${baseClasses} ${isActive ? activeClasses : inactiveClasses}`}>
{isActive ? "Ativo" : "Inativo"}
</span>
);
}
/**
* Componente principal da tabela de cidades
*/
export default function GCidadeTable({
data,
onEdit,
onDelete
}: GCidadeTableProps) {
return (
<Table className="table-fixed w-full">
{/* Cabeçalho da tabela */}
<TableHeader>
<TableRow>
<TableHead className="w-15 font-bold">#</TableHead>
<TableHead className="w-25 font-bold">IBGE</TableHead>
<TableHead className="w-25 font-bold">UF</TableHead>
<TableHead className="font-bold">Descrição</TableHead>
<TableHead className="text-right"></TableHead>
</TableRow>
</TableHeader>
{/* Corpo da tabela */}
<TableBody>
{data.map((item) => (
<TableRow
key={item.cidade_id}
className="cursor-pointer"
>
{/* ID da cidade */}
<TableCell>
{item.cidade_id}
</TableCell>
{/* Código IBGE */}
<TableCell>
{item.codigo_ibge}
</TableCell>
{/* UF */}
<TableCell>
{item.uf}
</TableCell>
{/* Nome/descrição da cidade */}
<TableCell>
{item.cidade_nome}
</TableCell>
{/* Ações (menu dropdown) */}
<TableCell className="text-right">
<DropdownMenu>
{/* Botão de disparo do menu */}
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="icon"
className="cursor-pointer"
>
<EllipsisIcon />
</Button>
</DropdownMenuTrigger>
{/* Conteúdo do menu */}
<DropdownMenuContent side="left" align="start">
<DropdownMenuGroup>
{/* Opção editar */}
<DropdownMenuItem
className="cursor-pointer"
onSelect={() => onEdit(item, true)}
>
<PencilIcon className="mr-2 h-4 w-4" />
Editar
</DropdownMenuItem>
<DropdownMenuSeparator />
{/* Opção remover */}
<DropdownMenuItem
className="cursor-pointer"
onSelect={() => onDelete(item, true)}
>
<Trash2Icon className="mr-2 h-4 w-4" />
Remover
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
}

View file

@ -0,0 +1,86 @@
import API from "@/services/api/Api";
import { Methods } from "@/services/api/enums/ApiMethodEnum";
export default async function GCidadeIndexData() {
return Promise.resolve({
status: 200,
message: "Dados localizados",
data: [
{
"cidade_id": 1,
"uf": "AC",
"cidade_nome": "Acrelândia",
"codigo_ibge": null,
"codigo_gyn": null
},
{
"cidade_id": 2,
"uf": "AC",
"cidade_nome": "Assis Brasil",
"codigo_ibge": null,
"codigo_gyn": null
},
{
"cidade_id": 3,
"uf": "AC",
"cidade_nome": "Brasiléia",
"codigo_ibge": null,
"codigo_gyn": null
},
{
"cidade_id": 4,
"uf": "AC",
"cidade_nome": "Bujari",
"codigo_ibge": null,
"codigo_gyn": null
},
{
"cidade_id": 5,
"uf": "AC",
"cidade_nome": "Capixaba",
"codigo_ibge": null,
"codigo_gyn": null
},
{
"cidade_id": 6,
"uf": "AC",
"cidade_nome": "Cruzeiro do Sul",
"codigo_ibge": null,
"codigo_gyn": null
},
{
"cidade_id": 7,
"uf": "AC",
"cidade_nome": "Epitaciolândia",
"codigo_ibge": null,
"codigo_gyn": null
},
{
"cidade_id": 8,
"uf": "AC",
"cidade_nome": "Feijó",
"codigo_ibge": null,
"codigo_gyn": null
},
{
"cidade_id": 9,
"uf": "AC",
"cidade_nome": "Jordão",
"codigo_ibge": null,
"codigo_gyn": null
},
{
"cidade_id": 10,
"uf": "AC",
"cidade_nome": "Mâncio Lima",
"codigo_ibge": null,
"codigo_gyn": null
}
]
});
}

View file

@ -0,0 +1,14 @@
import API from "@/services/api/Api";
import { Methods } from "@/services/api/enums/ApiMethodEnum";
export default async function GCidadeRemoveData() {
return Promise.resolve({
status: 200,
message: "Dados removidos",
data: null
});
}

View file

@ -0,0 +1,15 @@
import API from "@/services/api/Api"; // Importa o serviço de API (ainda não utilizado aqui)
import { Methods } from "@/services/api/enums/ApiMethodEnum"; // Importa enum de métodos HTTP (também não usado neste trecho)
// Função assíncrona responsável por salvar dados de cidades
export default async function GCidadeSaveData() {
// Log para indicar que a função foi chamada
console.log("chegou");
// Retorna uma Promise resolvida simulando resposta da API
return Promise.resolve({
status: 200, // Código de status fictício
message: "Dados salvos" // Mensagem de sucesso
});
}

View file

@ -0,0 +1,32 @@
import { useResponse } from "@/app/_response/ResponseContext"; // Contexto global para gerenciar respostas da API
import { useState } from "react";
import { GCidadeIndexService } from "../../_services/g_cidade/GCidadeIndexService"; // Serviço que busca a lista de cidades
import GCidadeInterface from "../../_interfaces/GCidadeInterface"; // Interface tipada da cidade
// Hook customizado para leitura de dados de cidades
export const useGCidadeReadHook = () => {
// Hook do contexto de resposta para feedback global (alertas, mensagens etc.)
const { setResponse } = useResponse();
// Estado local que armazena a lista de cidades retornada da API
const [gCidade, setGCidade] = useState<GCidadeInterface[]>([]);
// Função assíncrona que busca os dados de cidades
const fetchGCidade = async () => {
// Chama o serviço responsável por consultar a API
const response = await GCidadeIndexService();
// Atualiza o estado local com os dados retornados
setGCidade(response.data);
// Atualiza o contexto global de resposta
setResponse(response);
// Retorna a resposta para quem chamou o hook
return response;
}
// Retorna os dados e a função de busca para serem usados no componente
return { gCidade, fetchGCidade }
}

View file

@ -0,0 +1,22 @@
import { useResponse } from "@/app/_response/ResponseContext"; // Contexto global para gerenciar respostas da API
import GCidadeInterface from "../../_interfaces/GCidadeInterface"; // Interface tipada da cidade
import GCidadeRemoveData from "../../_data/GCidade/GCidadeRemoveData"; // Função que remove a cidade via API
// Hook customizado para remoção de cidades
export const useGCidadeRemoveHook = () => {
// Hook do contexto de resposta para feedback global (alertas, mensagens etc.)
const { setResponse } = useResponse();
// Função assíncrona que remove uma cidade
const removeGCidade = async (data: GCidadeInterface) => {
// Chama a função de remoção passando os dados da cidade
const response = await GCidadeRemoveData(data);
// Atualiza o contexto global com a resposta da API
setResponse(response);
}
// Retorna a função de remoção para ser usada no componente
return { removeGCidade }
}

View file

@ -0,0 +1,34 @@
import { useState } from "react";
import { useResponse } from "@/app/_response/ResponseContext"
import GCidadeInterface from "../../_interfaces/GCidadeInterface";
import { GCidadeSaveService } from "../../_services/g_cidade/GCidadeSaveService";
export const useGCidadeSaveHook = () => {
const { setResponse } = useResponse();
const [gCidade, setGCidade] = useState<GCidadeInterface | null>(null);
// controla se o formulário está aberto ou fechado
const [isOpen, setIsOpen] = useState(false);
const saveGCidade = async (data: GCidadeInterface) => {
const response = await GCidadeSaveService(data);
// Guardar os dados localizados
setGCidade(response.data);
// Manda a resposta para o verificador de resposta
setResponse(response);
// Fecha o formulário automaticamente após salvar
setIsOpen(false);
// Retorna os dados imediatamente
return response;
}
return { gCidade, saveGCidade }
}

View file

@ -0,0 +1,9 @@
export default interface GCidadeInterface {
cidade_id?: number,
uf: string,
cidade_nome: string,
codigo_ibge?: string,
codigo_gyn?: string
}

View file

@ -0,0 +1,11 @@
import z from "zod";
export const GCidadeSchema = z.object({
cidade_id: z.number().optional(),
uf: z.string().min(1, "A UF é obrigatória").max(2, "A UF deve ter no máximo 2 caracteres"),
cidade_nome: z.string().min(1, "O nome da cidade é obrigatório"),
codigo_ibge: z.string().optional(),
codigo_gyn: z.string().optional()
})

View file

@ -0,0 +1,16 @@
import { withClientErrorHandler } from "@/actions/withClientErrorHandler/withClientErrorHandler";
// Função que envolve qualquer ação assíncrona para capturar e tratar erros do cliente
import GCidadeIndexData from "../../_data/GCidade/GCidadeIndexData";
// Função que retorna os dados da lista de cidades (chamada à API ou mock)
// Função assíncrona que executa a chamada para buscar os dados de cidades
async function executeGCidadeIndexService() {
// Chama a função que retorna os dados da cidade
const response = await GCidadeIndexData();
// Retorna a resposta para o chamador
return response;
}
// Exporta o serviço de índice de cidades já encapsulado com tratamento de erros
export const GCidadeIndexService = withClientErrorHandler(executeGCidadeIndexService);

View file

@ -0,0 +1,18 @@
import { withClientErrorHandler } from "@/actions/withClientErrorHandler/withClientErrorHandler";
// Função que envolve qualquer ação assíncrona para capturar e tratar erros do cliente
import GCidadeRemoveData from "../../_data/GCidade/GCidadeRemoveData";
// Função que remove os dados da cidade via API
import GCidadeInterface from "../../_interfaces/GCidadeInterface";
// Interface tipada da cidade
// Função assíncrona que executa a remoção de uma cidade
async function executeGCidadeRemoveService(data: GCidadeInterface) {
// Chama a função que remove os dados da cidade
const response = await GCidadeRemoveData(data);
// Retorna a resposta da remoção
return response;
}
// Exporta o serviço de remoção de cidade já encapsulado com tratamento de erros
export const GCidadeRemoveService = withClientErrorHandler(executeGCidadeRemoveService);

View file

@ -0,0 +1,18 @@
import { withClientErrorHandler } from "@/actions/withClientErrorHandler/withClientErrorHandler";
// Função que envolve qualquer ação assíncrona para capturar e tratar erros do cliente
import GCidadeSaveData from "../../_data/GCidade/GCidadeSaveData";
// Função que salva os dados da cidade via API (ou mock)
import GCidadeInterface from "../../_interfaces/GCidadeInterface";
// Interface tipada da cidade
// Função assíncrona que executa o salvamento de uma cidade
async function executeGCidadeSaveService(data: GCidadeInterface) {
// Chama a função que salva os dados da cidade
const response = await GCidadeSaveData(data);
// Retorna a resposta do salvamento
return response;
}
// Exporta o serviço de salvamento de cidade já encapsulado com tratamento de erros
export const GCidadeSaveService = withClientErrorHandler(executeGCidadeSaveService);

View file

@ -121,8 +121,8 @@ const data = {
url: "/cadastros/pessoas/complementos/",
},
{
title: "Regimes/Bens",
url: "/cadastros/regime-bens/",
title: "Cidades",
url: "/cadastros/cidades/",
},
{
title: "Regimes/Comunhão",