Ajustes final etapa1 #71

Merged
killer-cf merged 12 commits from ajustes_final_etapa1 into master 2025-12-29 17:28:52 +00:00
7 changed files with 438 additions and 86 deletions
Showing only changes of commit a8a7469812 - Show all commits

View File

@@ -1315,10 +1315,9 @@
tabindex="0"
aria-label="Redimensionar janela pela borda superior"
class="absolute top-0 right-0 left-0 z-50 h-2 cursor-ns-resize transition-colors"
style="--hover-bg: {obterPrimariaRgba(0.2)}"
style="--hover-bg: {obterPrimariaRgba(0.2)}; border-radius: 24px 24px 0 0;"
onmousedown={(e) => handleResizeStart(e, 'n')}
onkeydown={(e) => e.key === 'Enter' && handleResizeStart(e, 'n')}
style="border-radius: 24px 24px 0 0;"
></div>
<!-- Bottom -->
<div
@@ -1326,10 +1325,9 @@
tabindex="0"
aria-label="Redimensionar janela pela borda inferior"
class="absolute right-0 bottom-0 left-0 z-50 h-2 cursor-ns-resize transition-colors"
style="--hover-bg: {obterPrimariaRgba(0.2)}"
style="--hover-bg: {obterPrimariaRgba(0.2)}; border-radius: 0 0 24px 24px;"
onmousedown={(e) => handleResizeStart(e, 's')}
onkeydown={(e) => e.key === 'Enter' && handleResizeStart(e, 's')}
style="border-radius: 0 0 24px 24px;"
></div>
<!-- Left -->
<div
@@ -1337,10 +1335,9 @@
tabindex="0"
aria-label="Redimensionar janela pela borda esquerda"
class="absolute top-0 bottom-0 left-0 z-50 w-2 cursor-ew-resize transition-colors"
style="--hover-bg: {obterPrimariaRgba(0.2)}"
style="--hover-bg: {obterPrimariaRgba(0.2)}; border-radius: 24px 0 0 24px;"
onmousedown={(e) => handleResizeStart(e, 'w')}
onkeydown={(e) => e.key === 'Enter' && handleResizeStart(e, 'w')}
style="border-radius: 24px 0 0 24px;"
></div>
<!-- Right -->
<div
@@ -1348,10 +1345,9 @@
tabindex="0"
aria-label="Redimensionar janela pela borda direita"
class="absolute top-0 right-0 bottom-0 z-50 w-2 cursor-ew-resize transition-colors"
style="--hover-bg: {obterPrimariaRgba(0.2)}"
style="--hover-bg: {obterPrimariaRgba(0.2)}; border-radius: 0 24px 24px 0;"
onmousedown={(e) => handleResizeStart(e, 'e')}
onkeydown={(e) => e.key === 'Enter' && handleResizeStart(e, 'e')}
style="border-radius: 0 24px 24px 0;"
></div>
<!-- Corners -->
<div
@@ -1359,40 +1355,36 @@
tabindex="0"
aria-label="Redimensionar janela pelo canto superior esquerdo"
class="absolute top-0 left-0 z-50 h-4 w-4 cursor-nwse-resize transition-colors"
style="--hover-bg: {obterPrimariaRgba(0.2)}"
style="--hover-bg: {obterPrimariaRgba(0.2)}; border-radius: 24px 0 0 0;"
onmousedown={(e) => handleResizeStart(e, 'nw')}
onkeydown={(e) => e.key === 'Enter' && handleResizeStart(e, 'nw')}
style="border-radius: 24px 0 0 0;"
></div>
<div
role="button"
tabindex="0"
aria-label="Redimensionar janela pelo canto superior direito"
class="absolute top-0 right-0 z-50 h-4 w-4 cursor-nesw-resize transition-colors"
style="--hover-bg: {obterPrimariaRgba(0.2)}"
style="--hover-bg: {obterPrimariaRgba(0.2)}; border-radius: 0 24px 0 0;"
onmousedown={(e) => handleResizeStart(e, 'ne')}
onkeydown={(e) => e.key === 'Enter' && handleResizeStart(e, 'ne')}
style="border-radius: 0 24px 0 0;"
></div>
<div
role="button"
tabindex="0"
aria-label="Redimensionar janela pelo canto inferior esquerdo"
class="absolute bottom-0 left-0 z-50 h-4 w-4 cursor-nesw-resize transition-colors"
style="--hover-bg: {obterPrimariaRgba(0.2)}"
style="--hover-bg: {obterPrimariaRgba(0.2)}; border-radius: 0 0 0 24px;"
onmousedown={(e) => handleResizeStart(e, 'sw')}
onkeydown={(e) => e.key === 'Enter' && handleResizeStart(e, 'sw')}
style="border-radius: 0 0 0 24px;"
></div>
<div
role="button"
tabindex="0"
aria-label="Redimensionar janela pelo canto inferior direito"
class="absolute right-0 bottom-0 z-50 h-4 w-4 cursor-nwse-resize transition-colors"
style="--hover-bg: {obterPrimariaRgba(0.2)}"
style="--hover-bg: {obterPrimariaRgba(0.2)}; border-radius: 0 0 24px 0;"
onmousedown={(e) => handleResizeStart(e, 'se')}
onkeydown={(e) => e.key === 'Enter' && handleResizeStart(e, 'se')}
style="border-radius: 0 0 24px 0;"
></div>
</div>
</div>

