fix(): Ajustes para deploy

This commit is contained in:
Keven 2025-12-08 10:26:15 -03:00
parent ff3ff6c2e0
commit f09b152192
2 changed files with 74 additions and 392 deletions

View file

@ -1,278 +0,0 @@
'use client'
import React, { useMemo, useState } from "react";
import {
Card,
CardTitle,
CardContent,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import {
PieChart,
Pie,
Cell,
ResponsiveContainer,
BarChart,
Bar,
XAxis,
YAxis,
Tooltip,
Legend,
} from "recharts";
// Função de agregação
function sampleAggregations(data) {
const bySexo: Record<string, number> = {};
const byUF: Record<string, number> = {};
const byProf: Record<string, number> = {};
const byEstCivil: Record<string, number> = {};
const byInstr: Record<string, number> = {};
const docsExpiry: any[] = [];
const now = new Date();
for (const p of data) {
const sexo = p.SEXO || "Não informado";
bySexo[sexo] = (bySexo[sexo] || 0) + 1;
const uf = p.UF_RESIDENCIA || p.UF || "Não informado";
byUF[uf] = (byUF[uf] || 0) + 1;
const prof = p.TB_PROFISSAO_ID || "Outros";
byProf[prof] = (byProf[prof] || 0) + 1;
const ec = p.TB_ESTADOCIVIL_ID || "Não informado";
byEstCivil[ec] = (byEstCivil[ec] || 0) + 1;
const gi = p.GRAU_INSTRUCAO || "Não informado";
byInstr[gi] = (byInstr[gi] || 0) + 1;
if (p.DOCUMENTO_VALIDADE) {
const dv = new Date(p.DOCUMENTO_VALIDADE);
const days = Math.ceil((dv.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
docsExpiry.push({
id: p.PESSOA_ID || p.DOCUMENTO_NUMERO || Math.random(),
days,
date: p.DOCUMENTO_VALIDADE
});
}
}
return {
sexo: Object.entries(bySexo).map(([key, value]) => ({ name: key, value })),
uf: Object.entries(byUF).map(([key, value]) => ({ name: key, value })),
prof: Object.entries(byProf)
.map(([key, value]) => ({ name: String(key), value }))
.sort((a, b) => b.value - a.value)
.slice(0, 10),
estCivil: Object.entries(byEstCivil).map(([key, value]) => ({ name: key, value })),
instr: Object.entries(byInstr).map(([key, value]) => ({ name: key, value })),
docsExpiry,
};
}
// Paleta
const COLORS = [
"#4F46E5",
"#06B6D4",
"#F59E0B",
"#EF4444",
"#10B981",
"#8B5CF6",
"#F97316",
"#6366F1",
"#EC4899",
"#334155",
];
export default function PessoasDashboard({ dataset = null }) {
const mock = useMemo(() => {
if (dataset) return dataset;
// Mock realista
const nomes = ["Ana Clara", "Bruno Silva", "Carlos Souza", "Daniela Oliveira", "Eduardo Lima", "Fernanda Rocha", "Gustavo Alves", "Helena Martins"];
const sexos = ["Masculino", "Feminino", "Outro"];
const ufs = ["São Paulo", "Rio de Janeiro", "Minas Gerais", "Goiás", "Distrito Federal"];
const profs = ["Advogado", "Professor", "Agricultor", "Engenheiro Civil", "Estudante Universitário", "Médico", "Enfermeiro", "Empresário"];
const estc = ["Solteiro(a)", "Casado(a)", "Divorciado(a)", "Viúvo(a)", "União Estável"];
const instrucao = ["Ensino Fundamental", "Ensino Médio", "Ensino Superior", "Pós-Graduação", "Mestrado", "Doutorado"];
const arr: any[] = [];
for (let i = 0; i < 200; i++) {
const birth = new Date(1960 + Math.floor(Math.random() * 50), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1);
const docVal = new Date();
docVal.setFullYear(docVal.getFullYear() + Math.floor(Math.random() * 5));
arr.push({
PESSOA_ID: i + 1,
NOME: nomes[Math.floor(Math.random() * nomes.length)],
SEXO: sexos[Math.floor(Math.random() * sexos.length)],
UF_RESIDENCIA: ufs[Math.floor(Math.random() * ufs.length)],
TB_PROFISSAO_ID: profs[Math.floor(Math.random() * profs.length)],
TB_ESTADOCIVIL_ID: estc[Math.floor(Math.random() * estc.length)],
GRAU_INSTRUCAO: instrucao[Math.floor(Math.random() * instrucao.length)],
DOCUMENTO_VALIDADE: docVal.toISOString().slice(0, 10),
DATA_NASCIMENTO: birth.toISOString().slice(0, 10),
});
}
return arr;
}, [dataset]);
const aggregations = useMemo(() => sampleAggregations(mock), [mock]);
return (
<div className="p-6 space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-semibold">Dashboard de Pessoas</h1>
<div className="flex gap-2">
<Button onClick={() => {}}>Exportar CSV</Button>
<Button variant="ghost" onClick={() => window.location.reload()}>Atualizar</Button>
</div>
</div>
{/* Linha 1 */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
<Card>
<CardTitle>Distribuição por Sexo</CardTitle>
<CardContent className="h-64 p-4">
<ResponsiveContainer>
<PieChart>
<Pie data={aggregations.sexo} dataKey="value" nameKey="name" outerRadius={80} label>
{aggregations.sexo.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip />
</PieChart>
</ResponsiveContainer>
</CardContent>
</Card>
<Card>
<CardTitle>Idade (faixas etárias)</CardTitle>
<CardContent className="h-64 p-4">
<ResponsiveContainer>
<BarChart data={(() => {
const now = new Date();
const ages = mock.map((p) => {
const d = p.DATA_NASCIMENTO ? new Date(p.DATA_NASCIMENTO) : null;
if (!d) return null;
return Math.floor((now.getTime() - d.getTime()) / (1000 * 60 * 60 * 24 * 365.25));
}).filter(Boolean) as number[];
const buckets = ["0-17","18-25","26-35","36-45","46-60","61+"];
const counts: Record<string, number> = {"0-17":0,"18-25":0,"26-35":0,"36-45":0,"46-60":0,"61+":0};
for (const a of ages) {
if (a<=17) counts['0-17']++;
else if (a<=25) counts['18-25']++;
else if (a<=35) counts['26-35']++;
else if (a<=45) counts['36-45']++;
else if (a<=60) counts['46-60']++;
else counts['61+']++;
}
return buckets.map(b => ({ bucket: b, value: counts[b]}));
})()}>
<XAxis dataKey="bucket" />
<YAxis />
<Tooltip />
<Bar dataKey="value" fill={COLORS[0]} />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
<Card>
<CardTitle>Top Profissões</CardTitle>
<CardContent className="h-64 p-4">
<ResponsiveContainer>
<BarChart data={aggregations.prof} layout="vertical">
<XAxis type="number" />
<YAxis dataKey="name" type="category" />
<Tooltip />
<Bar dataKey="value" fill={COLORS[3]} />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
</div>
{/* Linha 2 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<Card>
<CardTitle>Distribuição por Unidade Federativa</CardTitle>
<CardContent className="h-72 p-4">
<ResponsiveContainer>
<BarChart data={aggregations.uf}>
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Bar dataKey="value" fill={COLORS[1]} />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
<Card>
<CardTitle>Documentos próximos ao vencimento</CardTitle>
<CardContent className="h-72 p-4 overflow-auto">
<ul className="text-sm space-y-2">
{aggregations.docsExpiry
.sort((a,b) => a.days - b.days)
.slice(0, 15)
.map((d) => (
<li key={d.id} className="flex justify-between">
<div>Documento #{d.id}</div>
<div>{d.date} ({d.days} dias)</div>
</li>
))}
</ul>
</CardContent>
</Card>
</div>
{/* Linha 3 */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
<Card>
<CardTitle>Estado Civil</CardTitle>
<CardContent className="h-56 p-4">
<ResponsiveContainer>
<PieChart>
<Pie data={aggregations.estCivil} dataKey="value" nameKey="name" outerRadius={70} label />
<Tooltip />
</PieChart>
</ResponsiveContainer>
</CardContent>
</Card>
<Card>
<CardTitle>Grau de Instrução</CardTitle>
<CardContent className="h-56 p-4">
<ResponsiveContainer>
<BarChart data={aggregations.instr}>
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Bar dataKey="value" fill={COLORS[2]} />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
<Card>
<CardTitle>Documentos por Tipo (placeholder)</CardTitle>
<CardContent className="h-56 p-4">
<ResponsiveContainer>
<BarChart data={aggregations.prof /* trocar por tipos de documento no dataset real */}>
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Bar dataKey="value" fill={COLORS[4]} />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
</div>
<div className="mt-6">
<small className="text-muted text-sm">
Dica: ao conectar seu dataset real, ajuste o mapeamento em <code>sampleAggregations</code> (por exemplo, <b>TB_PROFISSAO_ID nome da profissão</b>, <b>TB_ESTADOCIVIL_ID descrição</b>).
</small>
</div>
</div>
);
}

View file

@ -1,132 +1,92 @@
import * as React from "react"
import * as React from "react"
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils"
function Card({ className, ...props }: React.ComponentProps<"div">) {
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
)}
{...props}
/>
)
className
)}
{...props}
/>
)
}
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className
)}
{...props}
/>
)
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className
)}
{...props}
/>
)
}
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
className={cn("leading-none font-semibold", className)}
{...props}
/>
)
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props}
/>
)
}
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
)
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
)
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
)
}
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}