feat: add new features for point management and registration

- Introduced "Homologação de Registro" and "Dispensa de Registro" sections in the dashboard for enhanced point management.
- Updated the WidgetGestaoPontos component to include new links and icons for the added features.
- Enhanced backend functionality to support the new features, including querying and managing dispensas and homologações.
- Improved the PDF generation process to include daily balance calculations for employee time records.
- Implemented checks for active dispensas to prevent unauthorized point registrations.
This commit is contained in:
2025-11-19 16:37:31 -03:00
parent ed5695cf28
commit db61df1fb4
9 changed files with 2602 additions and 164 deletions

View File

@@ -0,0 +1,118 @@
<script lang="ts">
import { Clock, CheckCircle2, XCircle } from 'lucide-svelte';
import { resolve } from '$app/paths';
</script>
<div class="container mx-auto px-4 py-6">
<!-- Header -->
<div class="flex items-center gap-4 mb-6">
<div class="p-3 bg-primary/10 rounded-xl">
<Clock class="h-8 w-8 text-primary" strokeWidth={2} />
</div>
<div>
<h1 class="text-3xl font-bold text-base-content">Controle de Ponto</h1>
<p class="text-base-content/60 mt-1">Gerencie registros, homologações e dispensas de ponto</p>
</div>
</div>
<!-- Grid de Cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Card 1: Gestão de Pontos -->
<a
href={resolve('/(dashboard)/recursos-humanos/registro-pontos')}
class="card bg-base-100 shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1"
>
<div class="card-body">
<div class="flex items-start justify-between mb-4">
<div class="p-4 bg-blue-500/20 rounded-2xl">
<Clock class="h-8 w-8 text-blue-600" strokeWidth={2} />
</div>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-base-content/30"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"
/>
</svg>
</div>
<h2 class="card-title text-xl mb-2">Gestão de Pontos</h2>
<p class="text-base-content/70">
Visualizar e gerenciar registros de ponto dos funcionários, relatórios e histórico
</p>
</div>
</a>
<!-- Card 2: Homologação de Registro -->
<a
href={resolve('/(dashboard)/recursos-humanos/controle-ponto/homologacao')}
class="card bg-base-100 shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1"
>
<div class="card-body">
<div class="flex items-start justify-between mb-4">
<div class="p-4 bg-green-500/20 rounded-2xl">
<CheckCircle2 class="h-8 w-8 text-green-600" strokeWidth={2} />
</div>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-base-content/30"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"
/>
</svg>
</div>
<h2 class="card-title text-xl mb-2">Homologação de Registro</h2>
<p class="text-base-content/70">
Edite registros de ponto do seu time, ajuste banco de horas (compensar, abonar ou descontar)
</p>
</div>
</a>
<!-- Card 3: Dispensa de Registro -->
<a
href={resolve('/(dashboard)/recursos-humanos/controle-ponto/dispensa')}
class="card bg-base-100 shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1"
>
<div class="card-body">
<div class="flex items-start justify-between mb-4">
<div class="p-4 bg-orange-500/20 rounded-2xl">
<XCircle class="h-8 w-8 text-orange-600" strokeWidth={2} />
</div>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-base-content/30"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"
/>
</svg>
</div>
<h2 class="card-title text-xl mb-2">Dispensa de Registro</h2>
<p class="text-base-content/70">
Gerencie períodos onde funcionários estão dispensados de registrar ponto
</p>
</div>
</a>
</div>
</div>

View File

