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.
This commit is contained in:
202
packages/backend/convex/configuracaoRelogio.ts
Normal file
202
packages/backend/convex/configuracaoRelogio.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user