Files
sgse-app/packages/backend/convex/configuracaoRelogio.ts
deyvisonwanderley f0c6e4468f feat: integrate point management features into the dashboard
- Added a new "Meu Ponto" section for users to register their work hours, breaks, and attendance.
- Introduced a "Controle de Ponto" category in the Recursos Humanos section for managing employee time records.
- Enhanced the backend schema to support point registration and configuration settings.
- Updated various components to improve UI consistency and user experience across the dashboard.
2025-11-18 11:44:12 -03:00

203 lines
5.6 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,
};
}
return config;
},
});
/**
* 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(),
},
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,
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,
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,
});
},
});