Merge branch 'master' into feat-controle-ponto
This commit is contained in:
@@ -11,7 +11,8 @@
|
||||
| 'monitor'
|
||||
| 'document'
|
||||
| 'teams'
|
||||
| 'userPlus';
|
||||
| 'userPlus'
|
||||
| 'clock';
|
||||
type PaletteKey = 'primary' | 'success' | 'secondary' | 'accent' | 'info' | 'error' | 'warning';
|
||||
|
||||
type TiRouteId =
|
||||
@@ -25,7 +26,9 @@
|
||||
| '/(dashboard)/ti/solicitacoes-acesso'
|
||||
| '/(dashboard)/ti/times'
|
||||
| '/(dashboard)/ti/notificacoes'
|
||||
| '/(dashboard)/ti/monitoramento';
|
||||
| '/(dashboard)/ti/monitoramento'
|
||||
| '/(dashboard)/ti/configuracoes-ponto'
|
||||
| '/(dashboard)/ti/configuracoes-relogio';
|
||||
|
||||
type FeatureCard = {
|
||||
title: string;
|
||||
@@ -192,6 +195,13 @@
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round'
|
||||
}
|
||||
],
|
||||
clock: [
|
||||
{
|
||||
d: 'M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z',
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -231,15 +241,6 @@
|
||||
{ label: 'Alertas', variant: 'outline' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Suporte Técnico',
|
||||
description:
|
||||
'Central de atendimento para resolução de problemas técnicos e dúvidas sobre o sistema.',
|
||||
ctaLabel: 'Em breve',
|
||||
palette: 'info',
|
||||
icon: 'support',
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
title: 'Gerenciar Permissões',
|
||||
description:
|
||||
@@ -298,15 +299,6 @@
|
||||
palette: 'accent',
|
||||
icon: 'users'
|
||||
},
|
||||
{
|
||||
title: 'Solicitações de Acesso',
|
||||
description:
|
||||
'Gerencie e analise solicitações de acesso ao sistema. Aprove ou rejeite novas solicitações de forma eficiente.',
|
||||
ctaLabel: 'Gerenciar Solicitações',
|
||||
href: '/(dashboard)/ti/solicitacoes-acesso',
|
||||
palette: 'warning',
|
||||
icon: 'userPlus'
|
||||
},
|
||||
{
|
||||
title: 'Gestão de Times',
|
||||
description:
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { useQuery, useConvexClient } from 'convex-svelte';
|
||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||
import type { FunctionReference } from 'convex/server';
|
||||
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 {
|
||||
@@ -43,7 +42,7 @@
|
||||
| { tipo: 'chat'; dados: AgendamentoChat };
|
||||
|
||||
const client = useConvexClient();
|
||||
const currentUser = useQuery(api.auth.getCurrentUser, {});
|
||||
const currentUser = useQuery(api.auth.getCurrentUser as FunctionReference<'query'>);
|
||||
|
||||
// Queries
|
||||
const templatesQuery = useQuery(api.templatesMensagens.listarTemplates, {});
|
||||
@@ -57,10 +56,9 @@
|
||||
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 });
|
||||
});
|
||||
const emailsStatusQuery = useQuery(api.email.buscarEmailsPorIds, () =>
|
||||
emailIdsArray.length === 0 ? 'skip' : { emailIds: emailIdsArray }
|
||||
);
|
||||
|
||||
// Queries para agendamentos
|
||||
const agendamentosEmailQuery = useQuery(api.email.listarAgendamentosEmail, {});
|
||||
@@ -101,8 +99,8 @@
|
||||
const carregandoTemplates = $derived(templatesQuery === undefined || templatesQuery === null);
|
||||
const carregandoUsuarios = $derived(usuariosQuery === undefined || usuariosQuery === null);
|
||||
|
||||
// Verificar erros de forma mais robusta
|
||||
const erroTemplates = $derived.by(() => {
|
||||
// Verificar erros de forma mais robusta (código mantido mas variáveis não utilizadas removidas para lint)
|
||||
/* 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) {
|
||||
@@ -117,7 +115,7 @@
|
||||
return usuariosQuery.error !== undefined && usuariosQuery.error !== null;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}); */
|
||||
|
||||
// Log para debug (remover depois se necessário)
|
||||
$effect(() => {
|
||||
@@ -566,7 +564,7 @@
|
||||
titulo: tituloTemplate.trim(),
|
||||
corpo: corpoTemplate.trim(),
|
||||
variaveis: variaveis.length > 0 ? variaveis : undefined,
|
||||
criadoPorId: currentUser.data._id as Id<'usuarios'>
|
||||
criadoPorId: currentUser.data!._id as Id<'usuarios'>
|
||||
});
|
||||
|
||||
if (resultado.sucesso) {
|
||||
@@ -592,6 +590,11 @@
|
||||
}
|
||||
|
||||
async function enviarNotificacao() {
|
||||
if (!currentUser?.data) {
|
||||
mostrarMensagem('error', 'Você precisa estar autenticado para enviar notificações.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!enviarParaTodos && !destinatarioId) {
|
||||
mostrarMensagem('error', "Selecione um destinatário ou marque 'Enviar para todos'");
|
||||
return;
|
||||
@@ -622,7 +625,7 @@
|
||||
return;
|
||||
}
|
||||
agendadaPara = dataHora.getTime();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
mostrarMensagem('error', 'Data ou hora inválida');
|
||||
return;
|
||||
}
|
||||
@@ -743,9 +746,9 @@
|
||||
templateCodigo: template.codigo,
|
||||
variaveis: {
|
||||
nome: destinatario.nome,
|
||||
matricula: destinatario.matricula
|
||||
matricula: destinatario.matricula || ''
|
||||
},
|
||||
enviadoPor: currentUser.data._id as Id<'usuarios'>,
|
||||
enviadoPor: currentUser.data!._id as Id<'usuarios'>,
|
||||
agendadaPara: agendadaPara
|
||||
});
|
||||
if (emailId) {
|
||||
@@ -783,7 +786,7 @@
|
||||
destinatarioId: destinatario._id as Id<'usuarios'>,
|
||||
assunto: 'Notificação do Sistema',
|
||||
corpo: mensagemPersonalizada,
|
||||
enviadoPor: currentUser.data._id as Id<'usuarios'>,
|
||||
enviadoPor: currentUser.data!._id as Id<'usuarios'>,
|
||||
agendadaPara: agendadaPara
|
||||
});
|
||||
if (emailId) {
|
||||
@@ -946,7 +949,7 @@
|
||||
nome: destinatario.nome,
|
||||
matricula: destinatario.matricula || ''
|
||||
},
|
||||
enviadoPor: currentUser.data._id as Id<'usuarios'>,
|
||||
enviadoPor: currentUser.data!._id as Id<'usuarios'>,
|
||||
agendadaPara: agendadaPara
|
||||
});
|
||||
if (emailId) {
|
||||
@@ -992,11 +995,10 @@
|
||||
destinatarioId: destinatario._id as Id<'usuarios'>,
|
||||
assunto: 'Notificação do Sistema',
|
||||
corpo: mensagemPersonalizada,
|
||||
enviadoPor: currentUser.data?._id as Id<'usuarios'>,
|
||||
enviadoPor: currentUser.data!._id as Id<'usuarios'>,
|
||||
agendadaPara: agendadaPara
|
||||
});
|
||||
if (emailId) {
|
||||
resultadoEmail = { sucesso: true, emailId };
|
||||
if (agendadaPara) {
|
||||
const dataFormatada = format(
|
||||
new Date(agendadaPara),
|
||||
@@ -1203,7 +1205,7 @@
|
||||
{#if carregandoUsuarios}
|
||||
<option disabled>Carregando usuários...</option>
|
||||
{:else if usuarios.length > 0}
|
||||
{#each usuarios as usuario}
|
||||
{#each usuarios as usuario (usuario._id)}
|
||||
<option value={usuario._id}>
|
||||
{usuario.nome} ({usuario.matricula})
|
||||
</option>
|
||||
@@ -1213,11 +1215,11 @@
|
||||
{/if}
|
||||
</select>
|
||||
{#if enviarParaTodos}
|
||||
<label class="label">
|
||||
<div class="label">
|
||||
<span class="label-text-alt text-warning">
|
||||
⚠️ A notificação será enviada para todos os {usuarios.length} usuários do sistema
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1280,7 +1282,7 @@
|
||||
{#if carregandoTemplates}
|
||||
<option disabled>Carregando templates...</option>
|
||||
{:else if templates.length > 0}
|
||||
{#each templates as template}
|
||||
{#each templates as template (template._id)}
|
||||
<option value={template._id}>
|
||||
{template.nome}
|
||||
</option>
|
||||
@@ -1440,9 +1442,9 @@
|
||||
<!-- Terminal de Status -->
|
||||
<div class="mt-4">
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<label class="label">
|
||||
<div class="label pl-0">
|
||||
<span class="label-text font-medium">Terminal de Status</span>
|
||||
</label>
|
||||
</div>
|
||||
{#if logsEnvio.length > 0}
|
||||
<button type="button" class="btn btn-sm btn-ghost" onclick={limparLogs}>
|
||||
<svg
|
||||
@@ -1471,7 +1473,7 @@
|
||||
{#if logsEnvio.length === 0}
|
||||
<div class="text-neutral-content/60 italic">Aguardando envio de notificação...</div>
|
||||
{:else}
|
||||
{#each logsEnvio as log}
|
||||
{#each logsEnvio as log, i (i)}
|
||||
<div class="mb-1">
|
||||
<span class="text-neutral-content/60">[{formatarTimestamp(log.timestamp)}]</span>
|
||||
<span
|
||||
@@ -1529,7 +1531,7 @@
|
||||
{:else if templates.length > 0}
|
||||
<!-- Mostrar templates se existirem -->
|
||||
<div class="max-h-[600px] space-y-3 overflow-y-auto">
|
||||
{#each templates as template}
|
||||
{#each templates as template (template._id)}
|
||||
<div class="card bg-base-200 compact">
|
||||
<div class="card-body">
|
||||
<div class="flex items-start justify-between">
|
||||
@@ -1731,17 +1733,11 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each agendamentosFiltrados as agendamento}
|
||||
{#each agendamentosFiltrados as agendamento (agendamento.dados._id)}
|
||||
{@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'
|
||||
: '-'}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -1898,18 +1894,19 @@
|
||||
<div class="space-y-4">
|
||||
<!-- Código -->
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<label class="label" for="codigo-template">
|
||||
<span class="label-text font-medium">Código *</span>
|
||||
<span class="label-text-alt">Ex: AVISO_IMPORTANTE</span>
|
||||
</label>
|
||||
<input
|
||||
id="codigo-template"
|
||||
type="text"
|
||||
bind:value={codigoTemplate}
|
||||
placeholder="CODIGO_TEMPLATE"
|
||||
class="input input-bordered"
|
||||
maxlength="50"
|
||||
/>
|
||||
<label class="label">
|
||||
<label class="label" for="codigo-template">
|
||||
<span class="label-text-alt"
|
||||
>Código único para identificar o template (será convertido para MAIÚSCULAS)</span
|
||||
>
|
||||
@@ -1918,34 +1915,36 @@
|
||||
|
||||
<!-- Nome -->
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<label class="label" for="nome-template">
|
||||
<span class="label-text font-medium">Nome *</span>
|
||||
</label>
|
||||
<input
|
||||
id="nome-template"
|
||||
type="text"
|
||||
bind:value={nomeTemplate}
|
||||
placeholder="Nome do Template"
|
||||
class="input input-bordered"
|
||||
maxlength="100"
|
||||
/>
|
||||
<label class="label">
|
||||
<label class="label" for="nome-template">
|
||||
<span class="label-text-alt">Nome exibido na lista de templates</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Título -->
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<label class="label" for="titulo-template">
|
||||
<span class="label-text font-medium">Título *</span>
|
||||
</label>
|
||||
<input
|
||||
id="titulo-template"
|
||||
type="text"
|
||||
bind:value={tituloTemplate}
|
||||
placeholder="Título da Mensagem"
|
||||
class="input input-bordered"
|
||||
maxlength="200"
|
||||
/>
|
||||
<label class="label">
|
||||
<label class="label" for="titulo-template">
|
||||
<span class="label-text-alt"
|
||||
>Título usado no assunto do email ou cabeçalho da mensagem</span
|
||||
>
|
||||
@@ -1954,35 +1953,37 @@
|
||||
|
||||
<!-- Corpo -->
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<label class="label" for="corpo-template">
|
||||
<span class="label-text font-medium">Corpo da Mensagem *</span>
|
||||
</label>
|
||||
<textarea
|
||||
id="corpo-template"
|
||||
bind:value={corpoTemplate}
|
||||
placeholder="Digite o conteúdo da mensagem..."
|
||||
class="textarea textarea-bordered h-32"
|
||||
maxlength="2000"
|
||||
></textarea>
|
||||
<label class="label">
|
||||
<label class="label" for="corpo-template">
|
||||
<span class="label-text-alt"
|
||||
>Use {'{{'}variavel{'}}'} para variáveis dinâmicas (ex: {'{{'}nome{'}}'},
|
||||
{'{{'}data{'}}'})</span
|
||||
>Use {{variavel}} para variáveis dinâmicas (ex: {{nome}},
|
||||
{{data}})</span
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Variáveis -->
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<label class="label" for="variaveis-template">
|
||||
<span class="label-text font-medium">Variáveis (opcional)</span>
|
||||
</label>
|
||||
<input
|
||||
id="variaveis-template"
|
||||
type="text"
|
||||
bind:value={variaveisTemplate}
|
||||
placeholder="nome, data, valor (separadas por vírgula)"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
<label class="label">
|
||||
<label class="label" for="variaveis-template">
|
||||
<span class="label-text-alt"
|
||||
>Liste as variáveis que podem ser substituídas no template (separadas por vírgula ou
|
||||
espaço)</span
|
||||
@@ -2019,6 +2020,6 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-backdrop" onclick={fecharModalNovoTemplate}></div>
|
||||
<button class="modal-backdrop" onclick={fecharModalNovoTemplate}>fechar</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,836 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { useQuery, useConvexClient } from 'convex-svelte';
|
||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||
import ProtectedRoute from '$lib/components/ProtectedRoute.svelte';
|
||||
import StatsCard from '$lib/components/ti/StatsCard.svelte';
|
||||
import { format } from 'date-fns';
|
||||
import { ptBR } from 'date-fns/locale';
|
||||
import { FileText, Clock, CheckCircle2, XCircle } from 'lucide-svelte';
|
||||
|
||||
type StatusSolicitacao = 'pendente' | 'aprovado' | 'rejeitado';
|
||||
|
||||
type SolicitacaoAcesso = {
|
||||
_id: Id<'solicitacoesAcesso'>;
|
||||
_creationTime: number;
|
||||
nome: string;
|
||||
matricula: string;
|
||||
email: string;
|
||||
telefone: string;
|
||||
status: StatusSolicitacao;
|
||||
dataSolicitacao: number;
|
||||
dataResposta: number | null;
|
||||
observacoes: string | null;
|
||||
};
|
||||
|
||||
type FiltroStatus = 'todos' | 'pendente' | 'aprovado' | 'rejeitado';
|
||||
|
||||
type Mensagem = {
|
||||
tipo: 'success' | 'error' | 'info';
|
||||
texto: string;
|
||||
};
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
// Queries
|
||||
const solicitacoesQuery = useQuery(api.solicitacoesAcesso.getAll, {});
|
||||
|
||||
// Estados
|
||||
let filtroStatus = $state<FiltroStatus>('todos');
|
||||
let busca = $state('');
|
||||
let solicitacaoSelecionada = $state<SolicitacaoAcesso | null>(null);
|
||||
let modalDetalhesAberto = $state(false);
|
||||
let modalAprovarAberto = $state(false);
|
||||
let modalRejeitarAberto = $state(false);
|
||||
let observacoes = $state('');
|
||||
let mensagem = $state<Mensagem | null>(null);
|
||||
let processando = $state(false);
|
||||
|
||||
// Extrair dados das solicitações
|
||||
const solicitacoes = $derived.by(() => {
|
||||
if (solicitacoesQuery === undefined || solicitacoesQuery === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ('data' in solicitacoesQuery && solicitacoesQuery.data !== undefined) {
|
||||
return Array.isArray(solicitacoesQuery.data) ? solicitacoesQuery.data : [];
|
||||
}
|
||||
|
||||
if (Array.isArray(solicitacoesQuery)) {
|
||||
return solicitacoesQuery;
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
const carregando = $derived.by(() => {
|
||||
return solicitacoesQuery === undefined || solicitacoesQuery === null;
|
||||
});
|
||||
|
||||
// Estatísticas
|
||||
const stats = $derived.by(() => {
|
||||
if (carregando) return null;
|
||||
|
||||
const total = solicitacoes.length;
|
||||
const pendentes = solicitacoes.filter((s) => s.status === 'pendente').length;
|
||||
const aprovadas = solicitacoes.filter((s) => s.status === 'aprovado').length;
|
||||
const rejeitadas = solicitacoes.filter((s) => s.status === 'rejeitado').length;
|
||||
|
||||
return {
|
||||
total,
|
||||
pendentes,
|
||||
aprovadas,
|
||||
rejeitadas
|
||||
};
|
||||
});
|
||||
|
||||
// Filtrar e buscar solicitações
|
||||
const solicitacoesFiltradas = $derived.by(() => {
|
||||
let resultado = solicitacoes;
|
||||
|
||||
// Filtrar por status
|
||||
if (filtroStatus !== 'todos') {
|
||||
resultado = resultado.filter((s) => s.status === filtroStatus);
|
||||
}
|
||||
|
||||
// Buscar por nome, matrícula ou email
|
||||
if (busca.trim()) {
|
||||
const termo = busca.toLowerCase().trim();
|
||||
resultado = resultado.filter(
|
||||
(s) =>
|
||||
s.nome.toLowerCase().includes(termo) ||
|
||||
s.matricula.toLowerCase().includes(termo) ||
|
||||
s.email.toLowerCase().includes(termo)
|
||||
);
|
||||
}
|
||||
|
||||
// Ordenar por data (mais recente primeiro)
|
||||
return resultado.sort((a, b) => b.dataSolicitacao - a.dataSolicitacao);
|
||||
});
|
||||
|
||||
// Funções auxiliares
|
||||
function formatarData(timestamp: number): string {
|
||||
return format(new Date(timestamp), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR });
|
||||
}
|
||||
|
||||
function formatarDataRelativa(timestamp: number): string {
|
||||
const agora = Date.now();
|
||||
const diff = agora - timestamp;
|
||||
const dias = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
const horas = Math.floor(diff / (1000 * 60 * 60));
|
||||
const minutos = Math.floor(diff / (1000 * 60));
|
||||
|
||||
if (dias > 0) return `${dias} dia${dias > 1 ? 's' : ''} atrás`;
|
||||
if (horas > 0) return `${horas} hora${horas > 1 ? 's' : ''} atrás`;
|
||||
if (minutos > 0) return `${minutos} minuto${minutos > 1 ? 's' : ''} atrás`;
|
||||
return 'Agora';
|
||||
}
|
||||
|
||||
function getStatusBadge(status: StatusSolicitacao): string {
|
||||
switch (status) {
|
||||
case 'pendente':
|
||||
return 'badge-warning';
|
||||
case 'aprovado':
|
||||
return 'badge-success';
|
||||
case 'rejeitado':
|
||||
return 'badge-error';
|
||||
default:
|
||||
return 'badge-neutral';
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusTexto(status: StatusSolicitacao): string {
|
||||
switch (status) {
|
||||
case 'pendente':
|
||||
return 'Pendente';
|
||||
case 'aprovado':
|
||||
return 'Aprovado';
|
||||
case 'rejeitado':
|
||||
return 'Rejeitado';
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
// Funções de modal
|
||||
function abrirDetalhes(solicitacao: SolicitacaoAcesso) {
|
||||
solicitacaoSelecionada = solicitacao;
|
||||
modalDetalhesAberto = true;
|
||||
}
|
||||
|
||||
function fecharDetalhes() {
|
||||
modalDetalhesAberto = false;
|
||||
solicitacaoSelecionada = null;
|
||||
}
|
||||
|
||||
function abrirAprovar(solicitacao: SolicitacaoAcesso) {
|
||||
solicitacaoSelecionada = solicitacao;
|
||||
observacoes = '';
|
||||
modalAprovarAberto = true;
|
||||
}
|
||||
|
||||
function fecharAprovar() {
|
||||
modalAprovarAberto = false;
|
||||
solicitacaoSelecionada = null;
|
||||
observacoes = '';
|
||||
}
|
||||
|
||||
function abrirRejeitar(solicitacao: SolicitacaoAcesso) {
|
||||
solicitacaoSelecionada = solicitacao;
|
||||
observacoes = '';
|
||||
modalRejeitarAberto = true;
|
||||
}
|
||||
|
||||
function fecharRejeitar() {
|
||||
modalRejeitarAberto = false;
|
||||
solicitacaoSelecionada = null;
|
||||
observacoes = '';
|
||||
}
|
||||
|
||||
// Funções de ação
|
||||
async function aprovarSolicitacao() {
|
||||
if (!solicitacaoSelecionada) return;
|
||||
|
||||
processando = true;
|
||||
mensagem = null;
|
||||
|
||||
try {
|
||||
await client.mutation(api.solicitacoesAcesso.aprovar, {
|
||||
solicitacaoId: solicitacaoSelecionada._id,
|
||||
observacoes: observacoes.trim() || undefined
|
||||
});
|
||||
|
||||
mensagem = {
|
||||
tipo: 'success',
|
||||
texto: 'Solicitação aprovada com sucesso!'
|
||||
};
|
||||
|
||||
fecharAprovar();
|
||||
|
||||
// Limpar mensagem após 3 segundos
|
||||
setTimeout(() => {
|
||||
mensagem = null;
|
||||
}, 3000);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Erro ao aprovar solicitação';
|
||||
mensagem = {
|
||||
tipo: 'error',
|
||||
texto: errorMessage
|
||||
};
|
||||
} finally {
|
||||
processando = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function rejeitarSolicitacao() {
|
||||
if (!solicitacaoSelecionada) return;
|
||||
|
||||
processando = true;
|
||||
mensagem = null;
|
||||
|
||||
try {
|
||||
await client.mutation(api.solicitacoesAcesso.rejeitar, {
|
||||
solicitacaoId: solicitacaoSelecionada._id,
|
||||
observacoes: observacoes.trim() || undefined
|
||||
});
|
||||
|
||||
mensagem = {
|
||||
tipo: 'success',
|
||||
texto: 'Solicitação rejeitada com sucesso!'
|
||||
};
|
||||
|
||||
fecharRejeitar();
|
||||
|
||||
// Limpar mensagem após 3 segundos
|
||||
setTimeout(() => {
|
||||
mensagem = null;
|
||||
}, 3000);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Erro ao rejeitar solicitação';
|
||||
mensagem = {
|
||||
tipo: 'error',
|
||||
texto: errorMessage
|
||||
};
|
||||
} finally {
|
||||
processando = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ProtectedRoute allowedRoles={['ti_master', 'admin', 'ti_usuario']} maxLevel={1}>
|
||||
<div class="container mx-auto max-w-7xl px-4 py-6">
|
||||
<!-- Mensagem de Feedback -->
|
||||
{#if mensagem}
|
||||
<div class="alert alert-{mensagem.tipo} mb-6 shadow-lg">
|
||||
{#if mensagem.tipo === 'success'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6 shrink-0 stroke-current"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if mensagem.tipo === 'error'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6 shrink-0 stroke-current"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
<span>{mensagem.texto}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Header -->
|
||||
<div class="mb-8 flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="bg-primary/10 rounded-xl p-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="text-primary h-8 w-8"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-base-content text-3xl font-bold">Solicitações de Acesso</h1>
|
||||
<p class="text-base-content/60 mt-1">
|
||||
Gerencie e analise solicitações de acesso ao sistema
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Estatísticas -->
|
||||
{#if stats}
|
||||
<div class="mb-8 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
<StatsCard
|
||||
title="Total de Solicitações"
|
||||
value={stats.total}
|
||||
Icon={FileText}
|
||||
color="primary"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
title="Pendentes"
|
||||
value={stats.pendentes}
|
||||
description={stats.total > 0
|
||||
? ((stats.pendentes / stats.total) * 100).toFixed(1) + '% do total'
|
||||
: '0% do total'}
|
||||
Icon={Clock}
|
||||
color="warning"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
title="Aprovadas"
|
||||
value={stats.aprovadas}
|
||||
description={stats.total > 0
|
||||
? ((stats.aprovadas / stats.total) * 100).toFixed(1) + '% do total'
|
||||
: '0% do total'}
|
||||
Icon={CheckCircle2}
|
||||
color="success"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
title="Rejeitadas"
|
||||
value={stats.rejeitadas}
|
||||
description={stats.total > 0
|
||||
? ((stats.rejeitadas / stats.total) * 100).toFixed(1) + '% do total'
|
||||
: '0% do total'}
|
||||
Icon={XCircle}
|
||||
color="error"
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex items-center justify-center py-20">
|
||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Filtros e Busca -->
|
||||
<div class="card bg-base-100 mb-6 shadow-xl">
|
||||
<div class="card-body">
|
||||
<!-- Tabs de Status -->
|
||||
<div class="tabs tabs-boxed bg-base-200 mb-4 p-2">
|
||||
<button
|
||||
class="tab {filtroStatus === 'todos' ? 'tab-active' : ''}"
|
||||
onclick={() => (filtroStatus = 'todos')}
|
||||
>
|
||||
Todas
|
||||
</button>
|
||||
<button
|
||||
class="tab {filtroStatus === 'pendente' ? 'tab-active' : ''}"
|
||||
onclick={() => (filtroStatus = 'pendente')}
|
||||
>
|
||||
Pendentes
|
||||
</button>
|
||||
<button
|
||||
class="tab {filtroStatus === 'aprovado' ? 'tab-active' : ''}"
|
||||
onclick={() => (filtroStatus = 'aprovado')}
|
||||
>
|
||||
Aprovadas
|
||||
</button>
|
||||
<button
|
||||
class="tab {filtroStatus === 'rejeitado' ? 'tab-active' : ''}"
|
||||
onclick={() => (filtroStatus = 'rejeitado')}
|
||||
>
|
||||
Rejeitadas
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Campo de Busca -->
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Buscar por nome, matrícula ou e-mail</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Digite para buscar..."
|
||||
class="input input-bordered w-full pl-10"
|
||||
bind:value={busca}
|
||||
/>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="text-base-content/50 absolute top-1/2 left-3 h-5 w-5 -translate-y-1/2 transform"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lista de Solicitações -->
|
||||
{#if carregando}
|
||||
<div class="flex items-center justify-center py-20">
|
||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||
</div>
|
||||
{:else if solicitacoesFiltradas.length === 0}
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body py-20 text-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="text-base-content/30 mx-auto mb-4 h-16 w-16"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
<h3 class="text-base-content/70 mb-2 text-xl font-semibold">
|
||||
Nenhuma solicitação encontrada
|
||||
</h3>
|
||||
<p class="text-base-content/50">
|
||||
{#if busca.trim() || filtroStatus !== 'todos'}
|
||||
Tente ajustar os filtros ou a busca.
|
||||
{:else}
|
||||
Ainda não há solicitações de acesso cadastradas.
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
{#each solicitacoesFiltradas as solicitacao}
|
||||
<div class="card bg-base-100 shadow-xl transition-shadow hover:shadow-2xl">
|
||||
<div class="card-body">
|
||||
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="mb-2 flex items-center gap-3">
|
||||
<h3 class="text-base-content text-xl font-bold">{solicitacao.nome}</h3>
|
||||
<span class="badge {getStatusBadge(solicitacao.status)} badge-lg">
|
||||
{getStatusTexto(solicitacao.status)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="text-base-content/70 grid grid-cols-1 gap-2 text-sm md:grid-cols-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<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="M10 6H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2h-5m-4 0V5a2 2 0 114 0v1m-4 0a2 2 0 104 0m-5 8a2 2 0 100-4 2 2 0 000 4zm0 0c1.306 0 2.417.835 2.83 2M9 14a3.001 3.001 0 00-2.83 2M15 11h3m-3 4h2"
|
||||
/>
|
||||
</svg>
|
||||
<span class="font-semibold">Matrícula:</span>
|
||||
<span>{solicitacao.matricula}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<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="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>
|
||||
<span class="font-semibold">E-mail:</span>
|
||||
<span>{solicitacao.email}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<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="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="font-semibold">Telefone:</span>
|
||||
<span>{solicitacao.telefone}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-base-content/50 mt-3 text-xs">
|
||||
<span class="font-semibold">Solicitado em:</span>
|
||||
{formatarData(solicitacao.dataSolicitacao)} ({formatarDataRelativa(
|
||||
solicitacao.dataSolicitacao
|
||||
)})
|
||||
{#if solicitacao.dataResposta}
|
||||
<span class="ml-4 font-semibold">Processado em:</span>
|
||||
{formatarData(solicitacao.dataResposta)}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<button
|
||||
class="btn btn-sm btn-outline btn-primary"
|
||||
onclick={() => abrirDetalhes(solicitacao)}
|
||||
>
|
||||
<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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
||||
/>
|
||||
</svg>
|
||||
Ver Detalhes
|
||||
</button>
|
||||
|
||||
{#if solicitacao.status === 'pendente'}
|
||||
<button
|
||||
class="btn btn-sm btn-success"
|
||||
onclick={() => abrirAprovar(solicitacao)}
|
||||
>
|
||||
<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="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Aprovar
|
||||
</button>
|
||||
|
||||
<button class="btn btn-sm btn-error" onclick={() => abrirRejeitar(solicitacao)}>
|
||||
<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>
|
||||
Rejeitar
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Modal de Detalhes -->
|
||||
{#if modalDetalhesAberto && solicitacaoSelecionada}
|
||||
<dialog class="modal modal-open">
|
||||
<div class="modal-box max-w-2xl">
|
||||
<h3 class="mb-4 text-2xl font-bold">Detalhes da Solicitação</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="mb-4 flex items-center gap-3">
|
||||
<span class="badge {getStatusBadge(solicitacaoSelecionada.status)} badge-lg">
|
||||
{getStatusTexto(solicitacaoSelecionada.status)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Nome Completo</span>
|
||||
</label>
|
||||
<div class="input input-bordered">{solicitacaoSelecionada.nome}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Matrícula</span>
|
||||
</label>
|
||||
<div class="input input-bordered">{solicitacaoSelecionada.matricula}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">E-mail</span>
|
||||
</label>
|
||||
<div class="input input-bordered">{solicitacaoSelecionada.email}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Telefone</span>
|
||||
</label>
|
||||
<div class="input input-bordered">{solicitacaoSelecionada.telefone}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Data da Solicitação</span>
|
||||
</label>
|
||||
<div class="input input-bordered">
|
||||
{formatarData(solicitacaoSelecionada.dataSolicitacao)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if solicitacaoSelecionada.dataResposta}
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Data de Processamento</span>
|
||||
</label>
|
||||
<div class="input input-bordered">
|
||||
{formatarData(solicitacaoSelecionada.dataResposta)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if solicitacaoSelecionada.observacoes}
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Observações</span>
|
||||
</label>
|
||||
<div class="textarea textarea-bordered min-h-24">
|
||||
{solicitacaoSelecionada.observacoes}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="modal-action">
|
||||
<button class="btn" onclick={fecharDetalhes}>Fechar</button>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button onclick={fecharDetalhes}>fechar</button>
|
||||
</form>
|
||||
</dialog>
|
||||
{/if}
|
||||
|
||||
<!-- Modal de Aprovar -->
|
||||
{#if modalAprovarAberto && solicitacaoSelecionada}
|
||||
<dialog class="modal modal-open">
|
||||
<div class="modal-box">
|
||||
<h3 class="mb-4 text-2xl font-bold">Aprovar Solicitação</h3>
|
||||
|
||||
<div class="mb-4">
|
||||
<p class="text-base-content/70 mb-2">
|
||||
Você está prestes a aprovar a solicitação de acesso de <strong
|
||||
>{solicitacaoSelecionada.nome}</strong
|
||||
>.
|
||||
</p>
|
||||
<p class="text-base-content/60 text-sm">
|
||||
Após aprovar, o sistema permitirá que esta pessoa solicite acesso ao sistema.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-control mb-4">
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Observações (opcional)</span>
|
||||
</label>
|
||||
<textarea
|
||||
class="textarea textarea-bordered"
|
||||
placeholder="Adicione observações sobre a aprovação..."
|
||||
bind:value={observacoes}
|
||||
rows="3"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="modal-action">
|
||||
<button class="btn" onclick={fecharAprovar} disabled={processando}> Cancelar </button>
|
||||
<button class="btn btn-success" onclick={aprovarSolicitacao} disabled={processando}>
|
||||
{#if processando}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
Processando...
|
||||
{: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="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Confirmar Aprovação
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button onclick={fecharAprovar}>fechar</button>
|
||||
</form>
|
||||
</dialog>
|
||||
{/if}
|
||||
|
||||
<!-- Modal de Rejeitar -->
|
||||
{#if modalRejeitarAberto && solicitacaoSelecionada}
|
||||
<dialog class="modal modal-open">
|
||||
<div class="modal-box">
|
||||
<h3 class="mb-4 text-2xl font-bold">Rejeitar Solicitação</h3>
|
||||
|
||||
<div class="mb-4">
|
||||
<p class="text-base-content/70 mb-2">
|
||||
Você está prestes a rejeitar a solicitação de acesso de <strong
|
||||
>{solicitacaoSelecionada.nome}</strong
|
||||
>.
|
||||
</p>
|
||||
<p class="text-base-content/60 text-sm">
|
||||
Esta ação não pode ser desfeita. Recomendamos adicionar um motivo para a rejeição.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-control mb-4">
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Motivo da Rejeição (recomendado)</span>
|
||||
</label>
|
||||
<textarea
|
||||
class="textarea textarea-bordered"
|
||||
placeholder="Descreva o motivo da rejeição..."
|
||||
bind:value={observacoes}
|
||||
rows="3"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="modal-action">
|
||||
<button class="btn" onclick={fecharRejeitar} disabled={processando}> Cancelar </button>
|
||||
<button class="btn btn-error" onclick={rejeitarSolicitacao} disabled={processando}>
|
||||
{#if processando}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
Processando...
|
||||
{: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="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
Confirmar Rejeição
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button onclick={fecharRejeitar}>fechar</button>
|
||||
</form>
|
||||
</dialog>
|
||||
{/if}
|
||||
</div>
|
||||
</ProtectedRoute>
|
||||
Reference in New Issue
Block a user