|
|
|
|
@@ -6,9 +6,10 @@
|
|
|
|
|
const client = useConvexClient();
|
|
|
|
|
|
|
|
|
|
type Row = { _id: string; nome: string; valor: number; count: number };
|
|
|
|
|
let rows: Array<Row> = [];
|
|
|
|
|
let isLoading = true;
|
|
|
|
|
let notice: { kind: "error" | "success"; text: string } | null = null;
|
|
|
|
|
let rows: Array<Row> = $state<Array<Row>>([]);
|
|
|
|
|
let isLoading = $state(true);
|
|
|
|
|
let notice = $state<{ kind: "error" | "success"; text: string } | null>(null);
|
|
|
|
|
let containerWidth = $state(1200);
|
|
|
|
|
|
|
|
|
|
onMount(async () => {
|
|
|
|
|
try {
|
|
|
|
|
@@ -29,9 +30,25 @@
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let chartWidth = 900;
|
|
|
|
|
let chartHeight = 400;
|
|
|
|
|
const padding = { top: 40, right: 30, bottom: 100, left: 80 };
|
|
|
|
|
// Dimensões responsivas
|
|
|
|
|
$effect(() => {
|
|
|
|
|
const updateSize = () => {
|
|
|
|
|
const container = document.querySelector('.chart-container');
|
|
|
|
|
if (container) {
|
|
|
|
|
containerWidth = Math.min(container.clientWidth - 32, 1200);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
updateSize();
|
|
|
|
|
window.addEventListener('resize', updateSize);
|
|
|
|
|
|
|
|
|
|
return () => window.removeEventListener('resize', updateSize);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const chartHeight = 350;
|
|
|
|
|
const padding = { top: 20, right: 20, bottom: 80, left: 70 };
|
|
|
|
|
|
|
|
|
|
let chartWidth = $derived(containerWidth);
|
|
|
|
|
|
|
|
|
|
function getMax<T>(arr: Array<T>, sel: (t: T) => number): number {
|
|
|
|
|
let m = 0;
|
|
|
|
|
@@ -67,19 +84,19 @@
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<div class="container mx-auto px-4 py-6 space-y-6">
|
|
|
|
|
<div class="container mx-auto px-4 py-6 max-w-7xl">
|
|
|
|
|
<!-- Breadcrumb -->
|
|
|
|
|
<div class="text-sm breadcrumbs">
|
|
|
|
|
<div class="text-sm breadcrumbs mb-4">
|
|
|
|
|
<ul>
|
|
|
|
|
<li><a href="/">Dashboard</a></li>
|
|
|
|
|
<li><a href="/recursos-humanos">Recursos Humanos</a></li>
|
|
|
|
|
<li><a href="/recursos-humanos/funcionarios">Funcionários</a></li>
|
|
|
|
|
<li class="font-semibold">Relatórios</li>
|
|
|
|
|
<li><a href="/" class="hover:text-primary">Dashboard</a></li>
|
|
|
|
|
<li><a href="/recursos-humanos" class="hover:text-primary">Recursos Humanos</a></li>
|
|
|
|
|
<li><a href="/recursos-humanos/funcionarios" class="hover:text-primary">Funcionários</a></li>
|
|
|
|
|
<li class="font-semibold text-primary">Relatórios</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Header -->
|
|
|
|
|
<div class="flex items-center gap-4 mb-6">
|
|
|
|
|
<div class="flex items-center gap-4 mb-8">
|
|
|
|
|
<div class="p-3 bg-primary/10 rounded-xl">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
|
|
|
@@ -87,37 +104,40 @@
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h1 class="text-3xl font-bold text-base-content">Relatórios de Funcionários</h1>
|
|
|
|
|
<p class="text-base-content/60">Análise de distribuição de salários e funcionários por símbolo</p>
|
|
|
|
|
<p class="text-base-content/60 mt-1">Análise de distribuição de salários e funcionários por símbolo</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{#if notice}
|
|
|
|
|
<div class="alert" class:alert-error={notice.kind === "error"} class:alert-success={notice.kind === "success"}>
|
|
|
|
|
<div class="alert mb-6" class:alert-error={notice.kind === "error"} class:alert-success={notice.kind === "success"}>
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6">
|
|
|
|
|
<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>{notice.text}</span>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
{#if isLoading}
|
|
|
|
|
<div class="flex justify-center items-center py-12">
|
|
|
|
|
<span class="loading loading-spinner loading-lg"></span>
|
|
|
|
|
<div class="flex justify-center items-center py-20">
|
|
|
|
|
<span class="loading loading-spinner loading-lg text-primary"></span>
|
|
|
|
|
</div>
|
|
|
|
|
{:else}
|
|
|
|
|
<div class="grid gap-6">
|
|
|
|
|
<div class="space-y-6 chart-container">
|
|
|
|
|
<!-- Gráfico 1: Símbolo x Salário (Valor) - Layer Chart -->
|
|
|
|
|
<div class="card bg-gradient-to-br from-base-100 to-base-200 shadow-xl border border-base-300">
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
<div class="flex items-center gap-3 mb-4">
|
|
|
|
|
<div class="p-2 bg-primary/10 rounded-lg">
|
|
|
|
|
<div class="card bg-base-100 shadow-lg hover:shadow-xl transition-shadow border border-base-300">
|
|
|
|
|
<div class="card-body p-6">
|
|
|
|
|
<div class="flex items-center gap-3 mb-6">
|
|
|
|
|
<div class="p-2.5 bg-primary/10 rounded-lg">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h3 class="text-xl font-bold text-base-content">Distribuição de Salários por Símbolo</h3>
|
|
|
|
|
<p class="text-sm text-base-content/60">Valores dos símbolos cadastrados no sistema</p>
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
<h3 class="text-lg font-bold text-base-content">Distribuição de Salários por Símbolo</h3>
|
|
|
|
|
<p class="text-sm text-base-content/60 mt-0.5">Valores dos símbolos cadastrados no sistema</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="w-full overflow-x-auto bg-base-100 rounded-lg p-4">
|
|
|
|
|
<div class="w-full overflow-x-auto bg-base-200/30 rounded-xl p-4">
|
|
|
|
|
<svg width={chartWidth} height={chartHeight} role="img" aria-label="Gráfico de área: salário por símbolo">
|
|
|
|
|
{#if rows.length === 0}
|
|
|
|
|
<text x="16" y="32" class="opacity-60">Sem dados</text>
|
|
|
|
|
@@ -191,20 +211,20 @@
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Gráfico 2: Quantidade de Funcionários por Símbolo - Layer Chart -->
|
|
|
|
|
<div class="card bg-gradient-to-br from-base-100 to-base-200 shadow-xl border border-base-300">
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
<div class="flex items-center gap-3 mb-4">
|
|
|
|
|
<div class="p-2 bg-secondary/10 rounded-lg">
|
|
|
|
|
<div class="card bg-base-100 shadow-lg hover:shadow-xl transition-shadow border border-base-300">
|
|
|
|
|
<div class="card-body p-6">
|
|
|
|
|
<div class="flex items-center gap-3 mb-6">
|
|
|
|
|
<div class="p-2.5 bg-secondary/10 rounded-lg">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-secondary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h3 class="text-xl font-bold text-base-content">Distribuição de Funcionários por Símbolo</h3>
|
|
|
|
|
<p class="text-sm text-base-content/60">Quantidade de funcionários alocados em cada símbolo</p>
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
<h3 class="text-lg font-bold text-base-content">Distribuição de Funcionários por Símbolo</h3>
|
|
|
|
|
<p class="text-sm text-base-content/60 mt-0.5">Quantidade de funcionários alocados em cada símbolo</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="w-full overflow-x-auto bg-base-100 rounded-lg p-4">
|
|
|
|
|
<div class="w-full overflow-x-auto bg-base-200/30 rounded-xl p-4">
|
|
|
|
|
<svg width={chartWidth} height={chartHeight} role="img" aria-label="Gráfico de área: quantidade por símbolo">
|
|
|
|
|
{#if rows.length === 0}
|
|
|
|
|
<text x="16" y="32" class="opacity-60">Sem dados</text>
|
|
|
|
|
@@ -276,8 +296,71 @@
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Tabela Resumo -->
|
|
|
|
|
<div class="card bg-base-100 shadow-lg border border-base-300">
|
|
|
|
|
<div class="card-body p-6">
|
|
|
|
|
<div class="flex items-center gap-3 mb-6">
|
|
|
|
|
<div class="p-2.5 bg-accent/10 rounded-lg">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-accent" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
<h3 class="text-lg font-bold text-base-content">Tabela Resumo - Símbolos e Funcionários</h3>
|
|
|
|
|
<p class="text-sm text-base-content/60 mt-0.5">Visão detalhada dos dados apresentados nos gráficos</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="overflow-x-auto">
|
|
|
|
|
<table class="table table-zebra">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th class="bg-base-200">Símbolo</th>
|
|
|
|
|
<th class="bg-base-200 text-right">Valor (R$)</th>
|
|
|
|
|
<th class="bg-base-200 text-right">Funcionários</th>
|
|
|
|
|
<th class="bg-base-200 text-right">Total (R$)</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
{#if rows.length === 0}
|
|
|
|
|
<tr>
|
|
|
|
|
<td colspan="4" class="text-center text-base-content/60 py-8">Nenhum dado disponível</td>
|
|
|
|
|
</tr>
|
|
|
|
|
{:else}
|
|
|
|
|
{#each rows as row}
|
|
|
|
|
<tr class="hover">
|
|
|
|
|
<td class="font-semibold">{row.nome}</td>
|
|
|
|
|
<td class="text-right font-mono">
|
|
|
|
|
{row.valor.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
|
|
|
|
</td>
|
|
|
|
|
<td class="text-right">
|
|
|
|
|
<span class="badge badge-primary badge-outline">{row.count}</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="text-right font-mono font-semibold text-primary">
|
|
|
|
|
{(row.valor * row.count).toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
{/each}
|
|
|
|
|
<!-- Total Geral -->
|
|
|
|
|
<tr class="font-bold bg-base-200 border-t-2 border-base-300">
|
|
|
|
|
<td>TOTAL GERAL</td>
|
|
|
|
|
<td class="text-right font-mono">
|
|
|
|
|
{rows.reduce((sum, r) => sum + r.valor, 0).toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
|
|
|
|
</td>
|
|
|
|
|
<td class="text-right">
|
|
|
|
|
<span class="badge badge-primary">{rows.reduce((sum, r) => sum + r.count, 0)}</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="text-right font-mono text-primary text-lg">
|
|
|
|
|
{rows.reduce((sum, r) => sum + (r.valor * r.count), 0).toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
{/if}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|