refactor: enhance saldo calculation and display in registro-pontos page

- Updated the logic for calculating daily and period saldo differences to ensure accuracy by directly computing the difference between worked and expected hours.
- Improved the display of saldo differences in the UI, including formatting adjustments for better readability.
- Refactored the rendering logic to ensure consistent styling and user experience across the registro-pontos page.
This commit is contained in:
2025-11-23 14:29:49 -03:00
parent 35e7c10ed0
commit 1ad0ee91cb
2 changed files with 70 additions and 34 deletions

View File

@@ -34,7 +34,7 @@
? 'bg-red-50 border-red-200 text-red-700 dark:bg-red-900/20 dark:border-red-800 dark:text-red-400' ? 'bg-red-50 border-red-200 text-red-700 dark:bg-red-900/20 dark:border-red-800 dark:text-red-400'
: 'bg-green-50 border-green-200 text-green-700 dark:bg-green-900/20 dark:border-green-800 dark:text-green-400' : 'bg-green-50 border-green-200 text-green-700 dark:bg-green-900/20 dark:border-green-800 dark:text-green-400'
}"> }">
<span class="font-bold">+{trabalhado.horas}h {trabalhado.minutos}min</span> <span class="font-bold text-green-600 dark:text-green-400">+{trabalhado.horas}h {trabalhado.minutos}min</span>
<span class="text-base-content/50">/</span> <span class="text-base-content/50">/</span>
<span class={isNegativo ? 'text-red-600 dark:text-red-400' : 'text-green-600 dark:text-green-400'}> <span class={isNegativo ? 'text-red-600 dark:text-red-400' : 'text-green-600 dark:text-green-400'}>
{sinalDiferenca}{diferenca.horas}h {diferenca.minutos}min {sinalDiferenca}{diferenca.horas}h {diferenca.minutos}min

View File

