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:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user