feat: enhance LGPD compliance features by adding configurable data protection officer details, consent term settings, and improved error handling across various components
This commit is contained in:
@@ -4,8 +4,15 @@
|
||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||
import { Shield, FileText, AlertTriangle, CheckCircle, Settings, Users } from 'lucide-svelte';
|
||||
import StatsCard from '$lib/components/ti/StatsCard.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const estatisticas = useQuery(api.lgpd.obterEstatisticasLGPD, {});
|
||||
|
||||
onMount(() => {
|
||||
if (estatisticas?.error) {
|
||||
console.error('Erro ao carregar estatísticas LGPD:', estatisticas.error);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="container mx-auto px-4 py-6 max-w-7xl">
|
||||
@@ -23,11 +30,20 @@
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
{#if estatisticas}
|
||||
{#if estatisticas === undefined}
|
||||
<div class="flex justify-center items-center py-20">
|
||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||
</div>
|
||||
{:else if estatisticas?.error}
|
||||
<div class="alert alert-error mb-6">
|
||||
<AlertTriangle class="h-5 w-5" />
|
||||
<span>Erro ao carregar estatísticas: {estatisticas.error}</span>
|
||||
</div>
|
||||
{:else if estatisticas?.data}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
<StatsCard
|
||||
title="Solicitações Pendentes"
|
||||
value={estatisticas.solicitacoesPendentes}
|
||||
value={estatisticas.data.solicitacoesPendentes}
|
||||
description="Aguardando resposta"
|
||||
Icon={AlertTriangle}
|
||||
color="warning"
|
||||
@@ -35,7 +51,7 @@
|
||||
|
||||
<StatsCard
|
||||
title="Solicitações Vencendo"
|
||||
value={estatisticas.solicitacoesVencendo}
|
||||
value={estatisticas.data.solicitacoesVencendo}
|
||||
description="Prazo próximo"
|
||||
Icon={AlertTriangle}
|
||||
color="error"
|
||||
@@ -43,7 +59,7 @@
|
||||
|
||||
<StatsCard
|
||||
title="Total de Solicitações"
|
||||
value={estatisticas.totalSolicitacoes}
|
||||
value={estatisticas.data.totalSolicitacoes}
|
||||
description="Todas as solicitações"
|
||||
Icon={FileText}
|
||||
color="info"
|
||||
@@ -51,16 +67,12 @@
|
||||
|
||||
<StatsCard
|
||||
title="Consentimentos Ativos"
|
||||
value={estatisticas.consentimentosAtivos}
|
||||
value={estatisticas.data.consentimentosAtivos}
|
||||
description="Consentimentos válidos"
|
||||
Icon={CheckCircle}
|
||||
color="success"
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex justify-center items-center py-20">
|
||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Ações Rápidas -->
|
||||
@@ -87,14 +99,14 @@
|
||||
</div>
|
||||
|
||||
<!-- Informações -->
|
||||
{#if estatisticas}
|
||||
{#if estatisticas?.data}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Solicitações por Tipo -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-xl mb-4">Solicitações por Tipo</h2>
|
||||
<div class="space-y-2">
|
||||
{#each Object.entries(estatisticas.solicitacoesPorTipo) as [tipo, quantidade]}
|
||||
{#each Object.entries(estatisticas.data.solicitacoesPorTipo) as [tipo, quantidade]}
|
||||
<div class="flex justify-between items-center p-2 bg-base-200 rounded">
|
||||
<span class="text-sm font-medium">{tipo}</span>
|
||||
<span class="badge badge-primary">{quantidade}</span>
|
||||
@@ -111,19 +123,19 @@
|
||||
<div class="space-y-3">
|
||||
<div class="flex justify-between">
|
||||
<span>Total de ROTs:</span>
|
||||
<span class="font-semibold">{estatisticas.totalROTs}</span>
|
||||
<span class="font-semibold">{estatisticas.data.totalROTs}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>ROTs Ativos:</span>
|
||||
<span class="font-semibold text-success">{estatisticas.rotsAtivos}</span>
|
||||
<span class="font-semibold text-success">{estatisticas.data.rotsAtivos}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>Total de Consentimentos:</span>
|
||||
<span class="font-semibold">{estatisticas.totalConsentimentos}</span>
|
||||
<span class="font-semibold">{estatisticas.data.totalConsentimentos}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>Consentimentos Ativos:</span>
|
||||
<span class="font-semibold text-success">{estatisticas.consentimentosAtivos}</span>
|
||||
<span class="font-semibold text-success">{estatisticas.data.consentimentosAtivos}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { resolve } from '$app/paths';
|
||||
import { useQuery, useMutation } from 'convex-svelte';
|
||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||
import { Shield, Save, Mail, Phone, User, Calendar } from 'lucide-svelte';
|
||||
import { Shield, Save, Mail, Phone, User, Calendar, ToggleLeft, ToggleRight } from 'lucide-svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
const config = useQuery(api.lgpd.obterConfiguracaoLGPD, {});
|
||||
@@ -11,8 +11,11 @@
|
||||
let encarregadoNome = $state('');
|
||||
let encarregadoEmail = $state('');
|
||||
let encarregadoTelefone = $state('');
|
||||
let encarregadoHorarioAtendimento = $state('Segunda a Sexta, das 8h às 17h');
|
||||
let prazoRespostaPadrao = $state(15);
|
||||
let diasAlertaVencimento = $state(3);
|
||||
let termoObrigatorio = $state(false);
|
||||
let versaoTermoAtual = $state('1.0');
|
||||
let carregando = $state(false);
|
||||
|
||||
// Sincronizar com query
|
||||
@@ -21,8 +24,11 @@
|
||||
encarregadoNome = config.data.encarregadoNome || '';
|
||||
encarregadoEmail = config.data.encarregadoEmail || '';
|
||||
encarregadoTelefone = config.data.encarregadoTelefone || '';
|
||||
encarregadoHorarioAtendimento = config.data.encarregadoHorarioAtendimento || 'Segunda a Sexta, das 8h às 17h';
|
||||
prazoRespostaPadrao = config.data.prazoRespostaPadrao;
|
||||
diasAlertaVencimento = config.data.diasAlertaVencimento;
|
||||
termoObrigatorio = config.data.termoObrigatorio;
|
||||
versaoTermoAtual = config.data.versaoTermoAtual;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -34,13 +40,17 @@
|
||||
encarregadoNome: encarregadoNome || undefined,
|
||||
encarregadoEmail: encarregadoEmail || undefined,
|
||||
encarregadoTelefone: encarregadoTelefone || undefined,
|
||||
encarregadoHorarioAtendimento: encarregadoHorarioAtendimento || undefined,
|
||||
prazoRespostaPadrao,
|
||||
diasAlertaVencimento
|
||||
diasAlertaVencimento,
|
||||
termoObrigatorio,
|
||||
versaoTermoAtual: versaoTermoAtual || '1.0'
|
||||
});
|
||||
|
||||
toast.success('Configurações salvas com sucesso!');
|
||||
} catch (error: any) {
|
||||
toast.error(error.message || 'Erro ao salvar configurações');
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : 'Erro ao salvar configurações';
|
||||
toast.error(message);
|
||||
} finally {
|
||||
carregando = false;
|
||||
}
|
||||
@@ -120,6 +130,73 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Horário de Atendimento</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<Calendar class="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-base-content/40" />
|
||||
<input
|
||||
type="text"
|
||||
bind:value={encarregadoHorarioAtendimento}
|
||||
placeholder="Segunda a Sexta, das 8h às 17h"
|
||||
class="input input-bordered w-full pl-10"
|
||||
/>
|
||||
</div>
|
||||
<label class="label">
|
||||
<span class="label-text-alt text-base-content/60">
|
||||
Horário de atendimento do Encarregado de Dados
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Configurações de Termo de Consentimento -->
|
||||
<div class="card bg-base-100 shadow-xl mb-6">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-2xl mb-4">Termo de Consentimento</h2>
|
||||
<p class="text-base-content/60 mb-6">
|
||||
Configure se o termo de consentimento é obrigatório para acesso ao sistema.
|
||||
</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="form-control">
|
||||
<label class="cursor-pointer label justify-start gap-4">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={termoObrigatorio}
|
||||
class="checkbox checkbox-primary"
|
||||
/>
|
||||
<div>
|
||||
<span class="label-text font-semibold">Termo de Consentimento Obrigatório</span>
|
||||
<p class="text-sm text-base-content/60">
|
||||
Quando habilitado, os usuários precisam aceitar o termo de consentimento
|
||||
antes de acessar o sistema.
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Versão Atual do Termo</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={versaoTermoAtual}
|
||||
placeholder="1.0"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
<label class="label">
|
||||
<span class="label-text-alt text-base-content/60">
|
||||
Versão do termo de consentimento atual. Ao alterar, usuários precisarão aceitar
|
||||
novamente.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -87,8 +87,9 @@
|
||||
compartilhamentoTerceiros = false;
|
||||
terceiros = [];
|
||||
descricao = '';
|
||||
} catch (error: any) {
|
||||
toast.error(error.message || 'Erro ao criar registro');
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : 'Erro ao criar registro';
|
||||
toast.error(message);
|
||||
} finally {
|
||||
carregando = false;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { resolve } from '$app/paths';
|
||||
import { useQuery, useMutation } from 'convex-svelte';
|
||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||
import {
|
||||
Shield,
|
||||
FileText,
|
||||
@@ -106,7 +107,7 @@
|
||||
|
||||
try {
|
||||
await responderSolicitacao({
|
||||
solicitacaoId: solicitacaoSelecionada as any,
|
||||
solicitacaoId: solicitacaoSelecionada as Id<'solicitacoesLGPD'>,
|
||||
resposta: resposta.trim(),
|
||||
status: statusResposta
|
||||
});
|
||||
@@ -114,8 +115,9 @@
|
||||
toast.success('Solicitação respondida com sucesso!');
|
||||
solicitacaoSelecionada = null;
|
||||
resposta = '';
|
||||
} catch (error: any) {
|
||||
toast.error(error.message || 'Erro ao responder solicitação');
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : 'Erro ao responder solicitação';
|
||||
toast.error(message);
|
||||
} finally {
|
||||
carregando = false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user