feat: add date formatting utility and enhance filtering in registro-pontos
- Introduced a new utility function `formatarDataDDMMAAAA` to format dates in DD/MM/AAAA format, supporting various input types. - Updated the `registro-pontos` page to utilize the new date formatting function for displaying dates consistently. - Implemented advanced filtering options for status and location, allowing users to filter records based on their criteria. - Enhanced CSV export functionality to include formatted dates and additional filtering capabilities, improving data management for users.
This commit is contained in:
26
apps/web/src/lib/components/ponto/LocalizacaoIcon.svelte
Normal file
26
apps/web/src/lib/components/ponto/LocalizacaoIcon.svelte
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { MapPin, AlertCircle, HelpCircle } from 'lucide-svelte';
|
||||
|
||||
interface Props {
|
||||
dentroRaioPermitido: boolean | null | undefined;
|
||||
showTooltip?: boolean;
|
||||
}
|
||||
|
||||
let { dentroRaioPermitido, showTooltip = true }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if dentroRaioPermitido === true}
|
||||
<div class="tooltip tooltip-top" data-tip={showTooltip ? 'Dentro do Raio' : ''}>
|
||||
<MapPin class="h-5 w-5 text-success" strokeWidth={2.5} />
|
||||
</div>
|
||||
{:else if dentroRaioPermitido === false}
|
||||
<div class="tooltip tooltip-top" data-tip={showTooltip ? 'Fora do Raio' : ''}>
|
||||
<AlertCircle class="h-5 w-5 text-error" strokeWidth={2.5} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="tooltip tooltip-top" data-tip={showTooltip ? 'Não Validado' : ''}>
|
||||
<HelpCircle class="h-5 w-5 text-base-content/40" strokeWidth={2.5} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
36
apps/web/src/lib/components/ponto/SaldoDiarioBadge.svelte
Normal file
36
apps/web/src/lib/components/ponto/SaldoDiarioBadge.svelte
Normal file
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
saldo?: {
|
||||
saldoMinutos: number;
|
||||
horas: number;
|
||||
minutos: number;
|
||||
positivo: boolean;
|
||||
} | null;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
}
|
||||
|
||||
let { saldo, size = 'md' }: Props = $props();
|
||||
|
||||
function formatarSaldo(saldo: NonNullable<Props['saldo']>): string {
|
||||
const sinal = saldo.positivo ? '+' : '-';
|
||||
return `${sinal}${saldo.horas}h ${saldo.minutos}min`;
|
||||
}
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'badge-sm',
|
||||
md: 'badge-lg',
|
||||
lg: 'badge-xl'
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if saldo}
|
||||
<span
|
||||
class="badge font-semibold shadow-sm {sizeClasses[size]} {saldo.positivo
|
||||
? 'badge-success'
|
||||
: 'badge-error'}"
|
||||
>
|
||||
{formatarSaldo(saldo)}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="badge badge-ghost {sizeClasses[size]}">-</span>
|
||||
{/if}
|
||||
@@ -122,3 +122,37 @@ export function getProximoTipoRegistro(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formata data no formato DD/MM/AAAA
|
||||
* Suporta strings ISO (YYYY-MM-DD), objetos Date, e timestamps
|
||||
*/
|
||||
export function formatarDataDDMMAAAA(data: string | Date | number): string {
|
||||
if (!data) return '';
|
||||
|
||||
let dataObj: Date;
|
||||
|
||||
if (typeof data === 'string') {
|
||||
// Se for string no formato ISO (YYYY-MM-DD), adicionar hora para evitar problemas de timezone
|
||||
if (data.match(/^\d{4}-\d{2}-\d{2}$/)) {
|
||||
dataObj = new Date(data + 'T12:00:00');
|
||||
} else {
|
||||
dataObj = new Date(data);
|
||||
}
|
||||
} else if (typeof data === 'number') {
|
||||
dataObj = new Date(data);
|
||||
} else {
|
||||
dataObj = data;
|
||||
}
|
||||
|
||||
// Verificar se a data é válida
|
||||
if (isNaN(dataObj.getTime())) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const dia = dataObj.getDate().toString().padStart(2, '0');
|
||||
const mes = (dataObj.getMonth() + 1).toString().padStart(2, '0');
|
||||
const ano = dataObj.getFullYear();
|
||||
|
||||
return `${dia}/${mes}/${ano}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||
import { Clock, Filter, Download, Printer, BarChart3, Users, CheckCircle2, XCircle, TrendingUp, TrendingDown, FileText } from 'lucide-svelte';
|
||||
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||
import { formatarHoraPonto, getTipoRegistroLabel } from '$lib/utils/ponto';
|
||||
import { formatarHoraPonto, getTipoRegistroLabel, formatarDataDDMMAAAA } from '$lib/utils/ponto';
|
||||
import LocalizacaoIcon from '$lib/components/ponto/LocalizacaoIcon.svelte';
|
||||
import SaldoDiarioBadge from '$lib/components/ponto/SaldoDiarioBadge.svelte';
|
||||
import jsPDF from 'jspdf';
|
||||
import autoTable from 'jspdf-autotable';
|
||||
import logoGovPE from '$lib/assets/logo_governo_PE.png';
|
||||
import PrintPontoModal from '$lib/components/ponto/PrintPontoModal.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { Chart, registerables } from 'chart.js';
|
||||
import Papa from 'papaparse';
|
||||
|
||||
Chart.register(...registerables);
|
||||
|
||||
@@ -20,6 +23,8 @@
|
||||
let dataInicio = $state(new Date().toISOString().split('T')[0]!);
|
||||
let dataFim = $state(new Date().toISOString().split('T')[0]!);
|
||||
let funcionarioIdFiltro = $state<Id<'funcionarios'> | ''>('');
|
||||
let statusFiltro = $state<'todos' | 'dentro' | 'fora'>('todos');
|
||||
let localizacaoFiltro = $state<'todos' | 'dentro' | 'fora'>('todos');
|
||||
let carregando = $state(false);
|
||||
let mostrarModalImpressao = $state(false);
|
||||
let funcionarioParaImprimir = $state<Id<'funcionarios'> | ''>('');
|
||||
@@ -209,6 +214,33 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Filtrar registros com base nos filtros avançados
|
||||
const registrosFiltrados = $derived.by(() => {
|
||||
if (!registros || registros.length === 0) return [];
|
||||
|
||||
let resultado = [...registros];
|
||||
|
||||
// Filtro de status (Dentro/Fora do Prazo)
|
||||
if (statusFiltro !== 'todos') {
|
||||
resultado = resultado.filter(r => {
|
||||
if (statusFiltro === 'dentro') return r.dentroDoPrazo === true;
|
||||
if (statusFiltro === 'fora') return r.dentroDoPrazo === false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Filtro de localização (Dentro/Fora do Raio)
|
||||
if (localizacaoFiltro !== 'todos') {
|
||||
resultado = resultado.filter(r => {
|
||||
if (localizacaoFiltro === 'dentro') return r.dentroRaioPermitido === true;
|
||||
if (localizacaoFiltro === 'fora') return r.dentroRaioPermitido === false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return resultado;
|
||||
});
|
||||
|
||||
// Agrupar registros por funcionário e data
|
||||
const registrosAgrupados = $derived.by(() => {
|
||||
const agrupados: Record<
|
||||
@@ -230,12 +262,15 @@
|
||||
// Usar Set para evitar registros duplicados
|
||||
const registrosProcessados = new Set<string>();
|
||||
|
||||
// Usar registros filtrados ao invés de registros originais
|
||||
const registrosParaAgrupar = registrosFiltrados;
|
||||
|
||||
// Verificar se registros é um array válido
|
||||
if (!Array.isArray(registros) || registros.length === 0) {
|
||||
if (!Array.isArray(registrosParaAgrupar) || registrosParaAgrupar.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (const registro of registros) {
|
||||
for (const registro of registrosParaAgrupar) {
|
||||
// Verificar se o registro tem os campos necessários
|
||||
if (!registro || !registro._id || !registro.funcionarioId || !registro.data) {
|
||||
console.warn('⚠️ [DEBUG] Registro inválido ignorado:', registro);
|
||||
@@ -345,16 +380,7 @@
|
||||
return `${sinal}${saldo.horas}h ${saldo.minutos}min`;
|
||||
}
|
||||
|
||||
// Função para formatar data em português
|
||||
function formatarData(data: string): string {
|
||||
if (!data) return '';
|
||||
const dataObj = new Date(data + 'T00:00:00');
|
||||
return dataObj.toLocaleDateString('pt-BR', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric'
|
||||
});
|
||||
}
|
||||
// Usar função centralizada formatarDataDDMMAAAA da lib/utils/ponto.ts
|
||||
|
||||
// Obter nome do funcionário selecionado
|
||||
const funcionarioSelecionadoNome = $derived.by(() => {
|
||||
@@ -407,6 +433,93 @@
|
||||
mostrarModalImpressao = true;
|
||||
}
|
||||
|
||||
// Função para limpar todos os filtros
|
||||
function limparFiltros() {
|
||||
dataInicio = new Date().toISOString().split('T')[0]!;
|
||||
dataFim = new Date().toISOString().split('T')[0]!;
|
||||
funcionarioIdFiltro = '';
|
||||
statusFiltro = 'todos';
|
||||
localizacaoFiltro = 'todos';
|
||||
toast.success('Filtros limpos com sucesso!');
|
||||
}
|
||||
|
||||
// Função para exportar registros para CSV
|
||||
async function exportarCSV() {
|
||||
try {
|
||||
const registrosParaExportar = registrosFiltrados;
|
||||
|
||||
if (!registrosParaExportar || registrosParaExportar.length === 0) {
|
||||
toast.error('Nenhum registro para exportar');
|
||||
return;
|
||||
}
|
||||
|
||||
// Preparar dados para CSV
|
||||
const csvData = registrosParaExportar.map((registro) => {
|
||||
const funcionarioNome = registro.funcionario?.nome || 'N/A';
|
||||
const funcionarioMatricula = registro.funcionario?.matricula || 'N/A';
|
||||
const tipo = config
|
||||
? getTipoRegistroLabel(registro.tipo, {
|
||||
nomeEntrada: config.nomeEntrada,
|
||||
nomeSaidaAlmoco: config.nomeSaidaAlmoco,
|
||||
nomeRetornoAlmoco: config.nomeRetornoAlmoco,
|
||||
nomeSaida: config.nomeSaida,
|
||||
})
|
||||
: getTipoRegistroLabel(registro.tipo);
|
||||
|
||||
const horario = formatarHoraPonto(registro.hora, registro.minuto);
|
||||
const status = registro.dentroDoPrazo ? 'Dentro do Prazo' : 'Fora do Prazo';
|
||||
const localizacao = registro.dentroRaioPermitido === true
|
||||
? 'Dentro do Raio'
|
||||
: registro.dentroRaioPermitido === false
|
||||
? 'Fora do Raio'
|
||||
: 'Não Validado';
|
||||
|
||||
return {
|
||||
'Data': formatarDataDDMMAAAA(registro.data),
|
||||
'Funcionário': funcionarioNome,
|
||||
'Matrícula': funcionarioMatricula,
|
||||
'Tipo': tipo,
|
||||
'Horário': horario,
|
||||
'Status': status,
|
||||
'Localização': localizacao,
|
||||
'IP': registro.ipAddress || 'N/A',
|
||||
'Dispositivo': registro.deviceType || 'N/A',
|
||||
};
|
||||
});
|
||||
|
||||
// Gerar CSV usando Papa Parse
|
||||
const csv = Papa.unparse(csvData, {
|
||||
header: true,
|
||||
delimiter: ';',
|
||||
encoding: 'UTF-8',
|
||||
});
|
||||
|
||||
// Adicionar BOM para Excel reconhecer UTF-8 corretamente
|
||||
const BOM = '\uFEFF';
|
||||
const csvComBOM = BOM + csv;
|
||||
|
||||
// Criar blob e download
|
||||
const blob = new Blob([csvComBOM], { type: 'text/csv;charset=utf-8;' });
|
||||
const link = document.createElement('a');
|
||||
const url = URL.createObjectURL(blob);
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute(
|
||||
'download',
|
||||
`registros-ponto-${formatarDataDDMMAAAA(dataInicio)}-${formatarDataDDMMAAAA(dataFim)}.csv`
|
||||
);
|
||||
link.style.visibility = 'hidden';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
toast.success('CSV exportado com sucesso!');
|
||||
} catch (error) {
|
||||
console.error('Erro ao exportar CSV:', error);
|
||||
toast.error('Erro ao exportar CSV. Tente novamente.');
|
||||
}
|
||||
}
|
||||
|
||||
async function gerarPDFComSelecao(sections: {
|
||||
dadosFuncionario: boolean;
|
||||
registrosPonto: boolean;
|
||||
@@ -497,10 +610,8 @@
|
||||
}
|
||||
|
||||
yPosition += 5;
|
||||
// Formatar período para exibição
|
||||
const dataInicioParts = dataInicio.split('-');
|
||||
const dataFimParts = dataFim.split('-');
|
||||
const periodoFormatado = `${dataInicioParts[2]}/${dataInicioParts[1]}/${dataInicioParts[0]} a ${dataFimParts[2]}/${dataFimParts[1]}/${dataFimParts[0]}`;
|
||||
// Formatar período para exibição usando função centralizada
|
||||
const periodoFormatado = `${formatarDataDDMMAAAA(dataInicio)} a ${formatarDataDDMMAAAA(dataFim)}`;
|
||||
doc.text(`Período: ${periodoFormatado}`, 15, yPosition);
|
||||
yPosition += 10;
|
||||
}
|
||||
@@ -571,6 +682,7 @@
|
||||
hora: number;
|
||||
minuto: number;
|
||||
dentroDoPrazo: boolean;
|
||||
dentroRaioPermitido: boolean | null | undefined;
|
||||
}>
|
||||
> = {};
|
||||
|
||||
@@ -585,14 +697,14 @@
|
||||
hora: r.hora,
|
||||
minuto: r.minuto,
|
||||
dentroDoPrazo: r.dentroDoPrazo,
|
||||
dentroRaioPermitido: r.dentroRaioPermitido,
|
||||
});
|
||||
}
|
||||
|
||||
// Criar dados da tabela com saldo diário
|
||||
for (const [data, regs] of Object.entries(registrosPorData)) {
|
||||
// Formatar data para exibição (DD/MM/YYYY)
|
||||
const dataParts = data.split('-');
|
||||
const dataFormatada = `${dataParts[2]}/${dataParts[1]}/${dataParts[0]}`;
|
||||
// Formatar data para exibição usando função centralizada (DD/MM/AAAA)
|
||||
const dataFormatada = formatarDataDDMMAAAA(data);
|
||||
|
||||
// Calcular saldo diário como diferença entre saída e entrada
|
||||
const saldoDiarioDia = calcularSaldoDiario(regs);
|
||||
@@ -621,6 +733,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Adicionar localização (geofencing)
|
||||
if (reg.dentroRaioPermitido === true) {
|
||||
linha.push('✅ Dentro do Raio');
|
||||
} else if (reg.dentroRaioPermitido === false) {
|
||||
linha.push('⚠️ Fora do Raio');
|
||||
} else {
|
||||
linha.push('❓ Não Validado');
|
||||
}
|
||||
|
||||
linha.push(reg.dentroDoPrazo ? 'Sim' : 'Não');
|
||||
|
||||
tableData.push(linha);
|
||||
@@ -631,6 +752,7 @@
|
||||
if (sections.saldoDiario) {
|
||||
headers.push('Saldo Diário');
|
||||
}
|
||||
headers.push('Localização');
|
||||
headers.push('Dentro do Prazo');
|
||||
|
||||
// Salvar a posição Y antes da tabela
|
||||
@@ -740,9 +862,8 @@
|
||||
yPosition += 10;
|
||||
|
||||
const homologacoesData = homologacoes.map((h) => {
|
||||
// Formatar data de criação
|
||||
const dataCriacao = new Date(h.criadoEm);
|
||||
const dataFormatada = `${dataCriacao.getDate().toString().padStart(2, '0')}/${(dataCriacao.getMonth() + 1).toString().padStart(2, '0')}/${dataCriacao.getFullYear()}`;
|
||||
// Formatar data de criação usando função centralizada (DD/MM/AAAA)
|
||||
const dataFormatada = formatarDataDDMMAAAA(h.criadoEm);
|
||||
|
||||
if (h.registroId && h.horaAnterior !== undefined) {
|
||||
return [
|
||||
@@ -804,13 +925,9 @@
|
||||
yPosition += 10;
|
||||
|
||||
const dispensasData = dispensas.map((d) => {
|
||||
// Formatar data de início
|
||||
const dataInicioParts = d.dataInicio.split('-');
|
||||
const dataInicioFormatada = `${dataInicioParts[2]}/${dataInicioParts[1]}/${dataInicioParts[0]}`;
|
||||
|
||||
// Formatar data de fim
|
||||
const dataFimParts = d.dataFim.split('-');
|
||||
const dataFimFormatada = `${dataFimParts[2]}/${dataFimParts[1]}/${dataFimParts[0]}`;
|
||||
// Formatar data de início e fim usando função centralizada (DD/MM/AAAA)
|
||||
const dataInicioFormatada = formatarDataDDMMAAAA(d.dataInicio);
|
||||
const dataFimFormatada = formatarDataDDMMAAAA(d.dataFim);
|
||||
|
||||
return [
|
||||
`${dataInicioFormatada} ${d.horaInicio.toString().padStart(2, '0')}:${d.minutoInicio.toString().padStart(2, '0')}`,
|
||||
@@ -1805,13 +1922,34 @@
|
||||
<!-- Filtros -->
|
||||
<div class="card bg-base-100/90 backdrop-blur-sm border border-base-300 shadow-xl mb-8">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div class="p-2 bg-secondary/10 rounded-lg">
|
||||
<Filter class="h-5 w-5 text-secondary" strokeWidth={2.5} />
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 bg-secondary/10 rounded-lg">
|
||||
<Filter class="h-5 w-5 text-secondary" strokeWidth={2.5} />
|
||||
</div>
|
||||
<h2 class="card-title text-2xl mb-0">Filtros de Busca</h2>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline btn-secondary gap-2 shadow-md hover:shadow-lg transition-all"
|
||||
onclick={limparFiltros}
|
||||
>
|
||||
<Filter class="h-4 w-4" />
|
||||
Limpar Filtros
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary gap-2 shadow-md hover:shadow-lg transition-all"
|
||||
onclick={exportarCSV}
|
||||
disabled={registrosFiltrados.length === 0}
|
||||
>
|
||||
<Download class="h-4 w-4" />
|
||||
Exportar CSV
|
||||
</button>
|
||||
</div>
|
||||
<h2 class="card-title text-2xl mb-0">Filtros de Busca</h2>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label" for="data-inicio">
|
||||
<span class="label-text font-semibold">Data Início</span>
|
||||
@@ -1851,7 +1989,51 @@
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label" for="status">
|
||||
<span class="label-text font-semibold">Status</span>
|
||||
</label>
|
||||
<select
|
||||
id="status"
|
||||
bind:value={statusFiltro}
|
||||
class="select select-bordered select-primary focus:select-primary focus:ring-2 focus:ring-primary/20"
|
||||
>
|
||||
<option value="todos">Todos</option>
|
||||
<option value="dentro">Dentro do Prazo</option>
|
||||
<option value="fora">Fora do Prazo</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label" for="localizacao">
|
||||
<span class="label-text font-semibold">Localização</span>
|
||||
</label>
|
||||
<select
|
||||
id="localizacao"
|
||||
bind:value={localizacaoFiltro}
|
||||
class="select select-bordered select-primary focus:select-primary focus:ring-2 focus:ring-primary/20"
|
||||
>
|
||||
<option value="todos">Todas</option>
|
||||
<option value="dentro">Dentro do Raio</option>
|
||||
<option value="fora">Fora do Raio</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Indicador de registros filtrados -->
|
||||
{#if statusFiltro !== 'todos' || localizacaoFiltro !== 'todos'}
|
||||
<div class="mt-4 p-3 bg-info/10 border border-info/20 rounded-lg">
|
||||
<p class="text-sm text-base-content/80">
|
||||
<strong>{registrosFiltrados.length}</strong> registro(s) encontrado(s) com os filtros aplicados
|
||||
{#if registros.length !== registrosFiltrados.length}
|
||||
<span class="text-xs text-base-content/60">
|
||||
(de {registros.length} total)
|
||||
</span>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1878,13 +2060,13 @@
|
||||
{#if dataInicio}
|
||||
<div class="badge badge-info badge-lg gap-2 px-4 py-3">
|
||||
<Clock class="h-4 w-4" />
|
||||
De: {formatarData(dataInicio)}
|
||||
De: {formatarDataDDMMAAAA(dataInicio)}
|
||||
</div>
|
||||
{/if}
|
||||
{#if dataFim}
|
||||
<div class="badge badge-info badge-lg gap-2 px-4 py-3">
|
||||
<Clock class="h-4 w-4" />
|
||||
Até: {formatarData(dataFim)}
|
||||
Até: {formatarDataDDMMAAAA(dataFim)}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1916,7 +2098,7 @@
|
||||
<div class="flex-1">
|
||||
<h3 class="font-bold text-base-content">Nenhum registro encontrado</h3>
|
||||
<div class="text-sm mt-2 opacity-80">
|
||||
<p>Período: <span class="font-semibold">{formatarData(dataInicio)} até {formatarData(dataFim)}</span></p>
|
||||
<p>Período: <span class="font-semibold">{formatarDataDDMMAAAA(dataInicio)} até {formatarDataDDMMAAAA(dataFim)}</span></p>
|
||||
{#if funcionarioIdFiltro && funcionarioSelecionadoNome}
|
||||
<p class="mt-1">Funcionário: <span class="font-semibold">{funcionarioSelecionadoNome}</span></p>
|
||||
{/if}
|
||||
@@ -2011,6 +2193,7 @@
|
||||
<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">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">Status</th>
|
||||
<th class="whitespace-nowrap font-bold text-base-content border-b border-base-400">Ações</th>
|
||||
</tr>
|
||||
@@ -2018,8 +2201,7 @@
|
||||
<tbody>
|
||||
{#each Object.values(grupo.registrosPorData) as grupoData}
|
||||
{@const totalRegistros = grupoData.registros.length}
|
||||
{@const dataParts = grupoData.data.split('-')}
|
||||
{@const dataFormatada = `${dataParts[2]}/${dataParts[1]}/${dataParts[0]}`}
|
||||
{@const dataFormatada = formatarDataDDMMAAAA(grupoData.data)}
|
||||
{#each grupoData.registros as registro, index}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap">{dataFormatada}</td>
|
||||
@@ -2036,17 +2218,12 @@
|
||||
<td class="whitespace-nowrap">{formatarHoraPonto(registro.hora, registro.minuto)}</td>
|
||||
{#if index === 0}
|
||||
<td class="whitespace-nowrap" rowspan={totalRegistros}>
|
||||
{#if grupoData.saldoDiario}
|
||||
<span
|
||||
class="badge badge-lg font-semibold {grupoData.saldoDiario.positivo ? 'badge-success shadow-sm' : 'badge-error shadow-sm'}"
|
||||
>
|
||||
{formatarSaldoDiario(grupoData.saldoDiario)}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="badge badge-ghost badge-lg">-</span>
|
||||
{/if}
|
||||
<SaldoDiarioBadge saldo={grupoData.saldoDiario} size="md" />
|
||||
</td>
|
||||
{/if}
|
||||
<td class="whitespace-nowrap">
|
||||
<LocalizacaoIcon dentroRaioPermitido={registro.dentroRaioPermitido} />
|
||||
</td>
|
||||
<td class="whitespace-nowrap">
|
||||
<span
|
||||
class="badge badge-lg font-semibold {registro.dentroDoPrazo ? 'badge-success shadow-sm' : 'badge-error shadow-sm'}"
|
||||
|
||||
Reference in New Issue
Block a user