@@ -0,0 +1,360 @@
<script lang="ts">
import { onMount } from 'svelte';
import { useQuery, useConvexClient } from 'convex-svelte';
import { api } from '@sgse-app/backend/convex/_generated/api';
import { Clock, Plus, X, Trash2 } from 'lucide-svelte';
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
import { toast } from 'svelte-sonner';
const client = useConvexClient();
// Estados
let funcionariosSelecionados = $state<Id<'funcionarios'>[]>([]);
let modoCriacao = $state(false);
// Formulário
let dataInicio = $state(new Date().toISOString().split('T')[0]!);
let horaInicio = $state(8);
let minutoInicio = $state(0);
let dataFim = $state(new Date().toISOString().split('T')[0]!);
let horaFim = $state(18);
let minutoFim = $state(0);
let motivo = $state('');
let isento = $state(false);
// Queries
const subordinadosQuery = useQuery(api.times.listarSubordinadosDoGestorAtual, {});
const dispensasQuery = useQuery(api.pontos.listarDispensas, {
apenasAtivas: false, // Mostrar todas para o gestor ver histórico
});
const subordinados = $derived(subordinadosQuery?.data || []);
const dispensas = $derived(dispensasQuery?.data || []);
// Lista de funcionários do time
const funcionarios = $derived.by(() => {
const funcs: Array<{ _id: Id<'funcionarios'>; nome: string; matricula?: string }> = [];
for (const time of subordinados) {
for (const membro of time.membros) {
if (membro.funcionario && !funcs.find((f) => f._id === membro.funcionario._id)) {
funcs.push({
_id: membro.funcionario._id,
nome: membro.funcionario.nome,
matricula: membro.funcionario.matricula,
});
}
}
}
return funcs;
});
function abrirCriacao() {
modoCriacao = true;
funcionariosSelecionados = [];
dataInicio = new Date().toISOString().split('T')[0]!;
horaInicio = 8;
minutoInicio = 0;
dataFim = new Date().toISOString().split('T')[0]!;
horaFim = 18;
minutoFim = 0;
motivo = '';
isento = false;
}
function cancelar() {
modoCriacao = false;
funcionariosSelecionados = [];
}
function toggleFuncionario(funcionarioId: Id<'funcionarios'>) {
if (funcionariosSelecionados.includes(funcionarioId)) {
funcionariosSelecionados = funcionariosSelecionados.filter((id) => id !== funcionarioId);
} else {
funcionariosSelecionados = [...funcionariosSelecionados, funcionarioId];
}
}
async function salvarDispensa() {
if (funcionariosSelecionados.length === 0) {
toast.error('Selecione pelo menos um funcionário');
return;
}
if (!motivo.trim()) {
toast.error('Informe o motivo da dispensa');
return;
}
const dataInicioObj = new Date(dataInicio);
const dataFimObj = new Date(dataFim);
if (dataFimObj < dataInicioObj) {
toast.error('Data fim deve ser maior ou igual à data início');
return;
}
try {
// Criar dispensa para cada funcionário selecionado
const promises = funcionariosSelecionados.map((funcionarioId) =>
client.mutation(api.pontos.criarDispensaRegistro, {
funcionarioId,
dataInicio,
horaInicio,
minutoInicio,
dataFim,
horaFim,
minutoFim,
motivo,
isento,
})
);
await Promise.all(promises);
toast.success(
`Dispensa criada com sucesso para ${funcionariosSelecionados.length} funcionário(s)`
);
cancelar();
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
toast.error(`Erro ao criar dispensa: ${errorMessage}`);
}
}
async function removerDispensa(dispensaId: Id<'dispensasRegistro'>) {
if (!confirm('Deseja realmente remover esta dispensa?')) return;
try {
await client.mutation(api.pontos.removerDispensaRegistro, {
dispensaId,
});
toast.success('Dispensa removida com sucesso');
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
toast.error(`Erro ao remover dispensa: ${errorMessage}`);
}
}
function formatarDataHora(data: string, hora: number, minuto: number): string {
return `${new Date(data).toLocaleDateString('pt-BR')} ${hora.toString().padStart(2, '0')}:${minuto.toString().padStart(2, '0')}`;
}
</script>
<div class="container mx-auto px-4 py-6">
<!-- Header -->
<div class="flex items-center justify-between mb-6">
<div class="flex items-center gap-4">
<div class="p-3 bg-primary/10 rounded-xl">
<Clock class="h-8 w-8 text-primary" strokeWidth={2} />
</div>
<div>
<h1 class="text-3xl font-bold text-base-content">Dispensa de Registro</h1>
<p class="text-base-content/60 mt-1">Gerencie períodos de dispensa de registro de ponto</p>
</div>
</div>
{#if !modoCriacao}
<button class="btn btn-primary gap-2" onclick={abrirCriacao}>
<Plus class="h-4 w-4" />
Nova Dispensa
</button>
{/if}
</div>
<!-- Formulário de Criação -->
{#if modoCriacao}
<div class="card bg-base-100 shadow-xl mb-6">
<div class="card-body">
<h2 class="card-title mb-4">Criar Dispensa de Registro</h2>
<!-- Seleção de Funcionários -->
<div class="form-control mb-4">
<label class="label">
<span class="label-text font-medium">Funcionários</span>
</label>
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 max-h-60 overflow-y-auto border border-base-300 rounded-lg p-4">
{#each funcionarios as funcionario}
<label class="label cursor-pointer">
<span class="label-text">
{funcionario.nome} {funcionario.matricula ? `(${funcionario.matricula})` : ''}
</span>
<input
type="checkbox"
class="checkbox checkbox-primary"
checked={funcionariosSelecionados.includes(funcionario._id)}
onchange={() => toggleFuncionario(funcionario._id)}
/>
</label>
{/each}
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Data Início</span>
</label>
<input type="date" class="input input-bordered" bind:value={dataInicio} />
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Hora Início</span>
</label>
<div class="flex gap-2">
<input
type="number"
min="0"
max="23"
class="input input-bordered flex-1"
bind:value={horaInicio}
/>
<span class="self-center">:</span>
<input
type="number"
min="0"
max="59"
class="input input-bordered flex-1"
bind:value={minutoInicio}
/>
</div>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Data Fim</span>
</label>
<input type="date" class="input input-bordered" bind:value={dataFim} />
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Hora Fim</span>
</label>
<div class="flex gap-2">
<input
type="number"
min="0"
max="23"
class="input input-bordered flex-1"
bind:value={horaFim}
/>
<span class="self-center">:</span>
<input
type="number"
min="0"
max="59"
class="input input-bordered flex-1"
bind:value={minutoFim}
/>
</div>
</div>
<div class="form-control md:col-span-2">
<label class="label">
<span class="label-text font-medium">Motivo</span>
</label>
<textarea class="textarea textarea-bordered" bind:value={motivo} rows="3"></textarea>
</div>
<div class="form-control md:col-span-2">
<label class="label cursor-pointer">
<span class="label-text font-medium">Isento de Registro (caso excepcional - sem expiração)</span>
<input type="checkbox" class="checkbox checkbox-primary" bind:checked={isento} />
</label>
<p class="text-sm text-base-content/70 mt-1">
Se marcado, o funcionário ficará permanentemente dispensado de registrar ponto
</p>
</div>
</div>
<div class="flex gap-2 mt-4">
<button class="btn btn-primary gap-2" onclick={salvarDispensa}>
<Plus class="h-4 w-4" />
Criar Dispensa
</button>
<button class="btn btn-ghost gap-2" onclick={cancelar}>
<X class="h-4 w-4" />
Cancelar
</button>
</div>
</div>
</div>
{/if}
<!-- Lista de Dispensas -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title mb-4">Dispensas Ativas</h2>
{#if dispensas.length === 0}
<div class="alert alert-info">
<span>Nenhuma dispensa ativa encontrada</span>
</div>
{:else}
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th>Funcionário</th>
<th>Período</th>
<th>Motivo</th>
<th>Status</th>
<th>Gestor</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{#each dispensas as dispensa}
<tr>
<td>
{dispensa.funcionario?.nome || '-'}
{#if dispensa.funcionario?.matricula}
<br />
<span class="text-sm text-base-content/70">
Mat: {dispensa.funcionario.matricula}
</span>
{/if}
</td>
<td>
<div class="text-sm">
<div>
<strong>Início:</strong>{' '}
{formatarDataHora(dispensa.dataInicio, dispensa.horaInicio, dispensa.minutoInicio)}
</div>
<div>
<strong>Fim:</strong>{' '}
{formatarDataHora(dispensa.dataFim, dispensa.horaFim, dispensa.minutoFim)}
</div>
</div>
</td>
<td>{dispensa.motivo}</td>
<td>
{#if dispensa.isento}
<span class="badge badge-warning">Isento (sem expiração)</span>
{:else if dispensa.expirada}
<span class="badge badge-error">Expirada</span>
{:else}
<span class="badge badge-success">Ativa</span>
{/if}
</td>
<td>{dispensa.gestor?.nome || '-'}</td>
<td>
<button
class="btn btn-sm btn-error gap-2"
onclick={() => removerDispensa(dispensa._id)}
>
<Trash2 class="h-4 w-4" />
Remover
</button>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
</div>
</div>

View File

@@ -0,0 +1,538 @@
<script lang="ts">
import { onMount } from 'svelte';
import { useQuery, useConvexClient } from 'convex-svelte';
import { api } from '@sgse-app/backend/convex/_generated/api';
import { Clock, Edit, TrendingUp, TrendingDown, Save, X } from 'lucide-svelte';
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
import { formatarHoraPonto, getTipoRegistroLabel } from '$lib/utils/ponto';
import { toast } from 'svelte-sonner';
const client = useConvexClient();
// Estados
let funcionarioSelecionado = $state<Id<'funcionarios'> | ''>('');
let registroSelecionado = $state<Id<'registrosPonto'> | ''>('');
let modoEdicao = $state(false);
let modoAjuste = $state(false);
// Formulário de edição
let horaNova = $state(8);
let minutoNova = $state(0);
let motivoId = $state('');
let motivoTipo = $state('');
let motivoDescricao = $state('');
let observacoes = $state('');
// Formulário de ajuste
let tipoAjuste = $state<'compensar' | 'abonar' | 'descontar'>('compensar');
let periodoDias = $state(0);
let periodoHoras = $state(0);
let periodoMinutos = $state(0);
// Queries
const subordinadosQuery = useQuery(api.times.listarSubordinadosDoGestorAtual, {});
const motivosQuery = useQuery(api.pontos.obterMotivosAtestados, {});
// Parâmetros reativos para queries
const homologacoesParams = $derived({
funcionarioId: funcionarioSelecionado || undefined,
});
const registrosQueryParams = $derived({
funcionarioId: funcionarioSelecionado || undefined,
dataInicio: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]!,
dataFim: new Date().toISOString().split('T')[0]!,
});
const homologacoesQuery = useQuery(api.pontos.listarHomologacoes, homologacoesParams);
const registrosQuery = useQuery(api.pontos.listarRegistrosPeriodo, registrosQueryParams);
const subordinados = $derived(subordinadosQuery?.data || []);
const motivos = $derived(motivosQuery?.data);
const homologacoes = $derived(homologacoesQuery?.data || []);
const registros = $derived(registrosQuery?.data || []);
// Lista de funcionários do time
const funcionarios = $derived.by(() => {
const funcs: Array<{ _id: Id<'funcionarios'>; nome: string; matricula?: string }> = [];
for (const time of subordinados) {
for (const membro of time.membros) {
if (membro.funcionario && !funcs.find((f) => f._id === membro.funcionario._id)) {
funcs.push({
_id: membro.funcionario._id,
nome: membro.funcionario.nome,
matricula: membro.funcionario.matricula,
});
}
}
}
return funcs;
});
function abrirEdicao(registroId: Id<'registrosPonto'>) {
const registro = registros.find((r) => r._id === registroId);
if (!registro) return;
registroSelecionado = registroId;
horaNova = registro.hora;
minutoNova = registro.minuto;
motivoId = '';
motivoTipo = '';
motivoDescricao = '';
observacoes = '';
modoEdicao = true;
modoAjuste = false;
}
function abrirAjuste() {
modoAjuste = true;
modoEdicao = false;
registroSelecionado = '';
tipoAjuste = 'compensar';
periodoDias = 0;
periodoHoras = 0;
periodoMinutos = 0;
motivoId = '';
motivoTipo = '';
motivoDescricao = '';
observacoes = '';
}
function cancelar() {
modoEdicao = false;
modoAjuste = false;
registroSelecionado = '';
}
async function salvarEdicao() {
if (!registroSelecionado || !funcionarioSelecionado) {
toast.error('Selecione um funcionário e um registro');
return;
}
try {
await client.mutation(api.pontos.editarRegistroPonto, {
registroId: registroSelecionado,
horaNova,
minutoNova,
motivoId: motivoId || undefined,
motivoTipo: motivoTipo || undefined,
motivoDescricao: motivoDescricao || undefined,
observacoes: observacoes || undefined,
});
toast.success('Registro editado com sucesso');
cancelar();
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
toast.error(`Erro ao editar registro: ${errorMessage}`);
}
}
async function salvarAjuste() {
if (!funcionarioSelecionado) {
toast.error('Selecione um funcionário');
return;
}
if (periodoDias === 0 && periodoHoras === 0 && periodoMinutos === 0) {
toast.error('Informe pelo menos um período (dias, horas ou minutos)');
return;
}
try {
await client.mutation(api.pontos.ajustarBancoHoras, {
funcionarioId: funcionarioSelecionado,
tipoAjuste,
periodoDias,
periodoHoras,
periodoMinutos,
motivoId: motivoId || undefined,
motivoTipo: motivoTipo || undefined,
motivoDescricao: motivoDescricao || undefined,
observacoes: observacoes || undefined,
});
toast.success('Banco de horas ajustado com sucesso');
cancelar();
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
toast.error(`Erro ao ajustar banco de horas: ${errorMessage}`);
}
}
</script>
<div class="container mx-auto px-4 py-6">
<!-- Header -->
<div class="flex items-center justify-between mb-6">
<div class="flex items-center gap-4">
<div class="p-3 bg-primary/10 rounded-xl">
<Clock class="h-8 w-8 text-primary" strokeWidth={2} />
</div>
<div>
<h1 class="text-3xl font-bold text-base-content">Homologação de Registro</h1>
<p class="text-base-content/60 mt-1">Edite registros de ponto e ajuste banco de horas</p>
</div>
</div>
</div>
<!-- Seleção de Funcionário -->
<div class="card bg-base-100 shadow-xl mb-6">
<div class="card-body">
<h2 class="card-title mb-4">Selecionar Funcionário</h2>
<select
class="select select-bordered w-full"
bind:value={funcionarioSelecionado}
disabled={modoEdicao || modoAjuste}
>
<option value="">Selecione um funcionário</option>
{#each funcionarios as funcionario}
<option value={funcionario._id}>
{funcionario.nome} {funcionario.matricula ? `(${funcionario.matricula})` : ''}
</option>
{/each}
</select>
</div>
</div>
<!-- Botões de Ação -->
{#if funcionarioSelecionado && !modoEdicao && !modoAjuste}
<div class="flex gap-4 mb-6">
<button class="btn btn-primary gap-2" onclick={abrirAjuste}>
<TrendingUp class="h-4 w-4" />
Ajustar Banco de Horas
</button>
</div>
{/if}
<!-- Formulário de Edição -->
{#if modoEdicao && registroSelecionado}
<div class="card bg-base-100 shadow-xl mb-6">
<div class="card-body">
<h2 class="card-title mb-4">Editar Registro de Ponto</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Nova Hora</span>
</label>
<input
type="number"
min="0"
max="23"
class="input input-bordered"
bind:value={horaNova}
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Novo Minuto</span>
</label>
<input
type="number"
min="0"
max="59"
class="input input-bordered"
bind:value={minutoNova}
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Motivo (Tipo)</span>
</label>
<select class="select select-bordered" bind:value={motivoTipo}>
<option value="">Selecione um tipo</option>
{#if motivos?.opcoesPadrao}
{#each motivos.opcoesPadrao as opcao}
<option value={opcao}>{opcao}</option>
{/each}
{/if}
</select>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Descrição do Motivo</span>
</label>
<input type="text" class="input input-bordered" bind:value={motivoDescricao} />
</div>
<div class="form-control md:col-span-2">
<label class="label">
<span class="label-text font-medium">Observações</span>
</label>
<textarea class="textarea textarea-bordered" bind:value={observacoes} rows="3"></textarea>
</div>
</div>
<div class="flex gap-2 mt-4">
<button class="btn btn-primary gap-2" onclick={salvarEdicao}>
<Save class="h-4 w-4" />
Salvar
</button>
<button class="btn btn-ghost gap-2" onclick={cancelar}>
<X class="h-4 w-4" />
Cancelar
</button>
</div>
</div>
</div>
{/if}
<!-- Formulário de Ajuste -->
{#if modoAjuste}
<div class="card bg-base-100 shadow-xl mb-6">
<div class="card-body">
<h2 class="card-title mb-4">Ajustar Banco de Horas</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Tipo de Ajuste</span>
</label>
<select class="select select-bordered" bind:value={tipoAjuste}>
<option value="compensar">Compensar</option>
<option value="abonar">Abonar</option>
<option value="descontar">Descontar em Folha</option>
</select>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Dias</span>
</label>
<input
type="number"
min="0"
class="input input-bordered"
bind:value={periodoDias}
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Horas</span>
</label>
<input
type="number"
min="0"
max="23"
class="input input-bordered"
bind:value={periodoHoras}
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Minutos</span>
</label>
<input
type="number"
min="0"
max="59"
class="input input-bordered"
bind:value={periodoMinutos}
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Motivo (Tipo)</span>
</label>
<select class="select select-bordered" bind:value={motivoTipo}>
<option value="">Selecione um tipo</option>
{#if motivos?.opcoesPadrao}
{#each motivos.opcoesPadrao as opcao}
<option value={opcao}>{opcao}</option>
{/each}
{/if}
</select>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Descrição do Motivo</span>
</label>
<input type="text" class="input input-bordered" bind:value={motivoDescricao} />
</div>
<div class="form-control md:col-span-2">
<label class="label">
<span class="label-text font-medium">Observações</span>
</label>
<textarea class="textarea textarea-bordered" bind:value={observacoes} rows="3"></textarea>
</div>
</div>
<div class="flex gap-2 mt-4">
<button class="btn btn-primary gap-2" onclick={salvarAjuste}>
<Save class="h-4 w-4" />
Salvar
</button>
<button class="btn btn-ghost gap-2" onclick={cancelar}>
<X class="h-4 w-4" />
Cancelar
</button>
</div>
</div>
</div>
{/if}
<!-- Lista de Registros -->
{#if funcionarioSelecionado && !modoEdicao && !modoAjuste}
<div class="card bg-base-100 shadow-xl mb-6">
<div class="card-body">
<h2 class="card-title mb-4">Registros do Funcionário</h2>
{#if registros.length === 0}
<div class="alert alert-info">
<span>Nenhum registro encontrado</span>
</div>
{:else}
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th>Data</th>
<th>Tipo</th>
<th>Horário</th>
<th>Status</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{#each registros as registro}
<tr>
<td>{registro.data}</td>
<td>
{getTipoRegistroLabel(registro.tipo)}
</td>
<td>{formatarHoraPonto(registro.hora, registro.minuto)}</td>
<td>
<span
class="badge {registro.dentroDoPrazo ? 'badge-success' : 'badge-error'}"
>
{registro.dentroDoPrazo ? 'Dentro do Prazo' : 'Fora do Prazo'}
</span>
</td>
<td>
<button
class="btn btn-sm btn-outline btn-primary gap-2"
onclick={() => abrirEdicao(registro._id)}
>
<Edit class="h-4 w-4" />
Editar
</button>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
</div>
{/if}
<!-- Histórico de Homologações -->
{#if !modoEdicao && !modoAjuste}
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title mb-4">
Histórico de Homologações
{#if funcionarioSelecionado}
<span class="text-sm font-normal text-base-content/70">
- Funcionário selecionado
</span>
{:else}
<span class="text-sm font-normal text-base-content/70">
- Todas as homologações do seu time
</span>
{/if}
</h2>
{#if homologacoes.length === 0}
<div class="alert alert-info">
<span>Nenhuma homologação encontrada</span>
</div>
{:else}
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th>Data</th>
{#if !funcionarioSelecionado}
<th>Funcionário</th>
{/if}
<th>Tipo</th>
<th>Detalhes</th>
<th>Motivo</th>
<th>Observações</th>
</tr>
</thead>
<tbody>
{#each homologacoes as homologacao}
<tr>
<td>
{new Date(homologacao.criadoEm).toLocaleDateString('pt-BR')}
</td>
{#if !funcionarioSelecionado}
<td>
{homologacao.funcionario?.nome || '-'}
{#if homologacao.funcionario?.matricula}
<br />
<span class="text-xs text-base-content/70">
Mat: {homologacao.funcionario.matricula}
</span>
{/if}
</td>
{/if}
<td>
{#if homologacao.registroId}
<span class="badge badge-info">Edição de Registro</span>
{:else if homologacao.tipoAjuste}
<span class="badge badge-warning">
Ajuste: {homologacao.tipoAjuste}
</span>
{/if}
</td>
<td>
{#if homologacao.horaAnterior !== undefined}
<div class="text-sm">
<span class="line-through opacity-70">
{formatarHoraPonto(homologacao.horaAnterior, homologacao.minutoAnterior || 0)}
</span>
{' → '}
<span>
{formatarHoraPonto(homologacao.horaNova || 0, homologacao.minutoNova || 0)}
</span>
</div>
{:else if homologacao.ajusteMinutos}
<div class="text-sm">
{homologacao.periodoDias || 0}d {homologacao.periodoHoras || 0}h{' '}
{homologacao.periodoMinutos || 0}min
</div>
{/if}
</td>
<td>
<div class="text-sm">
{homologacao.motivoDescricao || homologacao.motivoTipo || '-'}
</div>
</td>
<td>
<div class="text-sm max-w-xs truncate" title={homologacao.observacoes || ''}>
{homologacao.observacoes || '-'}
</div>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
</div>
{/if}
</div>