feat: implement partial balance calculation and enhance UI for point registration

- Added a new function `calcularSaldosParciais` to compute partial balances between entry and exit records, returning a map of balances indexed by record.
- Improved the UI to display partial balances in the registro-pontos table, enhancing user visibility of time management.
- Updated the header section for better layout and information presentation regarding employee details.
This commit is contained in:
2025-11-22 20:55:03 -03:00
parent f818756efc
commit 80fc8bc82c

View File

@@ -404,6 +404,70 @@
}); });
// Função para calcular saldo diário como diferença entre saída e entrada // Função para calcular saldo diário como diferença entre saída e entrada
/**
* Calcula saldos parciais entre cada par entrada/saída
* Retorna um mapa com o índice do registro e seu saldo parcial
*/
function calcularSaldosParciais(registros: Array<{ tipo: string; hora: number; minuto: number; _id?: any }>): Map<number, { saldoMinutos: number; horas: number; minutos: number; positivo: boolean; parNumero: number }> {
const saldos = new Map<number, { saldoMinutos: number; horas: number; minutos: number; positivo: boolean; parNumero: number }>();
if (registros.length === 0) return saldos;
// Criar array com índices originais
const registrosComIndice = registros.map((r, idx) => ({ ...r, originalIndex: idx }));
// Ordenar registros por hora e minuto para processar em ordem cronológica
const registrosOrdenados = [...registrosComIndice].sort((a, b) => {
if (a.hora !== b.hora) {
return a.hora - b.hora;
}
return a.minuto - b.minuto;
});
// Identificar pares entrada/saída
// Par 1: entrada -> saida_almoco
// Par 2: retorno_almoco -> saida
let entradaAtual: typeof registrosComIndice[0] | null = null;
let parNumero = 1;
for (let i = 0; i < registrosOrdenados.length; i++) {
const registro = registrosOrdenados[i];
// Considerar entrada ou retorno_almoco como início de um período
if (registro.tipo === 'entrada' || registro.tipo === 'retorno_almoco') {
entradaAtual = registro;
} else if (entradaAtual) {
// Qualquer saída (saida_almoco ou saida) fecha o período atual
if (registro.tipo === 'saida_almoco' || registro.tipo === 'saida') {
// Calcular diferença entre saída e entrada
const minutosEntrada = entradaAtual.hora * 60 + entradaAtual.minuto;
const minutosSaida = registro.hora * 60 + registro.minuto;
let saldoMinutos = minutosSaida - minutosEntrada;
if (saldoMinutos < 0) {
saldoMinutos += 24 * 60; // Adicionar um dia em minutos
}
const horas = Math.floor(saldoMinutos / 60);
const minutos = saldoMinutos % 60;
// Salvar saldo no índice original do registro de saída
saldos.set(registro.originalIndex, {
saldoMinutos,
horas,
minutos,
positivo: true,
parNumero
});
entradaAtual = null; // Resetar para próximo par
parNumero++;
}
}
}
return saldos;
}
function calcularSaldoDiario(registros: Array<{ tipo: string; hora: number; minuto: number }>): { saldoMinutos: number; horas: number; minutos: number; positivo: boolean } | null { function calcularSaldoDiario(registros: Array<{ tipo: string; hora: number; minuto: number }>): { saldoMinutos: number; horas: number; minutos: number; positivo: boolean } | null {
if (registros.length === 0) return null; if (registros.length === 0) return null;
@@ -2173,21 +2237,25 @@
{#each registrosAgrupados as grupo} {#each registrosAgrupados as grupo}
<div class="card bg-gradient-to-br from-base-100 to-base-200/50 border border-base-300 shadow-lg hover:shadow-xl transition-all duration-300"> <div class="card bg-gradient-to-br from-base-100 to-base-200/50 border border-base-300 shadow-lg hover:shadow-xl transition-all duration-300">
<div class="card-body p-6"> <div class="card-body p-6">
<div class="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-4 mb-6"> <!-- Cabeçalho melhorado -->
<div class="mb-6 pb-4 border-b border-base-300">
<div class="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-4">
<div class="flex-1"> <div class="flex-1">
<div class="flex items-center gap-3 mb-2"> <div class="flex items-center gap-3 mb-3">
<div class="p-2 bg-primary/10 rounded-lg"> <div class="p-2 bg-primary/10 rounded-lg">
<Users class="h-5 w-5 text-primary" strokeWidth={2.5} /> <Users class="h-5 w-5 text-primary" strokeWidth={2.5} />
</div> </div>
<div>
<h3 class="font-bold text-xl text-base-content"> <h3 class="font-bold text-xl text-base-content">
{grupo.funcionario?.nome || 'Funcionário não encontrado'} {grupo.funcionario?.nome || 'Funcionário não encontrado'}
</h3> </h3>
</div>
{#if grupo.funcionario?.matricula} {#if grupo.funcionario?.matricula}
<p class="text-sm text-base-content/70 ml-11"> <p class="text-sm text-base-content/70 mt-1">
Matrícula: <span class="font-semibold">{grupo.funcionario.matricula}</span> Matrícula: <span class="font-semibold">{grupo.funcionario.matricula}</span>
</p> </p>
{/if} {/if}
</div>
</div>
{#if grupo.funcionario?.descricaoCargo} {#if grupo.funcionario?.descricaoCargo}
<p class="text-sm text-base-content/60 ml-11"> <p class="text-sm text-base-content/60 ml-11">
{grupo.funcionario.descricaoCargo} {grupo.funcionario.descricaoCargo}
@@ -2244,6 +2312,7 @@
<th class="whitespace-nowrap font-bold text-base-content border-b border-base-400">Data</th> <th class="whitespace-nowrap font-bold text-base-content border-b border-base-400">Data</th>
<th class="whitespace-nowrap font-bold text-base-content border-b border-base-400">Tipo</th> <th class="whitespace-nowrap font-bold text-base-content border-b border-base-400">Tipo</th>
<th class="whitespace-nowrap font-bold text-base-content border-b border-base-400">Horário</th> <th class="whitespace-nowrap font-bold text-base-content border-b border-base-400">Horário</th>
<th class="whitespace-nowrap font-bold text-base-content border-b border-base-400">Saldo Parcial</th>
<th class="whitespace-nowrap font-bold text-base-content border-b border-base-400">Saldo Diário</th> <th class="whitespace-nowrap font-bold text-base-content border-b border-base-400">Saldo Diário</th>
<th class="whitespace-nowrap font-bold text-base-content border-b border-base-400">Localização</th> <th class="whitespace-nowrap font-bold text-base-content border-b border-base-400">Localização</th>
<th class="whitespace-nowrap font-bold text-base-content border-b border-base-400">Status</th> <th class="whitespace-nowrap font-bold text-base-content border-b border-base-400">Status</th>
@@ -2254,7 +2323,9 @@
{#each Object.values(grupo.registrosPorData) as grupoData} {#each Object.values(grupo.registrosPorData) as grupoData}
{@const totalRegistros = grupoData.registros.length} {@const totalRegistros = grupoData.registros.length}
{@const dataFormatada = formatarDataDDMMAAAA(grupoData.data)} {@const dataFormatada = formatarDataDDMMAAAA(grupoData.data)}
{@const saldosParciais = calcularSaldosParciais(grupoData.registros)}
{#each grupoData.registros as registro, index} {#each grupoData.registros as registro, index}
{@const saldoParcial = saldosParciais.get(index)}
<tr> <tr>
<td class="whitespace-nowrap">{dataFormatada}</td> <td class="whitespace-nowrap">{dataFormatada}</td>
<td class="whitespace-nowrap"> <td class="whitespace-nowrap">
@@ -2268,6 +2339,15 @@
: getTipoRegistroLabel(registro.tipo)} : getTipoRegistroLabel(registro.tipo)}
</td> </td>
<td class="whitespace-nowrap">{formatarHoraPonto(registro.hora, registro.minuto)}</td> <td class="whitespace-nowrap">{formatarHoraPonto(registro.hora, registro.minuto)}</td>
<td class="whitespace-nowrap">
{#if saldoParcial}
<span class="badge badge-info badge-sm font-semibold">
Par {saldoParcial.parNumero}: +{saldoParcial.horas}h {saldoParcial.minutos}min
</span>
{:else}
<span class="text-base-content/40">-</span>
{/if}
</td>
{#if index === 0} {#if index === 0}
<td class="whitespace-nowrap" rowspan={totalRegistros}> <td class="whitespace-nowrap" rowspan={totalRegistros}>
<SaldoDiarioBadge saldo={grupoData.saldoDiario} size="md" /> <SaldoDiarioBadge saldo={grupoData.saldoDiario} size="md" />