View File

@@ -35,6 +35,9 @@
const client = useConvexClient();
// Estado de sincronização do relógio
let sincronizacaoConcluida = $state(false);
// Chave de refresh para forçar atualização das queries após registro
let refreshKey = $state(0);
@@ -60,17 +63,12 @@
funcionarioId && dataHoje ? { funcionarioId, data: dataHoje } : 'skip'
);
const registrosHojeQuery = $derived.by(() =>
useQuery(api.pontos.listarRegistrosDia, registrosHojeParams)
);
// Queries de ponto - usando useQuery com parâmetros derivados reativos
const registrosHojeQuery = useQuery(api.pontos.listarRegistrosDia, registrosHojeParams);
const historicoSaldoQuery = $derived.by(() =>
useQuery(api.pontos.obterHistoricoESaldoDia, historicoSaldoParams)
);
const historicoSaldoQuery = useQuery(api.pontos.obterHistoricoESaldoDia, historicoSaldoParams);
const dispensaQuery = $derived.by(() =>
useQuery(api.pontos.verificarDispensaAtiva, dispensaParams)
);
const dispensaQuery = useQuery(api.pontos.verificarDispensaAtiva, dispensaParams);
// Query para obter status atual do funcionário (férias/licença)
const funcionarioStatusQuery = useQuery(
@@ -355,6 +353,9 @@
justificativa = ''; // Limpar justificativa após registro
mostrandoModalConfirmacao = false;
// Aguardar um pouco para garantir que o backend processou o registro
await new Promise((resolve) => setTimeout(resolve, 800));
// Forçar atualização das queries para mostrar o novo registro
refreshKey++;
@@ -362,11 +363,13 @@
console.log('[RegistroPonto] Registro bem-sucedido, refreshKey incrementado:', refreshKey);
}
// Aguardar um pouco para garantir que o backend processou o registro
await new Promise((resolve) => setTimeout(resolve, 500));
// Forçar mais uma atualização após o delay para garantir sincronização
// Aguardar mais um pouco e forçar outra atualização para garantir sincronização completa
setTimeout(() => {
refreshKey++;
if (import.meta.env.DEV) {
console.log('[RegistroPonto] Segunda atualização, refreshKey incrementado:', refreshKey);
}
}, 1500);
// Mostrar comprovante após 1 segundo
setTimeout(() => {
@@ -500,25 +503,27 @@
timestampBase = Date.now();
}
// Aplicar GMT offset ao timestamp
// Quando GMT é 0, usar timestamp UTC puro e deixar toLocaleTimeString() fazer a conversão automática
// Quando GMT ≠ 0, aplicar offset configurado ao timestamp
// Aplicar GMT offset ao timestamp (o horário já vem corrigido do servidor)
// Apenas aplicar o offset configurado, sem ajustes adicionais de timezone
let timestamp: number;
if (gmtOffset !== 0) {
// Aplicar offset configurado
timestamp = timestampBase + gmtOffset * 60 * 60 * 1000;
} else {
// Quando GMT = 0, manter timestamp UTC puro
// O toLocaleTimeString() converterá automaticamente para o timezone local do navegador
// Quando GMT = 0, usar timestamp base diretamente (já vem corrigido)
timestamp = timestampBase;
}
// Usar métodos UTC diretamente para evitar conversão automática do navegador
// O timestamp já está ajustado, então formatamos como UTC para manter o valor correto
const dataObj = new Date(timestamp);
const data = dataObj.toLocaleDateString('pt-BR');
const hora = dataObj.toLocaleTimeString('pt-BR', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
const dia = String(dataObj.getUTCDate()).padStart(2, '0');
const mes = String(dataObj.getUTCMonth() + 1).padStart(2, '0');
const ano = dataObj.getUTCFullYear();
const data = `${dia}/${mes}/${ano}`;
const horaStr = String(dataObj.getUTCHours()).padStart(2, '0');
const minutoStr = String(dataObj.getUTCMinutes()).padStart(2, '0');
const segundoStr = String(dataObj.getUTCSeconds()).padStart(2, '0');
const hora = `${horaStr}:${minutoStr}:${segundoStr}`;
dataHoraAtual = { data, hora };
} catch (error) {
console.warn('Erro ao obter tempo do servidor, usando tempo local:', error);
@@ -866,7 +871,8 @@
!estaDispensado &&
!emFerias &&
!emLicenca &&
temFuncionarioAssociado
temFuncionarioAssociado &&
sincronizacaoConcluida // Só permitir registro após sincronização concluída
);
});
@@ -1131,16 +1137,32 @@
id="relogio-sincronizado-ref"
class="card from-primary/10 to-primary/5 border-primary/20 w-full max-w-sm rounded-2xl border-2 bg-linear-to-br p-5 shadow-lg"
>
<RelogioSincronizado />
<RelogioSincronizado bind:sincronizacaoConcluida />
</div>
</div>
<!-- Mensagem de Aguarde Sincronização -->
{#if !sincronizacaoConcluida}
<div class="alert alert-info mb-5 rounded-xl shadow-lg">
<span class="loading loading-spinner loading-sm text-info"></span>
<div>
<h3 class="font-bold">Aguarde a sincronização</h3>
<div class="text-sm">
O sistema está sincronizando o horário com o servidor. O botão de registro será habilitado
após a conclusão da sincronização.
</div>
</div>
</div>
{/if}
<!-- Botão de Registro -->
<button
class="btn btn-primary mb-5 w-full gap-2 rounded-xl font-semibold shadow-lg transition-all duration-300 hover:shadow-xl"
onclick={iniciarRegistroComFoto}
disabled={!podeRegistrar}
title={!temFuncionarioAssociado
title={!sincronizacaoConcluida
? 'Aguarde a sincronização do horário com o servidor'
: !temFuncionarioAssociado
? 'Você não possui funcionário associado à sua conta'
: estaDispensado
? 'Você está dispensado de registrar ponto no momento'
@@ -1157,6 +1179,9 @@
{:else}
Registrando...
{/if}
{:else if !sincronizacaoConcluida}
<span class="loading loading-spinner loading-sm"></span>
Aguardando Sincronização
{:else if !temFuncionarioAssociado}
<XCircle class="h-5 w-5" />
Funcionário Não Associado

View File

@@ -7,6 +7,9 @@
const client = useConvexClient();
// Expor estados para o componente pai usando $props() do Svelte 5
let { sincronizacaoConcluida = $bindable(false) }: { sincronizacaoConcluida: boolean } = $props();
let tempoAtual = $state<Date>(new Date());
let sincronizado = $state(false);
let sincronizando = $state(false);
@@ -16,6 +19,7 @@
let intervalId: ReturnType<typeof setInterval> | null = null;
let intervaloSincronizacao: ReturnType<typeof setInterval> | null = null;
let sincronizacaoEmAndamento = $state(false); // Flag para evitar múltiplas sincronizações simultâneas
let sincronizacaoInicialConcluida = $state(false); // Flag para indicar que a primeira sincronização foi concluída
async function atualizarTempo() {
// Evitar múltiplas sincronizações simultâneas
@@ -92,6 +96,11 @@
} finally {
sincronizando = false;
sincronizacaoEmAndamento = false;
// Marcar sincronização inicial como concluída após a primeira tentativa
if (!sincronizacaoInicialConcluida) {
sincronizacaoInicialConcluida = true;
sincronizacaoConcluida = true;
}
}
}
@@ -106,6 +115,7 @@
tempoAtual = new Date(obterTempoPC());
sincronizado = false;
erro = 'Usando servidor interno';
sincronizacaoConcluida = false; // Garantir que começa como false
// Atualizar display a cada segundo
intervalId = setInterval(atualizarRelogio, 1000);
// Sincronizar em background (não bloquear) após um pequeno delay para garantir que a UI está renderizada