refactor: remove ActionGuard and MenuProtection components, simplifying permission checks in various dashboard routes and enhancing footer with privacy policy link

This commit is contained in:
2025-12-13 10:50:57 -03:00
parent 13ec7cc8e3
commit c068715fc1
64 changed files with 504 additions and 585 deletions

View File

@@ -1,75 +0,0 @@
<script lang="ts">
import { useQuery } from 'convex-svelte';
import { api } from '@sgse-app/backend/convex/_generated/api';
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
import { TriangleAlert } from 'lucide-svelte';
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
interface Props {
recurso: string;
acao: string;
children?: any;
}
let { recurso, acao, children }: Props = $props();
let verificando = $state(true);
let permitido = $state(false);
// Usuário atual
const currentUser = useQuery(api.auth.getCurrentUser, {});
let permissaoQuery = $derived(
currentUser?.data
? useQuery(api.permissoesAcoes.verificarAcao, {
usuarioId: currentUser.data._id as Id<'usuarios'>,
recurso,
acao
})
: null
);
$effect(() => {
if (!currentUser?.data) {
verificando = false;
permitido = false;
const currentPath = window.location.pathname;
goto(`${resolve('/login')}?redirect=${encodeURIComponent(currentPath)}`, {
replaceState: true,
noScroll: true
});
return;
}
if (permissaoQuery?.error) {
verificando = false;
permitido = false;
} else if (permissaoQuery && !permissaoQuery.isLoading) {
// Backend retorna null quando permitido
verificando = false;
permitido = true;
}
});
</script>
{#if verificando}
<div class="flex min-h-screen items-center justify-center">
<div class="text-center">
<span class="loading loading-spinner loading-lg text-primary"></span>
<p class="text-base-content/70 mt-4">Verificando permissões...</p>
</div>
</div>
{:else if permitido}
{@render children?.()}
{:else}
<div class="flex min-h-screen items-center justify-center">
<div class="text-center">
<div class="bg-error/10 mb-4 inline-block rounded-full p-4">
<TriangleAlert class="text-error h-16 w-16" strokeWidth={2} />
</div>
<h2 class="text-base-content mb-2 text-2xl font-bold">Acesso Negado</h2>
<p class="text-base-content/70">Você não tem permissão para acessar esta ação.</p>
</div>
</div>
{/if}

View File

@@ -25,6 +25,11 @@
class="hover:text-primary transition-colors">Portal do Governo</a
>
</li>
<li>
<a href={resolve('/privacidade')} class="hover:text-primary transition-colors"
>Política de Privacidade</a
>
</li>
<li>
<a href={resolve('/abrir-chamado')} class="hover:text-primary transition-colors"
>Suporte</a

View File

@@ -1,123 +0,0 @@
<script lang="ts">
import { useQuery } from 'convex-svelte';
import { api } from '@sgse-app/backend/convex/_generated/api';
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
interface MenuProtectionProps {
menuPath: string;
requireGravar?: boolean;
children?: any;
redirectTo?: string;
}
let {
menuPath,
requireGravar = false,
children,
redirectTo = '/'
}: MenuProtectionProps = $props();
let verificando = $state(true);
let temPermissao = $state(false);
let motivoNegacao = $state('');
// Usuário atual (para autenticação básica)
const currentUser = useQuery(api.auth.getCurrentUser, {});
onMount(() => {
verificarPermissoes();
});
$effect(() => {
// Re-verificar quando o status do usuário atual mudar
verificarPermissoes();
});
function verificarPermissoes() {
// Dashboard e abertura de chamados são públicos
if (menuPath === '/' || menuPath === '/abrir-chamado') {
verificando = false;
temPermissao = true;
return;
}
// Se não está autenticado
if (!currentUser?.data) {
verificando = false;
temPermissao = false;
motivoNegacao = 'auth_required';
// Redirecionar para a página de login e salvar rota de redirecionamento
const currentPath = window.location.pathname;
goto(`${resolve('/login')}?redirect=${encodeURIComponent(currentPath)}`, {
replaceState: true,
noScroll: true
});
return;
}
// Se está autenticado, permitir acesso (component está sem verificação de menu específica no momento)
verificando = false;
temPermissao = true;
}
</script>
{#if verificando}
<div class="flex min-h-screen items-center justify-center">
<div class="text-center">
{#if motivoNegacao === 'auth_required'}
<div class="bg-warning/10 mb-4 inline-block rounded-full p-4">
<svg
xmlns="http://www.w3.org/2000/svg"
class="text-warning h-16 w-16"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
/>
</svg>
</div>
<h2 class="text-base-content mb-2 text-2xl font-bold">Acesso Restrito</h2>
<p class="text-base-content/70 mb-4">
Esta área requer autenticação.<br />
Por favor, faça login para continuar.
</p>
{:else}
<span class="loading loading-spinner loading-lg text-primary"></span>
<p class="text-base-content/70 mt-4">Verificando permissões...</p>
{/if}
</div>
</div>
{:else if temPermissao}
{@render children?.()}
{:else}
<div class="flex min-h-screen items-center justify-center">
<div class="text-center">
<div class="bg-error/10 mb-4 inline-block rounded-full p-4">
<svg
xmlns="http://www.w3.org/2000/svg"
class="text-error h-16 w-16"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<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"
/>
</svg>
</div>
<h2 class="text-base-content mb-2 text-2xl font-bold">Acesso Negado</h2>
<p class="text-base-content/70">Você não tem permissão para acessar esta página.</p>
</div>
</div>
{/if}

View File

@@ -3,16 +3,16 @@ import { api } from '@sgse-app/backend/convex/_generated/api';
import { error, redirect } from '@sveltejs/kit';
import type { FunctionReference } from 'convex/server';
export const load = async ({ locals }) => {
export const load = async ({ locals, url }) => {
if (!locals.token) {
throw redirect(302, '/login');
throw redirect(302, '/login?redirect=' + url.pathname);
}
try {
const client = createConvexHttpClient({ token: locals.token });
const currentUser = await client.query(api.auth.getCurrentUser as FunctionReference<'query'>);
if (!currentUser) {
throw redirect(302, '/login');
throw redirect(302, '/login?redirect=' + url.pathname);
}
return { currentUser };
} catch {

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -2,7 +2,6 @@
import { api } from '@sgse-app/backend/convex/_generated/api';
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
import { useConvexClient, useQuery } from 'convex-svelte';
import ActionGuard from '$lib/components/ActionGuard.svelte';
const client = useConvexClient();
@@ -232,25 +231,23 @@
</p>
</div>
<div class="flex items-center gap-4">
<ActionGuard recurso="setores" acao="criar">
<button class="btn btn-primary shadow-lg" onclick={openCreateModal}>
<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>
Novo Setor
</button>
</ActionGuard>
<button class="btn btn-primary shadow-lg" onclick={openCreateModal}>
<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>
Novo Setor
</button>
</div>
</div>
</section>
@@ -307,52 +304,48 @@
<td class="text-base-content/60 text-sm">{formatDate(setor.createdAt)}</td>
<td class="text-right">
<div class="flex justify-end gap-2">
<ActionGuard recurso="setores" acao="editar">
<button
class="btn btn-ghost btn-sm"
onclick={() => openEditModal(setor)}
aria-label="Editar setor {setor.nome}"
<button
class="btn btn-ghost btn-sm"
onclick={() => openEditModal(setor)}
aria-label="Editar setor {setor.nome}"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
</button>
</ActionGuard>
<ActionGuard recurso="setores" acao="excluir">
<button
class="btn btn-ghost btn-sm text-error"
onclick={() => openDeleteModal(setor)}
aria-label="Excluir setor {setor.nome}"
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
</button>
<button
class="btn btn-ghost btn-sm text-error"
onclick={() => openDeleteModal(setor)}
aria-label="Excluir setor {setor.nome}"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
</button>
</ActionGuard>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
</button>
</div>
</td>
</tr>

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -3,7 +3,6 @@
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
import { useConvexClient, useQuery } from 'convex-svelte';
import { goto } from '$app/navigation';
import ActionGuard from '$lib/components/ActionGuard.svelte';
const client = useConvexClient();
@@ -164,26 +163,24 @@
<option value="archived">Arquivado</option>
</select>
<ActionGuard recurso="fluxos_templates" acao="criar">
<button class="btn btn-secondary shadow-lg" onclick={openCreateModal}>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 4v16m8-8H4"
/>
</svg>
Novo Template
</button>
</ActionGuard>
<button class="btn btn-secondary shadow-lg" onclick={openCreateModal}>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 4v16m8-8H4"
/>
</svg>
Novo Template
</button>
</div>
</div>
</section>

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -4,7 +4,6 @@
import { useConvexClient, useQuery } from 'convex-svelte';
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import ActionGuard from '$lib/components/ActionGuard.svelte';
const client = useConvexClient();
let instanceId = $derived($page.params.id as Id<'flowInstances'>);
@@ -416,26 +415,24 @@
</div>
{#if instance.status === 'active'}
<ActionGuard recurso="fluxos_instancias" acao="cancelar">
<button class="btn btn-error btn-outline" onclick={() => (showCancelModal = true)}>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
Cancelar Fluxo
</button>
</ActionGuard>
<button class="btn btn-error btn-outline" onclick={() => (showCancelModal = true)}>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
Cancelar Fluxo
</button>
{/if}
</div>
</div>
@@ -616,67 +613,59 @@
{#if instance.status === 'active'}
<div class="flex flex-wrap gap-2">
{#if step.status === 'pending'}
<ActionGuard recurso="fluxos_instancias" acao="avancar_passo">
<button
class="btn btn-info btn-sm"
onclick={() => handleStartStep(step._id)}
disabled={isProcessing}
>
Iniciar
</button>
</ActionGuard>
<button
class="btn btn-info btn-sm"
onclick={() => handleStartStep(step._id)}
disabled={isProcessing}
>
Iniciar
</button>
{:else if step.status === 'in_progress'}
<ActionGuard recurso="fluxos_instancias" acao="avancar_passo">
<button
class="btn btn-success btn-sm"
onclick={() => handleCompleteStep(step._id)}
disabled={isProcessing}
>
Concluir
</button>
<button
class="btn btn-warning btn-sm"
onclick={() => handleBlockStep(step._id)}
disabled={isProcessing}
>
Bloquear
</button>
</ActionGuard>
<button
class="btn btn-success btn-sm"
onclick={() => handleCompleteStep(step._id)}
disabled={isProcessing}
>
Concluir
</button>
<button
class="btn btn-warning btn-sm"
onclick={() => handleBlockStep(step._id)}
disabled={isProcessing}
>
Bloquear
</button>
{:else if step.status === 'blocked'}
<ActionGuard recurso="fluxos_instancias" acao="avancar_passo">
<button
class="btn btn-info btn-sm"
onclick={() => handleStartStep(step._id)}
disabled={isProcessing}
>
Desbloquear
</button>
</ActionGuard>
<button
class="btn btn-info btn-sm"
onclick={() => handleStartStep(step._id)}
disabled={isProcessing}
>
Desbloquear
</button>
{/if}
<ActionGuard recurso="fluxos_instancias" acao="atribuir_usuario">
<button
class="btn btn-ghost btn-sm"
onclick={() => openReassignModal(step)}
aria-label="Reatribuir responsável"
<button
class="btn btn-ghost btn-sm"
onclick={() => openReassignModal(step)}
aria-label="Reatribuir responsável"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
</button>
</ActionGuard>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
</button>
<button
class="btn btn-ghost btn-sm"
@@ -700,29 +689,27 @@
</svg>
</button>
<ActionGuard recurso="fluxos_documentos" acao="upload">
<button
class="btn btn-ghost btn-sm"
onclick={() => openUploadModal(step)}
aria-label="Upload de documento"
<button
class="btn btn-ghost btn-sm"
onclick={() => openUploadModal(step)}
aria-label="Upload de documento"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
/>
</svg>
</button>
</ActionGuard>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
/>
</svg>
</button>
</div>
{/if}
</div>
@@ -759,15 +746,13 @@
/>
</svg>
{doc.name}
<ActionGuard recurso="fluxos_documentos" acao="excluir">
<button
class="btn btn-ghost btn-xs text-error"
onclick={() => handleDeleteDocument(doc._id)}
aria-label="Excluir documento {doc.name}"
>
×
</button>
</ActionGuard>
<button
class="btn btn-ghost btn-xs text-error"
onclick={() => handleDeleteDocument(doc._id)}
aria-label="Excluir documento {doc.name}"
>
×
</button>
</div>
{/each}
</div>

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -3,7 +3,6 @@
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
import { useConvexClient, useQuery } from 'convex-svelte';
import { goto } from '$app/navigation';
import ActionGuard from '$lib/components/ActionGuard.svelte';
const client = useConvexClient();
@@ -148,26 +147,24 @@
<option value="cancelled">Cancelado</option>
</select>
<ActionGuard recurso="fluxos_instancias" acao="criar">
<button class="btn btn-info shadow-lg" onclick={openCreateModal}>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 4v16m8-8H4"
/>
</svg>
Nova Instância
</button>
</ActionGuard>
<button class="btn btn-info shadow-lg" onclick={openCreateModal}>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 4v16m8-8H4"
/>
</svg>
Nova Instância
</button>
</div>
</div>
</section>

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -3,7 +3,6 @@
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
import { useConvexClient, useQuery } from 'convex-svelte';
import { goto } from '$app/navigation';
import ActionGuard from '$lib/components/ActionGuard.svelte';
const client = useConvexClient();
@@ -148,26 +147,24 @@
<option value="cancelled">Cancelado</option>
</select>
<ActionGuard recurso="fluxos_instancias" acao="criar">
<button class="btn btn-info shadow-lg" onclick={openCreateModal}>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 4v16m8-8H4"
/>
</svg>
Novo Fluxo
</button>
</ActionGuard>
<button class="btn btn-info shadow-lg" onclick={openCreateModal}>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 4v16m8-8H4"
/>
</svg>
Novo Fluxo
</button>
</div>
</div>
</section>

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -5,7 +5,6 @@
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import ActionGuard from '$lib/components/ActionGuard.svelte';
import RelogioPrazo from '$lib/components/RelogioPrazo.svelte';
const client = useConvexClient();
@@ -651,26 +650,24 @@
</div>
{#if instance.status === 'active'}
<ActionGuard recurso="fluxos_instancias" acao="cancelar">
<button class="btn btn-error btn-outline" onclick={() => (showCancelModal = true)}>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
Cancelar Fluxo
</button>
</ActionGuard>
<button class="btn btn-error btn-outline" onclick={() => (showCancelModal = true)}>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
Cancelar Fluxo
</button>
{/if}
</div>
</div>
@@ -867,68 +864,60 @@
{#if instance.status === 'active'}
<div class="flex flex-wrap gap-2">
{#if step.status === 'pending'}
<ActionGuard recurso="fluxos_instancias" acao="avancar_passo">
<button
class="btn btn-info btn-sm"
onclick={() => handleStartStep(step._id)}
disabled={isProcessing}
>
Iniciar
</button>
</ActionGuard>
<button
class="btn btn-info btn-sm"
onclick={() => handleStartStep(step._id)}
disabled={isProcessing}
>
Iniciar
</button>
{:else if step.status === 'in_progress'}
<ActionGuard recurso="fluxos_instancias" acao="avancar_passo">
<button
class="btn btn-success btn-sm"
onclick={() => handleCompleteStep(step._id)}
disabled={isProcessing}
>
Concluir
</button>
<button
class="btn btn-warning btn-sm"
onclick={() => handleBlockStep(step._id)}
disabled={isProcessing}
>
Bloquear
</button>
</ActionGuard>
<button
class="btn btn-success btn-sm"
onclick={() => handleCompleteStep(step._id)}
disabled={isProcessing}
>
Concluir
</button>
<button
class="btn btn-warning btn-sm"
onclick={() => handleBlockStep(step._id)}
disabled={isProcessing}
>
Bloquear
</button>
{:else if step.status === 'blocked'}
<ActionGuard recurso="fluxos_instancias" acao="avancar_passo">
<button
class="btn btn-info btn-sm"
onclick={() => handleStartStep(step._id)}
disabled={isProcessing}
>
Desbloquear
</button>
</ActionGuard>
<button
class="btn btn-info btn-sm"
onclick={() => handleStartStep(step._id)}
disabled={isProcessing}
>
Desbloquear
</button>
{/if}
<ActionGuard recurso="fluxos_instancias" acao="atribuir_usuario">
<button
class="btn btn-ghost btn-sm"
onclick={() => openReassignModal(step)}
aria-label="Reatribuir responsável"
title="Reatribuir responsável"
<button
class="btn btn-ghost btn-sm"
onclick={() => openReassignModal(step)}
aria-label="Reatribuir responsável"
title="Reatribuir responsável"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
</button>
</ActionGuard>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
</button>
</div>
{/if}
</div>
@@ -1104,30 +1093,28 @@
{/if}
</div>
{#if instance.status === 'active'}
<ActionGuard recurso="fluxos_documentos" acao="upload">
<button
class="btn btn-ghost btn-xs"
onclick={() => openUploadModal(step)}
aria-label="Upload de documento"
<button
class="btn btn-ghost btn-xs"
onclick={() => openUploadModal(step)}
aria-label="Upload de documento"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
/>
</svg>
Enviar
</button>
</ActionGuard>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
/>
</svg>
Enviar
</button>
{/if}
</div>
@@ -1158,29 +1145,27 @@
</div>
</div>
{#if instance.status === 'active'}
<ActionGuard recurso="fluxos_documentos" acao="excluir">
<button
class="btn btn-ghost btn-xs text-error shrink-0"
onclick={() => handleDeleteDocument(doc._id)}
aria-label="Excluir documento {doc.name}"
<button
class="btn btn-ghost btn-xs text-error shrink-0"
onclick={() => handleDeleteDocument(doc._id)}
aria-label="Excluir documento {doc.name}"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</ActionGuard>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
{/if}
</div>
{/each}

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,4 @@
export const load = async ({ parent }) => {
const { currentUser } = await parent();
console.log(currentUser);
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -0,0 +1,3 @@
export const load = async ({ parent }) => {
await parent();
};

View File

@@ -63,7 +63,6 @@
}
const gpsPromise = coletarGPS();
const result = await authClient.signIn.email(
{ email: matricula.trim(), password: senha },
{
@@ -158,7 +157,7 @@
}
})();
await goto(resolve(redirectAfterLogin as any), { replaceState: true });
await goto(resolve(redirectAfterLogin as string), { replaceState: true });
} else {
erroLogin = result.error?.message || 'Erro ao fazer login';
}

View File

@@ -3,6 +3,10 @@
import { Shield, FileText, Mail, Phone, Calendar } from 'lucide-svelte';
import { useQuery } from 'convex-svelte';
import { api } from '@sgse-app/backend/convex/_generated/api';
import { useAuth } from '@mmailaender/convex-better-auth-svelte/svelte';
const auth = useAuth();
const isAuthenticated = $derived(auth.isAuthenticated);
const configLGPD = useQuery(api.lgpd.obterConfiguracaoLGPD, {});
@@ -66,7 +70,7 @@
</p>
<div class="space-y-3">
<div class="flex items-start gap-3">
<FileText class="text-primary mt-1 h-5 w-5 flex-shrink-0" />
<FileText class="text-primary mt-1 h-5 w-5 shrink-0" />
<div>
<h3 class="font-semibold">Dados de Identificação</h3>
<p class="text-base-content/70 text-sm">
@@ -76,7 +80,7 @@
</div>
</div>
<div class="flex items-start gap-3">
<FileText class="text-primary mt-1 h-5 w-5 flex-shrink-0" />
<FileText class="text-primary mt-1 h-5 w-5 shrink-0" />
<div>
<h3 class="font-semibold">Dados de Contato</h3>
<p class="text-base-content/70 text-sm">
@@ -85,7 +89,7 @@
</div>
</div>
<div class="flex items-start gap-3">
<FileText class="text-primary mt-1 h-5 w-5 flex-shrink-0" />
<FileText class="text-primary mt-1 h-5 w-5 shrink-0" />
<div>
<h3 class="font-semibold">Dados Profissionais</h3>
<p class="text-base-content/70 text-sm">
@@ -95,7 +99,7 @@
</div>
</div>
<div class="flex items-start gap-3">
<FileText class="text-primary mt-1 h-5 w-5 flex-shrink-0" />
<FileText class="text-primary mt-1 h-5 w-5 shrink-0" />
<div>
<h3 class="font-semibold">Dados de Saúde</h3>
<p class="text-base-content/70 text-sm">
@@ -105,7 +109,7 @@
</div>
</div>
<div class="flex items-start gap-3">
<FileText class="text-primary mt-1 h-5 w-5 flex-shrink-0" />
<FileText class="text-primary mt-1 h-5 w-5 shrink-0" />
<div>
<h3 class="font-semibold">Dados de Acesso</h3>
<p class="text-base-content/70 text-sm">
@@ -346,15 +350,17 @@
</div>
</div>
</div>
<div class="mt-6">
<a
href={resolve('/privacidade/meus-dados')}
class="btn btn-primary btn-lg w-full md:w-auto"
>
<FileText class="h-5 w-5" />
Solicitar Meus Direitos
</a>
</div>
{#if isAuthenticated}
<div class="mt-6">
<a
href={resolve('/perfil/privacidade/meus-dados')}
class="btn btn-primary btn-lg w-full md:w-auto"
>
<FileText class="h-5 w-5" />
Solicitar Meus Direitos
</a>
</div>
{/if}
</div>
</section>
@@ -420,15 +426,17 @@
</section>
<!-- Ações -->
<div class="mt-8 flex flex-col gap-4 sm:flex-row">
<a href={resolve('/privacidade/meus-dados')} class="btn btn-primary btn-lg flex-1">
<FileText class="h-5 w-5" />
Solicitar Meus Direitos LGPD
</a>
<a href={resolve('/termo-consentimento')} class="btn btn-outline btn-lg flex-1">
<Shield class="h-5 w-5" />
Ver Termo de Consentimento
</a>
</div>
{#if isAuthenticated}
<div class="mt-8 flex flex-col gap-4 sm:flex-row">
<a href={resolve('/perfil/privacidade/meus-dados')} class="btn btn-primary btn-lg flex-1">
<FileText class="h-5 w-5" />
Solicitar Meus Direitos LGPD
</a>
<a href={resolve('/termo-consentimento')} class="btn btn-outline btn-lg flex-1">
<Shield class="h-5 w-5" />
Ver Termo de Consentimento
</a>
</div>
{/if}
</div>
</div>