feat: implement customizable point registration labels and GMT offset adjustment
- Added functionality to customize labels for point registration types (Entrada, Saída, etc.) in the configuration settings. - Introduced a GMT offset adjustment feature to account for time zone differences during point registration. - Updated the backend to ensure default values for custom labels and GMT offset are set correctly. - Enhanced the UI to allow users to input and save personalized names for each type of point registration. - Improved the point registration process to utilize the new configuration settings for displaying labels consistently across the application.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { useConvexClient, useQuery } from 'convex-svelte';
|
||||
import { useQuery } from 'convex-svelte';
|
||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||
import jsPDF from 'jspdf';
|
||||
import { Printer, X } from 'lucide-svelte';
|
||||
@@ -15,8 +14,8 @@
|
||||
|
||||
let { registroId, onClose }: Props = $props();
|
||||
|
||||
const client = useConvexClient();
|
||||
const registroQuery = useQuery(api.pontos.obterRegistro, { registroId });
|
||||
const configQuery = useQuery(api.configuracaoPonto.obterConfiguracao, {});
|
||||
|
||||
let gerando = $state(false);
|
||||
|
||||
@@ -98,7 +97,16 @@
|
||||
yPosition += 8;
|
||||
doc.setFontSize(10);
|
||||
|
||||
doc.text(`Tipo: ${getTipoRegistroLabel(registro.tipo)}`, 15, yPosition);
|
||||
const config = configQuery?.data;
|
||||
const tipoLabel = config
|
||||
? getTipoRegistroLabel(registro.tipo, {
|
||||
nomeEntrada: config.nomeEntrada,
|
||||
nomeSaidaAlmoco: config.nomeSaidaAlmoco,
|
||||
nomeRetornoAlmoco: config.nomeRetornoAlmoco,
|
||||
nomeSaida: config.nomeSaida,
|
||||
})
|
||||
: getTipoRegistroLabel(registro.tipo);
|
||||
doc.text(`Tipo: ${tipoLabel}`, 15, yPosition);
|
||||
yPosition += 6;
|
||||
|
||||
const dataHora = formatarDataHoraCompleta(registro.data, registro.hora, registro.minuto, registro.segundo);
|
||||
@@ -118,30 +126,6 @@
|
||||
);
|
||||
yPosition += 10;
|
||||
|
||||
// Detalhes de localização removidos do comprovante (serão exibidos apenas no relatório detalhado)
|
||||
|
||||
// Informações do Dispositivo (resumido)
|
||||
if (registro.browser || registro.sistemaOperacional) {
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.text('DISPOSITIVO', 15, yPosition);
|
||||
doc.setFont('helvetica', 'normal');
|
||||
yPosition += 8;
|
||||
doc.setFontSize(10);
|
||||
|
||||
if (registro.browser) {
|
||||
doc.text(`Navegador: ${registro.browser}${registro.browserVersion ? ` ${registro.browserVersion}` : ''}`, 15, yPosition);
|
||||
yPosition += 6;
|
||||
}
|
||||
if (registro.sistemaOperacional) {
|
||||
doc.text(`Sistema: ${registro.sistemaOperacional}${registro.osVersion ? ` ${registro.osVersion}` : ''}`, 15, yPosition);
|
||||
yPosition += 6;
|
||||
}
|
||||
if (registro.ipAddress) {
|
||||
doc.text(`IP: ${registro.ipAddress}`, 15, yPosition);
|
||||
yPosition += 6;
|
||||
}
|
||||
}
|
||||
|
||||
// Imagem capturada (se disponível)
|
||||
if (registro.imagemUrl) {
|
||||
yPosition += 10;
|
||||
@@ -292,9 +276,19 @@
|
||||
|
||||
<!-- Informações do Registro -->
|
||||
<div class="card bg-base-200">
|
||||
<div class="card-body">
|
||||
<h4 class="font-bold">Dados do Registro</h4>
|
||||
<p><strong>Tipo:</strong> {getTipoRegistroLabel(registro.tipo)}</p>
|
||||
<div class="card-body">
|
||||
<h4 class="font-bold">Dados do Registro</h4>
|
||||
<p>
|
||||
<strong>Tipo:</strong>
|
||||
{configQuery?.data
|
||||
? getTipoRegistroLabel(registro.tipo, {
|
||||
nomeEntrada: configQuery.data.nomeEntrada,
|
||||
nomeSaidaAlmoco: configQuery.data.nomeSaidaAlmoco,
|
||||
nomeRetornoAlmoco: configQuery.data.nomeRetornoAlmoco,
|
||||
nomeSaida: configQuery.data.nomeSaida,
|
||||
})
|
||||
: getTipoRegistroLabel(registro.tipo)}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Data e Hora:</strong>
|
||||
{formatarDataHoraCompleta(registro.data, registro.hora, registro.minuto, registro.segundo)}
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
// Estados
|
||||
let mostrandoWebcam = $state(false);
|
||||
let registrando = $state(false);
|
||||
let erro = $state<string | null>(null);
|
||||
let sucesso = $state<string | null>(null);
|
||||
let registroId = $state<Id<'registrosPonto'> | null>(null);
|
||||
let mostrandoComprovante = $state(false);
|
||||
@@ -63,6 +62,14 @@
|
||||
});
|
||||
|
||||
const tipoLabel = $derived.by(() => {
|
||||
if (config) {
|
||||
return getTipoRegistroLabel(proximoTipo, {
|
||||
nomeEntrada: config.nomeEntrada,
|
||||
nomeSaidaAlmoco: config.nomeSaidaAlmoco,
|
||||
nomeRetornoAlmoco: config.nomeRetornoAlmoco,
|
||||
nomeSaida: config.nomeSaida,
|
||||
});
|
||||
}
|
||||
return getTipoRegistroLabel(proximoTipo);
|
||||
});
|
||||
|
||||
@@ -153,7 +160,6 @@
|
||||
}
|
||||
|
||||
registrando = true;
|
||||
erro = null;
|
||||
sucesso = null;
|
||||
coletandoInfo = true;
|
||||
|
||||
@@ -189,7 +195,15 @@
|
||||
});
|
||||
|
||||
registroId = resultado.registroId;
|
||||
sucesso = `Ponto registrado com sucesso! Tipo: ${getTipoRegistroLabel(resultado.tipo)}`;
|
||||
const tipoLabelSucesso = config
|
||||
? getTipoRegistroLabel(resultado.tipo, {
|
||||
nomeEntrada: config.nomeEntrada,
|
||||
nomeSaidaAlmoco: config.nomeSaidaAlmoco,
|
||||
nomeRetornoAlmoco: config.nomeRetornoAlmoco,
|
||||
nomeSaida: config.nomeSaida,
|
||||
})
|
||||
: getTipoRegistroLabel(resultado.tipo);
|
||||
sucesso = `Ponto registrado com sucesso! Tipo: ${tipoLabelSucesso}`;
|
||||
imagemCapturada = null;
|
||||
justificativa = ''; // Limpar justificativa após registro
|
||||
mostrandoModalConfirmacao = false;
|
||||
@@ -208,7 +222,15 @@
|
||||
mensagemErro.includes('já existe um registro')
|
||||
) {
|
||||
mensagemErroModal = 'Registro de ponto duplicado';
|
||||
detalhesErroModal = `Não é possível registrar o ponto no mesmo minuto.\n\nVocê já possui um registro de ${getTipoRegistroLabel(proximoTipo)} para este minuto.\n\nPor favor, aguarde pelo menos 1 minuto antes de tentar registrar novamente.`;
|
||||
const tipoLabelErro = config
|
||||
? getTipoRegistroLabel(proximoTipo, {
|
||||
nomeEntrada: config.nomeEntrada,
|
||||
nomeSaidaAlmoco: config.nomeSaidaAlmoco,
|
||||
nomeRetornoAlmoco: config.nomeRetornoAlmoco,
|
||||
nomeSaida: config.nomeSaida,
|
||||
})
|
||||
: getTipoRegistroLabel(proximoTipo);
|
||||
detalhesErroModal = `Não é possível registrar o ponto no mesmo minuto.\n\nVocê já possui um registro de ${tipoLabelErro} para este minuto.\n\nPor favor, aguarde pelo menos 1 minuto antes de tentar registrar novamente.`;
|
||||
mostrarModalErro = true;
|
||||
} else {
|
||||
// Outros erros também mostram no modal
|
||||
@@ -216,8 +238,6 @@
|
||||
detalhesErroModal = mensagemErro;
|
||||
mostrarModalErro = true;
|
||||
}
|
||||
|
||||
erro = mensagemErro;
|
||||
} finally {
|
||||
registrando = false;
|
||||
coletandoInfo = false;
|
||||
@@ -309,7 +329,6 @@
|
||||
mostrarModalErro = false;
|
||||
mensagemErroModal = '';
|
||||
detalhesErroModal = '';
|
||||
erro = null;
|
||||
}
|
||||
|
||||
async function imprimirComprovante(registroId: Id<'registrosPonto'>) {
|
||||
@@ -322,6 +341,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Buscar configuração para usar nomes personalizados
|
||||
const configComprovante = await client.query(api.configuracaoPonto.obterConfiguracao, {});
|
||||
|
||||
const doc = new jsPDF();
|
||||
|
||||
// Logo
|
||||
@@ -385,7 +407,15 @@
|
||||
yPosition += 8;
|
||||
doc.setFontSize(10);
|
||||
|
||||
doc.text(`Tipo: ${getTipoRegistroLabel(registro.tipo)}`, 15, yPosition);
|
||||
const tipoLabelComprovante = configComprovante
|
||||
? getTipoRegistroLabel(registro.tipo, {
|
||||
nomeEntrada: configComprovante.nomeEntrada,
|
||||
nomeSaidaAlmoco: configComprovante.nomeSaidaAlmoco,
|
||||
nomeRetornoAlmoco: configComprovante.nomeRetornoAlmoco,
|
||||
nomeSaida: configComprovante.nomeSaida,
|
||||
})
|
||||
: getTipoRegistroLabel(registro.tipo);
|
||||
doc.text(`Tipo: ${tipoLabelComprovante}`, 15, yPosition);
|
||||
yPosition += 6;
|
||||
|
||||
const dataHora = formatarDataHoraCompleta(registro.data, registro.hora, registro.minuto, registro.segundo);
|
||||
@@ -578,10 +608,10 @@
|
||||
if (!config) return [];
|
||||
|
||||
const horarios = [
|
||||
{ tipo: 'entrada', horario: config.horarioEntrada, label: 'Entrada' },
|
||||
{ tipo: 'saida_almoco', horario: config.horarioSaidaAlmoco, label: 'Saída para Almoço' },
|
||||
{ tipo: 'retorno_almoco', horario: config.horarioRetornoAlmoco, label: 'Retorno do Almoço' },
|
||||
{ tipo: 'saida', horario: config.horarioSaida, label: 'Saída' }
|
||||
{ tipo: 'entrada', horario: config.horarioEntrada, label: config.nomeEntrada || 'Entrada 1' },
|
||||
{ tipo: 'saida_almoco', horario: config.horarioSaidaAlmoco, label: config.nomeSaidaAlmoco || 'Saída 1' },
|
||||
{ tipo: 'retorno_almoco', horario: config.horarioRetornoAlmoco, label: config.nomeRetornoAlmoco || 'Entrada 2' },
|
||||
{ tipo: 'saida', horario: config.horarioSaida, label: config.nomeSaida || 'Saída 2' }
|
||||
];
|
||||
|
||||
return horarios.map((h) => {
|
||||
@@ -761,7 +791,16 @@
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="flex-1">
|
||||
<div class="mb-1 flex items-center gap-2">
|
||||
<span class="font-semibold">{getTipoRegistroLabel(registro.tipo)}</span>
|
||||
<span class="font-semibold">
|
||||
{config
|
||||
? getTipoRegistroLabel(registro.tipo, {
|
||||
nomeEntrada: config.nomeEntrada,
|
||||
nomeSaidaAlmoco: config.nomeSaidaAlmoco,
|
||||
nomeRetornoAlmoco: config.nomeRetornoAlmoco,
|
||||
nomeSaida: config.nomeSaida,
|
||||
})
|
||||
: getTipoRegistroLabel(registro.tipo)}
|
||||
</span>
|
||||
{#if registro.dentroDoPrazo}
|
||||
<CheckCircle2 class="h-4 w-4 text-success" />
|
||||
{:else}
|
||||
|
||||
@@ -66,13 +66,34 @@ export function verificarDentroDoPrazo(
|
||||
|
||||
/**
|
||||
* Obtém label do tipo de registro
|
||||
* Se config fornecida, usa os nomes personalizados, senão usa os padrões
|
||||
*/
|
||||
export function getTipoRegistroLabel(tipo: 'entrada' | 'saida_almoco' | 'retorno_almoco' | 'saida'): string {
|
||||
export function getTipoRegistroLabel(
|
||||
tipo: 'entrada' | 'saida_almoco' | 'retorno_almoco' | 'saida',
|
||||
config?: {
|
||||
nomeEntrada?: string;
|
||||
nomeSaidaAlmoco?: string;
|
||||
nomeRetornoAlmoco?: string;
|
||||
nomeSaida?: string;
|
||||
}
|
||||
): string {
|
||||
// Se config fornecida, usar nomes personalizados
|
||||
if (config) {
|
||||
const labels: Record<string, string> = {
|
||||
entrada: config.nomeEntrada || 'Entrada 1',
|
||||
saida_almoco: config.nomeSaidaAlmoco || 'Saída 1',
|
||||
retorno_almoco: config.nomeRetornoAlmoco || 'Entrada 2',
|
||||
saida: config.nomeSaida || 'Saída 2',
|
||||
};
|
||||
return labels[tipo] || tipo;
|
||||
}
|
||||
|
||||
// Valores padrão
|
||||
const labels: Record<string, string> = {
|
||||
entrada: 'Entrada',
|
||||
saida_almoco: 'Saída para Almoço',
|
||||
retorno_almoco: 'Retorno do Almoço',
|
||||
saida: 'Saída',
|
||||
entrada: 'Entrada 1',
|
||||
saida_almoco: 'Saída 1',
|
||||
retorno_almoco: 'Entrada 2',
|
||||
saida: 'Saída 2',
|
||||
};
|
||||
return labels[tipo] || tipo;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user