refactor: update modal positioning logic across components to ensure consistent placement relative to the card, enhancing user experience

This commit is contained in:
2025-11-29 16:40:55 -03:00
parent 9dcd26ee82
commit bc62cd51c0
3 changed files with 145 additions and 105 deletions

View File

@@ -20,18 +20,21 @@
if (cardRef) { if (cardRef) {
const rect = cardRef.getBoundingClientRect(); const rect = cardRef.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight; const viewportHeight = window.innerHeight;
// Posicionar o modal na mesma posição do card de registro // Posicionar o modal na mesma altura Y do card (top do card) - mesma posição do texto "Registrar Ponto"
// Centralizado horizontalmente no card const top = rect.top;
const left = rect.left + (rect.width / 2);
// Posicionar abaixo do card com um pequeno espaçamento
const top = rect.bottom + 20;
// 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 { return {
top: top, top: finalTop,
left: left left: window.innerWidth / 2
}; };
} }
@@ -75,37 +78,12 @@
// Função para obter estilo do modal baseado na posição calculada // Função para obter estilo do modal baseado na posição calculada
function getModalStyle() { function getModalStyle() {
if (modalPosition) { if (modalPosition) {
// Garantir que o modal não saia da viewport // Posicionar na altura do card, centralizado horizontalmente
const viewportWidth = window.innerWidth; // position: fixed já é relativo à viewport, então podemos usar diretamente
const viewportHeight = window.innerHeight; return `position: fixed; top: ${modalPosition.top}px; left: 50%; transform: translateX(-50%); width: 100%; max-width: 700px;`;
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;`;
} }
// Se não houver posição calculada, centralizar na tela // 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 // Verificar se details contém instruções ou apenas detalhes técnicos

View File

@@ -6,7 +6,6 @@
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel'; import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
import { formatarDataHoraCompleta, getTipoRegistroLabel } from '$lib/utils/ponto'; import { formatarDataHoraCompleta, getTipoRegistroLabel } from '$lib/utils/ponto';
import logoGovPE from '$lib/assets/logo_governo_PE.png'; import logoGovPE from '$lib/assets/logo_governo_PE.png';
import { onMount } from 'svelte';
interface Props { interface Props {
registroId: Id<'registrosPonto'>; registroId: Id<'registrosPonto'>;
@@ -28,18 +27,21 @@
if (cardRef) { if (cardRef) {
const rect = cardRef.getBoundingClientRect(); const rect = cardRef.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight; const viewportHeight = window.innerHeight;
// Posicionar o modal na mesma posição do card de registro // Posicionar o modal na mesma altura Y do card (top do card) - mesma posição do texto "Registrar Ponto"
// Centralizado horizontalmente no card const top = rect.top;
const left = rect.left + (rect.width / 2);
// Posicionar abaixo do card com um pequeno espaçamento
const top = rect.bottom + 20;
// 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 { return {
top: top, top: finalTop,
left: left left: window.innerWidth / 2
}; };
} }
@@ -47,68 +49,55 @@
return null; return null;
} }
onMount(() => { // Atualizar posição quando o modal for aberto (quando registroQuery tiver dados)
// Usar requestAnimationFrame para garantir que o DOM está completamente renderizado $effect(() => {
const updatePosition = () => { if (registroQuery?.data) {
requestAnimationFrame(() => { // Usar requestAnimationFrame para garantir que o DOM está completamente renderizado
const pos = calcularPosicaoModal(); const updatePosition = () => {
if (pos) { requestAnimationFrame(() => {
modalPosition = pos; const pos = calcularPosicaoModal();
} if (pos) {
}); modalPosition = pos;
}; } else {
// Fallback para centralização
// Aguardar um pouco mais para garantir que o DOM está atualizado modalPosition = {
setTimeout(updatePosition, 50); top: window.innerHeight / 2,
left: window.innerWidth / 2
// Adicionar listener de scroll para atualizar posição };
const handleScroll = () => { }
updatePosition(); });
}; };
window.addEventListener('scroll', handleScroll, true); // Aguardar um pouco para garantir que o DOM está atualizado
window.addEventListener('resize', handleScroll); setTimeout(updatePosition, 50);
return () => { // Adicionar listener de scroll para atualizar posição
window.removeEventListener('scroll', handleScroll, true); const handleScroll = () => {
window.removeEventListener('resize', 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 // Função para obter estilo do modal baseado na posição calculada
function getModalStyle() { function getModalStyle() {
if (modalPosition) { if (modalPosition) {
// Garantir que o modal não saia da viewport // Posicionar na altura do card, centralizado horizontalmente
const viewportWidth = window.innerWidth; // position: fixed já é relativo à viewport, então podemos usar diretamente
const viewportHeight = window.innerHeight; return `position: fixed; top: ${modalPosition.top}px; left: 50%; transform: translateX(-50%); width: 100%; max-width: 700px;`;
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;`;
} }
// Se não houver posição calculada, centralizar na tela // 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() { async function gerarPDF() {

View File

@@ -877,12 +877,85 @@
const saldoPositivo = $derived(historicoSaldo ? historicoSaldo.saldoMinutos >= 0 : false); const saldoPositivo = $derived(historicoSaldo ? historicoSaldo.saldoMinutos >= 0 : false);
// Posicionamento dos modais // Posicionamento dos modais baseado no texto "Registrar Ponto"
// Posicionamento dos modais let modalPosition = $state<{ top: number; left: number } | null>(null);
// Removido modalPosition pois agora será centralizado via CSS fixo
// 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() { 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;'; return 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 100%; max-width: 800px;';
} }
</script> </script>