From bc62cd51c085128cf93ad0471ace61554edb1783 Mon Sep 17 00:00:00 2001 From: deyvisonwanderley Date: Sat, 29 Nov 2025 16:40:55 -0300 Subject: [PATCH] refactor: update modal positioning logic across components to ensure consistent placement relative to the card, enhancing user experience --- apps/web/src/lib/components/ErrorModal.svelte | 52 +++----- .../components/ponto/ComprovantePonto.svelte | 117 ++++++++---------- .../lib/components/ponto/RegistroPonto.svelte | 81 +++++++++++- 3 files changed, 145 insertions(+), 105 deletions(-) diff --git a/apps/web/src/lib/components/ErrorModal.svelte b/apps/web/src/lib/components/ErrorModal.svelte index a0176d0..c1cb5a0 100644 --- a/apps/web/src/lib/components/ErrorModal.svelte +++ b/apps/web/src/lib/components/ErrorModal.svelte @@ -20,18 +20,21 @@ if (cardRef) { const rect = cardRef.getBoundingClientRect(); - const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; - // Posicionar o modal na mesma posição do card de registro - // Centralizado horizontalmente no card - const left = rect.left + (rect.width / 2); - // Posicionar abaixo do card com um pequeno espaçamento - const top = rect.bottom + 20; + // Posicionar o modal na mesma altura Y do card (top do card) - mesma posição do texto "Registrar Ponto" + const top = rect.top; + // Garantir que o modal não saia da viewport + // Considerar uma altura mínima do modal (aproximadamente 300px) + const minTop = 20; + const maxTop = viewportHeight - 350; // Deixar espaço para o modal + const finalTop = Math.max(minTop, Math.min(top, maxTop)); + + // Centralizar horizontalmente return { - top: top, - left: left + top: finalTop, + left: window.innerWidth / 2 }; } @@ -75,37 +78,12 @@ // 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 - 20) { - 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 - 20) { - top = viewportHeight - modalHeight - 20; - } - // Ajustar se o modal sair da viewport acima - if (top < 20) { - top = 20; - } - - // Usar transform para centralizar horizontalmente baseado no left calculado - return `position: fixed; top: ${top}px; left: ${left}px; transform: translateX(-50%); max-width: ${Math.min(modalWidth, viewportWidth - 40)}px;`; + // Posicionar na altura do card, centralizado horizontalmente + // position: fixed já é relativo à viewport, então podemos usar diretamente + return `position: fixed; top: ${modalPosition.top}px; left: 50%; transform: translateX(-50%); width: 100%; max-width: 700px;`; } // Se não houver posição calculada, centralizar na tela - return 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);'; + return 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 100%; max-width: 700px;'; } // Verificar se details contém instruções ou apenas detalhes técnicos diff --git a/apps/web/src/lib/components/ponto/ComprovantePonto.svelte b/apps/web/src/lib/components/ponto/ComprovantePonto.svelte index 6031454..137b5ac 100644 --- a/apps/web/src/lib/components/ponto/ComprovantePonto.svelte +++ b/apps/web/src/lib/components/ponto/ComprovantePonto.svelte @@ -6,7 +6,6 @@ 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'>; @@ -28,18 +27,21 @@ if (cardRef) { const rect = cardRef.getBoundingClientRect(); - const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; - // Posicionar o modal na mesma posição do card de registro - // Centralizado horizontalmente no card - const left = rect.left + (rect.width / 2); - // Posicionar abaixo do card com um pequeno espaçamento - const top = rect.bottom + 20; + // Posicionar o modal na mesma altura Y do card (top do card) - mesma posição do texto "Registrar Ponto" + const top = rect.top; + // Garantir que o modal não saia da viewport + // Considerar uma altura mínima do modal (aproximadamente 300px) + const minTop = 20; + const maxTop = viewportHeight - 350; // Deixar espaço para o modal + const finalTop = Math.max(minTop, Math.min(top, maxTop)); + + // Centralizar horizontalmente return { - top: top, - left: left + top: finalTop, + left: window.innerWidth / 2 }; } @@ -47,68 +49,55 @@ return null; } - onMount(() => { - // Usar requestAnimationFrame para garantir que o DOM está completamente renderizado - const updatePosition = () => { - requestAnimationFrame(() => { - const pos = calcularPosicaoModal(); - if (pos) { - modalPosition = pos; - } - }); - }; - - // Aguardar um pouco mais para garantir que o DOM está atualizado - setTimeout(updatePosition, 50); - - // Adicionar listener de scroll para atualizar posição - const handleScroll = () => { - updatePosition(); - }; - - window.addEventListener('scroll', handleScroll, true); - window.addEventListener('resize', handleScroll); - - return () => { - window.removeEventListener('scroll', handleScroll, true); - window.removeEventListener('resize', handleScroll); - }; + // Atualizar posição quando o modal for aberto (quando registroQuery tiver dados) + $effect(() => { + if (registroQuery?.data) { + // Usar requestAnimationFrame para garantir que o DOM está completamente renderizado + const updatePosition = () => { + requestAnimationFrame(() => { + const pos = calcularPosicaoModal(); + if (pos) { + modalPosition = pos; + } else { + // Fallback para centralização + modalPosition = { + top: window.innerHeight / 2, + left: window.innerWidth / 2 + }; + } + }); + }; + + // Aguardar um pouco para garantir que o DOM está atualizado + setTimeout(updatePosition, 50); + + // Adicionar listener de scroll para atualizar posição + const handleScroll = () => { + updatePosition(); + }; + + window.addEventListener('scroll', handleScroll, true); + window.addEventListener('resize', handleScroll); + + return () => { + window.removeEventListener('scroll', handleScroll, true); + window.removeEventListener('resize', handleScroll); + }; + } else { + // Limpar posição quando o modal for fechado + modalPosition = null; + } }); // 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 - 20) { - 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 - 20) { - top = viewportHeight - modalHeight - 20; - } - // Ajustar se o modal sair da viewport acima - if (top < 20) { - top = 20; - } - - // Usar transform para centralizar horizontalmente baseado no left calculado - return `position: fixed; top: ${top}px; left: ${left}px; transform: translateX(-50%); max-width: ${Math.min(modalWidth, viewportWidth - 40)}px;`; + // Posicionar na altura do card, centralizado horizontalmente + // position: fixed já é relativo à viewport, então podemos usar diretamente + return `position: fixed; top: ${modalPosition.top}px; left: 50%; transform: translateX(-50%); width: 100%; max-width: 700px;`; } // Se não houver posição calculada, centralizar na tela - return 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);'; + return 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 100%; max-width: 700px;'; } async function gerarPDF() { diff --git a/apps/web/src/lib/components/ponto/RegistroPonto.svelte b/apps/web/src/lib/components/ponto/RegistroPonto.svelte index 07ca6cc..ee67c42 100644 --- a/apps/web/src/lib/components/ponto/RegistroPonto.svelte +++ b/apps/web/src/lib/components/ponto/RegistroPonto.svelte @@ -877,12 +877,85 @@ const saldoPositivo = $derived(historicoSaldo ? historicoSaldo.saldoMinutos >= 0 : false); - // Posicionamento dos modais - // Posicionamento dos modais - // Removido modalPosition pois agora será centralizado via CSS fixo + // Posicionamento dos modais baseado no texto "Registrar Ponto" + let modalPosition = $state<{ top: number; left: number } | null>(null); - // Função para obter estilo do modal (centralizado) + // Função para calcular a posição do modal baseada no card de registro de ponto + function calcularPosicaoModal() { + const cardRef = document.getElementById('card-registro-ponto-ref'); + + if (cardRef) { + const rect = cardRef.getBoundingClientRect(); + const viewportHeight = window.innerHeight; + + // Posicionar o modal na mesma altura Y do card (top do card) + // getBoundingClientRect() já retorna posição relativa à viewport quando usado com position: fixed + const top = rect.top; + + // Garantir que o modal não saia da viewport + // Considerar uma altura mínima do modal (aproximadamente 300px) + const minTop = 20; + const maxTop = viewportHeight - 350; // Deixar espaço para o modal + const finalTop = Math.max(minTop, Math.min(top, maxTop)); + + return { + top: finalTop, + left: window.innerWidth / 2 + }; + } + + // Se não encontrar, usar posição padrão (centro da tela) + return null; + } + + // Atualizar posição quando os modais forem abertos + $effect(() => { + if (mostrandoWebcam || mostrandoTransicao || aguardandoProcessamento || mostrandoModalConfirmacao) { + // Usar requestAnimationFrame para garantir que o DOM está completamente renderizado + const updatePosition = () => { + requestAnimationFrame(() => { + const pos = calcularPosicaoModal(); + if (pos) { + modalPosition = pos; + } else { + // Fallback para centralização + modalPosition = { + top: window.innerHeight / 2, + left: window.innerWidth / 2 + }; + } + }); + }; + + // Aguardar um pouco para garantir que o DOM está atualizado + setTimeout(updatePosition, 50); + + // Adicionar listener de scroll para atualizar posição + const handleScroll = () => { + updatePosition(); + }; + + window.addEventListener('scroll', handleScroll, true); + window.addEventListener('resize', handleScroll); + + return () => { + window.removeEventListener('scroll', handleScroll, true); + window.removeEventListener('resize', handleScroll); + }; + } else { + // Limpar posição quando o modal for fechado + modalPosition = null; + } + }); + + // Função para obter estilo do modal function getModalStyle() { + if (modalPosition) { + // Posicionar na altura do card, centralizado horizontalmente + // position: fixed já é relativo à viewport, então podemos usar diretamente + return `position: fixed; top: ${modalPosition.top}px; left: 50%; transform: translateX(-50%); width: 100%; max-width: 800px;`; + } + // Fallback para centralização padrão return 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 100%; max-width: 800px;'; }