Refactor useEmployees hook to enhance employee management functionality and integrate API services. Added methods for fetching employees, statistics, alerts, and handling employee updates, inactivation, and reactivation. Updated HRDashboard to include dialog descriptions for better user guidance.

This commit is contained in:
daivid.alves 2026-01-19 15:55:32 -03:00
parent d7691da32e
commit 817b0ec4eb
2 changed files with 291 additions and 25 deletions

View File

@ -9,6 +9,7 @@ import {
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogTrigger,
} from "@/components/ui/dialog";
import { useEmployees } from '../hooks/useEmployees';
@ -91,6 +92,9 @@ export const HRDashboard = () => {
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>{editingRequest ? 'Editar Solicitação' : 'Nova Solicitação de RH'}</DialogTitle>
<DialogDescription>
{editingRequest ? 'Atualize os dados da solicitação abaixo.' : 'Preencha os dados para criar uma nova solicitação de RH.'}
</DialogDescription>
</DialogHeader>
<EmployeeForm
initialData={editingRequest}

View File

@ -1,42 +1,304 @@
import { useState, useCallback } from 'react';
import { rhService } from '../services/rhService';
import { toast } from 'sonner';
/**
* @typedef {Object} Employee
* @property {number} id
* @property {string} name
* @property {string} type
* @property {string} date
* @property {string} status
* Hook customizado para gestão de colaboradores (Padrão Integra Finance).
* Centraliza toda a lógica de negócio e comunicação com a API de colaboradores.
*/
export const useEmployees = () => {
const [employees, setEmployees] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [statistics, setStatistics] = useState([]);
const [alerts, setAlerts] = useState([]);
/**
* Hook customizado para gestão de colaboradores/solicitações (Padrão Integra Finance).
*/
export const useEmployees = (initialData = []) => {
const [employees, setEmployees] = useState(initialData);
/**
* Busca lista de colaboradores com filtros
*/
const fetchEmployees = useCallback(async (params = {}) => {
setLoading(true);
setError(null);
try {
const response = await rhService.getCollaborators(params);
const rawList = Array.isArray(response) ? response : (response?.data || []);
// Mapeamento de campos da API para a UI
const mappedEmployees = rawList.map(emp => {
// Cálculo de status CNH baseado na validade
let cnhStatus = 'valida';
if (emp.validade_cnh) {
const expiry = new Date(emp.validade_cnh);
const now = new Date();
const diffDays = Math.ceil((expiry - now) / (1000 * 60 * 60 * 24));
if (diffDays < 0) cnhStatus = 'vencida';
else if (diffDays <= 30) cnhStatus = 'proxima_vencer';
}
const addEmployeeRequest = useCallback((newData) => {
setEmployees((prev) => [
...prev,
{ ...newData, id: prev.length > 0 ? Math.max(...prev.map(e => e.id)) + 1 : 1 }
]);
return {
id: emp.idcolaborador || emp.id,
name: emp.colaboradores || emp.nome || emp.name,
email: emp.email_corporativo || emp.email,
phone: emp.telefone,
cpf: emp.cpf_validado || emp.cpf,
role: emp.cargo || emp.role,
department: emp.empresas || emp.department,
status: emp.status_contrato || emp.status || 'Ativo',
cnh_status: cnhStatus,
admissionDate: emp.data_admissao ? new Date(emp.data_admissao).toLocaleDateString() : '-',
pasta: emp.pasta_rh || emp.pasta,
salary: emp.salario,
manager: emp.responsavel_direto_validado || '-'
};
});
setEmployees(mappedEmployees);
return mappedEmployees;
} catch (err) {
setError(err.message);
toast.error('Erro ao carregar colaboradores');
return [];
} finally {
setLoading(false);
}
}, []);
const updateEmployeeRequest = useCallback((id, updatedData) => {
setEmployees((prev) =>
prev.map((e) => (e.id === id ? { ...e, ...updatedData } : e))
);
/**
* Busca um colaborador por ID
*/
const fetchEmployeeById = useCallback(async (id) => {
try {
const response = await rhService.getCollaboratorById(id);
return response?.data || null;
} catch (err) {
toast.error('Erro ao buscar detalhes do colaborador');
return null;
}
}, []);
const deleteEmployeeRequest = useCallback((id) => {
setEmployees((prev) => prev.filter((e) => e.id !== id));
/**
* Busca estatísticas de colaboradores
*/
const fetchStatistics = useCallback(async () => {
setLoading(true);
try {
const response = await rhService.getStatistics();
console.log('RH Stats Response:', response); // Para debug se necessário
if (!response?.success) {
setStatistics([]);
return null;
}
const raw = response.data || {};
// Mapeamento dos cards superiores
const mappedStats = [
{
label: 'Total Geral',
value: Number(raw.total || 0),
trend: 'Colaboradores',
color: 'bg-indigo-500/10 text-indigo-500'
},
{
label: 'Colaboradores Ativos',
value: Number(raw.por_status_contrato?.find(i => i.status_contrato === 'Ativo')?.quantidade || 0),
trend: 'Em operação',
color: 'bg-emerald-500/10 text-emerald-500'
},
{
label: 'CNH Vencidas',
value: Number(raw.cnh_status?.cnh_vencida || 0),
trend: 'Ação imediata',
color: 'bg-rose-500/10 text-rose-500',
negative: true
},
{
label: 'CNH a Vencer',
value: Number(raw.cnh_status?.cnh_proxima_vencer || 0),
trend: 'Próximos 30 dias',
color: 'bg-amber-500/10 text-amber-500'
},
{
label: 'Contas Email Corp.',
value: Number(raw.email_corporativo?.com_email || 0),
trend: 'Acesso liberado',
color: 'bg-blue-500/10 text-blue-500'
},
{
label: 'Desktops da Empresa',
value: Number(raw.desktop_empresa?.com_desktop || 0),
trend: 'Patrimônio em uso',
color: 'bg-slate-500/10 text-slate-500'
}
];
setStatistics(mappedStats);
const result = {
stats: mappedStats,
charts: {
por_base: raw.por_base || [],
por_cargo: raw.por_cargo || [],
por_cliente: raw.por_cliente || [],
por_status: raw.por_status_contrato || [],
por_empresa: raw.por_empresa || []
}
};
return result;
} catch (err) {
console.error('Erro ao carregar estatísticas:', err);
setStatistics([]);
return null;
} finally {
setLoading(false);
}
}, []);
/**
* Busca alertas de colaboradores
*/
const fetchAlerts = useCallback(async () => {
try {
const response = await rhService.getAlerts();
if (!response?.success) {
setAlerts([]);
return null;
}
const raw = response.data || {};
const mappedAlerts = [
{
label: 'CNH Vencida AGORA',
value: raw.cnh_vencida?.length || 0,
trend: 'Bloqueio imediato',
color: 'bg-rose-500/10 text-rose-500',
negative: true,
urgent: true
},
{
label: 'Férias Próximas',
value: raw.ferias_proximas?.length || 0,
trend: 'Próximos 60 dias',
color: 'bg-blue-500/10 text-blue-500'
},
{
label: 'Falta Telefone',
value: raw.sem_telefone?.length || 0,
trend: 'Contato urgente',
color: 'bg-slate-500/10 text-slate-500',
negative: true
},
{
label: 'Sem Email Corp.',
value: raw.sem_email_corporativo?.length || 0,
trend: 'Pendência TI',
color: 'bg-amber-500/10 text-amber-500'
}
];
setAlerts(mappedAlerts);
return raw;
} catch (err) {
console.error('Erro ao carregar alertas:', err);
setAlerts([]);
return null;
}
}, []);
/**
* Atualiza dados de um colaborador
*/
const updateEmployee = useCallback(async (id, payload) => {
setLoading(true);
try {
const response = await rhService.updateCollaborator(id, payload);
if (response?.success) {
toast.success('Alterações salvas com sucesso!');
await fetchEmployees();
return response.data;
}
throw new Error(response?.error || 'Erro desconhecido');
} catch (err) {
toast.error('Erro ao salvar alterações: ' + err.message);
throw err;
} finally {
setLoading(false);
}
}, [fetchEmployees]);
/**
* Inativar colaborador
*/
const inactivateEmployee = useCallback(async (id) => {
setLoading(true);
try {
const response = await rhService.inactivateCollaborator(id);
if (response?.success) {
toast.success('Colaborador inativado com sucesso.');
await fetchEmployees();
return true;
}
throw new Error(response?.error || 'Falha ao inativar');
} catch (err) {
toast.error(err.message);
return false;
} finally {
setLoading(false);
}
}, [fetchEmployees]);
/**
* Reativar colaborador
*/
const reactivateEmployee = useCallback(async (id) => {
setLoading(true);
try {
const response = await rhService.reactivateCollaborator(id);
if (response?.success) {
toast.success('Colaborador reativado com sucesso.');
await fetchEmployees();
return true;
}
throw new Error(response?.error || 'Falha ao reativar');
} catch (err) {
toast.error(err.message);
return false;
} finally {
setLoading(false);
}
}, [fetchEmployees]);
/**
* Chat / Busca Inteligente
*/
const searchInChat = useCallback(async (pergunta) => {
try {
const response = await rhService.searchChat(pergunta);
return response;
} catch (err) {
toast.error('Erro na busca inteligente');
return null;
}
}, []);
return {
employees,
addEmployeeRequest,
updateEmployeeRequest,
deleteEmployeeRequest,
loading,
error,
statistics,
alerts,
fetchEmployees,
fetchEmployeeById,
fetchStatistics,
fetchAlerts,
updateEmployee,
inactivateEmployee,
reactivateEmployee,
searchInChat,
setEmployees
};
};