@@ -421,11 +421,11 @@
}; };
} else { } else {
// Fallback: usar cálculo simples se não houver configuração // Fallback: usar cálculo simples se não houver configuração
const primeiroRegistro = grupoData.registros[0]; const primeiroRegistro = grupoData.registros[0];
if (primeiroRegistro && 'saldoDiario' in primeiroRegistro && primeiroRegistro.saldoDiario) { if (primeiroRegistro && 'saldoDiario' in primeiroRegistro && primeiroRegistro.saldoDiario) {
grupoData.saldoDiario = primeiroRegistro.saldoDiario; grupoData.saldoDiario = primeiroRegistro.saldoDiario;
} else { } else {
grupoData.saldoDiario = calcularSaldoDiario(grupoData.registros); grupoData.saldoDiario = calcularSaldoDiario(grupoData.registros);
} }
} }
} }
@@ -1496,9 +1496,12 @@
saldoDiarioTotalDiferencaMinutos = -saldoDiarioTotalEsperadoMinutos; // Diferença negativa (0 - esperado) saldoDiarioTotalDiferencaMinutos = -saldoDiarioTotalEsperadoMinutos; // Diferença negativa (0 - esperado)
} }
// Calcular diferença corretamente: trabalhado - esperado (não somar diferenças dos pares)
const diferencaDiariaCorrigida = saldoDiarioTotalTrabalhadoMinutos - saldoDiarioTotalEsperadoMinutos;
// Armazenar saldo diário completo (usado no resumo do banco de horas) // Armazenar saldo diário completo (usado no resumo do banco de horas)
saldosDiariosPorData[data] = { saldosDiariosPorData[data] = {
diferencaMinutos: saldoDiarioTotalDiferencaMinutos, diferencaMinutos: diferencaDiariaCorrigida,
trabalhadoMinutos: saldoDiarioTotalTrabalhadoMinutos, trabalhadoMinutos: saldoDiarioTotalTrabalhadoMinutos,
esperadoMinutos: saldoDiarioTotalEsperadoMinutos esperadoMinutos: saldoDiarioTotalEsperadoMinutos
}; };
@@ -1646,9 +1649,9 @@
// Se não há tempo trabalhado, decrementar o tempo esperado completo // Se não há tempo trabalhado, decrementar o tempo esperado completo
if (saldoEsperado.trabalhadoMinutos === 0) { if (saldoEsperado.trabalhadoMinutos === 0) {
saldoDiarioAcumuladoMinutos -= saldoEsperado.esperadoMinutos; saldoDiarioAcumuladoMinutos -= saldoEsperado.esperadoMinutos;
} else { } else {
saldoDiarioAcumuladoMinutos -= saldoEsperado.trabalhadoMinutos; saldoDiarioAcumuladoMinutos -= saldoEsperado.trabalhadoMinutos;
} }
paresProcessadosParaSaldo.add(chavePar); paresProcessadosParaSaldo.add(chavePar);
} }
@@ -1664,7 +1667,7 @@
// Marcar linha para aplicar cor no saldo // Marcar linha para aplicar cor no saldo
if (trabalhouMaisQueEsperado) { if (trabalhouMaisQueEsperado) {
linha._saldoPositivo = true; // Verde: trabalhou mais que o esperado linha._saldoPositivo = true; // Verde: trabalhou mais que o esperado
} else { } else {
linha._saldoNegativo = true; // Vermelho: ainda falta trabalhar linha._saldoNegativo = true; // Vermelho: ainda falta trabalhar
} }
@@ -1756,9 +1759,9 @@
content: `0h 0min parcial | Saldo: ${sinalSaldo}${saldoAcumuladoHoras}h ${saldoAcumuladoMinutosResto}min`, content: `0h 0min parcial | Saldo: ${sinalSaldo}${saldoAcumuladoHoras}h ${saldoAcumuladoMinutosResto}min`,
rowSpan: 2 // retorno_almoco + saida rowSpan: 2 // retorno_almoco + saida
}); });
} else { } else {
linha.push('-'); linha.push('-');
} }
} else { } else {
// Há registros reais mas este par não foi marcado completamente // Há registros reais mas este par não foi marcado completamente
// Verificar se é um par completamente não marcado // Verificar se é um par completamente não marcado
@@ -1921,7 +1924,6 @@
const cargaHorariaDiariaEsperadaMinutos = minutosPar1EsperadoAjustadoConfig + minutosPar2EsperadoAjustadoConfig; const cargaHorariaDiariaEsperadaMinutos = minutosPar1EsperadoAjustadoConfig + minutosPar2EsperadoAjustadoConfig;
// Calcular saldos do período selecionado baseado nos saldos diários calculados // Calcular saldos do período selecionado baseado nos saldos diários calculados
let saldoPeriodoDiferencaMinutos = 0;
let saldoPeriodoTrabalhadoMinutos = 0; let saldoPeriodoTrabalhadoMinutos = 0;
let diasComSaldoPositivo = 0; let diasComSaldoPositivo = 0;
let diasComSaldoNegativo = 0; let diasComSaldoNegativo = 0;
@@ -1930,12 +1932,14 @@
if (sections.registrosPonto && Object.keys(saldosDiariosPorData).length > 0) { if (sections.registrosPonto && Object.keys(saldosDiariosPorData).length > 0) {
// Somar todos os saldos diários do período // Somar todos os saldos diários do período
for (const saldo of Object.values(saldosDiariosPorData)) { for (const saldo of Object.values(saldosDiariosPorData)) {
saldoPeriodoDiferencaMinutos += saldo.diferencaMinutos;
saldoPeriodoTrabalhadoMinutos += saldo.trabalhadoMinutos; saldoPeriodoTrabalhadoMinutos += saldo.trabalhadoMinutos;
if (saldo.diferencaMinutos > 0) { // Calcular diferença diária corretamente: trabalhado - esperado
const diferencaDiaria = saldo.trabalhadoMinutos - saldo.esperadoMinutos;
if (diferencaDiaria > 0) {
diasComSaldoPositivo++; diasComSaldoPositivo++;
} else if (saldo.diferencaMinutos < 0) { } else if (diferencaDiaria < 0) {
diasComSaldoNegativo++; diasComSaldoNegativo++;
} }
@@ -1962,14 +1966,21 @@
for (const regs of Object.values(registrosPorDataPeriodo)) { for (const regs of Object.values(registrosPorDataPeriodo)) {
const saldoDiario = calcularSaldoDiario(regs); const saldoDiario = calcularSaldoDiario(regs);
if (saldoDiario) { if (saldoDiario) {
saldoPeriodoDiferencaMinutos += saldoDiario.saldoMinutos;
saldoPeriodoTrabalhadoMinutos += saldoDiario.saldoMinutos; saldoPeriodoTrabalhadoMinutos += saldoDiario.saldoMinutos;
} }
} }
} }
// Calcular saldo esperado do período: carga horária diária × número de dias // Calcular saldo esperado do período: carga horária diária × número de dias
// SEMPRE calcular diretamente, não somar saldos diários esperados (pode duplicar)
const saldoPeriodoEsperadoMinutos = cargaHorariaDiariaEsperadaMinutos * totalDiasPeriodo; const saldoPeriodoEsperadoMinutos = cargaHorariaDiariaEsperadaMinutos * totalDiasPeriodo;
// Calcular diferença do período corretamente: trabalhado - esperado (para "Saldo do Período Exibido")
const saldoPeriodoDiferencaMinutos = saldoPeriodoTrabalhadoMinutos - saldoPeriodoEsperadoMinutos;
// Calcular diferença do período (trabalhado - esperado) para exibição na linha "Diferença do Período"
// Negativo quando trabalhado < esperado (vermelho), positivo quando trabalhado > esperado (verde)
const diferencaPeriodoTrabalhadoMenosEsperado = saldoPeriodoTrabalhadoMinutos - saldoPeriodoEsperadoMinutos;
// Calcular médias diárias // Calcular médias diárias
const mediaDiariaTrabalhadaHoras = totalDiasPeriodo > 0 ? Math.floor(saldoPeriodoTrabalhadoMinutos / 60 / totalDiasPeriodo) : 0; const mediaDiariaTrabalhadaHoras = totalDiasPeriodo > 0 ? Math.floor(saldoPeriodoTrabalhadoMinutos / 60 / totalDiasPeriodo) : 0;
@@ -1994,11 +2005,19 @@
const mediaDiariaEsperadaMinutos = totalEsperadoDiarioMinutos % 60; const mediaDiariaEsperadaMinutos = totalEsperadoDiarioMinutos % 60;
// Formatar valores // Formatar valores
// Saldo do Período Exibido: diferença (trabalhado - esperado)
const horasPeriodoDiferenca = Math.floor(Math.abs(saldoPeriodoDiferencaMinutos) / 60); const horasPeriodoDiferenca = Math.floor(Math.abs(saldoPeriodoDiferencaMinutos) / 60);
const minutosPeriodoDiferenca = Math.abs(saldoPeriodoDiferencaMinutos) % 60; const minutosPeriodoDiferenca = Math.abs(saldoPeriodoDiferencaMinutos) % 60;
const sinalPeriodoDiferenca = saldoPeriodoDiferencaMinutos >= 0 ? '+' : '-'; const sinalPeriodoDiferenca = saldoPeriodoDiferencaMinutos >= 0 ? '+' : '-';
const saldoPeriodoDiferencaFormatado = `${sinalPeriodoDiferenca}${horasPeriodoDiferenca}h ${minutosPeriodoDiferenca}min`; const saldoPeriodoDiferencaFormatado = `${sinalPeriodoDiferenca}${horasPeriodoDiferenca}h ${minutosPeriodoDiferenca}min`;
// Diferença do Período: trabalhado - esperado
// Negativo quando trabalhado < esperado (vermelho), positivo quando trabalhado > esperado (verde)
const horasDiferencaPeriodo = Math.floor(Math.abs(diferencaPeriodoTrabalhadoMenosEsperado) / 60);
const minutosDiferencaPeriodo = Math.abs(diferencaPeriodoTrabalhadoMenosEsperado) % 60;
const sinalDiferencaPeriodo = diferencaPeriodoTrabalhadoMenosEsperado >= 0 ? '+' : '-';
const diferencaPeriodoFormatado = `${sinalDiferencaPeriodo}${horasDiferencaPeriodo}h ${minutosDiferencaPeriodo}min`;
const horasPeriodoTrabalhado = Math.floor(saldoPeriodoTrabalhadoMinutos / 60); const horasPeriodoTrabalhado = Math.floor(saldoPeriodoTrabalhadoMinutos / 60);
const minutosPeriodoTrabalhado = saldoPeriodoTrabalhadoMinutos % 60; const minutosPeriodoTrabalhado = saldoPeriodoTrabalhadoMinutos % 60;
const saldoPeriodoTrabalhadoFormatado = `+${horasPeriodoTrabalhado}h ${minutosPeriodoTrabalhado}min`; const saldoPeriodoTrabalhadoFormatado = `+${horasPeriodoTrabalhado}h ${minutosPeriodoTrabalhado}min`;
@@ -2049,7 +2068,7 @@
bancoHorasData.push(['', '']); // Linha separadora bancoHorasData.push(['', '']); // Linha separadora
bancoHorasData.push(['Saldo Trabalhado do Período', saldoPeriodoTrabalhadoFormatado]); bancoHorasData.push(['Saldo Trabalhado do Período', saldoPeriodoTrabalhadoFormatado]);
bancoHorasData.push(['Saldo Esperado do Período', saldoPeriodoEsperadoFormatado]); bancoHorasData.push(['Saldo Esperado do Período', saldoPeriodoEsperadoFormatado]);
bancoHorasData.push(['Diferença do Período', saldoPeriodoDiferencaFormatado]); bancoHorasData.push(['Diferença do Período', diferencaPeriodoFormatado]);
// Adicionar estatísticas // Adicionar estatísticas
bancoHorasData.push(['', '']); // Linha separadora bancoHorasData.push(['', '']); // Linha separadora
@@ -2105,11 +2124,14 @@
} }
} else if (valor.includes('+') || campo.includes('Trabalhado') || campo.includes('Esperado')) { } else if (valor.includes('+') || campo.includes('Trabalhado') || campo.includes('Esperado')) {
// Verde para valores positivos ou campos de trabalhado/esperado // Verde para valores positivos ou campos de trabalhado/esperado
if (campo.includes('Diferença') && saldoPeriodoDiferencaMinutos < 0) { if (campo.includes('Diferença do Período')) {
data.cell.styles.textColor = [200, 0, 0]; // Vermelho se diferença negativa // Diferença do Período: trabalhado - esperado
data.cell.styles.fontStyle = 'bold'; // Negativo quando trabalhado < esperado (vermelho), positivo quando trabalhado > esperado (verde)
} else if (campo.includes('Diferença') && saldoPeriodoDiferencaMinutos > 0) { if (diferencaPeriodoTrabalhadoMenosEsperado < 0) {
data.cell.styles.textColor = [0, 128, 0]; // Verde se diferença positiva data.cell.styles.textColor = [200, 0, 0]; // Vermelho quando trabalhado < esperado
} else if (diferencaPeriodoTrabalhadoMenosEsperado > 0) {
data.cell.styles.textColor = [0, 128, 0]; // Verde quando trabalhado > esperado
}
data.cell.styles.fontStyle = 'bold'; data.cell.styles.fontStyle = 'bold';
} else if (campo.includes('Trabalhado') || campo.includes('Esperado')) { } else if (campo.includes('Trabalhado') || campo.includes('Esperado')) {
data.cell.styles.textColor = [0, 128, 0]; // Verde para trabalhado/esperado data.cell.styles.textColor = [0, 128, 0]; // Verde para trabalhado/esperado
@@ -2226,6 +2248,20 @@
theme: 'grid', theme: 'grid',
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' }, headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
styles: { fontSize: 9 }, styles: { fontSize: 9 },
columnStyles: {
0: { cellWidth: 30 }, // Data
1: { cellWidth: 40 }, // Tipo
2: { cellWidth: 50, cellPadding: { top: 2, bottom: 2, left: 2, right: 2 } }, // Detalhes - maior largura
3: { cellWidth: 40 }, // Motivo
4: { cellWidth: 'auto' }, // Observações
},
didParseCell: function(data) {
// Permitir quebra de linha na coluna Detalhes (índice 2)
if (data.column.index === 2) {
data.cell.styles.overflow = 'linebreak';
data.cell.styles.cellWidth = 50;
}
},
}); });
const lastPage = doc.getNumberOfPages(); const lastPage = doc.getNumberOfPages();
@@ -2985,8 +3021,8 @@
? `${distanciaExcedenteKm} km além do permitido` ? `${distanciaExcedenteKm} km além do permitido`
: `${distanciaExcedenteMetros} metros além do permitido`; : `${distanciaExcedenteMetros} metros além do permitido`;
geofencingData.push(['Distância Excedente', excedenteTexto]); geofencingData.push(['Distância Excedente', excedenteTexto]);
}
} }
}
geofencingData.push(['Status', statusTexto]); geofencingData.push(['Status', statusTexto]);
autoTable(doc, { autoTable(doc, {
@@ -3759,14 +3795,14 @@
<td class="whitespace-nowrap font-semibold text-sm">{dataFormatada}</td> <td class="whitespace-nowrap font-semibold text-sm">{dataFormatada}</td>
<td class="whitespace-nowrap"> <td class="whitespace-nowrap">
<span class="badge badge-outline badge-sm font-medium text-xs"> <span class="badge badge-outline badge-sm font-medium text-xs">
{config {config
? getTipoRegistroLabel(registro.tipo, { ? getTipoRegistroLabel(registro.tipo, {
nomeEntrada: config.nomeEntrada, nomeEntrada: config.nomeEntrada,
nomeSaidaAlmoco: config.nomeSaidaAlmoco, nomeSaidaAlmoco: config.nomeSaidaAlmoco,
nomeRetornoAlmoco: config.nomeRetornoAlmoco, nomeRetornoAlmoco: config.nomeRetornoAlmoco,
nomeSaida: config.nomeSaida, nomeSaida: config.nomeSaida,
}) })
: getTipoRegistroLabel(registro.tipo)} : getTipoRegistroLabel(registro.tipo)}
</span> </span>
</td> </td>
<td class="whitespace-nowrap font-mono text-sm font-medium">{formatarHoraPonto(registro.hora, registro.minuto)}</td> <td class="whitespace-nowrap font-mono text-sm font-medium">{formatarHoraPonto(registro.hora, registro.minuto)}</td>
@@ -3784,7 +3820,7 @@
{#if grupoData.saldoDiarioComparativo} {#if grupoData.saldoDiarioComparativo}
<SaldoDiarioComparativoBadge saldo={grupoData.saldoDiarioComparativo} size="md" /> <SaldoDiarioComparativoBadge saldo={grupoData.saldoDiarioComparativo} size="md" />
{:else if grupoData.saldoDiario} {:else if grupoData.saldoDiario}
<SaldoDiarioBadge saldo={grupoData.saldoDiario} size="md" /> <SaldoDiarioBadge saldo={grupoData.saldoDiario} size="md" />
{:else} {:else}
<span class="badge badge-ghost badge-lg">-</span> <span class="badge badge-ghost badge-lg">-</span>
{/if} {/if}