feat: enhance modal positioning and styling across components

- Implemented dynamic positioning for modals in ErrorModal, ComprovantePonto, and RegistroPonto components to ensure they are centered based on the viewport and container dimensions.
- Updated modal styles to improve visual consistency, including backdrop opacity adjustments and enhanced animations.
- Refactored modal handling logic to include scroll and resize event listeners for better responsiveness during user interactions.
This commit is contained in:
2025-11-23 16:24:58 -03:00
parent 51096e7aff
commit a3eab60fcd
3 changed files with 423 additions and 82 deletions

View File

@@ -11,6 +11,92 @@
let { open = $bindable(false), title = 'Erro', message, details, onClose }: Props = $props();
let modalPosition = $state<{ top: number; left: number } | null>(null);
// Função para calcular a posição baseada no container central da página
function calcularPosicaoModal() {
// Procurar pelo elemento container-central
const containerCentral = document.getElementById('container-central');
if (containerCentral) {
const rect = containerCentral.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// Centralizar baseado na largura do container central
const containerCenterX = rect.left + (rect.width / 2);
const containerTop = rect.top;
// Posicionar o modal centralizado verticalmente na viewport, mas respeitando o container
const top = Math.max(50, Math.min(containerTop + 100, viewportHeight / 2));
return {
top: top,
left: containerCenterX
};
}
// Se não encontrar, usar posição padrão (centro da tela)
return null;
}
$effect(() => {
if (open) {
// Aguardar um pouco para garantir que o DOM está atualizado
setTimeout(() => {
modalPosition = calcularPosicaoModal();
}, 10);
// Adicionar listener de scroll para atualizar posição
const handleScroll = () => {
modalPosition = calcularPosicaoModal();
};
window.addEventListener('scroll', handleScroll, true);
window.addEventListener('resize', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll, true);
window.removeEventListener('resize', handleScroll);
};
}
});
// Função para obter estilo do modal baseado na posição calculada
function getModalStyle() {
if (modalPosition) {
// Garantir que o modal não saia da viewport
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const modalWidth = 700; // Aproximadamente max-w-2xl
const modalHeight = Math.min(viewportHeight * 0.9, 600);
let left = modalPosition.left;
let top = modalPosition.top;
// Ajustar se o modal sair da viewport à direita
if (left + (modalWidth / 2) > viewportWidth) {
left = viewportWidth - (modalWidth / 2) - 20;
}
// Ajustar se o modal sair da viewport à esquerda
if (left - (modalWidth / 2) < 20) {
left = (modalWidth / 2) + 20;
}
// Ajustar se o modal sair da viewport abaixo
if (top + modalHeight > viewportHeight) {
top = viewportHeight - modalHeight - 20;
}
// Ajustar se o modal sair da viewport acima
if (top < 20) {
top = 20;
}
return `position: fixed; top: ${top}px; left: ${left}px; transform: translate(-50%, 0); max-width: ${Math.min(modalWidth, viewportWidth - 40)}px;`;
}
// Se não houver posição calculada, centralizar na tela
return 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);';
}
// Verificar se details contém instruções ou apenas detalhes técnicos
const temInstrucoes = $derived.by(() => {
if (!details) return false;
@@ -31,22 +117,22 @@
{#if open}
<div
class="fixed inset-0 z-50 flex items-center justify-center p-4"
class="fixed inset-0 z-50 pointer-events-none"
style="animation: fadeIn 0.2s ease-out;"
role="dialog"
aria-modal="true"
aria-labelledby="modal-error-title"
>
<!-- Backdrop -->
<!-- Backdrop leve -->
<div
class="absolute inset-0 bg-black/50 backdrop-blur-sm transition-opacity duration-200"
class="absolute inset-0 bg-black/20 transition-opacity duration-200 pointer-events-auto"
onclick={handleClose}
></div>
<!-- Modal Box -->
<div
class="relative bg-base-100 rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-hidden flex flex-col z-10 transform transition-all duration-300"
style="animation: slideUp 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);"
class="absolute bg-base-100 rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-hidden flex flex-col z-10 transform transition-all duration-300 pointer-events-auto"
style="animation: slideUp 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); {getModalStyle()}"
onclick={(e) => e.stopPropagation()}
>
<!-- Header fixo -->

View File

@@ -2,10 +2,11 @@
import { useQuery } from 'convex-svelte';
import { api } from '@sgse-app/backend/convex/_generated/api';
import jsPDF from 'jspdf';
import { Printer, X } from 'lucide-svelte';
import { Printer, X, User, Clock, CheckCircle2, XCircle, Calendar, MapPin } from 'lucide-svelte';
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
import { formatarDataHoraCompleta, getTipoRegistroLabel } from '$lib/utils/ponto';
import logoGovPE from '$lib/assets/logo_governo_PE.png';
import { onMount } from 'svelte';
interface Props {
registroId: Id<'registrosPonto'>;
@@ -18,6 +19,89 @@
const configQuery = useQuery(api.configuracaoPonto.obterConfiguracao, {});
let gerando = $state(false);
let modalPosition = $state<{ top: number; left: number } | null>(null);
// Função para calcular a posição baseada no container central da página
function calcularPosicaoModal() {
// Procurar pelo elemento container-central
const containerCentral = document.getElementById('container-central');
if (containerCentral) {
const rect = containerCentral.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// Centralizar baseado na largura do container central
const containerCenterX = rect.left + (rect.width / 2);
const containerTop = rect.top;
// Posicionar o modal centralizado verticalmente na viewport, mas respeitando o container
const top = Math.max(50, Math.min(containerTop + 100, viewportHeight / 2));
return {
top: top,
left: containerCenterX
};
}
// Se não encontrar, usar posição padrão (centro da tela)
return null;
}
onMount(() => {
// Aguardar um pouco para garantir que o DOM está atualizado
setTimeout(() => {
modalPosition = calcularPosicaoModal();
}, 10);
// Adicionar listener de scroll para atualizar posição
const handleScroll = () => {
modalPosition = calcularPosicaoModal();
};
window.addEventListener('scroll', handleScroll, true);
window.addEventListener('resize', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll, true);
window.removeEventListener('resize', handleScroll);
};
});
// Função para obter estilo do modal baseado na posição calculada
function getModalStyle() {
if (modalPosition) {
// Garantir que o modal não saia da viewport
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const modalWidth = 700; // Aproximadamente max-w-2xl
const modalHeight = Math.min(viewportHeight * 0.9, 600);
let left = modalPosition.left;
let top = modalPosition.top;
// Ajustar se o modal sair da viewport à direita
if (left + (modalWidth / 2) > viewportWidth) {
left = viewportWidth - (modalWidth / 2) - 20;
}
// Ajustar se o modal sair da viewport à esquerda
if (left - (modalWidth / 2) < 20) {
left = (modalWidth / 2) + 20;
}
// Ajustar se o modal sair da viewport abaixo
if (top + modalHeight > viewportHeight) {
top = viewportHeight - modalHeight - 20;
}
// Ajustar se o modal sair da viewport acima
if (top < 20) {
top = 20;
}
return `position: fixed; top: ${top}px; left: ${left}px; transform: translate(-50%, 0); max-width: ${Math.min(modalWidth, viewportWidth - 40)}px;`;
}
// Se não houver posição calculada, centralizar na tela
return 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);';
}
async function gerarPDF() {
if (!registroQuery?.data) return;
@@ -238,99 +322,178 @@
</script>
<div
class="fixed inset-0 z-50 flex items-center justify-center p-4"
class="fixed inset-0 z-50 pointer-events-none"
style="animation: fadeIn 0.2s ease-out;"
onkeydown={(e) => e.key === 'Escape' && onClose()}
role="dialog"
aria-modal="true"
aria-labelledby="modal-comprovante-title"
>
<!-- Backdrop com blur -->
role="dialog"
aria-modal="true"
aria-labelledby="modal-comprovante-title"
>
<!-- Backdrop leve -->
<div
class="absolute inset-0 bg-black/50 backdrop-blur-sm transition-opacity duration-200"
class="absolute inset-0 bg-black/20 transition-opacity duration-200 pointer-events-auto"
onclick={onClose}
></div>
<!-- Modal Box -->
<div
class="relative bg-base-100 rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-hidden flex flex-col z-10 transform transition-all duration-300"
style="animation: slideUp 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);"
class="absolute bg-gradient-to-br from-base-100 via-base-100 to-primary/5 rounded-2xl shadow-2xl border-2 border-primary/20 max-w-2xl w-full max-h-[90vh] overflow-hidden flex flex-col z-10 transform transition-all duration-300 pointer-events-auto"
style="animation: slideUp 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); {getModalStyle()}"
onclick={(e) => e.stopPropagation()}
>
<!-- Header fixo -->
<div class="flex items-center justify-between mb-4 pb-4 border-b border-base-300 flex-shrink-0">
<h3 id="modal-comprovante-title" class="font-bold text-lg">Comprovante de Registro de Ponto</h3>
<button class="btn btn-sm btn-circle btn-ghost hover:bg-base-300" onclick={onClose}>
<!-- Header Premium com gradiente -->
<div class="flex items-center justify-between px-6 py-5 bg-gradient-to-r from-primary/10 via-primary/5 to-transparent border-b-2 border-primary/20 flex-shrink-0">
<div class="flex items-center gap-3">
<div class="p-2.5 bg-primary/20 rounded-xl shadow-lg">
<Clock class="h-6 w-6 text-primary" strokeWidth={2.5} />
</div>
<div>
<h3 id="modal-comprovante-title" class="font-bold text-xl text-base-content">Comprovante de Registro de Ponto</h3>
<p class="text-sm text-base-content/70 mt-0.5">Detalhes do registro realizado</p>
</div>
</div>
<button class="btn btn-sm btn-circle btn-ghost hover:bg-base-300 transition-all" onclick={onClose}>
<X class="h-5 w-5" />
</button>
</div>
<!-- Conteúdo com rolagem -->
<div class="flex-1 overflow-y-auto pr-2 modal-scroll">
<div class="flex-1 overflow-y-auto px-6 py-6 modal-scroll">
{#if registroQuery === undefined}
<div class="flex justify-center items-center py-8">
<span class="loading loading-spinner loading-lg"></span>
<div class="flex justify-center items-center py-12">
<span class="loading loading-spinner loading-lg text-primary"></span>
</div>
{:else if !registroQuery?.data}
<div class="alert alert-error">
<span>Erro ao carregar registro</span>
<div class="alert alert-error shadow-lg">
<XCircle class="h-5 w-5" />
<span class="font-semibold">Erro ao carregar registro</span>
</div>
{:else}
{@const registro = registroQuery.data}
<div class="space-y-4">
<div class="space-y-6">
<!-- Informações do Funcionário -->
<div class="card bg-base-200">
<div class="card-body">
<h4 class="font-bold">Dados do Funcionário</h4>
<div class="card bg-gradient-to-br from-base-100 to-base-200 shadow-lg border-2 border-primary/10 hover:shadow-xl transition-all">
<div class="card-body p-6">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-primary/10 rounded-lg">
<User class="h-5 w-5 text-primary" strokeWidth={2} />
</div>
<h4 class="font-bold text-lg text-base-content">Dados do Funcionário</h4>
</div>
{#if registro.funcionario}
<p><strong>Matrícula:</strong> {registro.funcionario.matricula || 'N/A'}</p>
<p><strong>Nome:</strong> {registro.funcionario.nome}</p>
{#if registro.funcionario.descricaoCargo}
<p><strong>Cargo/Função:</strong> {registro.funcionario.descricaoCargo}</p>
{/if}
<div class="space-y-3">
{#if registro.funcionario.matricula}
<div class="flex items-start gap-3 p-3 bg-base-100 rounded-lg border border-base-300">
<div class="flex-1">
<span class="text-xs font-semibold text-base-content/60 uppercase tracking-wide">Matrícula</span>
<p class="text-base font-semibold text-base-content mt-1">{registro.funcionario.matricula}</p>
</div>
</div>
{/if}
<div class="flex items-start gap-3 p-3 bg-base-100 rounded-lg border border-base-300">
<div class="flex-1">
<span class="text-xs font-semibold text-base-content/60 uppercase tracking-wide">Nome</span>
<p class="text-base font-semibold text-base-content mt-1">{registro.funcionario.nome}</p>
</div>
</div>
{#if registro.funcionario.descricaoCargo}
<div class="flex items-start gap-3 p-3 bg-base-100 rounded-lg border border-base-300">
<div class="flex-1">
<span class="text-xs font-semibold text-base-content/60 uppercase tracking-wide">Cargo/Função</span>
<p class="text-base font-semibold text-base-content mt-1">{registro.funcionario.descricaoCargo}</p>
</div>
</div>
{/if}
</div>
{/if}
</div>
</div>
<!-- Informações do Registro -->
<div class="card bg-base-200">
<div class="card-body">
<h4 class="font-bold">Dados do Registro</h4>
<p>
<strong>Tipo:</strong>
{configQuery?.data
? getTipoRegistroLabel(registro.tipo, {
nomeEntrada: configQuery.data.nomeEntrada,
nomeSaidaAlmoco: configQuery.data.nomeSaidaAlmoco,
nomeRetornoAlmoco: configQuery.data.nomeRetornoAlmoco,
nomeSaida: configQuery.data.nomeSaida,
})
: getTipoRegistroLabel(registro.tipo)}
</p>
<p>
<strong>Data e Hora:</strong>
{formatarDataHoraCompleta(registro.data, registro.hora, registro.minuto, registro.segundo)}
</p>
<p>
<strong>Status:</strong>
<span class="badge {registro.dentroDoPrazo ? 'badge-success' : 'badge-error'}">
{registro.dentroDoPrazo ? 'Dentro do Prazo' : 'Fora do Prazo'}
</span>
</p>
<p><strong>Tolerância:</strong> {registro.toleranciaMinutos} minutos</p>
<div class="card bg-gradient-to-br from-primary/5 to-primary/10 shadow-lg border-2 border-primary/20 hover:shadow-xl transition-all">
<div class="card-body p-6">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-primary/20 rounded-lg">
<Clock class="h-5 w-5 text-primary" strokeWidth={2} />
</div>
<h4 class="font-bold text-lg text-base-content">Dados do Registro</h4>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Tipo -->
<div class="bg-base-100 rounded-lg p-4 border border-base-300 shadow-sm">
<span class="text-xs font-semibold text-base-content/60 uppercase tracking-wide">Tipo</span>
<p class="text-lg font-bold text-primary mt-1">
{configQuery?.data
? getTipoRegistroLabel(registro.tipo, {
nomeEntrada: configQuery.data.nomeEntrada,
nomeSaidaAlmoco: configQuery.data.nomeSaidaAlmoco,
nomeRetornoAlmoco: configQuery.data.nomeRetornoAlmoco,
nomeSaida: configQuery.data.nomeSaida,
})
: getTipoRegistroLabel(registro.tipo)}
</p>
</div>
<!-- Data e Hora -->
<div class="bg-base-100 rounded-lg p-4 border border-base-300 shadow-sm">
<span class="text-xs font-semibold text-base-content/60 uppercase tracking-wide">Data e Hora</span>
<p class="text-lg font-bold text-base-content mt-1">
{formatarDataHoraCompleta(registro.data, registro.hora, registro.minuto, registro.segundo)}
</p>
</div>
<!-- Status -->
<div class="bg-base-100 rounded-lg p-4 border border-base-300 shadow-sm">
<span class="text-xs font-semibold text-base-content/60 uppercase tracking-wide">Status</span>
<div class="mt-2">
<span class="badge badge-lg gap-2 {registro.dentroDoPrazo ? 'badge-success' : 'badge-error'}">
{#if registro.dentroDoPrazo}
<CheckCircle2 class="h-4 w-4" />
{:else}
<XCircle class="h-4 w-4" />
{/if}
{registro.dentroDoPrazo ? 'Dentro do Prazo' : 'Fora do Prazo'}
</span>
</div>
</div>
<!-- Tolerância -->
<div class="bg-base-100 rounded-lg p-4 border border-base-300 shadow-sm">
<span class="text-xs font-semibold text-base-content/60 uppercase tracking-wide">Tolerância</span>
<p class="text-lg font-bold text-base-content mt-1">{registro.toleranciaMinutos} minutos</p>
</div>
</div>
</div>
</div>
<!-- Imagem Capturada -->
{#if registro.imagemUrl}
<div class="card bg-base-200">
<div class="card-body">
<h4 class="font-bold mb-2">Foto Capturada</h4>
<div class="flex justify-center">
<div class="card bg-gradient-to-br from-base-100 to-base-200 shadow-lg border-2 border-primary/10 hover:shadow-xl transition-all">
<div class="card-body p-6">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-primary/10 rounded-lg">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-primary"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
</div>
<h4 class="font-bold text-lg text-base-content">Foto Capturada</h4>
</div>
<div class="flex justify-center bg-base-100 rounded-xl p-4 border-2 border-primary/20">
<img
src={registro.imagemUrl}
alt="Foto do registro de ponto"
class="max-w-full max-h-[250px] rounded-lg border-2 border-primary object-contain"
class="max-w-full max-h-[300px] rounded-lg shadow-md object-contain"
onerror={(e) => {
console.error('Erro ao carregar imagem:', e);
(e.target as HTMLImageElement).style.display = 'none';
@@ -345,16 +508,20 @@
</div>
<!-- Footer fixo com botões -->
<div class="flex justify-end gap-2 pt-4 mt-4 border-t border-base-300 flex-shrink-0">
<button class="btn btn-primary gap-2" onclick={gerarPDF} disabled={gerando}>
<div class="flex justify-end gap-3 px-6 py-4 border-t-2 border-primary/20 bg-base-100/50 backdrop-blur-sm flex-shrink-0">
<button class="btn btn-outline gap-2" onclick={onClose}>
<X class="h-4 w-4" />
Fechar
</button>
<button class="btn btn-primary gap-2 shadow-lg hover:shadow-xl transition-all" onclick={gerarPDF} disabled={gerando}>
{#if gerando}
<span class="loading loading-spinner loading-sm"></span>
Gerando...
{:else}
<Printer class="h-5 w-5" />
Imprimir Comprovante
{/if}
Imprimir Comprovante
</button>
<button class="btn btn-outline" onclick={onClose}>Fechar</button>
</div>
</div>
</div>

View File

@@ -851,6 +851,94 @@
});
const saldoPositivo = $derived(historicoSaldo ? historicoSaldo.saldoMinutos >= 0 : false);
// Posicionamento dos modais
let modalPosition = $state<{ top: number; left: number } | null>(null);
// Função para calcular a posição baseada no container central da página
function calcularPosicaoModal() {
// Procurar pelo elemento container-central
const containerCentral = document.getElementById('container-central');
if (containerCentral) {
const rect = containerCentral.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// Centralizar baseado na largura do container central
const containerCenterX = rect.left + (rect.width / 2);
const containerTop = rect.top;
// Posicionar o modal centralizado verticalmente na viewport, mas respeitando o container
const top = Math.max(50, Math.min(containerTop + 100, viewportHeight / 2));
return {
top: top,
left: containerCenterX
};
}
// Se não encontrar, usar posição padrão (centro da tela)
return null;
}
// Atualizar posição quando os modais forem abertos ou quando a página rolar
$effect(() => {
if (mostrandoWebcam || mostrandoModalConfirmacao || aguardandoProcessamento || mostrarModalErro) {
// Aguardar um pouco para garantir que o DOM está atualizado
setTimeout(() => {
modalPosition = calcularPosicaoModal();
}, 10);
// Adicionar listener de scroll para atualizar posição
const handleScroll = () => {
modalPosition = calcularPosicaoModal();
};
window.addEventListener('scroll', handleScroll, true);
window.addEventListener('resize', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll, true);
window.removeEventListener('resize', handleScroll);
};
}
});
// Função para obter estilo do modal baseado na posição calculada
function getModalStyle() {
if (modalPosition) {
// Garantir que o modal não saia da viewport
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const modalWidth = 800; // Aproximadamente max-w-2xl ou max-w-3xl
const modalHeight = Math.min(viewportHeight * 0.9, 600);
let left = modalPosition.left;
let top = modalPosition.top;
// Ajustar se o modal sair da viewport à direita
if (left + (modalWidth / 2) > viewportWidth) {
left = viewportWidth - (modalWidth / 2) - 20;
}
// Ajustar se o modal sair da viewport à esquerda
if (left - (modalWidth / 2) < 20) {
left = (modalWidth / 2) + 20;
}
// Ajustar se o modal sair da viewport abaixo
if (top + modalHeight > viewportHeight) {
top = viewportHeight - modalHeight - 20;
}
// Ajustar se o modal sair da viewport acima
if (top < 20) {
top = 20;
}
return `position: fixed; top: ${top}px; left: ${left}px; transform: translate(-50%, 0); max-width: ${Math.min(modalWidth, viewportWidth - 40)}px;`;
}
// Se não houver posição calculada, centralizar na tela
return 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);';
}
</script>
<div class="space-y-6">
@@ -1330,22 +1418,22 @@
<!-- Modal Webcam -->
{#if mostrandoWebcam}
<div
class="fixed inset-0 z-50 flex items-center justify-center p-4"
class="fixed inset-0 z-50 pointer-events-none"
style="animation: fadeIn 0.2s ease-out;"
role="dialog"
aria-modal="true"
aria-labelledby="modal-webcam-title"
>
<!-- Backdrop -->
<!-- Backdrop leve -->
<div
class="absolute inset-0 bg-black/50 backdrop-blur-sm transition-opacity duration-200"
class="absolute inset-0 bg-black/20 transition-opacity duration-200 pointer-events-auto"
onclick={handleWebcamCancel}
></div>
<!-- Modal Box -->
<div
class="relative bg-base-100 rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-hidden flex flex-col z-10 transform transition-all duration-300"
style="animation: slideUp 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);"
class="absolute bg-base-100 rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-hidden flex flex-col z-10 transform transition-all duration-300 pointer-events-auto"
style="animation: slideUp 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); {getModalStyle()}"
onclick={(e) => e.stopPropagation()}
>
<!-- Header fixo -->
@@ -1383,19 +1471,19 @@
<!-- Modal de Aguardando Processamento -->
{#if aguardandoProcessamento}
<div
class="fixed inset-0 z-50 flex items-center justify-center p-4"
class="fixed inset-0 z-50 pointer-events-none"
style="animation: fadeIn 0.2s ease-out;"
role="dialog"
aria-modal="true"
aria-labelledby="modal-aguardando-title"
>
<!-- Backdrop -->
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm transition-opacity duration-200"></div>
<!-- Backdrop leve -->
<div class="absolute inset-0 bg-black/20 transition-opacity duration-200 pointer-events-auto"></div>
<!-- Modal Box -->
<div
class="relative bg-base-100 rounded-2xl shadow-2xl max-w-md w-full z-10 transform transition-all duration-300 p-8"
style="animation: slideUp 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);"
class="absolute bg-base-100 rounded-2xl shadow-2xl max-w-md w-full z-10 transform transition-all duration-300 p-8 pointer-events-auto"
style="animation: slideUp 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); {getModalStyle()}"
>
<div class="flex flex-col items-center gap-4 text-center">
<span class="loading loading-spinner loading-lg text-primary"></span>
@@ -1409,22 +1497,22 @@
<!-- Modal de Confirmação -->
{#if mostrandoModalConfirmacao && imagemCapturada && dataHoraAtual}
<div
class="fixed inset-0 z-50 flex items-center justify-center p-4"
class="fixed inset-0 z-50 pointer-events-none"
style="animation: fadeIn 0.2s ease-out;"
role="dialog"
aria-modal="true"
aria-labelledby="modal-confirmacao-title"
>
<!-- Backdrop -->
<!-- Backdrop leve -->
<div
class="absolute inset-0 bg-black/50 backdrop-blur-sm transition-opacity duration-200"
class="absolute inset-0 bg-black/20 transition-opacity duration-200 pointer-events-auto"
onclick={cancelarRegistro}
></div>
<!-- Modal Box -->
<div
class="relative bg-base-100 rounded-2xl shadow-2xl max-w-3xl w-full max-h-[90vh] overflow-hidden flex flex-col z-10 transform transition-all duration-300"
style="animation: slideUp 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);"
class="absolute bg-base-100 rounded-2xl shadow-2xl max-w-3xl w-full max-h-[90vh] overflow-hidden flex flex-col z-10 transform transition-all duration-300 pointer-events-auto"
style="animation: slideUp 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); {getModalStyle()}"
onclick={(e) => e.stopPropagation()}
>
<!-- Header fixo -->