refactor: integrate current user data across components
- Replaced instances of `authStore` with `currentUser` to streamline user authentication handling. - Updated permission checks and user-related data retrieval to utilize the new `useQuery` for better performance and clarity. - Cleaned up component structures and improved formatting for consistency and readability. - Enhanced error handling and user feedback mechanisms in various components to improve user experience.
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { useQuery, useConvexClient } from "convex-svelte";
|
||||
import { api } from "@sgse-app/backend/convex/_generated/api";
|
||||
import { authStore } from "$lib/stores/auth.svelte";
|
||||
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
|
||||
|
||||
const client = useConvexClient();
|
||||
const currentUser = useQuery(api.auth.getCurrentUser, {});
|
||||
const configAtual = useQuery(api.configuracaoEmail.obterConfigEmail, {});
|
||||
|
||||
|
||||
let servidor = $state("");
|
||||
let porta = $state(587);
|
||||
let usuario = $state("");
|
||||
@@ -17,7 +17,9 @@
|
||||
let usarTLS = $state(true);
|
||||
let processando = $state(false);
|
||||
let testando = $state(false);
|
||||
let mensagem = $state<{ tipo: "success" | "error"; texto: string } | null>(null);
|
||||
let mensagem = $state<{ tipo: "success" | "error"; texto: string } | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
function mostrarMensagem(tipo: "success" | "error", texto: string) {
|
||||
mensagem = { tipo, texto };
|
||||
@@ -63,7 +65,13 @@
|
||||
|
||||
async function salvarConfiguracao() {
|
||||
// Validação de campos obrigatórios
|
||||
if (!servidor?.trim() || !porta || !usuario?.trim() || !emailRemetente?.trim() || !nomeRemetente?.trim()) {
|
||||
if (
|
||||
!servidor?.trim() ||
|
||||
!porta ||
|
||||
!usuario?.trim() ||
|
||||
!emailRemetente?.trim() ||
|
||||
!nomeRemetente?.trim()
|
||||
) {
|
||||
mostrarMensagem("error", "Preencha todos os campos obrigatórios");
|
||||
return;
|
||||
}
|
||||
@@ -89,24 +97,27 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (!authStore.usuario) {
|
||||
if (!currentUser?.data) {
|
||||
mostrarMensagem("error", "Usuário não autenticado");
|
||||
return;
|
||||
}
|
||||
|
||||
processando = true;
|
||||
try {
|
||||
const resultado = await client.mutation(api.configuracaoEmail.salvarConfigEmail, {
|
||||
servidor: servidor.trim(),
|
||||
porta: portaNum,
|
||||
usuario: usuario.trim(),
|
||||
senha: senha || "", // Senha vazia será tratada no backend
|
||||
emailRemetente: emailRemetente.trim(),
|
||||
nomeRemetente: nomeRemetente.trim(),
|
||||
usarSSL,
|
||||
usarTLS,
|
||||
configuradoPorId: authStore.usuario._id as Id<"usuarios">
|
||||
});
|
||||
const resultado = await client.mutation(
|
||||
api.configuracaoEmail.salvarConfigEmail,
|
||||
{
|
||||
servidor: servidor.trim(),
|
||||
porta: portaNum,
|
||||
usuario: usuario.trim(),
|
||||
senha: senha || "", // Senha vazia será tratada no backend
|
||||
emailRemetente: emailRemetente.trim(),
|
||||
nomeRemetente: nomeRemetente.trim(),
|
||||
usarSSL,
|
||||
usarTLS,
|
||||
configuradoPorId: currentUser.data._id as Id<"usuarios">,
|
||||
},
|
||||
);
|
||||
|
||||
if (resultado.sucesso) {
|
||||
mostrarMensagem("success", "Configuração salva com sucesso!");
|
||||
@@ -137,30 +148,39 @@
|
||||
|
||||
testando = true;
|
||||
try {
|
||||
const resultado = await client.action(api.configuracaoEmail.testarConexaoSMTP, {
|
||||
servidor: servidor.trim(),
|
||||
porta: portaNum,
|
||||
usuario: usuario.trim(),
|
||||
senha: senha,
|
||||
usarSSL,
|
||||
usarTLS,
|
||||
});
|
||||
const resultado = await client.action(
|
||||
api.configuracaoEmail.testarConexaoSMTP,
|
||||
{
|
||||
servidor: servidor.trim(),
|
||||
porta: portaNum,
|
||||
usuario: usuario.trim(),
|
||||
senha: senha,
|
||||
usarSSL,
|
||||
usarTLS,
|
||||
},
|
||||
);
|
||||
|
||||
if (resultado.sucesso) {
|
||||
mostrarMensagem("success", "Conexão testada com sucesso! Servidor SMTP está respondendo.");
|
||||
mostrarMensagem(
|
||||
"success",
|
||||
"Conexão testada com sucesso! Servidor SMTP está respondendo.",
|
||||
);
|
||||
} else {
|
||||
mostrarMensagem("error", `Erro ao testar conexão: ${resultado.erro}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Erro ao testar conexão:", error);
|
||||
mostrarMensagem("error", error.message || "Erro ao conectar com o servidor SMTP");
|
||||
mostrarMensagem(
|
||||
"error",
|
||||
error.message || "Erro ao conectar com o servidor SMTP",
|
||||
);
|
||||
} finally {
|
||||
testando = false;
|
||||
}
|
||||
}
|
||||
|
||||
const statusConfig = $derived(
|
||||
configAtual?.data?.ativo ? "Configurado" : "Não configurado"
|
||||
configAtual?.data?.ativo ? "Configurado" : "Não configurado",
|
||||
);
|
||||
|
||||
const isLoading = $derived(configAtual === undefined);
|
||||
@@ -172,13 +192,28 @@
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="p-3 bg-secondary/10 rounded-xl">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-secondary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8 text-secondary"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-base-content">Configurações de Email (SMTP)</h1>
|
||||
<p class="text-base-content/60 mt-1">Configurar servidor de email para envio de notificações</p>
|
||||
<h1 class="text-3xl font-bold text-base-content">
|
||||
Configurações de Email (SMTP)
|
||||
</h1>
|
||||
<p class="text-base-content/60 mt-1">
|
||||
Configurar servidor de email para envio de notificações
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -226,18 +261,40 @@
|
||||
|
||||
<!-- Status -->
|
||||
{#if !isLoading}
|
||||
<div class="alert {configAtual?.data?.ativo ? 'alert-success' : 'alert-warning'} mb-6">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6">
|
||||
<div
|
||||
class="alert {configAtual?.data?.ativo
|
||||
? 'alert-success'
|
||||
: 'alert-warning'} mb-6"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
class="stroke-current shrink-0 w-6 h-6"
|
||||
>
|
||||
{#if configAtual?.data?.ativo}
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
{:else}
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
{/if}
|
||||
</svg>
|
||||
<span>
|
||||
<strong>Status:</strong> {statusConfig}
|
||||
<strong>Status:</strong>
|
||||
{statusConfig}
|
||||
{#if configAtual?.data?.testadoEm}
|
||||
- Última conexão testada em {new Date(configAtual.data.testadoEm).toLocaleString('pt-BR')}
|
||||
- Última conexão testada em {new Date(
|
||||
configAtual.data.testadoEm,
|
||||
).toLocaleString("pt-BR")}
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
@@ -245,182 +302,207 @@
|
||||
|
||||
<!-- Formulário -->
|
||||
{#if !isLoading}
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">Dados do Servidor SMTP</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Servidor -->
|
||||
<div class="form-control md:col-span-1">
|
||||
<label class="label" for="smtp-servidor">
|
||||
<span class="label-text font-medium">Servidor SMTP *</span>
|
||||
</label>
|
||||
<input
|
||||
id="smtp-servidor"
|
||||
type="text"
|
||||
bind:value={servidor}
|
||||
placeholder="smtp.exemplo.com"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text-alt">Ex: smtp.gmail.com, smtp.office365.com</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">Dados do Servidor SMTP</h2>
|
||||
|
||||
<!-- Porta -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="smtp-porta">
|
||||
<span class="label-text font-medium">Porta *</span>
|
||||
</label>
|
||||
<input
|
||||
id="smtp-porta"
|
||||
type="number"
|
||||
bind:value={porta}
|
||||
placeholder="587"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text-alt">Comum: 587 (TLS), 465 (SSL), 25</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Usuário -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="smtp-usuario">
|
||||
<span class="label-text font-medium">Usuário/Email *</span>
|
||||
</label>
|
||||
<input
|
||||
id="smtp-usuario"
|
||||
type="text"
|
||||
bind:value={usuario}
|
||||
placeholder="usuario@exemplo.com"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Senha -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="smtp-senha">
|
||||
<span class="label-text font-medium">Senha *</span>
|
||||
</label>
|
||||
<input
|
||||
id="smtp-senha"
|
||||
type="password"
|
||||
bind:value={senha}
|
||||
placeholder="••••••••"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text-alt text-warning">
|
||||
{#if configAtual?.data?.ativo}
|
||||
Deixe em branco para manter a senha atual
|
||||
{:else}
|
||||
Digite a senha da conta de email
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email Remetente -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="smtp-email-remetente">
|
||||
<span class="label-text font-medium">Email Remetente *</span>
|
||||
</label>
|
||||
<input
|
||||
id="smtp-email-remetente"
|
||||
type="email"
|
||||
bind:value={emailRemetente}
|
||||
placeholder="noreply@sgse.pe.gov.br"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Nome Remetente -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="smtp-nome-remetente">
|
||||
<span class="label-text font-medium">Nome Remetente *</span>
|
||||
</label>
|
||||
<input
|
||||
id="smtp-nome-remetente"
|
||||
type="text"
|
||||
bind:value={nomeRemetente}
|
||||
placeholder="SGSE - Sistema de Gestão"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Opções de Segurança -->
|
||||
<div class="divider"></div>
|
||||
<h3 class="font-bold mb-2">Configurações de Segurança</h3>
|
||||
|
||||
<div class="flex flex-wrap gap-6">
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={usarSSL}
|
||||
onchange={toggleSSL}
|
||||
class="checkbox checkbox-primary"
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Servidor -->
|
||||
<div class="form-control md:col-span-1">
|
||||
<label class="label" for="smtp-servidor">
|
||||
<span class="label-text font-medium">Servidor SMTP *</span>
|
||||
</label>
|
||||
<input
|
||||
id="smtp-servidor"
|
||||
type="text"
|
||||
bind:value={servidor}
|
||||
placeholder="smtp.exemplo.com"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
<span class="label-text">Usar SSL (porta 465)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={usarTLS}
|
||||
onchange={toggleTLS}
|
||||
class="checkbox checkbox-primary"
|
||||
/>
|
||||
<span class="label-text">Usar TLS (porta 587)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="label">
|
||||
<span class="label-text-alt"
|
||||
>Ex: smtp.gmail.com, smtp.office365.com</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ações -->
|
||||
<div class="card-actions justify-end mt-6 gap-3">
|
||||
<button
|
||||
class="btn btn-outline btn-info"
|
||||
onclick={testarConexao}
|
||||
disabled={testando || processando}
|
||||
>
|
||||
{#if testando}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
{:else}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
{/if}
|
||||
Testar Conexão
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick={salvarConfiguracao}
|
||||
disabled={processando || testando}
|
||||
>
|
||||
{#if processando}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
{:else}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4" />
|
||||
</svg>
|
||||
{/if}
|
||||
Salvar Configuração
|
||||
</button>
|
||||
<!-- Porta -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="smtp-porta">
|
||||
<span class="label-text font-medium">Porta *</span>
|
||||
</label>
|
||||
<input
|
||||
id="smtp-porta"
|
||||
type="number"
|
||||
bind:value={porta}
|
||||
placeholder="587"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text-alt">Comum: 587 (TLS), 465 (SSL), 25</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Usuário -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="smtp-usuario">
|
||||
<span class="label-text font-medium">Usuário/Email *</span>
|
||||
</label>
|
||||
<input
|
||||
id="smtp-usuario"
|
||||
type="text"
|
||||
bind:value={usuario}
|
||||
placeholder="usuario@exemplo.com"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Senha -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="smtp-senha">
|
||||
<span class="label-text font-medium">Senha *</span>
|
||||
</label>
|
||||
<input
|
||||
id="smtp-senha"
|
||||
type="password"
|
||||
bind:value={senha}
|
||||
placeholder="••••••••"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
<div class="label">
|
||||
<span class="label-text-alt text-warning">
|
||||
{#if configAtual?.data?.ativo}
|
||||
Deixe em branco para manter a senha atual
|
||||
{:else}
|
||||
Digite a senha da conta de email
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email Remetente -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="smtp-email-remetente">
|
||||
<span class="label-text font-medium">Email Remetente *</span>
|
||||
</label>
|
||||
<input
|
||||
id="smtp-email-remetente"
|
||||
type="email"
|
||||
bind:value={emailRemetente}
|
||||
placeholder="noreply@sgse.pe.gov.br"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Nome Remetente -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="smtp-nome-remetente">
|
||||
<span class="label-text font-medium">Nome Remetente *</span>
|
||||
</label>
|
||||
<input
|
||||
id="smtp-nome-remetente"
|
||||
type="text"
|
||||
bind:value={nomeRemetente}
|
||||
placeholder="SGSE - Sistema de Gestão"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Opções de Segurança -->
|
||||
<div class="divider"></div>
|
||||
<h3 class="font-bold mb-2">Configurações de Segurança</h3>
|
||||
|
||||
<div class="flex flex-wrap gap-6">
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={usarSSL}
|
||||
onchange={toggleSSL}
|
||||
class="checkbox checkbox-primary"
|
||||
/>
|
||||
<span class="label-text">Usar SSL (porta 465)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={usarTLS}
|
||||
onchange={toggleTLS}
|
||||
class="checkbox checkbox-primary"
|
||||
/>
|
||||
<span class="label-text">Usar TLS (porta 587)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ações -->
|
||||
<div class="card-actions justify-end mt-6 gap-3">
|
||||
<button
|
||||
class="btn btn-outline btn-info"
|
||||
onclick={testarConexao}
|
||||
disabled={testando || processando}
|
||||
>
|
||||
{#if testando}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
Testar Conexão
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick={salvarConfiguracao}
|
||||
disabled={processando || testando}
|
||||
>
|
||||
{#if processando}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
Salvar Configuração
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Exemplos Comuns -->
|
||||
<div class="card bg-base-100 shadow-xl mt-6">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">Exemplos de Configuração</h2>
|
||||
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
@@ -464,13 +546,29 @@
|
||||
|
||||
<!-- Avisos -->
|
||||
<div class="alert alert-info mt-6">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
class="stroke-current shrink-0 w-6 h-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
></path>
|
||||
</svg>
|
||||
<div>
|
||||
<p><strong>Dica de Segurança:</strong> Para Gmail e outros provedores, você pode precisar gerar uma "senha de app" específica em vez de usar sua senha principal.</p>
|
||||
<p class="text-sm mt-1">Gmail: Conta Google → Segurança → Verificação em duas etapas → Senhas de app</p>
|
||||
<p>
|
||||
<strong>Dica de Segurança:</strong> Para Gmail e outros provedores, você
|
||||
pode precisar gerar uma "senha de app" específica em vez de usar sua senha
|
||||
principal.
|
||||
</p>
|
||||
<p class="text-sm mt-1">
|
||||
Gmail: Conta Google → Segurança → Verificação em duas etapas → Senhas de
|
||||
app
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { api } from "@sgse-app/backend/convex/_generated/api";
|
||||
import { format } from "date-fns";
|
||||
import { ptBR } from "date-fns/locale";
|
||||
import { authStore } from "$lib/stores/auth.svelte";
|
||||
import type { Id, Doc } from "@sgse-app/backend/convex/_generated/dataModel";
|
||||
|
||||
// Tipos para agendamentos
|
||||
@@ -44,6 +43,7 @@
|
||||
| { tipo: "chat"; dados: AgendamentoChat };
|
||||
|
||||
const client = useConvexClient();
|
||||
const currentUser = useQuery(api.auth.getCurrentUser, {});
|
||||
|
||||
// Queries
|
||||
const templatesQuery = useQuery(api.templatesMensagens.listarTemplates, {});
|
||||
@@ -580,7 +580,7 @@
|
||||
}
|
||||
|
||||
async function salvarNovoTemplate() {
|
||||
if (!authStore.usuario) {
|
||||
if (!currentUser?.data) {
|
||||
mostrarMensagem(
|
||||
"error",
|
||||
"Você precisa estar autenticado para criar templates.",
|
||||
@@ -622,7 +622,7 @@
|
||||
titulo: tituloTemplate.trim(),
|
||||
corpo: corpoTemplate.trim(),
|
||||
variaveis: variaveis.length > 0 ? variaveis : undefined,
|
||||
criadoPorId: authStore.usuario._id as Id<"usuarios">,
|
||||
criadoPorId: currentUser.data._id as Id<"usuarios">,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -749,12 +749,13 @@
|
||||
);
|
||||
|
||||
if (conversaId) {
|
||||
const mensagem = usarTemplate && templateSelecionado
|
||||
? renderizarTemplate(templateSelecionado.corpo, {
|
||||
nome: destinatario.nome,
|
||||
matricula: destinatario.matricula || "",
|
||||
})
|
||||
: mensagemPersonalizada;
|
||||
const mensagem =
|
||||
usarTemplate && templateSelecionado
|
||||
? renderizarTemplate(templateSelecionado.corpo, {
|
||||
nome: destinatario.nome,
|
||||
matricula: destinatario.matricula || "",
|
||||
})
|
||||
: mensagemPersonalizada;
|
||||
|
||||
if (agendadaPara) {
|
||||
// Agendar mensagem
|
||||
@@ -843,7 +844,7 @@
|
||||
nome: destinatario.nome,
|
||||
matricula: destinatario.matricula,
|
||||
},
|
||||
enviadoPor: authStore.usuario._id as Id<"usuarios">,
|
||||
enviadoPor: currentUser.data._id as Id<"usuarios">,
|
||||
agendadaPara: agendadaPara,
|
||||
},
|
||||
);
|
||||
@@ -894,7 +895,7 @@
|
||||
destinatarioId: destinatario._id as Id<"usuarios">,
|
||||
assunto: "Notificação do Sistema",
|
||||
corpo: mensagemPersonalizada,
|
||||
enviadoPor: authStore.usuario._id as Id<"usuarios">,
|
||||
enviadoPor: currentUser.data._id as Id<"usuarios">,
|
||||
agendadaPara: agendadaPara,
|
||||
},
|
||||
);
|
||||
@@ -1008,12 +1009,13 @@
|
||||
|
||||
if (conversaId) {
|
||||
// Renderizar template com variáveis do destinatário
|
||||
const mensagem = usarTemplate && templateSelecionado
|
||||
? renderizarTemplate(templateSelecionado.corpo, {
|
||||
nome: destinatario.nome,
|
||||
matricula: destinatario.matricula || "",
|
||||
})
|
||||
: mensagemPersonalizada;
|
||||
const mensagem =
|
||||
usarTemplate && templateSelecionado
|
||||
? renderizarTemplate(templateSelecionado.corpo, {
|
||||
nome: destinatario.nome,
|
||||
matricula: destinatario.matricula || "",
|
||||
})
|
||||
: mensagemPersonalizada;
|
||||
|
||||
if (agendadaPara) {
|
||||
await client.mutation(api.chat.agendarMensagem, {
|
||||
@@ -1096,7 +1098,7 @@
|
||||
nome: destinatario.nome,
|
||||
matricula: destinatario.matricula || "",
|
||||
},
|
||||
enviadoPor: authStore.usuario._id as Id<"usuarios">,
|
||||
enviadoPor: currentUser.data._id as Id<"usuarios">,
|
||||
agendadaPara: agendadaPara,
|
||||
},
|
||||
);
|
||||
@@ -1150,7 +1152,7 @@
|
||||
destinatarioId: destinatario._id as Id<"usuarios">,
|
||||
assunto: "Notificação do Sistema",
|
||||
corpo: mensagemPersonalizada,
|
||||
enviadoPor: authStore.usuario._id as Id<"usuarios">,
|
||||
enviadoPor: currentUser.data?._id as Id<"usuarios">,
|
||||
agendadaPara: agendadaPara,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import ProtectedRoute from "$lib/components/ProtectedRoute.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
|
||||
import { authStore } from "$lib/stores/auth.svelte";
|
||||
type RoleRow = {
|
||||
_id: Id<"roles">;
|
||||
_creationTime: number;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { useConvexClient, useQuery } from "convex-svelte";
|
||||
import { api } from "@sgse-app/backend/convex/_generated/api";
|
||||
import { authStore } from "$lib/stores/auth.svelte";
|
||||
import ProtectedRoute from "$lib/components/ProtectedRoute.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import type { Id, Doc } from "@sgse-app/backend/convex/_generated/dataModel";
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
|
||||
import ProtectedRoute from "$lib/components/ProtectedRoute.svelte";
|
||||
import UserStatusBadge from "$lib/components/ti/UserStatusBadge.svelte";
|
||||
import { authStore } from "$lib/stores/auth.svelte";
|
||||
import { format } from "date-fns";
|
||||
import { ptBR } from "date-fns/locale";
|
||||
|
||||
@@ -58,63 +57,70 @@
|
||||
};
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
const currentUser = useQuery(api.auth.getCurrentUser, {});
|
||||
|
||||
// Usar useQuery reativo - seguindo padrão usado em ti/times/+page.svelte
|
||||
const usuariosQuery = useQuery(api.usuarios.listar, {});
|
||||
|
||||
|
||||
// Extrair dados e determinar estado de carregamento
|
||||
const usuarios = $derived.by(() => {
|
||||
// Se usuariosQuery é undefined ou null, está carregando
|
||||
if (usuariosQuery === undefined || usuariosQuery === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
// Se usuariosQuery é um objeto vazio {}, está carregando
|
||||
if (typeof usuariosQuery === "object" && Object.keys(usuariosQuery).length === 0) {
|
||||
if (
|
||||
typeof usuariosQuery === "object" &&
|
||||
Object.keys(usuariosQuery).length === 0
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
// Se tem propriedade data, usar os dados
|
||||
if ("data" in usuariosQuery && usuariosQuery.data !== undefined) {
|
||||
return Array.isArray(usuariosQuery.data) ? usuariosQuery.data : [];
|
||||
}
|
||||
|
||||
|
||||
// Se usuariosQuery é diretamente um array (caso não tenha .data)
|
||||
if (Array.isArray(usuariosQuery)) {
|
||||
return usuariosQuery;
|
||||
}
|
||||
|
||||
|
||||
// Caso padrão: ainda carregando ou sem dados
|
||||
return [];
|
||||
});
|
||||
|
||||
|
||||
const carregandoUsuarios = $derived.by(() => {
|
||||
// Se é undefined/null, está carregando
|
||||
if (usuariosQuery === undefined || usuariosQuery === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Se é um objeto vazio {}, está carregando
|
||||
if (typeof usuariosQuery === "object" && Object.keys(usuariosQuery).length === 0) {
|
||||
if (
|
||||
typeof usuariosQuery === "object" &&
|
||||
Object.keys(usuariosQuery).length === 0
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Se não tem propriedade data, está carregando
|
||||
if (!("data" in usuariosQuery)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Se data é undefined, está carregando
|
||||
if (usuariosQuery.data === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Caso contrário, dados estão prontos
|
||||
return false;
|
||||
});
|
||||
|
||||
let erroUsuarios = $state<string | null>(null);
|
||||
|
||||
|
||||
// Monitorar erros e estado da query
|
||||
$effect(() => {
|
||||
try {
|
||||
@@ -124,7 +130,8 @@
|
||||
if ("error" in usuariosQuery) {
|
||||
const error = (usuariosQuery as { error?: unknown }).error;
|
||||
if (error !== undefined && error !== null) {
|
||||
erroUsuarios = error instanceof Error ? error.message : String(error);
|
||||
erroUsuarios =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
console.error("❌ [ERROR] Erro na query de usuários:", error);
|
||||
} else {
|
||||
// Se error existe mas é undefined/null, limpar erro
|
||||
@@ -138,16 +145,31 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Debug para identificar problemas (apenas em desenvolvimento)
|
||||
if (typeof window !== "undefined" && window.location.hostname === "localhost") {
|
||||
if (
|
||||
typeof window !== "undefined" &&
|
||||
window.location.hostname === "localhost"
|
||||
) {
|
||||
console.log("🔍 [DEBUG] usuariosQuery:", usuariosQuery);
|
||||
console.log("🔍 [DEBUG] typeof usuariosQuery:", typeof usuariosQuery);
|
||||
if (usuariosQuery && typeof usuariosQuery === "object") {
|
||||
console.log("🔍 [DEBUG] Object.keys(usuariosQuery):", Object.keys(usuariosQuery));
|
||||
console.log("🔍 [DEBUG] usuariosQuery?.data:", (usuariosQuery as { data?: unknown }).data);
|
||||
console.log("🔍 [DEBUG] 'data' in usuariosQuery:", "data" in usuariosQuery);
|
||||
console.log("🔍 [DEBUG] 'error' in usuariosQuery:", "error" in usuariosQuery);
|
||||
console.log(
|
||||
"🔍 [DEBUG] Object.keys(usuariosQuery):",
|
||||
Object.keys(usuariosQuery),
|
||||
);
|
||||
console.log(
|
||||
"🔍 [DEBUG] usuariosQuery?.data:",
|
||||
(usuariosQuery as { data?: unknown }).data,
|
||||
);
|
||||
console.log(
|
||||
"🔍 [DEBUG] 'data' in usuariosQuery:",
|
||||
"data" in usuariosQuery,
|
||||
);
|
||||
console.log(
|
||||
"🔍 [DEBUG] 'error' in usuariosQuery:",
|
||||
"error" in usuariosQuery,
|
||||
);
|
||||
}
|
||||
console.log("🔍 [DEBUG] usuarios (extraídos):", usuarios);
|
||||
console.log("🔍 [DEBUG] quantidade:", usuarios.length);
|
||||
@@ -179,7 +201,9 @@
|
||||
let filtroNome = $state("");
|
||||
let filtroMatricula = $state("");
|
||||
let filtroSetor = $state("");
|
||||
let filtroStatus = $state<"todos" | "ativo" | "inativo" | "bloqueado">("todos");
|
||||
let filtroStatus = $state<"todos" | "ativo" | "inativo" | "bloqueado">(
|
||||
"todos",
|
||||
);
|
||||
let filtroDataCriacaoInicio = $state("");
|
||||
let filtroDataCriacaoFim = $state("");
|
||||
let filtroUltimoAcessoInicio = $state("");
|
||||
@@ -201,9 +225,11 @@
|
||||
// Suportar ambos formatos (array direto ou objeto com .data), dependendo do hook/cliente
|
||||
const lista = Array.isArray(resposta)
|
||||
? resposta
|
||||
: (resposta && typeof resposta === "object" && Array.isArray((resposta as { data?: unknown }).data)
|
||||
? ((resposta as { data: unknown[] }).data)
|
||||
: []);
|
||||
: resposta &&
|
||||
typeof resposta === "object" &&
|
||||
Array.isArray((resposta as { data?: unknown }).data)
|
||||
? (resposta as { data: unknown[] }).data
|
||||
: [];
|
||||
|
||||
funcionarios = lista as Funcionario[];
|
||||
} catch (error: unknown) {
|
||||
@@ -240,7 +266,9 @@
|
||||
|
||||
// Verificar se há usuários com problemas
|
||||
const usuariosComProblemas = $derived.by(() => {
|
||||
return usuarios.filter((u) => u.role?.erro === true || (u.avisos && u.avisos.length > 0));
|
||||
return usuarios.filter(
|
||||
(u) => u.role?.erro === true || (u.avisos && u.avisos.length > 0),
|
||||
);
|
||||
});
|
||||
|
||||
// Lógica de filtros reativos
|
||||
@@ -250,13 +278,17 @@
|
||||
// Filtro por nome
|
||||
if (filtroNome.trim()) {
|
||||
const buscaNome = filtroNome.toLowerCase();
|
||||
resultado = resultado.filter((u) => u.nome.toLowerCase().includes(buscaNome));
|
||||
resultado = resultado.filter((u) =>
|
||||
u.nome.toLowerCase().includes(buscaNome),
|
||||
);
|
||||
}
|
||||
|
||||
// Filtro por matrícula
|
||||
if (filtroMatricula.trim()) {
|
||||
const buscaMatricula = filtroMatricula.toLowerCase();
|
||||
resultado = resultado.filter((u) => u.matricula.toLowerCase().includes(buscaMatricula));
|
||||
resultado = resultado.filter((u) =>
|
||||
u.matricula.toLowerCase().includes(buscaMatricula),
|
||||
);
|
||||
}
|
||||
|
||||
// Filtro por setor
|
||||
@@ -340,7 +372,7 @@
|
||||
|
||||
async function ativarDesativarUsuario(usuario: Usuario) {
|
||||
const confirmar = window.confirm(
|
||||
`Deseja realmente ${usuario.ativo ? "desativar" : "ativar"} o usuário ${usuario.nome}?`
|
||||
`Deseja realmente ${usuario.ativo ? "desativar" : "ativar"} o usuário ${usuario.nome}?`,
|
||||
);
|
||||
if (!confirmar) return;
|
||||
|
||||
@@ -351,11 +383,16 @@
|
||||
ativo: !usuario.ativo,
|
||||
});
|
||||
|
||||
mostrarMensagem("success", `Usuário ${usuario.ativo ? "desativado" : "ativado"} com sucesso!`);
|
||||
mostrarMensagem(
|
||||
"success",
|
||||
`Usuário ${usuario.ativo ? "desativado" : "ativado"} com sucesso!`,
|
||||
);
|
||||
await carregarUsuarios();
|
||||
} catch (error: unknown) {
|
||||
const message =
|
||||
error instanceof Error ? error.message : "Erro ao alterar status do usuário.";
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Erro ao alterar status do usuário.";
|
||||
mostrarMensagem("error", message);
|
||||
} finally {
|
||||
processando = false;
|
||||
@@ -381,7 +418,11 @@
|
||||
}
|
||||
|
||||
async function associarFuncionario() {
|
||||
if (!usuarioSelecionado || !funcionarioSelecionadoId || funcionarioSelecionadoId === "") {
|
||||
if (
|
||||
!usuarioSelecionado ||
|
||||
!funcionarioSelecionadoId ||
|
||||
funcionarioSelecionadoId === ""
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -412,7 +453,7 @@
|
||||
}
|
||||
|
||||
const confirmar = window.confirm(
|
||||
"Deseja realmente desassociar o funcionário deste usuário?"
|
||||
"Deseja realmente desassociar o funcionário deste usuário?",
|
||||
);
|
||||
if (!confirmar) return;
|
||||
|
||||
@@ -439,14 +480,14 @@
|
||||
async function bloquearUsuario(usuario: Usuario) {
|
||||
const motivo = window.prompt(
|
||||
"Digite o motivo do bloqueio:",
|
||||
usuario.motivoBloqueio || ""
|
||||
usuario.motivoBloqueio || "",
|
||||
);
|
||||
|
||||
if (!motivo || motivo.trim() === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!authStore.usuario) {
|
||||
if (!currentUser?.data) {
|
||||
mostrarMensagem("error", "Usuário não autenticado");
|
||||
return;
|
||||
}
|
||||
@@ -456,7 +497,7 @@
|
||||
const resultado = await client.mutation(api.usuarios.bloquearUsuario, {
|
||||
usuarioId: usuario._id,
|
||||
motivo: motivo.trim(),
|
||||
bloqueadoPorId: authStore.usuario._id as Id<"usuarios">,
|
||||
bloqueadoPorId: currentUser.data._id as Id<"usuarios">,
|
||||
});
|
||||
|
||||
if (resultado.sucesso) {
|
||||
@@ -475,13 +516,13 @@
|
||||
}
|
||||
|
||||
async function desbloquearUsuario(usuario: Usuario) {
|
||||
if (!authStore.usuario) {
|
||||
if (!currentUser?.data) {
|
||||
mostrarMensagem("error", "Usuário não autenticado");
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmar = window.confirm(
|
||||
`Deseja realmente desbloquear o usuário ${usuario.nome}?`
|
||||
`Deseja realmente desbloquear o usuário ${usuario.nome}?`,
|
||||
);
|
||||
if (!confirmar) return;
|
||||
|
||||
@@ -489,7 +530,7 @@
|
||||
try {
|
||||
const resultado = await client.mutation(api.usuarios.desbloquearUsuario, {
|
||||
usuarioId: usuario._id,
|
||||
desbloqueadoPorId: authStore.usuario._id as Id<"usuarios">,
|
||||
desbloqueadoPorId: currentUser.data._id as Id<"usuarios">,
|
||||
});
|
||||
|
||||
if (resultado.sucesso) {
|
||||
@@ -541,25 +582,54 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<ProtectedRoute allowedRoles={["ti_master", "admin", "ti_usuario"]} maxLevel={3}>
|
||||
<ProtectedRoute
|
||||
allowedRoles={["ti_master", "admin", "ti_usuario"]}
|
||||
maxLevel={3}
|
||||
>
|
||||
<div class="container mx-auto px-4 py-6 max-w-7xl">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="p-3 bg-primary/10 rounded-xl">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8 text-primary"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-base-content">Gestão de Usuários</h1>
|
||||
<p class="text-base-content/60 mt-1">Administre os usuários do sistema</p>
|
||||
<h1 class="text-3xl font-bold text-base-content">
|
||||
Gestão de Usuários
|
||||
</h1>
|
||||
<p class="text-base-content/60 mt-1">
|
||||
Administre os usuários do sistema
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<a href="/ti/usuarios/criar" class="btn btn-primary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
Criar Usuário
|
||||
</a>
|
||||
@@ -586,7 +656,8 @@
|
||||
<h3 class="font-bold">Atenção: Usuários com Problemas Detectados</h3>
|
||||
<div class="text-sm mt-2">
|
||||
<p>
|
||||
{usuariosComProblemas.length} usuário(s) possui(em) problemas que requerem atenção:
|
||||
{usuariosComProblemas.length} usuário(s) possui(em) problemas que requerem
|
||||
atenção:
|
||||
</p>
|
||||
<ul class="list-disc list-inside mt-2 space-y-1">
|
||||
{#each usuariosComProblemas.slice(0, 3) as usuario}
|
||||
@@ -598,11 +669,14 @@
|
||||
</li>
|
||||
{/each}
|
||||
{#if usuariosComProblemas.length > 3}
|
||||
<li class="text-base-content/60">... e mais {usuariosComProblemas.length - 3} usuário(s)</li>
|
||||
<li class="text-base-content/60">
|
||||
... e mais {usuariosComProblemas.length - 3} usuário(s)
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
<p class="mt-2 font-semibold">
|
||||
Por favor, corrija os perfis desses usuários para garantir acesso adequado ao sistema.
|
||||
Por favor, corrija os perfis desses usuários para garantir acesso
|
||||
adequado ao sistema.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -670,9 +744,24 @@
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="card-title">Filtros de Busca</h2>
|
||||
<button type="button" class="btn btn-sm btn-outline" onclick={limparFiltros}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline"
|
||||
onclick={limparFiltros}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
Limpar Filtros
|
||||
</button>
|
||||
@@ -712,7 +801,11 @@
|
||||
<label class="label" for="filtro-setor">
|
||||
<span class="label-text font-medium">Setor</span>
|
||||
</label>
|
||||
<select id="filtro-setor" bind:value={filtroSetor} class="select select-bordered select-sm">
|
||||
<select
|
||||
id="filtro-setor"
|
||||
bind:value={filtroSetor}
|
||||
class="select select-bordered select-sm"
|
||||
>
|
||||
<option value="">Todos os setores</option>
|
||||
{#each obterSetoresDisponiveis() as setor}
|
||||
<option value={setor}>{setor}</option>
|
||||
@@ -725,7 +818,11 @@
|
||||
<label class="label" for="filtro-status">
|
||||
<span class="label-text font-medium">Status</span>
|
||||
</label>
|
||||
<select id="filtro-status" bind:value={filtroStatus} class="select select-bordered select-sm">
|
||||
<select
|
||||
id="filtro-status"
|
||||
bind:value={filtroStatus}
|
||||
class="select select-bordered select-sm"
|
||||
>
|
||||
<option value="todos">Todos</option>
|
||||
<option value="ativo">Ativo</option>
|
||||
<option value="inativo">Inativo</option>
|
||||
@@ -736,7 +833,9 @@
|
||||
<!-- Data de Criação Início -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="filtro-data-criacao-inicio">
|
||||
<span class="label-text font-medium">Data de Criação (Início)</span>
|
||||
<span class="label-text font-medium"
|
||||
>Data de Criação (Início)</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
id="filtro-data-criacao-inicio"
|
||||
@@ -749,7 +848,8 @@
|
||||
<!-- Data de Criação Fim -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="filtro-data-criacao-fim">
|
||||
<span class="label-text font-medium">Data de Criação (Fim)</span>
|
||||
<span class="label-text font-medium">Data de Criação (Fim)</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
id="filtro-data-criacao-fim"
|
||||
@@ -762,7 +862,9 @@
|
||||
<!-- Último Acesso Início -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="filtro-ultimo-acesso-inicio">
|
||||
<span class="label-text font-medium">Último Acesso (Início)</span>
|
||||
<span class="label-text font-medium"
|
||||
>Último Acesso (Início)</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
id="filtro-ultimo-acesso-inicio"
|
||||
@@ -818,7 +920,8 @@
|
||||
<h3 class="font-bold">Erro ao carregar usuários</h3>
|
||||
<div class="text-sm mt-1">{erroUsuarios}</div>
|
||||
<div class="text-xs mt-2">
|
||||
Por favor, recarregue a página ou entre em contato com o suporte técnico se o problema persistir.
|
||||
Por favor, recarregue a página ou entre em contato com o suporte
|
||||
técnico se o problema persistir.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -892,13 +995,19 @@
|
||||
{usuario.role.descricao}
|
||||
</div>
|
||||
{#if usuario.avisos && usuario.avisos.length > 0}
|
||||
<div class="tooltip tooltip-error" data-tip={usuario.avisos[0].mensagem}>
|
||||
<div
|
||||
class="tooltip tooltip-error"
|
||||
data-tip={usuario.avisos[0].mensagem}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-xs btn-error btn-circle"
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
mostrarMensagem("error", usuario.avisos![0].mensagem);
|
||||
mostrarMensagem(
|
||||
"error",
|
||||
usuario.avisos![0].mensagem,
|
||||
);
|
||||
}}
|
||||
>
|
||||
!
|
||||
@@ -906,7 +1015,9 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="badge badge-outline">{usuario.role.nome}</div>
|
||||
<div class="badge badge-outline">
|
||||
{usuario.role.nome}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
@@ -931,7 +1042,9 @@
|
||||
</svg>
|
||||
Associado
|
||||
</div>
|
||||
<div class="text-sm font-medium">{usuario.funcionario.nome}</div>
|
||||
<div class="text-sm font-medium">
|
||||
{usuario.funcionario.nome}
|
||||
</div>
|
||||
{#if usuario.funcionario.matricula}
|
||||
<div class="text-xs text-base-content/60">
|
||||
Mat: {usuario.funcionario.matricula}
|
||||
@@ -959,7 +1072,10 @@
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<UserStatusBadge ativo={usuario.ativo} bloqueado={usuario.bloqueado} />
|
||||
<UserStatusBadge
|
||||
ativo={usuario.ativo}
|
||||
bloqueado={usuario.bloqueado}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{#if usuario.primeiroAcesso}
|
||||
@@ -969,10 +1085,14 @@
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-sm">{formatarData(usuario.ultimoAcesso)}</span>
|
||||
<span class="text-sm"
|
||||
>{formatarData(usuario.ultimoAcesso)}</span
|
||||
>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-sm">{formatarData(usuario.criadoEm)}</span>
|
||||
<span class="text-sm"
|
||||
>{formatarData(usuario.criadoEm)}</span
|
||||
>
|
||||
</td>
|
||||
<td>
|
||||
<div class="dropdown dropdown-end">
|
||||
@@ -981,35 +1101,74 @@
|
||||
class="btn btn-sm btn-ghost"
|
||||
aria-label="Menu de ações"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<ul role="menu" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-48 z-[1]">
|
||||
<ul
|
||||
role="menu"
|
||||
class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-48 z-[1]"
|
||||
>
|
||||
<li>
|
||||
<button type="button" onclick={() => ativarDesativarUsuario(usuario)} disabled={processando}>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => ativarDesativarUsuario(usuario)}
|
||||
disabled={processando}
|
||||
>
|
||||
{usuario.ativo ? "Desativar" : "Ativar"}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" onclick={() => abrirModalAssociar(usuario)} disabled={processando}>
|
||||
{usuario.funcionario ? "Alterar Funcionário" : "Associar Funcionário"}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => abrirModalAssociar(usuario)}
|
||||
disabled={processando}
|
||||
>
|
||||
{usuario.funcionario
|
||||
? "Alterar Funcionário"
|
||||
: "Associar Funcionário"}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
{#if usuario.bloqueado}
|
||||
<button type="button" onclick={() => desbloquearUsuario(usuario)} disabled={processando} class="text-success">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => desbloquearUsuario(usuario)}
|
||||
disabled={processando}
|
||||
class="text-success"
|
||||
>
|
||||
Desbloquear
|
||||
</button>
|
||||
{:else}
|
||||
<button type="button" onclick={() => bloquearUsuario(usuario)} disabled={processando} class="text-error">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => bloquearUsuario(usuario)}
|
||||
disabled={processando}
|
||||
class="text-error"
|
||||
>
|
||||
Bloquear
|
||||
</button>
|
||||
{/if}
|
||||
</li>
|
||||
<div class="divider my-0"></div>
|
||||
<li>
|
||||
<button type="button" onclick={() => abrirModalExcluir(usuario)} disabled={processando} class="text-error">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => abrirModalExcluir(usuario)}
|
||||
disabled={processando}
|
||||
class="text-error"
|
||||
>
|
||||
Excluir
|
||||
</button>
|
||||
</li>
|
||||
@@ -1034,7 +1193,8 @@
|
||||
|
||||
<div class="mb-6">
|
||||
<p class="text-base-content/80 mb-2">
|
||||
<strong>Usuário:</strong> {usuarioSelecionado.nome} ({usuarioSelecionado.matricula})
|
||||
<strong>Usuário:</strong>
|
||||
{usuarioSelecionado.nome} ({usuarioSelecionado.matricula})
|
||||
</p>
|
||||
|
||||
{#if usuarioSelecionado.funcionario}
|
||||
@@ -1053,7 +1213,8 @@
|
||||
></path>
|
||||
</svg>
|
||||
<span>
|
||||
Este usuário já possui um funcionário associado. Você pode alterá-lo ou desassociá-lo.
|
||||
Este usuário já possui um funcionário associado. Você pode
|
||||
alterá-lo ou desassociá-lo.
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1079,7 +1240,9 @@
|
||||
|
||||
<div class="border rounded-lg max-h-96 overflow-y-auto">
|
||||
{#if carregandoFuncionarios}
|
||||
<div class="p-4 flex items-center justify-center gap-3 text-base-content/60">
|
||||
<div
|
||||
class="p-4 flex items-center justify-center gap-3 text-base-content/60"
|
||||
>
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
Carregando funcionários disponíveis...
|
||||
</div>
|
||||
@@ -1110,7 +1273,9 @@
|
||||
{/if}
|
||||
</div>
|
||||
{#if func.descricaoCargo}
|
||||
<div class="text-xs text-base-content/60">{func.descricaoCargo}</div>
|
||||
<div class="text-xs text-base-content/60">
|
||||
{func.descricaoCargo}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</label>
|
||||
@@ -1147,7 +1312,9 @@
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
onclick={associarFuncionario}
|
||||
disabled={processando || !funcionarioSelecionadoId || funcionarioSelecionadoId === ""}
|
||||
disabled={processando ||
|
||||
!funcionarioSelecionadoId ||
|
||||
funcionarioSelecionadoId === ""}
|
||||
>
|
||||
{#if processando}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
@@ -1157,7 +1324,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-backdrop">
|
||||
<button type="button" onclick={fecharModalAssociar} onkeydown={(e) => e.key === "Escape" && fecharModalAssociar()} aria-label="Fechar modal" class="sr-only">Fechar</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={fecharModalAssociar}
|
||||
onkeydown={(e) => e.key === "Escape" && fecharModalAssociar()}
|
||||
aria-label="Fechar modal"
|
||||
class="sr-only">Fechar</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1170,7 +1343,8 @@
|
||||
|
||||
<div class="mb-4">
|
||||
<p class="text-base-content/80 mb-2">
|
||||
<strong>Usuário:</strong> {usuarioSelecionado.nome} ({usuarioSelecionado.matricula})
|
||||
<strong>Usuário:</strong>
|
||||
{usuarioSelecionado.nome} ({usuarioSelecionado.matricula})
|
||||
</p>
|
||||
<div class="alert alert-error">
|
||||
<svg
|
||||
@@ -1187,7 +1361,8 @@
|
||||
></path>
|
||||
</svg>
|
||||
<span>
|
||||
Esta ação não pode ser desfeita. O usuário será permanentemente excluído do sistema.
|
||||
Esta ação não pode ser desfeita. O usuário será permanentemente
|
||||
excluído do sistema.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1215,7 +1390,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-backdrop">
|
||||
<button type="button" onclick={fecharModalExcluir} onkeydown={(e) => e.key === "Escape" && fecharModalExcluir()} aria-label="Fechar modal" class="sr-only">Fechar</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={fecharModalExcluir}
|
||||
onkeydown={(e) => e.key === "Escape" && fecharModalExcluir()}
|
||||
aria-label="Fechar modal"
|
||||
class="sr-only">Fechar</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user