- Added regime de trabalho selection to employee forms for better categorization. - Updated backend validation to include regime de trabalho options for employees. - Enhanced employee data handling by integrating regime de trabalho into various components. - Removed the print modal for financial data to streamline the employee profile interface. - Improved overall code clarity and maintainability across multiple files.
407 lines
12 KiB
Svelte
407 lines
12 KiB
Svelte
<script lang="ts">
|
|
import { useQuery } from 'convex-svelte';
|
|
import { api } from '@sgse-app/backend/convex/_generated/api';
|
|
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
|
|
|
interface Props {
|
|
funcionarioId: Id<'funcionarios'>;
|
|
}
|
|
|
|
let { funcionarioId }: Props = $props();
|
|
|
|
// Queries
|
|
const saldosQuery = useQuery(api.saldoFerias.listarSaldos, { funcionarioId });
|
|
const solicitacoesQuery = useQuery(api.ferias.listarMinhasSolicitacoes, {
|
|
funcionarioId
|
|
});
|
|
|
|
const saldos = $derived(saldosQuery.data || []);
|
|
const solicitacoes = $derived(solicitacoesQuery.data || []);
|
|
|
|
// Estatísticas derivadas
|
|
const saldoAtual = $derived(saldos.find((s) => s.anoReferencia === new Date().getFullYear()));
|
|
const totalSolicitacoes = $derived(solicitacoes.length);
|
|
const aprovadas = $derived(
|
|
solicitacoes.filter((s) => s.status === 'aprovado' || s.status === 'data_ajustada_aprovada')
|
|
.length
|
|
);
|
|
const pendentes = $derived(
|
|
solicitacoes.filter((s) => s.status === 'aguardando_aprovacao').length
|
|
);
|
|
const reprovadas = $derived(solicitacoes.filter((s) => s.status === 'reprovado').length);
|
|
|
|
// Canvas para gráfico de pizza
|
|
let canvasSaldo = $state<HTMLCanvasElement>();
|
|
let canvasStatus = $state<HTMLCanvasElement>();
|
|
|
|
// Função para desenhar gráfico de pizza moderno
|
|
function desenharGraficoPizza(
|
|
canvas: HTMLCanvasElement,
|
|
dados: { label: string; valor: number; cor: string }[]
|
|
) {
|
|
const ctx = canvas.getContext('2d');
|
|
if (!ctx) return;
|
|
|
|
const width = canvas.width;
|
|
const height = canvas.height;
|
|
const centerX = width / 2;
|
|
const centerY = height / 2;
|
|
const radius = Math.min(width, height) / 2 - 20;
|
|
|
|
ctx.clearRect(0, 0, width, height);
|
|
|
|
const total = dados.reduce((acc, d) => acc + d.valor, 0);
|
|
if (total === 0) return;
|
|
|
|
let startAngle = -Math.PI / 2;
|
|
|
|
dados.forEach((item) => {
|
|
const sliceAngle = (2 * Math.PI * item.valor) / total;
|
|
|
|
// Desenhar fatia com sombra
|
|
ctx.save();
|
|
ctx.shadowColor = 'rgba(0, 0, 0, 0.2)';
|
|
ctx.shadowBlur = 15;
|
|
ctx.shadowOffsetX = 5;
|
|
ctx.shadowOffsetY = 5;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(centerX, centerY);
|
|
ctx.arc(centerX, centerY, radius, startAngle, startAngle + sliceAngle);
|
|
ctx.closePath();
|
|
|
|
ctx.fillStyle = item.cor;
|
|
ctx.fill();
|
|
|
|
ctx.restore();
|
|
|
|
// Desenhar borda branca
|
|
ctx.strokeStyle = '#ffffff';
|
|
ctx.lineWidth = 3;
|
|
ctx.stroke();
|
|
|
|
startAngle += sliceAngle;
|
|
});
|
|
|
|
// Desenhar círculo branco no centro (efeito donut)
|
|
ctx.beginPath();
|
|
ctx.arc(centerX, centerY, radius * 0.6, 0, 2 * Math.PI);
|
|
ctx.fillStyle = '#ffffff';
|
|
ctx.fill();
|
|
}
|
|
|
|
// Atualizar gráficos quando dados mudarem
|
|
$effect(() => {
|
|
if (canvasSaldo && saldoAtual) {
|
|
desenharGraficoPizza(canvasSaldo, [
|
|
{ label: 'Usado', valor: saldoAtual.diasUsados, cor: '#ff6b6b' },
|
|
{ label: 'Pendente', valor: saldoAtual.diasPendentes, cor: '#ffa94d' },
|
|
{
|
|
label: 'Disponível',
|
|
valor: saldoAtual.diasDisponiveis,
|
|
cor: '#51cf66'
|
|
}
|
|
]);
|
|
}
|
|
|
|
if (canvasStatus && totalSolicitacoes > 0) {
|
|
desenharGraficoPizza(canvasStatus, [
|
|
{ label: 'Aprovadas', valor: aprovadas, cor: '#51cf66' },
|
|
{ label: 'Pendentes', valor: pendentes, cor: '#ffa94d' },
|
|
{ label: 'Reprovadas', valor: reprovadas, cor: '#ff6b6b' }
|
|
]);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<div class="dashboard-ferias">
|
|
<!-- Header -->
|
|
<div class="mb-8">
|
|
<h1
|
|
class="from-primary to-secondary bg-linear-to-r bg-clip-text text-4xl font-bold text-transparent"
|
|
>
|
|
📊 Dashboard de Férias
|
|
</h1>
|
|
<p class="text-base-content/70 mt-2">Visualize seus saldos e histórico de solicitações</p>
|
|
</div>
|
|
|
|
{#if saldosQuery.isLoading || solicitacoesQuery.isLoading}
|
|
<!-- Loading Skeletons -->
|
|
<div class="mb-8 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
|
|
{#each Array(4)}
|
|
<div class="skeleton h-32 rounded-2xl"></div>
|
|
{/each}
|
|
</div>
|
|
{:else}
|
|
<!-- Cards de Estatísticas -->
|
|
<div class="mb-8 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
|
|
<!-- Card 1: Saldo Disponível -->
|
|
<div
|
|
class="stat from-success/20 to-success/5 border-success/30 rounded-2xl border-2 bg-linear-to-br shadow-2xl transition-all duration-300 hover:scale-105"
|
|
>
|
|
<div class="stat-figure text-success">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="inline-block h-10 w-10 stroke-current"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
></path>
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title text-success font-semibold">Disponível</div>
|
|
<div class="stat-value text-success text-4xl">
|
|
{saldoAtual?.diasDisponiveis || 0}
|
|
</div>
|
|
<div class="stat-desc text-success/70">dias para usar</div>
|
|
</div>
|
|
|
|
<!-- Card 2: Dias Usados -->
|
|
<div
|
|
class="stat from-error/20 to-error/5 border-error/30 rounded-2xl border-2 bg-linear-to-br shadow-2xl transition-all duration-300 hover:scale-105"
|
|
>
|
|
<div class="stat-figure text-error">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="inline-block h-10 w-10 stroke-current"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M6 18L18 6M6 6l12 12"
|
|
></path>
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title text-error font-semibold">Usado</div>
|
|
<div class="stat-value text-error text-4xl">
|
|
{saldoAtual?.diasUsados || 0}
|
|
</div>
|
|
<div class="stat-desc text-error/70">dias já gozados</div>
|
|
</div>
|
|
|
|
<!-- Card 3: Pendentes -->
|
|
<div
|
|
class="stat from-warning/20 to-warning/5 border-warning/30 rounded-2xl border-2 bg-linear-to-br shadow-2xl transition-all duration-300 hover:scale-105"
|
|
>
|
|
<div class="stat-figure text-warning">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="inline-block h-10 w-10 stroke-current"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
></path>
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title text-warning font-semibold">Pendentes</div>
|
|
<div class="stat-value text-warning text-4xl">
|
|
{saldoAtual?.diasPendentes || 0}
|
|
</div>
|
|
<div class="stat-desc text-warning/70">aguardando aprovação</div>
|
|
</div>
|
|
|
|
<!-- Card 4: Total de Direito -->
|
|
<div
|
|
class="stat from-primary/20 to-primary/5 border-primary/30 rounded-2xl border-2 bg-linear-to-br shadow-2xl transition-all duration-300 hover:scale-105"
|
|
>
|
|
<div class="stat-figure text-primary">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="inline-block h-10 w-10 stroke-current"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M13 10V3L4 14h7v7l9-11h-7z"
|
|
></path>
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title text-primary font-semibold">Total Direito</div>
|
|
<div class="stat-value text-primary text-4xl">
|
|
{saldoAtual?.diasDireito || 0}
|
|
</div>
|
|
<div class="stat-desc text-primary/70">dias no ano</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Gráficos -->
|
|
<div class="mb-8 grid grid-cols-1 gap-8 lg:grid-cols-2">
|
|
<!-- Gráfico 1: Distribuição de Saldo -->
|
|
<div class="card bg-base-100 border-base-300 border-2 shadow-2xl">
|
|
<div class="card-body">
|
|
<h2 class="card-title mb-4 text-2xl">
|
|
🥧 Distribuição de Saldo
|
|
<div class="badge badge-primary badge-lg">
|
|
Ano {saldoAtual?.anoReferencia || new Date().getFullYear()}
|
|
</div>
|
|
</h2>
|
|
|
|
{#if saldoAtual}
|
|
<div class="flex items-center justify-center">
|
|
<canvas bind:this={canvasSaldo} width="300" height="300" class="max-w-full"></canvas>
|
|
</div>
|
|
|
|
<!-- Legenda -->
|
|
<div class="mt-4 flex flex-wrap justify-center gap-4">
|
|
<div class="flex items-center gap-2">
|
|
<div class="h-4 w-4 rounded-full bg-[#51cf66]"></div>
|
|
<span class="text-sm font-semibold"
|
|
>Disponível: {saldoAtual.diasDisponiveis} dias</span
|
|
>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<div class="h-4 w-4 rounded-full bg-[#ffa94d]"></div>
|
|
<span class="text-sm font-semibold">Pendente: {saldoAtual.diasPendentes} dias</span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<div class="h-4 w-4 rounded-full bg-[#ff6b6b]"></div>
|
|
<span class="text-sm font-semibold">Usado: {saldoAtual.diasUsados} dias</span>
|
|
</div>
|
|
</div>
|
|
{:else}
|
|
<div class="alert alert-info">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="h-6 w-6 shrink-0 stroke-current"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
></path>
|
|
</svg>
|
|
<span>Nenhum saldo disponível para o ano atual</span>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Gráfico 2: Status de Solicitações -->
|
|
<div class="card bg-base-100 border-base-300 border-2 shadow-2xl">
|
|
<div class="card-body">
|
|
<h2 class="card-title mb-4 text-2xl">
|
|
📋 Status de Solicitações
|
|
<div class="badge badge-secondary badge-lg">
|
|
Total: {totalSolicitacoes}
|
|
</div>
|
|
</h2>
|
|
|
|
{#if totalSolicitacoes > 0}
|
|
<div class="flex items-center justify-center">
|
|
<canvas bind:this={canvasStatus} width="300" height="300" class="max-w-full"></canvas>
|
|
</div>
|
|
|
|
<!-- Legenda -->
|
|
<div class="mt-4 flex flex-wrap justify-center gap-4">
|
|
<div class="flex items-center gap-2">
|
|
<div class="h-4 w-4 rounded-full bg-[#51cf66]"></div>
|
|
<span class="text-sm font-semibold">Aprovadas: {aprovadas}</span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<div class="h-4 w-4 rounded-full bg-[#ffa94d]"></div>
|
|
<span class="text-sm font-semibold">Pendentes: {pendentes}</span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<div class="h-4 w-4 rounded-full bg-[#ff6b6b]"></div>
|
|
<span class="text-sm font-semibold">Reprovadas: {reprovadas}</span>
|
|
</div>
|
|
</div>
|
|
{:else}
|
|
<div class="alert alert-info">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="h-6 w-6 shrink-0 stroke-current"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
></path>
|
|
</svg>
|
|
<span>Nenhuma solicitação de férias ainda</span>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Histórico de Saldos -->
|
|
{#if saldos.length > 0}
|
|
<div class="card bg-base-100 border-base-300 border-2 shadow-2xl">
|
|
<div class="card-body">
|
|
<h2 class="card-title mb-4 text-2xl">📅 Histórico de Saldos</h2>
|
|
|
|
<div class="overflow-x-auto">
|
|
<table class="table-zebra table">
|
|
<thead>
|
|
<tr>
|
|
<th>Ano</th>
|
|
<th>Direito</th>
|
|
<th>Usado</th>
|
|
<th>Pendente</th>
|
|
<th>Disponível</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{#each saldos as saldo (saldo._id)}
|
|
<tr>
|
|
<td class="font-bold">{saldo.anoReferencia}</td>
|
|
<td>{saldo.diasDireito} dias</td>
|
|
<td><span class="badge badge-error">{saldo.diasUsados}</span></td>
|
|
<td><span class="badge badge-warning">{saldo.diasPendentes}</span></td>
|
|
<td><span class="badge badge-success">{saldo.diasDisponiveis}</span></td>
|
|
<td>
|
|
{#if saldo.status === 'ativo'}
|
|
<span class="badge badge-success">Ativo</span>
|
|
{:else if saldo.status === 'vencido'}
|
|
<span class="badge badge-error">Vencido</span>
|
|
{:else}
|
|
<span class="badge badge-neutral">Concluído</span>
|
|
{/if}
|
|
</td>
|
|
</tr>
|
|
{/each}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
.bg-clip-text {
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
}
|
|
|
|
canvas {
|
|
image-rendering: -webkit-optimize-contrast;
|
|
image-rendering: crisp-edges;
|
|
}
|
|
</style>
|