From ebde59c6d21533fe4e4d0d1c6491d4ebe384d192 Mon Sep 17 00:00:00 2001 From: deyvisonwanderley Date: Thu, 13 Nov 2025 05:51:55 -0300 Subject: [PATCH] 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. --- .../lib/components/AlterarStatusFerias.svelte | 277 +++++ .../components/ti/charts/BarChart3D.svelte | 372 ++++++ .../routes/(dashboard)/perfil/+page.svelte | 3 + .../atestados-licencas/+page.svelte | 466 ++++---- .../recursos-humanos/ferias/+page.svelte | 1023 +++++++++-------- packages/backend/convex/ferias.ts | 83 ++ 6 files changed, 1503 insertions(+), 721 deletions(-) create mode 100644 apps/web/src/lib/components/AlterarStatusFerias.svelte create mode 100644 apps/web/src/lib/components/ti/charts/BarChart3D.svelte diff --git a/apps/web/src/lib/components/AlterarStatusFerias.svelte b/apps/web/src/lib/components/AlterarStatusFerias.svelte new file mode 100644 index 0000000..43f2a44 --- /dev/null +++ b/apps/web/src/lib/components/AlterarStatusFerias.svelte @@ -0,0 +1,277 @@ + + +
+
+
+
+

+ {solicitacao.funcionario?.nome || 'Funcionário'} +

+

+ Ano de Referência: {solicitacao.anoReferencia} +

+
+
+ {getStatusTexto(solicitacao.status)} +
+
+ + +
+

Períodos Solicitados

