refactor: enhance vacation management components and add status update functionality

- Improved the vacation request component with better loading states and error handling.
- Added a new mutation to update the status of vacation requests, allowing transitions between different states.
- Enhanced the calendar display for vacation periods and integrated a 3D bar chart for visualizing vacation data.
- Refactored the code for better readability and maintainability, ensuring a smoother user experience.
This commit is contained in:
2025-11-13 05:51:55 -03:00
parent 0b7f1ad621
commit ebde59c6d2
6 changed files with 1503 additions and 721 deletions

View File

@@ -776,9 +776,243 @@
<!-- Calendário Interativo -->
{#if eventosQuery?.data}
<CalendarioAfastamentos eventos={eventosQuery.data} tipoFiltro={filtroTipo} />
<div class="mb-6">
<CalendarioAfastamentos eventos={eventosQuery.data} tipoFiltro={filtroTipo} />
</div>
{/if}
<!-- Lista de Funcionários Afastados -->
{#if graficosQuery?.data}
<div class="card bg-base-100 mb-6 shadow-xl">
<div class="card-body">
<h2 class="card-title mb-4">Funcionários Atualmente Afastados</h2>
{#if graficosQuery.data.funcionariosAfastados.length > 0}
<div class="overflow-x-auto">
<table class="table-zebra table">
<thead>
<tr>
<th>Funcionário</th>
<th>Tipo</th>
<th>Data Início</th>
<th>Data Fim</th>
</tr>
</thead>
<tbody>
{#each graficosQuery.data.funcionariosAfastados as item}
<tr>
<td>{item.funcionarioNome}</td>
<td>
<span
class="badge {item.tipo === 'atestado_medico'
? 'badge-error'
: item.tipo === 'declaracao_comparecimento'
? 'badge-warning'
: item.tipo === 'maternidade'
? 'badge-secondary'
: item.tipo === 'paternidade'
? 'badge-info'
: 'badge-success'}"
>
{item.tipo === 'atestado_medico'
? 'Atestado Médico'
: item.tipo === 'declaracao_comparecimento'
? 'Declaração'
: item.tipo === 'maternidade'
? 'Licença Maternidade'
: item.tipo === 'paternidade'
? 'Licença Paternidade'
: item.tipo}
</span>
</td>
<td>{formatarData(item.dataInicio)}</td>
<td>{formatarData(item.dataFim)}</td>
</tr>
{/each}
</tbody>
</table>
</div>
{:else}
<div class="text-base-content/60 py-10 text-center">
Nenhum funcionário afastado no momento
</div>
{/if}
</div>
</div>
{/if}
<!-- Tabela de Registros -->
<div class="card bg-base-100 mb-6 shadow-xl">
<div class="card-body">
<h2 class="card-title mb-4">Registros</h2>
<div class="overflow-x-auto">
<table class="table-zebra table">
<thead>
<tr>
<th>Funcionário</th>
<th>Tipo</th>
<th>Data Início</th>
<th>Data Fim</th>
<th>Dias</th>
<th>Status</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{#each registrosFiltrados.atestados as atestado}
<tr>
<td>{atestado.funcionario?.nome || '-'}</td>
<td>
<span
class="badge {atestado.tipo === 'atestado_medico'
? 'badge-error'
: 'badge-warning'}"
>
{atestado.tipo === 'atestado_medico' ? 'Atestado Médico' : 'Declaração'}
</span>
</td>
<td class="font-mono text-xs whitespace-nowrap"
>{formatarData(atestado.dataInicio)}</td
>
<td class="font-mono text-xs whitespace-nowrap"
>{formatarData(atestado.dataFim)}</td
>
<td>{atestado.dias}</td>
<td>
<span
class="badge {atestado.status === 'ativo'
? 'badge-success'
: 'badge-neutral'}"
>
{atestado.status === 'ativo' ? 'Ativo' : 'Finalizado'}
</span>
</td>
<td>
<div class="flex gap-2">
{#if atestado.documentoId}
<button
class="btn btn-xs btn-ghost"
onclick={async () => {
try {
const url = await client.query(
api.atestadosLicencas.obterUrlDocumento,
{
storageId: atestado.documentoId as any
}
);
if (url) {
window.open(url, '_blank');
} else {
mostrarErro(
'Erro ao visualizar documento',
'Não foi possível obter a URL do documento.',
'O documento pode ter sido removido ou não existe mais.'
);
}
} catch (err: any) {
console.error('Erro ao obter URL do documento:', err);
mostrarErro(
'Erro ao visualizar documento',
'Não foi possível abrir o documento.',
err?.message || err?.toString() || 'Erro desconhecido'
);
}
}}
>
Ver Doc
</button>
{/if}
<button
class="btn btn-xs btn-error"
onclick={() => excluirRegistro('atestado', atestado._id)}
>
Excluir
</button>
</div>
</td>
</tr>
{/each}
{#each registrosFiltrados.licencas as licenca}
<tr>
<td>{licenca.funcionario?.nome || '-'}</td>
<td>
<span
class="badge {licenca.tipo === 'maternidade'
? 'badge-secondary'
: 'badge-info'}"
>
Licença{' '}
{licenca.tipo === 'maternidade' ? 'Maternidade' : 'Paternidade'}
{licenca.ehProrrogacao ? ' (Prorrogação)' : ''}
</span>
</td>
<td class="font-mono text-xs whitespace-nowrap"
>{formatarData(licenca.dataInicio)}</td
>
<td class="font-mono text-xs whitespace-nowrap"
>{formatarData(licenca.dataFim)}</td
>
<td>{licenca.dias}</td>
<td>
<span
class="badge {licenca.status === 'ativo' ? 'badge-success' : 'badge-neutral'}"
>
{licenca.status === 'ativo' ? 'Ativo' : 'Finalizado'}
</span>
</td>
<td>
<div class="flex gap-2">
{#if licenca.documentoId}
<button
class="btn btn-xs btn-ghost"
onclick={async () => {
try {
const url = await client.query(
api.atestadosLicencas.obterUrlDocumento,
{
storageId: licenca.documentoId as any
}
);
if (url) {
window.open(url, '_blank');
} else {
mostrarErro(
'Erro ao visualizar documento',
'Não foi possível obter a URL do documento.',
'O documento pode ter sido removido ou não existe mais.'
);
}
} catch (err: any) {
console.error('Erro ao obter URL do documento:', err);
mostrarErro(
'Erro ao visualizar documento',
'Não foi possível abrir o documento.',
err?.message || err?.toString() || 'Erro desconhecido'
);
}
}}
>
Ver Doc
</button>
{/if}
<button
class="btn btn-xs btn-error"
onclick={() => excluirRegistro('licenca', licenca._id)}
>
Excluir
</button>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
{#if registrosFiltrados.atestados.length === 0 && registrosFiltrados.licencas.length === 0}
<div class="text-base-content/60 py-10 text-center">Nenhum registro encontrado</div>
{/if}
</div>
</div>
</div>
<!-- Gráficos -->
{#if graficosQuery?.data}
{@const dados = graficosQuery.data.totalDiasPorTipo}
@@ -1060,237 +1294,7 @@
</div>
</div>
</div>
<!-- Lista de Funcionários Afastados -->
<div class="card bg-base-100 mb-6 shadow-xl">
<div class="card-body">
<h2 class="card-title mb-4">Funcionários Atualmente Afastados</h2>
{#if graficosQuery.data.funcionariosAfastados.length > 0}
<div class="overflow-x-auto">
<table class="table-zebra table">
<thead>
<tr>
<th>Funcionário</th>
<th>Tipo</th>
<th>Data Início</th>
<th>Data Fim</th>
</tr>
</thead>
<tbody>
{#each graficosQuery.data.funcionariosAfastados as item}
<tr>
<td>{item.funcionarioNome}</td>
<td>
<span
class="badge {item.tipo === 'atestado_medico'
? 'badge-error'
: item.tipo === 'declaracao_comparecimento'
? 'badge-warning'
: item.tipo === 'maternidade'
? 'badge-secondary'
: item.tipo === 'paternidade'
? 'badge-info'
: 'badge-success'}"
>
{item.tipo === 'atestado_medico'
? 'Atestado Médico'
: item.tipo === 'declaracao_comparecimento'
? 'Declaração'
: item.tipo === 'maternidade'
? 'Licença Maternidade'
: item.tipo === 'paternidade'
? 'Licença Paternidade'
: item.tipo}
</span>
</td>
<td>{formatarData(item.dataInicio)}</td>
<td>{formatarData(item.dataFim)}</td>
</tr>
{/each}
</tbody>
</table>
</div>
{:else}
<div class="text-base-content/60 py-10 text-center">
Nenhum funcionário afastado no momento
</div>
{/if}
</div>
</div>
{/if}
<!-- Tabela de Registros -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title mb-4">Registros</h2>
<div class="overflow-x-auto">
<table class="table-zebra table">
<thead>
<tr>
<th>Funcionário</th>
<th>Tipo</th>
<th>Data Início</th>
<th>Data Fim</th>
<th>Dias</th>
<th>Status</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{#each registrosFiltrados.atestados as atestado}
<tr>
<td>{atestado.funcionario?.nome || '-'}</td>
<td>
<span
class="badge {atestado.tipo === 'atestado_medico'
? 'badge-error'
: 'badge-warning'}"
>
{atestado.tipo === 'atestado_medico' ? 'Atestado Médico' : 'Declaração'}
</span>
</td>
<td class="font-mono text-xs whitespace-nowrap"
>{formatarData(atestado.dataInicio)}</td
>
<td class="font-mono text-xs whitespace-nowrap"
>{formatarData(atestado.dataFim)}</td
>
<td>{atestado.dias}</td>
<td>
<span
class="badge {atestado.status === 'ativo'
? 'badge-success'
: 'badge-neutral'}"
>
{atestado.status === 'ativo' ? 'Ativo' : 'Finalizado'}
</span>
</td>
<td>
<div class="flex gap-2">
{#if atestado.documentoId}
<button
class="btn btn-xs btn-ghost"
onclick={async () => {
try {
const url = await client.query(
api.atestadosLicencas.obterUrlDocumento,
{
storageId: atestado.documentoId as any
}
);
if (url) {
window.open(url, '_blank');
} else {
mostrarErro(
'Erro ao visualizar documento',
'Não foi possível obter a URL do documento.',
'O documento pode ter sido removido ou não existe mais.'
);
}
} catch (err: any) {
console.error('Erro ao obter URL do documento:', err);
mostrarErro(
'Erro ao visualizar documento',
'Não foi possível abrir o documento.',
err?.message || err?.toString() || 'Erro desconhecido'
);
}
}}
>
Ver Doc
</button>
{/if}
<button
class="btn btn-xs btn-error"
onclick={() => excluirRegistro('atestado', atestado._id)}
>
Excluir
</button>
</div>
</td>
</tr>
{/each}
{#each registrosFiltrados.licencas as licenca}
<tr>
<td>{licenca.funcionario?.nome || '-'}</td>
<td>
<span
class="badge {licenca.tipo === 'maternidade'
? 'badge-secondary'
: 'badge-info'}"
>
Licença{' '}
{licenca.tipo === 'maternidade' ? 'Maternidade' : 'Paternidade'}
{licenca.ehProrrogacao ? ' (Prorrogação)' : ''}
</span>
</td>
<td class="font-mono text-xs whitespace-nowrap"
>{formatarData(licenca.dataInicio)}</td
>
<td class="font-mono text-xs whitespace-nowrap"
>{formatarData(licenca.dataFim)}</td
>
<td>{licenca.dias}</td>
<td>
<span
class="badge {licenca.status === 'ativo' ? 'badge-success' : 'badge-neutral'}"
>
{licenca.status === 'ativo' ? 'Ativo' : 'Finalizado'}
</span>
</td>
<td>
<div class="flex gap-2">
{#if licenca.documentoId}
<button
class="btn btn-xs btn-ghost"
onclick={async () => {
try {
const url = await client.query(
api.atestadosLicencas.obterUrlDocumento,
{
storageId: licenca.documentoId as any
}
);
if (url) {
window.open(url, '_blank');
} else {
mostrarErro(
'Erro ao visualizar documento',
'Não foi possível obter a URL do documento.',
'O documento pode ter sido removido ou não existe mais.'
);
}
} catch (err: any) {
console.error('Erro ao obter URL do documento:', err);
mostrarErro(
'Erro ao visualizar documento',
'Não foi possível abrir o documento.',
err?.message || err?.toString() || 'Erro desconhecido'
);
}
}}
>
Ver Doc
</button>
{/if}
<button
class="btn btn-xs btn-error"
onclick={() => excluirRegistro('licenca', licenca._id)}
>
Excluir
</button>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
{#if registrosFiltrados.atestados.length === 0 && registrosFiltrados.licencas.length === 0}
<div class="text-base-content/60 py-10 text-center">Nenhum registro encontrado</div>
{/if}
</div>
</div>
</div>
{:else if abaAtiva === 'atestado'}
<!-- Formulário Atestado Médico -->
<div class="card bg-base-100 shadow-xl">