Ajustes final etapa1 #71
@@ -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>
|
||||
|
||||
@@ -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
|
||||
refreshKey++;
|
||||
// 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,24 +1137,40 @@
|
||||
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
|
||||
? 'Você não possui funcionário associado à sua conta'
|
||||
: estaDispensado
|
||||
? 'Você está dispensado de registrar ponto no momento'
|
||||
: emFerias
|
||||
? 'Você está em férias. Durante o período de férias não é permitido registrar ponto.'
|
||||
: emLicenca
|
||||
? 'Você está em licença. Durante o período de licença não é permitido registrar ponto.'
|
||||
: ''}
|
||||
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'
|
||||
: emFerias
|
||||
? 'Você está em férias. Durante o período de férias não é permitido registrar ponto.'
|
||||
: emLicenca
|
||||
? 'Você está em licença. Durante o período de licença não é permitido registrar ponto.'
|
||||
: ''}
|
||||
>
|
||||
{#if registrando}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user