[MVPTN-86] feat(CRUD): Adicionado recursos dinâmicos de select

This commit is contained in:
Kenio 2025-10-17 18:21:30 -03:00
parent 6c7967587a
commit 56bca3ba41
23 changed files with 1306 additions and 235 deletions

View file

@ -1,9 +1,10 @@
'use client';
import React from "react";
import z from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import {
Dialog,
@ -15,7 +16,7 @@ import {
DialogTitle,
} from '@/components/ui/dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { DollarSign, Settings, SquarePen } from 'lucide-react';
import { CirclePlus, DollarSign, Settings, SquarePen, Trash } from 'lucide-react';
import {
Form,
FormControl,
@ -33,49 +34,143 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { TServicoTipoSchema } from '../../_schemas/TServicoTipoSchema';
import { TServicoTipoSchema, TServicoTipoFormValues } from '../../_schemas/TServicoTipoSchema';
import { useEffect } from 'react';
import GMarcacaoTipoSelect from '@/packages/administrativo/components/GMarcacaoTipo/GMarcacaoTipoSelect';
// Tipo inferido a partir do schema Zod
type FormValues = z.infer<typeof TServicoTipoSchema>;
import GEmolumentoSelect from '@/packages/administrativo/components/GEmolumento/GEmolumentoSelect';
import { useGEmolumentoItemReadHook } from "@/app/(protected)/(cadastros)/cadastros/_hooks/g_emolumento_item/useGEmolumentoItemReadHook";
import { GEmolumentoItemReadInterface } from "@/app/(protected)/(cadastros)/cadastros/_interfaces/GEmolumentoItemReadInterface";
import CategoriaServicoSelect from "@/shared/components/categoriaServicoSelect/CategoriaServicoSelect";
import CCaixaServicoSelect from "@/packages/administrativo/components/CCaixaServico/CCaixaServicoSelect";
// Propriedades esperadas pelo componente
interface Props {
isOpen: boolean;
data: FormValues | null;
data: TServicoTipoFormValues | null;
onClose: (item: null, isFormStatus: boolean) => void;
onSave: (data: FormValues) => void;
onSave: (data: TServicoTipoFormValues) => void;
}
// Componente principal do formulário
export default function TServicoTipoForm({ isOpen, data, onClose, onSave }: Props) {
// Estado para gerenciar os itens da tabela de etiquetas/carimbos
const [itensTabela, setItensTabela] = useState<{ marcacao_tipo_id: string; descricao: string }[]>([]);
// Função para adicionar um novo item à tabela
const handleAddEtiquetaCarimbo = () => {
const valorSelecionado = form.getValues('etiquetas_carimbos');
if (!valorSelecionado) return;
// Aqui você pode buscar o texto/descrição com base no seu componente GMarcacaoTipoSelect
// Exemplo: supondo que o valor seja um objeto { id, descricao }
const item = {
marcacao_tipo_id: valorSelecionado.key ?? valorSelecionado, // ou ajusta conforme a estrutura do seu componente
descricao: valorSelecionado.value ?? 'Sem descrição',
};
// Evita duplicatas
setItensTabela((prev) => {
if (prev.some((p) => p.marcacao_tipo_id === item.marcacao_tipo_id)) return prev;
return [...prev, item];
});
};
const handleRemoveItem = (marcacao_tipo_id: string) => {
setItensTabela((prev) => prev.filter((p) => p.marcacao_tipo_id !== marcacao_tipo_id));
};
// Parâmetros para o hook de leitura de emolumento_item
// Inicializa com valores padrão (0) para evitar uso de propriedades não declaradas.
const gEmolumentoItemReadParams: GEmolumentoItemReadInterface = { emolumento_id: 0, valor: 0 };
// Estados locais
const [open, setOpen] = React.useState(false);
const [isLoading, setIsLoading] = React.useState(false);
// Hook que realiza a leitura do emolumento_item
const { gGEmolumentoItem, fetchGEmolumentoItem } = useGEmolumentoItemReadHook();
// Busca os dados ao montar o componente (somente se houver ID válido)
React.useEffect(() => {
const loadData = async () => {
// Validação: só executa se houver emolumento_id informado e válido
if (
!gEmolumentoItemReadParams?.emolumento_id || // se não existir o campo
isNaN(Number(gEmolumentoItemReadParams.emolumento_id)) || // se não for número
Number(gEmolumentoItemReadParams.emolumento_id) <= 0 // se for zero ou negativo
) {
return; // encerra sem executar a busca
}
// Executa a busca apenas se ainda não houver dados carregados
if (!gGEmolumentoItem.length) {
setIsLoading(true);
await fetchGEmolumentoItem(gEmolumentoItemReadParams);
setIsLoading(false);
}
};
loadData();
}, [gEmolumentoItemReadParams.emolumento_id]);
// Inicializa o react-hook-form com validação via Zod
const form = useForm<FormValues>({
resolver: zodResolver(TServicoTipoSchema),
defaultValues: {
servico_tipo_id: 0,
descricao: '',
categoria: '',
servico_padrao: '',
servico_caixa: '',
averbacao: false,
transferencia_veiculo: false,
usar_a4: false,
etiqueta_unica: '',
},
});
const form = useForm<TServicoTipoFormValues>({
resolver: zodResolver(TServicoTipoSchema),
defaultValues: {
servico_tipo_id: 0,
descricao: "",
categoria: "",
frenteverso: "N",
averbacao: "N",
transferencia_veiculo: "N",
usar_a4: "N",
etiqueta_unica: "N",
situacao: "A",
selar: "N",
valor_emolumento: 0,
valor_taxa_judiciaria: 0,
fundesp_valor: 0,
valor_total: 0,
},
});
// Captura o ID do serviço para uso local
const servicoTipoId = Number(form.watch("servico_tipo_id") || data?.servico_tipo_id || 0);
// Atualiza os valores quando há dados para edição
useEffect(() => {
if (data) {
if (data && Number(data.servico_tipo_id) > 0) {
//Carrega os valores no form
form.reset(data);
} else {
form.reset();
// Se não houver dados, reseta o
// formulário para os valores iniciais
// Modo novo cadastro → zera o ID manualmente
form.reset({
...form.getValues(), // mantém outros valores default
servico_tipo_id: 0,
descricao: '',
});
}
}, [data, form]);
}, [data, form]);
return (
<Dialog
@ -103,11 +198,19 @@ export default function TServicoTipoForm({ isOpen, data, onClose, onSave }: Prop
<SquarePen className="me-1 inline" />
Dados do Tipo de Serviço
</TabsTrigger>
<TabsTrigger className="flex-1 text-center cursor-pointer" value="configuracoes">
<TabsTrigger
className="flex-1 text-center cursor-pointer"
value="configuracoes"
disabled={servicoTipoId <= 0}
>
<Settings className="inline" />
Configurações
</TabsTrigger>
<TabsTrigger className="flex-1 text-center cursor-pointer" value="valores">
<TabsTrigger
className="flex-1 text-center cursor-pointer"
value="valores"
disabled={servicoTipoId <= 0}
>
<DollarSign className="inline" />
Valores
</TabsTrigger>
@ -141,25 +244,11 @@ export default function TServicoTipoForm({ isOpen, data, onClose, onSave }: Prop
control={form.control}
name="categoria"
render={({ field }) => (
<FormItem className="w-full">
<FormItem className="w-full cursor-pointer">
<FormLabel>Categoria</FormLabel>
<Select
value={field.value}
onValueChange={field.onChange}
>
<FormControl className="w-full">
<SelectTrigger className="w-full cursor-pointer">
<SelectValue placeholder="Selecione uma categoria" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="1" className="cursor-pointer">Autenticação</SelectItem>
<SelectItem value="2" className="cursor-pointer">Certidão</SelectItem>
<SelectItem value="3" className="cursor-pointer">Serviços Gerais</SelectItem>
<SelectItem value="4" className="cursor-pointer">Reconhecimento</SelectItem>
<SelectItem value="5" className="cursor-pointer">Geral</SelectItem>
</SelectContent>
</Select>
<CategoriaServicoSelect
field={field}
/>
<FormMessage />
</FormItem>
)}
@ -198,27 +287,14 @@ export default function TServicoTipoForm({ isOpen, data, onClose, onSave }: Prop
<div className="col-span-12 sm:col-span-6 md:col-span-4">
<FormField
control={form.control}
name="servico_caixa"
name="servico_caixa_id"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Serviço Caixa</FormLabel>
<Select
value={field.value}
onValueChange={field.onChange}
>
<FormControl className="w-full">
<SelectTrigger className="w-full cursor-pointer">
<SelectValue placeholder="Selecione uma opção" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="1" className="cursor-pointer">Autenticação</SelectItem>
<SelectItem value="2" className="cursor-pointer">Reconhecimento</SelectItem>
<SelectItem value="3" className="cursor-pointer">Aluguel</SelectItem>
<SelectItem value="4" className="cursor-pointer">Papelaria</SelectItem>
<SelectItem value="5" className="cursor-pointer">Limpeza Cartório</SelectItem>
</SelectContent>
</Select>
<CCaixaServicoSelect
sistema_id={2}
field={field}
/>
<FormMessage />
</FormItem>
)}
@ -233,9 +309,10 @@ export default function TServicoTipoForm({ isOpen, data, onClose, onSave }: Prop
render={({ field }) => (
<FormItem className="flex flex-row items-center space-x-3 space-y-0">
<FormControl>
<Checkbox className="cursor-pointer"
checked={field.value}
onCheckedChange={field.onChange}
<Checkbox
className="cursor-pointer"
checked={field.value === "S"} // marcado se for "S"
onCheckedChange={(checked) => field.onChange(checked ? "S" : "N")} // salva "S" ou "N"
/>
</FormControl>
<FormLabel className="font-normal cursor-pointer">Averbação</FormLabel>
@ -252,9 +329,10 @@ export default function TServicoTipoForm({ isOpen, data, onClose, onSave }: Prop
render={({ field }) => (
<FormItem className="flex flex-row items-center space-x-3 space-y-0">
<FormControl>
<Checkbox className="cursor-pointer"
checked={field.value}
onCheckedChange={field.onChange}
<Checkbox
className="cursor-pointer"
checked={field.value === "S"} // marcado se o valor for "S"
onCheckedChange={(checked) => field.onChange(checked ? "S" : "N")} // salva "S" ou "N"
/>
</FormControl>
<FormLabel className="font-normal cursor-pointer">Transferência Veículo</FormLabel>
@ -271,9 +349,10 @@ export default function TServicoTipoForm({ isOpen, data, onClose, onSave }: Prop
render={({ field }) => (
<FormItem className="flex flex-row items-center space-x-3 space-y-0">
<FormControl>
<Checkbox className="cursor-pointer"
checked={field.value}
onCheckedChange={field.onChange}
<Checkbox
className="cursor-pointer"
checked={field.value === "S"} // marcado se o valor for "S"
onCheckedChange={(checked) => field.onChange(checked ? "S" : "N")} // grava "S" ou "N"
/>
</FormControl>
<FormLabel className="font-normal cursor-pointer">Usar A4</FormLabel>
@ -290,9 +369,10 @@ export default function TServicoTipoForm({ isOpen, data, onClose, onSave }: Prop
render={({ field }) => (
<FormItem className="flex flex-row items-center space-x-3 space-y-0">
<FormControl>
<Checkbox className="cursor-pointer"
checked={field.value}
onCheckedChange={field.onChange}
<Checkbox
className="cursor-pointer"
checked={field.value === "S"} // marcado se o valor for "S"
onCheckedChange={(checked) => field.onChange(checked ? "S" : "N")} // grava "S" ou "N"
/>
</FormControl>
<FormLabel className="font-normal cursor-pointer">Etiqueta Única</FormLabel>
@ -300,6 +380,63 @@ export default function TServicoTipoForm({ isOpen, data, onClose, onSave }: Prop
)}
/>
</div>
{/* Campo: Frente/Verso (Texto normal) */}
<div className="col-span-12 sm:col-span-6 md:col-span-4">
<FormField
control={form.control}
name="frenteverso"
render={({ field }) => {
const categoriaSelecionada = form.watch("categoria");
const isEnabled = categoriaSelecionada === "A";
return (
<FormItem className="flex flex-row items-center space-x-3 space-y-0">
<FormControl>
<Checkbox
className="cursor-pointer"
checked={field.value === "S"} // marca quando o valor for "S"
onCheckedChange={(checked) => field.onChange(checked ? "S" : "N")} // grava "S" ou "N"
disabled={!isEnabled}
/>
</FormControl>
<FormLabel className="font-normal cursor-pointer">Frente/Verso</FormLabel>
</FormItem>
);
}}
/>
</div>
{/* Campo: Etiqueta Única (Texto normal) */}
<div className="col-span-12 sm:col-span-6 md:col-span-4">
<FormField
control={form.control}
name="situacao"
render={({ field }) => {
// Considera "A" ou vazio como marcado
const isChecked = field.value === "A" || !field.value;
return (
<FormItem className="flex flex-row items-center space-x-3 space-y-0">
<FormControl>
<Checkbox
className="cursor-pointer"
checked={isChecked}
onCheckedChange={(checked) => {
field.onChange(checked ? "A" : "I"); // grava "A" ou "I" no form
}}
/>
</FormControl>
<FormLabel className="font-normal cursor-pointer">Ativo</FormLabel>
</FormItem>
);
}}
/>
</div>
</div>
</TabsContent>
@ -408,7 +545,7 @@ export default function TServicoTipoForm({ isOpen, data, onClose, onSave }: Prop
/>
</div>
<div className="col-span-12">Requerer</div>
<div className="col-span-12 underline">Requerer:</div>
{/* Campo: Biometria (Select) */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
@ -526,30 +663,283 @@ export default function TServicoTipoForm({ isOpen, data, onClose, onSave }: Prop
{/* Campo: etiquetas/carimbos (Select) */}
<div className="col-span-12 sm:col-span-6 md:col-span-12">
<div className="col-span-12 sm:col-span-6 md:col-span-10">
<FormField
control={form.control}
name="etiquetas_carimbos"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Etiquetas/Carimbos</FormLabel>
<GMarcacaoTipoSelect
grupo="MODELO_ETIQUETA"
sistema_id={2}
situacao="A"
field={field}
/>
<GMarcacaoTipoSelect
grupo="MODELO_ETIQUETA"
sistema_id={2}
situacao="A"
field={field}
/>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
{/* Campo: etiquetas/carimbos botão adicionar */}
<div className="col-span-12 sm:col-span-6 md:col-span-2">
<FormItem className="w-full">
<FormLabel>&nbsp;</FormLabel>
<Button
variant="outline"
className="w-full cursor-pointer"
type="button"
onClick={handleAddEtiquetaCarimbo}
>
<CirclePlus /> Adicionar
</Button>
</FormItem>
</div>
{/* Campo: etiquetas/carimbos (Select) */}
<div className="col-span-12 sm:col-span-6 md:col-span-12">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[90px] text-center">#</TableHead>
<TableHead>Descrição</TableHead>
<TableHead className="text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{itensTabela.map((item, index) => (
<TableRow key={item.marcacao_tipo_id}>
<TableCell className="w-[90px] text-center">
{String(index + 1).padStart(3, '0')}
</TableCell>
<TableCell>{item.descricao}</TableCell>
<TableCell className="w-[100px]">
<Button
variant="outline"
className="w-full cursor-pointer"
type="button"
onClick={() => handleRemoveItem(item.marcacao_tipo_id)}
>
<Trash /> Remover
</Button>
</TableCell>
</TableRow>
))}
{itensTabela.length === 0 && (
<TableRow>
<TableCell colSpan={3} className="text-center text-gray-500">
Nenhum item adicionado
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</div>
</TabsContent>
<TabsContent value="valores" className="space-y-4">
003
<div className="grid w-full grid-cols-12 gap-4 border p-4 rounded-md">
{/* Campo: Biometria (Select) */}
<div className="col-span-12 sm:col-span-6 md:col-span-2">
<FormField
control={form.control}
name="selar"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Selar</FormLabel>
<Select
value={field.value}
onValueChange={field.onChange}
>
<FormControl className="w-full">
<SelectTrigger className="w-full cursor-pointer">
<SelectValue placeholder="Selecione uma opção" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="S" className="cursor-pointer">Sim</SelectItem>
<SelectItem value="N" className="cursor-pointer">Não</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Campo: emolumentos (Select) */}
<div className="col-span-12 sm:col-span-6 md:col-span-10">
<FormField
control={form.control}
name="emolumento"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Emolumento</FormLabel>
<GEmolumentoSelect
className="w-full"
sistema_id={2}
field={field}
onSelectChange={async (selected) => {
// Extrai o ID do emolumento selecionado
const emolumentoId = Number(selected?.key);
// Se o ID for inválido, encerra sem fazer a requisição
if (!emolumentoId || isNaN(emolumentoId) || emolumentoId <= 0) return;
setIsLoading(true);
try {
// Faz a requisição para buscar o item do emolumento
const response = await fetchGEmolumentoItem({
emolumento_id: emolumentoId,
valor: 0,
});
// Extrai o primeiro item do array "data"
const item = response?.data?.[0];
// Se existir item, atualiza os campos do formulário
if (item) {
form.setValue("valor_emolumento", item.valor_emolumento ?? 0);
form.setValue("valor_taxa_judiciaria", item.valor_taxa_judiciaria ?? 0);
// form.setValue("fundesp_valor", item.valor_outra_taxa1 ?? 0);
// form.setValue("valor_pagina_extra", item.valor_pagina_extra ?? 0);
// form.setValue("emolumento_item_id", item.emolumento_item_id ?? 0);
// (💡 opcional) Atualiza o total automaticamente
const total =
(item.valor_emolumento ?? 0) +
(item.valor_taxa_judiciaria ?? 0) +
(item.valor_outra_taxa1 ?? 0);
form.setValue("valor_total", total);
}
} catch (error) {
console.error("Erro ao buscar item de emolumento:", error);
} finally {
setIsLoading(false);
}
}}
/>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Campo: emolumentos adicional (Select) */}
<div className="col-span-12 sm:col-span-6 md:col-span-12">
<FormField
control={form.control}
name="emolumento_auxiliar"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Emolumento Adicional (Mesma etiqueta)</FormLabel>
<GEmolumentoSelect
className="w-full"
sistema_id={2}
field={field}
/>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Campo: Quantidade de pessoas */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="valor_emolumento"
render={({ field }) => (
<FormItem>
<FormLabel>Emolumento R$</FormLabel>
<FormControl>
<Input {...field} value={field.value ?? ''}
disabled
placeholder="0,00"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Campo: Quantidade de pessoas */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="valor_taxa_judiciaria"
render={({ field }) => (
<FormItem>
<FormLabel>Taxa Judiciária R$</FormLabel>
<FormControl>
<Input {...field} value={field.value ?? ''}
disabled
placeholder="0,00"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Campo: Quantidade de pessoas */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="fundesp_valor"
render={({ field }) => (
<FormItem>
<FormLabel>Fundesp R$</FormLabel>
<FormControl>
<Input {...field} value={field.value ?? ''}
disabled
placeholder="0,00"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Campo: Quantidade de pessoas */}
<div className="col-span-12 sm:col-span-6 md:col-span-3">
<FormField
control={form.control}
name="valor_total"
render={({ field }) => (
<FormItem>
<FormLabel>Total R$</FormLabel>
<FormControl>
<Input {...field} value={field.value ?? ''} placeholder="0,00" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
</TabsContent>
{/* Campo oculto: ID */}
@ -559,11 +949,19 @@ export default function TServicoTipoForm({ isOpen, data, onClose, onSave }: Prop
{/* Rodapé do diálogo */}
<DialogFooter className="mt-4">
<DialogClose asChild>
<Button variant="outline" type="button" onClick={() => onClose(null, false)}>
<Button
variant="outline"
type="button"
onClick={() => onClose(null, false)}
className="cursor-pointer"
>
Cancelar
</Button>
</DialogClose>
<Button type="submit">Salvar</Button>
<Button
type="submit"
className="cursor-pointer"
>Salvar</Button>
</DialogFooter>
</form>
</Form>

View file

@ -0,0 +1,27 @@
// Importa o utilitário responsável por tratar erros de forma padronizada no cliente
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
// Importa a classe de serviço que gerencia requisições HTTP para a API
import API from '@/shared/services/api/Api';
// Importa o enum que define os métodos HTTP disponíveis (GET, POST, PUT, DELETE, etc.)
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import { CCaixaServicoReadInterface } from '../../_interfaces/CCaixaServicoReadInterface';
// Função assíncrona responsável por executar a requisição para listar os tipos de marcação
async function executeCCaixaServicoIndexData(data: CCaixaServicoReadInterface) {
// Cria uma nova instância da classe API para enviar a requisição
const api = new API();
// Concatena o endpoint com a query string (caso existam parâmetros)
const endpoint = `administrativo/c_caixa_servico/sistema/${data.sistema_id}`;
// Envia uma requisição GET para o endpoint 'administrativo/g_marcacao_tipo/'
return await api.send({
method: Methods.GET,
endpoint: endpoint,
});
}
// Exporta a função encapsulada pelo handler de erro, garantindo tratamento uniforme em caso de falhas
export const CCaixaServicoIndexData = withClientErrorHandler(executeCCaixaServicoIndexData);

View file

@ -0,0 +1,27 @@
// Importa o utilitário responsável por tratar erros de forma padronizada no cliente
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
// Importa a classe de serviço que gerencia requisições HTTP para a API
import API from '@/shared/services/api/Api';
// Importa o enum que define os métodos HTTP disponíveis (GET, POST, PUT, DELETE, etc.)
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import { GEmolumentoReadInterface } from '../../_interfaces/GEmolumentoReadInterface';
// Função assíncrona responsável por executar a requisição para listar os tipos de marcação
async function executeGEmolumentoIndexData(data: GEmolumentoReadInterface) {
// Cria uma nova instância da classe API para enviar a requisição
const api = new API();
// Concatena o endpoint com a query string (caso existam parâmetros)
const endpoint = `administrativo/g_emolumento/${data.sistema_id}`;
// Envia uma requisição GET para o endpoint 'administrativo/g_marcacao_tipo/'
return await api.send({
method: Methods.GET,
endpoint: endpoint,
});
}
// Exporta a função encapsulada pelo handler de erro, garantindo tratamento uniforme em caso de falhas
export const GEmolumentoIndexData = withClientErrorHandler(executeGEmolumentoIndexData);

View file

@ -0,0 +1,27 @@
// Importa o utilitário responsável por tratar erros de forma padronizada no cliente
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
// Importa a classe de serviço que gerencia requisições HTTP para a API
import API from '@/shared/services/api/Api';
// Importa o enum que define os métodos HTTP disponíveis (GET, POST, PUT, DELETE, etc.)
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
import { GEmolumentoItemReadInterface } from '../../_interfaces/GEmolumentoItemReadInterface';
// Função assíncrona responsável por executar a requisição para listar os tipos de marcação
async function executeGEmolumentoItemValorData(data: GEmolumentoItemReadInterface) {
// Cria uma nova instância da classe API para enviar a requisição
const api = new API();
// Concatena o endpoint com a query string (caso existam parâmetros)
const endpoint = `administrativo/g_emolumento_item/faixa/${data.emolumento_id}/${data.valor}`;
// Envia uma requisição GET para o endpoint 'administrativo/g_marcacao_tipo/'
return await api.send({
method: Methods.GET,
endpoint: endpoint,
});
}
// Exporta a função encapsulada pelo handler de erro, garantindo tratamento uniforme em caso de falhas
export const GEmolumentoItemValorData = withClientErrorHandler(executeGEmolumentoItemValorData);

View file

@ -0,0 +1,37 @@
// Importa o hook responsável por gerenciar e exibir respostas globais (sucesso, erro, etc.)
import { useResponse } from '@/shared/components/response/ResponseContext';
// Importa hooks do React para gerenciamento de estado e memorização de valores
import { useMemo, useState } from 'react';
// Importa a interface que define a estrutura dos dados de "CCaixaServico"
import { CCaixaServicoReadInterface } from '../../_interfaces/CCaixaServicoReadInterface';
// Importa o serviço responsável por buscar os dados de "CCaixaServico" na API
import { CCaixaServicoIndexService } from '../../_services/c_caixa_servico/CCaixaServicoIndexService';
import { CCaixaServicoInterface } from '../../_interfaces/CCaixaServicoInterface';
// Hook personalizado para leitura (consulta) dos tipos de marcação
export const useCCaixaServicoReadHook = () => {
// Obtém a função que atualiza a resposta global do sistema
const { setResponse } = useResponse();
// Define o estado local que armazenará a lista de tipos de marcação
const [cCaixaServico, setCCaixaServico] = useState<CCaixaServicoInterface[]>([]);
// Função responsável por buscar os dados da API e atualizar o estado
const fetchCCaixaServico = async (data: CCaixaServicoReadInterface) => {
// Executa o serviço que faz a requisição à API
const response = await CCaixaServicoIndexService(data);
// Atualiza o estado local com os dados retornados
setCCaixaServico(response.data);
// Atualiza o contexto global de resposta (ex: para exibir alertas ou mensagens)
setResponse(response);
};
// Retorna os dados e a função de busca, memorizando o valor para evitar recriações desnecessárias
return useMemo(() => ({ cCaixaServico, fetchCCaixaServico }), [cCaixaServico, fetchCCaixaServico]);
};

View file

@ -0,0 +1,37 @@
// Importa o hook responsável por gerenciar e exibir respostas globais (sucesso, erro, etc.)
import { useResponse } from '@/shared/components/response/ResponseContext';
// Importa hooks do React para gerenciamento de estado e memorização de valores
import { useMemo, useState } from 'react';
// Importa a interface que define a estrutura dos dados de "GEmolumento"
import { GEmolumentoReadInterface } from '../../_interfaces/GEmolumentoReadInterface';
// Importa o serviço responsável por buscar os dados de "GEmolumento" na API
import { GEmolumentoIndexService } from '../../_services/g_emolumento/GEmolumentoIndexService';
import { GEmolumentoInterface } from '../../_interfaces/GEmolumentoInterface';
// Hook personalizado para leitura (consulta) dos emolumentos
export const useGEmolumentoReadHook = () => {
// Obtém a função que atualiza a resposta global do sistema
const { setResponse } = useResponse();
// Define o estado local que armazenará a lista de emolumentos
const [gEmolumento, setGEmolumento] = useState<GEmolumentoInterface[]>([]);
// Função responsável por buscar os dados da API e atualizar o estado
const fetchGEmolumento = async (data: GEmolumentoReadInterface) => {
// Executa o serviço que faz a requisição à API
const response = await GEmolumentoIndexService(data);
// Atualiza o estado local com os dados retornados
setGEmolumento(response.data);
// Atualiza o contexto global de resposta (ex: para exibir alertas ou mensagens)
setResponse(response);
};
// Retorna os dados e a função de busca, memorizando o valor para evitar recriações desnecessárias
return useMemo(() => ({ gEmolumento, fetchGEmolumento }), [gEmolumento, fetchGEmolumento]);
};

View file

@ -0,0 +1,46 @@
// Importa o hook responsável por gerenciar e exibir respostas globais (sucesso, erro, etc.)
import { useResponse } from '@/shared/components/response/ResponseContext';
// Importa hooks do React para gerenciamento de estado e memorização de valores
import { useMemo, useState } from 'react';
// Importa a interface que define a estrutura dos dados de "gEmolumentoItem"
import { GEmolumentoItemReadInterface } from '../../_interfaces/GEmolumentoItemReadInterface';
// Importa o serviço responsável por buscar os dados de "GEmolumentoItem" na API
import { GEmolumentoItemValorService } from '../../_services/g_emolumento_item/GEmolumentoItemValorService';
import { GEmolumentoItemInterface } from '../../_interfaces/GEmolumentoItemInterface';
// Hook personalizado para leitura (consulta) dos emolumentos
export const useGEmolumentoItemReadHook = () => {
const { setResponse } = useResponse();
const [gGEmolumentoItem, setGEmolumentoItem] = useState<GEmolumentoItemInterface[]>([]);
const fetchGEmolumentoItem = async (data: GEmolumentoItemReadInterface) => {
try {
const response = await GEmolumentoItemValorService(data);
// Atualiza estado e contexto
setGEmolumentoItem(response.data);
setResponse(response);
// Retorna a resposta completa (para uso externo)
return response;
} catch (error) {
console.error("Erro ao buscar item de emolumento:", error);
setResponse({
message: "Erro ao buscar item de emolumento",
error: error instanceof Error ? error.message : String(error),
});
return null; // Retorna nulo para segurança
}
};
// Retorna função e dados memorizados
return useMemo(
() => ({ gGEmolumentoItem, fetchGEmolumentoItem }),
[gGEmolumentoItem]
);
};

View file

@ -0,0 +1,17 @@
// Interface que representa a tabela C_CAIXA_SERVICO
export interface CCaixaServicoInterface {
interno_sistema?: string; // VARCHAR(1)
caixa_servico_id: number; // NUMERIC(10,2) NOT NULL - Chave primária
descricao?: string; // VARCHAR(60)
situacao?: string; // VARCHAR(1)
tipo_transacao?: string; // VARCHAR(1)
sistema_id?: number; // NUMERIC(14,3)
selo_grupo_id?: number; // NUMERIC(10,2)
emitir_relatorio?: string; // VARCHAR(1)
repasse?: string; // VARCHAR(1)
repetir_descricao?: string; // VARCHAR(1)
codigo_conta?: number; // NUMERIC(10,2)
tipo_conta_carneleao?: string; // VARCHAR(60)
centro_de_custa_id?: number; // NUMERIC(10,2) - Chave estrangeira
devolucao_juizo?: string; // VARCHAR(1)
}

View file

@ -0,0 +1,3 @@
export interface CCaixaServicoReadInterface {
sistema_id?: number;
}

View file

@ -0,0 +1,20 @@
// Interface que representa a tabela G_EMOLUMENTO
export interface GEmolumentoInterface {
emolumento_id?: number; // NUMERIC(10,2) - Chave primária
descricao?: string; // VARCHAR(260)
tipo?: string; // VARCHAR(1)
sistema_id?: number; // NUMERIC(10,2)
selo_grupo_id?: number; // NUMERIC(10,2)
reg_averb?: string; // VARCHAR(1)
pre_definido?: string; // VARCHAR(1)
situacao?: string; // VARCHAR(1)
situacao_ri?: string; // VARCHAR(1)
com_reducao?: string; // VARCHAR(1)
motivo_reducao?: string; // VARCHAR(120)
valor_maximo_certidao?: number; // NUMERIC(14,3)
tipo_objetivo?: string; // VARCHAR(3)
modelo_tag?: string; // VARCHAR(3)
codigo_nota_id?: number; // NUMERIC(10,2)
convenio_codhab?: string; // VARCHAR(1)
item_df?: string; // VARCHAR(10)
}

View file

@ -0,0 +1,32 @@
// Interface que representa a tabela G_EMOLUMENTO_ITEM (inferido)
export interface GEmolumentoItemInterface {
valor_emolumento?: number; // NUMERIC(14,3)
emolumento_item_id: number; // NUMERIC(10,2) NOT NULL - Chave primária (assumida)
emolumento_id?: number; // NUMERIC(10,2)
valor_inicio?: number; // NUMERIC(14,3)
valor_fim?: number; // NUMERIC(14,3)
valor_taxa_judiciaria?: number; // NUMERIC(14,3)
emolumento_periodo_id?: number; // NUMERIC(10,2)
codigo?: number; // NUMERIC(10,2)
pagina_extra?: number; // NUMERIC(10,2)
valor_pagina_extra?: number; // NUMERIC(14,3)
valor_outra_taxa1?: number; // NUMERIC(14,3)
codigo_selo?: string; // VARCHAR(30)
valor_fundo_ri?: number; // NUMERIC(14,3)
codigo_tabela?: string; // VARCHAR(30)
selo_grupo_id?: number; // NUMERIC(10,2)
codigo_km?: string; // VARCHAR(30)
emolumento_acresce?: number; // NUMERIC(14,3)
taxa_acresce?: number; // NUMERIC(14,3)
funcivil_acresce?: number; // NUMERIC(14,3)
valor_fracao?: number; // NUMERIC(14,3)
valor_por_excedente_emol?: number; // NUMERIC(14,3)
valor_por_excedente_tj?: number; // NUMERIC(14,3)
valor_por_excedente_fundo?: number; // NUMERIC(14,3)
valor_limite_excedente_emol?: number; // NUMERIC(14,3)
valor_limite_excedente_tj?: number; // NUMERIC(14,3)
valor_limite_excedente_fundo?: number; // NUMERIC(14,3)
fundo_selo?: number; // NUMERIC(14,3)
distribuicao?: number; // NUMERIC(14,3)
vrc_ext?: number; // NUMERIC(10,2) - Renomeado de VRCEXT para vrc_ext (convenção)
}

View file

@ -0,0 +1,4 @@
export interface GEmolumentoItemReadInterface {
emolumento_id?: number,
valor?: number
}

View file

@ -0,0 +1,3 @@
export interface GEmolumentoReadInterface {
sistema_id?: number
}

View file

@ -1,122 +1,78 @@
import z from 'zod';
import z from "zod";
// Define um esquema para campos de 1 caractere (sim/não ou tipo)
// Poderia ser um z.enum(['S', 'N']) mais restritivo dependendo da regra de negócio,
// mas seguindo o padrão da DDL (VARCHAR(1)), o string.max(1) é mais flexível.
const OneCharString = z.string().max(1, 'Deve ter no máximo 1 caractere').optional();
// Define um esquema para campos string obrigatórios
const RequiredString = z.string().min(1, 'O campo é obrigatório');
// Define um esquema para campos numéricos opcionais, baseados em NUMERIC(10,2) ou similar
/**
* Tipos utilitários para campos simples
*/
const SN = z.enum(["S", "N"]).default("N"); // Campos do tipo Sim/Não
const AI = z.enum(["A", "I"]).default("A"); // Situação Ativo/Inativo
const OneCharString = z.string().max(1, "Deve ter no máximo 1 caractere").optional();
const RequiredString = z.string().min(1, "O campo é obrigatório");
const OptionalNumber = z.number().optional();
// Define um esquema para campos numéricos obrigatórios
const RequiredNumber = z.number();
/**
* Schema principal baseado na DDL e adaptado ao formulário React
*/
export const TServicoTipoSchema = z.object({
// Chave primária: NOT NULL na DDL
servico_tipo_id: RequiredNumber.describe('ID do Tipo de Serviço').optional(), // Mantido optional seguindo o padrão de GCidadeSchema para chaves ID
// VARCHAR(60)
descricao: z.string().max(60, 'A descrição deve ter no máximo 60 caracteres').optional(),
// NUMERIC(14,3)
valor: z.number().optional(),
// VARCHAR(1)
tipo_item: OneCharString,
// VARCHAR(1)
requer_autorizacao: OneCharString,
// VARCHAR(1)
requer_biometria: OneCharString,
// VARCHAR(1)
tipo_pessoa: OneCharString,
// NUMERIC(10,2) - Chave estrangeira
tb_reconhecimentotipo_id: OptionalNumber,
// VARCHAR(1)
tipo_permissao_cpf: OneCharString,
// VARCHAR(1)
requer_abonador: OneCharString,
// VARCHAR(1)
requer_representante: OneCharString,
// VARCHAR(1)
situacao: OneCharString,
// VARCHAR(1)
requer_cpf: OneCharString,
// VARCHAR(1)
servico_padrao: OneCharString,
// NUMERIC(10,2)
// Identificador
servico_tipo_id: RequiredNumber.describe("ID do Tipo de Serviço").optional(),
// Campos principais
descricao: z.string().max(60, "A descrição deve ter no máximo 60 caracteres").optional(),
categoria: z.string().optional(),
// Controle de flags (S/N)
frenteverso: SN.optional(),
averbacao: SN.optional(),
transferencia_veiculo: SN.optional(),
usar_a4: SN.optional(),
etiqueta_unica: SN.optional(),
selar: SN.optional(),
servico_padrao: SN.optional(),
lancar_taxa: SN.optional(),
lancar_fundesp: SN.optional(),
liberar_desconto: SN.optional(),
fundesp_automatica: SN.optional(),
lancar_valor_documento: SN.optional(),
valor_fixo: SN.optional(),
ato_praticado: SN.optional(),
apresentante_selo: SN.optional(),
renovacao_cartao: SN.optional(),
// Situação
situacao: AI,
// Campos numéricos
valor: OptionalNumber,
maximo_pessoa: OptionalNumber,
// VARCHAR(1)
alterar_valor: OneCharString,
// NUMERIC(10,2)
servico_caixa_id: OptionalNumber,
// VARCHAR(1)
lancar_taxa: OneCharString,
// VARCHAR(1)
lancar_fundesp: OneCharString,
// VARCHAR(1)
liberar_desconto: OneCharString,
// VARCHAR(1)
fundesp_automatica: OneCharString,
// VARCHAR(1)
lancar_valor_documento: OneCharString,
// VARCHAR(1)
valor_fixo: OneCharString,
// NUMERIC(10,2) - Chave estrangeira
emolumento_id: OptionalNumber,
// VARCHAR(1)
ato_praticado: OneCharString,
// VARCHAR(1)
selar: OneCharString,
// VARCHAR(1)
frenteverso: OneCharString,
// VARCHAR(1)
pagina_acrescida: OneCharString,
// NUMERIC(10,2)
emolumento_obrigatorio: OptionalNumber,
// VARCHAR(1)
apresentante_selo: OneCharString,
// VARCHAR(1)
renovacao_cartao: OneCharString,
// VARCHAR(1)
etiqueta_unica: OneCharString,
// VARCHAR(1)
transferencia_veiculo: OneCharString,
// VARCHAR(1)
usar_a4: OneCharString,
// VARCHAR(1)
averbacao: OneCharString,
});
// Relacionamentos e permissões
tipo_item: OneCharString,
requer_autorizacao: OneCharString,
requer_biometria: OneCharString,
tipo_pessoa: OneCharString,
tb_reconhecimentotipo_id: OptionalNumber,
tipo_permissao_cpf: OneCharString,
requer_abonador: OneCharString,
requer_representante: OneCharString,
requer_cpf: OneCharString,
alterar_valor: OneCharString,
pagina_acrescida: OneCharString,
// Campos auxiliares usados apenas no formulário (não persistidos)
valor_emolumento: z.number().optional(),
valor_taxa_judiciaria: z.number().optional(),
fundesp_valor: z.number().optional(),
valor_total: z.number().optional(),
etiquetas_carimbos: z.any().optional(),
emolumento: z.any().optional(),
emolumento_auxiliar: z.any().optional(),
});
/**
* Tipo inferido do schema usado diretamente no useForm
*/
export type TServicoTipoFormValues = z.infer<typeof TServicoTipoSchema>;

View file

@ -0,0 +1,20 @@
// Importa o utilitário responsável por lidar com erros de forma padronizada no cliente
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
// Importa a função que realiza a requisição de listagem dos tipos de marcação
import { CCaixaServicoIndexData } from '../../_data/CCaixaServico/CCaixaServicoIndexData';
import { CCaixaServicoReadInterface } from '../../_interfaces/CCaixaServicoReadInterface';
// Função assíncrona responsável por executar o serviço de listagem de tipos de marcação
async function executeCCaixaServicoIndexService(data: CCaixaServicoReadInterface) {
// Chama a função que realiza a requisição à API e aguarda a resposta
const response = await CCaixaServicoIndexData(data);
// Retorna a resposta obtida da requisição
return response;
}
// Exporta o serviço encapsulado pelo handler de erro, garantindo tratamento uniforme em caso de falhas
export const CCaixaServicoIndexService = withClientErrorHandler(
executeCCaixaServicoIndexService,
);

View file

@ -0,0 +1,20 @@
// Importa o utilitário responsável por lidar com erros de forma padronizada no cliente
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
// Importa a função que realiza a requisição de listagem dos tipos de marcação
import { GEmolumentoIndexData } from '../../_data/GEmolumento/GEmolumentoIndexData';
import { GEmolumentoReadInterface } from '../../_interfaces/GEmolumentoReadInterface';
// Função assíncrona responsável por executar o serviço de listagem de tipos de marcação
async function executeGEmolumentoIndexService(data: GEmolumentoReadInterface) {
// Chama a função que realiza a requisição à API e aguarda a resposta
const response = await GEmolumentoIndexData(data);
// Retorna a resposta obtida da requisição
return response;
}
// Exporta o serviço encapsulado pelo handler de erro, garantindo tratamento uniforme em caso de falhas
export const GEmolumentoIndexService = withClientErrorHandler(
executeGEmolumentoIndexService,
);

View file

@ -0,0 +1,20 @@
// Importa o utilitário responsável por lidar com erros de forma padronizada no cliente
import { withClientErrorHandler } from '@/shared/actions/withClientErrorHandler/withClientErrorHandler';
// Importa a função que realiza a requisição de listagem dos tipos de marcação
import { GEmolumentoItemValorData } from '../../_data/GEmolumentoItem/GEmolumentoItemValorData';
import { GEmolumentoItemReadInterface } from '../../_interfaces/GEmolumentoItemReadInterface';
// Função assíncrona responsável por executar o serviço de listagem de tipos de marcação
async function executeGEmolumentoItemValorService(data: GEmolumentoItemReadInterface) {
// Chama a função que realiza a requisição à API e aguarda a resposta
const response = await GEmolumentoItemValorData(data);
// Retorna a resposta obtida da requisição
return response;
}
// Exporta o serviço encapsulado pelo handler de erro, garantindo tratamento uniforme em caso de falhas
export const GEmolumentoItemValorService = withClientErrorHandler(
executeGEmolumentoItemValorService,
);

View file

@ -1,92 +1,116 @@
'use client';
"use client"
import * as React from 'react';
import * as React from "react"
import { cn } from '@/lib/utils';
import { cn } from "@/lib/utils"
function Table({ className, ...props }: React.ComponentProps<'table'>) {
function Table({ className, ...props }: React.ComponentProps<"table">) {
return (
<div data-slot="table-container" className="relative w-full overflow-x-auto">
<div
data-slot="table-container"
className="relative w-full overflow-x-auto"
>
<table
data-slot="table"
className={cn('w-full caption-bottom text-sm', className)}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
);
)
}
function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {
return <thead data-slot="table-header" className={cn('[&_tr]:border-b', className)} {...props} />;
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
return (
<thead
data-slot="table-header"
className={cn("[&_tr]:border-b", className)}
{...props}
/>
)
}
function TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
return (
<tbody
data-slot="table-body"
className={cn('[&_tr:last-child]:border-0', className)}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
);
)
}
function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
return (
<tfoot
data-slot="table-footer"
className={cn('bg-muted/50 border-t font-medium [&>tr]:last:border-b-0', className)}
className={cn(
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
);
)
}
function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
return (
<tr
data-slot="table-row"
className={cn(
'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',
className,
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
className
)}
{...props}
/>
);
)
}
function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
return (
<th
data-slot="table-head"
className={cn(
'text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className,
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
);
)
}
function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
return (
<td
data-slot="table-cell"
className={cn(
'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className,
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
);
)
}
function TableCaption({ className, ...props }: React.ComponentProps<'caption'>) {
function TableCaption({
className,
...props
}: React.ComponentProps<"caption">) {
return (
<caption
data-slot="table-caption"
className={cn('text-muted-foreground mt-4 text-sm', className)}
className={cn("text-muted-foreground mt-4 text-sm", className)}
{...props}
/>
);
)
}
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}

View file

@ -0,0 +1,102 @@
'use client';
import React from "react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import { FormControl } from "@/components/ui/form";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { CheckIcon, ChevronsUpDownIcon } from "lucide-react";
import GetCapitalize from "@/shared/actions/text/GetCapitalize";
import { useCCaixaServicoReadHook } from "@/app/(protected)/(cadastros)/cadastros/_hooks/c_caixa_servico/useCCaixaServicoReadHook";
import { CCaixaServicoReadInterface } from "@/app/(protected)/(cadastros)/cadastros/_interfaces/CCaixaServicoReadInterface";
export default function CCaixaServicoSelect({ sistema_id, field }: any) {
const cCaixaServicoReadParams: CCaixaServicoReadInterface = { sistema_id };
const [open, setOpen] = React.useState(false);
const [isLoading, setIsLoading] = React.useState(false);
const { cCaixaServico, fetchCCaixaServico } = useCCaixaServicoReadHook();
// Busca os dados uma única vez ao montar
React.useEffect(() => {
const loadData = async () => {
if (!cCaixaServico.length) {
setIsLoading(true);
await fetchCCaixaServico(cCaixaServicoReadParams);
setIsLoading(false);
}
};
loadData();
}, []);
const selected = cCaixaServico?.find(
(item) => String(item.caixa_servico_id) === String(field.value)
);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<FormControl className="w-full">
<Button
variant="outline"
role="combobox"
aria-expanded={open}
disabled={isLoading}
className="justify-between cursor-pointer"
>
{isLoading
? "Carregando..."
: field.value && typeof field.value === 'object' && field.value.value
? GetCapitalize(field.value.value) // Exibe a descrição do objeto
: field.value && typeof field.value !== 'object'
? field.value // Se for um ID (valor antigo), exibe ele
: "Selecione o serviço"}
<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 etiquetas/carimbos..." />
<CommandList>
<CommandEmpty>
{isLoading ? "Carregando..." : "Nenhum resultado encontrado."}
</CommandEmpty>
<CommandGroup>
{cCaixaServico?.map((item) => (
<CommandItem
className="cursor-pointer"
key={item.caixa_servico_id}
value={item.descricao?.toLowerCase() ?? ""}
onSelect={() => {
field.onChange({
key: Number(item.caixa_servico_id),
value: item.descricao,
});
setOpen(false);
}}
>
<CheckIcon
className={cn(
"mr-2 h-4 w-4",
String(field.value) === String(item.caixa_servico_id)
? "opacity-100"
: "opacity-0"
)}
/>
{GetCapitalize(item.descricao)}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View file

@ -0,0 +1,152 @@
'use client'; // Garante execução no cliente (Next.js App Router)
import React from "react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import { FormControl } from "@/components/ui/form";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { CheckIcon, ChevronsUpDownIcon } from "lucide-react";
import GetCapitalize from "@/shared/actions/text/GetCapitalize";
import { useGEmolumentoReadHook } from "@/app/(protected)/(cadastros)/cadastros/_hooks/g_emolumento/useGEmolumentoReadHook";
import { GEmolumentoReadInterface } from "@/app/(protected)/(cadastros)/cadastros/_interfaces/GEmolumentoReadInterface";
// Tipagem das props do componente
interface GEmolumentoSelectProps {
sistema_id: number; // ID do sistema usado para buscar os emolumentos
field: any; // Objeto de controle do react-hook-form
onSelectChange?: (emolumento: { key: number; value: string }) => void; // Função callback opcional para disparar eventos externos
className?: string; // Classe CSS opcional para customização
}
// Componente principal do select de emolumentos
export default function GEmolumentoSelect({ sistema_id, field, onSelectChange, className }: GEmolumentoSelectProps) {
// Define parâmetros de leitura para o hook que busca os emolumentos
const gEmolumentoReadParams: GEmolumentoReadInterface = { sistema_id };
// Estados locais do componente
const [open, setOpen] = React.useState(false); // Controla abertura do popover
const [isLoading, setIsLoading] = React.useState(false); // Exibe “Carregando...” enquanto busca dados
// Hook responsável por buscar emolumentos no backend
const { gEmolumento, fetchGEmolumento } = useGEmolumentoReadHook();
// Carrega os dados de emolumentos apenas uma vez ao montar o componente
React.useEffect(() => {
const loadData = async () => {
if (!gEmolumento.length) {
setIsLoading(true);
await fetchGEmolumento(gEmolumentoReadParams);
setIsLoading(false);
}
};
loadData();
}, []); // ← executa apenas uma vez
// Obtém o item selecionado com base no valor atual do campo
const selected = gEmolumento?.find(
(item) => String(item.emolumento_id) === String(field.value?.key ?? field.value)
);
// Estrutura visual do componente
return (
<Popover open={open} onOpenChange={setOpen}>
{/* === Botão principal (exibe valor selecionado) === */}
<PopoverTrigger asChild>
<FormControl className="w-full">
<Button
variant="outline"
role="combobox"
aria-expanded={open}
disabled={isLoading}
className={cn(
"justify-between cursor-pointer w-full whitespace-normal text-left break-words min-h-[2.5rem]",
className
)}
>
{/* Exibe o texto do botão */}
{isLoading
? "Carregando..."
: selected
? GetCapitalize(selected.descricao)
: "Selecione emolumento"}
{/* Ícone de seta */}
<ChevronsUpDownIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
{/* === Conteúdo do Popover (lista de opções) === */}
<PopoverContent
align="start"
side="bottom"
className="w-[var(--radix-popover-trigger-width)] max-w-full p-0"
>
<Command>
{/* Campo de busca dentro do popover */}
<CommandInput placeholder="Buscar emolumentos..." />
<CommandList>
{/* Estado vazio ou carregando */}
<CommandEmpty>
{isLoading ? "Carregando..." : "Nenhum resultado encontrado."}
</CommandEmpty>
{/* Grupo de opções */}
<CommandGroup>
{gEmolumento?.map((item) => (
<CommandItem
className="cursor-pointer w-full"
key={item.emolumento_id}
value={item.descricao?.toLowerCase() ?? ""}
// Quando o item é selecionado
onSelect={() => {
// Cria objeto com ID e descrição
const selectedValue = {
key: Number(item.emolumento_id),
value: item.descricao,
};
// Atualiza o valor no react-hook-form
field.onChange(selectedValue);
// Dispara callback externo, se existir (ex: fetchGEmolumentoItem)
if (onSelectChange) onSelectChange(selectedValue);
// Fecha o popover
setOpen(false);
}}
>
{/* Ícone de seleção (check) */}
<CheckIcon
className={cn(
"mr-2 h-4 w-4",
String(field.value?.key ?? field.value) === String(item.emolumento_id)
? "opacity-100"
: "opacity-0"
)}
/>
{/* Nome formatado do emolumento */}
{GetCapitalize(item.descricao)}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View file

@ -52,9 +52,11 @@ export default function GMarcacaoTipoSelect({ grupo, sistema_id, situacao, field
>
{isLoading
? "Carregando..."
: selected
? GetCapitalize(selected.descricao)
: "Selecione..."}
: field.value && typeof field.value === 'object' && field.value.value
? GetCapitalize(field.value.value) // Exibe a descrição do objeto
: field.value && typeof field.value !== 'object'
? field.value // Se for um ID (valor antigo), exibe ele
: "Selecione a etiqueta/carimbo"}
<ChevronsUpDownIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
@ -73,7 +75,10 @@ export default function GMarcacaoTipoSelect({ grupo, sistema_id, situacao, field
key={item.marcacao_tipo_id}
value={item.descricao?.toLowerCase() ?? ""}
onSelect={() => {
field.onChange(Number(item.marcacao_tipo_id));
field.onChange({
key: Number(item.marcacao_tipo_id),
value: item.descricao,
});
setOpen(false);
}}
>

View file

@ -0,0 +1,87 @@
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import { FormControl } from "@/components/ui/form";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { CategoriaServicoEnum } from "@/shared/enums/CategoriaServicoEnum";
import { CheckIcon, ChevronsUpDownIcon } from "lucide-react";
import React from "react";
type CategoriaServicoSelectProps = {
field: {
value?: string | null;
onChange: (value: string) => void;
};
};
export default function CategoriaServicoSelect({ field }: CategoriaServicoSelectProps) {
const [open, setOpen] = React.useState(false);
// Cria as opções a partir do enum
const options = Object.entries(CategoriaServicoEnum).map(([key, label]) => ({
value: String(key),
label,
}));
const selectedLabel =
field.value != null
? options.find((o) => o.value === String(field.value))?.label ?? "Selecione..."
: "Selecione...";
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<FormControl className="w-full">
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="justify-between cursor-pointer"
>
{selectedLabel}
<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 categoria..." />
<CommandList>
<CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
<CommandGroup>
{options.map((item) => (
<CommandItem
className="cursor-pointer"
key={item.value}
value={item.label.toLowerCase()}
onSelect={() => {
field.onChange(item.value); // envia número
setOpen(false);
}}
>
<CheckIcon
className={cn(
"mr-2 h-4 w-4",
String(field.value) === item.value
? "opacity-100"
: "opacity-0"
)}
/>
{item.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View file

@ -0,0 +1,7 @@
export const CategoriaServicoEnum = {
A: 'Autenticação',
C: 'Certidão',
G: 'Serviços Gerais',
R: 'Reconhecimento',
B: 'Geral',
} as const;