[MVPTN-87] feat(CRUD): Cria crud de dados mocados da Imovel e da Unidade do Imóvel

This commit is contained in:
Keven Willian Pereira de Souza 2025-10-01 18:11:21 -03:00
parent 8579387c53
commit 4eabe19ee3
36 changed files with 3504 additions and 35 deletions

View file

@ -0,0 +1,20 @@
/**
* Formata um número de CEP no padrão 99999-999
*
* @param value - CEP em string ou number
* @returns CEP formatado ou string vazia se inválido
*/
export function FormatCEP(value: string | number): string {
if (!value) return '';
// Converte para string e remove tudo que não seja número
const digits = String(value).replace(/\D/g, '');
// Garante que tenha no máximo 8 dígitos
const cleanValue = digits.slice(0, 8);
// Retorna formatado ou valor limpo se não tiver tamanho suficiente
if (cleanValue.length !== 8) return cleanValue;
return cleanValue.replace(/(\d{5})(\d{3})/, '$1-$2');
}

View file

@ -0,0 +1,171 @@
'use client';
import { useEffect, useState, useCallback } from 'react';
import { Card, CardContent } from '@/components/ui/card';
import Loading from '@/app/_components/loading/loading';
import TImovelTable from '../../../_components/t_imovel/TImovelTable';
import TImovelForm from '../../../_components/t_imovel/TImovelForm';
import { useTImovelIndexHook } from '../../../_hooks/t_imovel/useTImovelIndexHook';
import { useTImovelSaveHook } from '../../../_hooks/t_imovel/useTImovelSaveHook';
import { useTImovelDeleteHook } from '../../../_hooks/t_imovel/useTImovelDeleteHook';
import ConfirmDialog from '@/app/_components/confirm_dialog/ConfirmDialog';
import { useConfirmDialog } from '@/app/_components/confirm_dialog/useConfirmDialog';
import TImovelInterface from '../../../_interfaces/TImovelInterface';
import Header from '@/app/_components/structure/Header';
export default function TTBAndamentoServico() {
// 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={'Imóveis Urbanos'}
description={'Gerenciamento de imóveis urbanos'}
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}
/>
</div>
);
4;
}

View file

