[MVPTN-87] feat(CRUD): Ajustes diversos no crud de imovel e unidade

This commit is contained in:
Keven Willian Pereira de Souza 2025-10-02 15:30:15 -03:00
parent 4eabe19ee3
commit 1fcc5e442d
23 changed files with 886 additions and 465 deletions

View file

@ -0,0 +1,15 @@
import { UseFormReturn, FieldValues } from "react-hook-form";
/**
* Reseta o formulário com os dados recebidos (se existirem)
* @param form - Instância do react-hook-form
* @param data - Dados para popular o formulário
*/
export function ResetFormIfData<T extends FieldValues>(
form: UseFormReturn<T>,
data: T | null
) {
if (data) {
form.reset(data);
}
}

View file

@ -0,0 +1,6 @@
/**
* Converte o valor do input para número, enviando undefined se estiver vazio
*/
export function parseNumberInput(e: React.ChangeEvent<HTMLInputElement>): number | undefined {
return e.target.value ? Number(e.target.value) : undefined;
}

View file

@ -1,7 +1,7 @@
'use client';
import z from 'zod';
import { useEffect, useState } from 'react';
import { useEffect } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
@ -73,10 +73,13 @@ export default function GTBBairroForm({
>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Bairro</DialogTitle>
<DialogDescription>Crie ou edite um bairro</DialogDescription>
<DialogTitle>
Bairro
</DialogTitle>
<DialogDescription>
Crie ou edite um bairro
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSave)} className="space-y-6">
{/* Descrição */}
@ -93,7 +96,6 @@ export default function GTBBairroForm({
</FormItem>
)}
/>
{/* Situação */}
<Controller
name="situacao"
@ -108,7 +110,6 @@ export default function GTBBairroForm({
</div>
)}
/>
{/* Rodapé do Dialog */}
<DialogFooter className="mt-4">
<DialogClose asChild>

View file

