From 11eef4aa2ac2d1b972169d41f0b4be1d66514c50 Mon Sep 17 00:00:00 2001 From: killer-cf Date: Wed, 12 Nov 2025 16:36:29 -0300 Subject: [PATCH] refactor: improve Svelte components and enhance user experience - Updated various Svelte components to improve code readability and maintainability. - Standardized button classes across components for a consistent user interface. - Enhanced error handling and user feedback in modals and forms. - Cleaned up unnecessary imports and optimized component structure for better performance. --- .../lib/components/AprovarAusencias.svelte | 758 ++- .../src/lib/components/AprovarFerias.svelte | 836 ++-- apps/web/src/lib/components/ErrorModal.svelte | 131 +- .../lib/components/FuncionarioSelect.svelte | 332 +- apps/web/src/lib/components/PrintModal.svelte | 948 ++-- .../src/lib/components/SolicitarFerias.svelte | 2 +- .../WizardSolicitacaoAusencia.svelte | 895 ++-- .../src/lib/components/chat/ChatWindow.svelte | 956 ++-- .../chat/NewConversationModal.svelte | 816 ++-- .../components/chat/SalaReuniaoManager.svelte | 7 +- .../chat/ScheduleMessageModal.svelte | 493 +- .../lib/components/ti/AlertConfigModal.svelte | 917 ++-- .../(dashboard)/alterar-senha/+page.svelte | 947 ++-- .../(dashboard)/esqueci-senha/+page.svelte | 585 ++- .../gestao-ausencias/+page.svelte | 772 ++- .../routes/(dashboard)/perfil/+page.svelte | 6 +- .../atestados-licencas/+page.svelte | 3092 ++++++------ .../recursos-humanos/ausencias/+page.svelte | 777 ++- .../recursos-humanos/ferias/+page.svelte | 2 +- .../funcionarios/+page.svelte | 506 +- .../[funcionarioId]/documentos/+page.svelte | 587 ++- .../[funcionarioId]/editar/+page.svelte | 2 +- .../funcionarios/cadastro/+page.svelte | 6 +- .../funcionarios/excluir/+page.svelte | 774 +-- .../recursos-humanos/simbolos/+page.svelte | 759 +-- .../simbolos/[simboloId]/editar/+page.svelte | 798 ++-- .../simbolos/cadastro/+page.svelte | 958 ++-- .../gestao-ausencias/+page.svelte | 776 ++- .../(dashboard)/solicitar-acesso/+page.svelte | 697 ++- .../ti/monitoramento-emails/+page.svelte | 945 ++-- .../(dashboard)/ti/monitoramento/+page.svelte | 2 +- .../(dashboard)/ti/notificacoes/+page.svelte | 4229 ++++++++--------- .../ti/painel-permissoes/+page.svelte | 12 +- .../routes/(dashboard)/ti/perfis/+page.svelte | 1546 +++--- .../ti/personalizar-permissoes/+page.svelte | 197 +- .../ti/solicitacoes-acesso/+page.svelte | 1440 +++--- .../routes/(dashboard)/ti/times/+page.svelte | 8 +- .../(dashboard)/ti/usuarios/+page.svelte | 2653 +++++------ .../ti/usuarios/criar/+page.svelte | 1048 ++-- 39 files changed, 15729 insertions(+), 15486 deletions(-) diff --git a/apps/web/src/lib/components/AprovarAusencias.svelte b/apps/web/src/lib/components/AprovarAusencias.svelte index 49b6072..7dc5b55 100644 --- a/apps/web/src/lib/components/AprovarAusencias.svelte +++ b/apps/web/src/lib/components/AprovarAusencias.svelte @@ -1,426 +1,414 @@
- -
-

- Aprovar/Reprovar Ausência -

-

Analise a solicitação e tome uma decisão

-
+ +
+

Aprovar/Reprovar Ausência

+

Analise a solicitação e tome uma decisão

+
- -
-
- -
-

- - - - Funcionário -

-
-
-

Nome

-

- {solicitacao.funcionario?.nome || "N/A"} -

