[DCM-5] feat(CURD):implementando listagem, cadastro, editar e excluir clientes
This commit is contained in:
parent
30b2052f5a
commit
e1fa346cce
18 changed files with 568 additions and 585 deletions
22
package-lock.json
generated
22
package-lock.json
generated
|
|
@ -2705,6 +2705,7 @@
|
|||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
|
|
@ -2715,6 +2716,7 @@
|
|||
"integrity": "sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
|
|
@ -3242,6 +3244,7 @@
|
|||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
|
|
@ -4341,6 +4344,7 @@
|
|||
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
|
|
@ -4425,6 +4429,7 @@
|
|||
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
|
|
@ -4526,6 +4531,7 @@
|
|||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.9",
|
||||
|
|
@ -6669,6 +6675,7 @@
|
|||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.4.tgz",
|
||||
"integrity": "sha512-xH4Yjhb82sFYQfY3vbkJfgSDgXvBB6a8xPs9i35k6oZJRoQRihZH+4s9Yo2qsWpzBmZ3lPXaJ2KPXLfkvW4LnA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@next/env": "15.5.4",
|
||||
"@swc/helpers": "0.5.15",
|
||||
|
|
@ -7078,6 +7085,7 @@
|
|||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
|
|
@ -7235,6 +7243,7 @@
|
|||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -7244,6 +7253,7 @@
|
|||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
|
|
@ -7256,6 +7266,7 @@
|
|||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.64.0.tgz",
|
||||
"integrity": "sha512-fnN+vvTiMLnRqKNTVhDysdrUay0kUUAymQnFIznmgDvapjveUWOOPqMNzPg+A+0yf9DuE2h6xzBjN1s+Qx8wcg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
|
|
@ -7271,7 +7282,8 @@
|
|||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/react-masked-text": {
|
||||
"version": "1.0.5",
|
||||
|
|
@ -7284,6 +7296,7 @@
|
|||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
|
|
@ -7402,7 +7415,8 @@
|
|||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "3.1.0",
|
||||
|
|
@ -8196,6 +8210,7 @@
|
|||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
|
@ -8207,7 +8222,8 @@
|
|||
"version": "8.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tinymce/-/tinymce-8.1.2.tgz",
|
||||
"integrity": "sha512-KITxHEEHRlxC5xOnxA123eAJ67NgsWxNphtItWt9TRu07DiTZrWIqJeIKRX9euE51/l3kJO4WQiqoBXKTJJGsA==",
|
||||
"license": "GPL-2.0-or-later"
|
||||
"license": "GPL-2.0-or-later",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
|
|
|
|||
|
|
@ -3,63 +3,66 @@
|
|||
import { useEffect, useState, useCallback } from 'react';
|
||||
|
||||
// Componentes de UI Genéricos
|
||||
import z from 'zod';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import Loading from '@/shared/components/loading/loading';
|
||||
import Header from '@/shared/components/structure/Header';
|
||||
import ConfirmDialog from '@/shared/components/confirmDialog/ConfirmDialog';
|
||||
import { useConfirmDialog } from '@/shared/components/confirmDialog/useConfirmDialog';
|
||||
|
||||
// Componentes de UI Específicos de cliente
|
||||
// Componentes Específicos
|
||||
import ClientTable from '@/packages/administrativo/components/Client/ClientTable';
|
||||
import ClientForm from '@/packages/administrativo/components/Client/ClientForm';
|
||||
import ClientForm, { ClientFormData } from '@/packages/administrativo/components/Client/ClientForm';
|
||||
|
||||
// Hooks Específicos de cliente
|
||||
// Hooks
|
||||
import { useClientIndexHook } from '@/packages/administrativo/hooks/Client/useClientIndexHook';
|
||||
import { useClientSaveHook } from '@/packages/administrativo/hooks/Client/useClientSaveHook';
|
||||
import { useClientDeleteHook } from '@/packages/administrativo/hooks/Client/useClientDeleteHook';
|
||||
import { useClientDeleteHook } from '@/packages/administrativo/hooks/Client/useClientDeleteHook'; // Hook solicitado
|
||||
|
||||
// Interface
|
||||
import { ClientInterface } from '@/packages/administrativo/interfaces/Client/ClientInterface';
|
||||
import { ClientSchema } from '@/packages/administrativo/schemas/Client/ClientSchema';
|
||||
import { SituacoesEnum } from '@/shared/enums/SituacoesEnum';
|
||||
|
||||
// Dados iniciais
|
||||
const initialClient: ClientInterface = {
|
||||
client_id: 0, // ID inicial padrão (auto incremento no banco)
|
||||
cns: '', // Código opcional de 10 caracteres
|
||||
name: '', // Nome obrigatório
|
||||
date_register: new Date().toISOString(), // Data atual no formato ISO
|
||||
state: '', // UF (2 caracteres)
|
||||
city: '', // Nome da cidade
|
||||
responsible: '', // Responsável pelo cliente
|
||||
consultant: '', // Consultor associado
|
||||
type_contract: '', // Tipo de contrato (ex: A, M, S...)
|
||||
}
|
||||
client_id: 0,
|
||||
cns: '',
|
||||
name: '',
|
||||
date_register: new Date().toISOString(),
|
||||
state: '',
|
||||
city: '',
|
||||
status: SituacoesEnum.ATIVO,
|
||||
responsible: '',
|
||||
consultant: '',
|
||||
type_contract: '',
|
||||
};
|
||||
|
||||
export default function ClientsPage() {
|
||||
|
||||
const [buttonIsLoading, setButtonIsLoading] = useState(false);
|
||||
|
||||
// 1. Hooks de dados para cliente
|
||||
const { clients, fetchClients } = useClientIndexHook();
|
||||
const { saveClient } = useClientSaveHook();
|
||||
const { removeClient } = useClientDeleteHook(); // Presumindo que o hook existe e expõe `removeclient`
|
||||
|
||||
// 2. Estados da página
|
||||
const { saveClient } = useClientSaveHook();
|
||||
|
||||
// 2. Uso do hook de exclusão solicitado
|
||||
const { removeClient } = useClientDeleteHook();
|
||||
|
||||
// Estados da página
|
||||
const [selectedClient, setSelectedClient] = useState<ClientInterface | null>(null);
|
||||
const [isFormOpen, setIsFormOpen] = useState(false);
|
||||
|
||||
// Estado para armazenar quem será excluído
|
||||
const [clientToDelete, setClientToDelete] = useState<ClientInterface | null>(null);
|
||||
|
||||
// Hook do modal de confirmação
|
||||
const {
|
||||
isOpen: isConfirmOpen,
|
||||
openDialog: openConfirmDialog,
|
||||
handleCancel,
|
||||
closeDialog: closeConfirmDialog,
|
||||
} = useConfirmDialog();
|
||||
|
||||
// 3. Funções de manipulação do formulário
|
||||
// --- Lógica de Formulário ---
|
||||
const handleOpenForm = useCallback((client: ClientInterface | null) => {
|
||||
setSelectedClient(client);
|
||||
setSelectedClient(client || initialClient);
|
||||
setIsFormOpen(true);
|
||||
}, []);
|
||||
|
||||
|
|
@ -68,59 +71,65 @@ export default function ClientsPage() {
|
|||
setIsFormOpen(false);
|
||||
}, []);
|
||||
|
||||
// 4. Função para salvar (criar ou editar)
|
||||
const handleSave = useCallback(
|
||||
async (formData: ClientInterface) => {
|
||||
|
||||
async (formData: ClientFormData) => {
|
||||
setButtonIsLoading(true);
|
||||
|
||||
await saveClient(formData);
|
||||
|
||||
// Casting necessário pois o form usa z.input e o hook espera a interface completa
|
||||
await saveClient(formData as unknown as ClientInterface);
|
||||
setButtonIsLoading(false);
|
||||
|
||||
setIsFormOpen(false);
|
||||
|
||||
fetchClients(); // Atualiza a lista de clientes
|
||||
fetchClients();
|
||||
},
|
||||
[saveClient, fetchClients, handleCloseForm],
|
||||
[saveClient, fetchClients]
|
||||
);
|
||||
|
||||
// 5. Funções para exclusão
|
||||
const handleConfirmDelete = useCallback(
|
||||
// --- Lógica de Exclusão (Nova) ---
|
||||
|
||||
// Passo 1: O usuário clica em "Excluir" na tabela
|
||||
const handleRequestDelete = useCallback(
|
||||
(client: ClientInterface) => {
|
||||
setClientToDelete(client);
|
||||
openConfirmDialog();
|
||||
setClientToDelete(client); // Guarda quem será excluído
|
||||
openConfirmDialog(); // Abre o modal
|
||||
},
|
||||
[openConfirmDialog],
|
||||
);
|
||||
|
||||
const handleDelete = useCallback(async () => {
|
||||
// Passo 2: O usuário confirma no Modal
|
||||
const handleConfirmDelete = useCallback(async () => {
|
||||
if (!clientToDelete) return;
|
||||
|
||||
await removeClient(clientToDelete); // Chama o hook de remoção
|
||||
await fetchClients(); // Atualiza a lista
|
||||
setClientToDelete(null); // Limpa o estado
|
||||
handleCancel(); // Fecha o modal de confirmação
|
||||
}, [clientToDelete, fetchClients, handleCancel]);
|
||||
try {
|
||||
// Chama o seu hook de exclusão
|
||||
await removeClient(clientToDelete);
|
||||
|
||||
// 6. Busca inicial dos dados
|
||||
// Atualiza a lista
|
||||
await fetchClients();
|
||||
|
||||
} catch (error) {
|
||||
console.error("Erro ao excluir", error);
|
||||
} finally {
|
||||
setClientToDelete(null);
|
||||
closeConfirmDialog();
|
||||
}
|
||||
}, [clientToDelete, removeClient, fetchClients, closeConfirmDialog]);
|
||||
|
||||
// Busca inicial
|
||||
useEffect(() => {
|
||||
fetchClients();
|
||||
}, []);
|
||||
|
||||
// 7. Renderização condicional de loading
|
||||
if (clients?.length == 0) {
|
||||
// Loading inicial
|
||||
if (!clients) {
|
||||
return <Loading type={2} />;
|
||||
}
|
||||
|
||||
// 8. Renderização da página
|
||||
return (
|
||||
<div>
|
||||
<Header
|
||||
title={'Clientes'}
|
||||
description={'Gerenciamento de Clientes do Sistema'}
|
||||
buttonText={'Novo Cliente'}
|
||||
buttonAction={(data) => handleOpenForm(data = initialClient)}
|
||||
buttonAction={() => handleOpenForm(null)}
|
||||
/>
|
||||
|
||||
<Card>
|
||||
|
|
@ -128,20 +137,21 @@ export default function ClientsPage() {
|
|||
<ClientTable
|
||||
data={clients}
|
||||
onEdit={handleOpenForm}
|
||||
onDelete={handleConfirmDelete}
|
||||
onDelete={handleRequestDelete} // Passamos a função que abre o modal
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Componente de Confirmação que você enviou */}
|
||||
<ConfirmDialog
|
||||
isOpen={isConfirmOpen}
|
||||
title="Confirmar exclusão"
|
||||
description="Esta ação não pode ser desfeita."
|
||||
message={`Deseja realmente excluir o Cliente "${clientToDelete?.name}"?`}
|
||||
description="Esta ação removerá o cliente permanentemente do sistema."
|
||||
message={`Tem certeza que deseja excluir o cliente "${clientToDelete?.name}"?`}
|
||||
confirmText="Sim, excluir"
|
||||
cancelText="Cancelar"
|
||||
onConfirm={handleDelete}
|
||||
onCancel={handleCancel}
|
||||
onConfirm={handleConfirmDelete} // Chama a função que executa o removeClient
|
||||
onCancel={closeConfirmDialog}
|
||||
/>
|
||||
|
||||
<ClientForm
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
'use client'; // Define que este componente será executado no lado do cliente (Client Component)
|
||||
'use client';
|
||||
|
||||
import z from 'zod';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
|
||||
// Importação dos componentes da UI (botões, inputs, dialogs, etc.)
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
|
|
@ -26,170 +25,270 @@ import {
|
|||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
|
||||
// Componente de botão com estado de carregamento
|
||||
import LoadingButton from '@/shared/components/loadingButton/LoadingButton';
|
||||
|
||||
// Importa a interface tipada separadamente
|
||||
import { UserFormInterface, FormValues } from '../../interfaces/User/UserFormInterface';
|
||||
// Enum
|
||||
export enum SituacoesEnum {
|
||||
ATIVO = 'A',
|
||||
INATIVO = 'I',
|
||||
}
|
||||
|
||||
// Importa o hook que gerencia o formulário
|
||||
import { useUserFormHook } from '../../hooks/User/useUserFormHook';
|
||||
// 1. Schema
|
||||
const clientSchema = z.object({
|
||||
client_id: z.number().optional(),
|
||||
name: z.string().min(3, 'O nome é obrigatório'),
|
||||
// Permitimos string vazia transformando em null ou undefined se necessário,
|
||||
// mas para formulários de texto, manter string vazia costuma ser mais fácil.
|
||||
cns: z.string().min(3, 'O CNS é obrigatório'),
|
||||
state: z.string().max(2).optional().nullable(),
|
||||
city: z.string().optional().nullable(),
|
||||
responsible: z.string().optional().nullable(),
|
||||
consultant: z.string().optional().nullable(),
|
||||
type_contract: z.string().optional().nullable(),
|
||||
|
||||
// Componente principal do formulário de usuário
|
||||
export default function UserForm({ isOpen, data, onClose, onSave, buttonIsLoading }: UserFormInterface) {
|
||||
// Status com valor padrão
|
||||
status: z.nativeEnum(SituacoesEnum).default(SituacoesEnum.ATIVO),
|
||||
});
|
||||
|
||||
// Usa o hook que centraliza toda a lógica do form
|
||||
const form = useUserFormHook(data);
|
||||
// AQUI: Usamos z.input para evitar conflito de tipos com campos que têm default()
|
||||
export type ClientFormData = z.input<typeof clientSchema>;
|
||||
|
||||
// Callback de erro da submissão do formulário
|
||||
function onError(error: any) {
|
||||
console.log("error", error);
|
||||
}
|
||||
interface ClientFormProps {
|
||||
isOpen: boolean;
|
||||
data: any | null;
|
||||
onClose: () => void;
|
||||
onSave: (data: ClientFormData) => void;
|
||||
buttonIsLoading?: boolean;
|
||||
}
|
||||
|
||||
export default function ClientForm({
|
||||
isOpen,
|
||||
data,
|
||||
onClose,
|
||||
onSave,
|
||||
buttonIsLoading,
|
||||
}: ClientFormProps) {
|
||||
|
||||
const form = useForm<ClientFormData>({
|
||||
resolver: zodResolver(clientSchema),
|
||||
defaultValues: {
|
||||
client_id: 0,
|
||||
name: '',
|
||||
cns: '',
|
||||
state: '',
|
||||
city: '',
|
||||
responsible: '',
|
||||
consultant: '',
|
||||
type_contract: '',
|
||||
status: SituacoesEnum.ATIVO,
|
||||
},
|
||||
});
|
||||
|
||||
// Reset do formulário ao abrir/fechar ou mudar dados
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
if (data) {
|
||||
form.reset({
|
||||
...data,
|
||||
// Garante que nulos venham como strings vazias ou mantenham o valor
|
||||
cns: data.cns ?? '',
|
||||
state: data.state ?? '',
|
||||
city: data.city ?? '',
|
||||
responsible: data.responsible ?? '',
|
||||
consultant: data.consultant ?? '',
|
||||
type_contract: data.type_contract ?? '',
|
||||
status: data.status || SituacoesEnum.ATIVO,
|
||||
});
|
||||
} else {
|
||||
form.reset({
|
||||
client_id: 0,
|
||||
name: '',
|
||||
cns: '',
|
||||
state: '',
|
||||
city: '',
|
||||
responsible: '',
|
||||
consultant: '',
|
||||
type_contract: '',
|
||||
status: SituacoesEnum.ATIVO,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [isOpen, data, form]);
|
||||
|
||||
const handleFormSubmit = (values: ClientFormData) => {
|
||||
// Ao salvar, o Zod já garantiu que o 'status' existe (por causa do default),
|
||||
// mesmo que o tipo ClientFormData diga que é opcional.
|
||||
onSave(values);
|
||||
};
|
||||
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
if (!open) {
|
||||
form.reset();
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
// Componente de diálogo (modal)
|
||||
<Dialog
|
||||
open={isOpen} // Define se o diálogo está aberto
|
||||
onOpenChange={(open) => {
|
||||
if (!open) onClose(null, false); // Fecha o diálogo quando necessário
|
||||
}}
|
||||
>
|
||||
{/* Conteúdo do modal */}
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{data?.user_id ? 'Editar Usuário' : 'Novo Usuário'}</DialogTitle>
|
||||
<DialogDescription>Gerencie os dados do usuário aqui.</DialogDescription>
|
||||
<DialogTitle>
|
||||
{data?.client_id ? 'Editar Cliente' : 'Novo Cliente'}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Preencha os campos abaixo.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{/* Wrapper do formulário com react-hook-form */}
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSave, onError)} className="space-y-4">
|
||||
<form onSubmit={form.handleSubmit(handleFormSubmit)} className="space-y-4">
|
||||
|
||||
{/* Campo: Nome */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Nome</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Digite o nome completo" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{/* Linha 1: Nome e Status */}
|
||||
<div className="flex gap-4 items-start">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormLabel>Nome / Razão Social</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Digite o nome" {...field} value={field.value ?? ''} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Campo: Email */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="email" {...field} placeholder="exemplo@email.com" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{/* Checkbox de Status */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="status"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center space-x-3 space-y-0 mt-8 border p-2 rounded-md h-10">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value === SituacoesEnum.ATIVO}
|
||||
onCheckedChange={(checked) => {
|
||||
field.onChange(checked ? SituacoesEnum.ATIVO : SituacoesEnum.INATIVO);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="leading-none">
|
||||
<Label>Ativo?</Label>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Campo: Senha */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Senha</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
{...field}
|
||||
placeholder={data ? 'Deixe em branco para não alterar' : 'Digite a senha'}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{/* Linha 2: CNS e Tipo Contrato */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cns"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>CNS</FormLabel>
|
||||
<FormControl>
|
||||
{/* O '?? ""' previne erro de uncontrolled component se o valor for null */}
|
||||
<Input placeholder="CNS" {...field} value={field.value ?? ''} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="type_contract"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Tipo de Contrato</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Ex: Mensal" {...field} value={field.value ?? ''} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Campo: Equipe */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="team"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Equipe</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Digite o nome da equipe" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{/* Linha 3: Cidade e UF */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="city"
|
||||
render={({ field }) => (
|
||||
<FormItem className="col-span-2">
|
||||
<FormLabel>Cidade</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Cidade" {...field} value={field.value ?? ''} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Campo: Cargo */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cargo"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Cargo</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} value={field.value ?? ''} placeholder="Digite o cargo do usuário" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="state"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>UF</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="UF" maxLength={2} {...field} value={field.value ?? ''} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Campo: Status (Ativo/Inativo) */}
|
||||
<Controller
|
||||
name="status"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
checked={field.value === 'A'}
|
||||
onCheckedChange={(checked) => field.onChange(checked ? 'A' : 'I')}
|
||||
/>
|
||||
<Label>Ativo</Label>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
{/* Linha 4: Responsável e Consultor */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="responsible"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Responsável</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Responsável" {...field} value={field.value ?? ''} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="consultant"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Consultor</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Consultor" {...field} value={field.value ?? ''} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Rodapé do formulário (botões) */}
|
||||
<DialogFooter className="mt-4">
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
type="button"
|
||||
onClick={() => onClose(null, false)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
Cancelar
|
||||
</Button>
|
||||
</DialogClose>
|
||||
|
||||
{/* Botão de salvar com loading */}
|
||||
<LoadingButton
|
||||
text={data?.user_id ? 'Salvar' : 'Cadastrar'}
|
||||
textLoading="Aguarde..."
|
||||
type="submit"
|
||||
loading={buttonIsLoading}
|
||||
<Button type="button" variant="outline" onClick={onClose}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<LoadingButton
|
||||
type="submit"
|
||||
text="Salvar"
|
||||
textLoading="Salvando..."
|
||||
loading={buttonIsLoading}
|
||||
/>
|
||||
</DialogFooter>
|
||||
|
||||
{/* Campos ocultos para dados técnicos */}
|
||||
<input type="hidden" {...form.register('user_id')} />
|
||||
<input type="hidden" {...form.register('date_register')} />
|
||||
<input type="hidden" {...form.register('date_update')} />
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
'use client'; // Indica que este componente é executado no lado do cliente (Next.js)
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
// Importei o ícone Trash2 (lixeira)
|
||||
import { ChartPie, EllipsisIcon, PencilIcon, Trash2 } from 'lucide-react';
|
||||
|
||||
// Componentes de UI
|
||||
// UI Components
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
|
@ -22,18 +24,14 @@ import {
|
|||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
|
||||
// Ícones da biblioteca Lucide
|
||||
import { ChartPie, EllipsisIcon, PencilIcon } from 'lucide-react';
|
||||
import { Spinner } from "@/components/ui/spinner"
|
||||
|
||||
// Interfaces e componentes auxiliares
|
||||
import { ClientTableInterface } from '../../interfaces/Client/ClienteTableInterface';
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { StatusBadge } from '@/components/StatusBadge';
|
||||
import { CheckLiberationHelper } from '@/shared/utils/CheckLiberationHelper';
|
||||
import FinanceStatusDialog from '@/shared/components/finance/FinanceStatusDialog';
|
||||
|
||||
// Tipagem do retorno financeiro do helper
|
||||
// Utils & Interfaces
|
||||
import { ClientTableInterface } from '../../interfaces/Client/ClienteTableInterface';
|
||||
import { CheckLiberationHelper } from '@/shared/utils/CheckLiberationHelper';
|
||||
|
||||
interface FinancialStatus {
|
||||
status: string;
|
||||
message: string;
|
||||
|
|
@ -48,26 +46,26 @@ interface FinancialStatus {
|
|||
}
|
||||
|
||||
export default function ClientTable({ data, pagination, onEdit, onDelete }: ClientTableInterface) {
|
||||
const router = useRouter(); // Hook do Next.js para navegação
|
||||
|
||||
// Estado que armazena o status financeiro de cada cliente, indexado pelo ID
|
||||
const router = useRouter();
|
||||
const [financialStatus, setFinancialStatus] = useState<Record<number, FinancialStatus>>({});
|
||||
|
||||
// Efeito que busca o status financeiro de todos os clientes ao carregar ou atualizar a tabela
|
||||
useEffect(() => {
|
||||
// Se não houver dados válidos, encerra a execução
|
||||
if (!Array.isArray(data)) return;
|
||||
if (!data || !Array.isArray(data)) return;
|
||||
|
||||
// Função assíncrona responsável por consultar os status financeiros
|
||||
const fetchStatus = async () => {
|
||||
const newStatuses: Record<number, FinancialStatus> = {}; // Armazena os resultados temporariamente
|
||||
|
||||
// Faz as requisições em paralelo para cada cliente
|
||||
const newStatuses: Record<number, FinancialStatus> = {};
|
||||
await Promise.all(
|
||||
data.map(async (client) => {
|
||||
const res = await CheckLiberationHelper(client.cns); // Chama o helper externo
|
||||
|
||||
// Normaliza o retorno e armazena no objeto temporário
|
||||
if (!client.cns) {
|
||||
newStatuses[client.client_id] = {
|
||||
status: 'indefinido',
|
||||
message: 'CNS não cadastrado',
|
||||
expired_count: 0,
|
||||
details: []
|
||||
};
|
||||
return;
|
||||
}
|
||||
const res = await CheckLiberationHelper(client.cns);
|
||||
newStatuses[client.client_id] = {
|
||||
status: res.status || 'indefinido',
|
||||
message: res.message || '',
|
||||
|
|
@ -77,136 +75,126 @@ export default function ClientTable({ data, pagination, onEdit, onDelete }: Clie
|
|||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Atualiza o estado global com todos os resultados de uma vez
|
||||
setFinancialStatus(newStatuses);
|
||||
};
|
||||
|
||||
// Executa a função
|
||||
fetchStatus();
|
||||
}, [data]); // Dependência: roda novamente se o array de clientes mudar
|
||||
}, [data]);
|
||||
|
||||
const getFinanceColor = (status: string) => {
|
||||
return status === 'em atraso' ? 'text-red-600' : 'text-green-600';
|
||||
};
|
||||
|
||||
return (
|
||||
<Table>
|
||||
{/* Cabeçalho da tabela */}
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>#</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Financeiro</TableHead>
|
||||
<TableHead className="w-[80px]">#</TableHead>
|
||||
<TableHead className="w-[100px]">Status</TableHead>
|
||||
<TableHead className="w-[200px]">Financeiro</TableHead>
|
||||
<TableHead>CNS</TableHead>
|
||||
<TableHead>Nome</TableHead>
|
||||
<TableHead className="text-right">Ações</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
{/* Corpo da tabela */}
|
||||
<TableBody>
|
||||
{Array.isArray(data) && data.length > 0 ? (
|
||||
data.map((client) => (
|
||||
<TableRow key={client.client_id}>
|
||||
{/* Coluna: ID do cliente */}
|
||||
<TableCell className="font-medium">{client.client_id}</TableCell>
|
||||
data.map((client) => {
|
||||
const finStatus = financialStatus[client.client_id];
|
||||
|
||||
return (
|
||||
<TableRow key={client.client_id}>
|
||||
<TableCell className="font-medium">{client.client_id}</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<StatusBadge status={client.status} />
|
||||
</TableCell>
|
||||
|
||||
{/* Coluna: Status cadastral (ativo, inativo, etc) */}
|
||||
<TableCell>
|
||||
<StatusBadge status={client.status as any} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{finStatus ? (
|
||||
<FinanceStatusDialog
|
||||
triggerElement={
|
||||
<button
|
||||
type="button"
|
||||
className={`font-semibold cursor-pointer underline underline-offset-2 bg-transparent border-0 p-0 ${getFinanceColor(finStatus.status)}`}
|
||||
title={finStatus.message}
|
||||
>
|
||||
{finStatus.status.toUpperCase()}
|
||||
{finStatus.expired_count > 0 && ` (${finStatus.expired_count})`}
|
||||
</button>
|
||||
}
|
||||
status={finStatus.status}
|
||||
message={finStatus.message}
|
||||
details={finStatus.details}
|
||||
expired_count={finStatus.expired_count}
|
||||
/>
|
||||
) : (
|
||||
<span className="text-gray-400 opacity-50"><Spinner/></span>
|
||||
)}
|
||||
</TableCell>
|
||||
|
||||
{/* Coluna: Status financeiro retornado da API */}
|
||||
<TableCell>
|
||||
{financialStatus[client.client_id] ? (
|
||||
<FinanceStatusDialog
|
||||
triggerElement={
|
||||
// Usar <button> garante que o elemento seja interativo e receba handlers clonados
|
||||
<button
|
||||
type="button"
|
||||
className={
|
||||
financialStatus[client.client_id].status === 'em atraso'
|
||||
? 'text-red-600 font-semibold cursor-pointer underline underline-offset-2 bg-transparent border-0 p-0'
|
||||
: 'text-green-600 font-semibold cursor-pointer underline underline-offset-2 bg-transparent border-0 p-0'
|
||||
}
|
||||
title={financialStatus[client.client_id].message}
|
||||
>
|
||||
{financialStatus[client.client_id].status.toUpperCase()}
|
||||
{financialStatus[client.client_id].expired_count > 0 &&
|
||||
` (${financialStatus[client.client_id].expired_count} pendência${
|
||||
financialStatus[client.client_id].expired_count > 1 ? 's' : ''
|
||||
})`}
|
||||
</button>
|
||||
}
|
||||
status={financialStatus[client.client_id].status}
|
||||
message={financialStatus[client.client_id].message}
|
||||
details={financialStatus[client.client_id].details}
|
||||
expired_count={financialStatus[client.client_id].expired_count}
|
||||
/>
|
||||
) : (
|
||||
<span className="text-gray-400 italic"><Spinner /></span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>{client.cns}</TableCell>
|
||||
<TableCell className="truncate max-w-[250px]" title={client.name}>
|
||||
{client.name}
|
||||
</TableCell>
|
||||
|
||||
{/* Coluna: CNS do cliente */}
|
||||
<TableCell>{client.cns}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 p-0">
|
||||
<EllipsisIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="left" align="start">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem className="cursor-pointer" onSelect={() => onEdit(client)}>
|
||||
<PencilIcon className="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onSelect={() => router.push(`/administrativo/clientes/${client.client_id}`)}
|
||||
>
|
||||
<ChartPie className="mr-2 h-4 w-4" />
|
||||
Dashboard
|
||||
</DropdownMenuItem>
|
||||
|
||||
{/* Coluna: Nome do cliente */}
|
||||
<TableCell>{client.name}</TableCell>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{/* OPÇÃO DE EXCLUIR */}
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer text-red-600 focus:text-red-600 focus:bg-red-50"
|
||||
onSelect={() => onDelete(client)}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Excluir
|
||||
</DropdownMenuItem>
|
||||
|
||||
{/* Coluna: Ações (menu suspenso) */}
|
||||
<TableCell className="text-right">
|
||||
<DropdownMenu>
|
||||
{/* Botão principal do menu */}
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon" className="cursor-pointer">
|
||||
<EllipsisIcon />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
{/* Conteúdo do menu suspenso */}
|
||||
<DropdownMenuContent side="left" align="start">
|
||||
<DropdownMenuGroup>
|
||||
{/* Ação: Editar cliente */}
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onSelect={() => onEdit(client)}
|
||||
>
|
||||
<PencilIcon className="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{/* Ação: Ir para dashboard do cliente */}
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onSelect={() => router.push(`/administrativo/clientes/${client.client_id}`)}
|
||||
>
|
||||
<ChartPie className="mr-2 h-4 w-4" />
|
||||
Dashboard
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
// Caso não existam registros
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="text-center text-muted-foreground">
|
||||
<TableCell colSpan={6} className="h-24 text-center text-muted-foreground">
|
||||
Nenhum cliente encontrado.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
|
||||
{/* Rodapé da tabela com totalização */}
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TableCell colSpan={5}>
|
||||
Total de clientes: {pagination?.total_records ?? data?.length}
|
||||
<TableCell colSpan={6}>
|
||||
Total de clientes: {pagination?.total_records ?? data?.length ?? 0}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +1,20 @@
|
|||
'use server'
|
||||
// Define que este módulo será executado no lado do servidor (Server Action do Next.js)
|
||||
|
||||
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
|
||||
// Importa o enumerador que contém os métodos HTTP disponíveis (GET, POST, PUT, DELETE)
|
||||
|
||||
import API from '@/shared/services/api/Api';
|
||||
// Importa a classe responsável por centralizar e padronizar as chamadas HTTP na aplicação
|
||||
|
||||
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
|
||||
// Importa o decorador que adiciona tratamento global de erros às funções assíncronas
|
||||
|
||||
// Função principal responsável por enviar a requisição de exclusão de um cliente
|
||||
async function executeClientDeleteData(client_id: number) {
|
||||
|
||||
// Cria uma nova instância da classe API, que gerencia headers, baseURL e envio das requisições
|
||||
const api = new API();
|
||||
|
||||
// Envia a requisição DELETE para o endpoint correspondente ao cliente informado
|
||||
const response = await api.send({
|
||||
'method': Methods.DELETE, // Define o método HTTP como DELETE
|
||||
'endpoint': `administrativo/client/${client_id}` // Define o endpoint incluindo o ID do cliente
|
||||
'method': Methods.DELETE,
|
||||
'endpoint': `administrativo/client/${client_id}`
|
||||
});
|
||||
|
||||
// Retorna a resposta da API, contendo status, mensagem e possíveis dados adicionais
|
||||
return response;
|
||||
|
||||
}
|
||||
|
||||
// Exporta a função encapsulada com o tratador global de erros
|
||||
export const ClientDeleteData = withClientErrorHandler(executeClientDeleteData);
|
||||
// `withClientErrorHandler` garante que qualquer exceção durante a execução da função seja capturada
|
||||
// Isso permite um tratamento padronizado de erros e evita quebra no fluxo de execução do app
|
||||
export const ClientDeleteData = withClientErrorHandler(executeClientDeleteData)
|
||||
|
|
@ -1,39 +1,19 @@
|
|||
'use server';
|
||||
// Indica que este módulo será executado no lado do servidor (Server Action do Next.js)
|
||||
|
||||
'use server'
|
||||
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
|
||||
// Importa o enum com os tipos de métodos HTTP disponíveis (GET, POST, PUT, DELETE...)
|
||||
|
||||
import API from '@/shared/services/api/Api';
|
||||
// Importa a classe responsável por centralizar chamadas à API (wrapper de fetch ou axios)
|
||||
|
||||
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
|
||||
// Importa um decorador/função HOC que trata erros de forma padronizada nas requisições
|
||||
|
||||
/**
|
||||
* Função principal responsável por buscar a lista de clientes na API.
|
||||
* Executa uma requisição HTTP GET para o endpoint administrativo/client.
|
||||
*/
|
||||
async function executeClientIndexData() {
|
||||
|
||||
// Instancia o serviço de API para uso nesta função
|
||||
|
||||
const api = new API();
|
||||
|
||||
// Executa uma requisição GET para o endpoint administrativo/client/
|
||||
// - Usa o método 'send' da classe API
|
||||
// - Passa o método HTTP e o endpoint como parâmetros
|
||||
const response = await api.send({
|
||||
method: Methods.GET, // Método HTTP GET
|
||||
endpoint: `administrativo/client/`, // Rota da API que retorna a lista de clientes
|
||||
'method': Methods.GET,
|
||||
'endpoint': `administrativo/client/`
|
||||
});
|
||||
|
||||
// Retorna a resposta obtida da API
|
||||
return response;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporta a função encapsulada com o handler de erro.
|
||||
* Caso ocorra falha na requisição, o withClientErrorHandler
|
||||
* intercepta o erro e o trata de forma uniforme (ex: logging, toast, etc.)
|
||||
*/
|
||||
export const ClientIndexData = withClientErrorHandler(executeClientIndexData);
|
||||
export const ClientIndexData = withClientErrorHandler(executeClientIndexData)
|
||||
|
|
@ -1,42 +1,26 @@
|
|||
'use server'
|
||||
// Define que este módulo será executado no lado do servidor (Server Action do Next.js)
|
||||
|
||||
import { Methods } from '@/shared/services/api/enums/ApiMethodEnum';
|
||||
// Importa o enumerador que define os métodos HTTP disponíveis (GET, POST, PUT, DELETE)
|
||||
|
||||
import API from '@/shared/services/api/Api';
|
||||
// Importa a classe responsável por realizar as chamadas HTTP centralizadas da aplicação
|
||||
|
||||
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
|
||||
// Importa o decorador que adiciona tratamento global de erros à função principal
|
||||
|
||||
// Ajuste o caminho da importação conforme a estrutura real das suas pastas
|
||||
import { ClientInterface } from '../../interfaces/Client/ClientInterface';
|
||||
// Importa a tipagem do objeto de cliente, garantindo consistência nos dados enviados
|
||||
|
||||
// Função principal responsável por salvar (criar ou atualizar) os dados de um cliente
|
||||
async function executeClientSaveData(form: ClientInterface) {
|
||||
// Verifica se existe um `client_id`; se sim, trata-se de uma atualização (PUT), caso contrário, é um novo cadastro (POST)
|
||||
const isUpdate = Boolean(form.client_id);
|
||||
// Verifica se existe ID e se é maior que 0 para considerar edição
|
||||
const isUpdate = Boolean(form.client_id && form.client_id > 0);
|
||||
|
||||
// Cria uma nova instância da classe API para enviar a requisição
|
||||
const api = new API();
|
||||
|
||||
// Envia a requisição para o endpoint responsável por salvar os dados do cliente
|
||||
const response = await api.send({
|
||||
// Define o método HTTP dinamicamente com base no tipo de operação (POST ou PUT)
|
||||
'method': isUpdate ? Methods.PUT : Methods.POST,
|
||||
|
||||
// Define o endpoint, incluindo o `client_id` se for atualização
|
||||
// Se for update, adiciona o ID na URL. Se for create, remove (fica vazio após a barra)
|
||||
'endpoint': `administrativo/client/${form.client_id || ''}`,
|
||||
|
||||
// Corpo da requisição contendo os dados do formulário
|
||||
'body': form
|
||||
});
|
||||
|
||||
// Retorna a resposta da API (pode conter status, dados ou mensagens)
|
||||
return response;
|
||||
|
||||
}
|
||||
|
||||
// Exporta a função encapsulada com o tratador global de erros
|
||||
export const ClientSaveData = withClientErrorHandler(executeClientSaveData);
|
||||
// `withClientErrorHandler` assegura que qualquer erro durante a execução será capturado e tratado de forma padronizada
|
||||
export const ClientSaveData = withClientErrorHandler(executeClientSaveData)
|
||||
|
|
@ -1,37 +1,29 @@
|
|||
'use client';
|
||||
// Define que este módulo será executado no lado do cliente (Client Component do Next.js)
|
||||
|
||||
import { useState } from 'react';
|
||||
// Importa o hook `useState` do React (embora não esteja sendo usado aqui, pode ser útil em versões futuras)
|
||||
|
||||
import { useResponse } from '@/shared/components/response/ResponseContext';
|
||||
// Importa o hook de contexto responsável por exibir feedbacks globais (como toasts, alerts ou modais)
|
||||
|
||||
import { ClientDeleteService } from '../../services/Client/ClientDeleteService';
|
||||
// Importa o serviço responsável por realizar a exclusão do cliente via API
|
||||
|
||||
import { ClientInterface } from '../../interfaces/Client/ClientInterface';
|
||||
// Importa a tipagem do objeto `ClientInterface` para garantir segurança de tipo e padronização dos dados
|
||||
|
||||
// Hook personalizado responsável por encapsular a lógica de exclusão de clientes
|
||||
export const useClientDeleteHook = () => {
|
||||
// Obtém a função `setResponse` do contexto global, usada para exibir feedbacks ao usuário
|
||||
const { setResponse } = useResponse();
|
||||
|
||||
// Função assíncrona que executa a exclusão de um cliente específico
|
||||
const removeClient = async (client: ClientInterface) => {
|
||||
try {
|
||||
// Chama o serviço de exclusão, enviando o ID do cliente como parâmetro
|
||||
const response = await ClientDeleteService(client.client_id);
|
||||
|
||||
// Define a resposta no contexto global, permitindo exibir mensagem de sucesso/erro na interface
|
||||
// Proteção extra: não tenta deletar se não tiver ID
|
||||
if (!client.client_id) {
|
||||
console.error("Tentativa de excluir cliente sem ID");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await ClientDeleteService(client.client_id);
|
||||
setResponse(response);
|
||||
} catch (error) {
|
||||
// Captura e exibe o erro no console (embora o handler global já trate exceções)
|
||||
console.error('Erro ao remover usuário:', error);
|
||||
console.error('Erro ao remover cliente:', error);
|
||||
// O withClientErrorHandler no Service já deve capturar isso,
|
||||
// mas o try/catch aqui garante que a UI não quebre.
|
||||
}
|
||||
};
|
||||
|
||||
// Retorna a função principal de exclusão, permitindo que o componente que usa este hook a invoque
|
||||
return { removeClient };
|
||||
};
|
||||
};
|
||||
|
|
@ -1,43 +1,29 @@
|
|||
'use client';
|
||||
// Define que este arquivo será executado no lado do cliente (Next.js Client Component)
|
||||
// useClientIndexHook.ts
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { ClientInterface } from '../../interfaces/Client/ClientInterface';
|
||||
import { ClientIndexService } from '../../services/Client/ClientIndexService';
|
||||
import { useResponse } from '@/shared/components/response/ResponseContext';
|
||||
|
||||
// Hook personalizado responsável por gerenciar a listagem de clientes
|
||||
export const useClientIndexHook = () => {
|
||||
// Obtém a função para definir mensagens globais de resposta (toast, modal, etc.)
|
||||
const { setResponse } = useResponse();
|
||||
|
||||
// Estado local que armazena a lista de clientes retornados pela API
|
||||
const [clients, setClients] = useState<ClientInterface[]>([]);
|
||||
|
||||
// Função responsável por buscar os clientes da API
|
||||
const fetchClients = async () => {
|
||||
try {
|
||||
// Chama o serviço que faz a requisição HTTP
|
||||
const response = await ClientIndexService();
|
||||
const fetchClients = useCallback(async () => {
|
||||
const response = await ClientIndexService();
|
||||
|
||||
// Atualiza o estado local com os dados retornados
|
||||
setClients(response.data);
|
||||
|
||||
// Define a resposta global (útil para exibir mensagens de sucesso/erro)
|
||||
setResponse(response);
|
||||
} catch (error) {
|
||||
// Caso ocorra erro na requisição, registra no console
|
||||
console.error('Erro ao buscar clientes:', error);
|
||||
|
||||
// Atualiza o contexto de resposta com erro
|
||||
setResponse({
|
||||
status: 'error',
|
||||
message: 'Falha ao carregar a lista de clientes.',
|
||||
data: [],
|
||||
});
|
||||
if (response.data) {
|
||||
setClients(response.data);
|
||||
}
|
||||
|
||||
// CRÍTICO: Não chame setResponse se deu tudo certo (200).
|
||||
// Isso evita que o contexto atualize e force um re-render da tela toda.
|
||||
if (response.code !== 200) {
|
||||
setResponse(response);
|
||||
}
|
||||
};
|
||||
|
||||
// Retorna as variáveis e funções que o componente poderá utilizar
|
||||
}, [setResponse]);
|
||||
|
||||
return { clients, fetchClients };
|
||||
};
|
||||
};
|
||||
|
|
@ -1,43 +1,28 @@
|
|||
'use client';
|
||||
// Indica que este arquivo será executado no lado do cliente (Client Component do Next.js)
|
||||
|
||||
import { useState } from 'react';
|
||||
import { ClientInterface } from '../../interfaces/Client/ClientInterface';
|
||||
import { ClientSaveService } from '../../services/Client/ClientSaveService';
|
||||
import { useResponse } from '@/shared/components/response/ResponseContext'; // 🔧 corrigido nome de import (clientesponse → clientResponse)
|
||||
import { useResponse } from '@/shared/components/response/ResponseContext';
|
||||
|
||||
// Hook personalizado responsável por salvar (criar ou atualizar) clientes
|
||||
export const useClientSaveHook = () => {
|
||||
// Obtém a função global para definir mensagens de resposta (toast, modal, etc.)
|
||||
const { setResponse } = useResponse();
|
||||
|
||||
// Estado local para armazenar o cliente salvo/retornado pela API
|
||||
const [cliente, setCliente] = useState<ClientInterface>();
|
||||
// Estado para armazenar o último cliente salvo (útil se precisar redirecionar ou mostrar dados)
|
||||
const [savedClient, setSavedClient] = useState<ClientInterface | null>(null);
|
||||
|
||||
// Função responsável por enviar os dados do cliente para a API
|
||||
const saveClient = async (clientData: ClientInterface) => {
|
||||
try {
|
||||
// Faz a chamada ao serviço que salva os dados do cliente
|
||||
const response = await ClientSaveService(clientData);
|
||||
const saveClient = async (client: ClientInterface) => {
|
||||
const response = await ClientSaveService(client);
|
||||
|
||||
// Atualiza o estado com o cliente retornado (pode conter ID ou dados processados)
|
||||
setCliente(response.data);
|
||||
|
||||
// Define a resposta global (útil para feedback de sucesso)
|
||||
setResponse(response);
|
||||
} catch (error) {
|
||||
// Em caso de erro, exibe no console para depuração
|
||||
console.error('Erro ao salvar cliente:', error);
|
||||
|
||||
// Define resposta de erro global
|
||||
setResponse({
|
||||
status: 'error',
|
||||
message: 'Falha ao salvar o cliente.',
|
||||
data: null,
|
||||
});
|
||||
if (response.data) {
|
||||
setSavedClient(response.data);
|
||||
}
|
||||
|
||||
setResponse(response);
|
||||
|
||||
// Retornamos a resposta para que o componente (page/modal) saiba se deve fechar ou não
|
||||
return response;
|
||||
};
|
||||
|
||||
// Retorna o cliente salvo e a função responsável por salvar
|
||||
return { cliente, saveClient };
|
||||
};
|
||||
return { savedClient, saveClient };
|
||||
};
|
||||
|
|
@ -13,7 +13,9 @@ export const useUserIndexHook = () => {
|
|||
const fetchUsuarios = async () => {
|
||||
const response = await UserIndexService();
|
||||
|
||||
setUsuarios(response.data);
|
||||
if (response.data) {
|
||||
setUsuarios(response.data);
|
||||
}
|
||||
|
||||
// Define os dados do componente de resposta (toast, modal, etc)
|
||||
setResponse(response);
|
||||
|
|
|
|||
|
|
@ -1,16 +1,35 @@
|
|||
/**
|
||||
* Interface que representa a tabela `client` do banco de dados `monitoring`.
|
||||
* Cada campo reflete a tipagem e as restrições da DDL original.
|
||||
*/
|
||||
import { SituacoesEnum } from "@/shared/enums/SituacoesEnum";
|
||||
|
||||
export interface ClientInterface {
|
||||
client_id?: number; // ID único do cliente (chave primária, gerada automaticamente)
|
||||
cns?: string | null; // Código CNS (campo opcional)
|
||||
name: string; // Nome do cliente (campo obrigatório)
|
||||
date_register?: string | null; // Data/hora do registro (timestamp gerado automaticamente)
|
||||
state?: string | null; // Sigla do estado (ex: 'SP', 'RJ', etc.)
|
||||
city?: string | null; // Nome da cidade
|
||||
responsible?: string | null; // Responsável principal pelo cliente
|
||||
consultant?: string | null; // Nome do consultor associado
|
||||
type_contract?: string | null; // Tipo de contrato (ex: 'A' = anual, 'M' = mensal, etc.)
|
||||
status?: string | null; // Tipo de contrato (ex: 'A' = Ativo, 'I' = Inativo, etc.)
|
||||
client_id: number;
|
||||
cns: string;
|
||||
name: string;
|
||||
|
||||
// Campos que vieram como null no JSON, mas sabemos que são strings pelo contexto do negócio
|
||||
state: string | null;
|
||||
city: string | null;
|
||||
responsible: string | null;
|
||||
consultant: string | null;
|
||||
type_contract: string | null; // No Postman era string ("1"), então mantive string | null
|
||||
|
||||
status: SituacoesEnum; // 'A' mapeado para o Enum existente
|
||||
|
||||
date_register?: string; // Opcional pois geralmente não é enviado na criação (gerado pelo banco)
|
||||
}
|
||||
|
||||
// Interface auxiliar para a Paginação (caso você vá tipar a resposta completa do serviço)
|
||||
export interface ClientPaginationInterface {
|
||||
total_records: number;
|
||||
total_pages: number;
|
||||
current_page: number;
|
||||
next_page: number | null;
|
||||
first: number;
|
||||
skip: number;
|
||||
}
|
||||
|
||||
// Interface para a resposta completa da API (Útil para o Service/Data layer)
|
||||
export interface ClientResponseInterface {
|
||||
message: string;
|
||||
data: ClientInterface[];
|
||||
pagination: ClientPaginationInterface;
|
||||
}
|
||||
|
|
@ -1,67 +1,29 @@
|
|||
import { z } from "zod";
|
||||
import { SituacoesEnum } from "@/shared/enums/SituacoesEnum";
|
||||
|
||||
/**
|
||||
* Schema de validação para a tabela `client`
|
||||
* Baseado na DDL do banco de dados `monitoring.client`
|
||||
*/
|
||||
export const ClientSchema = z.object({
|
||||
client_id: z.number().optional(), // ID gerado automaticamente (AUTO_INCREMENT)
|
||||
client_id: z.number().optional(),
|
||||
|
||||
// Validações Obrigatórias
|
||||
name: z.string().min(3, { message: "O nome deve ter no mínimo 3 caracteres." }),
|
||||
|
||||
cns: z.string().min(1, { message: "O CNS é obrigatório." }),
|
||||
// Dica: Se quiser validar o tamanho exato do CNS (geralmente 15 dígitos), use .length(15, "O CNS deve ter 15 dígitos")
|
||||
|
||||
cns: z
|
||||
.string()
|
||||
.max(10, { message: "O campo CNS deve ter no máximo 10 caracteres." })
|
||||
.nullable()
|
||||
.optional(), // Campo opcional e pode ser nulo
|
||||
// Campos Opcionais / Nulos
|
||||
// O .or(z.literal("")) ajuda caso o input envie string vazia em vez de null
|
||||
state: z.string().nullable().optional().or(z.literal("")),
|
||||
city: z.string().nullable().optional().or(z.literal("")),
|
||||
responsible: z.string().nullable().optional().or(z.literal("")),
|
||||
consultant: z.string().nullable().optional().or(z.literal("")),
|
||||
type_contract: z.string().nullable().optional().or(z.literal("")),
|
||||
|
||||
name: z
|
||||
.string()
|
||||
.min(3, { message: "O nome deve ter no mínimo 3 caracteres." })
|
||||
.max(550, { message: "O nome deve ter no máximo 550 caracteres." }), // Campo obrigatório
|
||||
// Enumeração (Correção aplicada: nativeEnum para Enums do TypeScript)
|
||||
status: z.nativeEnum(SituacoesEnum,{ message: "Selecione um status válido." }),
|
||||
|
||||
date_register: z
|
||||
.string()
|
||||
.nullable()
|
||||
.optional(), // Timestamp gerado automaticamente pelo banco
|
||||
|
||||
state: z
|
||||
.string()
|
||||
.length(2, { message: "O estado deve conter exatamente 2 caracteres (ex: 'SP')." })
|
||||
.nullable()
|
||||
.optional(), // Campo opcional
|
||||
|
||||
city: z
|
||||
.string()
|
||||
.max(160, { message: "O nome da cidade deve ter no máximo 160 caracteres." })
|
||||
.nullable()
|
||||
.optional(),
|
||||
|
||||
responsible: z
|
||||
.string()
|
||||
.max(160, { message: "O nome do responsável deve ter no máximo 160 caracteres." })
|
||||
.nullable()
|
||||
.optional(),
|
||||
|
||||
consultant: z
|
||||
.string()
|
||||
.max(160, { message: "O nome do consultor deve ter no máximo 160 caracteres." })
|
||||
.nullable()
|
||||
.optional(),
|
||||
|
||||
type_contract: z
|
||||
.string()
|
||||
.length(1, { message: "O tipo de contrato deve conter apenas 1 caractere." })
|
||||
.nullable()
|
||||
.optional(), // Pode representar tipo de contrato (ex: 'A' = anual, 'M' = mensal)
|
||||
|
||||
status: z
|
||||
.string()
|
||||
.length(1, { message: "O status de deve conter apenas 1 caractere." })
|
||||
.nullable()
|
||||
.optional(), // Pode representar tipo de status (ex: 'A' = ativo, 'I' = inativo)
|
||||
// Campos de Auditoria (Geralmente apenas para leitura ou ocultos)
|
||||
date_register: z.string().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Tipo TypeScript inferido automaticamente a partir do schema.
|
||||
* Permite utilizar a tipagem do Zod em qualquer lugar do código.
|
||||
*/
|
||||
export type ClientSchemaType = z.infer<typeof ClientSchema>;
|
||||
// Extrai o tipo TypeScript automaticamente a partir do Schema (opcional, mas útil)
|
||||
export type ClientSchemaType = z.infer<typeof ClientSchema>;
|
||||
|
|
@ -1,31 +1,21 @@
|
|||
'use server'
|
||||
// Indica que este módulo será executado no lado do servidor (Server Action do Next.js)
|
||||
'use server'
|
||||
|
||||
import { withClientErrorHandler } from "@/withClientErrorHandler/withClientErrorHandler"
|
||||
// Importa o decorador responsável por tratar erros de forma padronizada em funções assíncronas
|
||||
|
||||
import { ClientDeleteData } from "../../data/Client/ClientDeleteData"
|
||||
// Importa a função responsável por realizar a operação real de exclusão do Cliente (camada de acesso a dados)
|
||||
|
||||
// Função principal que executa o processo de exclusão de um Cliente
|
||||
async function executeClientDeleteService(usuarioId: number) {
|
||||
async function executeClientDeleteService(client_id: number) {
|
||||
|
||||
// Validação básica: impede que IDs inválidos ou menores que 1 sejam processados
|
||||
if (usuarioId <= 0) {
|
||||
// Validação básica de negócio
|
||||
if (client_id <= 0) {
|
||||
return {
|
||||
'code': 400, // Código HTTP indicando requisição inválida
|
||||
'message': 'Cliente informado inválido', // Mensagem de erro clara e descritiva
|
||||
'code': 400,
|
||||
'message': 'ID do cliente inválido',
|
||||
}
|
||||
}
|
||||
|
||||
// Chama a função que realiza a exclusão no banco ou API, passando o ID do Cliente
|
||||
const response = await ClientDeleteData(usuarioId)
|
||||
const response = await ClientDeleteData(client_id)
|
||||
|
||||
// Retorna a resposta vinda da camada de dados, que pode conter status e mensagem
|
||||
return response
|
||||
}
|
||||
|
||||
// Exporta a função encapsulada com o tratador global de erros
|
||||
export const ClientDeleteService = withClientErrorHandler(executeClientDeleteService)
|
||||
// `withClientErrorHandler` garante que exceções não tratadas sejam capturadas e formatadas de forma uniforme
|
||||
// Dessa forma, evita que erros internos quebrem o fluxo da aplicação
|
||||
export const ClientDeleteService = withClientErrorHandler(executeClientDeleteService)
|
||||
|
|
@ -1,24 +1,13 @@
|
|||
'use server';
|
||||
// Indica que este código será executado no lado do servidor (Server Component ou ação server-side do Next.js)
|
||||
'use server'
|
||||
|
||||
import { withClientErrorHandler } from "@/withClientErrorHandler/withClientErrorHandler";
|
||||
import { ClientIndexData } from "../../data/Client/ClientIndexData";
|
||||
|
||||
// Função principal que executa a chamada para buscar a lista de clientes
|
||||
async function executeClientIndexService() {
|
||||
try {
|
||||
// Chama o método responsável por buscar os dados da API (camada "data")
|
||||
const response = await ClientIndexData();
|
||||
|
||||
console.log('Kenão')
|
||||
const response = await ClientIndexData();
|
||||
|
||||
// Retorna a resposta recebida da API (geralmente um objeto com status, mensagem e dados)
|
||||
return response;
|
||||
} catch (error) {
|
||||
// Caso ocorra algum erro inesperado, lança o erro para ser tratado pelo wrapper
|
||||
throw error;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
// Exporta o serviço com tratamento global de erros usando o wrapper `withClientErrorHandler`
|
||||
export const ClientIndexService = withClientErrorHandler(executeClientIndexService);
|
||||
export const ClientIndexService = withClientErrorHandler(executeClientIndexService);
|
||||
|
|
@ -1,20 +1,12 @@
|
|||
'use server'
|
||||
// Indica que este módulo será executado no lado do servidor (Server Action do Next.js)
|
||||
|
||||
import { withClientErrorHandler } from "@/withClientErrorHandler/withClientErrorHandler";
|
||||
// Importa o utilitário responsável por envolver a função com tratamento global de erros do cliente
|
||||
|
||||
import { ClientSaveData } from "../../data/Client/ClientSaveData";
|
||||
// Importa a função que realiza a operação de salvamento de dados do cliente (chamada à API ou banco de dados)
|
||||
import { ClientInterface } from "../../interfaces/Client/ClientInterface";
|
||||
|
||||
// Função principal que executa o salvamento de dados de um cliente
|
||||
async function executeClientSave(form: any) {
|
||||
async function executeClientSave(form: ClientInterface) {
|
||||
|
||||
// Chama a função que realmente salva os dados, repassando o formulário recebido
|
||||
return await ClientSaveData(form);
|
||||
}
|
||||
|
||||
// Exporta o serviço, agora protegido pelo tratador de erros
|
||||
export const ClientSaveService = withClientErrorHandler(executeClientSave);
|
||||
// `withClientErrorHandler` adiciona camadas de controle de exceção e formatação de erro à função principal
|
||||
// Assim, qualquer falha em `executeClientSave` será capturada e tratada de forma padronizada
|
||||
export const ClientSaveService = withClientErrorHandler(executeClientSave);
|
||||
|
|
@ -58,13 +58,13 @@ export async function CheckLiberationHelper(cns: string) {
|
|||
|
||||
// Cria um resumo com detalhes de cada item (útil para exibir tooltip, modal, etc.)
|
||||
const details = [
|
||||
...expired.map((item) => ({
|
||||
...expired.map((item: { description: any; reference: any; days: any; }) => ({
|
||||
type: 'vencido',
|
||||
description: item.description,
|
||||
reference: item.reference,
|
||||
days: item.days,
|
||||
})),
|
||||
...next.map((item) => ({
|
||||
...next.map((item: { description: any; reference: any; days: any; }) => ({
|
||||
type: 'a_vencer',
|
||||
description: item.description,
|
||||
reference: item.reference,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
export default interface withClientErrorHandlerInterface<T = any> {
|
||||
code: number;
|
||||
|
||||
status: number;
|
||||
data?: T;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue