feat: enhance point registration and management features
- Added functionality to capture and display images during point registration, improving user experience. - Implemented error handling for image uploads and webcam access, ensuring smoother operation. - Introduced a justification field for point registration, allowing users to provide context for their entries. - Enhanced the backend to support new features, including image handling and justification storage. - Updated UI components for better layout and responsiveness, improving overall usability.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { useQuery, useConvexClient } from 'convex-svelte';
|
||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||
import { Clock, Filter, Download, Printer, BarChart3, Users, CheckCircle2, XCircle } from 'lucide-svelte';
|
||||
import { Clock, Filter, Download, Printer, BarChart3, Users, CheckCircle2, XCircle, TrendingUp, TrendingDown } from 'lucide-svelte';
|
||||
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||
import { formatarHoraPonto, getTipoRegistroLabel } from '$lib/utils/ponto';
|
||||
import jsPDF from 'jspdf';
|
||||
@@ -17,18 +17,22 @@
|
||||
let funcionarioIdFiltro = $state<Id<'funcionarios'> | ''>('');
|
||||
let carregando = $state(false);
|
||||
|
||||
// Queries
|
||||
const funcionariosQuery = useQuery(api.funcionarios.getAll, {});
|
||||
const registrosQuery = useQuery(api.pontos.listarRegistrosPeriodo, {
|
||||
// Parâmetros reativos para queries
|
||||
const registrosParams = $derived({
|
||||
funcionarioId: funcionarioIdFiltro || undefined,
|
||||
dataInicio,
|
||||
dataFim,
|
||||
});
|
||||
const estatisticasQuery = useQuery(api.pontos.obterEstatisticas, {
|
||||
const estatisticasParams = $derived({
|
||||
dataInicio,
|
||||
dataFim,
|
||||
});
|
||||
|
||||
// Queries
|
||||
const funcionariosQuery = useQuery(api.funcionarios.getAll, {});
|
||||
const registrosQuery = useQuery(api.pontos.listarRegistrosPeriodo, registrosParams);
|
||||
const estatisticasQuery = useQuery(api.pontos.obterEstatisticas, estatisticasParams);
|
||||
|
||||
const funcionarios = $derived(funcionariosQuery?.data || []);
|
||||
const registros = $derived(registrosQuery?.data || []);
|
||||
const estatisticas = $derived(estatisticasQuery?.data);
|
||||
@@ -39,6 +43,7 @@
|
||||
string,
|
||||
{
|
||||
funcionario: { nome: string; matricula?: string; descricaoCargo?: string } | null;
|
||||
funcionarioId: Id<'funcionarios'>;
|
||||
registros: typeof registros;
|
||||
}
|
||||
> = {};
|
||||
@@ -48,6 +53,7 @@
|
||||
if (!agrupados[key]) {
|
||||
agrupados[key] = {
|
||||
funcionario: registro.funcionario,
|
||||
funcionarioId: registro.funcionarioId,
|
||||
registros: [],
|
||||
};
|
||||
}
|
||||
@@ -57,6 +63,19 @@
|
||||
return Object.values(agrupados);
|
||||
});
|
||||
|
||||
// Query para banco de horas de cada funcionário
|
||||
const funcionariosComBancoHoras = $derived.by(() => {
|
||||
return registrosAgrupados.map((grupo) => grupo.funcionarioId);
|
||||
});
|
||||
|
||||
// Função para formatar saldo de horas
|
||||
function formatarSaldoHoras(minutos: number): string {
|
||||
const horas = Math.floor(Math.abs(minutos) / 60);
|
||||
const mins = Math.abs(minutos) % 60;
|
||||
const sinal = minutos >= 0 ? '+' : '-';
|
||||
return `${sinal}${horas}h ${mins}min`;
|
||||
}
|
||||
|
||||
async function imprimirFichaPonto(funcionarioId: Id<'funcionarios'>) {
|
||||
const registrosFuncionario = registros.filter((r) => r.funcionarioId === funcionarioId);
|
||||
if (registrosFuncionario.length === 0) {
|
||||
@@ -297,7 +316,7 @@
|
||||
<div class="card bg-base-200">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<div class="flex-1">
|
||||
<h3 class="font-bold text-lg">
|
||||
{grupo.funcionario?.nome || 'Funcionário não encontrado'}
|
||||
</h3>
|
||||
@@ -307,9 +326,39 @@
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Banco de Horas -->
|
||||
{#key grupo.funcionarioId}
|
||||
{@const bancoHorasQuery = useQuery(
|
||||
api.pontos.obterBancoHorasFuncionario,
|
||||
{ funcionarioId: grupo.funcionarioId }
|
||||
)}
|
||||
{@const bancoHoras = bancoHorasQuery?.data}
|
||||
{@const saldoAcumulado = bancoHoras?.saldoAcumuladoMinutos ?? 0}
|
||||
{@const saldoPositivo = saldoAcumulado >= 0}
|
||||
|
||||
{#if bancoHoras}
|
||||
<div class="mx-4 rounded-lg border-2 p-3 {saldoPositivo ? 'border-success bg-success/10' : 'border-error bg-error/10'}">
|
||||
<div class="flex items-center gap-2">
|
||||
{#if saldoPositivo}
|
||||
<TrendingUp class="h-5 w-5 text-success" />
|
||||
{:else}
|
||||
<TrendingDown class="h-5 w-5 text-error" />
|
||||
{/if}
|
||||
<div>
|
||||
<p class="text-xs font-semibold opacity-70">Banco de Horas</p>
|
||||
<p class="text-lg font-bold">
|
||||
{formatarSaldoHoras(saldoAcumulado)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/key}
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
onclick={() => imprimirFichaPonto(grupo.registros[0]!.funcionarioId)}
|
||||
onclick={() => imprimirFichaPonto(grupo.funcionarioId)}
|
||||
>
|
||||
<Printer class="h-4 w-4" />
|
||||
Imprimir Ficha
|
||||
|
||||
Reference in New Issue
Block a user