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:
2025-11-19 06:22:07 -03:00
parent f465bd973e
commit 7cdc726781
10 changed files with 311 additions and 93 deletions

View File

@@ -30,11 +30,22 @@ export const obterConfiguracao = query({
horarioRetornoAlmoco: '13:00',
horarioSaida: '17:00',
toleranciaMinutos: 15,
nomeEntrada: 'Entrada 1',
nomeSaidaAlmoco: 'Saída 1',
nomeRetornoAlmoco: 'Entrada 2',
nomeSaida: 'Saída 2',
ativo: false,
};
}
return config;
// Garantir que os nomes padrão estejam definidos
return {
...config,
nomeEntrada: config.nomeEntrada || 'Entrada 1',
nomeSaidaAlmoco: config.nomeSaidaAlmoco || 'Saída 1',
nomeRetornoAlmoco: config.nomeRetornoAlmoco || 'Entrada 2',
nomeSaida: config.nomeSaida || 'Saída 2',
};
},
});
@@ -48,6 +59,10 @@ export const salvarConfiguracao = mutation({
horarioRetornoAlmoco: v.string(),
horarioSaida: v.string(),
toleranciaMinutos: v.number(),
nomeEntrada: v.optional(v.string()),
nomeSaidaAlmoco: v.optional(v.string()),
nomeRetornoAlmoco: v.optional(v.string()),
nomeSaida: v.optional(v.string()),
},
handler: async (ctx, args) => {
const usuario = await getCurrentUserFunction(ctx);
@@ -115,6 +130,10 @@ export const salvarConfiguracao = mutation({
horarioRetornoAlmoco: args.horarioRetornoAlmoco,
horarioSaida: args.horarioSaida,
toleranciaMinutos: args.toleranciaMinutos,
nomeEntrada: args.nomeEntrada || 'Entrada 1',
nomeSaidaAlmoco: args.nomeSaidaAlmoco || 'Saída 1',
nomeRetornoAlmoco: args.nomeRetornoAlmoco || 'Entrada 2',
nomeSaida: args.nomeSaida || 'Saída 2',
ativo: true,
atualizadoPor: usuario._id as Id<'usuarios'>,
atualizadoEm: Date.now(),

View File

@@ -24,10 +24,14 @@ export const obterConfiguracao = query({
fallbackParaPC: true,
ultimaSincronizacao: null,
offsetSegundos: null,
gmtOffset: 0,
};
}
return config;
return {
...config,
gmtOffset: config.gmtOffset ?? 0,
};
},
});
@@ -40,6 +44,7 @@ export const salvarConfiguracao = mutation({
portaNTP: v.optional(v.number()),
usarServidorExterno: v.boolean(),
fallbackParaPC: v.boolean(),
gmtOffset: v.optional(v.number()),
},
handler: async (ctx, args) => {
const usuario = await getCurrentUserFunction(ctx);
@@ -72,6 +77,7 @@ export const salvarConfiguracao = mutation({
portaNTP: args.portaNTP,
usarServidorExterno: args.usarServidorExterno,
fallbackParaPC: args.fallbackParaPC,
gmtOffset: args.gmtOffset ?? 0,
atualizadoPor: usuario._id as Id<'usuarios'>,
atualizadoEm: Date.now(),
});
@@ -83,6 +89,7 @@ export const salvarConfiguracao = mutation({
portaNTP: args.portaNTP,
usarServidorExterno: args.usarServidorExterno,
fallbackParaPC: args.fallbackParaPC,
gmtOffset: args.gmtOffset ?? 0,
atualizadoPor: usuario._id as Id<'usuarios'>,
atualizadoEm: Date.now(),
});

View File

@@ -1,5 +1,5 @@
import { v } from 'convex/values';
import { internalMutation, mutation, query } from './_generated/server';
import { mutation, query } from './_generated/server';
import type { MutationCtx, QueryCtx } from './_generated/server';
import { getCurrentUserFunction } from './auth';
import type { Id } from './_generated/dataModel';
@@ -18,38 +18,6 @@ export const generateUploadUrl = mutation({
},
});
interface InformacoesDispositivo {
ipAddress?: string;
ipPublico?: string;
ipLocal?: string;
userAgent?: string;
browser?: string;
browserVersion?: string;
engine?: string;
sistemaOperacional?: string;
osVersion?: string;
arquitetura?: string;
plataforma?: string;
latitude?: number;
longitude?: number;
precisao?: number;
endereco?: string;
cidade?: string;
estado?: string;
pais?: string;
timezone?: string;
deviceType?: string;
deviceModel?: string;
screenResolution?: string;
coresTela?: number;
idioma?: string;
isMobile?: boolean;
isTablet?: boolean;
isDesktop?: boolean;
connectionType?: string;
memoryInfo?: string;
}
/**
* Calcula se o registro está dentro do prazo baseado na configuração
* Se toleranciaMinutos for 0, desconsidera atrasos (sempre retorna true)
@@ -175,12 +143,20 @@ export const registrarPonto = mutation({
throw new Error('Configuração de ponto não encontrada');
}
// Converter timestamp para data/hora
const dataObj = new Date(args.timestamp);
// Obter configuração de ponto para GMT offset (buscar configuração ativa)
const configPonto = await ctx.db
.query('configuracaoPonto')
.withIndex('by_ativo', (q) => q.eq('ativo', true))
.first();
// Converter timestamp para data/hora com ajuste de GMT
const gmtOffset = configPonto?.gmtOffset ?? 0;
const timestampAjustado = args.timestamp + (gmtOffset * 60 * 60 * 1000);
const dataObj = new Date(timestampAjustado);
const data = dataObj.toISOString().split('T')[0]!; // YYYY-MM-DD
const hora = dataObj.getHours();
const minuto = dataObj.getMinutes();
const segundo = dataObj.getSeconds();
const hora = dataObj.getUTCHours();
const minuto = dataObj.getUTCMinutes();
const segundo = dataObj.getUTCSeconds();
// Verificar se já existe registro no mesmo minuto
const funcionarioId = usuario.funcionarioId; // Já verificado acima, não é undefined
@@ -317,7 +293,6 @@ export const listarRegistrosPeriodo = query({
// Por enquanto, permitir se tiver funcionarioId ou for admin
// TODO: Implementar verificação de permissão adequada
const dataInicio = new Date(args.dataInicio);
const dataFim = new Date(args.dataFim);
dataFim.setHours(23, 59, 59, 999);

View File

@@ -1403,6 +1403,11 @@ export default defineSchema({
horarioRetornoAlmoco: v.string(), // HH:mm
horarioSaida: v.string(), // HH:mm
toleranciaMinutos: v.number(),
// Nomes personalizados dos tipos de registro
nomeEntrada: v.optional(v.string()), // Padrão: "Entrada 1"
nomeSaidaAlmoco: v.optional(v.string()), // Padrão: "Saída 1"
nomeRetornoAlmoco: v.optional(v.string()), // Padrão: "Entrada 2"
nomeSaida: v.optional(v.string()), // Padrão: "Saída 2"
ativo: v.boolean(),
atualizadoPor: v.id("usuarios"),
atualizadoEm: v.number(),
@@ -1416,6 +1421,8 @@ export default defineSchema({
fallbackParaPC: v.boolean(),
ultimaSincronizacao: v.optional(v.number()),
offsetSegundos: v.optional(v.number()),
// Ajuste de fuso horário (GMT offset em horas)
gmtOffset: v.optional(v.number()), // Padrão: 0 (UTC)
atualizadoPor: v.id("usuarios"),
atualizadoEm: v.number(),
})