@ -0,0 +1,125 @@
import { ColumnDef } from "@tanstack/react-table";
import TImovelInterface from "../../_interfaces/TImovelInterface";
import { Button } from "@/components/ui/button";
import {
EllipsisIcon,
PencilIcon,
Trash2Icon,
} from "lucide-react";
import { FormatDateTime } from "@/actions/dateTime/FormatDateTime";
import { FormatCEP } from "@/actions/CEP/FormatCEP";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ImovelTipoRegistro } from "@/enums/ImovelTipoRegistro";
import { SortableHeader } from "@/app/_components/dataTable/SortableHeader";
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">
{imovel.cidade}/{imovel.uf}
</span>
<span className="text-sm text-gray-500">{imovel.gtb_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

@ -0,0 +1,304 @@
'use client';
import z from 'zod';
import { useEffect } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
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';
import { TImovelSchema } from '../../_schemas/TImovelSchema';
import LoadingButton from '@/app/_components/loadingButton/LoadingButton';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { HouseIcon, IdCardIcon, UserIcon } from 'lucide-react';
import { Select } from '@/components/ui/select';
import TImovelUnidadePage from '../t_imovel_unidade/TImovelUnidadePage';
type FormValues = z.infer<typeof TImovelSchema>;
interface Props {
isOpen: boolean;
data: FormValues | null;
onClose: (item: null, isFormStatus: boolean) => void;
onSave: (data: FormValues) => void;
buttonIsLoading: boolean;
}
export default function TCensecForm({ isOpen, data, onClose, onSave, buttonIsLoading }: Props) {
// Inicializa o react-hook-form com schema zod
const form = useForm<FormValues>({
resolver: zodResolver(TImovelSchema),
defaultValues: {},
});
// Atualiza o formulário quando recebe dados para edição
useEffect(() => {
if (data) form.reset(data);
}, [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" value="dadosDoImovel">
<UserIcon className="me-1 inline" />
Dados do Imóvel
</TabsTrigger>
<TabsTrigger className="flex-1 text-center" value="unidades">
<IdCardIcon className="inline" />
Unidades
</TabsTrigger>
</TabsList>
{/* Dados do Imóvel */}
<TabsContent value="dadosDoImovel" className="space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{/* Tipo Classe */}
<FormField
control={form.control}
name="tipo_classe"
render={({ field }) => (
<FormItem>
<FormLabel>Tipo Classe</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite o tipo de classe" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Tipo Registro */}
<FormField
control={form.control}
name="tipo_registro"
render={({ field }) => (
<FormItem>
<FormLabel>Tipo Registro</FormLabel>
<FormControl>
<Select {...field}>
<option value="M">Matrícula</option>
<option value="T">Transcrição</option>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Número */}
<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>
)}
/>
{/* Número Letra */}
<FormField
control={form.control}
name="numero_letra"
render={({ field }) => (
<FormItem>
<FormLabel>Número Letra</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite a letra" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Cidade */}
<FormField
control={form.control}
name="cidade"
render={({ field }) => (
<FormItem>
<FormLabel>Cidade</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite a cidade" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* UF */}
<FormField
control={form.control}
name="uf"
render={({ field }) => (
<FormItem>
<FormLabel>UF</FormLabel>
<FormControl>
<Input {...field} placeholder="UF" maxLength={2} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Bairro */}
<FormField
control={form.control}
name="tb_bairro_id"
render={({ field }) => (
<FormItem>
<FormLabel>Bairro</FormLabel>
<FormControl>
<Input {...field} type="number" placeholder="Digite o ID do bairro" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* CEP */}
<FormField
control={form.control}
name="cep"
render={({ field }) => (
<FormItem>
<FormLabel>CEP</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite o CEP" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Cartório */}
<FormField
control={form.control}
name="cartorio"
render={({ field }) => (
<FormItem>
<FormLabel>Cartório</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite o cartório" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Livro */}
<FormField
control={form.control}
name="livro"
render={({ field }) => (
<FormItem>
<FormLabel>Livro</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite o livro" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* CNS */}
<FormField
control={form.control}
name="cns"
render={({ field }) => (
<FormItem>
<FormLabel>CNS</FormLabel>
<FormControl>
<Input {...field} type="number" placeholder="Digite o CNS" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* GTB Descrição */}
<FormField
control={form.control}
name="gtb_descricao"
render={({ field }) => (
<FormItem>
<FormLabel>GTB Descrição</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite a descrição" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</TabsContent>
{/* Unidades */}
<TabsContent value="unidades" className="space-y-4">
{/* Conteúdo das unidades */}
< TImovelUnidadePage />
</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

@ -0,0 +1,28 @@
'use client';
import { DataTable } from '@/app/_components/dataTable/DataTable';
import TImovelColumns from './TImovelColumns';
import TImovelInterface from '../../_interfaces/TImovelInterface';
interface TImovelTableProps {
data: TImovelInterface[];
onEdit: (item: TImovelInterface, isEditingFormStatus: boolean) => void;
onDelete: (item: TImovelInterface, isEditingFormStatus: boolean) => void;
}
/**
* Componente principal da tabela
*/
export default function TPessoaTable({ data, onEdit, onDelete }: TImovelTableProps) {
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,67 @@
import { ColumnDef } from "@tanstack/react-table";
import TImovelUnidadeInterface from "../../_interfaces/TImovelUnidadeInterface";
import { Button } from "@/components/ui/button";
import {
EllipsisIcon,
PencilIcon,
Trash2Icon,
} from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { SortableHeader } from "@/app/_components/dataTable/SortableHeader";
export default function TImovelUnidadeColumns(
onEdit: (item: TImovelUnidadeInterface, isEditingFormStatus: boolean) => void,
onDelete: (item: TImovelUnidadeInterface, isEditingFormStatus: boolean) => void
): ColumnDef<TImovelUnidadeInterface>[] {
return [
// ID
{
accessorKey: "imovel_unidade_id",
header: ({ column }) => SortableHeader("#", column),
cell: ({ row }) => Number(row.getValue("imovel_unidade_id")),
enableSorting: false,
},
// 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

@ -0,0 +1,306 @@
'use client';
import z from 'zod';
import { useEffect } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
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';
import { TImovelSchema } from '../../_schemas/TImovelSchema';
import LoadingButton from '@/app/_components/loadingButton/LoadingButton';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { HouseIcon, IdCardIcon, UserIcon } from 'lucide-react';
import { Select } from '@/components/ui/select';
type FormValues = z.infer<typeof TImovelSchema>;
interface Props {
isOpen: boolean;
data: FormValues | null;
onClose: (item: null, isFormStatus: boolean) => void;
onSave: (data: FormValues) => void;
buttonIsLoading: boolean;
}
export default function TImovelUnidadeForm({ isOpen, data, onClose, onSave, buttonIsLoading }: Props) {
// Inicializa o react-hook-form com schema zod
const form = useForm<FormValues>({
resolver: zodResolver(TImovelSchema),
defaultValues: {},
});
// Atualiza o formulário quando recebe dados para edição
useEffect(() => {
if (data) form.reset(data);
}, [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" value="dadosDoImovel">
<UserIcon className="me-1 inline" />
Dados do Imóvel
</TabsTrigger>
<TabsTrigger className="flex-1 text-center" value="unidades">
<IdCardIcon className="inline" />
Unidades
</TabsTrigger>
</TabsList>
{/* Dados do Imóvel */}
<TabsContent value="dadosDoImovel" className="space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{/* Tipo Classe */}
<FormField
control={form.control}
name="tipo_classe"
render={({ field }) => (
<FormItem>
<FormLabel>Tipo Classe</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite o tipo de classe" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Tipo Registro */}
<FormField
control={form.control}
name="tipo_registro"
render={({ field }) => (
<FormItem>
<FormLabel>Tipo Registro</FormLabel>
<FormControl>
<Select {...field}>
<option value="M">Matrícula</option>
<option value="T">Transcrição</option>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Número */}
<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>
)}
/>
{/* Número Letra */}
<FormField
control={form.control}
name="numero_letra"
render={({ field }) => (
<FormItem>
<FormLabel>Número Letra</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite a letra" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Cidade */}
<FormField
control={form.control}
name="cidade"
render={({ field }) => (
<FormItem>
<FormLabel>Cidade</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite a cidade" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* UF */}
<FormField
control={form.control}
name="uf"
render={({ field }) => (
<FormItem>
<FormLabel>UF</FormLabel>
<FormControl>
<Input {...field} placeholder="UF" maxLength={2} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Bairro */}
<FormField
control={form.control}
name="tb_bairro_id"
render={({ field }) => (
<FormItem>
<FormLabel>Bairro</FormLabel>
<FormControl>
<Input {...field} type="number" placeholder="Digite o ID do bairro" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* CEP */}
<FormField
control={form.control}
name="cep"
render={({ field }) => (
<FormItem>
<FormLabel>CEP</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite o CEP" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Cartório */}
<FormField
control={form.control}
name="cartorio"
render={({ field }) => (
<FormItem>
<FormLabel>Cartório</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite o cartório" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Livro */}
<FormField
control={form.control}
name="livro"
render={({ field }) => (
<FormItem>
<FormLabel>Livro</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite o livro" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* CNS */}
<FormField
control={form.control}
name="cns"
render={({ field }) => (
<FormItem>
<FormLabel>CNS</FormLabel>
<FormControl>
<Input {...field} type="number" placeholder="Digite o CNS" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* GTB Descrição */}
<FormField
control={form.control}
name="gtb_descricao"
render={({ field }) => (
<FormItem>
<FormLabel>GTB Descrição</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite a descrição" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</TabsContent>
{/* Unidades */}
<TabsContent value="unidades" className="space-y-4">
{/* Conteúdo das unidades */}
</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

@ -0,0 +1,166 @@
'use client';
import { useEffect, useState, useCallback } from 'react';
import Loading from '@/app/_components/loading/loading';
import TImovelUnidadeTable from './TImovelUnidadeTable';
import TImovelUnidadeForm from './TImovelUnidadeForm';
import { useTImovelUnidadeIndexHook } from '../.././_hooks/t_imovel_unidade/useTImovelUnidadeIndexHook';
import { useTImovelUnidadeSaveHook } from '../.././_hooks/t_imovel_unidade/useTImovelUnidadeSaveHook';
import { useTImovelUnidadeDeleteHook } from '../.././_hooks/t_imovel_unidade/useTImovelUnidadeDeleteHook';
import ConfirmDialog from '@/app/_components/confirm_dialog/ConfirmDialog';
import { useConfirmDialog } from '@/app/_components/confirm_dialog/useConfirmDialog';
import TImovelUnidadeInterface from '../../_interfaces/TImovelUnidadeInterface';
import Header from '@/app/_components/structure/Header';
export default function TImovelUnidadePage() {
// Controle de estado do botão
const [buttonIsLoading, setButtonIsLoading] = useState(false);
// Hooks para leitura e salvamento
const { tImovelUnidade, indexTImovelUnidade } = useTImovelUnidadeIndexHook();
const { saveTImovelUnidade } = useTImovelUnidadeSaveHook();
const { deleteTImovelUnidade } = useTImovelUnidadeDeleteHook();
// Estados
const [selectedAndamento, setSelectedAndamento] = useState<TImovelUnidadeInterface | null>(null);
const [isFormOpen, setIsFormOpen] = useState(false);
// Estado para saber qual item será deletado
const [itemToDelete, setItemToDelete] = useState<TImovelUnidadeInterface | 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: TImovelUnidadeInterface | 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: TImovelUnidadeInterface) => {
// Coloca o botão em estado de loading
setButtonIsLoading(true);
// Aguarda salvar o registro
await saveTImovelUnidade(formData);
// Remove o botão em estado de loading
setButtonIsLoading(false);
// Atualiza a lista de dados
indexTImovelUnidade();
},
[saveTImovelUnidade, indexTImovelUnidade, handleCloseForm],
);
/**
* Quando o usuário clica em "remover" na tabela
*/
const handleConfirmDelete = useCallback(
(item: TImovelUnidadeInterface) => {
// 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 deleteTImovelUnidade(itemToDelete);
// Atualiza a lista
await indexTImovelUnidade();
// Limpa o item selecionado
setItemToDelete(null);
// Fecha o modal
handleCancel();
}, [itemToDelete, indexTImovelUnidade, handleCancel]);
/**
* Busca inicial dos dados
*/
useEffect(() => {
indexTImovelUnidade();
}, []);
/**
* Tela de loading enquanto carrega os dados
*/
if (tImovelUnidade?.length == 0) {
return <Loading type={2} />;
}
return (
<div>
{/* Cabeçalho */}
<Header
title={'Unidades'}
description={'Gerenciamento de unidades'}
buttonText={'Nova unidade'}
buttonAction={() => {
handleOpenForm(null);
}}
/>
{/* Tabela de andamentos */}
<TImovelUnidadeTable
data={tImovelUnidade}
onEdit={handleOpenForm}
onDelete={handleConfirmDelete} />
{/* Modal de confirmação */}
<ConfirmDialog
isOpen={isConfirmOpen}
title="Confirmar exclusão"
description="Atenção"
message={`Deseja realmente excluir a unidade "${itemToDelete?.cidade}"?`}
confirmText="Sim, excluir"
cancelText="Cancelar"
onConfirm={handleDelete}
onCancel={handleCancel}
/>
{/* Formulário de criação/edição */}
<TImovelUnidadeForm
isOpen={isFormOpen}
data={selectedAndamento}
onClose={handleCloseForm}
onSave={handleSave}
buttonIsLoading={buttonIsLoading}
/>
</div>
);
}

View file

@ -0,0 +1,28 @@
'use client';
import { DataTable } from '@/app/_components/dataTable/DataTable';
import TImovelUnidadeInterface from '../../_interfaces/TImovelUnidadeInterface';
import TImovelUnidadeColumns from './TImovelUnidadeColumns';
interface TImovelUnidadeTableProps {
data: TImovelUnidadeInterface[];
onEdit: (item: TImovelUnidadeInterface, isEditingFormStatus: boolean) => void;
onDelete: (item: TImovelUnidadeInterface, isEditingFormStatus: boolean) => void;
}
/**
* Componente principal da tabela
*/
export default function TImovelUnidadeTable({ data, onEdit, onDelete }: TImovelUnidadeTableProps) {
const columns = TImovelUnidadeColumns(onEdit, onDelete);
return (
<div className="max-h-[40vh] overflow-y-auto">
<DataTable
data={data}
columns={columns}
filterColumn="numero"
filterPlaceholder="Buscar pelo numero de transcrição, matricula etc..."
/>
</div>
);
}

View file

@ -111,7 +111,6 @@ export default function TCensecForm({
<DialogTitle>Pessoa</DialogTitle>
<DialogDescription>Preencha os dados da pessoa</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSave)} className="space-y-6">
{/* Tabs */}
@ -128,7 +127,6 @@ export default function TCensecForm({
<IdCardIcon /> Documentos
</TabsTrigger>
</TabsList>
{/* Dados Pessoais */}
<TabsContent value="dadosPessoais" className="space-y-4">
<div className="grid w-full grid-cols-12 gap-4">
@ -534,7 +532,6 @@ export default function TCensecForm({
</div>
</div>
</TabsContent>
{/* Endereço */}
<TabsContent value="endereco" className="space-y-4">
<div className="grid w-full grid-cols-12 gap-4">
@ -700,7 +697,6 @@ export default function TCensecForm({
</div>
</div>
</TabsContent>
{/* Documentos */}
<TabsContent value="documentos" className="space-y-4">
<div className="grid w-full grid-cols-12 gap-4">

View file

@ -0,0 +1,13 @@
import { withClientErrorHandler } from "@/actions/withClientErrorHandler/withClientErrorHandler";
import TImovelInterface from "../../_interfaces/TImovelInterface";
async function executeTImovelDeleteData(data: TImovelInterface) {
return Promise.resolve({
status: 200,
message: 'Dados Removidos'
});
}
export const TImovelDeleteData = withClientErrorHandler(executeTImovelDeleteData);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,13 @@
import { withClientErrorHandler } from "@/actions/withClientErrorHandler/withClientErrorHandler";
import TImovelInterface from "../../_interfaces/TImovelInterface";
async function executeTImovelSaveData(data: TImovelInterface) {
return Promise.resolve({
status: 200,
message: 'Dados salvos',
});
}
export const TImovelSaveData = withClientErrorHandler(executeTImovelSaveData);

View file

@ -0,0 +1,13 @@
import { withClientErrorHandler } from "@/actions/withClientErrorHandler/withClientErrorHandler";
import TImovelUnidadeInterface from "../../_interfaces/TImovelUnidadeInterface";
async function executeTImovelUnidadeDeleteData(data: TImovelUnidadeInterface) {
return Promise.resolve({
status: 200,
message: 'Dados Removidos'
});
}
export const TImovelUnidadeDeleteData = withClientErrorHandler(executeTImovelUnidadeDeleteData);

View file

@ -0,0 +1,162 @@
import { withClientErrorHandler } from "@/actions/withClientErrorHandler/withClientErrorHandler";
async function executeTImovelUnidadeIndexData() {
return Promise.resolve({
status: 200,
message: 'Dados Salvos',
data: [
{
"imovel_unidade_id": 3921.00,
"imovel_id": 5459.00,
"numero_unidade": "asdfasd",
"quadra": null,
"area": null,
"superquadra": null,
"conjunto": null,
"bloco": null,
"area_descritiva": null,
"caracteristica": null,
"reserva_florestal": null,
"geo_referenciamento": null,
"logradouro": null,
"tb_tipologradouro_id": null,
"selecionado": "S",
"complemento": null,
"tipo_imovel": 67.00,
"tipo_construcao": 0.00,
"texto": null,
"numero_edificacao": null,
"iptu": "",
"ccir": null,
"nirf": null,
"lote": null,
"torre": null,
"nomeloteamento": null,
"nomecondominio": null,
"numero": null,
"cnm_numero": null,
"imovel_publico_uniao": null,
"spu_rip": null,
"cat": null,
"inscricao_municipal": null,
"cib": null,
"area_construida": null
},
{
"imovel_unidade_id": 3918.00,
"imovel_id": 5456.00,
"numero_unidade": null,
"quadra": "45",
"area": null,
"superquadra": null,
"conjunto": null,
"bloco": null,
"area_descritiva": null,
"caracteristica": null,
"reserva_florestal": null,
"geo_referenciamento": null,
"logradouro": null,
"tb_tipologradouro_id": null,
"selecionado": "S",
"complemento": null,
"tipo_imovel": 67.00,
"tipo_construcao": 0.00,
"texto": null,
"numero_edificacao": null,
"iptu": null,
"ccir": null,
"nirf": null,
"lote": null,
"torre": null,
"nomeloteamento": null,
"nomecondominio": null,
"numero": null,
"cnm_numero": null,
"imovel_publico_uniao": null,
"spu_rip": null,
"cat": null,
"inscricao_municipal": null,
"cib": null,
"area_construida": null
},
{
"imovel_unidade_id": 3917.00,
"imovel_id": 5454.00,
"numero_unidade": null,
"quadra": "45",
"area": 160.00,
"superquadra": null,
"conjunto": null,
"bloco": null,
"area_descritiva": null,
"caracteristica": null,
"reserva_florestal": null,
"geo_referenciamento": null,
"logradouro": "RUA P 3",
"tb_tipologradouro_id": 1.00,
"selecionado": "",
"complemento": null,
"tipo_imovel": 67.00,
"tipo_construcao": 0.00,
"texto": null,
"numero_edificacao": null,
"iptu": "1200",
"ccir": null,
"nirf": null,
"lote": "12",
"torre": null,
"nomeloteamento": null,
"nomecondominio": null,
"numero": 125.00,
"cnm_numero": null,
"imovel_publico_uniao": null,
"spu_rip": null,
"cat": null,
"inscricao_municipal": null,
"cib": null,
"area_construida": null
},
{
"imovel_unidade_id": 3916.00,
"imovel_id": 5453.00,
"numero_unidade": null,
"quadra": "06",
"area": 461.51,
"superquadra": null,
"conjunto": null,
"bloco": null,
"area_descritiva": null,
"caracteristica": null,
"reserva_florestal": null,
"geo_referenciamento": null,
"logradouro": "Mariana Vilela",
"tb_tipologradouro_id": 3.00,
"selecionado": "S",
"complemento": null,
"tipo_imovel": 71.00,
"tipo_construcao": 2.00,
"texto": "{...}",
"numero_edificacao": null,
"iptu": "001.211.0006.0012.0001",
"ccir": null,
"nirf": null,
"lote": "12",
"torre": null,
"nomeloteamento": null,
"nomecondominio": null,
"numero": null,
"cnm_numero": null,
"imovel_publico_uniao": null,
"spu_rip": null,
"cat": null,
"inscricao_municipal": null,
"cib": null,
"area_construida": null
}
]
});
}
export const TImovelUnidadeIndexData = withClientErrorHandler(executeTImovelUnidadeIndexData);

View file

@ -0,0 +1,13 @@
import { withClientErrorHandler } from "@/actions/withClientErrorHandler/withClientErrorHandler";
import TImovelUnidadeInterface from "../../_interfaces/TImovelUnidadeInterface";
async function executeTImovelUnidadeSaveData(data: TImovelUnidadeInterface) {
return Promise.resolve({
status: 200,
message: 'Dados salvos',
});
}
export const TImovelUnidadeSaveData = withClientErrorHandler(executeTImovelUnidadeSaveData);

View file

@ -0,0 +1,20 @@
import { useResponse } from '@/app/_response/ResponseContext';
import { useState } from 'react';
import TImovelInterface from '../../_interfaces/TImovelInterface';
import { TImovelDeleteService } from '../../_services/t_imovel/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

@ -0,0 +1,29 @@
'use client';
import { useResponse } from '@/app/_response/ResponseContext';
import { useState } from 'react';
import TImovelInterface from '../../_interfaces/TImovelInterface';
import { TImovelIndexData } from '../../_data/TImovel/TImovelIndexData';
export const useTImovelIndexHook = () => {
const { setResponse } = useResponse();
const [tImovel, setTImovel] = useState<
TImovelInterface[] | null
>(null);
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

@ -0,0 +1,33 @@
'use client';
import { useResponse } from '@/app/_response/ResponseContext';
import { useState } from 'react';
import TImovelInterface from '../../_interfaces/TImovelInterface';
import { TImovelSaveService } from '../../_services/t_imovel/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,20 @@
import { useResponse } from '@/app/_response/ResponseContext';
import { useState } from 'react';
import TImovelUnidadeInterface from '../../_interfaces/TImovelUnidadeInterface';
import { TImovelDeleteService } from '../../_services/t_imovel/TImovelDeleteService';
export const useTImovelUnidadeDeleteHook = () => {
const { setResponse } = useResponse();
const [tImovelUnidade, setTImovelUnidade] = useState<TImovelUnidadeInterface>();
const deleteTImovelUnidade = async (data: TImovelUnidadeInterface) => {
const response = await TImovelDeleteService(data);
setTImovelUnidade(data);
setResponse(response);
};
return { tImovelUnidade, deleteTImovelUnidade };
};

View file

@ -0,0 +1,29 @@
'use client';
import { useResponse } from '@/app/_response/ResponseContext';
import { useState } from 'react';
import TImovelUnidadeInterface from '../../_interfaces/TImovelUnidadeInterface';
import { TImovelUnidadeIndexData } from '../../_data/TImovelUnidade/TImovelUnidadeIndexData';
export const useTImovelUnidadeIndexHook = () => {
const { setResponse } = useResponse();
const [tImovelUnidade, setTImovelUnidade] = useState<
TImovelUnidadeInterface[] | null
>(null);
const indexTImovelUnidade = async () => {
const response = await TImovelUnidadeIndexData();
// Armazena os dados consultados
setTImovelUnidade(response.data);
// Define os dados do componente de resposta (toast, modal, etc)
setResponse(response);
};
return {
tImovelUnidade,
indexTImovelUnidade
};
};

View file

@ -0,0 +1,33 @@
'use client';
import { useResponse } from '@/app/_response/ResponseContext';
import { useState } from 'react';
import TImovelUnidadeInterface from '../../_interfaces/TImovelUnidadeInterface';
import { TImovelUnidadeSaveService } from '../../_services/t_imovel_unidade/TImovelUnidadeSaveService';
export const useTImovelUnidadeSaveHook = () => {
const { setResponse } = useResponse();
const [tImovelUnidade, setTImovelUnidade] = useState<TImovelUnidadeInterface>();
// controla se o formulário está aberto ou fechado
const [isOpen, setIsOpen] = useState(false);
const saveTImovelUnidade = async (data: TImovelUnidadeInterface) => {
const response = await TImovelUnidadeSaveService(data);
// Armazena os dados da repsota
setTImovelUnidade(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 { tImovelUnidade, saveTImovelUnidade };
};

View file

@ -0,0 +1,16 @@
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,
gtb_descricao?: string,
}

View file

@ -0,0 +1,37 @@
export default interface TImovelUnidadeInterface {
imovel_unidade_id?: number;
imovel_id?: number;
numero_unidade?: string;
quadra?: string;
area?: number;
superquadra?: string;
conjunto?: string;
bloco?: string;
area_descritiva?: string;
caracteristica?: string;
reserva_florestal?: string;
geo_referenciamento?: string;
logradouro?: string;
tb_tipologradouro_id?: number;
selecionado?: string;
complemento?: string;
tipo_imovel?: number;
tipo_construcao?: number;
texto?: string;
numero_edificacao?: string;
iptu?: string;
ccir?: string;
nirf?: string;
lote?: string;
torre?: string;
nomeloteamento?: string;
nomecondominio?: string;
numero?: number;
cnm_numero?: string;
imovel_publico_uniao?: string;
spu_rip?: string;
cat?: string;
inscricao_municipal?: string;
cib?: string;
area_construida?: number;
}

View file

@ -0,0 +1,17 @@
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,
});

View file

@ -0,0 +1,39 @@
import z from "zod";
export const TImovelUnidadeSchema = z.object({
imovel_unidade_id: z.number().optional(),
imovel_id: z.number().optional(),
numero_unidade: z.string().optional(),
quadra: z.string().optional(),
area: z.number().optional(),
superquadra: z.string().optional(),
conjunto: z.string().optional(),
bloco: z.string().optional(),
area_descritiva: z.string().optional(),
caracteristica: z.string().optional(),
reserva_florestal: z.string().optional(),
geo_referenciamento: z.string().optional(),
logradouro: z.string().optional(),
tb_tipologradouro_id: z.number().optional(),
selecionado: z.string().optional(),
complemento: z.string().optional(),
tipo_imovel: z.number().optional(),
tipo_construcao: z.number().optional(),
texto: z.string().optional(),
numero_edificacao: z.string().optional(),
iptu: z.string().optional(),
ccir: z.string().optional(),
nirf: z.string().optional(),
lote: z.string().optional(),
torre: z.string().optional(),
nomeloteamento: z.string().optional(),
nomecondominio: z.string().optional(),
numero: z.number().optional(),
cnm_numero: z.string().optional(),
imovel_publico_uniao: z.string().optional(),
spu_rip: z.string().optional(),
cat: z.string().optional(),
inscricao_municipal: z.string().optional(),
cib: z.string().optional(),
area_construida: z.number().optional(),
});

View file

@ -0,0 +1,11 @@
import { withClientErrorHandler } from '@/actions/withClientErrorHandler/withClientErrorHandler';
import { TImovelDeleteData } from '../../_data/TImovel/TImovelDeleteData';
import TImovelInterface from '../../_interfaces/TImovelInterface';
async function executeTImovelDeleteService(data: TImovelInterface) {
const response = await TImovelDeleteData(data);
return response;
}
export const TImovelDeleteService = withClientErrorHandler(executeTImovelDeleteService);

View file

@ -0,0 +1,10 @@
import { withClientErrorHandler } from '@/actions/withClientErrorHandler/withClientErrorHandler';
import { TImovelIndexData } from '../../_data/TImovel/TImovelIndexData';
export default async function executeTImovelIndexService() {
const response = await TImovelIndexData();
return response;
}
export const TImovelIndexService = withClientErrorHandler(executeTImovelIndexService);

View file

@ -0,0 +1,11 @@
import { withClientErrorHandler } from '@/actions/withClientErrorHandler/withClientErrorHandler';
import { TImovelSaveData } from '../../_data/TImovel/TImovelSaveData';
import TImovelInterface from '../../_interfaces/TImovelInterface';
async function executeTImovelSaveService(data: TImovelInterface) {
const response = await TImovelSaveData(data);
return response;
}
export const TImovelSaveService = withClientErrorHandler(executeTImovelSaveService);

View file

@ -0,0 +1,11 @@
import { withClientErrorHandler } from '@/actions/withClientErrorHandler/withClientErrorHandler';
import TImovelUnidadeInterface from '../../_interfaces/TImovelUnidadeInterface';
import { TImovelUnidadeDeleteData } from '../../_data/TImovelUnidade/TImovelUnidadeDeleteData';
async function executeTImovelUnidadeDeleteService(data: TImovelUnidadeInterface) {
const response = await TImovelUnidadeDeleteData(data);
return response;
}
export const TImovelUnidadeDeleteService = withClientErrorHandler(executeTImovelUnidadeDeleteService);

View file

@ -0,0 +1,10 @@
import { withClientErrorHandler } from '@/actions/withClientErrorHandler/withClientErrorHandler';
import { TImovelUnidadeIndexData } from '../../_data/TImovelUnidade/TImovelUnidadeIndexData';
export default async function executeTImovelUnidadeIndexService() {
const response = await TImovelUnidadeIndexData();
return response;
}
export const TImovelUnidadeIndexService = withClientErrorHandler(executeTImovelUnidadeIndexService);

View file

@ -0,0 +1,11 @@
import { withClientErrorHandler } from '@/actions/withClientErrorHandler/withClientErrorHandler';
import TImovelUnidadeInterface from '../../_interfaces/TImovelUnidadeInterface';
import { TImovelUnidadeSaveData } from '../../_data/TImovelUnidade/TImovelUnidadeSaveData';
async function executeTImovelUnidadeSaveService(data: TImovelUnidadeInterface) {
const response = await TImovelUnidadeSaveData(data);
return response;
}
export const TImovelUnidadeSaveService = withClientErrorHandler(executeTImovelUnidadeSaveService);

View file

@ -0,0 +1,12 @@
import { Button } from "@/components/ui/button";
import { ArrowUpDownIcon } from "lucide-react";
export const SortableHeader = (label: string, column: any) => (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
{label}
<ArrowUpDownIcon className="ml-1 h-4 w-4" />
</Button>
);

View file

@ -31,16 +31,21 @@ import {
TableHeader,
TableRow,
} from '@/components/ui/table';
import { ChevronLeftIcon, ChevronRightIcon, EyeIcon } from 'lucide-react';
import {
ChevronLeftIcon,
ChevronRightIcon,
EyeIcon,
} from 'lucide-react';
// Tipagem genérica
export interface DataTableProps<TData> {
data: TData[];
data?: TData[] | null;
columns: ColumnDef<TData, any>[];
filterColumn?: string; // Define qual coluna será usada para filtro
filterPlaceholder?: string;
onEdit?: (item: TData) => void;
onDelete?: (item: TData) => void;
onRowClick?: (item: TData) => void;
}
export function DataTable<TData>({
@ -50,7 +55,11 @@ export function DataTable<TData>({
filterPlaceholder = 'Buscar...',
onEdit,
onDelete,
onRowClick,
}: DataTableProps<TData>) {
// Garante que data sempre seja array
const safeData = Array.isArray(data) ? data : [];
// Estados internos da tabela
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
@ -59,8 +68,40 @@ export function DataTable<TData>({
// Configuração da tabela
const table = useReactTable({
data,
columns,
data: safeData,
columns: [
...columns,
...(onEdit || onDelete
? [
{
id: 'actions',
header: 'Ações',
cell: ({ row }: any) => (
<div className="flex gap-2">
{onEdit && (
<Button
variant="ghost"
size="sm"
onClick={() => onEdit(row.original)}
>
Editar
</Button>
)}
{onDelete && (
<Button
variant="destructive"
size="sm"
onClick={() => onDelete(row.original)}
>
Excluir
</Button>
)}
</div>
),
} as ColumnDef<TData, any>,
]
: []),
],
state: {
sorting,
columnFilters,
@ -84,9 +125,9 @@ export function DataTable<TData>({
{filterColumn && (
<Input
placeholder={filterPlaceholder}
value={(table.getColumn(filterColumn as string)?.getFilterValue() as string) ?? ''}
value={(table.getColumn(filterColumn)?.getFilterValue() as string) ?? ''}
onChange={(e) =>
table.getColumn(filterColumn as string)?.setFilterValue(e.target.value)
table.getColumn(filterColumn)?.setFilterValue(e.target.value)
}
className="w-full"
/>
@ -95,7 +136,8 @@ export function DataTable<TData>({
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto cursor-pointer">
<EyeIcon /> Colunas visíveis
<EyeIcon className="mr-2 h-4 w-4" />
Colunas visíveis
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
@ -133,9 +175,13 @@ export function DataTable<TData>({
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.length ? (
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id} className="cursor-pointer">
<TableRow
key={row.id}
className={onRowClick ? 'cursor-pointer hover:bg-muted/50' : ''}
onClick={() => onRowClick?.(row.original)}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
@ -145,7 +191,10 @@ export function DataTable<TData>({
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
<TableCell
colSpan={table.getAllColumns().length}
className="h-24 text-center"
>
Nenhum resultado encontrado.
</TableCell>
</TableRow>
@ -155,27 +204,51 @@ export function DataTable<TData>({
</div>
{/* Paginação */}
<div className="flex items-center justify-end gap-2">
<div className="flex items-center justify-between gap-4">
<span className="text-sm text-muted-foreground">
Página {table.getState().pagination.pageIndex + 1} de {table.getPageCount()}
</span>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
aria-label="Primeira página"
>
Primeira
</Button>
<Button
className="cursor-pointer"
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
aria-label="Página anterior"
>
<ChevronLeftIcon />
<ChevronLeftIcon className="h-4 w-4" />
Anterior
</Button>
<Button
className="cursor-pointer"
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
aria-label="Próxima página"
>
Próxima
<ChevronRightIcon />
<ChevronRightIcon className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
aria-label="Última página"
>
Última
</Button>
</div>
</div>
</div>
);

View file

@ -152,6 +152,10 @@ const data = {
title: 'Pessoas/Jurídica',
url: '/cadastros/pessoa/juridica',
},
{
title: 'Imovel/Urbano',
url: '/cadastros/imoveis/urbanos',
},
],
},
{

View file

@ -0,0 +1,4 @@
export enum ImovelTipoRegistro {
M = 'Matrícula',
T = 'Transcrição'
}