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

@@ -0,0 +1,277 @@
<script lang="ts">
import { useConvexClient } from 'convex-svelte';
import { api } from '@sgse-app/backend/convex/_generated/api';
import type { Id, Doc } from '@sgse-app/backend/convex/_generated/dataModel';
type SolicitacaoFerias = Doc<'solicitacoesFerias'> & {
funcionario?: Doc<'funcionarios'> | null;
gestor?: Doc<'usuarios'> | null;
};
interface Props {
solicitacao: SolicitacaoFerias;
usuarioId: Id<'usuarios'>;
onSucesso?: () => void;
onCancelar?: () => void;
}
let { solicitacao, usuarioId, onSucesso, onCancelar }: Props = $props();
const client = useConvexClient();
let processando = $state(false);
let erro = $state('');
function getStatusBadge(status: string) {
const badges: Record<string, string> = {
aguardando_aprovacao: 'badge-warning',
aprovado: 'badge-success',
reprovado: 'badge-error',
data_ajustada_aprovada: 'badge-info'
};
return badges[status] || 'badge-neutral';
}
function getStatusTexto(status: string) {
const textos: Record<string, string> = {
aguardando_aprovacao: 'Aguardando Aprovação',
aprovado: 'Aprovado',
reprovado: 'Reprovado',
data_ajustada_aprovada: 'Data Ajustada e Aprovada'
};
return textos[status] || status;
}
async function voltarParaAguardando() {
try {
processando = true;
erro = '';
await client.mutation(api.ferias.atualizarStatus, {
solicitacaoId: solicitacao._id,
novoStatus: 'aguardando_aprovacao',
usuarioId: usuarioId
});
if (onSucesso) onSucesso();
} catch (e) {
erro = e instanceof Error ? e.message : String(e);
} finally {
processando = false;
}
}
function formatarData(data: number) {
return new Date(data).toLocaleString('pt-BR');
}
</script>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<div class="mb-4 flex items-start justify-between">
<div>
<h2 class="card-title text-2xl">
{solicitacao.funcionario?.nome || 'Funcionário'}
</h2>
<p class="text-base-content/70 mt-1 text-sm">
Ano de Referência: {solicitacao.anoReferencia}
</p>
</div>
<div class={`badge ${getStatusBadge(solicitacao.status)} badge-lg`}>
{getStatusTexto(solicitacao.status)}
</div>
</div>
<!-- Períodos Solicitados -->
<div class="mt-4">
<h3 class="mb-3 text-lg font-semibold">Períodos Solicitados</h3>
<div class="space-y-2">
{#each solicitacao.periodos as periodo, index (index)}
<div class="bg-base-200 flex items-center gap-4 rounded-lg p-3">
<div class="badge badge-primary">{index + 1}</div>
<div class="grid flex-1 grid-cols-3 gap-2 text-sm">
<div>
<span class="text-base-content/70">Início:</span>
<span class="ml-1 font-semibold"
>{new Date(periodo.dataInicio).toLocaleDateString('pt-BR')}</span
>
</div>
<div>
<span class="text-base-content/70">Fim:</span>
<span class="ml-1 font-semibold"
>{new Date(periodo.dataFim).toLocaleDateString('pt-BR')}</span
>
</div>
<div>
<span class="text-base-content/70">Dias:</span>
<span class="text-primary ml-1 font-bold">{periodo.diasCorridos}</span>
</div>
</div>
</div>
{/each}
</div>
</div>
<!-- Observações -->
{#if solicitacao.observacao}
<div class="mt-4">
<h3 class="mb-2 font-semibold">Observações</h3>
<div class="bg-base-200 rounded-lg p-3 text-sm">
{solicitacao.observacao}
</div>
</div>
{/if}
<!-- Histórico -->
{#if solicitacao.historicoAlteracoes && solicitacao.historicoAlteracoes.length > 0}
<div class="mt-4">
<h3 class="mb-2 font-semibold">Histórico</h3>
<div class="space-y-1">
{#each solicitacao.historicoAlteracoes as hist (hist.data)}
<div class="text-base-content/70 flex items-center gap-2 text-xs">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-3 w-3"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>{formatarData(hist.data)}</span>
<span>-</span>
<span>{hist.acao}</span>
</div>
{/each}
</div>
</div>
{/if}
<!-- Ação: Voltar para Aguardando Aprovação -->
{#if solicitacao.status !== 'aguardando_aprovacao'}
<div class="divider mt-6"></div>
<div class="alert alert-info">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="h-6 w-6 shrink-0 stroke-current"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<div>
<h3 class="font-bold">Alterar Status</h3>
<div class="text-sm">
Ao voltar para "Aguardando Aprovação", a solicitação ficará disponível para aprovação ou
reprovação pelo gestor.
</div>
</div>
</div>
<div class="card-actions mt-4 justify-end">
<button
type="button"
class="btn btn-warning gap-2"
onclick={voltarParaAguardando}
disabled={processando}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
/>
</svg>
Voltar para Aguardando Aprovação
</button>
</div>
{:else}
<div class="divider mt-6"></div>
<div class="alert">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-info h-6 w-6 shrink-0"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<span>Esta solicitação já está aguardando aprovação.</span>
</div>
{/if}
<!-- Motivo Reprovação (se reprovado) -->
{#if solicitacao.status === 'reprovado' && solicitacao.motivoReprovacao}
<div class="alert alert-error mt-4">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 shrink-0 stroke-current"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<div>
<div class="font-bold">Motivo da Reprovação:</div>
<div class="text-sm">{solicitacao.motivoReprovacao}</div>
</div>
</div>
{/if}
<!-- Erro -->
{#if erro}
<div class="alert alert-error mt-4">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 shrink-0 stroke-current"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>{erro}</span>
</div>
{/if}
<!-- Botão Fechar -->
{#if onCancelar}
<div class="card-actions mt-4 justify-end">
<button type="button" class="btn" onclick={onCancelar} disabled={processando}>
Cancelar
</button>
</div>
{/if}
</div>
</div>