feat: update point registration process with mandatory photo capture and location details
- Removed location details from the point receipt, now displayed only in detailed reports. - Implemented mandatory photo capture during point registration, enhancing accountability. - Added confirmation modal for users to verify details before finalizing point registration. - Improved error handling for webcam access and photo capture, ensuring a smoother user experience. - Enhanced UI components for better feedback and interaction during the registration process.
This commit is contained in:
@@ -45,6 +45,8 @@
|
||||
let mensagemErroModal = $state('');
|
||||
let detalhesErroModal = $state('');
|
||||
let justificativa = $state('');
|
||||
let mostrandoModalConfirmacao = $state(false);
|
||||
let dataHoraAtual = $state<{ data: string; hora: string } | null>(null);
|
||||
|
||||
const registrosHoje = $derived(registrosHojeQuery?.data || []);
|
||||
const config = $derived(configQuery?.data);
|
||||
@@ -89,9 +91,64 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Verificar permissões de localização e webcam
|
||||
async function verificarPermissoes(): Promise<{ localizacao: boolean; webcam: boolean }> {
|
||||
let localizacaoAutorizada = false;
|
||||
let webcamAutorizada = false;
|
||||
|
||||
// Verificar permissão de geolocalização
|
||||
if (navigator.geolocation) {
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
reject(new Error('Timeout'));
|
||||
}, 5000);
|
||||
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
() => {
|
||||
clearTimeout(timeoutId);
|
||||
localizacaoAutorizada = true;
|
||||
resolve();
|
||||
},
|
||||
() => {
|
||||
clearTimeout(timeoutId);
|
||||
reject(new Error('Permissão de localização negada'));
|
||||
},
|
||||
{ timeout: 5000, maximumAge: 0, enableHighAccuracy: false }
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Permissão de localização não concedida:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Verificar permissão de webcam
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
||||
webcamAutorizada = true;
|
||||
// Parar o stream imediatamente, apenas verificamos a permissão
|
||||
stream.getTracks().forEach(track => track.stop());
|
||||
} catch (error) {
|
||||
console.warn('Permissão de webcam não concedida:', error);
|
||||
}
|
||||
}
|
||||
|
||||
return { localizacao: localizacaoAutorizada, webcam: webcamAutorizada };
|
||||
}
|
||||
|
||||
async function registrarPonto() {
|
||||
if (registrando) return;
|
||||
|
||||
// Verificar permissões antes de registrar
|
||||
const permissoes = await verificarPermissoes();
|
||||
if (!permissoes.localizacao || !permissoes.webcam) {
|
||||
mensagemErroModal = 'Permissões necessárias';
|
||||
detalhesErroModal = 'Para registrar o ponto, é necessário autorizar o compartilhamento de localização e a captura de foto.';
|
||||
mostrarModalErro = true;
|
||||
return;
|
||||
}
|
||||
|
||||
registrando = true;
|
||||
erro = null;
|
||||
sucesso = null;
|
||||
@@ -106,16 +163,17 @@
|
||||
const timestamp = await obterTempoServidor(client);
|
||||
const sincronizadoComServidor = true; // Sempre true quando usamos obterTempoServidor
|
||||
|
||||
// Upload da imagem se houver (não bloquear se falhar)
|
||||
// Upload da imagem (obrigatória agora)
|
||||
let imagemId: Id<'_storage'> | undefined = undefined;
|
||||
if (imagemCapturada) {
|
||||
try {
|
||||
imagemId = await uploadImagem(imagemCapturada);
|
||||
} catch (error) {
|
||||
console.warn('Erro ao fazer upload da imagem, continuando sem foto:', error);
|
||||
// Continuar sem foto se o upload falhar
|
||||
imagemId = undefined;
|
||||
console.error('Erro ao fazer upload da imagem:', error);
|
||||
throw new Error('Erro ao fazer upload da imagem. Tente novamente.');
|
||||
}
|
||||
} else {
|
||||
throw new Error('É necessário capturar uma foto para registrar o ponto.');
|
||||
}
|
||||
|
||||
// Registrar ponto
|
||||
@@ -131,6 +189,7 @@
|
||||
sucesso = `Ponto registrado com sucesso! Tipo: ${getTipoRegistroLabel(resultado.tipo)}`;
|
||||
imagemCapturada = null;
|
||||
justificativa = ''; // Limpar justificativa após registro
|
||||
mostrandoModalConfirmacao = false;
|
||||
|
||||
// Mostrar comprovante após 1 segundo
|
||||
setTimeout(() => {
|
||||
@@ -162,50 +221,82 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleWebcamCapture(blob: Blob | null) {
|
||||
async function handleWebcamCapture(blob: Blob | null) {
|
||||
if (blob) {
|
||||
imagemCapturada = blob;
|
||||
}
|
||||
mostrandoWebcam = false;
|
||||
|
||||
// Se estava capturando automaticamente, registrar o ponto após capturar (com ou sem foto)
|
||||
if (capturandoAutomaticamente) {
|
||||
// Se capturou a foto, mostrar modal de confirmação
|
||||
if (blob && capturandoAutomaticamente) {
|
||||
capturandoAutomaticamente = false;
|
||||
// Pequeno delay para garantir que a imagem foi processada (se houver)
|
||||
setTimeout(() => {
|
||||
registrarPonto();
|
||||
}, 100);
|
||||
// Obter data e hora sincronizada do servidor
|
||||
try {
|
||||
const timestamp = await obterTempoServidor(client);
|
||||
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' });
|
||||
dataHoraAtual = { data, hora };
|
||||
} catch (error) {
|
||||
console.warn('Erro ao obter tempo do servidor, usando tempo local:', error);
|
||||
atualizarDataHoraAtual();
|
||||
}
|
||||
mostrandoModalConfirmacao = true;
|
||||
}
|
||||
}
|
||||
|
||||
function handleWebcamCancel() {
|
||||
const estavaCapturando = capturandoAutomaticamente;
|
||||
mostrandoWebcam = false;
|
||||
capturandoAutomaticamente = false;
|
||||
imagemCapturada = null;
|
||||
// Se estava capturando automaticamente e cancelou, registrar sem foto
|
||||
if (estavaCapturando) {
|
||||
registrarPonto();
|
||||
}
|
||||
mostrandoModalConfirmacao = false;
|
||||
}
|
||||
|
||||
function handleWebcamError() {
|
||||
// Em caso de erro na captura, registrar sem foto
|
||||
// Em caso de erro na captura, fechar tudo
|
||||
mostrandoWebcam = false;
|
||||
capturandoAutomaticamente = false;
|
||||
imagemCapturada = null;
|
||||
// Registrar ponto sem foto
|
||||
registrarPonto();
|
||||
mostrandoModalConfirmacao = false;
|
||||
mensagemErroModal = 'Erro ao capturar foto';
|
||||
detalhesErroModal = 'Não foi possível acessar a webcam. Verifique as permissões do navegador.';
|
||||
mostrarModalErro = true;
|
||||
}
|
||||
|
||||
function atualizarDataHoraAtual() {
|
||||
const agora = new Date();
|
||||
const data = agora.toLocaleDateString('pt-BR');
|
||||
const hora = agora.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
dataHoraAtual = { data, hora };
|
||||
}
|
||||
|
||||
async function iniciarRegistroComFoto() {
|
||||
if (registrando || coletandoInfo) return;
|
||||
|
||||
// Abrir webcam automaticamente
|
||||
// Verificar permissões antes de abrir webcam
|
||||
const permissoes = await verificarPermissoes();
|
||||
if (!permissoes.localizacao || !permissoes.webcam) {
|
||||
mensagemErroModal = 'Permissões necessárias';
|
||||
detalhesErroModal = 'Para registrar o ponto, é necessário autorizar o compartilhamento de localização e a captura de foto.';
|
||||
mostrarModalErro = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Abrir webcam
|
||||
capturandoAutomaticamente = true;
|
||||
mostrandoWebcam = true;
|
||||
}
|
||||
|
||||
function confirmarRegistro() {
|
||||
mostrandoModalConfirmacao = false;
|
||||
registrarPonto();
|
||||
}
|
||||
|
||||
function cancelarRegistro() {
|
||||
mostrandoModalConfirmacao = false;
|
||||
imagemCapturada = null;
|
||||
}
|
||||
|
||||
function fecharComprovante() {
|
||||
mostrandoComprovante = false;
|
||||
registroId = null;
|
||||
@@ -503,20 +594,15 @@
|
||||
>
|
||||
<div class="modal-box max-w-2xl w-[95%] max-h-[90vh] overflow-y-auto relative" style="margin: auto; position: relative;">
|
||||
<div class="sticky top-0 bg-base-100 z-10 pb-3 mb-4 border-b border-base-300 -mx-6 px-6">
|
||||
<h3 class="text-lg font-bold">
|
||||
{#if capturandoAutomaticamente}
|
||||
Capturando foto automaticamente...
|
||||
{:else}
|
||||
Capturar Foto
|
||||
{/if}
|
||||
</h3>
|
||||
<h3 class="text-lg font-bold">Capturar Foto</h3>
|
||||
</div>
|
||||
<div class="min-h-[200px] flex items-center justify-center py-4">
|
||||
<WebcamCapture
|
||||
onCapture={handleWebcamCapture}
|
||||
onCancel={handleWebcamCancel}
|
||||
onError={handleWebcamError}
|
||||
autoCapture={capturandoAutomaticamente}
|
||||
autoCapture={false}
|
||||
fotoObrigatoria={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -533,6 +619,61 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Modal de Confirmação -->
|
||||
{#if mostrandoModalConfirmacao && imagemCapturada && dataHoraAtual}
|
||||
<div class="modal modal-open">
|
||||
<div class="modal-box max-w-2xl">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="font-bold text-lg">Confirmar Registro de Ponto</h3>
|
||||
<button class="btn btn-sm btn-circle btn-ghost" onclick={cancelarRegistro}>
|
||||
<XCircle class="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Imagem capturada -->
|
||||
<div class="flex justify-center">
|
||||
<img
|
||||
src={URL.createObjectURL(imagemCapturada)}
|
||||
alt="Foto capturada"
|
||||
class="max-w-full max-h-96 rounded-lg border-2 border-primary object-contain"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Data e Hora -->
|
||||
<div class="text-center space-y-2">
|
||||
<div class="text-lg font-semibold">
|
||||
<span class="text-base-content/70">Data: </span>
|
||||
<span>{dataHoraAtual.data}</span>
|
||||
</div>
|
||||
<div class="text-lg font-semibold">
|
||||
<span class="text-base-content/70">Hora: </span>
|
||||
<span>{dataHoraAtual.hora}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Botões -->
|
||||
<div class="flex justify-end gap-2 pt-4">
|
||||
<button class="btn btn-outline" onclick={cancelarRegistro} disabled={registrando}>
|
||||
<XCircle class="h-5 w-5" />
|
||||
Cancelar
|
||||
</button>
|
||||
<button class="btn btn-primary" onclick={confirmarRegistro} disabled={registrando}>
|
||||
{#if registrando}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
Registrando...
|
||||
{:else}
|
||||
<CheckCircle2 class="h-5 w-5" />
|
||||
Confirmar Registro
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-backdrop" onclick={cancelarRegistro}></div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Modal Comprovante -->
|
||||
{#if mostrandoComprovante && registroId}
|
||||
<ComprovantePonto {registroId} onClose={fecharComprovante} />
|
||||
|
||||
Reference in New Issue
Block a user