@ -1,12 +1,10 @@
'use client';
import z from 'zod';
import { useEffect } from 'react';
import { useForm, Controller } from 'react-hook-form';
import React, { useEffect } from 'react';
import { useForm } 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,
@ -25,35 +23,50 @@ import {
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { TImovelSchema } from '../../_schemas/TImovelSchema';
import { TImovelFormValues, 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 { CheckIcon, ChevronsUpDownIcon, HouseIcon, IdCardIcon } from 'lucide-react';
import { Select, SelectContent, SelectItem, SelectTrigger } from '@/components/ui/select';
import TImovelUnidadePage from '../t_imovel_unidade/TImovelUnidadePage';
import { ImovelTipoRegistro } from '@/enums/ImovelTipoRegistro';
import { ImovelTipoClasseEnum } from '@/enums/ImovelTipoClasseEnum';
import { ResetFormIfData } from '@/actions/form/ResetFormIfData';
import { TImovelFormProps } from './TImovelFormProps';
import { useGTBBairroReadHook } from '../../_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 '@/actions/text/GetCapitalize';
type FormValues = z.infer<typeof TImovelSchema>;
export default function TImovelForm({ isOpen, data, onClose, onSave, buttonIsLoading }: TImovelFormProps) {
interface Props {
isOpen: boolean;
data: FormValues | null;
onClose: (item: null, isFormStatus: boolean) => void;
onSave: (data: FormValues) => void;
buttonIsLoading: boolean;
}
const { gTBBairro, fetchGTBBairro } = useGTBBairroReadHook();
export default function TCensecForm({ isOpen, data, onClose, onSave, buttonIsLoading }: Props) {
// Inicializa o react-hook-form com schema zod
const form = useForm<FormValues>({
const form = useForm<TImovelFormValues>({
resolver: zodResolver(TImovelSchema),
defaultValues: {},
});
// Atualiza o formulário quando recebe dados para edição
useEffect(() => {
if (data) form.reset(data);
// 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 (
@ -77,200 +90,270 @@ export default function TCensecForm({ isOpen, data, onClose, onSave, buttonIsLoa
{/* 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" />
<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" value="unidades">
<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 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>
)}
/>
<div className="grid w-full grid-cols-12 gap-4">
{/* 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>
)}
/>
<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 */}
<FormField
control={form.control}
name="cep"
render={({ field }) => (
<FormItem>
<FormLabel>CEP</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite o CEP" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<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 */}
<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>
)}
/>
<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 */}
<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 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" />
</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[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 */}
@ -286,7 +369,6 @@ export default function TCensecForm({ isOpen, data, onClose, onSave, buttonIsLoa
Cancelar
</Button>
</DialogClose>
<LoadingButton
text="Salvar"
textLoading="Aguarde..."
@ -301,4 +383,4 @@ export default function TCensecForm({ isOpen, data, onClose, onSave, buttonIsLoa
</DialogContent>
</Dialog>
);
}
}

View file

@ -0,0 +1,7 @@
export interface TImovelFormProps {
isOpen: boolean;
data: FormValues | null;
onClose: (item: null, isFormStatus: boolean) => void;
onSave: (data: FormValues) => void;
buttonIsLoading: boolean;
}

View file

@ -5,7 +5,7 @@ import TImovelColumns from './TImovelColumns';
import TImovelInterface from '../../_interfaces/TImovelInterface';
interface TImovelTableProps {
data: TImovelInterface[];
data?: TImovelInterface[];
onEdit: (item: TImovelInterface, isEditingFormStatus: boolean) => void;
onDelete: (item: TImovelInterface, isEditingFormStatus: boolean) => void;
}

View file

@ -26,7 +26,30 @@ export default function TImovelUnidadeColumns(
accessorKey: "imovel_unidade_id",
header: ({ column }) => SortableHeader("#", column),
cell: ({ row }) => Number(row.getValue("imovel_unidade_id")),
enableSorting: false,
},
// Número da Unidade
{
accessorKey: "numero_unidade",
header: ({ column }) => SortableHeader("Número da Unidade", column),
cell: ({ row }) => row.getValue("numero_unidade"),
},
// Quadra
{
accessorKey: "quadra",
header: ({ column }) => SortableHeader("Quadra", column),
cell: ({ row }) => row.getValue("quadra"),
},
// Area
{
accessorKey: "area",
header: ({ column }) => SortableHeader("Área", column),
cell: ({ row }) => row.getValue("area"),
},
// Logradouros
{
accessorKey: "logradouro",
header: ({ column }) => SortableHeader("Logradouro", column),
cell: ({ row }) => row.getValue("logradouro"),
},
// Ações
{

View file

@ -1,12 +1,10 @@
'use client';
import z from 'zod';
import { useEffect } from 'react';
import { useForm, Controller } from 'react-hook-form';
import React, { useEffect } from 'react';
import { useForm } 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,
@ -25,34 +23,50 @@ import {
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 { TImovelUnidadeFormValues, TImovelUnidadeSchema } from '../../_schemas/TImovelUnidadeSchema';
import TImovelUnidadeProps from './TImovelUnidadeFormProps';
import { useGTBTipoLogradouroReadHook } from '../../_hooks/g_tb_tipologradouro/useGTBTipoLogradouroReadHook';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react';
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command';
import { cn } from '@/lib/utils';
import GetCapitalize from '@/actions/text/GetCapitalize';
import { ResetFormIfData } from '@/actions/form/ResetFormIfData';
import { parseNumberInput } from '@/actions/form/parseNumberInput';
import { ImovelTipoEnum } from '@/enums/ImovelTipoEnum';
import { ImovelConstrucaoEnum } from '@/enums/ImovelConstrucaoEnum';
type FormValues = z.infer<typeof TImovelSchema>;
export default function TImovelUnidadeForm({ isOpen, data, onClose, onSave, buttonIsLoading }: TImovelUnidadeProps) {
interface Props {
isOpen: boolean;
data: FormValues | null;
onClose: (item: null, isFormStatus: boolean) => void;
onSave: (data: FormValues) => void;
buttonIsLoading: boolean;
}
const { gTBTipoLogradouro, fetchGTBTipoLogradouro } = useGTBTipoLogradouroReadHook();
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: {},
const form = useForm<TImovelUnidadeFormValues>({
resolver: zodResolver(TImovelUnidadeSchema),
defaultValues: {
imovel_id: 1,
},
});
// Atualiza o formulário quando recebe dados para edição
useEffect(() => {
if (data) form.reset(data);
// Se existir dados, reseta o formulário com os mesmos
ResetFormIfData(form, data);
// Carregamento de dados iniciais
async function loadData() {
// Carrega o tipo de logradouro
await fetchGTBTipoLogradouro();
}
// Executa a função
loadData();
}, [data, form]);
return (
@ -62,224 +76,450 @@ export default function TImovelUnidadeForm({ isOpen, data, onClose, onSave, butt
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">
<DialogContent className="w-full max-w-full p-6 sm:max-w-2xl md:max-w-2xl lg:max-w-3xl max-h-[60vh] overflow-auto">
<DialogHeader>
<DialogTitle>
Imóvel Urbano
Unidades do Imóvel
</DialogTitle>
<DialogDescription>
Cadastro de imóvel urbano
Cadastro de unidades do imóvel
</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 }) => (
<div className="grid grid-cols-12 sm:grid-cols-12 lg:grid-cols-12 gap-4">
{/* Quadra */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="quadra"
render={({ field }) => (
<FormItem>
<FormLabel>Quadra</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Lote */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="lote"
render={({ field }) => (
<FormItem>
<FormLabel>Lote</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Area */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="area"
render={({ field }) => (
<FormItem>
<FormLabel>Área(m2)</FormLabel>
<FormControl>
<Input {...field}
type='number'
onChange={e => field.onChange(parseNumberInput(e))}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Inscrição Municipal */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="inscricao_municipal"
render={({ field }) => (
<FormItem>
<FormLabel>Inscrição Municipal</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Tipo Logradouro */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="tb_tipologradouro_id"
render={({ field }) => {
const [open, setOpen] = React.useState(false);
return (
<FormItem>
<FormLabel>Tipo Classe</FormLabel>
<FormControl>
<Input {...field} placeholder="Digite o tipo de classe" />
</FormControl>
<FormLabel>Tipo logradouro</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
? gTBTipoLogradouro.find(
(item) =>
String(item.tb_tipologradouro_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>
{gTBTipoLogradouro?.map((item) => (
<CommandItem
key={item.tb_tipologradouro_id}
value={(item.descricao ?? '').toLowerCase()}
onSelect={() => {
field.onChange(Number(item.tb_tipologradouro_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>
)}
/>
{/* Tipo Registro */}
<FormField
control={form.control}
name="tipo_registro"
render={({ field }) => (
);
}}
/>
</div>
{/* Logradouro */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="logradouro"
render={({ field }) => (
<FormItem>
<FormLabel>Logradouro</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Tipo Imóvel */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="tipo_imovel"
render={({ field }) => {
const [open, setOpen] = React.useState(false);
// transforma o objeto em um array [{value, label}]
const options = Object.entries(ImovelTipoEnum).map(([id, label]) => ({
value: Number(id),
label,
}));
return (
<FormItem>
<FormLabel>Tipo Registro</FormLabel>
<FormControl>
<Select {...field}>
<option value="M">Matrícula</option>
<option value="T">Transcrição</option>
</Select>
</FormControl>
<FormLabel>Tipo Imóvel</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
? options.find((item) => item.value === field.value)?.label
: "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 imóvel..." />
<CommandList>
<CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
<CommandGroup>
{options.map((item) => (
<CommandItem
key={item.value}
value={item.label.toLowerCase()}
onSelect={() => {
field.onChange(item.value); // salva o número (id)
setOpen(false);
}}
>
<CheckIcon
className={cn(
"mr-2 h-4 w-4",
field.value === item.value
? "opacity-100"
: "opacity-0"
)}
/>
{item.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<FormMessage />
</FormItem>
)}
/>
{/* Número */}
<FormField
control={form.control}
name="numero"
render={({ field }) => (
);
}}
/>
</div>
{/* Construção */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="tipo_construcao"
render={({ field }) => {
const [open, setOpen] = React.useState(false);
const options = Object.entries(ImovelConstrucaoEnum).map(([id, label]) => ({
value: Number(id),
label,
}));
return (
<FormItem>
<FormLabel>Número</FormLabel>
<FormControl>
<Input {...field} type="number" placeholder="Digite o número" />
</FormControl>
<FormLabel>Construção</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
? options.find((item) => item.value === field.value)?.label
: "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 construção..." />
<CommandList>
<CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
<CommandGroup>
{options.map((item) => (
<CommandItem
key={item.value}
value={item.label.toLowerCase()}
onSelect={() => {
field.onChange(item.value);
setOpen(false);
}}
>
<CheckIcon
className={cn(
"mr-2 h-4 w-4",
field.value === item.value
? "opacity-100"
: "opacity-0"
)}
/>
{item.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<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>
);
}}
/>
</div>
{/* Iptu */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="iptu"
render={({ field }) => (
<FormItem>
<FormLabel>IPTU</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Unidade */}
<div className="col-span-12 sm:col-span-6 md:col-span-2">
<FormField
control={form.control}
name="numero_unidade"
render={({ field }) => (
<FormItem>
<FormLabel>Unidade</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Torre */}
<div className="col-span-12 sm:col-span-6 md:col-span-2">
<FormField
control={form.control}
name="torre"
render={({ field }) => (
<FormItem>
<FormLabel>Torre</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Condominio */}
<div className="col-span-12 sm:col-span-6 md:col-span-2">
<FormField
control={form.control}
name="nomecondominio"
render={({ field }) => (
<FormItem>
<FormLabel>Condominio</FormLabel>
<FormControl>
<Input {...field}
type='text'
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Loteamento */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="nomeloteamento"
render={({ field }) => (
<FormItem>
<FormLabel>Loteamento</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* CNM */}
<div className="col-span-12 sm:col-span-6 md:col-span-6">
<FormField
control={form.control}
name="cib"
render={({ field }) => (
<FormItem>
<FormLabel>CNM</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* CIB */}
<div className="col-span-12 sm:col-span-6 md:col-span-6">
<FormField
control={form.control}
name="cnm_numero"
render={({ field }) => (
<FormItem>
<FormLabel>CIB</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Numero da Edificação */}
<div className="col-span-12 sm:col-span-6 md:col-span-4">
<FormField
control={form.control}
name="numero_edificacao"
render={({ field }) => (
<FormItem>
<FormLabel>Número da Edificação</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Complemento */}
<div className="col-span-12 sm:col-span-6 md:col-span-8">
<FormField
control={form.control}
name="complemento"
render={({ field }) => (
<FormItem>
<FormLabel>Complemento</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
{/* Rodapé do Dialog */}
<DialogFooter className="mt-4 flex flex-col sm:flex-row gap-2 justify-end">
<DialogClose asChild>
@ -287,7 +527,6 @@ export default function TImovelUnidadeForm({ isOpen, data, onClose, onSave, butt
Cancelar
</Button>
</DialogClose>
<LoadingButton
text="Salvar"
textLoading="Aguarde..."
@ -295,8 +534,8 @@ export default function TImovelUnidadeForm({ isOpen, data, onClose, onSave, butt
loading={buttonIsLoading}
/>
</DialogFooter>
{/* Campo oculto */}
<input type="hidden" {...form.register("imovel_unidade_id")} />
<input type="hidden" {...form.register("imovel_id")} />
</form>
</Form>

View file

@ -0,0 +1,9 @@
import { TImovelUnidadeFormValues } from "../../_schemas/TImovelUnidadeSchema";
export default interface TImovelUnidadeProps {
isOpen: boolean;
data: TImovelUnidadeFormValues | null;
onClose: (item: null, isFormStatus: boolean) => void;
onSave: (data: TImovelUnidadeFormValues) => void;
buttonIsLoading: boolean;
}

View file

@ -20,8 +20,8 @@ export default function TImovelUnidadeTable({ data, onEdit, onDelete }: TImovelU
<DataTable
data={data}
columns={columns}
filterColumn="numero"
filterPlaceholder="Buscar pelo numero de transcrição, matricula etc..."
filterColumn="numero_unidade"
filterPlaceholder="Busque pelo numero da unidade"
/>
</div>
);

View file

@ -716,7 +716,6 @@ export default function TCensecForm({
)}
/>
</div>
{/* Número */}
<div className="col-span-12 sm:col-span-12 md:col-span-5">
<FormField
@ -733,7 +732,6 @@ export default function TCensecForm({
)}
/>
</div>
{/* CPF */}
<div className="col-span-12 sm:col-span-12 md:col-span-4">
<FormField
@ -750,7 +748,6 @@ export default function TCensecForm({
)}
/>
</div>
{/* Órgão Emissor */}
<div className="col-span-12 sm:col-span-12 md:col-span-4">
<FormField
@ -771,7 +768,6 @@ export default function TCensecForm({
)}
/>
</div>
{/* UF */}
<div className="col-span-12 sm:col-span-12 md:col-span-2">
<FormField
@ -788,7 +784,6 @@ export default function TCensecForm({
)}
/>
</div>
{/* Data de Expedição */}
<div className="col-span-12 sm:col-span-12 md:col-span-3">
<FormField

View file

@ -3,6 +3,11 @@ import TImovelInterface from "../../_interfaces/TImovelInterface";
async function executeTImovelSaveData(data: TImovelInterface) {
console.log({
status: 200,
message: 'Dados salvos',
});
return Promise.resolve({
status: 200,
message: 'Dados salvos',

View file

@ -8,9 +8,7 @@ import { TImovelIndexData } from '../../_data/TImovel/TImovelIndexData';
export const useTImovelIndexHook = () => {
const { setResponse } = useResponse();
const [tImovel, setTImovel] = useState<
TImovelInterface[] | null
>(null);
const [tImovel, setTImovel] = useState<TImovelInterface[]>();
const indexTImovel = async () => {
const response = await TImovelIndexData();

View file

@ -9,8 +9,7 @@ export const useTImovelUnidadeIndexHook = () => {
const { setResponse } = useResponse();
const [tImovelUnidade, setTImovelUnidade] = useState<
TImovelUnidadeInterface[] | null
>(null);
TImovelUnidadeInterface[]>();
const indexTImovelUnidade = async () => {
const response = await TImovelUnidadeIndexData();

View file

@ -30,4 +30,5 @@ export const useTImovelUnidadeSaveHook = () => {
};
return { tImovelUnidade, saveTImovelUnidade };
};

View file

@ -11,7 +11,7 @@ export default interface TImovelUnidadeInterface {
caracteristica?: string;
reserva_florestal?: string;
geo_referenciamento?: string;
logradouro?: string;
logradouro: string;
tb_tipologradouro_id?: number;
selecionado?: string;
complemento?: string;

View file

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

@ -13,7 +13,7 @@ export const TImovelUnidadeSchema = z.object({
caracteristica: z.string().optional(),
reserva_florestal: z.string().optional(),
geo_referenciamento: z.string().optional(),
logradouro: z.string().optional(),
logradouro: z.string().min(1, 'O campo deve ser preenchido').max(90, 'O campo não deve exceder 90 caracteres'),
tb_tipologradouro_id: z.number().optional(),
selecionado: z.string().optional(),
complemento: z.string().optional(),
@ -36,4 +36,7 @@ export const TImovelUnidadeSchema = z.object({
inscricao_municipal: z.string().optional(),
cib: z.string().optional(),
area_construida: z.number().optional(),
});
});
// Exportar o tipo inferido junto (opcional)
export type TImovelUnidadeFormValues = z.infer<typeof TImovelUnidadeSchema>;

View file

@ -216,6 +216,7 @@ export function DataTable<TData>({
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
aria-label="Primeira página"
className='cursor-pointer'
>
Primeira
</Button>
@ -225,6 +226,7 @@ export function DataTable<TData>({
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
aria-label="Página anterior"
className='cursor-pointer'
>
<ChevronLeftIcon className="h-4 w-4" />
Anterior
@ -235,6 +237,7 @@ export function DataTable<TData>({
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
aria-label="Próxima página"
className='cursor-pointer'
>
Próxima
<ChevronRightIcon className="h-4 w-4" />
@ -245,6 +248,7 @@ export function DataTable<TData>({
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
aria-label="Última página"
className='cursor-pointer'
>
Última
</Button>

View file

@ -0,0 +1,5 @@
export const ImovelConstrucaoEnum: { [key: number]: string } = {
0: 'Construção averbada',
1: 'Em construção',
2: 'Não se aplica',
};

View file

@ -0,0 +1,4 @@
export const ImovelTipoClasseEnum: { [key: number]: string } = {
1: 'Urbano',
3: 'Rural',
};

View file

@ -0,0 +1,22 @@
export const ImovelTipoEnum: { [key: number]: string } = {
15: 'Loja',
31: 'Galpão',
65: 'Apartamento',
67: 'Casa',
69: 'Fazenda / Sítio / Chácara',
71: 'Terreno / Fração',
89: 'Outros',
90: 'Sala',
91: 'Conjunto de salas',
92: 'Sobreloja',
17: 'Sala / Conjunto',
33: 'Prédio Comercial',
35: 'Prédio Residencial',
73: 'Sala ou Loja',
85: 'Construções',
87: 'Desmembramento',
93: 'Vaga de Garagem',
94: 'Laje',
95: 'Estacionamento',
96: 'Barraco'
};

View file

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