-
- {#if solicitacao.time} -
-

Time

-
- {solicitacao.time.nome} -
-
- {/if} -
-
+ +
+
+ +
+

+ + + + Funcionário +

+
+
+

Nome

+

+ {solicitacao.funcionario?.nome || 'N/A'} +

+
+ {#if solicitacao.time} +
+

Time

+
+ {solicitacao.time.nome} +
+
+ {/if} +
+
-
+
- -
-

- - - - Período da Ausência -

-
-
-
Data Início
-
- {new Date(solicitacao.dataInicio).toLocaleDateString("pt-BR")} -
-
-
-
Data Fim
-
- {new Date(solicitacao.dataFim).toLocaleDateString("pt-BR")} -
-
-
-
Total de Dias
-
- {totalDias} -
-
dias corridos
-
-
-
+ +
+

+ + + + Período da Ausência +

+
+
+
Data Início
+
+ {new Date(solicitacao.dataInicio).toLocaleDateString('pt-BR')} +
+
+
+
Data Fim
+
+ {new Date(solicitacao.dataFim).toLocaleDateString('pt-BR')} +
+
+
+
Total de Dias
+
+ {totalDias} +
+
dias corridos
+
+
+
-
+
- -
-

- - - - Motivo da Ausência -

-
-
-

{solicitacao.motivo}

-
-
-
+ +
+

+ + + + Motivo da Ausência +

+
+
+

{solicitacao.motivo}

+
+
+
- -
-
- Status: -
- {getStatusTexto(solicitacao.status)} -
-
-
+ +
+
+ Status: +
+ {getStatusTexto(solicitacao.status)} +
+
+
- - {#if erro} -
- - - - {erro} -
- {/if} + + {#if erro} +
+ + + + {erro} +
+ {/if} - - {#if solicitacao.status === "aguardando_aprovacao"} -
- - -
+ + {#if solicitacao.status === 'aguardando_aprovacao'} +
+ + +
- - {#if motivoReprovacao !== undefined} -
-
- - -
-
- {/if} - {:else} -
- - - - Esta solicitação já foi processada. -
- {/if} + + {#if motivoReprovacao !== undefined} +
+
+ + +
+
+ {/if} + {:else} +
+ + + + Esta solicitação já foi processada. +
+ {/if} - -
- -
-
-
+ +
+ +
+
+
diff --git a/apps/web/src/lib/components/AprovarFerias.svelte b/apps/web/src/lib/components/AprovarFerias.svelte index f15afe9..7dc924e 100644 --- a/apps/web/src/lib/components/AprovarFerias.svelte +++ b/apps/web/src/lib/components/AprovarFerias.svelte @@ -1,384 +1,462 @@
-
-
-
-

- {solicitacao.funcionario?.nome || "Funcionário"} -

-

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

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

Períodos Solicitados

-
- {#each solicitacao.periodos as periodo, 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} -
- - - - {formatarData(hist.data)} - - - {hist.acao} -
- {/each} -
-
- {/if} - - - {#if solicitacao.status === "aguardando_aprovacao"} -
- - {#if !modoAjuste} - -
-
- - - -
- - -
-
-

Reprovar Solicitação

- - -
-
-
- {:else} - -
-

Ajustar Períodos

- {#each periodos as periodo, index} -
-
-
Período {index + 1}
-
-
- - calcularDias(periodo)} - /> -
-
- - calcularDias(periodo)} - /> -
-
- -
- {periodo.diasCorridos} -
-
-
-
-
- {/each} - -
- - -
-
- {/if} - {/if} - - - {#if solicitacao.status === "reprovado" && solicitacao.motivoReprovacao} -
- - - -
-
Motivo da Reprovação:
-
{solicitacao.motivoReprovacao}
-
-
- {/if} - - - {#if erro} -
- - - - {erro} -
- {/if} - - - {#if onCancelar} -
- -
- {/if} -
-
+
+
+
+

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

+

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

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

Períodos Solicitados

+
+ {#each solicitacao.periodos as periodo, 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} +
+ + + + {formatarData(hist.data)} + - + {hist.acao} +
+ {/each} +
+
+ {/if} + + + {#if solicitacao.status === 'aguardando_aprovacao'} +
+ + {#if !modoAjuste} + +
+
+ + + +
+ + +
+
+

Reprovar Solicitação

+ + +
+
+
+ {:else} + +
+

Ajustar Períodos

+ {#each periodos as periodo, index} +
+
+
Período {index + 1}
+
+
+ + calcularDias(periodo)} + /> +
+
+ + calcularDias(periodo)} + /> +
+
+ +
+ {periodo.diasCorridos} +
+
+
+
+
+ {/each} + +
+ + +
+
+ {/if} + {/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/ErrorModal.svelte b/apps/web/src/lib/components/ErrorModal.svelte index ba035d8..5ccc653 100644 --- a/apps/web/src/lib/components/ErrorModal.svelte +++ b/apps/web/src/lib/components/ErrorModal.svelte @@ -1,82 +1,73 @@ {#if open} - e.target === e.currentTarget && handleClose()} - > - - - + + {/if} - diff --git a/apps/web/src/lib/components/FuncionarioSelect.svelte b/apps/web/src/lib/components/FuncionarioSelect.svelte index 544dcd4..7355f18 100644 --- a/apps/web/src/lib/components/FuncionarioSelect.svelte +++ b/apps/web/src/lib/components/FuncionarioSelect.svelte @@ -1,189 +1,187 @@ -
- +
+ -
- +
+ - {#if value} - - {:else} -
- - - -
- {/if} + {#if value} + + {:else} +
+ + + +
+ {/if} - {#if mostrarDropdown && funcionariosFiltrados.length > 0} -
- {#each funcionariosFiltrados as funcionario} - - {/each} -
- {/if} + {#if mostrarDropdown && funcionariosFiltrados.length > 0} +
+ {#each funcionariosFiltrados as funcionario} + + {/each} +
+ {/if} - {#if mostrarDropdown && busca && funcionariosFiltrados.length === 0} -
- Nenhum funcionário encontrado -
- {/if} -
+ {#if mostrarDropdown && busca && funcionariosFiltrados.length === 0} +
+ Nenhum funcionário encontrado +
+ {/if} +
- {#if funcionarioSelecionado} -
- Selecionado: {funcionarioSelecionado.nome} - {#if funcionarioSelecionado.matricula} - - {funcionarioSelecionado.matricula} - {/if} -
- {/if} + {#if funcionarioSelecionado} +
+ Selecionado: {funcionarioSelecionado.nome} + {#if funcionarioSelecionado.matricula} + - {funcionarioSelecionado.matricula} + {/if} +
+ {/if}
diff --git a/apps/web/src/lib/components/PrintModal.svelte b/apps/web/src/lib/components/PrintModal.svelte index 0811111..26ade77 100644 --- a/apps/web/src/lib/components/PrintModal.svelte +++ b/apps/web/src/lib/components/PrintModal.svelte @@ -1,510 +1,584 @@ - - + - diff --git a/apps/web/src/lib/components/SolicitarFerias.svelte b/apps/web/src/lib/components/SolicitarFerias.svelte index 7658086..4da0ddb 100644 --- a/apps/web/src/lib/components/SolicitarFerias.svelte +++ b/apps/web/src/lib/components/SolicitarFerias.svelte @@ -317,7 +317,7 @@
{#if onCancelar} - {/if} diff --git a/apps/web/src/lib/components/ausencias/WizardSolicitacaoAusencia.svelte b/apps/web/src/lib/components/ausencias/WizardSolicitacaoAusencia.svelte index 5ce13b6..67a8b81 100644 --- a/apps/web/src/lib/components/ausencias/WizardSolicitacaoAusencia.svelte +++ b/apps/web/src/lib/components/ausencias/WizardSolicitacaoAusencia.svelte @@ -1,503 +1,486 @@
- -
-

- Solicite uma ausência para assuntos particulares -

-
+ +
+

Solicite uma ausência para assuntos particulares

+
- -
-
-
-
- {#if passoAtual > 1} - - - - {:else} - {passoAtual} - {/if} -
-
-
Selecionar Período
-
Escolha as datas no calendário
-
-
-
-
-
-
- {#if passoAtual > 2} - - - - {:else} - 2 - {/if} -
-
-
Informar Motivo
-
Descreva o motivo da ausência
-
-
-
-
+ +
+
+
+
+ {#if passoAtual > 1} + + + + {:else} + {passoAtual} + {/if} +
+
+
Selecionar Período
+
Escolha as datas no calendário
+
+
+
+
+
+
+ {#if passoAtual > 2} + + + + {:else} + 2 + {/if} +
+
+
Informar Motivo
+
Descreva o motivo da ausência
+
+
+
+
- -
-
- {#if passoAtual === 1} - -
-
-

Selecione o Período

-

- Clique e arraste no calendário para selecionar o período de - ausência -

-
+ +
+
+ {#if passoAtual === 1} + +
+
+

Selecione o Período

+

+ Clique e arraste no calendário para selecionar o período de ausência +

+
- {#if ausenciasExistentesQuery === undefined} -
- - Carregando ausências existentes... -
- {:else} - - {/if} + {#if ausenciasExistentesQuery === undefined} +
+ + Carregando ausências existentes... +
+ {:else} + + {/if} - {#if dataInicio && dataFim} -
- - - -
-

Período selecionado!

-

- De {new Date(dataInicio).toLocaleDateString("pt-BR")} até{" "} - {new Date(dataFim).toLocaleDateString("pt-BR")} ({totalDias} dias) -

-
-
- {/if} -
- {:else if passoAtual === 2} - -
-
-

Informe o Motivo

-

- Descreva o motivo da sua solicitação de ausência (mínimo 10 - caracteres) -

-
+ {#if dataInicio && dataFim} +
+ + + +
+

Período selecionado!

+

+ De {new Date(dataInicio).toLocaleDateString('pt-BR')} até{' '} + {new Date(dataFim).toLocaleDateString('pt-BR')} ({totalDias} dias) +

+
+
+ {/if} +
+ {:else if passoAtual === 2} + +
+
+

Informe o Motivo

+

+ Descreva o motivo da sua solicitação de ausência (mínimo 10 caracteres) +

+
- - {#if dataInicio && dataFim} -
-
-

- - - - Resumo do Período -

-
-
-

Data Início

-

- {new Date(dataInicio).toLocaleDateString("pt-BR")} -

-
-
-

Data Fim

-

- {new Date(dataFim).toLocaleDateString("pt-BR")} -

-
-
-

Total de Dias

-

- {totalDias} dias -

-
-
-
-
- {/if} + + {#if dataInicio && dataFim} +
+
+

+ + + + Resumo do Período +

+
+
+

Data Início

+

+ {new Date(dataInicio).toLocaleDateString('pt-BR')} +

+
+
+

Data Fim

+

+ {new Date(dataFim).toLocaleDateString('pt-BR')} +

+
+
+

Total de Dias

+

+ {totalDias} dias +

+
+
+
+
+ {/if} - -
- - - -
+ +
+ + + +
- {#if motivo.trim().length > 0 && motivo.trim().length < 10} -
- - - - O motivo deve ter no mínimo 10 caracteres -
- {/if} -
- {/if} + {#if motivo.trim().length > 0 && motivo.trim().length < 10} +
+ + + + O motivo deve ter no mínimo 10 caracteres +
+ {/if} +
+ {/if} - -
- + +
+ - {#if passoAtual < totalPassos} - - {:else} - - {/if} -
+ {#if passoAtual < totalPassos} + + {:else} + + {/if} +
- -
- -
-
-
+ +
+ +
+
+
diff --git a/apps/web/src/lib/components/chat/ChatWindow.svelte b/apps/web/src/lib/components/chat/ChatWindow.svelte index 9cecb9d..f3eb486 100644 --- a/apps/web/src/lib/components/chat/ChatWindow.svelte +++ b/apps/web/src/lib/components/chat/ChatWindow.svelte @@ -1,545 +1,493 @@ -
(showAdminMenu = false)}> - -
e.stopPropagation()} - > - - +
(showAdminMenu = false)}> + +
e.stopPropagation()} + > + + - -
- {#if conversa() && conversa()?.tipo === "individual" && conversa()?.outroUsuario} - - {:else} -
- {getAvatarConversa()} -
- {/if} - {#if getStatusConversa()} -
- -
- {/if} -
+ +
+ {#if conversa() && conversa()?.tipo === 'individual' && conversa()?.outroUsuario} + + {:else} +
+ {getAvatarConversa()} +
+ {/if} + {#if getStatusConversa()} +
+ +
+ {/if} +
-
-

- {getNomeConversa()} -

- {#if getStatusMensagem()} -

- {getStatusMensagem()} -

- {:else if getStatusConversa()} -

- {getStatusConversa() === "online" - ? "Online" - : getStatusConversa() === "ausente" - ? "Ausente" - : getStatusConversa() === "em_reuniao" - ? "Em reunião" - : getStatusConversa() === "externo" - ? "Externo" - : "Offline"} -

- {:else if conversa() && (conversa()?.tipo === "grupo" || conversa()?.tipo === "sala_reuniao")} -
-

- {conversa()?.participantesInfo?.length || 0} - {conversa()?.participantesInfo?.length === 1 - ? "participante" - : "participantes"} -

- {#if conversa()?.participantesInfo && conversa()?.participantesInfo.length > 0} -
-
- {#each conversa()?.participantesInfo.slice(0, 5) as participante (participante._id)} -
- {#if participante.fotoPerfilUrl} - {participante.nome} - {:else if participante.avatar} - {participante.nome} - {:else} - {participante.nome} - {/if} -
- {/each} - {#if conversa()?.participantesInfo.length > 5} -
- +{conversa()?.participantesInfo.length - 5} -
- {/if} -
- {#if conversa()?.tipo === "sala_reuniao" && isAdmin?.data} - • Admin - {/if} -
- {/if} -
- {/if} -
+
+

+ {getNomeConversa()} +

+ {#if getStatusMensagem()} +

+ {getStatusMensagem()} +

+ {:else if getStatusConversa()} +

+ {getStatusConversa() === 'online' + ? 'Online' + : getStatusConversa() === 'ausente' + ? 'Ausente' + : getStatusConversa() === 'em_reuniao' + ? 'Em reunião' + : getStatusConversa() === 'externo' + ? 'Externo' + : 'Offline'} +

+ {:else if conversa() && (conversa()?.tipo === 'grupo' || conversa()?.tipo === 'sala_reuniao')} +
+

+ {conversa()?.participantesInfo?.length || 0} + {conversa()?.participantesInfo?.length === 1 ? 'participante' : 'participantes'} +

+ {#if conversa()?.participantesInfo && conversa()?.participantesInfo.length > 0} +
+
+ {#each conversa()?.participantesInfo.slice(0, 5) as participante (participante._id)} +
+ {#if participante.fotoPerfilUrl} + {participante.nome} + {:else if participante.avatar} + {participante.nome} + {:else} + {participante.nome} + {/if} +
+ {/each} + {#if conversa()?.participantesInfo.length > 5} +
+ +{conversa()?.participantesInfo.length - 5} +
+ {/if} +
+ {#if conversa()?.tipo === 'sala_reuniao' && isAdmin?.data} + • Admin + {/if} +
+ {/if} +
+ {/if} +
- -
- - {#if conversa() && (conversa()?.tipo === "grupo" || conversa()?.tipo === "sala_reuniao")} - - {/if} + +
+ + {#if conversa() && (conversa()?.tipo === 'grupo' || conversa()?.tipo === 'sala_reuniao')} + + {/if} - - {#if conversa()?.tipo === "sala_reuniao" && isAdmin?.data} -
- - {#if showAdminMenu} -
    e.stopPropagation()} - > -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- {/if} -
- {/if} + + {#if conversa()?.tipo === 'sala_reuniao' && isAdmin?.data} +
+ + {#if showAdminMenu} +
    e.stopPropagation()} + > +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+ {/if} +
+ {/if} - - -
-
+ + +
+
- -
- } /> -
+ +
+ } /> +
- -
- } /> -
+ +
+ } /> +
{#if showScheduleModal} - } - onClose={() => (showScheduleModal = false)} - /> + } + onClose={() => (showScheduleModal = false)} + /> {/if} -{#if showSalaManager && conversa()?.tipo === "sala_reuniao"} - } - isAdmin={isAdmin?.data ?? false} - onClose={() => (showSalaManager = false)} - /> +{#if showSalaManager && conversa()?.tipo === 'sala_reuniao'} + } + isAdmin={isAdmin?.data ?? false} + onClose={() => (showSalaManager = false)} + /> {/if} -{#if showNotificacaoModal && conversa()?.tipo === "sala_reuniao" && isAdmin?.data} - - e.target === e.currentTarget && (showNotificacaoModal = false)} - > - + + {/if} diff --git a/apps/web/src/lib/components/chat/NewConversationModal.svelte b/apps/web/src/lib/components/chat/NewConversationModal.svelte index 3489113..3da3013 100644 --- a/apps/web/src/lib/components/chat/NewConversationModal.svelte +++ b/apps/web/src/lib/components/chat/NewConversationModal.svelte @@ -1,454 +1,422 @@ - e.target === e.currentTarget && onClose()} -> - - + + {#if activeTab === 'grupo'} +
+ + {#if selectedUsers.length < 2 && activeTab === 'grupo'} +

+ Selecione pelo menos 2 participantes +

+ {/if} +
+ {:else if activeTab === 'sala_reuniao'} +
+ + {#if selectedUsers.length < 1 && activeTab === 'sala_reuniao'} +

+ Selecione pelo menos 1 participante +

+ {/if} +
+ {/if} +
+ diff --git a/apps/web/src/lib/components/chat/SalaReuniaoManager.svelte b/apps/web/src/lib/components/chat/SalaReuniaoManager.svelte index 32eb20e..34a1bc5 100644 --- a/apps/web/src/lib/components/chat/SalaReuniaoManager.svelte +++ b/apps/web/src/lib/components/chat/SalaReuniaoManager.svelte @@ -212,12 +212,7 @@ {conversa()?.nome || 'Sem nome'}

-
diff --git a/apps/web/src/lib/components/chat/ScheduleMessageModal.svelte b/apps/web/src/lib/components/chat/ScheduleMessageModal.svelte index 0ceddd2..8a9363b 100644 --- a/apps/web/src/lib/components/chat/ScheduleMessageModal.svelte +++ b/apps/web/src/lib/components/chat/ScheduleMessageModal.svelte @@ -1,288 +1,269 @@ - e.target === e.currentTarget && onClose()} -> - + + diff --git a/apps/web/src/lib/components/ti/AlertConfigModal.svelte b/apps/web/src/lib/components/ti/AlertConfigModal.svelte index 0551e90..5a3a4e8 100644 --- a/apps/web/src/lib/components/ti/AlertConfigModal.svelte +++ b/apps/web/src/lib/components/ti/AlertConfigModal.svelte @@ -1,502 +1,479 @@ - + + + diff --git a/apps/web/src/routes/(dashboard)/alterar-senha/+page.svelte b/apps/web/src/routes/(dashboard)/alterar-senha/+page.svelte index fde48a7..11ca82d 100644 --- a/apps/web/src/routes/(dashboard)/alterar-senha/+page.svelte +++ b/apps/web/src/routes/(dashboard)/alterar-senha/+page.svelte @@ -1,512 +1,495 @@ -
- -
-
- - - -

Alterar Senha

-
-

- Atualize sua senha de acesso ao sistema -

-
+
+ +
+
+ + + +

Alterar Senha

+
+

Atualize sua senha de acesso ao sistema

+
- - + + - - {#if notice} -
- - {#if notice.type === "success"} - - {:else} - - {/if} - - {notice.message} -
- {/if} + + {#if notice} +
+ + {#if notice.type === 'success'} + + {:else} + + {/if} + + {notice.message} +
+ {/if} - -
-
-
- -
- -
- - -
-
+ +
+
+ + +
+ +
+ + +
+
- -
- -
- - -
-
- - Mínimo 8 caracteres, com letras maiúsculas, minúsculas, números e - caracteres especiais - -
-
+ +
+ +
+ + +
+
+ + Mínimo 8 caracteres, com letras maiúsculas, minúsculas, números e caracteres especiais + +
+
- -
- -
- - -
-
+ +
+ +
+ + +
+
- -
- - - -
-

Requisitos de Senha:

-
    -
  • Mínimo de 8 caracteres
  • -
  • Pelo menos uma letra maiúscula (A-Z)
  • -
  • Pelo menos uma letra minúscula (a-z)
  • -
  • Pelo menos um número (0-9)
  • -
  • Pelo menos um caractere especial (!@#$%^&*...)
  • -
-
-
+ +
+ + + +
+

Requisitos de Senha:

+
    +
  • Mínimo de 8 caracteres
  • +
  • Pelo menos uma letra maiúscula (A-Z)
  • +
  • Pelo menos uma letra minúscula (a-z)
  • +
  • Pelo menos um número (0-9)
  • +
  • Pelo menos um caractere especial (!@#$%^&*...)
  • +
+
+
- -
- - -
- -
-
+ +
+ + +
+ +
+
- -
-
-

- - - - Dicas de Segurança -

-
    -
  • ✅ Nunca compartilhe sua senha com ninguém
  • -
  • ✅ Use uma senha única para cada sistema
  • -
  • ✅ Altere sua senha regularmente
  • -
  • - ✅ Não use informações pessoais óbvias (nome, data de nascimento, - etc.) -
  • -
  • ✅ Considere usar um gerenciador de senhas
  • -
-
-
+ +
+
+

+ + + + Dicas de Segurança +

+
    +
  • ✅ Nunca compartilhe sua senha com ninguém
  • +
  • ✅ Use uma senha única para cada sistema
  • +
  • ✅ Altere sua senha regularmente
  • +
  • ✅ Não use informações pessoais óbvias (nome, data de nascimento, etc.)
  • +
  • ✅ Considere usar um gerenciador de senhas
  • +
+
+
diff --git a/apps/web/src/routes/(dashboard)/esqueci-senha/+page.svelte b/apps/web/src/routes/(dashboard)/esqueci-senha/+page.svelte index 2cedee5..9d85216 100644 --- a/apps/web/src/routes/(dashboard)/esqueci-senha/+page.svelte +++ b/apps/web/src/routes/(dashboard)/esqueci-senha/+page.svelte @@ -1,264 +1,363 @@ -
- -
-
- - - -

Esqueci Minha Senha

-
-

- Solicite a recuperação da sua senha de acesso -

-
+
+ +
+
+ + + +

Esqueci Minha Senha

+
+

Solicite a recuperação da sua senha de acesso

+
- - + + - - {#if notice} -
- - {#if notice.type === "success"} - - {:else if notice.type === "error"} - - {:else} - - {/if} - - {notice.message} -
- {/if} + + {#if notice} +
+ + {#if notice.type === 'success'} + + {:else if notice.type === 'error'} + + {:else} + + {/if} + + {notice.message} +
+ {/if} - {#if !solicitacaoEnviada} - -
-
-
- - - -
-

Como funciona?

-

- Informe sua matrícula e e-mail cadastrados. A equipe de TI receberá sua solicitação e entrará em contato para resetar sua senha. -

-
-
+ {#if !solicitacaoEnviada} + +
+
+
+ + + +
+

Como funciona?

+

+ Informe sua matrícula e e-mail cadastrados. A equipe de TI receberá sua solicitação e + entrará em contato para resetar sua senha. +

+
+
-
- -
- - -
+ + +
+ + +
- -
- - - -
+ +
+ + + +
- -
- - - - - Voltar - - -
-
-
-
- {:else} - -
-
-
- - - -
-

Solicitação Enviada!

-

- Sua solicitação de recuperação de senha foi enviada para a equipe de TI. - Você receberá um contato em breve com as instruções para resetar sua senha. -

-
- - - - - Voltar ao Dashboard - - -
-
-
- {/if} + +
+ + + + + Voltar + + +
+ +
+
+ {:else} + +
+
+
+ + + +
+

Solicitação Enviada!

+

+ Sua solicitação de recuperação de senha foi enviada para a equipe de TI. Você receberá um + contato em breve com as instruções para resetar sua senha. +

+
+ + + + + Voltar ao Dashboard + + +
+
+
+ {/if} - -
-
-

- - - - Precisa de Ajuda? -

-

- Se você não conseguir recuperar sua senha ou tiver problemas com o sistema, entre em contato diretamente com a equipe de TI: -

-
-
- - - - ti@sgse.pe.gov.br -
-
- - - - (81) 3183-8000 -
-
-
-
+ +
+
+

+ + + + Precisa de Ajuda? +

+

+ Se você não conseguir recuperar sua senha ou tiver problemas com o sistema, entre em contato + diretamente com a equipe de TI: +

+
+
+ + + + ti@sgse.pe.gov.br +
+
+ + + + (81) 3183-8000 +
+
+
+
- diff --git a/apps/web/src/routes/(dashboard)/gestao-pessoas/gestao-ausencias/+page.svelte b/apps/web/src/routes/(dashboard)/gestao-pessoas/gestao-ausencias/+page.svelte index 9f54d61..774da20 100644 --- a/apps/web/src/routes/(dashboard)/gestao-pessoas/gestao-ausencias/+page.svelte +++ b/apps/web/src/routes/(dashboard)/gestao-pessoas/gestao-ausencias/+page.svelte @@ -1,419 +1,407 @@ -
- - +
+ + - -
-
-
-
- - - -
-
-

Gestão de Ausências

-

- Visão geral de todas as solicitações de ausências -

-
-
- -
-
+ +
+
+
+
+ + + +
+
+

Gestão de Ausências

+

Visão geral de todas as solicitações de ausências

+
+
+ +
+
- -
-
-
- - - -
-
Total
-
{stats.total}
-
Solicitações
-
+ +
+
+
+ + + +
+
Total
+
{stats.total}
+
Solicitações
+
-
-
- - - -
-
Pendentes
-
{stats.aguardando}
-
Aguardando
-
+
+
+ + + +
+
Pendentes
+
{stats.aguardando}
+
Aguardando
+
-
-
- - - -
-
Aprovadas
-
{stats.aprovadas}
-
Deferidas
-
+
+
+ + + +
+
Aprovadas
+
{stats.aprovadas}
+
Deferidas
+
-
-
- - - -
-
Reprovadas
-
{stats.reprovadas}
-
Indeferidas
-
-
+
+
+ + + +
+
Reprovadas
+
{stats.reprovadas}
+
Indeferidas
+
+
- -
-
-

Filtros

-
-
- - -
-
-
-
+ +
+
+

Filtros

+
+
+ + +
+
+
+
- -
-
-

- Todas as Solicitações ({ausenciasFiltradas.length}) -

+ +
+
+

+ Todas as Solicitações ({ausenciasFiltradas.length}) +

- {#if ausenciasFiltradas.length === 0} -
- - - - Nenhuma solicitação encontrada com os filtros aplicados. -
- {:else} -
- - - - - - - - - - - - - - - {#each ausenciasFiltradas as ausencia} - - - - - - - - - - - {/each} - -
FuncionárioTimePeríodoDiasMotivoStatusSolicitado emAções
- {ausencia.funcionario?.nome || "N/A"} - - {#if ausencia.time} -
- {ausencia.time.nome} -
- {:else} - Sem time - {/if} -
- {new Date(ausencia.dataInicio).toLocaleDateString("pt-BR")} até{" "} - {new Date(ausencia.dataFim).toLocaleDateString("pt-BR")} - - {calcularDias(ausencia.dataInicio, ausencia.dataFim)} dias - - {ausencia.motivo} - -
- {getStatusTexto(ausencia.status)} -
-
- {new Date(ausencia.criadoEm).toLocaleDateString("pt-BR")} - - {#if ausencia.status === "aguardando_aprovacao"} - - {:else} - - {/if} -
-
- {/if} -
-
+ {#if ausenciasFiltradas.length === 0} +
+ + + + Nenhuma solicitação encontrada com os filtros aplicados. +
+ {:else} +
+ + + + + + + + + + + + + + + {#each ausenciasFiltradas as ausencia} + + + + + + + + + + + {/each} + +
FuncionárioTimePeríodoDiasMotivoStatusSolicitado emAções
+ {ausencia.funcionario?.nome || 'N/A'} + + {#if ausencia.time} +
+ {ausencia.time.nome} +
+ {:else} + Sem time + {/if} +
+ {new Date(ausencia.dataInicio).toLocaleDateString('pt-BR')} até{' '} + {new Date(ausencia.dataFim).toLocaleDateString('pt-BR')} + + {calcularDias(ausencia.dataInicio, ausencia.dataFim)} dias + + {ausencia.motivo} + +
+ {getStatusTexto(ausencia.status)} +
+
+ {new Date(ausencia.criadoEm).toLocaleDateString('pt-BR')} + + {#if ausencia.status === 'aguardando_aprovacao'} + + {:else} + + {/if} +
+
+ {/if} +
+
{#if solicitacaoSelecionada && currentUser.data} - {#await client.query(api.ausencias.obterDetalhes, { - solicitacaoId: solicitacaoSelecionada, - }) then detalhes} - {#if detalhes} - - - - - {/if} - {/await} + {#await client.query( api.ausencias.obterDetalhes, { solicitacaoId: solicitacaoSelecionada } ) then detalhes} + {#if detalhes} + + + + + {/if} + {/await} {/if} - diff --git a/apps/web/src/routes/(dashboard)/perfil/+page.svelte b/apps/web/src/routes/(dashboard)/perfil/+page.svelte index 58831bf..3da2728 100644 --- a/apps/web/src/routes/(dashboard)/perfil/+page.svelte +++ b/apps/web/src/routes/(dashboard)/perfil/+page.svelte @@ -1748,7 +1748,7 @@ {:else} - - + +
+
+
+
+ + + +
+
+

Atestados & Licenças

+

Registro de atestados médicos e licenças

+
+
+ +
+
- -
- - - - - -
+ +
+ + + + + +
- - {#if abaAtiva === "dashboard"} - - - {#if statsQuery?.data} -
-
-
- - - -
-
Atestados Ativos
-
- {statsQuery.data.totalAtestadosAtivos} -
-
+ + {#if abaAtiva === 'dashboard'} + + + {#if statsQuery?.data} +
+
+
+ + + +
+
Atestados Ativos
+
+ {statsQuery.data.totalAtestadosAtivos} +
+
-
-
- - - -
-
Licenças Ativas
-
- {statsQuery.data.totalLicencasAtivas} -
-
+
+
+ + + +
+
Licenças Ativas
+
+ {statsQuery.data.totalLicencasAtivas} +
+
-
-
- - - -
-
Afastados Hoje
-
- {statsQuery.data.funcionariosAfastadosHoje} -
-
+
+
+ + + +
+
Afastados Hoje
+
+ {statsQuery.data.funcionariosAfastadosHoje} +
+
-
-
- - - -
-
Dias no Mês
-
- {statsQuery.data.totalDiasAfastamentoMes} -
-
-
- {/if} +
+
+ + + +
+
Dias no Mês
+
+ {statsQuery.data.totalDiasAfastamentoMes} +
+
+
+ {/if} - -
-
-

Filtros

-
-
- -
+ +
+
+

Filtros

+
+
+ +
-
- -
+
+ +
-
- -
+
+ +
-
- -
+
+ +
-
- -
-
-
-
+
+ +
+
+
+
- - {#if eventosQuery?.data} - - {/if} + + {#if eventosQuery?.data} + + {/if} - - {#if graficosQuery?.data} - {@const dados = graficosQuery.data.totalDiasPorTipo} - {@const maxDias = Math.max(...dados.map((d) => d.dias), 1)} - {@const chartWidth = 800} - {@const chartHeight = 350} - {@const padding = { top: 20, right: 40, bottom: 80, left: 70 }} - {@const barWidth = (chartWidth - padding.left - padding.right) / dados.length - 10} - {@const innerHeight = chartHeight - padding.top - padding.bottom} - {@const tendencias = graficosQuery.data.tendenciasMensais} - {@const tipos = ["atestado_medico", "declaracao_comparecimento", "maternidade", "paternidade", "ferias"]} - {@const cores = ["#ef4444", "#f97316", "#ec4899", "#3b82f6", "#10b981"]} - {@const nomes = ["Atestado Médico", "Declaração", "Maternidade", "Paternidade", "Férias"]} - {@const maxValor = Math.max( - ...tendencias.flatMap((t) => - tipos.map((tipo) => t[tipo as keyof typeof t] as number) - ), - 1 - )} - {@const chartWidth2 = 900} - {@const chartHeight2 = 400} - {@const padding2 = { top: 20, right: 40, bottom: 80, left: 70 }} - {@const innerWidth = chartWidth2 - padding2.left - padding2.right} - {@const innerHeight2 = chartHeight2 - padding2.top - padding2.bottom} - -
-
-

Total de Dias por Tipo

-
+ + {#if graficosQuery?.data} + {@const dados = graficosQuery.data.totalDiasPorTipo} + {@const maxDias = Math.max(...dados.map((d) => d.dias), 1)} + {@const chartWidth = 800} + {@const chartHeight = 350} + {@const padding = { top: 20, right: 40, bottom: 80, left: 70 }} + {@const barWidth = (chartWidth - padding.left - padding.right) / dados.length - 10} + {@const innerHeight = chartHeight - padding.top - padding.bottom} + {@const tendencias = graficosQuery.data.tendenciasMensais} + {@const tipos = [ + 'atestado_medico', + 'declaracao_comparecimento', + 'maternidade', + 'paternidade', + 'ferias' + ]} + {@const cores = ['#ef4444', '#f97316', '#ec4899', '#3b82f6', '#10b981']} + {@const nomes = ['Atestado Médico', 'Declaração', 'Maternidade', 'Paternidade', 'Férias']} + {@const maxValor = Math.max( + ...tendencias.flatMap((t) => tipos.map((tipo) => t[tipo as keyof typeof t] as number)), + 1 + )} + {@const chartWidth2 = 900} + {@const chartHeight2 = 400} + {@const padding2 = { top: 20, right: 40, bottom: 80, left: 70 }} + {@const innerWidth = chartWidth2 - padding2.left - padding2.right} + {@const innerHeight2 = chartHeight2 - padding2.top - padding2.bottom} + +
+
+

Total de Dias por Tipo

+
+ + + {#each [0, 1, 2, 3, 4, 5] as t} + {@const val = Math.round((maxDias / 5) * t)} + {@const y = chartHeight - padding.bottom - (val / maxDias) * innerHeight} + + + {val} + + {/each} - - - {#each [0, 1, 2, 3, 4, 5] as t} - {@const val = Math.round((maxDias / 5) * t)} - {@const y = chartHeight - padding.bottom - (val / maxDias) * innerHeight} - - - {val} - - {/each} + + + - - - + + {#each dados as item, i} + {@const x = padding.left + i * (barWidth + 10) + 5} + {@const height = (item.dias / maxDias) * innerHeight} + {@const y = chartHeight - padding.bottom - height} + {@const colors = ['#ef4444', '#f97316', '#ec4899', '#3b82f6', '#10b981']} - - {#each dados as item, i} - {@const x = padding.left + i * (barWidth + 10) + 5} - {@const height = (item.dias / maxDias) * innerHeight} - {@const y = chartHeight - padding.bottom - height} - {@const colors = ["#ef4444", "#f97316", "#ec4899", "#3b82f6", "#10b981"]} + + + + + + + - - - - - - - + + - - + + {#if item.dias > 0} + + {item.dias} + + {/if} - - {#if item.dias > 0} - - {item.dias} - - {/if} + + +
+ + {item.tipo} + +
+
+ {/each} +
+
+
+
- - -
- - {item.tipo} - -
-
- {/each} - -
-
-
+ +
+
+

Tendências Mensais (Últimos 6 Meses)

+
+ + + {#each [0, 1, 2, 3, 4, 5] as t} + {@const val = Math.round((maxValor / 5) * t)} + {@const y = chartHeight2 - padding2.bottom - (val / maxValor) * innerHeight2} + + + {val} + + {/each} - -
-
-

Tendências Mensais (Últimos 6 Meses)

-
- - - {#each [0, 1, 2, 3, 4, 5] as t} - {@const val = Math.round((maxValor / 5) * t)} - {@const y = chartHeight2 - padding2.bottom - (val / maxValor) * innerHeight2} - - - {val} - - {/each} + + + - - - + + {#each tipos as tipo, tipoIdx} + {@const cor = cores[tipoIdx]} - - {#each tipos as tipo, tipoIdx} - {@const cor = cores[tipoIdx]} - - - - - - - - + + + + + + + - {@const pontos = tendencias.map((t, i) => { - const x = padding2.left + (i / (tendencias.length - 1 || 1)) * innerWidth; - const valor = t[tipo as keyof typeof t] as number; - const y = chartHeight2 - padding2.bottom - (valor / maxValor) * innerHeight2; - return { x, y, valor }; - })} + {@const pontos = tendencias.map((t, i) => { + const x = padding2.left + (i / (tendencias.length - 1 || 1)) * innerWidth; + const valor = t[tipo as keyof typeof t] as number; + const y = chartHeight2 - padding2.bottom - (valor / maxValor) * innerHeight2; + return { x, y, valor }; + })} - - {#if pontos.length > 0} - {@const pathArea = `M ${pontos[0].x} ${chartHeight2 - padding2.bottom} ` + pontos.map(p => `L ${p.x} ${p.y}`).join(' ') + ` L ${pontos[pontos.length - 1].x} ${chartHeight2 - padding2.bottom} Z`} - - {/if} + + {#if pontos.length > 0} + {@const pathArea = + `M ${pontos[0].x} ${chartHeight2 - padding2.bottom} ` + + pontos.map((p) => `L ${p.x} ${p.y}`).join(' ') + + ` L ${pontos[pontos.length - 1].x} ${chartHeight2 - padding2.bottom} Z`} + + {/if} - - {#if pontos.length > 1} - `${p.x},${p.y}`).join(' ')} - fill="none" - stroke={cor} - stroke-width="3" - stroke-linecap="round" - stroke-linejoin="round" - /> - {/if} + + {#if pontos.length > 1} + `${p.x},${p.y}`).join(' ')} + fill="none" + stroke={cor} + stroke-width="3" + stroke-linecap="round" + stroke-linejoin="round" + /> + {/if} - - {#each pontos as ponto, pontoIdx} - - - {nomes[tipoIdx]}: {ponto.valor} dias em {tendencias[pontoIdx]?.mes || ""} - {/each} - {/each} + + {#each pontos as ponto, pontoIdx} + + + {nomes[tipoIdx]}: {ponto.valor} dias em {tendencias[pontoIdx]?.mes || + ''} + {/each} + {/each} - - {#each tendencias as t, i} - {@const x = padding2.left + (i / (tendencias.length - 1 || 1)) * innerWidth} - -
- - {t.mes} - -
-
- {/each} -
+ + {#each tendencias as t, i} + {@const x = padding2.left + (i / (tendencias.length - 1 || 1)) * innerWidth} + +
+ + {t.mes} + +
+
+ {/each} + - -
- {#each tipos as tipo, idx} -
-
- {nomes[idx]} -
- {/each} -
-
-
-
+ +
+ {#each tipos as tipo, idx} +
+
+ {nomes[idx]} +
+ {/each} +
+
+
+
- -
-
-

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} + +
+
+

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"} - -
-
-

Registrar Atestado Médico

+ +
+
+

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'} + +
+
+

Registrar Atestado Médico

-
- +
+ -
-
- Data Início * -
- -
+
+
+ Data Início * +
+ +
-
-
- Data Fim * -
- -
+
+
+ Data Fim * +
+ +
-
-
- CID * -
- -
+
+
+ CID * +
+ +
-
- { - atestadoMedico.documentoId = await handleDocumentoUpload( - file - ); - }} - onRemove={async () => { - atestadoMedico.documentoId = undefined; - }} - /> -
+
+ { + atestadoMedico.documentoId = await handleDocumentoUpload(file); + }} + onRemove={async () => { + atestadoMedico.documentoId = undefined; + }} + /> +
-
-
- Observações -
- -
-
+
+
+ Observações +
+ +
+
-
- - -
-
-
- {:else if abaAtiva === "declaracao"} - -
-
-

Registrar Declaração de Comparecimento

+
+ + +
+
+
+ {:else if abaAtiva === 'declaracao'} + +
+
+

Registrar Declaração de Comparecimento

-
- +
+ -
-
- Data Início * -
- -
+
+
+ Data Início * +
+ +
-
-
- Data Fim * -
- -
+
+
+ Data Fim * +
+ +
-
- { - declaracao.documentoId = await handleDocumentoUpload(file); - }} - onRemove={async () => { - declaracao.documentoId = undefined; - }} - /> -
+
+ { + declaracao.documentoId = await handleDocumentoUpload(file); + }} + onRemove={async () => { + declaracao.documentoId = undefined; + }} + /> +
-
-
- Observações -
- -
-
+
+
+ Observações +
+ +
+
-
- - -
-
-
- {:else if abaAtiva === "maternidade"} - -
-
-

Registrar Licença Maternidade

+
+ + +
+
+
+ {:else if abaAtiva === 'maternidade'} + +
+
+

Registrar Licença Maternidade

-
- +
+ -
-
- Data Início * -
- -
+
+
+ Data Início * +
+ +
-
-
- Data Fim * -
- -
- Calculado automaticamente (120 dias) -
-
+
+
+ Data Fim * +
+ +
+ Calculado automaticamente (120 dias) +
+
-
- -
+
+ +
- {#if licencaMaternidade.ehProrrogacao} -
-
- Licença Original * -
- -
- {/if} + {#if licencaMaternidade.ehProrrogacao} +
+
+ Licença Original * +
+ +
+ {/if} -
- { - licencaMaternidade.documentoId = await handleDocumentoUpload( - file - ); - }} - onRemove={async () => { - licencaMaternidade.documentoId = undefined; - }} - /> -
+
+ { + licencaMaternidade.documentoId = await handleDocumentoUpload(file); + }} + onRemove={async () => { + licencaMaternidade.documentoId = undefined; + }} + /> +
-
-
- Observações -
- -
-
+
+
+ Observações +
+ +
+
-
- - -
-
-
- {:else if abaAtiva === "paternidade"} - -
-
-

Registrar Licença Paternidade

+
+ + +
+
+
+ {:else if abaAtiva === 'paternidade'} + +
+
+

Registrar Licença Paternidade

-
- +
+ -
-
- Data Início * -
- -
+
+
+ Data Início * +
+ +
-
-
- Data Fim * -
- -
- Calculado automaticamente (20 dias) -
-
+
+
+ Data Fim * +
+ +
+ Calculado automaticamente (20 dias) +
+
-
- { - licencaPaternidade.documentoId = await handleDocumentoUpload( - file - ); - }} - onRemove={async () => { - licencaPaternidade.documentoId = undefined; - }} - /> -
+
+ { + licencaPaternidade.documentoId = await handleDocumentoUpload(file); + }} + onRemove={async () => { + licencaPaternidade.documentoId = undefined; + }} + /> +
-
-
- Observações -
- -
-
+
+
+ Observações +
+ +
+
-
- - -
-
-
- {/if} +
+ + +
+
+
+ {/if}
{ - erroModal.aberto = false; - }} + bind:open={erroModal.aberto} + title={erroModal.titulo} + message={erroModal.mensagem} + details={erroModal.detalhes} + onClose={() => { + erroModal.aberto = false; + }} /> - diff --git a/apps/web/src/routes/(dashboard)/recursos-humanos/ausencias/+page.svelte b/apps/web/src/routes/(dashboard)/recursos-humanos/ausencias/+page.svelte index cfd5744..829ff89 100644 --- a/apps/web/src/routes/(dashboard)/recursos-humanos/ausencias/+page.svelte +++ b/apps/web/src/routes/(dashboard)/recursos-humanos/ausencias/+page.svelte @@ -1,426 +1,405 @@ -
- - +
+ + - -
-
-
-
- - - -
-
-

- Dashboard de Ausências -

-

- Visão geral de todas as solicitações de ausências -

-
-
- -
-
+ +
+
+
+
+ + + +
+
+

Dashboard de Ausências

+

Visão geral de todas as solicitações de ausências

+
+
+ +
+
- -
-
-
- - - -
-
Total
-
{stats.total}
-
Solicitações
-
+ +
+
+
+ + + +
+
Total
+
{stats.total}
+
Solicitações
+
-
-
- - - -
-
Pendentes
-
{stats.aguardando}
-
Aguardando
-
+
+
+ + + +
+
Pendentes
+
{stats.aguardando}
+
Aguardando
+
-
-
- - - -
-
Aprovadas
-
{stats.aprovadas}
-
Deferidas
-
+
+
+ + + +
+
Aprovadas
+
{stats.aprovadas}
+
Deferidas
+
-
-
- - - -
-
Reprovadas
-
{stats.reprovadas}
-
Indeferidas
-
-
+
+
+ + + +
+
Reprovadas
+
{stats.reprovadas}
+
Indeferidas
+
+
- -
-
-

Filtros

-
-
- - -
-
-
-
+ +
+
+

Filtros

+
+
+ + +
+
+
+
- -
-
-

- Todas as Solicitações ({ausenciasFiltradas.length}) -

+ +
+
+

+ Todas as Solicitações ({ausenciasFiltradas.length}) +

- {#if ausenciasFiltradas.length === 0} -
- - - - Nenhuma solicitação encontrada com os filtros aplicados. -
- {:else} -
- - - - - - - - - - - - - - - {#each ausenciasFiltradas as ausencia} - - - - - - - - - - - {/each} - -
FuncionárioTimePeríodoDiasMotivoStatusSolicitado emAções
- {ausencia.funcionario?.nome || "N/A"} - - {#if ausencia.time} -
- {ausencia.time.nome} -
- {:else} - Sem time - {/if} -
- {new Date(ausencia.dataInicio).toLocaleDateString("pt-BR")} até{" "} - {new Date(ausencia.dataFim).toLocaleDateString("pt-BR")} - - {calcularDias(ausencia.dataInicio, ausencia.dataFim)} dias - - {ausencia.motivo} - -
- {getStatusTexto(ausencia.status)} -
-
- {new Date(ausencia.criadoEm).toLocaleDateString("pt-BR")} - - {#if ausencia.status === "aguardando_aprovacao"} - - {:else} - - {/if} -
-
- {/if} -
-
+ {#if ausenciasFiltradas.length === 0} +
+ + + + Nenhuma solicitação encontrada com os filtros aplicados. +
+ {:else} +
+ + + + + + + + + + + + + + + {#each ausenciasFiltradas as ausencia} + + + + + + + + + + + {/each} + +
FuncionárioTimePeríodoDiasMotivoStatusSolicitado emAções
+ {ausencia.funcionario?.nome || 'N/A'} + + {#if ausencia.time} +
+ {ausencia.time.nome} +
+ {:else} + Sem time + {/if} +
+ {new Date(ausencia.dataInicio).toLocaleDateString('pt-BR')} até{' '} + {new Date(ausencia.dataFim).toLocaleDateString('pt-BR')} + + {calcularDias(ausencia.dataInicio, ausencia.dataFim)} dias + + {ausencia.motivo} + +
+ {getStatusTexto(ausencia.status)} +
+
+ {new Date(ausencia.criadoEm).toLocaleDateString('pt-BR')} + + {#if ausencia.status === 'aguardando_aprovacao'} + + {:else} + + {/if} +
+
+ {/if} +
+
{#if solicitacaoSelecionada && currentUser.data} - {#await client.query( api.ausencias.obterDetalhes, { solicitacaoId: solicitacaoSelecionada }, ) then detalhes} - {#if detalhes} - - - - - {/if} - {/await} + {#await client.query( api.ausencias.obterDetalhes, { solicitacaoId: solicitacaoSelecionada } ) then detalhes} + {#if detalhes} + + + + + {/if} + {/await} {/if} 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 58a1b33..9c8243c 100644 --- a/apps/web/src/routes/(dashboard)/recursos-humanos/ferias/+page.svelte +++ b/apps/web/src/routes/(dashboard)/recursos-humanos/ferias/+page.svelte @@ -824,7 +824,7 @@

Visão geral de todas as solicitações e funcionários

- - - + +
+
+
+
+ + + +
+
+

Funcionários Cadastrados

+

Gerencie os funcionários da secretaria

+
+
+ +
+
- -
-
-

- - - - Filtros de Pesquisa -

-
-
- - -
-
- - -
-
- - -
-
- - -
-
- {#if filtroNome || filtroCPF || filtroMatricula || filtroTipo} -
- -
- {/if} -
-
+ +
+
+

+ + + + Filtros de Pesquisa +

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ {#if filtroNome || filtroCPF || filtroMatricula || filtroTipo} +
+ +
+ {/if} +
+
- -
-
-
-
- - - - - - - - - - - - - - {#each filtered as f} - - - - - - - - - - {/each} - -
NomeCPFMatrículaTipoCidadeUFAções
{f.nome}{f.cpf}{f.matricula}{f.simboloTipo}{f.cidade}{f.uf} - -
-
-
-
-
+ +
+
+
+
+ + + + + + + + + + + + + + {#each filtered as f} + + + + + + + + + + {/each} + +
NomeCPFMatrículaTipoCidadeUFAções
{f.nome}{f.cpf}{f.matricula}{f.simboloTipo}{f.cidade}{f.uf} + +
+
+
+
+
- -
- Exibindo {filtered.length} de {list.length} funcionário(s) -
+ +
+ Exibindo {filtered.length} de {list.length} funcionário(s) +
- - {#if funcionarioParaImprimir} - funcionarioParaImprimir = null} - /> - {/if} + + {#if funcionarioParaImprimir} + (funcionarioParaImprimir = null)} + /> + {/if}
diff --git a/apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/[funcionarioId]/documentos/+page.svelte b/apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/[funcionarioId]/documentos/+page.svelte index 5e02825..f96c08b 100644 --- a/apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/[funcionarioId]/documentos/+page.svelte +++ b/apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/[funcionarioId]/documentos/+page.svelte @@ -1,277 +1,358 @@ {#if loading} -
- -
+
+ +
{:else if funcionario} -
- - +
+ + - -
-
-
-
- - - -
-
-

Gerenciar Documentos

-

{funcionario.nome} - Matrícula: {funcionario.matricula}

-
-
+ +
+
+
+
+ + + +
+
+

Gerenciar Documentos

+

+ {funcionario.nome} - Matrícula: {funcionario.matricula} +

+
+
- -
-
+ +
+
- -
-
-
-
- - - -
-
Total de Documentos
-
{contarDocumentos().total}
-
-
+ +
+
+
+
+ + + +
+
Total de Documentos
+
{contarDocumentos().total}
+
+
-
-
-
- - - -
-
Documentos Enviados
-
{contarDocumentos().enviados}
-
-
+
+
+
+ + + +
+
Documentos Enviados
+
{contarDocumentos().enviados}
+
+
-
-
-
- - - -
-
Documentos Pendentes
-
{contarDocumentos().pendentes}
-
-
-
+
+
+
+ + + +
+
Documentos Pendentes
+
{contarDocumentos().pendentes}
+
+
+
- -
- -
+ +
+ +
- -
-
-
- - - -
-
-
+ +
+
+
+ + + +
+
+
- - {#each categoriasDocumentos as categoria} - {@const docsCategoria = getDocumentosByCategoria(categoria).filter(doc => { - const temDocumento = !!documentosStorage[doc.campo]; - if (filtro === "enviados") return temDocumento; - if (filtro === "pendentes") return !temDocumento; - return true; - })} - - {#if docsCategoria.length > 0} -
-
-

- {categoria} -
{docsCategoria.length}
-

+ + {#each categoriasDocumentos as categoria} + {@const docsCategoria = getDocumentosByCategoria(categoria).filter((doc) => { + const temDocumento = !!documentosStorage[doc.campo]; + if (filtro === 'enviados') return temDocumento; + if (filtro === 'pendentes') return !temDocumento; + return true; + })} -
- {#each docsCategoria as doc} - handleDocumentoUpload(doc.campo, file)} - onRemove={() => handleDocumentoRemove(doc.campo)} - /> - {/each} -
-
-
- {/if} - {/each} + {#if docsCategoria.length > 0} +
+
+

+ {categoria} +
{docsCategoria.length}
+

- {#if documentosFiltrados().length === 0} -
- - - - Nenhum documento encontrado com o filtro selecionado. -
- {/if} -
+
+ {#each docsCategoria as doc} + handleDocumentoUpload(doc.campo, file)} + onRemove={() => handleDocumentoRemove(doc.campo)} + /> + {/each} +
+ + + {/if} + {/each} + + {#if documentosFiltrados().length === 0} +
+ + + + Nenhum documento encontrado com o filtro selecionado. +
+ {/if} +
{/if} - diff --git a/apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/[funcionarioId]/editar/+page.svelte b/apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/[funcionarioId]/editar/+page.svelte index c51afda..3316c22 100644 --- a/apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/[funcionarioId]/editar/+page.svelte +++ b/apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/[funcionarioId]/editar/+page.svelte @@ -1708,7 +1708,7 @@
- +
+
+ + + +
+
+

Excluir Funcionários

+

Selecione o funcionário que deseja remover do sistema

+
+ +
{#if notice} -
- {#if notice.kind === "success"} - - - - {:else} - - - - {/if} - {notice.text} - -
+
+ {#if notice.kind === 'success'} + + + + {:else} + + + + {/if} + {notice.text} + +
{/if} -
-
-
-

- - - - Filtros de Busca -

- {#if filtro} - - {/if} -
- -
- - -
- - {filtered.length} funcionário{filtered.length !== 1 ? 's' : ''} encontrado{filtered.length !== 1 ? 's' : ''} - -
-
-
+
+
+
+

+ + + + Filtros de Busca +

+ {#if filtro} + + {/if} +
+ +
+ + +
+ + {filtered.length} funcionário{filtered.length !== 1 ? 's' : ''} encontrado{filtered.length !== + 1 + ? 's' + : ''} + +
+
+
-
-

- - - - Lista de Funcionários -

+
+

+ + + + Lista de Funcionários +

- {#if list.length === 0} -
-
- - - -
-

Nenhum funcionário cadastrado

-

Cadastre funcionários para gerenciá-los aqui

-
- {:else if filtered.length === 0} -
-
- - - -
-

Nenhum resultado encontrado

-

Tente ajustar os filtros de busca

- -
- {:else} -
- - - - - - - - - - - {#each filtered as f} - - - - - - - {/each} - -
NomeCPFMatrículaAções
{f.nome}{f.cpf}{f.matricula} - -
-
- {/if} -
+ {#if list.length === 0} +
+
+ + + +
+

Nenhum funcionário cadastrado

+

+ Cadastre funcionários para gerenciá-los aqui +

+
+ {:else if filtered.length === 0} +
+
+ + + +
+

Nenhum resultado encontrado

+

Tente ajustar os filtros de busca

+ +
+ {:else} +
+ + + + + + + + + + + {#each filtered as f} + + + + + + + {/each} + +
NomeCPFMatrículaAções
{f.nome}{f.cpf}{f.matricula} + +
+
+ {/if} +
- - + + - - diff --git a/apps/web/src/routes/(dashboard)/recursos-humanos/simbolos/+page.svelte b/apps/web/src/routes/(dashboard)/recursos-humanos/simbolos/+page.svelte index 3ea6688..76a2f2c 100644 --- a/apps/web/src/routes/(dashboard)/recursos-humanos/simbolos/+page.svelte +++ b/apps/web/src/routes/(dashboard)/recursos-humanos/simbolos/+page.svelte @@ -1,359 +1,440 @@
- - + + - -
-
-
-
- - - -
-
-

Símbolos Cadastrados

-

Gerencie cargos comissionados e funções gratificadas

-
-
- - - - - Novo Símbolo - -
-
+ +
+
+
+
+ + + +
+
+

Símbolos Cadastrados

+

Gerencie cargos comissionados e funções gratificadas

+
+
+ + + + + Novo Símbolo + +
+
- - {#if notice} -
- - {#if notice.kind === "success"} - - {:else} - - {/if} - - {notice.text} -
- {/if} + + {#if notice} +
+ + {#if notice.kind === 'success'} + + {:else} + + {/if} + + {notice.text} +
+ {/if} - -
-
-

- - - - Filtros de Pesquisa -

-
-
- - -
-
- - -
-
- - -
-
- {#if filtroNome || filtroTipo || filtroDescricao} -
- -
- {/if} -
-
+ +
+
+

+ + + + Filtros de Pesquisa +

+
+
+ + +
+
+ + +
+
+ + +
+
+ {#if filtroNome || filtroTipo || filtroDescricao} +
+ +
+ {/if} +
+
- {#if isLoading} -
- -
- {:else} - -
-
-
-
- - - - - - - - - - - - - - {#if filtered.length > 0} - {#each filtered as simbolo} - - - - - - - - - - {/each} - {:else} - - - - {/if} - -
NomeTipoValor ReferênciaValor VencimentoValor TotalDescriçãoAções
{simbolo.nome} - - {getTipoLabel(simbolo.tipo)} - - {simbolo.repValor ? formatMoney(simbolo.repValor) : "—"}{simbolo.vencValor ? formatMoney(simbolo.vencValor) : "—"}{formatMoney(simbolo.valor)}{simbolo.descricao} - -
Nenhum símbolo encontrado com os filtros atuais.
-
-
-
-
- - -
- Exibindo {filtered.length} de {list.length} símbolo(s) -
- {/if} + {#if isLoading} +
+ +
+ {:else} + +
+
+
+
+ + + + + + + + + + + + + + {#if filtered.length > 0} + {#each filtered as simbolo} + + + + + + + + + + {/each} + {:else} + + + + {/if} + +
NomeTipoValor ReferênciaValor VencimentoValor TotalDescriçãoAções
{simbolo.nome} + + {getTipoLabel(simbolo.tipo)} + + {simbolo.repValor ? formatMoney(simbolo.repValor) : '—'}{simbolo.vencValor ? formatMoney(simbolo.vencValor) : '—'}{formatMoney(simbolo.valor)}{simbolo.descricao} + +
Nenhum símbolo encontrado com os filtros atuais.
+
+
+
+
+ + +
+ Exibindo {filtered.length} de {list.length} símbolo(s) +
+ {/if}
- - + + diff --git a/apps/web/src/routes/(dashboard)/recursos-humanos/simbolos/[simboloId]/editar/+page.svelte b/apps/web/src/routes/(dashboard)/recursos-humanos/simbolos/[simboloId]/editar/+page.svelte index 59859f3..1b6960a 100644 --- a/apps/web/src/routes/(dashboard)/recursos-humanos/simbolos/[simboloId]/editar/+page.svelte +++ b/apps/web/src/routes/(dashboard)/recursos-humanos/simbolos/[simboloId]/editar/+page.svelte @@ -1,431 +1,415 @@ {#if simboloQuery.isLoading} -
- -
+
+ +
{:else if !simboloQuery.data} -
- - - - Símbolo não encontrado. -
+
+ + + + Símbolo não encontrado. +
{:else} -
{ - e.preventDefault(); - e.stopPropagation(); - form.handleSubmit(); - }} - > -
-
- {#if notice} -
- {notice.text} -
- {/if} -
-

Editar Símbolo

-

- Atualize os campos abaixo para editar o símbolo. -

-
+ { + e.preventDefault(); + e.stopPropagation(); + form.handleSubmit(); + }} + > +
+
+ {#if notice} +
+ {notice.text} +
+ {/if} +
+

Editar Símbolo

+

Atualize os campos abaixo para editar o símbolo.

+
- - {#snippet children({ name, state, handleChange })} -
- - { - const target = e.target as HTMLInputElement; - const value = target.value; - handleChange(value); - }} - required - aria-required="true" - /> -
- Informe o nome identificador do símbolo. -
-
- {/snippet} -
+ + {#snippet children({ name, state, handleChange })} +
+ + { + const target = e.target as HTMLInputElement; + const value = target.value; + handleChange(value); + }} + required + aria-required="true" + /> +
+ Informe o nome identificador do símbolo. +
+
+ {/snippet} +
- - {#snippet children({ name, state, handleChange })} -
- - { - const target = e.target as HTMLInputElement; - const value = target.value; - handleChange(value); - }} - required - aria-required="true" - /> -
- Descreva brevemente o símbolo. -
-
- {/snippet} -
+ + {#snippet children({ name, state, handleChange })} +
+ + { + const target = e.target as HTMLInputElement; + const value = target.value; + handleChange(value); + }} + required + aria-required="true" + /> +
+ Descreva brevemente o símbolo. +
+
+ {/snippet} +
- (value ? undefined : "Obrigatório"), - }} - > - {#snippet children({ name, state, handleChange })} -
- - -
- {/snippet} -
+ (value ? undefined : 'Obrigatório') + }} + > + {#snippet children({ name, state, handleChange })} +
+ + +
+ {/snippet} +
- {#if tipo === "cargo_comissionado"} -
- - form.getFieldValue("tipo") === "cargo_comissionado" && !value - ? "Obrigatório" - : undefined, - }} - > - {#snippet children({ name, state, handleChange })} -
- - { - const target = e.target as HTMLInputElement; - const formatted = formatCurrencyBR(target.value); - target.value = formatted; - handleChange(formatted); - }} - required - aria-required="true" - /> -
- Valor efetivo de vencimento. -
-
- {/snippet} -
+ {#if tipo === 'cargo_comissionado'} +
+ + form.getFieldValue('tipo') === 'cargo_comissionado' && !value + ? 'Obrigatório' + : undefined + }} + > + {#snippet children({ name, state, handleChange })} +
+ + { + const target = e.target as HTMLInputElement; + const formatted = formatCurrencyBR(target.value); + target.value = formatted; + handleChange(formatted); + }} + required + aria-required="true" + /> +
+ Valor efetivo de vencimento. +
+
+ {/snippet} +
- - form.getFieldValue("tipo") === "cargo_comissionado" && !value - ? "Obrigatório" - : undefined, - }} - > - {#snippet children({ name, state, handleChange })} -
- - { - const target = e.target as HTMLInputElement; - const formatted = formatCurrencyBR(target.value); - target.value = formatted; - handleChange(formatted); - }} - required - aria-required="true" - /> -
- Valor base de referência. -
-
- {/snippet} -
-
- {#if getTotalPreview()} -
- Total previsto: R$ {getTotalPreview()} -
- {/if} - {:else} - - form.getFieldValue("tipo") === "funcao_gratificada" && !value - ? "Obrigatório" - : undefined, - }} - > - {#snippet children({ name, state, handleChange })} -
- - { - const target = e.target as HTMLInputElement; - const formatted = formatCurrencyBR(target.value); - target.value = formatted; - handleChange(formatted); - }} - required - aria-required="true" - /> -
- Informe o valor da função gratificada. -
-
- {/snippet} -
- {/if} + + form.getFieldValue('tipo') === 'cargo_comissionado' && !value + ? 'Obrigatório' + : undefined + }} + > + {#snippet children({ name, state, handleChange })} +
+ + { + const target = e.target as HTMLInputElement; + const formatted = formatCurrencyBR(target.value); + target.value = formatted; + handleChange(formatted); + }} + required + aria-required="true" + /> +
+ Valor base de referência. +
+
+ {/snippet} +
+
+ {#if getTotalPreview()} +
+ Total previsto: R$ {getTotalPreview()} +
+ {/if} + {:else} + + form.getFieldValue('tipo') === 'funcao_gratificada' && !value + ? 'Obrigatório' + : undefined + }} + > + {#snippet children({ name, state, handleChange })} +
+ + { + const target = e.target as HTMLInputElement; + const formatted = formatCurrencyBR(target.value); + target.value = formatted; + handleChange(formatted); + }} + required + aria-required="true" + /> +
+ Informe o valor da função gratificada. +
+
+ {/snippet} +
+ {/if} - ({ - canSubmit: state.canSubmit, - isSubmitting: state.isSubmitting, - })} - > - {#snippet children({ canSubmit, isSubmitting })} -
- - -
- {/snippet} -
-
-
- + ({ + canSubmit: state.canSubmit, + isSubmitting: state.isSubmitting + })} + > + {#snippet children({ canSubmit, isSubmitting })} +
+ + +
+ {/snippet} +
+
+
+ {/if} - diff --git a/apps/web/src/routes/(dashboard)/recursos-humanos/simbolos/cadastro/+page.svelte b/apps/web/src/routes/(dashboard)/recursos-humanos/simbolos/cadastro/+page.svelte index 1396d45..d75fa52 100644 --- a/apps/web/src/routes/(dashboard)/recursos-humanos/simbolos/cadastro/+page.svelte +++ b/apps/web/src/routes/(dashboard)/recursos-humanos/simbolos/cadastro/+page.svelte @@ -1,477 +1,539 @@ -
- - +
+ + - -
-
-
- - - -
-
-

Cadastro de Símbolo

-

Preencha os campos abaixo para cadastrar um novo cargo ou função

-
-
-
+ +
+
+
+ + + +
+
+

Cadastro de Símbolo

+

+ Preencha os campos abaixo para cadastrar um novo cargo ou função +

+
+
+
- - {#if notice} -
- - {#if notice.kind === "success"} - - {:else} - - {/if} - - {notice.text} -
- {/if} + + {#if notice} +
+ + {#if notice.kind === 'success'} + + {:else} + + {/if} + + {notice.text} +
+ {/if} - -
{ - e.preventDefault(); - e.stopPropagation(); - form.handleSubmit(); - }} - > -
-
-

Informações Básicas

+ + { + e.preventDefault(); + e.stopPropagation(); + form.handleSubmit(); + }} + > +
+
+

Informações Básicas

- - - {#snippet children({ name, state, handleChange })} -
- - { - const target = e.target as HTMLInputElement; - handleChange(target.value); - }} - required - /> - -
- {/snippet} -
+ + + {#snippet children({ name, state, handleChange })} +
+ + { + const target = e.target as HTMLInputElement; + handleChange(target.value); + }} + required + /> + +
+ {/snippet} +
- - - {#snippet children({ name, state, handleChange })} -
- - - -
- {/snippet} -
+ + + {#snippet children({ name, state, handleChange })} +
+ + + +
+ {/snippet} +
- - (value ? undefined : "Obrigatório"), - }} - > - {#snippet children({ name, state, handleChange })} -
- - - -
- {/snippet} -
-
-
+ + (value ? undefined : 'Obrigatório') + }} + > + {#snippet children({ name, state, handleChange })} +
+ + + +
+ {/snippet} +
+
+
- -
-
-

- Valores Financeiros - - {tipo === "cargo_comissionado" ? "Cargo Comissionado" : "Função Gratificada"} - -

+ +
+
+

+ Valores Financeiros + + {tipo === 'cargo_comissionado' ? 'Cargo Comissionado' : 'Função Gratificada'} + +

- {#if tipo === "cargo_comissionado"} -
- - - form.getFieldValue("tipo") === "cargo_comissionado" && !value - ? "Obrigatório" - : undefined, - }} - > - {#snippet children({ name, state, handleChange })} -
- - - -
- {/snippet} -
+ {#if tipo === 'cargo_comissionado'} +
+ + + form.getFieldValue('tipo') === 'cargo_comissionado' && !value + ? 'Obrigatório' + : undefined + }} + > + {#snippet children({ name, state, handleChange })} +
+ + + +
+ {/snippet} +
- - - form.getFieldValue("tipo") === "cargo_comissionado" && !value - ? "Obrigatório" - : undefined, - }} - > - {#snippet children({ name, state, handleChange })} -
- - - -
- {/snippet} -
-
+ + + form.getFieldValue('tipo') === 'cargo_comissionado' && !value + ? 'Obrigatório' + : undefined + }} + > + {#snippet children({ name, state, handleChange })} +
+ + + +
+ {/snippet} +
+
- - {#if getTotalPreview()} -
- - - -
-

Valor Total Calculado

-
R$ {getTotalPreview()}
-
-
- {/if} - {:else} - - - form.getFieldValue("tipo") === "funcao_gratificada" && !value - ? "Obrigatório" - : undefined, - }} - > - {#snippet children({ name, state, handleChange })} -
- - - -
- {/snippet} -
- {/if} -
-
+ + {#if getTotalPreview()} +
+ + + +
+

Valor Total Calculado

+
R$ {getTotalPreview()}
+
+
+ {/if} + {:else} + + + form.getFieldValue('tipo') === 'funcao_gratificada' && !value + ? 'Obrigatório' + : undefined + }} + > + {#snippet children({ name, state, handleChange })} +
+ + + +
+ {/snippet} +
+ {/if} +
+
- - ({ - canSubmit: state.canSubmit, - isSubmitting: state.isSubmitting, - })} - > - {#snippet children({ canSubmit, isSubmitting })} -
-
-
- - -
-
-
- {/snippet} -
-
+ + ({ + canSubmit: state.canSubmit, + isSubmitting: state.isSubmitting + })} + > + {#snippet children({ canSubmit, isSubmitting })} +
+
+
+ + +
+
+
+ {/snippet} +
+
diff --git a/apps/web/src/routes/(dashboard)/secretaria-executiva/gestao-ausencias/+page.svelte b/apps/web/src/routes/(dashboard)/secretaria-executiva/gestao-ausencias/+page.svelte index d6618dd..28b8fac 100644 --- a/apps/web/src/routes/(dashboard)/secretaria-executiva/gestao-ausencias/+page.svelte +++ b/apps/web/src/routes/(dashboard)/secretaria-executiva/gestao-ausencias/+page.svelte @@ -1,424 +1,406 @@ -
- - +
+ + - -
-
-
-
- - - -
-
-

Gestão de Ausências

-

- Visão geral de todas as solicitações de ausências -

-
-
- -
-
+ +
+
+
+
+ + + +
+
+

Gestão de Ausências

+

Visão geral de todas as solicitações de ausências

+
+
+ +
+
- -
-
-
- - - -
-
Total
-
{stats.total}
-
Solicitações
-
+ +
+
+
+ + + +
+
Total
+
{stats.total}
+
Solicitações
+
-
-
- - - -
-
Pendentes
-
{stats.aguardando}
-
Aguardando
-
+
+
+ + + +
+
Pendentes
+
{stats.aguardando}
+
Aguardando
+
-
-
- - - -
-
Aprovadas
-
{stats.aprovadas}
-
Deferidas
-
+
+
+ + + +
+
Aprovadas
+
{stats.aprovadas}
+
Deferidas
+
-
-
- - - -
-
Reprovadas
-
{stats.reprovadas}
-
Indeferidas
-
-
+
+
+ + + +
+
Reprovadas
+
{stats.reprovadas}
+
Indeferidas
+
+
- -
-
-

Filtros

-
-
- - -
-
-
-
+ +
+
+

Filtros

+
+
+ + +
+
+
+
- -
-
-

- Todas as Solicitações ({ausenciasFiltradas.length}) -

+ +
+
+

+ Todas as Solicitações ({ausenciasFiltradas.length}) +

- {#if ausenciasFiltradas.length === 0} -
- - - - Nenhuma solicitação encontrada com os filtros aplicados. -
- {:else} -
- - - - - - - - - - - - - - - {#each ausenciasFiltradas as ausencia} - - - - - - - - - - - {/each} - -
FuncionárioTimePeríodoDiasMotivoStatusSolicitado emAções
- {ausencia.funcionario?.nome || "N/A"} - - {#if ausencia.time} -
- {ausencia.time.nome} -
- {:else} - Sem time - {/if} -
- {new Date(ausencia.dataInicio).toLocaleDateString("pt-BR")} até{" "} - {new Date(ausencia.dataFim).toLocaleDateString("pt-BR")} - - {calcularDias(ausencia.dataInicio, ausencia.dataFim)} dias - - {ausencia.motivo} - -
- {getStatusTexto(ausencia.status)} -
-
- {new Date(ausencia.criadoEm).toLocaleDateString("pt-BR")} - - {#if ausencia.status === "aguardando_aprovacao"} - - {:else} - - {/if} -
-
- {/if} -
-
+ {#if ausenciasFiltradas.length === 0} +
+ + + + Nenhuma solicitação encontrada com os filtros aplicados. +
+ {:else} +
+ + + + + + + + + + + + + + + {#each ausenciasFiltradas as ausencia} + + + + + + + + + + + {/each} + +
FuncionárioTimePeríodoDiasMotivoStatusSolicitado emAções
+ {ausencia.funcionario?.nome || 'N/A'} + + {#if ausencia.time} +
+ {ausencia.time.nome} +
+ {:else} + Sem time + {/if} +
+ {new Date(ausencia.dataInicio).toLocaleDateString('pt-BR')} até{' '} + {new Date(ausencia.dataFim).toLocaleDateString('pt-BR')} + + {calcularDias(ausencia.dataInicio, ausencia.dataFim)} dias + + {ausencia.motivo} + +
+ {getStatusTexto(ausencia.status)} +
+
+ {new Date(ausencia.criadoEm).toLocaleDateString('pt-BR')} + + {#if ausencia.status === 'aguardando_aprovacao'} + + {:else} + + {/if} +
+
+ {/if} +
+
{#if solicitacaoSelecionada && currentUser.data} - {#await client.query( api.ausencias.obterDetalhes, { solicitacaoId: solicitacaoSelecionada }, ) then detalhes} - {#if detalhes} - - - - - {/if} - {/await} + {#await client.query( api.ausencias.obterDetalhes, { solicitacaoId: solicitacaoSelecionada } ) then detalhes} + {#if detalhes} + + + + + {/if} + {/await} {/if} diff --git a/apps/web/src/routes/(dashboard)/solicitar-acesso/+page.svelte b/apps/web/src/routes/(dashboard)/solicitar-acesso/+page.svelte index 5afa446..e349a09 100644 --- a/apps/web/src/routes/(dashboard)/solicitar-acesso/+page.svelte +++ b/apps/web/src/routes/(dashboard)/solicitar-acesso/+page.svelte @@ -1,394 +1,347 @@
- -
-
-
-
- - Acesso ao Sistema - -

- Solicitar Acesso ao SGSE -

-

- Preencha o formulário abaixo para solicitar acesso ao Sistema de - Gerenciamento da Secretaria de Esportes. Sua solicitação será analisada - pela equipe de Tecnologia da Informação. -

-
-
+ +
+
+
+
+ + Acesso ao Sistema + +

+ Solicitar Acesso ao SGSE +

+

+ Preencha o formulário abaixo para solicitar acesso ao Sistema de Gerenciamento da Secretaria + de Esportes. Sua solicitação será analisada pela equipe de Tecnologia da Informação. +

+
+
- - {#if notice} -
- - {#if notice.type === "success"} - - {:else} - - {/if} - - {notice.message} -
- {/if} + + {#if notice} +
+ + {#if notice.type === 'success'} + + {:else} + + {/if} + + {notice.message} +
+ {/if} - -
-
-
-
{ - e.preventDefault(); - e.stopPropagation(); - form.handleSubmit(); - }} - > -
- - - {#snippet children(field)} -
- - field.handleChange(e.currentTarget.value)} - /> - {#if field.state.meta.errors.length > 0} - - {/if} -
- {/snippet} -
+ +
+
+
+ { + e.preventDefault(); + e.stopPropagation(); + form.handleSubmit(); + }} + > +
+ + + {#snippet children(field)} +
+ + field.handleChange(e.currentTarget.value)} + /> + {#if field.state.meta.errors.length > 0} + + {/if} +
+ {/snippet} +
- - - {#snippet children(field)} -
- - field.handleChange(e.currentTarget.value)} - /> - {#if field.state.meta.errors.length > 0} - - {/if} -
- {/snippet} -
+ + + {#snippet children(field)} +
+ + field.handleChange(e.currentTarget.value)} + /> + {#if field.state.meta.errors.length > 0} + + {/if} +
+ {/snippet} +
- - - {#snippet children(field)} -
- - field.handleChange(e.currentTarget.value)} - /> - {#if field.state.meta.errors.length > 0} - - {/if} -
- {/snippet} -
+ + + {#snippet children(field)} +
+ + field.handleChange(e.currentTarget.value)} + /> + {#if field.state.meta.errors.length > 0} + + {/if} +
+ {/snippet} +
- - - {#snippet children(field)} -
- - { - const masked = maskTelefone(e.currentTarget.value); - e.currentTarget.value = masked; - field.handleChange(masked); - }} - maxlength="15" - /> - {#if field.state.meta.errors.length > 0} - - {/if} -
- {/snippet} -
-
+ + + {#snippet children(field)} +
+ + { + const masked = maskTelefone(e.currentTarget.value); + e.currentTarget.value = masked; + field.handleChange(masked); + }} + maxlength="15" + /> + {#if field.state.meta.errors.length > 0} + + {/if} +
+ {/snippet} +
+
- -
- - -
- -
-
+ +
+ + +
+ +
+ - -
- - - -
-

Informações Importantes

-
-
- - Todos os campos marcados com * são obrigatórios -
-
- - Sua solicitação será analisada pela equipe de TI em até 48 horas - úteis -
-
- - Você receberá um e-mail com o resultado da análise -
-
- - Em caso de dúvidas, entre em contato com o suporte técnico -
-
-
-
+ +
+ + + +
+

Informações Importantes

+
+
+ + Todos os campos marcados com * são obrigatórios +
+
+ + Sua solicitação será analisada pela equipe de TI em até 48 horas úteis +
+
+ + Você receberá um e-mail com o resultado da análise +
+
+ + Em caso de dúvidas, entre em contato com o suporte técnico +
+
+
+
diff --git a/apps/web/src/routes/(dashboard)/ti/monitoramento-emails/+page.svelte b/apps/web/src/routes/(dashboard)/ti/monitoramento-emails/+page.svelte index 214332c..0a3cd0e 100644 --- a/apps/web/src/routes/(dashboard)/ti/monitoramento-emails/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/monitoramento-emails/+page.svelte @@ -1,463 +1,568 @@
-
-

📧 Monitoramento de Emails

-

- Acompanhe o status da fila de emails e identifique problemas de envio -

-
+
+

📧 Monitoramento de Emails

+

+ Acompanhe o status da fila de emails e identifique problemas de envio +

+
- - {#if estatisticas?.data} -
-
-
Total
-
{estatisticas.data.total}
-
Emails na fila
-
+ + {#if estatisticas?.data} +
+
+
Total
+
{estatisticas.data.total}
+
Emails na fila
+
-
-
Pendentes
-
{estatisticas.data.pendentes}
-
Aguardando envio
-
+
+
Pendentes
+
{estatisticas.data.pendentes}
+
Aguardando envio
+
-
-
Enviando
-
{estatisticas.data.enviando}
-
Em processamento
-
+
+
Enviando
+
{estatisticas.data.enviando}
+
Em processamento
+
-
-
Enviados
-
{estatisticas.data.enviados}
-
Concluídos
-
+
+
Enviados
+
{estatisticas.data.enviados}
+
Concluídos
+
-
-
Falhas
-
{estatisticas.data.falhas}
-
Com erro
-
-
- {:else if estatisticas === undefined} -
- -
- {/if} +
+
Falhas
+
{estatisticas.data.falhas}
+
Com erro
+
+
+ {:else if estatisticas === undefined} +
+ +
+ {/if} - -
-
-
-
- -
+ +
+
+
+
+ +
- -
-
-
+ +
+
+
- -
-
-

Fila de Emails

+ +
+
+

Fila de Emails

- {#if filaEmails?.data && filaEmails.data.length > 0} -
- - - - - - - - - - - - - - - {#each filaEmails.data as email} - - - - - - - - - - - {/each} - -
DestinatárioAssuntoStatusTentativasCriado emÚltima tentativaErroAções
-
{email.destinatario}
-
-
- {email.assunto} -
-
- - {getStatusLabel(email.status)} - - {email.tentativas || 0}{formatarData(email.criadoEm)} - {formatarData(email.ultimaTentativa)} - - {#if email.erroDetalhes} - - {:else} - - - {/if} - - -
-
- {:else if filaEmails?.data !== undefined} -
-

Nenhum email na fila

-
- {:else} -
- -
- {/if} -
-
+ {#if filaEmails?.data && filaEmails.data.length > 0} +
+ + + + + + + + + + + + + + + {#each filaEmails.data as email} + + + + + + + + + + + {/each} + +
DestinatárioAssuntoStatusTentativasCriado emÚltima tentativaErroAções
+
{email.destinatario}
+
+
+ {email.assunto} +
+
+ + {getStatusLabel(email.status)} + + {email.tentativas || 0}{formatarData(email.criadoEm)} + {formatarData(email.ultimaTentativa)} + + {#if email.erroDetalhes} + + {:else} + - + {/if} + + +
+
+ {:else if filaEmails?.data !== undefined} +
+

Nenhum email na fila

+
+ {:else} +
+ +
+ {/if} +
+
- -
-
-

🔍 Troubleshooting

-
-

- Emails pendentes não estão sendo enviados? -

-
    -
  • Verifique se a configuração SMTP está ativa em Configurações de Email
  • -
  • Confirme se o cron job está rodando (verifique logs do Convex)
  • -
  • Clique em "Processar Fila Manualmente" para forçar o processamento
  • -
+ +
+
+

🔍 Troubleshooting

+
+

+ Emails pendentes não estão sendo enviados? +

+
    +
  • Verifique se a configuração SMTP está ativa em Configurações de Email
  • +
  • Confirme se o cron job está rodando (verifique logs do Convex)
  • +
  • Clique em "Processar Fila Manualmente" para forçar o processamento
  • +
-

- Emails com status "Falha"? -

-
    -
  • Verifique as credenciais SMTP em Configurações de Email
  • -
  • Confirme se o servidor SMTP está acessível
  • -
  • Verifique os logs do Convex para detalhes do erro
  • -
  • Teste a conexão SMTP na página de configurações
  • -
-
-
-
+

+ Emails com status "Falha"? +

+
    +
  • Verifique as credenciais SMTP em Configurações de Email
  • +
  • Confirme se o servidor SMTP está acessível
  • +
  • Verifique os logs do Convex para detalhes do erro
  • +
  • Teste a conexão SMTP na página de configurações
  • +
+
+
+
{#if modalDetalhesAberto && emailSelecionado} - {/if} - diff --git a/apps/web/src/routes/(dashboard)/ti/monitoramento/+page.svelte b/apps/web/src/routes/(dashboard)/ti/monitoramento/+page.svelte index 274680b..32b2e99 100644 --- a/apps/web/src/routes/(dashboard)/ti/monitoramento/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/monitoramento/+page.svelte @@ -30,7 +30,7 @@

- + - import { useQuery, useConvexClient } from "convex-svelte"; - import { api } from "@sgse-app/backend/convex/_generated/api"; - import { format } from "date-fns"; - import { ptBR } from "date-fns/locale"; - import type { Id, Doc } from "@sgse-app/backend/convex/_generated/dataModel"; - - // Tipos para agendamentos - type TipoAgendamento = "email" | "chat"; - type StatusAgendamento = "agendado" | "enviado" | "cancelado"; - - interface AgendamentoEmail { - _id: Id<"notificacoesEmail">; - _creationTime: number; - destinatario: string; - destinatarioId: Id<"usuarios"> | undefined; - assunto: string; - corpo: string; - templateId: Id<"templatesMensagens"> | undefined; - status: "pendente" | "enviando" | "enviado" | "falha"; - agendadaPara: number | undefined; - enviadoPor: Id<"usuarios">; - criadoEm: number; - enviadoEm: number | undefined; - destinatarioInfo: Doc<"usuarios"> | null; - templateInfo: Doc<"templatesMensagens"> | null; - } - - interface AgendamentoChat { - _id: Id<"mensagens">; - _creationTime: number; - conversaId: Id<"conversas">; - remetenteId: Id<"usuarios">; - conteudo: string; - agendadaPara: number | undefined; - enviadaEm: number; - conversaInfo: Doc<"conversas"> | null; - destinatarioInfo: Doc<"usuarios"> | null; - } - - type Agendamento = - | { tipo: "email"; dados: AgendamentoEmail } - | { tipo: "chat"; dados: AgendamentoChat }; - - const client = useConvexClient(); - const currentUser = useQuery(api.auth.getCurrentUser, {}); - - // Queries - const templatesQuery = useQuery(api.templatesMensagens.listarTemplates, {}); - const usuariosQuery = useQuery(api.usuarios.listar, {}); - - // Mapa de emailIds para rastrear status - let emailIdsRastreados = $state>(new Set()); - - // Query para buscar status dos emails (só executa quando há IDs) - const emailIdsArray = $derived( - Array.from(emailIdsRastreados).map((id) => id as Id<"notificacoesEmail">), - ); - // Usar função para evitar execução quando array está vazio - const emailsStatusQuery = $derived.by(() => { - if (emailIdsArray.length === 0) return null; - return useQuery(api.email.buscarEmailsPorIds, { emailIds: emailIdsArray }); - }); - - // Queries para agendamentos - const agendamentosEmailQuery = useQuery( - api.email.listarAgendamentosEmail, - {}, - ); - const agendamentosChatQuery = useQuery(api.chat.listarAgendamentosChat, {}); - - // Filtro de agendamentos - type FiltroAgendamento = "todos" | "agendados" | "enviados"; - let filtroAgendamento = $state("todos"); - - // Extrair dados das queries de forma robusta - const templates = $derived.by(() => { - if (templatesQuery === undefined || templatesQuery === null) { - return []; - } - if ("data" in templatesQuery && templatesQuery.data !== undefined) { - return Array.isArray(templatesQuery.data) ? templatesQuery.data : []; - } - if (Array.isArray(templatesQuery)) { - return templatesQuery; - } - return []; - }); - - const usuarios = $derived.by(() => { - if (usuariosQuery === undefined || usuariosQuery === null) { - return []; - } - if ("data" in usuariosQuery && usuariosQuery.data !== undefined) { - return Array.isArray(usuariosQuery.data) ? usuariosQuery.data : []; - } - if (Array.isArray(usuariosQuery)) { - return usuariosQuery; - } - return []; - }); - - // Estados de carregamento e erro - const carregandoTemplates = $derived( - templatesQuery === undefined || templatesQuery === null, - ); - const carregandoUsuarios = $derived( - usuariosQuery === undefined || usuariosQuery === null, - ); - - // Verificar erros de forma mais robusta - const erroTemplates = $derived.by(() => { - if (templatesQuery === undefined || templatesQuery === null) return false; - // Verificar se é um objeto com propriedade error - if (typeof templatesQuery === "object" && "error" in templatesQuery) { - return ( - templatesQuery.error !== undefined && templatesQuery.error !== null - ); - } - return false; - }); - - const erroUsuarios = $derived.by(() => { - if (usuariosQuery === undefined || usuariosQuery === null) return false; - if (typeof usuariosQuery === "object" && "error" in usuariosQuery) { - return usuariosQuery.error !== undefined && usuariosQuery.error !== null; - } - return false; - }); - - // Log para debug (remover depois se necessário) - $effect(() => { - if (templatesQuery !== undefined && templatesQuery !== null) { - console.log("Templates Query:", templatesQuery); - console.log("Templates Extraídos:", templates); - } - }); - - let destinatarioId = $state(""); - let enviarParaTodos = $state(false); - let canal = $state<"chat" | "email" | "ambos">("chat"); - let templateId = $state(""); - let mensagemPersonalizada = $state(""); - let usarTemplate = $state(true); - let processando = $state(false); - let criandoTemplates = $state(false); - let progressoEnvio = $state({ total: 0, enviados: 0, falhas: 0 }); - - // Estrutura de dados para logs de envio - type StatusLog = "sucesso" | "erro" | "fila" | "info" | "enviando"; - type TipoLog = "chat" | "email"; - type LogEnvio = { - timestamp: number; - tipo: TipoLog; - destinatario: string; - status: StatusLog; - mensagem: string; - emailId?: string; // Para emails, guardar o ID para rastrear status - }; - - let logsEnvio = $state([]); - let terminalScrollRef: HTMLDivElement | null = $state(null); - - // Estados para agendamento - let agendarEnvio = $state(false); - let dataAgendamento = $state(""); - let horaAgendamento = $state(""); - - // Calcular data/hora mínimas - const now = new Date(); - const minDate = format(now, "yyyy-MM-dd"); - - // Hora mínima recalculada baseada na data selecionada - const horaMinima = $derived.by(() => { - if (!dataAgendamento) return format(now, "HH:mm"); - const hoje = format(now, "yyyy-MM-dd"); - if (dataAgendamento === hoje) { - return format(now, "HH:mm"); - } - return undefined; // Sem restrição se for data futura - }); - - function getPreviewAgendamento(): string { - if (!agendarEnvio || !dataAgendamento || !horaAgendamento) return ""; - try { - const dataHora = new Date(`${dataAgendamento}T${horaAgendamento}`); - return `Será enviada em ${format(dataHora, "dd/MM/yyyy 'às' HH:mm", { locale: ptBR })}`; - } catch { - return ""; - } - } - - // Estados para modal de novo template - let modalNovoTemplateAberto = $state(false); - let codigoTemplate = $state(""); - let nomeTemplate = $state(""); - let tituloTemplate = $state(""); - let corpoTemplate = $state(""); - let variaveisTemplate = $state(""); - let criandoNovoTemplate = $state(false); - - // Estado para mensagens de feedback - let mensagem = $state<{ - tipo: "success" | "error" | "info"; - texto: string; - } | null>(null); - - const templateSelecionado = $derived( - templates.find((t) => t._id === templateId), - ); - - // Função para renderizar template com variáveis (similar à função do backend) - function renderizarTemplate( - template: string, - variaveis: Record, - ): string { - let resultado = template; - - for (const [chave, valor] of Object.entries(variaveis)) { - const placeholder = `{{${chave}}}`; - resultado = resultado.replace(new RegExp(placeholder, "g"), valor); - } - - return resultado; - } - - // Função para mostrar mensagens - function mostrarMensagem(tipo: "success" | "error" | "info", texto: string) { - mensagem = { tipo, texto }; - setTimeout(() => { - mensagem = null; - }, 5000); - } - - // Função para adicionar log ao terminal - function adicionarLog( - tipo: TipoLog, - destinatario: string, - status: StatusLog, - mensagem: string, - emailId?: string, - ) { - logsEnvio = [ - ...logsEnvio, - { - timestamp: Date.now(), - tipo, - destinatario, - status, - mensagem, - emailId, - }, - ]; - - // Adicionar emailId ao rastreamento se fornecido - if (emailId) { - emailIdsRastreados = new Set([...emailIdsRastreados, emailId]); - } - - // Auto-scroll para o final - setTimeout(() => { - if (terminalScrollRef) { - terminalScrollRef.scrollTop = terminalScrollRef.scrollHeight; - } - }, 10); - } - - // Atualizar logs quando status dos emails mudar - $effect(() => { - if (!emailsStatusQuery || emailsStatusQuery === undefined) return; - - // Extrair dados da query - const emails = Array.isArray(emailsStatusQuery) - ? emailsStatusQuery - : "data" in emailsStatusQuery && Array.isArray(emailsStatusQuery.data) - ? emailsStatusQuery.data - : []; - - if (emails.length === 0) return; - - for (const email of emails) { - if (!email) continue; - - // Encontrar logs relacionados a este email - const logsRelacionados = logsEnvio.filter( - (log) => log.emailId === email._id, - ); - - for (const log of logsRelacionados) { - // Verificar se o status mudou - const novoStatus: StatusLog = - email.status === "enviado" - ? "sucesso" - : email.status === "falha" - ? "erro" - : email.status === "enviando" - ? "enviando" - : email.status === "pendente" - ? "fila" - : "info"; - - // Se o status mudou, atualizar o log - if (log.status !== novoStatus) { - const indice = logsEnvio.findIndex((l) => l === log); - if (indice !== -1) { - const novaMensagem = - email.status === "enviado" - ? "Email enviado com sucesso" - : email.status === "falha" - ? `Falha ao enviar: ${email.erroDetalhes || "Erro desconhecido"}` - : email.status === "enviando" - ? "Enviando email..." - : email.status === "pendente" - ? "Email em fila de envio" - : log.mensagem; - - logsEnvio = logsEnvio.map((l, i) => - i === indice - ? { ...l, status: novoStatus, mensagem: novaMensagem } - : l, - ); - } - } - } - } - }); - - // Função para limpar logs - function limparLogs() { - logsEnvio = []; - emailIdsRastreados = new Set(); - } - - // Extrair e processar agendamentos - const agendamentosEmail = $derived.by(() => { - if (!agendamentosEmailQuery || agendamentosEmailQuery === undefined) - return []; - const dados = Array.isArray(agendamentosEmailQuery) - ? agendamentosEmailQuery - : "data" in agendamentosEmailQuery && - Array.isArray(agendamentosEmailQuery.data) - ? agendamentosEmailQuery.data - : []; - return dados as AgendamentoEmail[]; - }); - - const agendamentosChat = $derived.by(() => { - if (!agendamentosChatQuery || agendamentosChatQuery === undefined) - return []; - const dados = Array.isArray(agendamentosChatQuery) - ? agendamentosChatQuery - : "data" in agendamentosChatQuery && - Array.isArray(agendamentosChatQuery.data) - ? agendamentosChatQuery.data - : []; - return dados as AgendamentoChat[]; - }); - - // Combinar e processar agendamentos - const todosAgendamentos = $derived.by(() => { - const agendamentos: Agendamento[] = []; - - for (const email of agendamentosEmail) { - if (email.agendadaPara) { - agendamentos.push({ - tipo: "email", - dados: email, - }); - } - } - - for (const chat of agendamentosChat) { - if (chat.agendadaPara) { - agendamentos.push({ - tipo: "chat", - dados: chat, - }); - } - } - - // Ordenar: futuros primeiro (mais próximos primeiro), depois passados (mais recentes primeiro) - return agendamentos.sort((a, b) => { - const timestampA = - a.tipo === "email" - ? (a.dados.agendadaPara ?? 0) - : (a.dados.agendadaPara ?? 0); - const timestampB = - b.tipo === "email" - ? (b.dados.agendadaPara ?? 0) - : (b.dados.agendadaPara ?? 0); - const agora = Date.now(); - - const aFuturo = timestampA > agora; - const bFuturo = timestampB > agora; - - // Futuros primeiro - if (aFuturo && !bFuturo) return -1; - if (!aFuturo && bFuturo) return 1; - - // Dentro do mesmo grupo, ordenar por timestamp - if (aFuturo) { - // Futuros: mais próximos primeiro - return timestampA - timestampB; - } else { - // Passados: mais recentes primeiro - return timestampB - timestampA; - } - }); - }); - - // Filtrar agendamentos - const agendamentosFiltrados = $derived.by(() => { - if (filtroAgendamento === "todos") return todosAgendamentos; - - return todosAgendamentos.filter((ag) => { - const status = obterStatusAgendamento(ag); - if (filtroAgendamento === "agendados") return status === "agendado"; - if (filtroAgendamento === "enviados") return status === "enviado"; - return true; - }); - }); - - // Função para obter status do agendamento - function obterStatusAgendamento(agendamento: Agendamento): StatusAgendamento { - if (agendamento.tipo === "email") { - const email = agendamento.dados; - if (email.status === "enviado") return "enviado"; - if (email.agendadaPara && email.agendadaPara <= Date.now()) - return "enviado"; - return "agendado"; - } else { - const chat = agendamento.dados; - if (chat.agendadaPara && chat.agendadaPara <= Date.now()) - return "enviado"; - return "agendado"; - } - } - - // Função para cancelar agendamento - async function cancelarAgendamento(agendamento: Agendamento) { - if (!confirm("Tem certeza que deseja cancelar este agendamento?")) { - return; - } - - try { - if (agendamento.tipo === "email") { - const resultado = await client.mutation( - api.email.cancelarAgendamentoEmail, - { - emailId: agendamento.dados._id, - }, - ); - if (resultado.sucesso) { - mostrarMensagem( - "success", - "Agendamento de email cancelado com sucesso!", - ); - } else { - mostrarMensagem( - "error", - resultado.erro || "Erro ao cancelar agendamento", - ); - } - } else { - const resultado = await client.mutation( - api.chat.cancelarMensagemAgendada, - { - mensagemId: agendamento.dados._id, - }, - ); - if (resultado.sucesso) { - mostrarMensagem( - "success", - "Agendamento de chat cancelado com sucesso!", - ); - } else { - mostrarMensagem( - "error", - resultado.erro || "Erro ao cancelar agendamento", - ); - } - } - } catch (error) { - const erro = error instanceof Error ? error.message : "Erro desconhecido"; - mostrarMensagem("error", `Erro ao cancelar agendamento: ${erro}`); - } - } - - // Função para obter nome do destinatário - function obterNomeDestinatario(agendamento: Agendamento): string { - if (agendamento.tipo === "email") { - return ( - agendamento.dados.destinatarioInfo?.nome || - agendamento.dados.destinatario || - "Usuário" - ); - } else { - return agendamento.dados.destinatarioInfo?.nome || "Usuário"; - } - } - - // Função para formatar data/hora do agendamento - function formatarDataAgendamento(agendamento: Agendamento): string { - const timestamp = - agendamento.tipo === "email" - ? agendamento.dados.agendadaPara - : agendamento.dados.agendadaPara; - - if (!timestamp) return "N/A"; - - return format(new Date(timestamp), "dd/MM/yyyy 'às' HH:mm", { - locale: ptBR, - }); - } - - // Função para formatar timestamp - function formatarTimestamp(timestamp: number): string { - return format(new Date(timestamp), "HH:mm:ss", { locale: ptBR }); - } - - // Função para obter cor do status - function obterCorStatus(status: StatusLog): string { - switch (status) { - case "sucesso": - return "text-success"; - case "erro": - return "text-error"; - case "fila": - return "text-warning"; - case "enviando": - return "text-info"; - case "info": - return "text-info"; - default: - return "text-base-content"; - } - } - - async function criarTemplatesPadrao() { - if (criandoTemplates) return; - - criandoTemplates = true; - try { - const resultado = await client.mutation( - api.templatesMensagens.criarTemplatesPadrao, - {}, - ); - if (resultado.sucesso) { - mostrarMensagem( - "success", - "Templates padrão criados com sucesso! A página será recarregada.", - ); - setTimeout(() => { - window.location.reload(); - }, 2000); - } else { - mostrarMensagem("error", "Erro ao criar templates padrão."); - } - } catch (error) { - const erro = error instanceof Error ? error.message : "Erro desconhecido"; - console.error("Erro ao criar templates:", error); - mostrarMensagem("error", "Erro ao criar templates: " + erro); - } finally { - criandoTemplates = false; - } - } - - function abrirModalNovoTemplate() { - modalNovoTemplateAberto = true; - // Limpar campos - codigoTemplate = ""; - nomeTemplate = ""; - tituloTemplate = ""; - corpoTemplate = ""; - variaveisTemplate = ""; - } - - function fecharModalNovoTemplate() { - modalNovoTemplateAberto = false; - } - - async function salvarNovoTemplate() { - if (!currentUser?.data) { - mostrarMensagem( - "error", - "Você precisa estar autenticado para criar templates.", - ); - return; - } - - // Validações - if (!codigoTemplate.trim()) { - mostrarMensagem("error", "O código do template é obrigatório."); - return; - } - if (!nomeTemplate.trim()) { - mostrarMensagem("error", "O nome do template é obrigatório."); - return; - } - if (!tituloTemplate.trim()) { - mostrarMensagem("error", "O título do template é obrigatório."); - return; - } - if (!corpoTemplate.trim()) { - mostrarMensagem("error", "O corpo do template é obrigatório."); - return; - } - - // Processar variáveis (separadas por vírgula ou espaço) - const variaveis = variaveisTemplate - .split(/[,;\s]+/) - .map((v) => v.trim()) - .filter((v) => v.length > 0); - - criandoNovoTemplate = true; - try { - const resultado = await client.mutation( - api.templatesMensagens.criarTemplate, - { - codigo: codigoTemplate.trim().toUpperCase().replace(/\s+/g, "_"), - nome: nomeTemplate.trim(), - titulo: tituloTemplate.trim(), - corpo: corpoTemplate.trim(), - variaveis: variaveis.length > 0 ? variaveis : undefined, - criadoPorId: currentUser.data._id as Id<"usuarios">, - }, - ); - - if (resultado.sucesso) { - mostrarMensagem("success", "Template criado com sucesso!"); - fecharModalNovoTemplate(); - // Recarregar a página para atualizar a lista - setTimeout(() => { - window.location.reload(); - }, 1500); - } else { - mostrarMensagem( - "error", - "Erro ao criar template: " + (resultado.erro || "Erro desconhecido"), - ); - } - } catch (error) { - const erro = error instanceof Error ? error.message : "Erro desconhecido"; - console.error("Erro ao criar template:", error); - mostrarMensagem("error", "Erro ao criar template: " + erro); - } finally { - criandoNovoTemplate = false; - } - } - - async function enviarNotificacao() { - if (!enviarParaTodos && !destinatarioId) { - mostrarMensagem( - "error", - "Selecione um destinatário ou marque 'Enviar para todos'", - ); - return; - } - - if (usarTemplate && !templateId) { - mostrarMensagem("error", "Selecione um template"); - return; - } - - if (!usarTemplate && !mensagemPersonalizada.trim()) { - mostrarMensagem("error", "Digite uma mensagem"); - return; - } - - // Validar agendamento se marcado - let agendadaPara: number | undefined = undefined; - if (agendarEnvio) { - if (!dataAgendamento || !horaAgendamento) { - mostrarMensagem("error", "Preencha a data e hora para agendamento"); - return; - } - - try { - const dataHora = new Date(`${dataAgendamento}T${horaAgendamento}`); - if (dataHora.getTime() <= Date.now()) { - mostrarMensagem("error", "A data e hora devem ser futuras"); - return; - } - agendadaPara = dataHora.getTime(); - } catch (error) { - mostrarMensagem("error", "Data ou hora inválida"); - return; - } - } - - processando = true; - progressoEnvio = { total: 0, enviados: 0, falhas: 0 }; - - // Limpar logs anteriores quando iniciar novo envio - logsEnvio = []; - emailIdsRastreados = new Set(); - - try { - // Obter lista de destinatários - const destinatarios: typeof usuarios = enviarParaTodos - ? usuarios - : usuarios.filter((u) => u._id === destinatarioId); - - if (destinatarios.length === 0) { - adicionarLog( - "email", - "Sistema", - "erro", - "Nenhum destinatário encontrado", - ); - mostrarMensagem("error", "Nenhum destinatário encontrado"); - return; - } - - progressoEnvio.total = destinatarios.length; - - // Log inicial - const tipoMensagem = usarTemplate - ? `Template: ${templateSelecionado?.nome || ""}` - : "Mensagem personalizada"; - const destinatariosText = enviarParaTodos - ? `Todos os usuários (${destinatarios.length})` - : destinatarios.map((d) => d.nome).join(", "); - adicionarLog( - "email", - "Sistema", - "info", - `Iniciando envio de notificação via ${canal} para ${destinatariosText} - ${tipoMensagem}`, - ); - - // Se for envio para um único usuário - if (destinatarios.length === 1) { - const destinatario = destinatarios[0]; - let resultadoChat = null; - let resultadoEmail = null; - - // ENVIAR PARA CHAT - if (canal === "chat" || canal === "ambos") { - try { - adicionarLog( - "chat", - destinatario.nome, - "enviando", - "Criando/buscando conversa...", - ); - const conversaId = await client.mutation( - api.chat.criarOuBuscarConversaIndividual, - { outroUsuarioId: destinatario._id as Id<"usuarios"> }, - ); - - if (conversaId) { - const mensagem = - usarTemplate && templateSelecionado - ? renderizarTemplate(templateSelecionado.corpo, { - nome: destinatario.nome, - matricula: destinatario.matricula || "", - }) - : mensagemPersonalizada; - - if (agendadaPara) { - // Agendar mensagem - adicionarLog( - "chat", - destinatario.nome, - "info", - "Agendando mensagem...", - ); - resultadoChat = await client.mutation( - api.chat.agendarMensagem, - { - conversaId: conversaId, - conteudo: mensagem, - agendadaPara: agendadaPara, - }, - ); - const dataFormatada = format( - new Date(agendadaPara), - "dd/MM/yyyy 'às' HH:mm", - { locale: ptBR }, - ); - adicionarLog( - "chat", - destinatario.nome, - "sucesso", - `Mensagem agendada para ${dataFormatada}`, - ); - } else { - // Envio imediato - adicionarLog( - "chat", - destinatario.nome, - "enviando", - "Enviando mensagem...", - ); - resultadoChat = await client.mutation(api.chat.enviarMensagem, { - conversaId: conversaId, - conteudo: mensagem, - tipo: "texto", - permitirNotificacaoParaSiMesmo: true, - }); - adicionarLog( - "chat", - destinatario.nome, - "sucesso", - "Mensagem enviada com sucesso", - ); - } - } else { - adicionarLog( - "chat", - destinatario.nome, - "erro", - "Falha ao criar/buscar conversa", - ); - } - } catch (error) { - const erro = - error instanceof Error ? error.message : "Erro desconhecido"; - console.error("Erro ao enviar chat:", error); - adicionarLog("chat", destinatario.nome, "erro", `Erro: ${erro}`); - } - } - - // ENVIAR PARA EMAIL - if (canal === "email" || canal === "ambos") { - if (destinatario.email) { - try { - adicionarLog( - "email", - destinatario.nome, - "enviando", - `Enfileirando email para ${destinatario.email}...`, - ); - if (usarTemplate && templateId) { - const template = templateSelecionado; - if (template) { - const emailId = await client.action( - api.email.enviarEmailComTemplate, - { - destinatario: destinatario.email, - destinatarioId: destinatario._id as Id<"usuarios">, - templateCodigo: template.codigo, - variaveis: { - nome: destinatario.nome, - matricula: destinatario.matricula, - }, - enviadoPor: currentUser.data._id as Id<"usuarios">, - agendadaPara: agendadaPara, - }, - ); - if (emailId) { - if (agendadaPara) { - const dataFormatada = format( - new Date(agendadaPara), - "dd/MM/yyyy 'às' HH:mm", - { locale: ptBR }, - ); - adicionarLog( - "email", - destinatario.nome, - "fila", - `Email agendado para ${dataFormatada}`, - emailId, - ); - } else { - adicionarLog( - "email", - destinatario.nome, - "fila", - "Email enfileirado para envio", - emailId, - ); - } - } else { - adicionarLog( - "email", - destinatario.nome, - "erro", - "Falha ao enfileirar email", - ); - } - } else { - adicionarLog( - "email", - destinatario.nome, - "erro", - "Template não encontrado", - ); - } - } else { - const emailId = await client.mutation( - api.email.enfileirarEmail, - { - destinatario: destinatario.email, - destinatarioId: destinatario._id as Id<"usuarios">, - assunto: "Notificação do Sistema", - corpo: mensagemPersonalizada, - enviadoPor: currentUser.data._id as Id<"usuarios">, - agendadaPara: agendadaPara, - }, - ); - if (emailId) { - resultadoEmail = { sucesso: true, emailId }; - if (agendadaPara) { - const dataFormatada = format( - new Date(agendadaPara), - "dd/MM/yyyy 'às' HH:mm", - { locale: ptBR }, - ); - adicionarLog( - "email", - destinatario.nome, - "fila", - `Email agendado para ${dataFormatada}`, - emailId, - ); - } else { - adicionarLog( - "email", - destinatario.nome, - "fila", - "Email enfileirado para envio", - emailId, - ); - } - } else { - adicionarLog( - "email", - destinatario.nome, - "erro", - "Falha ao enfileirar email", - ); - } - } - } catch (error) { - const erro = - error instanceof Error ? error.message : "Erro desconhecido"; - console.error("Erro ao enviar email:", error); - adicionarLog("email", destinatario.nome, "erro", `Erro: ${erro}`); - } - } else { - adicionarLog( - "email", - destinatario.nome, - "erro", - "Destinatário não possui email cadastrado", - ); - } - } - - // Feedback de sucesso - let mensagemSucesso = agendadaPara - ? `Notificação agendada com sucesso!` - : "Notificação enviada com sucesso!"; - if (agendadaPara) { - const dataFormatada = format( - new Date(agendadaPara), - "dd/MM/yyyy 'às' HH:mm", - { locale: ptBR }, - ); - mensagemSucesso += ` Será enviada em: ${dataFormatada}`; - } else { - if (canal === "ambos") { - if (resultadoChat && resultadoEmail) { - mensagemSucesso = "Notificação enviada para Chat e Email!"; - } else if (resultadoChat) { - mensagemSucesso = "Notificação enviada para Chat. Email falhou."; - } else if (resultadoEmail) { - mensagemSucesso = "Notificação enviada para Email. Chat falhou."; - } - } else if (canal === "chat" && resultadoChat) { - mensagemSucesso = "Mensagem enviada no Chat!"; - } else if (canal === "email" && resultadoEmail) { - mensagemSucesso = "Email enfileirado para envio!"; - } - } - - mostrarMensagem("success", mensagemSucesso); - progressoEnvio.enviados = 1; - } else { - // ENVIO EM MASSA - let sucessosChat = 0; - let sucessosEmail = 0; - let falhasChat = 0; - let falhasEmail = 0; - - adicionarLog( - "email", - "Sistema", - "info", - `Processando ${destinatarios.length} destinatários...`, - ); - - for (const destinatario of destinatarios) { - try { - // ENVIAR PARA CHAT - if (canal === "chat" || canal === "ambos") { - try { - adicionarLog( - "chat", - destinatario.nome, - "enviando", - "Processando...", - ); - const conversaId = await client.mutation( - api.chat.criarOuBuscarConversaIndividual, - { outroUsuarioId: destinatario._id as Id<"usuarios"> }, - ); - - if (conversaId) { - // Renderizar template com variáveis do destinatário - const mensagem = - usarTemplate && templateSelecionado - ? renderizarTemplate(templateSelecionado.corpo, { - nome: destinatario.nome, - matricula: destinatario.matricula || "", - }) - : mensagemPersonalizada; - - if (agendadaPara) { - await client.mutation(api.chat.agendarMensagem, { - conversaId: conversaId, - conteudo: mensagem, - agendadaPara: agendadaPara, - }); - const dataFormatada = format( - new Date(agendadaPara), - "dd/MM/yyyy 'às' HH:mm", - { locale: ptBR }, - ); - adicionarLog( - "chat", - destinatario.nome, - "sucesso", - `Agendado para ${dataFormatada}`, - ); - } else { - await client.mutation(api.chat.enviarMensagem, { - conversaId: conversaId, - conteudo: mensagem, - tipo: "texto", - permitirNotificacaoParaSiMesmo: true, - }); - adicionarLog( - "chat", - destinatario.nome, - "sucesso", - "Enviado com sucesso", - ); - } - sucessosChat++; - } else { - adicionarLog( - "chat", - destinatario.nome, - "erro", - "Falha ao criar/buscar conversa", - ); - falhasChat++; - } - } catch (error) { - const erro = - error instanceof Error ? error.message : "Erro desconhecido"; - console.error( - `Erro ao enviar chat para ${destinatario.nome}:`, - error, - ); - adicionarLog( - "chat", - destinatario.nome, - "erro", - `Erro: ${erro}`, - ); - falhasChat++; - } - } - - // ENVIAR PARA EMAIL - if (canal === "email" || canal === "ambos") { - if (destinatario.email) { - try { - adicionarLog( - "email", - destinatario.nome, - "enviando", - `Enfileirando email para ${destinatario.email}...`, - ); - if (usarTemplate && templateId) { - const template = templateSelecionado; - if (template) { - const emailId = await client.action( - api.email.enviarEmailComTemplate, - { - destinatario: destinatario.email, - destinatarioId: destinatario._id as Id<"usuarios">, - templateCodigo: template.codigo, - variaveis: { - nome: destinatario.nome, - matricula: destinatario.matricula || "", - }, - enviadoPor: currentUser.data._id as Id<"usuarios">, - agendadaPara: agendadaPara, - }, - ); - if (emailId) { - if (agendadaPara) { - const dataFormatada = format( - new Date(agendadaPara), - "dd/MM/yyyy 'às' HH:mm", - { locale: ptBR }, - ); - adicionarLog( - "email", - destinatario.nome, - "fila", - `Agendado para ${dataFormatada}`, - emailId, - ); - } else { - adicionarLog( - "email", - destinatario.nome, - "fila", - "Enfileirado para envio", - emailId, - ); - } - sucessosEmail++; - } else { - adicionarLog( - "email", - destinatario.nome, - "erro", - "Falha ao enfileirar email", - ); - falhasEmail++; - } - } else { - adicionarLog( - "email", - destinatario.nome, - "erro", - "Template não encontrado", - ); - falhasEmail++; - } - } else { - const emailId = await client.mutation( - api.email.enfileirarEmail, - { - destinatario: destinatario.email, - destinatarioId: destinatario._id as Id<"usuarios">, - assunto: "Notificação do Sistema", - corpo: mensagemPersonalizada, - enviadoPor: currentUser.data?._id as Id<"usuarios">, - agendadaPara: agendadaPara, - }, - ); - if (emailId) { - resultadoEmail = { sucesso: true, emailId }; - if (agendadaPara) { - const dataFormatada = format( - new Date(agendadaPara), - "dd/MM/yyyy 'às' HH:mm", - { locale: ptBR }, - ); - adicionarLog( - "email", - destinatario.nome, - "fila", - `Agendado para ${dataFormatada}`, - emailId, - ); - } else { - adicionarLog( - "email", - destinatario.nome, - "fila", - "Enfileirado para envio", - emailId, - ); - } - sucessosEmail++; - } else { - adicionarLog( - "email", - destinatario.nome, - "erro", - "Falha ao enfileirar email", - ); - falhasEmail++; - } - } - } catch (error) { - const erro = - error instanceof Error - ? error.message - : "Erro desconhecido"; - console.error( - `Erro ao enviar email para ${destinatario.nome}:`, - error, - ); - adicionarLog( - "email", - destinatario.nome, - "erro", - `Erro: ${erro}`, - ); - falhasEmail++; - } - } else { - adicionarLog( - "email", - destinatario.nome, - "erro", - "Destinatário não possui email cadastrado", - ); - falhasEmail++; - } - } - - progressoEnvio.enviados++; - } catch (error) { - console.error( - `Erro geral ao enviar para ${destinatario.nome}:`, - error, - ); - progressoEnvio.falhas++; - } - } - - // Feedback de envio em massa - let mensagemMassa = agendadaPara - ? `Agendamento em massa concluído! ` - : `Envio em massa concluído! `; - - if (agendadaPara) { - const dataFormatada = format( - new Date(agendadaPara), - "dd/MM/yyyy 'às' HH:mm", - { locale: ptBR }, - ); - mensagemMassa += `Será enviado em: ${dataFormatada}. `; - } - - if (canal === "ambos") { - mensagemMassa += `Chat: ${sucessosChat} ${agendadaPara ? "agendados" : "enviados"}, ${falhasChat} falhas. `; - mensagemMassa += `Email: ${sucessosEmail} ${agendadaPara ? "agendados" : "enviados"}, ${falhasEmail} falhas.`; - } else if (canal === "chat") { - mensagemMassa += `Chat: ${sucessosChat} ${agendadaPara ? "agendados" : "enviados"}, ${falhasChat} falhas.`; - } else if (canal === "email") { - mensagemMassa += `Email: ${sucessosEmail} ${agendadaPara ? "agendados" : "enviados"}, ${falhasEmail} falhas.`; - } - - // Adicionar log resumo - adicionarLog("email", "Sistema", "info", mensagemMassa); - mostrarMensagem("success", mensagemMassa); - } - - // Limpar form - destinatarioId = ""; - enviarParaTodos = false; - templateId = ""; - mensagemPersonalizada = ""; - agendarEnvio = false; - dataAgendamento = ""; - horaAgendamento = ""; - } catch (error) { - const erro = error instanceof Error ? error.message : "Erro desconhecido"; - console.error("Erro ao enviar notificação:", error); - adicionarLog("email", "Sistema", "erro", `Erro geral: ${erro}`); - mostrarMensagem("error", "Erro ao enviar notificação: " + erro); - } finally { - processando = false; - progressoEnvio = { total: 0, enviados: 0, falhas: 0 }; - adicionarLog("email", "Sistema", "info", "Processo de envio finalizado"); - } - } + import { useQuery, useConvexClient } from 'convex-svelte'; + import { api } from '@sgse-app/backend/convex/_generated/api'; + import { format } from 'date-fns'; + import { ptBR } from 'date-fns/locale'; + import type { Id, Doc } from '@sgse-app/backend/convex/_generated/dataModel'; + + // Tipos para agendamentos + type TipoAgendamento = 'email' | 'chat'; + type StatusAgendamento = 'agendado' | 'enviado' | 'cancelado'; + + interface AgendamentoEmail { + _id: Id<'notificacoesEmail'>; + _creationTime: number; + destinatario: string; + destinatarioId: Id<'usuarios'> | undefined; + assunto: string; + corpo: string; + templateId: Id<'templatesMensagens'> | undefined; + status: 'pendente' | 'enviando' | 'enviado' | 'falha'; + agendadaPara: number | undefined; + enviadoPor: Id<'usuarios'>; + criadoEm: number; + enviadoEm: number | undefined; + destinatarioInfo: Doc<'usuarios'> | null; + templateInfo: Doc<'templatesMensagens'> | null; + } + + interface AgendamentoChat { + _id: Id<'mensagens'>; + _creationTime: number; + conversaId: Id<'conversas'>; + remetenteId: Id<'usuarios'>; + conteudo: string; + agendadaPara: number | undefined; + enviadaEm: number; + conversaInfo: Doc<'conversas'> | null; + destinatarioInfo: Doc<'usuarios'> | null; + } + + type Agendamento = + | { tipo: 'email'; dados: AgendamentoEmail } + | { tipo: 'chat'; dados: AgendamentoChat }; + + const client = useConvexClient(); + const currentUser = useQuery(api.auth.getCurrentUser, {}); + + // Queries + const templatesQuery = useQuery(api.templatesMensagens.listarTemplates, {}); + const usuariosQuery = useQuery(api.usuarios.listar, {}); + + // Mapa de emailIds para rastrear status + let emailIdsRastreados = $state>(new Set()); + + // Query para buscar status dos emails (só executa quando há IDs) + const emailIdsArray = $derived( + Array.from(emailIdsRastreados).map((id) => id as Id<'notificacoesEmail'>) + ); + // Usar função para evitar execução quando array está vazio + const emailsStatusQuery = $derived.by(() => { + if (emailIdsArray.length === 0) return null; + return useQuery(api.email.buscarEmailsPorIds, { emailIds: emailIdsArray }); + }); + + // Queries para agendamentos + const agendamentosEmailQuery = useQuery(api.email.listarAgendamentosEmail, {}); + const agendamentosChatQuery = useQuery(api.chat.listarAgendamentosChat, {}); + + // Filtro de agendamentos + type FiltroAgendamento = 'todos' | 'agendados' | 'enviados'; + let filtroAgendamento = $state('todos'); + + // Extrair dados das queries de forma robusta + const templates = $derived.by(() => { + if (templatesQuery === undefined || templatesQuery === null) { + return []; + } + if ('data' in templatesQuery && templatesQuery.data !== undefined) { + return Array.isArray(templatesQuery.data) ? templatesQuery.data : []; + } + if (Array.isArray(templatesQuery)) { + return templatesQuery; + } + return []; + }); + + const usuarios = $derived.by(() => { + if (usuariosQuery === undefined || usuariosQuery === null) { + return []; + } + if ('data' in usuariosQuery && usuariosQuery.data !== undefined) { + return Array.isArray(usuariosQuery.data) ? usuariosQuery.data : []; + } + if (Array.isArray(usuariosQuery)) { + return usuariosQuery; + } + return []; + }); + + // Estados de carregamento e erro + const carregandoTemplates = $derived(templatesQuery === undefined || templatesQuery === null); + const carregandoUsuarios = $derived(usuariosQuery === undefined || usuariosQuery === null); + + // Verificar erros de forma mais robusta + const erroTemplates = $derived.by(() => { + if (templatesQuery === undefined || templatesQuery === null) return false; + // Verificar se é um objeto com propriedade error + if (typeof templatesQuery === 'object' && 'error' in templatesQuery) { + return templatesQuery.error !== undefined && templatesQuery.error !== null; + } + return false; + }); + + const erroUsuarios = $derived.by(() => { + if (usuariosQuery === undefined || usuariosQuery === null) return false; + if (typeof usuariosQuery === 'object' && 'error' in usuariosQuery) { + return usuariosQuery.error !== undefined && usuariosQuery.error !== null; + } + return false; + }); + + // Log para debug (remover depois se necessário) + $effect(() => { + if (templatesQuery !== undefined && templatesQuery !== null) { + console.log('Templates Query:', templatesQuery); + console.log('Templates Extraídos:', templates); + } + }); + + let destinatarioId = $state(''); + let enviarParaTodos = $state(false); + let canal = $state<'chat' | 'email' | 'ambos'>('chat'); + let templateId = $state(''); + let mensagemPersonalizada = $state(''); + let usarTemplate = $state(true); + let processando = $state(false); + let criandoTemplates = $state(false); + let progressoEnvio = $state({ total: 0, enviados: 0, falhas: 0 }); + + // Estrutura de dados para logs de envio + type StatusLog = 'sucesso' | 'erro' | 'fila' | 'info' | 'enviando'; + type TipoLog = 'chat' | 'email'; + type LogEnvio = { + timestamp: number; + tipo: TipoLog; + destinatario: string; + status: StatusLog; + mensagem: string; + emailId?: string; // Para emails, guardar o ID para rastrear status + }; + + let logsEnvio = $state([]); + let terminalScrollRef: HTMLDivElement | null = $state(null); + + // Estados para agendamento + let agendarEnvio = $state(false); + let dataAgendamento = $state(''); + let horaAgendamento = $state(''); + + // Calcular data/hora mínimas + const now = new Date(); + const minDate = format(now, 'yyyy-MM-dd'); + + // Hora mínima recalculada baseada na data selecionada + const horaMinima = $derived.by(() => { + if (!dataAgendamento) return format(now, 'HH:mm'); + const hoje = format(now, 'yyyy-MM-dd'); + if (dataAgendamento === hoje) { + return format(now, 'HH:mm'); + } + return undefined; // Sem restrição se for data futura + }); + + function getPreviewAgendamento(): string { + if (!agendarEnvio || !dataAgendamento || !horaAgendamento) return ''; + try { + const dataHora = new Date(`${dataAgendamento}T${horaAgendamento}`); + return `Será enviada em ${format(dataHora, "dd/MM/yyyy 'às' HH:mm", { locale: ptBR })}`; + } catch { + return ''; + } + } + + // Estados para modal de novo template + let modalNovoTemplateAberto = $state(false); + let codigoTemplate = $state(''); + let nomeTemplate = $state(''); + let tituloTemplate = $state(''); + let corpoTemplate = $state(''); + let variaveisTemplate = $state(''); + let criandoNovoTemplate = $state(false); + + // Estado para mensagens de feedback + let mensagem = $state<{ + tipo: 'success' | 'error' | 'info'; + texto: string; + } | null>(null); + + const templateSelecionado = $derived(templates.find((t) => t._id === templateId)); + + // Função para renderizar template com variáveis (similar à função do backend) + function renderizarTemplate(template: string, variaveis: Record): string { + let resultado = template; + + for (const [chave, valor] of Object.entries(variaveis)) { + const placeholder = `{{${chave}}}`; + resultado = resultado.replace(new RegExp(placeholder, 'g'), valor); + } + + return resultado; + } + + // Função para mostrar mensagens + function mostrarMensagem(tipo: 'success' | 'error' | 'info', texto: string) { + mensagem = { tipo, texto }; + setTimeout(() => { + mensagem = null; + }, 5000); + } + + // Função para adicionar log ao terminal + function adicionarLog( + tipo: TipoLog, + destinatario: string, + status: StatusLog, + mensagem: string, + emailId?: string + ) { + logsEnvio = [ + ...logsEnvio, + { + timestamp: Date.now(), + tipo, + destinatario, + status, + mensagem, + emailId + } + ]; + + // Adicionar emailId ao rastreamento se fornecido + if (emailId) { + emailIdsRastreados = new Set([...emailIdsRastreados, emailId]); + } + + // Auto-scroll para o final + setTimeout(() => { + if (terminalScrollRef) { + terminalScrollRef.scrollTop = terminalScrollRef.scrollHeight; + } + }, 10); + } + + // Atualizar logs quando status dos emails mudar + $effect(() => { + if (!emailsStatusQuery || emailsStatusQuery === undefined) return; + + // Extrair dados da query + const emails = Array.isArray(emailsStatusQuery) + ? emailsStatusQuery + : 'data' in emailsStatusQuery && Array.isArray(emailsStatusQuery.data) + ? emailsStatusQuery.data + : []; + + if (emails.length === 0) return; + + for (const email of emails) { + if (!email) continue; + + // Encontrar logs relacionados a este email + const logsRelacionados = logsEnvio.filter((log) => log.emailId === email._id); + + for (const log of logsRelacionados) { + // Verificar se o status mudou + const novoStatus: StatusLog = + email.status === 'enviado' + ? 'sucesso' + : email.status === 'falha' + ? 'erro' + : email.status === 'enviando' + ? 'enviando' + : email.status === 'pendente' + ? 'fila' + : 'info'; + + // Se o status mudou, atualizar o log + if (log.status !== novoStatus) { + const indice = logsEnvio.findIndex((l) => l === log); + if (indice !== -1) { + const novaMensagem = + email.status === 'enviado' + ? 'Email enviado com sucesso' + : email.status === 'falha' + ? `Falha ao enviar: ${email.erroDetalhes || 'Erro desconhecido'}` + : email.status === 'enviando' + ? 'Enviando email...' + : email.status === 'pendente' + ? 'Email em fila de envio' + : log.mensagem; + + logsEnvio = logsEnvio.map((l, i) => + i === indice ? { ...l, status: novoStatus, mensagem: novaMensagem } : l + ); + } + } + } + } + }); + + // Função para limpar logs + function limparLogs() { + logsEnvio = []; + emailIdsRastreados = new Set(); + } + + // Extrair e processar agendamentos + const agendamentosEmail = $derived.by(() => { + if (!agendamentosEmailQuery || agendamentosEmailQuery === undefined) return []; + const dados = Array.isArray(agendamentosEmailQuery) + ? agendamentosEmailQuery + : 'data' in agendamentosEmailQuery && Array.isArray(agendamentosEmailQuery.data) + ? agendamentosEmailQuery.data + : []; + return dados as AgendamentoEmail[]; + }); + + const agendamentosChat = $derived.by(() => { + if (!agendamentosChatQuery || agendamentosChatQuery === undefined) return []; + const dados = Array.isArray(agendamentosChatQuery) + ? agendamentosChatQuery + : 'data' in agendamentosChatQuery && Array.isArray(agendamentosChatQuery.data) + ? agendamentosChatQuery.data + : []; + return dados as AgendamentoChat[]; + }); + + // Combinar e processar agendamentos + const todosAgendamentos = $derived.by(() => { + const agendamentos: Agendamento[] = []; + + for (const email of agendamentosEmail) { + if (email.agendadaPara) { + agendamentos.push({ + tipo: 'email', + dados: email + }); + } + } + + for (const chat of agendamentosChat) { + if (chat.agendadaPara) { + agendamentos.push({ + tipo: 'chat', + dados: chat + }); + } + } + + // Ordenar: futuros primeiro (mais próximos primeiro), depois passados (mais recentes primeiro) + return agendamentos.sort((a, b) => { + const timestampA = + a.tipo === 'email' ? (a.dados.agendadaPara ?? 0) : (a.dados.agendadaPara ?? 0); + const timestampB = + b.tipo === 'email' ? (b.dados.agendadaPara ?? 0) : (b.dados.agendadaPara ?? 0); + const agora = Date.now(); + + const aFuturo = timestampA > agora; + const bFuturo = timestampB > agora; + + // Futuros primeiro + if (aFuturo && !bFuturo) return -1; + if (!aFuturo && bFuturo) return 1; + + // Dentro do mesmo grupo, ordenar por timestamp + if (aFuturo) { + // Futuros: mais próximos primeiro + return timestampA - timestampB; + } else { + // Passados: mais recentes primeiro + return timestampB - timestampA; + } + }); + }); + + // Filtrar agendamentos + const agendamentosFiltrados = $derived.by(() => { + if (filtroAgendamento === 'todos') return todosAgendamentos; + + return todosAgendamentos.filter((ag) => { + const status = obterStatusAgendamento(ag); + if (filtroAgendamento === 'agendados') return status === 'agendado'; + if (filtroAgendamento === 'enviados') return status === 'enviado'; + return true; + }); + }); + + // Função para obter status do agendamento + function obterStatusAgendamento(agendamento: Agendamento): StatusAgendamento { + if (agendamento.tipo === 'email') { + const email = agendamento.dados; + if (email.status === 'enviado') return 'enviado'; + if (email.agendadaPara && email.agendadaPara <= Date.now()) return 'enviado'; + return 'agendado'; + } else { + const chat = agendamento.dados; + if (chat.agendadaPara && chat.agendadaPara <= Date.now()) return 'enviado'; + return 'agendado'; + } + } + + // Função para cancelar agendamento + async function cancelarAgendamento(agendamento: Agendamento) { + if (!confirm('Tem certeza que deseja cancelar este agendamento?')) { + return; + } + + try { + if (agendamento.tipo === 'email') { + const resultado = await client.mutation(api.email.cancelarAgendamentoEmail, { + emailId: agendamento.dados._id + }); + if (resultado.sucesso) { + mostrarMensagem('success', 'Agendamento de email cancelado com sucesso!'); + } else { + mostrarMensagem('error', resultado.erro || 'Erro ao cancelar agendamento'); + } + } else { + const resultado = await client.mutation(api.chat.cancelarMensagemAgendada, { + mensagemId: agendamento.dados._id + }); + if (resultado.sucesso) { + mostrarMensagem('success', 'Agendamento de chat cancelado com sucesso!'); + } else { + mostrarMensagem('error', resultado.erro || 'Erro ao cancelar agendamento'); + } + } + } catch (error) { + const erro = error instanceof Error ? error.message : 'Erro desconhecido'; + mostrarMensagem('error', `Erro ao cancelar agendamento: ${erro}`); + } + } + + // Função para obter nome do destinatário + function obterNomeDestinatario(agendamento: Agendamento): string { + if (agendamento.tipo === 'email') { + return ( + agendamento.dados.destinatarioInfo?.nome || agendamento.dados.destinatario || 'Usuário' + ); + } else { + return agendamento.dados.destinatarioInfo?.nome || 'Usuário'; + } + } + + // Função para formatar data/hora do agendamento + function formatarDataAgendamento(agendamento: Agendamento): string { + const timestamp = + agendamento.tipo === 'email' + ? agendamento.dados.agendadaPara + : agendamento.dados.agendadaPara; + + if (!timestamp) return 'N/A'; + + return format(new Date(timestamp), "dd/MM/yyyy 'às' HH:mm", { + locale: ptBR + }); + } + + // Função para formatar timestamp + function formatarTimestamp(timestamp: number): string { + return format(new Date(timestamp), 'HH:mm:ss', { locale: ptBR }); + } + + // Função para obter cor do status + function obterCorStatus(status: StatusLog): string { + switch (status) { + case 'sucesso': + return 'text-success'; + case 'erro': + return 'text-error'; + case 'fila': + return 'text-warning'; + case 'enviando': + return 'text-info'; + case 'info': + return 'text-info'; + default: + return 'text-base-content'; + } + } + + async function criarTemplatesPadrao() { + if (criandoTemplates) return; + + criandoTemplates = true; + try { + const resultado = await client.mutation(api.templatesMensagens.criarTemplatesPadrao, {}); + if (resultado.sucesso) { + mostrarMensagem( + 'success', + 'Templates padrão criados com sucesso! A página será recarregada.' + ); + setTimeout(() => { + window.location.reload(); + }, 2000); + } else { + mostrarMensagem('error', 'Erro ao criar templates padrão.'); + } + } catch (error) { + const erro = error instanceof Error ? error.message : 'Erro desconhecido'; + console.error('Erro ao criar templates:', error); + mostrarMensagem('error', 'Erro ao criar templates: ' + erro); + } finally { + criandoTemplates = false; + } + } + + function abrirModalNovoTemplate() { + modalNovoTemplateAberto = true; + // Limpar campos + codigoTemplate = ''; + nomeTemplate = ''; + tituloTemplate = ''; + corpoTemplate = ''; + variaveisTemplate = ''; + } + + function fecharModalNovoTemplate() { + modalNovoTemplateAberto = false; + } + + async function salvarNovoTemplate() { + if (!currentUser?.data) { + mostrarMensagem('error', 'Você precisa estar autenticado para criar templates.'); + return; + } + + // Validações + if (!codigoTemplate.trim()) { + mostrarMensagem('error', 'O código do template é obrigatório.'); + return; + } + if (!nomeTemplate.trim()) { + mostrarMensagem('error', 'O nome do template é obrigatório.'); + return; + } + if (!tituloTemplate.trim()) { + mostrarMensagem('error', 'O título do template é obrigatório.'); + return; + } + if (!corpoTemplate.trim()) { + mostrarMensagem('error', 'O corpo do template é obrigatório.'); + return; + } + + // Processar variáveis (separadas por vírgula ou espaço) + const variaveis = variaveisTemplate + .split(/[,;\s]+/) + .map((v) => v.trim()) + .filter((v) => v.length > 0); + + criandoNovoTemplate = true; + try { + const resultado = await client.mutation(api.templatesMensagens.criarTemplate, { + codigo: codigoTemplate.trim().toUpperCase().replace(/\s+/g, '_'), + nome: nomeTemplate.trim(), + titulo: tituloTemplate.trim(), + corpo: corpoTemplate.trim(), + variaveis: variaveis.length > 0 ? variaveis : undefined, + criadoPorId: currentUser.data._id as Id<'usuarios'> + }); + + if (resultado.sucesso) { + mostrarMensagem('success', 'Template criado com sucesso!'); + fecharModalNovoTemplate(); + // Recarregar a página para atualizar a lista + setTimeout(() => { + window.location.reload(); + }, 1500); + } else { + mostrarMensagem( + 'error', + 'Erro ao criar template: ' + (resultado.erro || 'Erro desconhecido') + ); + } + } catch (error) { + const erro = error instanceof Error ? error.message : 'Erro desconhecido'; + console.error('Erro ao criar template:', error); + mostrarMensagem('error', 'Erro ao criar template: ' + erro); + } finally { + criandoNovoTemplate = false; + } + } + + async function enviarNotificacao() { + if (!enviarParaTodos && !destinatarioId) { + mostrarMensagem('error', "Selecione um destinatário ou marque 'Enviar para todos'"); + return; + } + + if (usarTemplate && !templateId) { + mostrarMensagem('error', 'Selecione um template'); + return; + } + + if (!usarTemplate && !mensagemPersonalizada.trim()) { + mostrarMensagem('error', 'Digite uma mensagem'); + return; + } + + // Validar agendamento se marcado + let agendadaPara: number | undefined = undefined; + if (agendarEnvio) { + if (!dataAgendamento || !horaAgendamento) { + mostrarMensagem('error', 'Preencha a data e hora para agendamento'); + return; + } + + try { + const dataHora = new Date(`${dataAgendamento}T${horaAgendamento}`); + if (dataHora.getTime() <= Date.now()) { + mostrarMensagem('error', 'A data e hora devem ser futuras'); + return; + } + agendadaPara = dataHora.getTime(); + } catch (error) { + mostrarMensagem('error', 'Data ou hora inválida'); + return; + } + } + + processando = true; + progressoEnvio = { total: 0, enviados: 0, falhas: 0 }; + + // Limpar logs anteriores quando iniciar novo envio + logsEnvio = []; + emailIdsRastreados = new Set(); + + try { + // Obter lista de destinatários + const destinatarios: typeof usuarios = enviarParaTodos + ? usuarios + : usuarios.filter((u) => u._id === destinatarioId); + + if (destinatarios.length === 0) { + adicionarLog('email', 'Sistema', 'erro', 'Nenhum destinatário encontrado'); + mostrarMensagem('error', 'Nenhum destinatário encontrado'); + return; + } + + progressoEnvio.total = destinatarios.length; + + // Log inicial + const tipoMensagem = usarTemplate + ? `Template: ${templateSelecionado?.nome || ''}` + : 'Mensagem personalizada'; + const destinatariosText = enviarParaTodos + ? `Todos os usuários (${destinatarios.length})` + : destinatarios.map((d) => d.nome).join(', '); + adicionarLog( + 'email', + 'Sistema', + 'info', + `Iniciando envio de notificação via ${canal} para ${destinatariosText} - ${tipoMensagem}` + ); + + // Se for envio para um único usuário + if (destinatarios.length === 1) { + const destinatario = destinatarios[0]; + let resultadoChat = null; + let resultadoEmail = null; + + // ENVIAR PARA CHAT + if (canal === 'chat' || canal === 'ambos') { + try { + adicionarLog('chat', destinatario.nome, 'enviando', 'Criando/buscando conversa...'); + const conversaId = await client.mutation(api.chat.criarOuBuscarConversaIndividual, { + outroUsuarioId: destinatario._id as Id<'usuarios'> + }); + + if (conversaId) { + const mensagem = + usarTemplate && templateSelecionado + ? renderizarTemplate(templateSelecionado.corpo, { + nome: destinatario.nome, + matricula: destinatario.matricula || '' + }) + : mensagemPersonalizada; + + if (agendadaPara) { + // Agendar mensagem + adicionarLog('chat', destinatario.nome, 'info', 'Agendando mensagem...'); + resultadoChat = await client.mutation(api.chat.agendarMensagem, { + conversaId: conversaId, + conteudo: mensagem, + agendadaPara: agendadaPara + }); + const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { + locale: ptBR + }); + adicionarLog( + 'chat', + destinatario.nome, + 'sucesso', + `Mensagem agendada para ${dataFormatada}` + ); + } else { + // Envio imediato + adicionarLog('chat', destinatario.nome, 'enviando', 'Enviando mensagem...'); + resultadoChat = await client.mutation(api.chat.enviarMensagem, { + conversaId: conversaId, + conteudo: mensagem, + tipo: 'texto', + permitirNotificacaoParaSiMesmo: true + }); + adicionarLog('chat', destinatario.nome, 'sucesso', 'Mensagem enviada com sucesso'); + } + } else { + adicionarLog('chat', destinatario.nome, 'erro', 'Falha ao criar/buscar conversa'); + } + } catch (error) { + const erro = error instanceof Error ? error.message : 'Erro desconhecido'; + console.error('Erro ao enviar chat:', error); + adicionarLog('chat', destinatario.nome, 'erro', `Erro: ${erro}`); + } + } + + // ENVIAR PARA EMAIL + if (canal === 'email' || canal === 'ambos') { + if (destinatario.email) { + try { + adicionarLog( + 'email', + destinatario.nome, + 'enviando', + `Enfileirando email para ${destinatario.email}...` + ); + if (usarTemplate && templateId) { + const template = templateSelecionado; + if (template) { + const emailId = await client.action(api.email.enviarEmailComTemplate, { + destinatario: destinatario.email, + destinatarioId: destinatario._id as Id<'usuarios'>, + templateCodigo: template.codigo, + variaveis: { + nome: destinatario.nome, + matricula: destinatario.matricula + }, + enviadoPor: currentUser.data._id as Id<'usuarios'>, + agendadaPara: agendadaPara + }); + if (emailId) { + if (agendadaPara) { + const dataFormatada = format( + new Date(agendadaPara), + "dd/MM/yyyy 'às' HH:mm", + { locale: ptBR } + ); + adicionarLog( + 'email', + destinatario.nome, + 'fila', + `Email agendado para ${dataFormatada}`, + emailId + ); + } else { + adicionarLog( + 'email', + destinatario.nome, + 'fila', + 'Email enfileirado para envio', + emailId + ); + } + } else { + adicionarLog('email', destinatario.nome, 'erro', 'Falha ao enfileirar email'); + } + } else { + adicionarLog('email', destinatario.nome, 'erro', 'Template não encontrado'); + } + } else { + const emailId = await client.mutation(api.email.enfileirarEmail, { + destinatario: destinatario.email, + destinatarioId: destinatario._id as Id<'usuarios'>, + assunto: 'Notificação do Sistema', + corpo: mensagemPersonalizada, + enviadoPor: currentUser.data._id as Id<'usuarios'>, + agendadaPara: agendadaPara + }); + if (emailId) { + resultadoEmail = { sucesso: true, emailId }; + if (agendadaPara) { + const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { + locale: ptBR + }); + adicionarLog( + 'email', + destinatario.nome, + 'fila', + `Email agendado para ${dataFormatada}`, + emailId + ); + } else { + adicionarLog( + 'email', + destinatario.nome, + 'fila', + 'Email enfileirado para envio', + emailId + ); + } + } else { + adicionarLog('email', destinatario.nome, 'erro', 'Falha ao enfileirar email'); + } + } + } catch (error) { + const erro = error instanceof Error ? error.message : 'Erro desconhecido'; + console.error('Erro ao enviar email:', error); + adicionarLog('email', destinatario.nome, 'erro', `Erro: ${erro}`); + } + } else { + adicionarLog( + 'email', + destinatario.nome, + 'erro', + 'Destinatário não possui email cadastrado' + ); + } + } + + // Feedback de sucesso + let mensagemSucesso = agendadaPara + ? `Notificação agendada com sucesso!` + : 'Notificação enviada com sucesso!'; + if (agendadaPara) { + const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { + locale: ptBR + }); + mensagemSucesso += ` Será enviada em: ${dataFormatada}`; + } else { + if (canal === 'ambos') { + if (resultadoChat && resultadoEmail) { + mensagemSucesso = 'Notificação enviada para Chat e Email!'; + } else if (resultadoChat) { + mensagemSucesso = 'Notificação enviada para Chat. Email falhou.'; + } else if (resultadoEmail) { + mensagemSucesso = 'Notificação enviada para Email. Chat falhou.'; + } + } else if (canal === 'chat' && resultadoChat) { + mensagemSucesso = 'Mensagem enviada no Chat!'; + } else if (canal === 'email' && resultadoEmail) { + mensagemSucesso = 'Email enfileirado para envio!'; + } + } + + mostrarMensagem('success', mensagemSucesso); + progressoEnvio.enviados = 1; + } else { + // ENVIO EM MASSA + let sucessosChat = 0; + let sucessosEmail = 0; + let falhasChat = 0; + let falhasEmail = 0; + + adicionarLog( + 'email', + 'Sistema', + 'info', + `Processando ${destinatarios.length} destinatários...` + ); + + for (const destinatario of destinatarios) { + try { + // ENVIAR PARA CHAT + if (canal === 'chat' || canal === 'ambos') { + try { + adicionarLog('chat', destinatario.nome, 'enviando', 'Processando...'); + const conversaId = await client.mutation(api.chat.criarOuBuscarConversaIndividual, { + outroUsuarioId: destinatario._id as Id<'usuarios'> + }); + + if (conversaId) { + // Renderizar template com variáveis do destinatário + const mensagem = + usarTemplate && templateSelecionado + ? renderizarTemplate(templateSelecionado.corpo, { + nome: destinatario.nome, + matricula: destinatario.matricula || '' + }) + : mensagemPersonalizada; + + if (agendadaPara) { + await client.mutation(api.chat.agendarMensagem, { + conversaId: conversaId, + conteudo: mensagem, + agendadaPara: agendadaPara + }); + const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { + locale: ptBR + }); + adicionarLog( + 'chat', + destinatario.nome, + 'sucesso', + `Agendado para ${dataFormatada}` + ); + } else { + await client.mutation(api.chat.enviarMensagem, { + conversaId: conversaId, + conteudo: mensagem, + tipo: 'texto', + permitirNotificacaoParaSiMesmo: true + }); + adicionarLog('chat', destinatario.nome, 'sucesso', 'Enviado com sucesso'); + } + sucessosChat++; + } else { + adicionarLog('chat', destinatario.nome, 'erro', 'Falha ao criar/buscar conversa'); + falhasChat++; + } + } catch (error) { + const erro = error instanceof Error ? error.message : 'Erro desconhecido'; + console.error(`Erro ao enviar chat para ${destinatario.nome}:`, error); + adicionarLog('chat', destinatario.nome, 'erro', `Erro: ${erro}`); + falhasChat++; + } + } + + // ENVIAR PARA EMAIL + if (canal === 'email' || canal === 'ambos') { + if (destinatario.email) { + try { + adicionarLog( + 'email', + destinatario.nome, + 'enviando', + `Enfileirando email para ${destinatario.email}...` + ); + if (usarTemplate && templateId) { + const template = templateSelecionado; + if (template) { + const emailId = await client.action(api.email.enviarEmailComTemplate, { + destinatario: destinatario.email, + destinatarioId: destinatario._id as Id<'usuarios'>, + templateCodigo: template.codigo, + variaveis: { + nome: destinatario.nome, + matricula: destinatario.matricula || '' + }, + enviadoPor: currentUser.data._id as Id<'usuarios'>, + agendadaPara: agendadaPara + }); + if (emailId) { + if (agendadaPara) { + const dataFormatada = format( + new Date(agendadaPara), + "dd/MM/yyyy 'às' HH:mm", + { locale: ptBR } + ); + adicionarLog( + 'email', + destinatario.nome, + 'fila', + `Agendado para ${dataFormatada}`, + emailId + ); + } else { + adicionarLog( + 'email', + destinatario.nome, + 'fila', + 'Enfileirado para envio', + emailId + ); + } + sucessosEmail++; + } else { + adicionarLog( + 'email', + destinatario.nome, + 'erro', + 'Falha ao enfileirar email' + ); + falhasEmail++; + } + } else { + adicionarLog('email', destinatario.nome, 'erro', 'Template não encontrado'); + falhasEmail++; + } + } else { + const emailId = await client.mutation(api.email.enfileirarEmail, { + destinatario: destinatario.email, + destinatarioId: destinatario._id as Id<'usuarios'>, + assunto: 'Notificação do Sistema', + corpo: mensagemPersonalizada, + enviadoPor: currentUser.data?._id as Id<'usuarios'>, + agendadaPara: agendadaPara + }); + if (emailId) { + resultadoEmail = { sucesso: true, emailId }; + if (agendadaPara) { + const dataFormatada = format( + new Date(agendadaPara), + "dd/MM/yyyy 'às' HH:mm", + { locale: ptBR } + ); + adicionarLog( + 'email', + destinatario.nome, + 'fila', + `Agendado para ${dataFormatada}`, + emailId + ); + } else { + adicionarLog( + 'email', + destinatario.nome, + 'fila', + 'Enfileirado para envio', + emailId + ); + } + sucessosEmail++; + } else { + adicionarLog('email', destinatario.nome, 'erro', 'Falha ao enfileirar email'); + falhasEmail++; + } + } + } catch (error) { + const erro = error instanceof Error ? error.message : 'Erro desconhecido'; + console.error(`Erro ao enviar email para ${destinatario.nome}:`, error); + adicionarLog('email', destinatario.nome, 'erro', `Erro: ${erro}`); + falhasEmail++; + } + } else { + adicionarLog( + 'email', + destinatario.nome, + 'erro', + 'Destinatário não possui email cadastrado' + ); + falhasEmail++; + } + } + + progressoEnvio.enviados++; + } catch (error) { + console.error(`Erro geral ao enviar para ${destinatario.nome}:`, error); + progressoEnvio.falhas++; + } + } + + // Feedback de envio em massa + let mensagemMassa = agendadaPara + ? `Agendamento em massa concluído! ` + : `Envio em massa concluído! `; + + if (agendadaPara) { + const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { + locale: ptBR + }); + mensagemMassa += `Será enviado em: ${dataFormatada}. `; + } + + if (canal === 'ambos') { + mensagemMassa += `Chat: ${sucessosChat} ${agendadaPara ? 'agendados' : 'enviados'}, ${falhasChat} falhas. `; + mensagemMassa += `Email: ${sucessosEmail} ${agendadaPara ? 'agendados' : 'enviados'}, ${falhasEmail} falhas.`; + } else if (canal === 'chat') { + mensagemMassa += `Chat: ${sucessosChat} ${agendadaPara ? 'agendados' : 'enviados'}, ${falhasChat} falhas.`; + } else if (canal === 'email') { + mensagemMassa += `Email: ${sucessosEmail} ${agendadaPara ? 'agendados' : 'enviados'}, ${falhasEmail} falhas.`; + } + + // Adicionar log resumo + adicionarLog('email', 'Sistema', 'info', mensagemMassa); + mostrarMensagem('success', mensagemMassa); + } + + // Limpar form + destinatarioId = ''; + enviarParaTodos = false; + templateId = ''; + mensagemPersonalizada = ''; + agendarEnvio = false; + dataAgendamento = ''; + horaAgendamento = ''; + } catch (error) { + const erro = error instanceof Error ? error.message : 'Erro desconhecido'; + console.error('Erro ao enviar notificação:', error); + adicionarLog('email', 'Sistema', 'erro', `Erro geral: ${erro}`); + mostrarMensagem('error', 'Erro ao enviar notificação: ' + erro); + } finally { + processando = false; + progressoEnvio = { total: 0, enviados: 0, falhas: 0 }; + adicionarLog('email', 'Sistema', 'info', 'Processo de envio finalizado'); + } + } -
- -
-
-
- - - -
-
-

- Notificações e Mensagens -

-

- Enviar notificações para usuários do sistema -

-
-
-
+
+ +
+
+
+ + + +
+
+

Notificações e Mensagens

+

Enviar notificações para usuários do sistema

+
+
+
- - {#if mensagem} -
- - {#if mensagem.tipo === "success"} - - {:else if mensagem.tipo === "error"} - - {:else} - - {/if} - - {mensagem.texto} - -
- {/if} + + {#if mensagem} +
+ + {#if mensagem.tipo === 'success'} + + {:else if mensagem.tipo === 'error'} + + {:else} + + {/if} + + {mensagem.texto} + +
+ {/if} -
- -
-
-

Enviar Notificação

+
+ +
+
+

Enviar Notificação

- -
-
- - -
- - {#if enviarParaTodos} - - {/if} -
+ +
+
+ + +
+ + {#if enviarParaTodos} + + {/if} +
- -
-
- Canal de Envio * -
-
- - - -
-
+ +
+
+ Canal de Envio * +
+
+ + + +
+
- -
-
- Tipo de Mensagem -
-
- - -
-
+ +
+
+ Tipo de Mensagem +
+
+ + +
+
- {#if usarTemplate} - -
- - -
+ {#if usarTemplate} + +
+ + +
- {#if templateSelecionado} -
- - - -
-
{templateSelecionado.titulo}
-
{templateSelecionado.corpo}
-
-
- {/if} - {:else} - -
- - -
- {/if} + {#if templateSelecionado} +
+ + + +
+
{templateSelecionado.titulo}
+
{templateSelecionado.corpo}
+
+
+ {/if} + {:else} + +
+ + +
+ {/if} - -
- + +
+ - {#if agendarEnvio} -
-
- - -
+ {#if agendarEnvio} +
+
+ + +
-
- - -
-
+
+ + +
+
- {#if getPreviewAgendamento()} -
- - - - {getPreviewAgendamento()} -
- {/if} - {/if} -
+ {#if getPreviewAgendamento()} +
+ + + + {getPreviewAgendamento()} +
+ {/if} + {/if} +
- -
- -
+ +
+ +
- -
-
- - {#if logsEnvio.length > 0} - - {/if} -
-
- {#if logsEnvio.length === 0} -
- Aguardando envio de notificação... -
- {:else} - {#each logsEnvio as log} -
- [{formatarTimestamp(log.timestamp)}] - - {log.tipo.toUpperCase()} - - {log.destinatario}: - - {log.mensagem} - -
- {/each} - {/if} -
-
-
-
+ +
+
+ + {#if logsEnvio.length > 0} + + {/if} +
+
+ {#if logsEnvio.length === 0} +
Aguardando envio de notificação...
+ {:else} + {#each logsEnvio as log} +
+ [{formatarTimestamp(log.timestamp)}] + + {log.tipo.toUpperCase()} + + {log.destinatario}: + + {log.mensagem} + +
+ {/each} + {/if} +
+
+
+
- -
-
-
-

Templates Disponíveis

- -
+ +
+
+
+

Templates Disponíveis

+ +
- {#if carregandoTemplates} -
- -

Carregando templates...

-
- {:else if templates.length > 0} - -
- {#each templates as template} -
-
-
-
-

{template.nome}

-

{template.titulo}

-

{template.corpo}

-
- - {template.tipo} - - {#if template.variaveis && template.variaveis.length > 0} - - {template.variaveis.length} variáveis - - {/if} -
-
- {#if template.tipo !== "sistema"} - - {/if} -
-
-
- {/each} -
- {:else} - -
- - - -

- Nenhum template disponível -

-

- Clique no botão abaixo para criar os templates padrão do sistema. -

- -
- {/if} -
-
-
+ {#if carregandoTemplates} +
+ +

Carregando templates...

+
+ {:else if templates.length > 0} + +
+ {#each templates as template} +
+
+
+
+

{template.nome}

+

{template.titulo}

+

{template.corpo}

+
+ + {template.tipo} + + {#if template.variaveis && template.variaveis.length > 0} + + {template.variaveis.length} variáveis + + {/if} +
+
+ {#if template.tipo !== 'sistema'} + + {/if} +
+
+
+ {/each} +
+ {:else} + +
+ + + +

Nenhum template disponível

+

+ Clique no botão abaixo para criar os templates padrão do sistema. +

+ +
+ {/if} +
+
+
- -
-
-
-
-
- - - -
-

Histórico de Agendamentos

-
+ +
+
+
+
+
+ + + +
+

Histórico de Agendamentos

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

- Nenhum agendamento encontrado -

-

- Os agendamentos aparecerão aqui quando você agendar envios. -

-
- {:else} - -
- - - - - - - - - - - - - {#each agendamentosFiltrados as agendamento} - {@const status = obterStatusAgendamento(agendamento)} - {@const nomeDestinatario = obterNomeDestinatario(agendamento)} - {@const dataFormatada = formatarDataAgendamento(agendamento)} - {@const podeCancelar = status === "agendado"} - {@const templateNome = - agendamento.tipo === "email" && agendamento.dados.templateInfo - ? agendamento.dados.templateInfo.nome - : agendamento.tipo === "email" && - agendamento.dados.templateId - ? "Template removido" - : "-"} - - - - - - - - - {/each} - -
TipoDestinatárioData/HoraStatusTemplateAções
-
- {#if agendamento.tipo === "email"} - - - - Email - {:else} - - - - Chat - {/if} -
-
-
{nomeDestinatario}
- {#if agendamento.tipo === "email"} -
- {agendamento.dados.destinatario} -
- {/if} -
-
{dataFormatada}
- {#if podeCancelar} - {@const tempoRestante = - agendamento.tipo === "email" - ? (agendamento.dados.agendadaPara ?? 0) - Date.now() - : (agendamento.dados.agendadaPara ?? 0) - Date.now()} - {@const horasRestantes = Math.floor( - tempoRestante / (1000 * 60 * 60), - )} - {@const minutosRestantes = Math.floor( - (tempoRestante % (1000 * 60 * 60)) / (1000 * 60), - )} - {#if horasRestantes < 1 && minutosRestantes < 60} -
- Em {minutosRestantes} min -
- {:else if horasRestantes < 24} -
- Em {horasRestantes}h {minutosRestantes}min -
- {/if} - {/if} -
- {#if status === "agendado"} - Agendado - {:else if status === "enviado"} - Enviado - {:else} - Cancelado - {/if} - - {#if agendamento.tipo === "email"} - {#if agendamento.dados.templateInfo} -
- {agendamento.dados.templateInfo.nome} -
- {:else if agendamento.dados.templateId} -
- Template removido -
- {:else} -
-
- {/if} - {:else} -
-
- {/if} -
- {#if podeCancelar} - - {:else} - - - {/if} -
-
- {/if} -
-
+ {#if agendamentosFiltrados.length === 0} +
+ + + +

Nenhum agendamento encontrado

+

+ Os agendamentos aparecerão aqui quando você agendar envios. +

+
+ {:else} + +
+ + + + + + + + + + + + + {#each agendamentosFiltrados as agendamento} + {@const status = obterStatusAgendamento(agendamento)} + {@const nomeDestinatario = obterNomeDestinatario(agendamento)} + {@const dataFormatada = formatarDataAgendamento(agendamento)} + {@const podeCancelar = status === 'agendado'} + {@const templateNome = + agendamento.tipo === 'email' && agendamento.dados.templateInfo + ? agendamento.dados.templateInfo.nome + : agendamento.tipo === 'email' && agendamento.dados.templateId + ? 'Template removido' + : '-'} + + + + + + + + + {/each} + +
TipoDestinatárioData/HoraStatusTemplateAções
+
+ {#if agendamento.tipo === 'email'} + + + + Email + {:else} + + + + Chat + {/if} +
+
+
{nomeDestinatario}
+ {#if agendamento.tipo === 'email'} +
+ {agendamento.dados.destinatario} +
+ {/if} +
+
{dataFormatada}
+ {#if podeCancelar} + {@const tempoRestante = + agendamento.tipo === 'email' + ? (agendamento.dados.agendadaPara ?? 0) - Date.now() + : (agendamento.dados.agendadaPara ?? 0) - Date.now()} + {@const horasRestantes = Math.floor(tempoRestante / (1000 * 60 * 60))} + {@const minutosRestantes = Math.floor( + (tempoRestante % (1000 * 60 * 60)) / (1000 * 60) + )} + {#if horasRestantes < 1 && minutosRestantes < 60} +
+ Em {minutosRestantes} min +
+ {:else if horasRestantes < 24} +
+ Em {horasRestantes}h {minutosRestantes}min +
+ {/if} + {/if} +
+ {#if status === 'agendado'} + Agendado + {:else if status === 'enviado'} + Enviado + {:else} + Cancelado + {/if} + + {#if agendamento.tipo === 'email'} + {#if agendamento.dados.templateInfo} +
+ {agendamento.dados.templateInfo.nome} +
+ {:else if agendamento.dados.templateId} +
Template removido
+ {:else} +
-
+ {/if} + {:else} +
-
+ {/if} +
+ {#if podeCancelar} + + {:else} + - + {/if} +
+
+ {/if} +
+
- -
- - - - Para enviar emails, certifique-se de configurar o SMTP em Configurações - de Email. -
+ +
+ + + + Para enviar emails, certifique-se de configurar o SMTP em Configurações de Email. +
{#if modalNovoTemplateAberto} - {/if} diff --git a/apps/web/src/routes/(dashboard)/ti/painel-permissoes/+page.svelte b/apps/web/src/routes/(dashboard)/ti/painel-permissoes/+page.svelte index f4225cb..130a768 100644 --- a/apps/web/src/routes/(dashboard)/ti/painel-permissoes/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/painel-permissoes/+page.svelte @@ -284,7 +284,7 @@ Criar novo perfil -
@@ -415,7 +411,7 @@
Perfil filtrado - {/if} -
+ + {#if !carregando && roles.length > 0} +
+
+
+
+ + + +

Filtros de Busca

+
+ {#if temFiltrosAtivos} + + {/if} +
-
- -
- -
- - - - -
-
+
+ +
+ +
+ + + + +
+
- -
- - -
+ +
+ + +
- -
- - -
-
+ +
+ + +
+
-
-
- {rolesFiltradas.length} - de - {roles.length} - perfil(is) - {#if temFiltrosAtivos} - Filtrado - {/if} -
-
-
-
- {/if} +
+
+ {rolesFiltradas.length} + de + {roles.length} + perfil(is) + {#if temFiltrosAtivos} + Filtrado + {/if} +
+
+ + + {/if} - - {#if carregando} -
- -
- {:else if roles.length === 0} -
- - - -

Nenhum perfil encontrado

-

- Não há perfis cadastrados no sistema. -

-
- {:else if rolesFiltradas.length === 0} -
-
-
- - - -

Nenhum perfil encontrado

-

- Nenhum perfil corresponde aos filtros aplicados. -

- {#if temFiltrosAtivos} - - {/if} -
-
-
- {:else} -
- {#each rolesFiltradas as role} -
abrirDetalhes(role)} - > -
-
-
-

{role.descricao}

-
- {obterTextoNivel(role.nivel)} -
-
-
- - - -
-
+ + {#if carregando} +
+ +
+ {:else if roles.length === 0} +
+ + + +

Nenhum perfil encontrado

+

Não há perfis cadastrados no sistema.

+
+ {:else if rolesFiltradas.length === 0} +
+
+
+ + + +

Nenhum perfil encontrado

+

+ Nenhum perfil corresponde aos filtros aplicados. +

+ {#if temFiltrosAtivos} + + {/if} +
+
+
+ {:else} +
+ {#each rolesFiltradas as role} +
abrirDetalhes(role)} + > +
+
+
+

{role.descricao}

+
+ {obterTextoNivel(role.nivel)} +
+
+
+ + + +
+
-
-
- - - - Nome técnico: - {role.nome} -
+
+
+ + + + Nome técnico: + {role.nome} +
- {#if role.setor} -
- - - - Setor: - {role.setor} -
- {/if} + {#if role.setor} +
+ + + + Setor: + {role.setor} +
+ {/if} -
- - - - Nível: - {role.nivel} -
-
+
+ + + + Nível: + {role.nivel} +
+
-
- -
-
-
- {/each} -
- {/if} -
+
+ +
+
+
+ {/each} + + {/if} + - - {#if modalDetalhesAberto && roleSelecionada} -
+ {/if} diff --git a/apps/web/src/routes/(dashboard)/ti/personalizar-permissoes/+page.svelte b/apps/web/src/routes/(dashboard)/ti/personalizar-permissoes/+page.svelte index 8aaa195..5ec6985 100644 --- a/apps/web/src/routes/(dashboard)/ti/personalizar-permissoes/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/personalizar-permissoes/+page.svelte @@ -1,107 +1,100 @@ - - - + + + - -
-
-
- - - -
-
-

- Funcionalidade descontinuada -

-

- Agora as permissões são configuradas por ação em cada perfil no painel - de permissões. -

-
- -
-
-
- - - - - A personalização por usuário foi substituída por permissões por ação - por perfil. Utilize o - Painel de Permissões para configurar. - -
+ +
+
+
+ + + +
+
+

Funcionalidade descontinuada

+

+ Agora as permissões são configuradas por ação em cada perfil no painel de permissões. +

+
+ +
+
+
+ + + + + A personalização por usuário foi substituída por permissões por ação + por perfil. Utilize o + Painel de Permissões para configurar. + +
diff --git a/apps/web/src/routes/(dashboard)/ti/solicitacoes-acesso/+page.svelte b/apps/web/src/routes/(dashboard)/ti/solicitacoes-acesso/+page.svelte index 43dfb7f..b63e18f 100644 --- a/apps/web/src/routes/(dashboard)/ti/solicitacoes-acesso/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/solicitacoes-acesso/+page.svelte @@ -1,694 +1,836 @@ - -
- - {#if mensagem} -
- {#if mensagem.tipo === "success"} - - - - {:else if mensagem.tipo === "error"} - - - - {/if} - {mensagem.texto} -
- {/if} + +
+ + {#if mensagem} +
+ {#if mensagem.tipo === 'success'} + + + + {:else if mensagem.tipo === 'error'} + + + + {/if} + {mensagem.texto} +
+ {/if} - -
-
-
- - - -
-
-

Solicitações de Acesso

-

Gerencie e analise solicitações de acesso ao sistema

-
-
-
+ +
+
+
+ + + +
+
+

Solicitações de Acesso

+

+ Gerencie e analise solicitações de acesso ao sistema +

+
+
+
- - {#if stats} -
- - - - - - - -
- {:else} -
- -
- {/if} + + {#if stats} +
+ - -
-
- -
- - - - -
+ 0 + ? ((stats.pendentes / stats.total) * 100).toFixed(1) + '% do total' + : '0% do total'} + Icon={Clock} + color="warning" + /> - -
- -
- - - - -
-
-
-
+ 0 + ? ((stats.aprovadas / stats.total) * 100).toFixed(1) + '% do total' + : '0% do total'} + Icon={CheckCircle2} + color="success" + /> - - {#if carregando} -
- -
- {:else if solicitacoesFiltradas.length === 0} -
-
- - - -

Nenhuma solicitação encontrada

-

- {#if busca.trim() || filtroStatus !== "todos"} - Tente ajustar os filtros ou a busca. - {:else} - Ainda não há solicitações de acesso cadastradas. - {/if} -

-
-
- {:else} -
- {#each solicitacoesFiltradas as solicitacao} -
-
-
-
-
-

{solicitacao.nome}

- - {getStatusTexto(solicitacao.status)} - -
- -
-
- - - - Matrícula: - {solicitacao.matricula} -
- -
- - - - E-mail: - {solicitacao.email} -
- -
- - - - Telefone: - {solicitacao.telefone} -
-
- -
- Solicitado em: {formatarData(solicitacao.dataSolicitacao)} ({formatarDataRelativa(solicitacao.dataSolicitacao)}) - {#if solicitacao.dataResposta} - Processado em: {formatarData(solicitacao.dataResposta)} - {/if} -
-
- -
- - - {#if solicitacao.status === "pendente"} - - - - {/if} -
-
-
-
- {/each} -
- {/if} + 0 + ? ((stats.rejeitadas / stats.total) * 100).toFixed(1) + '% do total' + : '0% do total'} + Icon={XCircle} + color="error" + /> +
+ {:else} +
+ +
+ {/if} - - {#if modalDetalhesAberto && solicitacaoSelecionada} - - - - - {/if} + +
+
+ +
+ + + + +
- - {#if modalAprovarAberto && solicitacaoSelecionada} - - - - - {/if} + +
+ +
+ + + + +
+
+
+
- - {#if modalRejeitarAberto && solicitacaoSelecionada} - - - - - {/if} -
+ + {#if carregando} +
+ +
+ {:else if solicitacoesFiltradas.length === 0} +
+
+ + + +

+ Nenhuma solicitação encontrada +

+

+ {#if busca.trim() || filtroStatus !== 'todos'} + Tente ajustar os filtros ou a busca. + {:else} + Ainda não há solicitações de acesso cadastradas. + {/if} +

+
+
+ {:else} +
+ {#each solicitacoesFiltradas as solicitacao} +
+
+
+
+
+

{solicitacao.nome}

+ + {getStatusTexto(solicitacao.status)} + +
+ +
+
+ + + + Matrícula: + {solicitacao.matricula} +
+ +
+ + + + E-mail: + {solicitacao.email} +
+ +
+ + + + Telefone: + {solicitacao.telefone} +
+
+ +
+ Solicitado em: + {formatarData(solicitacao.dataSolicitacao)} ({formatarDataRelativa( + solicitacao.dataSolicitacao + )}) + {#if solicitacao.dataResposta} + Processado em: + {formatarData(solicitacao.dataResposta)} + {/if} +
+
+ +
+ + + {#if solicitacao.status === 'pendente'} + + + + {/if} +
+
+
+
+ {/each} +
+ {/if} + + + {#if modalDetalhesAberto && solicitacaoSelecionada} + + + + + {/if} + + + {#if modalAprovarAberto && solicitacaoSelecionada} + + + + + {/if} + + + {#if modalRejeitarAberto && solicitacaoSelecionada} + + + + + {/if} +
diff --git a/apps/web/src/routes/(dashboard)/ti/times/+page.svelte b/apps/web/src/routes/(dashboard)/ti/times/+page.svelte index 17ae303..668f23b 100644 --- a/apps/web/src/routes/(dashboard)/ti/times/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/times/+page.svelte @@ -318,7 +318,7 @@
- + @@ -457,7 +455,7 @@

{time.nome}

+ + {#if !carregandoUsuarios && usuarios.length > 0} +
+
+
+

Filtros de Busca

+ +
-
- -
- - -
+
+ +
+ + +
- -
- - -
+ +
+ + +
- -
- - -
+ +
+ + +
- -
- - -
+ +
+ + +
- -
- - -
+ +
+ + +
- -
- - -
+ +
+ + +
- -
- - -
+ +
+ + +
- -
- - -
-
+ +
+ + +
+
-
- Mostrando {usuariosFiltrados.length} de {usuarios.length} usuário(s) -
-
-
- {/if} +
+ Mostrando {usuariosFiltrados.length} de {usuarios.length} usuário(s) +
+ + + {/if} - - {#if carregandoUsuarios} -
- -

Carregando usuários...

-
- {:else if erroUsuarios} -
- - - -
-

Erro ao carregar usuários

-
{erroUsuarios}
-
- Por favor, recarregue a página ou entre em contato com o suporte - técnico se o problema persistir. -
-
-
- {:else if usuarios.length === 0} -
- - - -

Nenhum usuário encontrado

-

- Cadastre um usuário para começar a gestão de acessos. -

-
- {:else} -
-
-

Usuários ({usuarios.length})

+ + {#if carregandoUsuarios} +
+ +

Carregando usuários...

+
+ {:else if erroUsuarios} +
+ + + +
+

Erro ao carregar usuários

+
{erroUsuarios}
+
+ Por favor, recarregue a página ou entre em contato com o suporte técnico se o problema + persistir. +
+
+
+ {:else if usuarios.length === 0} +
+ + + +

Nenhum usuário encontrado

+

+ Cadastre um usuário para começar a gestão de acessos. +

+
+ {:else} +
+
+

Usuários ({usuarios.length})

-
- - - - - - - - - - - - - - - - - - {#each usuariosFiltrados as usuario} - - - - - - - - - - - - - - {/each} - -
MatrículaNomeEmailRole/PerfilSetorFuncionário VinculadoStatusPrimeiro AcessoÚltimo AcessoData de CriaçãoAções
{usuario.matricula}{usuario.nome}{usuario.email} -
- {#if usuario.role.erro} -
- - - - {usuario.role.descricao} -
- {#if usuario.avisos && usuario.avisos.length > 0} -
- -
- {/if} - {:else} -
- {usuario.role.nome} -
- {/if} -
-
{usuario.role.setor || "-"} - {#if usuario.funcionario} -
-
- - - - Associado -
-
- {usuario.funcionario.nome} -
- {#if usuario.funcionario.matricula} -
- Mat: {usuario.funcionario.matricula} -
- {/if} -
- {:else} -
- - - - Não associado -
- {/if} -
- - - {#if usuario.primeiroAcesso} -
Sim
- {:else} -
Não
- {/if} -
- {formatarData(usuario.ultimoAcesso)} - - {formatarData(usuario.criadoEm)} - - -
-
-
-
- {/if} -
+
+ + + + + + + + + + + + + + + + + + {#each usuariosFiltrados as usuario} + + + + + + + + + + + + + + {/each} + +
MatrículaNomeEmailRole/PerfilSetorFuncionário VinculadoStatusPrimeiro AcessoÚltimo AcessoData de CriaçãoAções
{usuario.matricula}{usuario.nome}{usuario.email} +
+ {#if usuario.role.erro} +
+ + + + {usuario.role.descricao} +
+ {#if usuario.avisos && usuario.avisos.length > 0} +
+ +
+ {/if} + {:else} +
+ {usuario.role.nome} +
+ {/if} +
+
{usuario.role.setor || '-'} + {#if usuario.funcionario} +
+
+ + + + Associado +
+
+ {usuario.funcionario.nome} +
+ {#if usuario.funcionario.matricula} +
+ Mat: {usuario.funcionario.matricula} +
+ {/if} +
+ {:else} +
+ + + + Não associado +
+ {/if} +
+ + + {#if usuario.primeiroAcesso} +
Sim
+ {:else} +
Não
+ {/if} +
+ {formatarData(usuario.ultimoAcesso)} + + {formatarData(usuario.criadoEm)} + + +
+
+
+ + {/if} + - - {#if modalAssociarAberto && usuarioSelecionado} - + {/if} - - {#if modalExcluirAberto && usuarioSelecionado} - + {/if}
diff --git a/apps/web/src/routes/(dashboard)/ti/usuarios/criar/+page.svelte b/apps/web/src/routes/(dashboard)/ti/usuarios/criar/+page.svelte index e36a233..de91390 100644 --- a/apps/web/src/routes/(dashboard)/ti/usuarios/criar/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/usuarios/criar/+page.svelte @@ -1,574 +1,540 @@ - -
- -
-
-
-
- - - -
-
-

- Criar Novo Usuário -

-

- Cadastre um novo usuário no sistema -

-
-
- - - - - Voltar para Usuários - -
-
+ +
+ +
+
+
+
+ + + +
+
+

Criar Novo Usuário

+

Cadastre um novo usuário no sistema

+
+
+ + + + + Voltar para Usuários + +
+
- - + + - - {#if mensagem} -
- - {#if mensagem.tipo === "success"} - - {:else} - - {/if} - - {mensagem.texto} -
- {/if} + + {#if mensagem} +
+ + {#if mensagem.tipo === 'success'} + + {:else} + + {/if} + + {mensagem.texto} +
+ {/if} - -
-
-
-
- - - -
-

Informações do Usuário

-
+ +
+
+
+
+ + + +
+

Informações do Usuário

+
-
-
- -
- - -
- Ao selecionar, os campos serão preenchidos automaticamente -
-
+ +
+ +
+ + +
+ Ao selecionar, os campos serão preenchidos automaticamente +
+
- -
- - -
+ +
+ + +
- -
- - -
+ +
+ + +
- -
- - - {#if !roles?.data || !Array.isArray(roles.data)} -
- Carregando perfis disponíveis... -
- {/if} -
+ +
+ + + {#if !roles?.data || !Array.isArray(roles.data)} +
+ Carregando perfis disponíveis... +
+ {/if} +
-
-
- - - - Senha Inicial -
-
+
+
+ + + + Senha Inicial +
+
- -
- - -
- Mínimo 8 caracteres -
-
+ +
+ + +
+ Mínimo 8 caracteres +
+
- -
- - -
+ +
+ + +
- -
- + +
+ - {#if mostrarSenha && senhaGerada} -
- - - -
-

Senha Gerada:

-
- - {senhaGerada} - - -
-

- ⚠️ IMPORTANTE: Anote esta senha! Você precisará - repassá-la manualmente ao usuário até que o SMTP seja configurado. -

-
-
- {/if} -
-
+ {#if mostrarSenha && senhaGerada} +
+ + + +
+

Senha Gerada:

+
+ + {senhaGerada} + + +
+

+ ⚠️ IMPORTANTE: Anote esta senha! Você precisará repassá-la manualmente + ao usuário até que o SMTP seja configurado. +

+
+
+ {/if} +
+
-
- - - -
-

Informações Importantes

-
    -
  • O usuário deverá alterar a senha no primeiro acesso
  • -
  • - As credenciais devem ser repassadas manualmente (por enquanto) -
  • -
  • - Configure o SMTP em Configurações de Email para envio automático -
  • -
-
-
+
+ + + +
+

Informações Importantes

+
    +
  • O usuário deverá alterar a senha no primeiro acesso
  • +
  • As credenciais devem ser repassadas manualmente (por enquanto)
  • +
  • + Configure o SMTP em Configurações de Email para envio automático +
  • +
+
+
-
- - - - - Cancelar - - -
-
-
-
-
+
+ + + + + Cancelar + + +
+ +
+
+