feat: update ESLint and TypeScript configurations across frontend and backend; enhance component structure and improve data handling in various modules

This commit is contained in:
2025-12-02 16:36:02 -03:00
parent f48d28067c
commit d79e6959c3
215 changed files with 29474 additions and 28173 deletions

View File

@@ -5,31 +5,31 @@
<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 class="mb-6 flex items-center gap-4">
<div class="bg-primary/10 rounded-xl p-3">
<Clock class="text-primary h-8 w-8" strokeWidth={2} />
</div>
<div>
<h1 class="text-3xl font-bold text-base-content">Controle de Ponto</h1>
<h1 class="text-base-content text-3xl font-bold">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">
<div class="grid grid-cols-1 gap-6 md:grid-cols-3">
<!-- 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"
class="card bg-base-100 transform shadow-xl transition-all duration-300 hover:-translate-y-1 hover:shadow-2xl"
>
<div class="card-body">
<div class="flex items-start justify-between mb-4">
<div class="p-4 bg-blue-500/20 rounded-2xl">
<div class="mb-4 flex items-start justify-between">
<div class="rounded-2xl bg-blue-500/20 p-4">
<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"
class="text-base-content/30 h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
@@ -42,7 +42,7 @@
/>
</svg>
</div>
<h2 class="card-title text-xl mb-2">Gestão de Pontos</h2>
<h2 class="card-title mb-2 text-xl">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>
@@ -52,16 +52,16 @@
<!-- 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"
class="card bg-base-100 transform shadow-xl transition-all duration-300 hover:-translate-y-1 hover:shadow-2xl"
>
<div class="card-body">
<div class="flex items-start justify-between mb-4">
<div class="p-4 bg-green-500/20 rounded-2xl">
<div class="mb-4 flex items-start justify-between">
<div class="rounded-2xl bg-green-500/20 p-4">
<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"
class="text-base-content/30 h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
@@ -74,9 +74,10 @@
/>
</svg>
</div>
<h2 class="card-title text-xl mb-2">Homologação de Registro</h2>
<h2 class="card-title mb-2 text-xl">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)
Edite registros de ponto do seu time, ajuste banco de horas (compensar, abonar ou
descontar)
</p>
</div>
</a>
@@ -84,16 +85,16 @@
<!-- 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"
class="card bg-base-100 transform shadow-xl transition-all duration-300 hover:-translate-y-1 hover:shadow-2xl"
>
<div class="card-body">
<div class="flex items-start justify-between mb-4">
<div class="p-4 bg-orange-500/20 rounded-2xl">
<div class="mb-4 flex items-start justify-between">
<div class="rounded-2xl bg-orange-500/20 p-4">
<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"
class="text-base-content/30 h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
@@ -106,7 +107,7 @@
/>
</svg>
</div>
<h2 class="card-title text-xl mb-2">Dispensa de Registro</h2>
<h2 class="card-title mb-2 text-xl">Dispensa de Registro</h2>
<p class="text-base-content/70">
Gerencie períodos onde funcionários estão dispensados de registrar ponto
</p>
@@ -114,5 +115,3 @@
</a>
</div>
</div>

View File