+
+ {#each solicitacao.periodos as periodo, index (index)} +
+
{index + 1}
+
+
+ Início: + {new Date(periodo.dataInicio).toLocaleDateString('pt-BR')} +
+
+ Fim: + {new Date(periodo.dataFim).toLocaleDateString('pt-BR')} +
+
+ Dias: + {periodo.diasCorridos} +
+
+
+ {/each} +
+
+ + + {#if solicitacao.observacao} +
+

Observações

+
+ {solicitacao.observacao} +
+
+ {/if} + + + {#if solicitacao.historicoAlteracoes && solicitacao.historicoAlteracoes.length > 0} +
+

Histórico

+
+ {#each solicitacao.historicoAlteracoes as hist (hist.data)} +
+ + + + {formatarData(hist.data)} + - + {hist.acao} +
+ {/each} +
+
+ {/if} + + + {#if solicitacao.status !== 'aguardando_aprovacao'} +
+
+ + + +
+

Alterar Status

+
+ Ao voltar para "Aguardando Aprovação", a solicitação ficará disponível para aprovação ou + reprovação pelo gestor. +
+
+
+ +
+ +
+ {:else} +
+
+ + + + Esta solicitação já está aguardando aprovação. +
+ {/if} + + + {#if solicitacao.status === 'reprovado' && solicitacao.motivoReprovacao} +
+ + + +
+
Motivo da Reprovação:
+
{solicitacao.motivoReprovacao}
+
+
+ {/if} + + + {#if erro} +
+ + + + {erro} +
+ {/if} + + + {#if onCancelar} +
+ +
+ {/if} +
+
diff --git a/apps/web/src/lib/components/ti/charts/BarChart3D.svelte b/apps/web/src/lib/components/ti/charts/BarChart3D.svelte new file mode 100644 index 0000000..cef1b58 --- /dev/null +++ b/apps/web/src/lib/components/ti/charts/BarChart3D.svelte @@ -0,0 +1,372 @@ + + +
+ +
diff --git a/apps/web/src/routes/(dashboard)/perfil/+page.svelte b/apps/web/src/routes/(dashboard)/perfil/+page.svelte index 3da2728..dc835ef 100644 --- a/apps/web/src/routes/(dashboard)/perfil/+page.svelte +++ b/apps/web/src/routes/(dashboard)/perfil/+page.svelte @@ -91,6 +91,7 @@ : { data: null } ); + // Query para times onde o usuário é gestor - usando $derived para garantir reatividade const meusTimesGestorQuery = $derived( currentUser?.data?._id ? useQuery(api.times.listarPorGestor, { @@ -105,6 +106,8 @@ const minhasSolicitacoes = $derived(minhasSolicitacoesQuery?.data || []); const minhasAusencias = $derived(minhasAusenciasQuery?.data || []); const meuTime = $derived(meuTimeQuery?.data); + + // Extração de meusTimesGestor const meusTimesGestor = $derived(meusTimesGestorQuery?.data || []); // Verificar se é gestor diff --git a/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte b/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte index a346d4b..028d93f 100644 --- a/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte +++ b/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte @@ -776,9 +776,243 @@ {#if eventosQuery?.data} - +
+ +
{/if} + + {#if graficosQuery?.data} +
+
+

Funcionários Atualmente Afastados

+ {#if graficosQuery.data.funcionariosAfastados.length > 0} +
+ + + + + + + + + + + {#each graficosQuery.data.funcionariosAfastados as item} + + + + + + + {/each} + +
FuncionárioTipoData InícioData Fim
{item.funcionarioNome} + + {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} + + {formatarData(item.dataInicio)}{formatarData(item.dataFim)}
+
+ {:else} +
+ Nenhum funcionário afastado no momento +
+ {/if} +
+
+ {/if} + + +
+
+

Registros

+
+ + + + + + + + + + + + + + {#each registrosFiltrados.atestados as atestado} + + + + + + + + + + {/each} + {#each registrosFiltrados.licencas as licenca} + + + + + + + + + + {/each} + +
FuncionárioTipoData InícioData FimDiasStatusAções
{atestado.funcionario?.nome || '-'} + + {atestado.tipo === 'atestado_medico' ? 'Atestado Médico' : 'Declaração'} + + {formatarData(atestado.dataInicio)}{formatarData(atestado.dataFim)}{atestado.dias} + + {atestado.status === 'ativo' ? 'Ativo' : 'Finalizado'} + + +
+ {#if atestado.documentoId} + + {/if} + +
+
{licenca.funcionario?.nome || '-'} + + Licença{' '} + {licenca.tipo === 'maternidade' ? 'Maternidade' : 'Paternidade'} + {licenca.ehProrrogacao ? ' (Prorrogação)' : ''} + + {formatarData(licenca.dataInicio)}{formatarData(licenca.dataFim)}{licenca.dias} + + {licenca.status === 'ativo' ? 'Ativo' : 'Finalizado'} + + +
+ {#if licenca.documentoId} + + {/if} + +
+
+ {#if registrosFiltrados.atestados.length === 0 && registrosFiltrados.licencas.length === 0} +
Nenhum registro encontrado
+ {/if} +
+
+
+ {#if graficosQuery?.data} {@const dados = graficosQuery.data.totalDiasPorTipo} @@ -1060,237 +1294,7 @@ - - -
-
-

Funcionários Atualmente Afastados

- {#if graficosQuery.data.funcionariosAfastados.length > 0} -
- - - - - - - - - - - {#each graficosQuery.data.funcionariosAfastados as item} - - - - - - - {/each} - -
FuncionárioTipoData InícioData Fim
{item.funcionarioNome} - - {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} - - {formatarData(item.dataInicio)}{formatarData(item.dataFim)}
-
- {:else} -
- Nenhum funcionário afastado no momento -
- {/if} -
-
{/if} - - -
-
-

Registros

-
- - - - - - - - - - - - - - {#each registrosFiltrados.atestados as atestado} - - - - - - - - - - {/each} - {#each registrosFiltrados.licencas as licenca} - - - - - - - - - - {/each} - -
FuncionárioTipoData InícioData FimDiasStatusAções
{atestado.funcionario?.nome || '-'} - - {atestado.tipo === 'atestado_medico' ? 'Atestado Médico' : 'Declaração'} - - {formatarData(atestado.dataInicio)}{formatarData(atestado.dataFim)}{atestado.dias} - - {atestado.status === 'ativo' ? 'Ativo' : 'Finalizado'} - - -
- {#if atestado.documentoId} - - {/if} - -
-
{licenca.funcionario?.nome || '-'} - - Licença{' '} - {licenca.tipo === 'maternidade' ? 'Maternidade' : 'Paternidade'} - {licenca.ehProrrogacao ? ' (Prorrogação)' : ''} - - {formatarData(licenca.dataInicio)}{formatarData(licenca.dataFim)}{licenca.dias} - - {licenca.status === 'ativo' ? 'Ativo' : 'Finalizado'} - - -
- {#if licenca.documentoId} - - {/if} - -
-
- {#if registrosFiltrados.atestados.length === 0 && registrosFiltrados.licencas.length === 0} -
Nenhum registro encontrado
- {/if} -
-
-
{:else if abaAtiva === 'atestado'}
diff --git a/apps/web/src/routes/(dashboard)/recursos-humanos/ferias/+page.svelte b/apps/web/src/routes/(dashboard)/recursos-humanos/ferias/+page.svelte index cd6d2f2..2ac2bf0 100644 --- a/apps/web/src/routes/(dashboard)/recursos-humanos/ferias/+page.svelte +++ b/apps/web/src/routes/(dashboard)/recursos-humanos/ferias/+page.svelte @@ -1,12 +1,14 @@ @@ -1316,6 +1503,51 @@
{/if} + + {#if isLoading && !hasError} +
+
+
+
+
+
+ {:else if !hasError} +
+
+
+
+ + + +
+
+

Calendário Geral de Férias

+

+ Visualize os períodos aprovados diretamente no calendário interativo +

+
+
+
+
+
+
+
+ {/if} + {#if !isLoading || !hasError}
@@ -1450,6 +1682,7 @@ Total Dias Status Solicitado em + Ações @@ -1495,6 +1728,35 @@
{formatarData(solicitacao._creationTime)} + + + {/each} @@ -1549,168 +1811,60 @@
- - {#if periodosPorMesAtivos.length === 0} - Sem dados registrados até o momento. - {:else} - {@const maxDias = Math.max( - 1, - getMax(periodosPorMesAtivos, (p) => p.totalDias) - )} - {#each [0, 1, 2, 3, 4, 5] as passo (passo)} - {@const valor = Math.round((maxDias / 5) * passo)} - {@const y = chartHeight - padding.bottom - scaleY(valor, maxDias)} - - - {valor} dia(s) - - {/each} - - - p.totalDias, maxDias)} - fill="url(#gradient-ferias-mes)" - opacity="0.75" - /> - { - const x = getX(index, periodosPorMesAtivos.length); - const y = chartHeight - padding.bottom - scaleY(item.totalDias, maxDias); - return `${x},${y}`; - }) - .join(' ')} - fill="none" - stroke="rgb(59, 130, 246)" - stroke-width="3" - stroke-linecap="round" - /> - {#each periodosPorMesAtivos as item, index (item.label)} - {@const x = getX(index, periodosPorMesAtivos.length)} - {@const y = chartHeight - padding.bottom - scaleY(item.totalDias, maxDias)} - - - {item.totalDias} dia(s) - - - {item.quantidadePeriodos} período(s) - - {/each} - {#each periodosPorMesAtivos as item, index (item.label)} - {@const x = getX(index, periodosPorMesAtivos.length)} - -
- {item.label} -
-
- {/each} - - - - - - - {/if} -
- {#if periodosPorMes.length > 1} -
-
- Janela exibida - - {periodosPorMes[rangeInicioIndice]?.label ?? '-'} - → - {periodosPorMes[rangeFimIndice]?.label ?? '-'} - -
-
-
- - -
-
- - -
-
-

- Ajuste com o mouse os intervalos exibidos no gráfico. -

+ {#if periodosPorMesAtivos.length === 0} +
+

Sem dados registrados até o momento.

+ {:else} + + {#if periodosPorMes.length > 1} +
+
+ Janela exibida + + {periodosPorMes[rangeInicioIndice]?.label ?? '-'} + → + {periodosPorMes[rangeFimIndice]?.label ?? '-'} + +
+
+
+ + +
+
+ + +
+
+

+ Ajuste com o mouse os intervalos exibidos no gráfico. +

+
+ {/if} {/if}
@@ -1746,166 +1900,49 @@
- - {#if solicitacoesPorAno.length === 0} - Ainda não há solicitações registradas para exibição. - {:else} - {@const maxDiasAno = Math.max( - 1, - getMax(solicitacoesPorAno, (item) => item.diasTotais) - )} - {#each [0, 1, 2, 3, 4, 5] as passo (passo)} - {@const valor = Math.round((maxDiasAno / 5) * passo)} - {@const y = chartHeight - padding.bottom - scaleY(valor, maxDiasAno)} - - - {valor} dia(s) - - {/each} - - - item.diasTotais, maxDiasAno)} - fill="url(#gradient-ferias-ano)" - opacity="0.75" - /> - { - const x = getX(index, solicitacoesPorAno.length); - const y = chartHeight - padding.bottom - scaleY(item.diasTotais, maxDiasAno); - return `${x},${y}`; - }) - .join(' ')} - fill="none" - stroke="rgb(16, 185, 129)" - stroke-width="3" - stroke-linecap="round" - /> - {#each solicitacoesPorAno as item, index (item.ano)} - {@const x = getX(index, solicitacoesPorAno.length)} - {@const y = chartHeight - padding.bottom - scaleY(item.diasTotais, maxDiasAno)} - - - {item.diasTotais} dia(s) - - - {item.solicitacoes} solicitação(ões) - - {/each} - {#each solicitacoesPorAno as item, index (item.ano)} - {@const x = getX(index, solicitacoesPorAno.length)} - -
- {item.ano} -
-
- {/each} - - - - - - - {/if} -
-
- - - - -
-
-
-
- - - -
-
-

Calendário Geral de Férias

-

- Visualize os períodos aprovados diretamente no calendário interativo -

-
-
-
-
+ {#if solicitacoesPorAno.length === 0} +
+

+ Ainda não há solicitações registradas para exibição. +

+
+ {:else} + + {/if}
{/if} + + +{#if solicitacaoSelecionada && currentUser.data} + {#await client.query( api.ferias.obterDetalhes, { solicitacaoId: solicitacaoSelecionada } ) then detalhes} + {#if detalhes} + + + + + {/if} + {/await} +{/if} +