fix(): Ajustes para deploy
This commit is contained in:
parent
ff3ff6c2e0
commit
f09b152192
2 changed files with 74 additions and 392 deletions
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue