- 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.
210 lines
5.7 KiB
TypeScript
210 lines
5.7 KiB
TypeScript
import { v } from 'convex/values';
|
|
import { action, internalMutation, internalQuery, mutation, query } from './_generated/server';
|
|
import { getCurrentUserFunction } from './auth';
|
|
import type { Id } from './_generated/dataModel';
|
|
import { api, internal } from './_generated/api';
|
|
|
|
/**
|
|
* Obtém a configuração do relógio
|
|
*/
|
|
export const obterConfiguracao = query({
|
|
args: {},
|
|
handler: async (ctx) => {
|
|
const config = await ctx.db
|
|
.query('configuracaoRelogio')
|
|
.withIndex('by_ativo', (q) => q.eq('usarServidorExterno', true))
|
|
.first();
|
|
|
|
if (!config) {
|
|
// Retornar configuração padrão
|
|
return {
|
|
servidorNTP: 'pool.ntp.org',
|
|
portaNTP: 123,
|
|
usarServidorExterno: false,
|
|
fallbackParaPC: true,
|
|
ultimaSincronizacao: null,
|
|
offsetSegundos: null,
|
|
gmtOffset: 0,
|
|
};
|
|
}
|
|
|
|
return {
|
|
...config,
|
|
gmtOffset: config.gmtOffset ?? 0,
|
|
};
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Salva configuração do relógio (apenas TI)
|
|
*/
|
|
export const salvarConfiguracao = mutation({
|
|
args: {
|
|
servidorNTP: v.optional(v.string()),
|
|
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);
|
|
if (!usuario) {
|
|
throw new Error('Usuário não autenticado');
|
|
}
|
|
|
|
// TODO: Verificar se usuário tem permissão de TI
|
|
|
|
// Validar servidor NTP se usar servidor externo
|
|
if (args.usarServidorExterno) {
|
|
if (!args.servidorNTP || args.servidorNTP.trim() === '') {
|
|
throw new Error('Servidor NTP é obrigatório quando usar servidor externo');
|
|
}
|
|
if (!args.portaNTP || args.portaNTP < 1 || args.portaNTP > 65535) {
|
|
throw new Error('Porta NTP deve estar entre 1 e 65535');
|
|
}
|
|
}
|
|
|
|
// Buscar configuração existente
|
|
const configExistente = await ctx.db
|
|
.query('configuracaoRelogio')
|
|
.withIndex('by_ativo', (q) => q.eq('usarServidorExterno', args.usarServidorExterno))
|
|
.first();
|
|
|
|
if (configExistente) {
|
|
// Atualizar configuração existente
|
|
await ctx.db.patch(configExistente._id, {
|
|
servidorNTP: args.servidorNTP,
|
|
portaNTP: args.portaNTP,
|
|
usarServidorExterno: args.usarServidorExterno,
|
|
fallbackParaPC: args.fallbackParaPC,
|
|
gmtOffset: args.gmtOffset ?? 0,
|
|
atualizadoPor: usuario._id as Id<'usuarios'>,
|
|
atualizadoEm: Date.now(),
|
|
});
|
|
return { configId: configExistente._id };
|
|
} else {
|
|
// Criar nova configuração
|
|
const configId = await ctx.db.insert('configuracaoRelogio', {
|
|
servidorNTP: args.servidorNTP,
|
|
portaNTP: args.portaNTP,
|
|
usarServidorExterno: args.usarServidorExterno,
|
|
fallbackParaPC: args.fallbackParaPC,
|
|
gmtOffset: args.gmtOffset ?? 0,
|
|
atualizadoPor: usuario._id as Id<'usuarios'>,
|
|
atualizadoEm: Date.now(),
|
|
});
|
|
return { configId };
|
|
}
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Obtém tempo do servidor (timestamp atual)
|
|
*/
|
|
export const obterTempoServidor = query({
|
|
args: {},
|
|
handler: async () => {
|
|
return {
|
|
timestamp: Date.now(),
|
|
data: new Date().toISOString(),
|
|
};
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Sincroniza tempo com servidor NTP (via action)
|
|
* Nota: NTP real requer biblioteca específica, aqui fazemos uma aproximação
|
|
*/
|
|
export const sincronizarTempo = action({
|
|
args: {},
|
|
handler: async (ctx) => {
|
|
// Buscar configuração diretamente do banco usando query pública
|
|
const config = await ctx.runQuery(api.configuracaoRelogio.obterConfiguracao, {});
|
|
|
|
if (!config.usarServidorExterno) {
|
|
return {
|
|
sucesso: true,
|
|
timestamp: Date.now(),
|
|
usandoServidorExterno: false,
|
|
offsetSegundos: 0,
|
|
};
|
|
}
|
|
|
|
// Tentar obter tempo de um servidor NTP público via HTTP
|
|
// Nota: Esta é uma aproximação. Para NTP real, seria necessário usar uma biblioteca específica
|
|
try {
|
|
// Usar API pública de tempo como fallback
|
|
const response = await fetch('https://worldtimeapi.org/api/timezone/America/Recife');
|
|
if (!response.ok) {
|
|
throw new Error('Falha ao obter tempo do servidor');
|
|
}
|
|
|
|
const data = (await response.json()) as { datetime: string };
|
|
const serverTime = new Date(data.datetime).getTime();
|
|
const localTime = Date.now();
|
|
const offsetSegundos = Math.floor((serverTime - localTime) / 1000);
|
|
|
|
// Atualizar configuração com offset
|
|
// Buscar configuração diretamente usando query interna
|
|
const configs = await ctx.runQuery(internal.configuracaoRelogio.listarConfiguracoes, {});
|
|
const configExistente = configs.find(
|
|
(c: { usarServidorExterno: boolean; _id: Id<'configuracaoRelogio'> }) =>
|
|
c.usarServidorExterno === config.usarServidorExterno
|
|
);
|
|
if (configExistente) {
|
|
await ctx.runMutation(internal.configuracaoRelogio.atualizarSincronizacao, {
|
|
configId: configExistente._id,
|
|
offsetSegundos,
|
|
});
|
|
}
|
|
|
|
return {
|
|
sucesso: true,
|
|
timestamp: serverTime,
|
|
usandoServidorExterno: true,
|
|
offsetSegundos,
|
|
};
|
|
} catch {
|
|
// Se falhar e fallbackParaPC estiver ativo, usar tempo local
|
|
if (config.fallbackParaPC) {
|
|
return {
|
|
sucesso: true,
|
|
timestamp: Date.now(),
|
|
usandoServidorExterno: false,
|
|
offsetSegundos: 0,
|
|
aviso: 'Falha ao sincronizar com servidor externo, usando relógio do PC',
|
|
};
|
|
}
|
|
|
|
throw new Error('Falha ao sincronizar tempo e fallback desabilitado');
|
|
}
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Lista configurações (internal)
|
|
*/
|
|
export const listarConfiguracoes = internalQuery({
|
|
args: {},
|
|
handler: async (ctx) => {
|
|
return await ctx.db.query('configuracaoRelogio').collect();
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Atualiza informações de sincronização (internal)
|
|
*/
|
|
export const atualizarSincronizacao = internalMutation({
|
|
args: {
|
|
configId: v.id('configuracaoRelogio'),
|
|
offsetSegundos: v.number(),
|
|
},
|
|
handler: async (ctx, args) => {
|
|
await ctx.db.patch(args.configId, {
|
|
ultimaSincronizacao: Date.now(),
|
|
offsetSegundos: args.offsetSegundos,
|
|
});
|
|
},
|
|
});
|
|
|