@@ -36,7 +36,7 @@
// Queries
const subordinadosQuery = useQuery(api.times.listarSubordinadosDoGestorAtual, {});
const dispensasQuery = useQuery(api.pontos.listarDispensas, {
apenasAtivas: true, // Mostrar apenas dispensas ativas
apenasAtivas: true // Mostrar apenas dispensas ativas
});
const subordinados = $derived(subordinadosQuery?.data || []);
@@ -44,7 +44,12 @@
// Lista de funcionários do time
const funcionarios = $derived.by(() => {
const funcs: Array<{ _id: Id<'funcionarios'>; nome: string; matricula?: string; fotoPerfilUrl?: string | null }> = [];
const funcs: Array<{
_id: Id<'funcionarios'>;
nome: string;
matricula?: string;
fotoPerfilUrl?: string | null;
}> = [];
for (const time of subordinados) {
for (const membro of time.membros) {
if (membro.funcionario && !funcs.find((f) => f._id === membro.funcionario._id)) {
@@ -52,7 +57,7 @@
_id: membro.funcionario._id,
nome: membro.funcionario.nome,
matricula: membro.funcionario.matricula,
fotoPerfilUrl: membro.funcionario.fotoPerfilUrl,
fotoPerfilUrl: membro.funcionario.fotoPerfilUrl
});
}
}
@@ -115,7 +120,7 @@
horaFim: horaFim.hora,
minutoFim: horaFim.minuto,
motivo,
isento,
isento
})
);
@@ -146,7 +151,7 @@
try {
await client.mutation(api.pontos.removerDispensaRegistro, {
dispensaId: dispensaParaExcluir,
dispensaId: dispensaParaExcluir
});
toast.success('Dispensa removida com sucesso');
@@ -164,13 +169,13 @@
<div class="container mx-auto px-4 py-6">
<!-- Header -->
<div class="flex items-center justify-between mb-6">
<div class="mb-6 flex items-center justify-between">
<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 class="bg-primary/10 rounded-xl p-3">
<Clock class="text-primary h-8 w-8" strokeWidth={2} />
</div>
<div>
<h1 class="text-3xl font-bold text-base-content">Dispensa de Registro</h1>
<h1 class="text-base-content text-3xl font-bold">Dispensa de Registro</h1>
<p class="text-base-content/60 mt-1">Gerencie períodos de dispensa de registro de ponto</p>
</div>
</div>
@@ -184,7 +189,7 @@
<!-- Formulário de Criação -->
{#if modoCriacao}
<div class="card bg-base-100 shadow-xl mb-6">
<div class="card bg-base-100 mb-6 shadow-xl">
<div class="card-body space-y-6">
<h2 class="card-title border-b pb-3 text-xl">Criar Dispensa de Registro</h2>
@@ -198,9 +203,11 @@
</span>
{/if}
</label>
<div class="max-h-60 overflow-y-auto border border-base-300 rounded-lg p-4 space-y-2">
<div class="border-base-300 max-h-60 space-y-2 overflow-y-auto rounded-lg border p-4">
{#each funcionarios as funcionario}
<label class="flex items-center justify-between p-3 rounded-lg hover:bg-base-200 transition-colors cursor-pointer">
<label
class="hover:bg-base-200 flex cursor-pointer items-center justify-between rounded-lg p-3 transition-colors"
>
<div class="flex items-center gap-3">
<UserAvatar
fotoPerfilUrl={funcionario.fotoPerfilUrl}
@@ -223,15 +230,13 @@
</label>
{/each}
{#if funcionarios.length === 0}
<div class="text-center py-4 text-base-content/60">
Nenhum funcionário disponível
</div>
<div class="text-base-content/60 py-4 text-center">Nenhum funcionário disponível</div>
{/if}
</div>
</div>
<!-- Período -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Data Início</span>
@@ -280,16 +285,17 @@
<input type="checkbox" class="checkbox checkbox-primary" bind:checked={isento} />
<div>
<span class="label-text font-medium">Isento de Registro</span>
<p class="text-sm text-base-content/70 mt-1">
Caso excepcional - sem expiração. O funcionário ficará permanentemente dispensado de registrar ponto.
<p class="text-base-content/70 mt-1 text-sm">
Caso excepcional - sem expiração. O funcionário ficará permanentemente dispensado de
registrar ponto.
</p>
</div>
</label>
</div>
<!-- Ações -->
<div class="flex gap-2 pt-4 border-t">
<button class="btn btn-primary gap-2 flex-1" onclick={salvarDispensa}>
<div class="flex gap-2 border-t pt-4">
<button class="btn btn-primary flex-1 gap-2" onclick={salvarDispensa}>
<Plus class="h-4 w-4" />
Criar Dispensa
</button>
@@ -313,7 +319,7 @@
</div>
{:else}
<div class="overflow-x-auto">
<table class="table table-zebra">
<table class="table-zebra table">
<thead>
<tr>
<th>Funcionário</th>
@@ -337,7 +343,7 @@
<div>
<div class="font-medium">{dispensa.funcionario?.nome || '-'}</div>
{#if dispensa.funcionario?.matricula}
<span class="text-sm text-base-content/70">
<span class="text-base-content/70 text-sm">
Mat: {dispensa.funcionario.matricula}
</span>
{/if}
@@ -348,7 +354,11 @@
<div class="text-sm">
<div>
<strong>Início:</strong>{' '}
{formatarDataHora(dispensa.dataInicio, dispensa.horaInicio, dispensa.minutoInicio)}
{formatarDataHora(
dispensa.dataInicio,
dispensa.horaInicio,
dispensa.minutoInicio
)}
</div>
<div>
<strong>Fim:</strong>{' '}
@@ -389,11 +399,11 @@
{#if mostrandoModalExcluir && dispensaParaExcluir}
<dialog class="modal modal-open">
<div class="modal-box">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-error/10 rounded-lg">
<AlertTriangle class="h-6 w-6 text-error" strokeWidth={2} />
<div class="mb-4 flex items-center gap-3">
<div class="bg-error/10 rounded-lg p-2">
<AlertTriangle class="text-error h-6 w-6" strokeWidth={2} />
</div>
<h3 class="font-bold text-lg">Confirmar Remoção</h3>
<h3 class="text-lg font-bold">Confirmar Remoção</h3>
</div>
<p class="text-base-content mb-6">
Deseja realmente remover esta dispensa? Esta ação não pode ser desfeita.
@@ -412,4 +422,3 @@
</dialog>
{/if}
</div>

View File

@@ -2,7 +2,17 @@
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, Trash2, Eye, MoreVertical } from 'lucide-svelte';
import {
Clock,
Edit,
TrendingUp,
TrendingDown,
Save,
X,
Trash2,
Eye,
MoreVertical
} 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';
@@ -19,7 +29,7 @@
let homologacaoParaExcluir = $state<Id<'homologacoesPonto'> | null>(null);
let mostrandoModalDetalhes = $state(false);
let mostrandoModalExcluir = $state(false);
// Filtros de período
const hoje = new Date();
const trintaDiasAtras = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
@@ -28,7 +38,11 @@
// Monitorar mudanças em funcionarioSelecionado
$effect(() => {
console.log('🔄 [DEBUG] funcionarioSelecionado mudou:', funcionarioSelecionado, typeof funcionarioSelecionado);
console.log(
'🔄 [DEBUG] funcionarioSelecionado mudou:',
funcionarioSelecionado,
typeof funcionarioSelecionado
);
});
// Formulário de edição
@@ -95,7 +109,7 @@
return data.toLocaleDateString('pt-BR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
year: 'numeric'
});
});
@@ -112,9 +126,9 @@
// Parâmetros reativos para queries
const homologacoesParams = $derived({
funcionarioId: funcionarioSelecionado || undefined,
funcionarioId: funcionarioSelecionado || undefined
});
// Parâmetros para query de registros - só executa quando há funcionário selecionado
const registrosQueryParams = $derived.by(() => {
// Verificar se funcionarioSelecionado não é string vazia
@@ -124,21 +138,19 @@
return {
funcionarioId: funcionarioSelecionado as Id<'funcionarios'>,
dataInicio: dataInicioFiltro,
dataFim: dataFimFiltro,
dataFim: dataFimFiltro
};
});
const homologacoesQuery = useQuery(api.pontos.listarHomologacoes, homologacoesParams);
const registrosQuery = $derived(
registrosQueryParams
? useQuery(api.pontos.listarRegistrosPeriodo, registrosQueryParams)
: null
registrosQueryParams ? useQuery(api.pontos.listarRegistrosPeriodo, registrosQueryParams) : null
);
const subordinados = $derived(subordinadosQuery?.data || []);
const motivos = $derived(motivosQuery?.data);
const homologacoes = $derived(homologacoesQuery?.data || []);
// Registros já filtrados pela query no backend
const registros = $derived.by(() => {
if (!funcionarioSelecionado || funcionarioSelecionado === '' || !registrosQuery) {
@@ -151,13 +163,18 @@
// A query do backend já filtra pelo funcionário, mas adicionamos verificação extra
return dados.filter((r) => String(r.funcionarioId) === String(funcionarioSelecionado));
});
// Verificar se é gestor (tem subordinados)
const isGestor = $derived(subordinados.length > 0);
// Lista de funcionários do time
const funcionarios = $derived.by(() => {
const funcs: Array<{ _id: Id<'funcionarios'>; nome: string; matricula?: string; fotoPerfilUrl?: string | null }> = [];
const funcs: Array<{
_id: Id<'funcionarios'>;
nome: string;
matricula?: string;
fotoPerfilUrl?: string | null;
}> = [];
for (const time of subordinados) {
for (const membro of time.membros) {
if (membro.funcionario && !funcs.find((f) => f._id === membro.funcionario._id)) {
@@ -165,7 +182,7 @@
_id: membro.funcionario._id,
nome: membro.funcionario.nome,
matricula: membro.funcionario.matricula,
fotoPerfilUrl: membro.funcionario.fotoPerfilUrl,
fotoPerfilUrl: membro.funcionario.fotoPerfilUrl
});
}
}
@@ -203,7 +220,7 @@
observacoes = '';
modoEdicao = true;
abaAtiva = 'editar';
// Resetar campos de ajuste
tipoAjuste = 'compensar';
const hoje = new Date().toISOString().split('T')[0]!;
@@ -242,7 +259,7 @@
motivoId: motivoId || undefined,
motivoTipo: motivoTipo || undefined,
motivoDescricao: motivoDescricao || undefined,
observacoes: observacoes || undefined,
observacoes: observacoes || undefined
});
toast.success('Registro editado com sucesso');
@@ -287,7 +304,7 @@
motivoId: motivoId || undefined,
motivoTipo: motivoTipo || undefined,
motivoDescricao: motivoDescricao || undefined,
observacoes: observacoes || undefined,
observacoes: observacoes || undefined
});
toast.success('Banco de horas ajustado com sucesso');
@@ -323,7 +340,7 @@
try {
await client.mutation(api.pontos.excluirHomologacao, {
homologacaoId: homologacaoParaExcluir,
homologacaoId: homologacaoParaExcluir
});
toast.success('Homologação excluída com sucesso');
@@ -344,7 +361,9 @@
abrirEdicaoComAjuste(homologacao.registroId);
} else {
// Se for ajuste de banco de horas, não há como editar diretamente
toast.info('Ajustes de banco de horas não podem ser editados. Crie um novo ajuste para corrigir.');
toast.info(
'Ajustes de banco de horas não podem ser editados. Crie um novo ajuste para corrigir.'
);
}
}
@@ -356,23 +375,23 @@
<div class="container mx-auto px-4 py-6">
<!-- Header -->
<div class="flex items-center justify-between mb-6">
<div class="mb-6 flex items-center justify-between">
<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 class="bg-primary/10 rounded-xl p-3">
<Clock class="text-primary h-8 w-8" strokeWidth={2} />
</div>
<div>
<h1 class="text-3xl font-bold text-base-content">Homologação de Registro</h1>
<h1 class="text-base-content text-3xl font-bold">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 bg-base-100 mb-6 shadow-xl">
<div class="card-body">
<h2 class="card-title mb-4">Selecionar Funcionário</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Funcionário</span>
@@ -385,7 +404,8 @@
<option value="">Selecione um funcionário</option>
{#each funcionarios as funcionario}
<option value={funcionario._id as string}>
{funcionario.nome} {funcionario.matricula ? `(${funcionario.matricula})` : ''}
{funcionario.nome}
{funcionario.matricula ? `(${funcionario.matricula})` : ''}
</option>
{/each}
</select>
@@ -420,23 +440,23 @@
<!-- Formulário Unificado de Edição e Ajuste -->
{#if modoEdicao && registroSelecionado}
<div class="card bg-base-100 shadow-xl mb-6 border-2 border-primary/20">
<div class="card bg-base-100 border-primary/20 mb-6 border-2 shadow-xl">
<div class="card-body p-8">
<!-- Cabeçalho Elegante -->
<div class="mb-8 pb-6 border-b border-base-300">
<div class="border-base-300 mb-8 border-b pb-6">
<div class="flex items-start gap-4">
<div class="p-3 bg-primary/10 rounded-xl">
<Edit class="h-7 w-7 text-primary" strokeWidth={2} />
<div class="bg-primary/10 rounded-xl p-3">
<Edit class="text-primary h-7 w-7" strokeWidth={2} />
</div>
<div class="flex-1">
<h2 class="text-3xl font-bold text-base-content mb-2">
Homologar Registro de Ponto
</h2>
<h2 class="text-base-content mb-2 text-3xl font-bold">Homologar Registro de Ponto</h2>
{#if dataRegistroFormatada}
<div class="flex items-center gap-2 text-base-content/70">
<div class="text-base-content/70 flex items-center gap-2">
<Clock class="h-4 w-4" />
<span class="text-sm">
Registro do dia <span class="font-semibold text-primary">{dataRegistroFormatada}</span>
Registro do dia <span class="text-primary font-semibold"
>{dataRegistroFormatada}</span
>
</span>
</div>
{/if}
@@ -445,19 +465,23 @@
</div>
<!-- Abas Modernas -->
<div class="tabs tabs-boxed mb-8 bg-base-200/50 p-1 rounded-lg">
<div class="tabs tabs-boxed bg-base-200/50 mb-8 rounded-lg p-1">
<button
class="tab tab-lg flex-1 transition-all {abaAtiva === 'editar' ? 'tab-active bg-primary text-primary-content shadow-md' : 'hover:bg-base-300'}"
class="tab tab-lg flex-1 transition-all {abaAtiva === 'editar'
? 'tab-active bg-primary text-primary-content shadow-md'
: 'hover:bg-base-300'}"
onclick={() => (abaAtiva = 'editar')}
>
<Edit class="h-4 w-4 mr-2" />
<Edit class="mr-2 h-4 w-4" />
Editar Horário
</button>
<button
class="tab tab-lg flex-1 transition-all {abaAtiva === 'ajustar' ? 'tab-active bg-primary text-primary-content shadow-md' : 'hover:bg-base-300'}"
class="tab tab-lg flex-1 transition-all {abaAtiva === 'ajustar'
? 'tab-active bg-primary text-primary-content shadow-md'
: 'hover:bg-base-300'}"
onclick={() => (abaAtiva = 'ajustar')}
>
<TrendingUp class="h-4 w-4 mr-2" />
<TrendingUp class="mr-2 h-4 w-4" />
Ajustar Banco de Horas
</button>
</div>
@@ -466,15 +490,19 @@
{#if abaAtiva === 'editar'}
<div class="space-y-8">
<!-- Card: Nova Hora -->
<div class="card bg-gradient-to-br from-primary/5 to-primary/10 border border-primary/20 shadow-sm">
<div
class="card from-primary/5 to-primary/10 border-primary/20 border bg-gradient-to-br shadow-sm"
>
<div class="card-body p-6">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-primary/20 rounded-lg">
<Clock class="h-5 w-5 text-primary" />
<div class="mb-4 flex items-center gap-3">
<div class="bg-primary/20 rounded-lg p-2">
<Clock class="text-primary h-5 w-5" />
</div>
<div>
<h3 class="text-lg font-semibold text-base-content">Nova Hora</h3>
<p class="text-sm text-base-content/60">Defina o novo horário para este registro</p>
<h3 class="text-base-content text-lg font-semibold">Nova Hora</h3>
<p class="text-base-content/60 text-sm">
Defina o novo horário para este registro
</p>
</div>
</div>
<div class="form-control">
@@ -488,19 +516,22 @@
</div>
<!-- Card: Motivo e Justificativa -->
<div class="card bg-base-50 border border-base-300 shadow-sm">
<div class="card bg-base-50 border-base-300 border shadow-sm">
<div class="card-body p-6">
<h3 class="text-lg font-semibold text-base-content mb-6 flex items-center gap-2">
<div class="w-1 h-6 bg-primary rounded-full"></div>
<h3 class="text-base-content mb-6 flex items-center gap-2 text-lg font-semibold">
<div class="bg-primary h-6 w-1 rounded-full"></div>
Motivo e Justificativa
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Tipo de Motivo</span>
<span class="label-text-alt text-error font-bold">*</span>
</label>
<select class="select select-bordered select-primary w-full" bind:value={motivoTipo}>
<select
class="select select-bordered select-primary w-full"
bind:value={motivoTipo}
>
<option value="">Selecione um tipo</option>
{#if motivos?.opcoesPadrao}
{#each motivos.opcoesPadrao as opcao}
@@ -526,10 +557,10 @@
</div>
<!-- Card: Observações -->
<div class="card bg-base-50 border border-base-300 shadow-sm">
<div class="card bg-base-50 border-base-300 border shadow-sm">
<div class="card-body p-6">
<h3 class="text-lg font-semibold text-base-content mb-4 flex items-center gap-2">
<div class="w-1 h-6 bg-primary rounded-full"></div>
<h3 class="text-base-content mb-4 flex items-center gap-2 text-lg font-semibold">
<div class="bg-primary h-6 w-1 rounded-full"></div>
Observações Adicionais
</h3>
<div class="form-control">
@@ -544,12 +575,12 @@
</div>
<!-- Botões de ação -->
<div class="flex gap-3 justify-end pt-6 border-t border-base-300">
<button class="btn btn-ghost gap-2 btn-lg" onclick={cancelar}>
<div class="border-base-300 flex justify-end gap-3 border-t pt-6">
<button class="btn btn-ghost btn-lg gap-2" onclick={cancelar}>
<X class="h-5 w-5" />
Cancelar
</button>
<button class="btn btn-primary gap-2 btn-lg shadow-lg" onclick={salvarEdicao}>
<button class="btn btn-primary btn-lg gap-2 shadow-lg" onclick={salvarEdicao}>
<Save class="h-5 w-5" />
Salvar Alterações
</button>
@@ -561,19 +592,26 @@
{#if abaAtiva === 'ajustar'}
<div class="space-y-8">
<!-- Card: Tipo de Ajuste -->
<div class="card bg-gradient-to-br from-warning/5 to-warning/10 border border-warning/20 shadow-sm">
<div
class="card from-warning/5 to-warning/10 border-warning/20 border bg-gradient-to-br shadow-sm"
>
<div class="card-body p-6">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-warning/20 rounded-lg">
<TrendingUp class="h-5 w-5 text-warning" />
<div class="mb-4 flex items-center gap-3">
<div class="bg-warning/20 rounded-lg p-2">
<TrendingUp class="text-warning h-5 w-5" />
</div>
<div>
<h3 class="text-lg font-semibold text-base-content">Tipo de Ajuste</h3>
<p class="text-sm text-base-content/60">Selecione o tipo de ajuste a ser aplicado</p>
<h3 class="text-base-content text-lg font-semibold">Tipo de Ajuste</h3>
<p class="text-base-content/60 text-sm">
Selecione o tipo de ajuste a ser aplicado
</p>
</div>
</div>
<div class="form-control">
<select class="select select-bordered select-warning w-full max-w-md text-base font-medium" bind:value={tipoAjuste}>
<select
class="select select-bordered select-warning w-full max-w-md text-base font-medium"
bind:value={tipoAjuste}
>
<option value="compensar">🕐 Compensar</option>
<option value="abonar">✅ Abonar</option>
<option value="descontar">❌ Descontar em Folha</option>
@@ -583,28 +621,32 @@
</div>
<!-- Card: Período com Data e Hora -->
<div class="card bg-base-50 border border-base-300 shadow-sm">
<div class="card bg-base-50 border-base-300 border shadow-sm">
<div class="card-body p-6">
<div class="flex items-center justify-between mb-6">
<div class="mb-6 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="p-2 bg-primary/20 rounded-lg">
<Clock class="h-5 w-5 text-primary" />
<div class="bg-primary/20 rounded-lg p-2">
<Clock class="text-primary h-5 w-5" />
</div>
<div>
<h3 class="text-lg font-semibold text-base-content">Período do Ajuste</h3>
<p class="text-sm text-base-content/60">Defina o período de início e fim do ajuste</p>
<h3 class="text-base-content text-lg font-semibold">Período do Ajuste</h3>
<p class="text-base-content/60 text-sm">
Defina o período de início e fim do ajuste
</p>
</div>
</div>
<span class="badge badge-error badge-lg">Obrigatório</span>
</div>
<!-- Data e Hora Início -->
<div class="mb-6 p-4 bg-primary/5 rounded-lg border border-primary/10">
<h4 class="text-base font-semibold text-base-content mb-4 flex items-center gap-2">
<div class="w-3 h-3 rounded-full bg-primary shadow-sm"></div>
<div class="bg-primary/5 border-primary/10 mb-6 rounded-lg border p-4">
<h4
class="text-base-content mb-4 flex items-center gap-2 text-base font-semibold"
>
<div class="bg-primary h-3 w-3 rounded-full shadow-sm"></div>
Início do Período
</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Data Início</span>
@@ -633,16 +675,18 @@
<!-- Separador Visual -->
<div class="divider my-6">
<Clock class="h-5 w-5 text-base-content/40" />
<Clock class="text-base-content/40 h-5 w-5" />
</div>
<!-- Data e Hora Fim -->
<div class="p-4 bg-secondary/5 rounded-lg border border-secondary/10">
<h4 class="text-base font-semibold text-base-content mb-4 flex items-center gap-2">
<div class="w-3 h-3 rounded-full bg-secondary shadow-sm"></div>
<div class="bg-secondary/5 border-secondary/10 rounded-lg border p-4">
<h4
class="text-base-content mb-4 flex items-center gap-2 text-base font-semibold"
>
<div class="bg-secondary h-3 w-3 rounded-full shadow-sm"></div>
Fim do Período
</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Data Fim</span>
@@ -672,17 +716,28 @@
<!-- Preview do Período Calculado -->
{#if dataInicioAjuste && horaInicioAjuste && dataFimAjuste && horaFimAjuste}
{@const periodoCalculado = calcularPeriodo(dataInicioAjuste, horaInicioAjuste, dataFimAjuste, horaFimAjuste)}
{@const periodoCalculado = calcularPeriodo(
dataInicioAjuste,
horaInicioAjuste,
dataFimAjuste,
horaFimAjuste
)}
{#if periodoCalculado.dias > 0 || periodoCalculado.horas > 0 || periodoCalculado.minutos > 0}
<div class="mt-6 p-4 bg-info/10 rounded-lg border border-info/30 shadow-sm">
<div class="flex items-center gap-2 mb-2">
<Clock class="h-4 w-4 text-info" />
<div class="text-sm font-semibold text-info">Período Calculado</div>
<div class="bg-info/10 border-info/30 mt-6 rounded-lg border p-4 shadow-sm">
<div class="mb-2 flex items-center gap-2">
<Clock class="text-info h-4 w-4" />
<div class="text-info text-sm font-semibold">Período Calculado</div>
</div>
<div class="text-base font-bold text-base-content">
{periodoCalculado.dias > 0 ? `${periodoCalculado.dias} dia${periodoCalculado.dias > 1 ? 's' : ''} ` : ''}
{periodoCalculado.horas > 0 ? `${periodoCalculado.horas} hora${periodoCalculado.horas > 1 ? 's' : ''} ` : ''}
{periodoCalculado.minutos > 0 ? `${periodoCalculado.minutos} minuto${periodoCalculado.minutos > 1 ? 's' : ''}` : ''}
<div class="text-base-content text-base font-bold">
{periodoCalculado.dias > 0
? `${periodoCalculado.dias} dia${periodoCalculado.dias > 1 ? 's' : ''} `
: ''}
{periodoCalculado.horas > 0
? `${periodoCalculado.horas} hora${periodoCalculado.horas > 1 ? 's' : ''} `
: ''}
{periodoCalculado.minutos > 0
? `${periodoCalculado.minutos} minuto${periodoCalculado.minutos > 1 ? 's' : ''}`
: ''}
</div>
</div>
{/if}
@@ -691,19 +746,22 @@
</div>
<!-- Card: Motivo e Justificativa -->
<div class="card bg-base-50 border border-base-300 shadow-sm">
<div class="card bg-base-50 border-base-300 border shadow-sm">
<div class="card-body p-6">
<h3 class="text-lg font-semibold text-base-content mb-6 flex items-center gap-2">
<div class="w-1 h-6 bg-warning rounded-full"></div>
<h3 class="text-base-content mb-6 flex items-center gap-2 text-lg font-semibold">
<div class="bg-warning h-6 w-1 rounded-full"></div>
Motivo e Justificativa
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Tipo de Motivo</span>
<span class="label-text-alt text-error font-bold">*</span>
</label>
<select class="select select-bordered select-warning w-full" bind:value={motivoTipo}>
<select
class="select select-bordered select-warning w-full"
bind:value={motivoTipo}
>
<option value="">Selecione um tipo</option>
{#if motivos?.opcoesPadrao}
{#each motivos.opcoesPadrao as opcao}
@@ -729,10 +787,10 @@
</div>
<!-- Card: Observações -->
<div class="card bg-base-50 border border-base-300 shadow-sm">
<div class="card bg-base-50 border-base-300 border shadow-sm">
<div class="card-body p-6">
<h3 class="text-lg font-semibold text-base-content mb-4 flex items-center gap-2">
<div class="w-1 h-6 bg-warning rounded-full"></div>
<h3 class="text-base-content mb-4 flex items-center gap-2 text-lg font-semibold">
<div class="bg-warning h-6 w-1 rounded-full"></div>
Observações Adicionais
</h3>
<div class="form-control">
@@ -747,12 +805,12 @@
</div>
<!-- Botões de ação -->
<div class="flex gap-3 justify-end pt-6 border-t border-base-300">
<button class="btn btn-ghost gap-2 btn-lg" onclick={cancelar}>
<div class="border-base-300 flex justify-end gap-3 border-t pt-6">
<button class="btn btn-ghost btn-lg gap-2" onclick={cancelar}>
<X class="h-5 w-5" />
Cancelar
</button>
<button class="btn btn-warning gap-2 btn-lg shadow-lg" onclick={salvarAjuste}>
<button class="btn btn-warning btn-lg gap-2 shadow-lg" onclick={salvarAjuste}>
<Save class="h-5 w-5" />
Salvar Ajuste
</button>
@@ -765,7 +823,7 @@
<!-- Lista de Registros -->
{#if funcionarioSelecionado}
<div class="card bg-base-100 shadow-xl mb-6">
<div class="card bg-base-100 mb-6 shadow-xl">
<div class="card-body">
<h2 class="card-title mb-4">Registros do Funcionário</h2>
@@ -779,9 +837,9 @@
</div>
{:else}
<div class="overflow-x-auto">
<div class="max-h-[600px] overflow-y-auto border border-base-300 rounded-lg">
<table class="table table-zebra">
<thead class="sticky top-0 bg-base-200 z-10 shadow-sm">
<div class="border-base-300 max-h-[600px] overflow-y-auto rounded-lg border">
<table class="table-zebra table">
<thead class="bg-base-200 sticky top-0 z-10 shadow-sm">
<tr>
<th class="bg-base-200 whitespace-nowrap">Data</th>
<th class="bg-base-200 whitespace-nowrap">Tipo</th>
@@ -797,7 +855,9 @@
<td class="whitespace-nowrap">
{getTipoRegistroLabel(registro.tipo)}
</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">
<span
class="badge {registro.dentroDoPrazo ? 'badge-success' : 'badge-error'}"
@@ -831,11 +891,9 @@
<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>
<span class="text-base-content/70 text-sm font-normal"> - Funcionário selecionado </span>
{:else}
<span class="text-sm font-normal text-base-content/70">
<span class="text-base-content/70 text-sm font-normal">
- Todas as homologações do seu time
</span>
{/if}
@@ -850,134 +908,140 @@
<span>Nenhuma homologação encontrada</span>
</div>
{:else}
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<div class="overflow-x-auto">
<table class="table-zebra table">
<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>
{#if isGestor}
<th>Ações</th>
{/if}
</tr>
</thead>
<tbody>
{#each homologacoes as homologacao}
<tr>
<th>Data</th>
<td>
{new Date(homologacao.criadoEm).toLocaleDateString('pt-BR')}
</td>
{#if !funcionarioSelecionado}
<th>Funcionário</th>
<td>
<div class="flex items-center gap-3">
<UserAvatar
fotoPerfilUrl={homologacao.fotoPerfilUrl}
nome={homologacao.funcionario?.nome || 'Funcionário'}
size="sm"
/>
<div>
<div class="font-medium">{homologacao.funcionario?.nome || '-'}</div>
{#if homologacao.funcionario?.matricula}
<span class="text-base-content/70 text-xs">
Mat: {homologacao.funcionario.matricula}
</span>
{/if}
</div>
</div>
</td>
{/if}
<th>Tipo</th>
<th>Detalhes</th>
<th>Motivo</th>
<th>Observações</th>
<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="max-w-xs truncate text-sm" title={homologacao.observacoes || ''}>
{homologacao.observacoes || '-'}
</div>
</td>
{#if isGestor}
<th>Ações</th>
<td>
<div class="flex gap-1">
<button
class="btn btn-sm btn-outline btn-info btn-square hover:btn-info transition-transform hover:scale-110"
onclick={() => abrirDetalhes(homologacao._id)}
title="Ver detalhes"
>
<Eye class="h-4 w-4" />
</button>
{#if homologacao.registroId}
<button
class="btn btn-sm btn-square bg-primary/10 text-primary border-primary/20 hover:bg-primary/20 hover:border-primary/40 transition-all hover:shadow-md"
onclick={() => editarHomologacao(homologacao._id)}
title="Editar"
>
<Edit class="h-4 w-4" />
</button>
{/if}
<button
class="btn btn-sm btn-square bg-error/10 text-error border-error/20 hover:bg-error/20 hover:border-error/40 transition-all hover:shadow-md"
onclick={() => abrirModalExcluir(homologacao._id)}
title="Excluir"
>
<Trash2 class="h-4 w-4" />
</button>
</div>
</td>
{/if}
</tr>
</thead>
<tbody>
{#each homologacoes as homologacao}
<tr>
<td>
{new Date(homologacao.criadoEm).toLocaleDateString('pt-BR')}
</td>
{#if !funcionarioSelecionado}
<td>
<div class="flex items-center gap-3">
<UserAvatar
fotoPerfilUrl={homologacao.fotoPerfilUrl}
nome={homologacao.funcionario?.nome || 'Funcionário'}
size="sm"
/>
<div>
<div class="font-medium">{homologacao.funcionario?.nome || '-'}</div>
{#if homologacao.funcionario?.matricula}
<span class="text-xs text-base-content/70">
Mat: {homologacao.funcionario.matricula}
</span>
{/if}
</div>
</div>
</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>
{#if isGestor}
<td>
<div class="flex gap-1">
<button
class="btn btn-sm btn-outline btn-info btn-square hover:btn-info hover:scale-110 transition-transform"
onclick={() => abrirDetalhes(homologacao._id)}
title="Ver detalhes"
>
<Eye class="h-4 w-4" />
</button>
{#if homologacao.registroId}
<button
class="btn btn-sm btn-square bg-primary/10 text-primary border-primary/20 hover:bg-primary/20 hover:border-primary/40 hover:shadow-md transition-all"
onclick={() => editarHomologacao(homologacao._id)}
title="Editar"
>
<Edit class="h-4 w-4" />
</button>
{/if}
<button
class="btn btn-sm btn-square bg-error/10 text-error border-error/20 hover:bg-error/20 hover:border-error/40 hover:shadow-md transition-all"
onclick={() => abrirModalExcluir(homologacao._id)}
title="Excluir"
>
<Trash2 class="h-4 w-4" />
</button>
</div>
</td>
{/if}
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
</div>
<!-- Modal de Detalhes da Homologação -->
{#if mostrandoModalDetalhes && homologacaoSelecionada}
<div class="modal modal-open">
<div class="modal-box max-w-3xl">
<h3 class="font-bold text-lg mb-4">Detalhes da Homologação</h3>
<h3 class="mb-4 text-lg font-bold">Detalhes da Homologação</h3>
<div class="space-y-4">
<!-- Informações Gerais -->
<div class="card bg-base-200">
<div class="card-body p-4">
<h4 class="font-semibold mb-3">Informações Gerais</h4>
<h4 class="mb-3 font-semibold">Informações Gerais</h4>
<div class="grid grid-cols-2 gap-4 text-sm">
<div>
<span class="font-medium">Data:</span>{' '}
@@ -991,16 +1055,18 @@
</div>
<div>
<span class="font-medium">Funcionário:</span>
<div class="flex items-center gap-3 mt-2">
<div class="mt-2 flex items-center gap-3">
<UserAvatar
fotoPerfilUrl={homologacaoSelecionada.fotoPerfilUrl}
nome={homologacaoSelecionada.funcionario?.nome || 'Funcionário'}
size="sm"
/>
<div>
<div class="font-medium">{homologacaoSelecionada.funcionario?.nome || '-'}</div>
<div class="font-medium">
{homologacaoSelecionada.funcionario?.nome || '-'}
</div>
{#if homologacaoSelecionada.funcionario?.matricula}
<span class="text-xs text-base-content/70">
<span class="text-base-content/70 text-xs">
Mat: {homologacaoSelecionada.funcionario.matricula}
</span>
{/if}
@@ -1029,18 +1095,24 @@
{#if homologacaoSelecionada.registroId}
<div class="card bg-base-200">
<div class="card-body p-4">
<h4 class="font-semibold mb-3">Edição de Registro</h4>
<div class="text-sm space-y-2">
<h4 class="mb-3 font-semibold">Edição de Registro</h4>
<div class="space-y-2 text-sm">
<div>
<span class="font-medium">Horário Anterior:</span>{' '}
<span class="line-through opacity-70">
{formatarHoraPonto(homologacaoSelecionada.horaAnterior || 0, homologacaoSelecionada.minutoAnterior || 0)}
{formatarHoraPonto(
homologacaoSelecionada.horaAnterior || 0,
homologacaoSelecionada.minutoAnterior || 0
)}
</span>
</div>
<div>
<span class="font-medium">Horário Novo:</span>{' '}
<span>
{formatarHoraPonto(homologacaoSelecionada.horaNova || 0, homologacaoSelecionada.minutoNova || 0)}
{formatarHoraPonto(
homologacaoSelecionada.horaNova || 0,
homologacaoSelecionada.minutoNova || 0
)}
</span>
</div>
{#if homologacaoSelecionada.registro}
@@ -1059,15 +1131,16 @@
{:else if homologacaoSelecionada.tipoAjuste}
<div class="card bg-base-200">
<div class="card-body p-4">
<h4 class="font-semibold mb-3">Ajuste de Banco de Horas</h4>
<div class="text-sm space-y-2">
<h4 class="mb-3 font-semibold">Ajuste de Banco de Horas</h4>
<div class="space-y-2 text-sm">
<div>
<span class="font-medium">Tipo de Ajuste:</span>{' '}
{homologacaoSelecionada.tipoAjuste}
</div>
<div>
<span class="font-medium">Período:</span>{' '}
{homologacaoSelecionada.periodoDias || 0}d {homologacaoSelecionada.periodoHoras || 0}h{' '}
{homologacaoSelecionada.periodoDias || 0}d {homologacaoSelecionada.periodoHoras ||
0}h{' '}
{homologacaoSelecionada.periodoMinutos || 0}min
</div>
{#if homologacaoSelecionada.ajusteMinutos}
@@ -1086,8 +1159,8 @@
<!-- Motivo e Observações -->
<div class="card bg-base-200">
<div class="card-body p-4">
<h4 class="font-semibold mb-3">Motivo e Observações</h4>
<div class="text-sm space-y-2">
<h4 class="mb-3 font-semibold">Motivo e Observações</h4>
<div class="space-y-2 text-sm">
<div>
<span class="font-medium">Tipo de Motivo:</span>{' '}
{homologacaoSelecionada.motivoTipo || '-'}
@@ -1099,7 +1172,7 @@
{#if homologacaoSelecionada.observacoes}
<div>
<span class="font-medium">Observações:</span>
<div class="mt-1 p-2 bg-base-100 rounded">
<div class="bg-base-100 mt-1 rounded p-2">
{homologacaoSelecionada.observacoes}
</div>
</div>
@@ -1121,14 +1194,14 @@
{#if mostrandoModalExcluir && homologacaoParaExcluir}
<div class="modal modal-open">
<div class="modal-box">
<h3 class="font-bold text-lg mb-4">Confirmar Exclusão</h3>
<h3 class="mb-4 text-lg font-bold">Confirmar Exclusão</h3>
<p class="mb-4">
Tem certeza que deseja excluir esta homologação? Esta ação não pode ser desfeita.
</p>
<div class="modal-action">
<button class="btn btn-ghost" onclick={fecharModalExcluir}>Cancelar</button>
<button class="btn btn-error" onclick={excluirHomologacao}>
<Trash2 class="h-4 w-4 mr-2" />
<Trash2 class="mr-2 h-4 w-4" />
Excluir
</button>
</div>
@@ -1137,4 +1210,3 @@
</div>
{/if}
</div>