fix: remoção dos icones

This commit is contained in:
Alessandro Gonçaalves 2025-10-20 17:35:24 -03:00
parent dd5f92825c
commit 35aca238ea
2 changed files with 543 additions and 310 deletions

View File

@ -1,4 +1,4 @@
'use client'; "use client";
import * as React from "react"; import * as React from "react";
import { import {
@ -25,7 +25,7 @@ import {
SelectItem, SelectItem,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Download, Filter, X } from "lucide-react"; import { Download, Filter, X } from "lucide-react";
import * as XLSX from 'xlsx'; import * as XLSX from "xlsx";
interface AnaliticoItem { interface AnaliticoItem {
codigo_grupo: string; codigo_grupo: string;
@ -66,7 +66,9 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
const [globalFilter, setGlobalFilter] = React.useState(""); const [globalFilter, setGlobalFilter] = React.useState("");
const [columnFilters, setColumnFilters] = React.useState<any[]>([]); const [columnFilters, setColumnFilters] = React.useState<any[]>([]);
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const [conditions, setConditions] = React.useState([{ column: "", operator: "contains", value: "" }]); const [conditions, setConditions] = React.useState([
{ column: "", operator: "contains", value: "" },
]);
const fetchData = React.useCallback(async () => { const fetchData = React.useCallback(async () => {
// Só faz a requisição se tiver dataInicio e dataFim // Só faz a requisição se tiver dataInicio e dataFim
@ -93,10 +95,10 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
const result = await response.json(); const result = await response.json();
setData(result as AnaliticoItem[]); setData(result as AnaliticoItem[]);
} else { } else {
console.error('Erro ao buscar dados:', await response.text()); console.error("Erro ao buscar dados:", await response.text());
} }
} catch (error) { } catch (error) {
console.error('Erro ao buscar dados:', error); console.error("Erro ao buscar dados:", error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -114,8 +116,8 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
filterFn: "advancedText", filterFn: "advancedText",
cell: ({ getValue }: { getValue: () => string }) => { cell: ({ getValue }: { getValue: () => string }) => {
const value = getValue(); const value = getValue();
return new Date(value).toLocaleDateString('pt-BR'); return new Date(value).toLocaleDateString("pt-BR");
} },
}, },
{ {
accessorKey: "data_vencimento", accessorKey: "data_vencimento",
@ -123,8 +125,8 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
filterFn: "advancedText", filterFn: "advancedText",
cell: ({ getValue }: { getValue: () => string }) => { cell: ({ getValue }: { getValue: () => string }) => {
const value = getValue(); const value = getValue();
return new Date(value).toLocaleDateString('pt-BR'); return new Date(value).toLocaleDateString("pt-BR");
} },
}, },
{ {
accessorKey: "data_caixa", accessorKey: "data_caixa",
@ -132,13 +134,29 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
filterFn: "advancedText", filterFn: "advancedText",
cell: ({ getValue }: { getValue: () => string }) => { cell: ({ getValue }: { getValue: () => string }) => {
const value = getValue(); const value = getValue();
return new Date(value).toLocaleDateString('pt-BR'); return new Date(value).toLocaleDateString("pt-BR");
} },
},
{
accessorKey: "codigo_fornecedor",
header: "Cód. Fornec.",
filterFn: "advancedText",
},
{
accessorKey: "nome_fornecedor",
header: "Fornecedor",
filterFn: "advancedText",
},
{
accessorKey: "codigo_centrocusto",
header: "Cód. Centro",
filterFn: "advancedText",
},
{
accessorKey: "codigo_conta",
header: "Cód. Conta",
filterFn: "advancedText",
}, },
{ accessorKey: "codigo_fornecedor", header: "Cód. Fornec.", filterFn: "advancedText" },
{ accessorKey: "nome_fornecedor", header: "Fornecedor", filterFn: "advancedText" },
{ accessorKey: "codigo_centrocusto", header: "Cód. Centro", filterFn: "advancedText" },
{ accessorKey: "codigo_conta", header: "Cód. Conta", filterFn: "advancedText" },
{ accessorKey: "conta", header: "Conta", filterFn: "advancedText" }, { accessorKey: "conta", header: "Conta", filterFn: "advancedText" },
{ {
accessorKey: "valor", accessorKey: "valor",
@ -146,20 +164,28 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
filterFn: "advancedText", filterFn: "advancedText",
cell: ({ getValue }: { getValue: () => number }) => { cell: ({ getValue }: { getValue: () => number }) => {
const value = getValue(); const value = getValue();
const formatted = new Intl.NumberFormat('pt-BR', { const formatted = new Intl.NumberFormat("pt-BR", {
style: 'currency', style: "currency",
currency: 'BRL', currency: "BRL",
}).format(value); }).format(value);
const isNegative = value < 0; const isNegative = value < 0;
return ( return (
<span className={isNegative ? 'text-red-600' : 'text-gray-900'}> <span className={isNegative ? "text-red-600" : "text-gray-900"}>
{formatted} {formatted}
</span> </span>
); );
}
}, },
{ accessorKey: "historico", header: "Histórico", filterFn: "advancedText" }, },
{ accessorKey: "historico2", header: "Histórico 2", filterFn: "advancedText" }, {
accessorKey: "historico",
header: "Histórico",
filterFn: "advancedText",
},
{
accessorKey: "historico2",
header: "Histórico 2",
filterFn: "advancedText",
},
{ accessorKey: "recnum", header: "Recnum", filterFn: "advancedText" }, { accessorKey: "recnum", header: "Recnum", filterFn: "advancedText" },
], ],
[] []
@ -204,7 +230,6 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
[] []
); );
const table = useReactTable({ const table = useReactTable({
data, data,
columns, columns,
@ -227,7 +252,6 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
const virtualRows = rowVirtualizer.getVirtualItems(); const virtualRows = rowVirtualizer.getVirtualItems();
const applyFilters = () => { const applyFilters = () => {
// Agrupar múltiplas condições por coluna // Agrupar múltiplas condições por coluna
const grouped: Record<string, any[]> = {}; const grouped: Record<string, any[]> = {};
@ -235,7 +259,9 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
conditions.forEach((c) => { conditions.forEach((c) => {
if ( if (
c.column && c.column &&
(c.operator === "empty" || c.operator === "notEmpty" || (c.value ?? "") !== "") (c.operator === "empty" ||
c.operator === "notEmpty" ||
(c.value ?? "") !== "")
) { ) {
if (!grouped[c.column]) grouped[c.column] = []; if (!grouped[c.column]) grouped[c.column] = [];
grouped[c.column].push({ operator: c.operator, value: c.value }); grouped[c.column].push({ operator: c.operator, value: c.value });
@ -260,7 +286,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
const totalValor = data.reduce((sum, item) => { const totalValor = data.reduce((sum, item) => {
const valor = const valor =
typeof item.valor === 'string' ? parseFloat(item.valor) : item.valor; typeof item.valor === "string" ? parseFloat(item.valor) : item.valor;
return sum + (isNaN(valor) ? 0 : valor); return sum + (isNaN(valor) ? 0 : valor);
}, 0); }, 0);
@ -269,23 +295,23 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
// Preparar dados para exportação // Preparar dados para exportação
const exportData = data.map((item) => ({ const exportData = data.map((item) => ({
'Data Competência': new Date(item.data_competencia).toLocaleDateString( "Data Competência": new Date(item.data_competencia).toLocaleDateString(
'pt-BR' "pt-BR"
), ),
'Data Vencimento': new Date(item.data_vencimento).toLocaleDateString( "Data Vencimento": new Date(item.data_vencimento).toLocaleDateString(
'pt-BR' "pt-BR"
), ),
'Data Caixa': new Date(item.data_caixa).toLocaleDateString('pt-BR'), "Data Caixa": new Date(item.data_caixa).toLocaleDateString("pt-BR"),
'Código Fornecedor': item.codigo_fornecedor, "Código Fornecedor": item.codigo_fornecedor,
Fornecedor: item.nome_fornecedor, Fornecedor: item.nome_fornecedor,
'Código Centro Custo': item.codigo_centrocusto, "Código Centro Custo": item.codigo_centrocusto,
'Centro Custo': item.codigo_centrocusto, // Assumindo que é o mesmo valor "Centro Custo": item.codigo_centrocusto, // Assumindo que é o mesmo valor
'Código Conta': item.codigo_conta, "Código Conta": item.codigo_conta,
Conta: item.conta, Conta: item.conta,
Valor: Valor:
typeof item.valor === 'string' ? parseFloat(item.valor) : item.valor, typeof item.valor === "string" ? parseFloat(item.valor) : item.valor,
Histórico: item.historico, Histórico: item.historico,
'Histórico 2': item.historico2, "Histórico 2": item.historico2,
Recnum: item.recnum, Recnum: item.recnum,
})); }));
@ -295,18 +321,18 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
// Adicionar resumo na segunda aba // Adicionar resumo na segunda aba
const resumoData = [ const resumoData = [
{ Métrica: 'Total de Registros', Valor: data.length }, { Métrica: "Total de Registros", Valor: data.length },
{ Métrica: 'Valor Total', Valor: totalValor }, { Métrica: "Valor Total", Valor: totalValor },
]; ];
const wsResumo = XLSX.utils.json_to_sheet(resumoData); const wsResumo = XLSX.utils.json_to_sheet(resumoData);
// Adicionar abas ao workbook // Adicionar abas ao workbook
XLSX.utils.book_append_sheet(wb, ws, 'Dados Analíticos'); XLSX.utils.book_append_sheet(wb, ws, "Dados Analíticos");
XLSX.utils.book_append_sheet(wb, wsResumo, 'Resumo'); XLSX.utils.book_append_sheet(wb, wsResumo, "Resumo");
// Gerar nome do arquivo com data e hora // Gerar nome do arquivo com data e hora
const now = new Date(); const now = new Date();
const timestamp = now.toISOString().slice(0, 19).replace(/:/g, '-'); const timestamp = now.toISOString().slice(0, 19).replace(/:/g, "-");
const fileName = `analitico_${timestamp}.xlsx`; const fileName = `analitico_${timestamp}.xlsx`;
// Fazer download // Fazer download
@ -318,14 +344,28 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
{/* Header Section */} {/* Header Section */}
<div className="mb-6"> <div className="mb-6">
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-3 mb-4">
<div className="w-12 h-12 bg-gradient-to-r from-blue-600 to-indigo-600 rounded-xl flex items-center justify-center shadow-lg"> {/* <div className="w-12 h-12 bg-gradient-to-r from-blue-600 to-indigo-600 rounded-xl flex items-center justify-center shadow-lg">
<svg className="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={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" /> className="w-7 h-7 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={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"
/>
</svg> </svg>
</div> </div> */}
<div> <div>
<h1 className="text-2xl font-bold text-gray-900">Análise Analítica</h1> <h1 className="text-2xl font-bold text-gray-900">
<p className="text-sm text-gray-500">Relatório detalhado de transações</p> Análise Analítica
</h1>
<p className="text-sm text-gray-500">
Relatório detalhado de transações
</p>
</div> </div>
</div> </div>
@ -334,7 +374,9 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
<Input <Input
placeholder="Filtrar tudo..." placeholder="Filtrar tudo..."
value={globalFilter ?? ""} value={globalFilter ?? ""}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setGlobalFilter(e.target.value)} onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setGlobalFilter(e.target.value)
}
className="w-64 bg-white border-gray-300 focus:border-blue-500 focus:ring-blue-500" className="w-64 bg-white border-gray-300 focus:border-blue-500 focus:ring-blue-500"
/> />
<Button <Button
@ -344,6 +386,11 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
> >
<Filter className="w-4 h-4 mr-2" /> <Filter className="w-4 h-4 mr-2" />
Filtros Avançados Filtros Avançados
{columnFilters.length > 0 && (
<span className="ml-2 bg-blue-600 text-white text-xs px-2 py-1 rounded-full">
{columnFilters.length}
</span>
)}
</Button> </Button>
{(columnFilters.length > 0 || globalFilter) && ( {(columnFilters.length > 0 || globalFilter) && (
<Button <Button
@ -393,8 +440,8 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
ref={parentRef} ref={parentRef}
className="max-h-[500px] overflow-auto scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100 hover:scrollbar-thumb-gray-400" className="max-h-[500px] overflow-auto scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100 hover:scrollbar-thumb-gray-400"
style={{ style={{
scrollbarWidth: 'thin', scrollbarWidth: "thin",
scrollbarColor: '#cbd5e0 #f7fafc' scrollbarColor: "#cbd5e0 #f7fafc",
}} }}
> >
{loading ? ( {loading ? (
@ -408,15 +455,28 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
<div className="flex items-center justify-center h-64"> <div className="flex items-center justify-center h-64">
<div className="text-center"> <div className="text-center">
<div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4"> <div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> className="w-8 h-8 text-gray-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg> </svg>
</div> </div>
<p className="text-gray-500">Nenhum dado encontrado</p> <p className="text-gray-500">Nenhum dado encontrado</p>
</div> </div>
</div> </div>
) : ( ) : (
<div className="relative" style={{ height: `${rowVirtualizer.getTotalSize()}px` }}> <div
className="relative"
style={{ height: `${rowVirtualizer.getTotalSize()}px` }}
>
{virtualRows.map((virtualRow) => { {virtualRows.map((virtualRow) => {
const row = table.getRowModel().rows[virtualRow.index]; const row = table.getRowModel().rows[virtualRow.index];
return ( return (
@ -425,18 +485,57 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
className="absolute top-0 left-0 w-full grid grid-cols-12 gap-4 px-4 py-3 text-sm border-b border-gray-100 hover:bg-gray-50 transition-colors" className="absolute top-0 left-0 w-full grid grid-cols-12 gap-4 px-4 py-3 text-sm border-b border-gray-100 hover:bg-gray-50 transition-colors"
style={{ transform: `translateY(${virtualRow.start}px)` }} style={{ transform: `translateY(${virtualRow.start}px)` }}
> >
<div className="col-span-1 text-gray-600">{new Date(row.original.data_competencia).toLocaleDateString('pt-BR')}</div> <div className="col-span-1 text-gray-600">
<div className="col-span-1 text-gray-600">{new Date(row.original.data_vencimento).toLocaleDateString('pt-BR')}</div> {new Date(
<div className="col-span-1 text-gray-600">{new Date(row.original.data_caixa).toLocaleDateString('pt-BR')}</div> row.original.data_competencia
<div className="col-span-1 font-medium text-gray-900">{row.original.codigo_fornecedor}</div> ).toLocaleDateString("pt-BR")}
<div className="col-span-2 text-gray-700 truncate" title={row.original.nome_fornecedor}>{row.original.nome_fornecedor}</div> </div>
<div className="col-span-1 text-gray-600">{row.original.codigo_centrocusto}</div> <div className="col-span-1 text-gray-600">
<div className="col-span-1 text-gray-600">{row.original.codigo_conta}</div> {new Date(
<div className="col-span-2 text-gray-700 truncate" title={row.original.conta}>{row.original.conta}</div> row.original.data_vencimento
<div className={`col-span-1 text-right font-semibold ${row.original.valor < 0 ? 'text-red-600' : 'text-gray-900'}`}> ).toLocaleDateString("pt-BR")}
{new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(row.original.valor)} </div>
<div className="col-span-1 text-gray-600">
{new Date(row.original.data_caixa).toLocaleDateString(
"pt-BR"
)}
</div>
<div className="col-span-1 font-medium text-gray-900">
{row.original.codigo_fornecedor}
</div>
<div
className="col-span-2 text-gray-700 truncate"
title={row.original.nome_fornecedor}
>
{row.original.nome_fornecedor}
</div>
<div className="col-span-1 text-gray-600">
{row.original.codigo_centrocusto}
</div>
<div className="col-span-1 text-gray-600">
{row.original.codigo_conta}
</div>
<div
className="col-span-2 text-gray-700 truncate"
title={row.original.conta}
>
{row.original.conta}
</div>
<div
className={`col-span-1 text-right font-semibold ${
row.original.valor < 0
? "text-red-600"
: "text-gray-900"
}`}
>
{new Intl.NumberFormat("pt-BR", {
style: "currency",
currency: "BRL",
}).format(row.original.valor)}
</div>
<div className="col-span-1 text-gray-500">
{row.original.recnum}
</div> </div>
<div className="col-span-1 text-gray-500">{row.original.recnum}</div>
</div> </div>
); );
})} })}
@ -449,28 +548,50 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 border-t border-blue-200 p-6"> <div className="bg-gradient-to-r from-blue-50 to-indigo-50 border-t border-blue-200 p-6">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="w-12 h-12 bg-gradient-to-r from-blue-600 to-indigo-600 rounded-lg flex items-center justify-center"> {/* <div className="w-12 h-12 bg-gradient-to-r from-blue-600 to-indigo-600 rounded-lg flex items-center justify-center">
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" /> className="w-6 h-6 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
/>
</svg> </svg>
</div> </div> */}
<div> <div>
<h3 className="text-lg font-bold text-gray-900"> <h3 className="text-lg font-bold text-gray-900">
Total de Registros: <span className="text-blue-600">{table.getRowModel().rows.length}</span> Total de Registros:{" "}
<span className="text-blue-600">
{table.getRowModel().rows.length}
</span>
</h3> </h3>
<p className="text-sm text-gray-600">Transações encontradas</p> <p className="text-sm text-gray-600">
Transações encontradas
</p>
</div> </div>
</div> </div>
<div className="text-right"> <div className="text-right">
<h3 className="text-lg font-bold"> <h3 className="text-lg font-bold">
<span className={totalValor < 0 ? 'text-red-600' : 'text-green-600'}> <span
Valor Total: {new Intl.NumberFormat('pt-BR', { className={
style: 'currency', totalValor < 0 ? "text-red-600" : "text-green-600"
currency: 'BRL', }
>
Valor Total:{" "}
{new Intl.NumberFormat("pt-BR", {
style: "currency",
currency: "BRL",
}).format(totalValor)} }).format(totalValor)}
</span> </span>
</h3> </h3>
<p className="text-sm text-gray-600">Soma de todos os valores</p> <p className="text-sm text-gray-600">
Soma de todos os valores
</p>
</div> </div>
</div> </div>
</div> </div>
@ -482,12 +603,17 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="max-w-2xl w-full mx-4 bg-white"> <DialogContent className="max-w-2xl w-full mx-4 bg-white">
<DialogHeader className="pb-4"> <DialogHeader className="pb-4">
<DialogTitle className="text-xl font-semibold text-gray-900">Filtros Avançados</DialogTitle> <DialogTitle className="text-xl font-semibold text-gray-900">
Filtros Avançados
</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="space-y-4 max-h-96 overflow-y-auto bg-white"> <div className="space-y-4 max-h-96 overflow-y-auto bg-white">
{conditions.map((cond, idx) => ( {conditions.map((cond, idx) => (
<div key={idx} className="flex gap-3 items-start p-4 bg-gray-50 rounded-lg border border-gray-200"> <div
key={idx}
className="flex gap-3 items-start p-4 bg-gray-50 rounded-lg border border-gray-200"
>
<div className="flex-1"> <div className="flex-1">
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-1">
Coluna Coluna
@ -505,7 +631,10 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{columns.map((col) => ( {columns.map((col) => (
<SelectItem key={col.accessorKey} value={col.accessorKey}> <SelectItem
key={col.accessorKey}
value={col.accessorKey}
>
{col.header} {col.header}
</SelectItem> </SelectItem>
))} ))}
@ -522,7 +651,8 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
onValueChange={(v: string) => { onValueChange={(v: string) => {
const next = [...conditions]; const next = [...conditions];
next[idx].operator = v; next[idx].operator = v;
if (v === "empty" || v === "notEmpty") next[idx].value = ""; if (v === "empty" || v === "notEmpty")
next[idx].value = "";
setConditions(next); setConditions(next);
}} }}
> >
@ -540,7 +670,9 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
</Select> </Select>
</div> </div>
{!(cond.operator === "empty" || cond.operator === "notEmpty") && ( {!(
cond.operator === "empty" || cond.operator === "notEmpty"
) && (
<div className="flex-1"> <div className="flex-1">
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-1">
Valor Valor

View File

@ -1,8 +1,8 @@
'use client'; "use client";
import { LoaderPinwheel } from 'lucide-react'; import { LoaderPinwheel } from "lucide-react";
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import AnaliticoComponent from './analitico'; import AnaliticoComponent from "./analitico";
interface DREItem { interface DREItem {
codfilial: string; codfilial: string;
@ -17,7 +17,7 @@ interface DREItem {
} }
interface HierarchicalRow { interface HierarchicalRow {
type: 'grupo' | 'subgrupo' | 'centro_custo' | 'conta'; type: "grupo" | "subgrupo" | "centro_custo" | "conta";
level: number; level: number;
grupo?: string; grupo?: string;
subgrupo?: string; subgrupo?: string;
@ -30,7 +30,6 @@ interface HierarchicalRow {
percentuaisPorMes?: Record<string, number>; percentuaisPorMes?: Record<string, number>;
} }
export default function Teste() { export default function Teste() {
const [data, setData] = useState<DREItem[]>([]); const [data, setData] = useState<DREItem[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@ -46,12 +45,12 @@ export default function Teste() {
// Estados para analítico // Estados para analítico
const [analiticoFiltros, setAnaliticoFiltros] = useState({ const [analiticoFiltros, setAnaliticoFiltros] = useState({
dataInicio: '', dataInicio: "",
dataFim: '', dataFim: "",
centroCusto: '', centroCusto: "",
codigoGrupo: '', codigoGrupo: "",
codigoSubgrupo: '', codigoSubgrupo: "",
codigoConta: '', codigoConta: "",
}); });
const [linhaSelecionada, setLinhaSelecionada] = useState<string | null>(null); const [linhaSelecionada, setLinhaSelecionada] = useState<string | null>(null);
@ -63,7 +62,7 @@ export default function Teste() {
try { try {
setLoading(true); setLoading(true);
setError(null); setError(null);
const response = await fetch('/api/dre'); const response = await fetch("/api/dre");
if (!response.ok) { if (!response.ok) {
throw new Error(`Erro ao carregar dados: ${response.status}`); throw new Error(`Erro ao carregar dados: ${response.status}`);
@ -79,29 +78,29 @@ export default function Teste() {
const dataCompetencia = new Date(item.data_competencia); const dataCompetencia = new Date(item.data_competencia);
return `${dataCompetencia.getFullYear()}-${String( return `${dataCompetencia.getFullYear()}-${String(
dataCompetencia.getMonth() + 1 dataCompetencia.getMonth() + 1
).padStart(2, '0')}`; ).padStart(2, "0")}`;
}) })
), ),
].sort() as string[]; ].sort() as string[];
setMesesDisponiveis(meses); setMesesDisponiveis(meses);
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : 'Erro desconhecido'); setError(err instanceof Error ? err.message : "Erro desconhecido");
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
const formatCurrency = (value: string | number) => { const formatCurrency = (value: string | number) => {
const numValue = typeof value === 'string' ? parseFloat(value) : value; const numValue = typeof value === "string" ? parseFloat(value) : value;
return numValue.toLocaleString('pt-BR', { return numValue.toLocaleString("pt-BR", {
style: 'currency', style: "currency",
currency: 'BRL', currency: "BRL",
}); });
}; };
const formatCurrencyWithColor = (value: string | number) => { const formatCurrencyWithColor = (value: string | number) => {
const numValue = typeof value === 'string' ? parseFloat(value) : value; const numValue = typeof value === "string" ? parseFloat(value) : value;
const formatted = formatCurrency(value); const formatted = formatCurrency(value);
const isNegative = numValue < 0; const isNegative = numValue < 0;
return { formatted, isNegative }; return { formatted, isNegative };
@ -110,9 +109,9 @@ export default function Teste() {
// Função para extrair códigos dos grupos e subgrupos // Função para extrair códigos dos grupos e subgrupos
const extractCodes = (grupo: string, subgrupo?: string) => { const extractCodes = (grupo: string, subgrupo?: string) => {
const grupoMatch = grupo.match(/^(\d+)/); const grupoMatch = grupo.match(/^(\d+)/);
const codigoGrupo = grupoMatch ? grupoMatch[1] : ''; const codigoGrupo = grupoMatch ? grupoMatch[1] : "";
let codigoSubgrupo = ''; let codigoSubgrupo = "";
if (subgrupo) { if (subgrupo) {
// Primeiro tenta extrair código numérico (ex: "001.008 - LOJA 8" -> "001.008") // Primeiro tenta extrair código numérico (ex: "001.008 - LOJA 8" -> "001.008")
const subgrupoMatch = subgrupo.match(/^(\d+(?:\.\d+)+)/); const subgrupoMatch = subgrupo.match(/^(\d+(?:\.\d+)+)/);
@ -140,14 +139,14 @@ export default function Teste() {
const dataFimStr = new Date(dataFim).toISOString().substring(0, 7); // YYYY-MM const dataFimStr = new Date(dataFim).toISOString().substring(0, 7); // YYYY-MM
const { codigoGrupo, codigoSubgrupo } = extractCodes( const { codigoGrupo, codigoSubgrupo } = extractCodes(
row.grupo || '', row.grupo || "",
row.subgrupo row.subgrupo
); );
// Criar um identificador único para a linha // Criar um identificador único para a linha
const linhaId = `${row.type}-${row.grupo || ''}-${row.subgrupo || ''}-${ const linhaId = `${row.type}-${row.grupo || ""}-${row.subgrupo || ""}-${
row.centro_custo || '' row.centro_custo || ""
}-${row.codigo_conta || ''}`; }-${row.codigo_conta || ""}`;
setLinhaSelecionada(linhaId); setLinhaSelecionada(linhaId);
// Se um mês específico foi selecionado, usar apenas esse mês // Se um mês específico foi selecionado, usar apenas esse mês
@ -157,10 +156,10 @@ export default function Teste() {
setAnaliticoFiltros({ setAnaliticoFiltros({
dataInicio: dataInicioFiltro, dataInicio: dataInicioFiltro,
dataFim: dataFimFiltro, dataFim: dataFimFiltro,
centroCusto: row.centro_custo || '', centroCusto: row.centro_custo || "",
codigoGrupo, codigoGrupo,
codigoSubgrupo, codigoSubgrupo,
codigoConta: row.codigo_conta?.toString() || '', codigoConta: row.codigo_conta?.toString() || "",
}); });
}; };
@ -194,7 +193,6 @@ export default function Teste() {
setExpandedCentros(newExpanded); setExpandedCentros(newExpanded);
}; };
const calcularValoresPorMes = (items: DREItem[]): Record<string, number> => { const calcularValoresPorMes = (items: DREItem[]): Record<string, number> => {
const valoresPorMes: Record<string, number> = {}; const valoresPorMes: Record<string, number> = {};
@ -202,7 +200,7 @@ export default function Teste() {
const dataCompetencia = new Date(item.data_competencia); const dataCompetencia = new Date(item.data_competencia);
const anoMes = `${dataCompetencia.getFullYear()}-${String( const anoMes = `${dataCompetencia.getFullYear()}-${String(
dataCompetencia.getMonth() + 1 dataCompetencia.getMonth() + 1
).padStart(2, '0')}`; ).padStart(2, "0")}`;
if (!valoresPorMes[anoMes]) { if (!valoresPorMes[anoMes]) {
valoresPorMes[anoMes] = 0; valoresPorMes[anoMes] = 0;
@ -221,7 +219,7 @@ export default function Teste() {
const percentuais: Record<string, number> = {}; const percentuais: Record<string, number> = {};
// Se for o grupo 03, retorna 100% para todos os meses // Se for o grupo 03, retorna 100% para todos os meses
if (grupo.includes('03')) { if (grupo.includes("03")) {
Object.keys(valoresPorMes).forEach((mes) => { Object.keys(valoresPorMes).forEach((mes) => {
percentuais[mes] = 100; percentuais[mes] = 100;
}); });
@ -237,8 +235,8 @@ export default function Teste() {
const dataCompetencia = new Date(item.data_competencia); const dataCompetencia = new Date(item.data_competencia);
const anoMes = `${dataCompetencia.getFullYear()}-${String( const anoMes = `${dataCompetencia.getFullYear()}-${String(
dataCompetencia.getMonth() + 1 dataCompetencia.getMonth() + 1
).padStart(2, '0')}`; ).padStart(2, "0")}`;
return anoMes === mes && item.grupo.includes('03'); return anoMes === mes && item.grupo.includes("03");
}); });
const valorGrupo03 = grupo03Items.reduce( const valorGrupo03 = grupo03Items.reduce(
@ -262,16 +260,16 @@ export default function Teste() {
// Agrupar por grupo, mas tratar grupo 05 como subgrupo do grupo 04 // Agrupar por grupo, mas tratar grupo 05 como subgrupo do grupo 04
const grupos = data.reduce((acc, item) => { const grupos = data.reduce((acc, item) => {
// Se for grupo 05, adicionar ao grupo 04 como subgrupo // Se for grupo 05, adicionar ao grupo 04 como subgrupo
if (item.grupo.includes('05')) { if (item.grupo.includes("05")) {
// Encontrar grupo 04 existente ou criar um // Encontrar grupo 04 existente ou criar um
const grupo04Key = Object.keys(acc).find((key) => key.includes('04')); const grupo04Key = Object.keys(acc).find((key) => key.includes("04"));
if (grupo04Key) { if (grupo04Key) {
acc[grupo04Key].push(item); acc[grupo04Key].push(item);
} else { } else {
// Se não existe grupo 04, criar um grupo especial // Se não existe grupo 04, criar um grupo especial
const grupo04Nome = const grupo04Nome =
Object.keys(acc).find((key) => key.includes('04')) || Object.keys(acc).find((key) => key.includes("04")) ||
'04 - GRUPO 04'; "04 - GRUPO 04";
if (!acc[grupo04Nome]) { if (!acc[grupo04Nome]) {
acc[grupo04Nome] = []; acc[grupo04Nome] = [];
} }
@ -288,7 +286,9 @@ export default function Teste() {
}, {} as Record<string, DREItem[]>); }, {} as Record<string, DREItem[]>);
// Ordenar grupos // Ordenar grupos
const sortedGrupos = Object.entries(grupos).sort(([a], [b]) => a.localeCompare(b)); const sortedGrupos = Object.entries(grupos).sort(([a], [b]) =>
a.localeCompare(b)
);
sortedGrupos.forEach(([grupo, items]) => { sortedGrupos.forEach(([grupo, items]) => {
const totalGrupo = items.reduce( const totalGrupo = items.reduce(
@ -299,7 +299,7 @@ export default function Teste() {
// Linha do grupo // Linha do grupo
const valoresPorMes = calcularValoresPorMes(items); const valoresPorMes = calcularValoresPorMes(items);
rows.push({ rows.push({
type: 'grupo', type: "grupo",
level: 0, level: 0,
grupo, grupo,
total: totalGrupo, total: totalGrupo,
@ -312,7 +312,7 @@ export default function Teste() {
// Agrupar por subgrupo dentro do grupo // Agrupar por subgrupo dentro do grupo
const subgrupos = items.reduce((acc, item) => { const subgrupos = items.reduce((acc, item) => {
// Se o item originalmente era do grupo 05, agrupar tudo sob o nome do grupo 05 // Se o item originalmente era do grupo 05, agrupar tudo sob o nome do grupo 05
if (item.grupo.includes('05')) { if (item.grupo.includes("05")) {
const subgrupoKey = item.grupo; // Usar o nome completo do grupo 05 const subgrupoKey = item.grupo; // Usar o nome completo do grupo 05
if (!acc[subgrupoKey]) { if (!acc[subgrupoKey]) {
acc[subgrupoKey] = []; acc[subgrupoKey] = [];
@ -329,7 +329,9 @@ export default function Teste() {
}, {} as Record<string, DREItem[]>); }, {} as Record<string, DREItem[]>);
// Ordenar subgrupos // Ordenar subgrupos
const sortedSubgrupos = Object.entries(subgrupos).sort(([a], [b]) => a.localeCompare(b)); const sortedSubgrupos = Object.entries(subgrupos).sort(([a], [b]) =>
a.localeCompare(b)
);
sortedSubgrupos.forEach(([subgrupo, subgrupoItems]) => { sortedSubgrupos.forEach(([subgrupo, subgrupoItems]) => {
const totalSubgrupo = subgrupoItems.reduce( const totalSubgrupo = subgrupoItems.reduce(
@ -340,7 +342,7 @@ export default function Teste() {
// Linha do subgrupo // Linha do subgrupo
const valoresSubgrupoPorMes = calcularValoresPorMes(subgrupoItems); const valoresSubgrupoPorMes = calcularValoresPorMes(subgrupoItems);
rows.push({ rows.push({
type: 'subgrupo', type: "subgrupo",
level: 1, level: 1,
grupo, grupo,
subgrupo, subgrupo,
@ -364,7 +366,9 @@ export default function Teste() {
}, {} as Record<string, DREItem[]>); }, {} as Record<string, DREItem[]>);
// Ordenar centros de custo // Ordenar centros de custo
const sortedCentros = Object.entries(centros).sort(([a], [b]) => a.localeCompare(b)); const sortedCentros = Object.entries(centros).sort(([a], [b]) =>
a.localeCompare(b)
);
sortedCentros.forEach(([centro, centroItems]) => { sortedCentros.forEach(([centro, centroItems]) => {
const totalCentro = centroItems.reduce( const totalCentro = centroItems.reduce(
@ -375,7 +379,7 @@ export default function Teste() {
// Linha do centro de custo // Linha do centro de custo
const valoresCentroPorMes = calcularValoresPorMes(centroItems); const valoresCentroPorMes = calcularValoresPorMes(centroItems);
rows.push({ rows.push({
type: 'centro_custo', type: "centro_custo",
level: 2, level: 2,
grupo, grupo,
subgrupo, subgrupo,
@ -402,7 +406,9 @@ export default function Teste() {
}, {} as Record<string, DREItem[]>); }, {} as Record<string, DREItem[]>);
// Ordenar contas // Ordenar contas
const sortedContas = Object.entries(contas).sort(([a], [b]) => a.localeCompare(b)); const sortedContas = Object.entries(contas).sort(([a], [b]) =>
a.localeCompare(b)
);
sortedContas.forEach(([conta, contaItems]) => { sortedContas.forEach(([conta, contaItems]) => {
const totalConta = contaItems.reduce( const totalConta = contaItems.reduce(
@ -413,7 +419,7 @@ export default function Teste() {
// Linha da conta (sem ano/mês no nome) // Linha da conta (sem ano/mês no nome)
const valoresContaPorMes = calcularValoresPorMes(contaItems); const valoresContaPorMes = calcularValoresPorMes(contaItems);
rows.push({ rows.push({
type: 'conta', type: "conta",
level: 3, level: 3,
grupo, grupo,
subgrupo, subgrupo,
@ -439,28 +445,30 @@ export default function Teste() {
}; };
const getRowStyle = (row: HierarchicalRow) => { const getRowStyle = (row: HierarchicalRow) => {
const baseStyle = 'transition-all duration-200 hover:bg-gradient-to-r hover:from-blue-50/30 hover:to-indigo-50/30'; const baseStyle =
"transition-all duration-200 hover:bg-gradient-to-r hover:from-blue-50/30 hover:to-indigo-50/30";
// Criar identificador único para a linha // Criar identificador único para a linha
const linhaId = `${row.type}-${row.grupo || ''}-${row.subgrupo || ''}-${ const linhaId = `${row.type}-${row.grupo || ""}-${row.subgrupo || ""}-${
row.centro_custo || '' row.centro_custo || ""
}-${row.codigo_conta || ''}`; }-${row.codigo_conta || ""}`;
const isSelected = linhaSelecionada === linhaId; const isSelected = linhaSelecionada === linhaId;
let style = baseStyle; let style = baseStyle;
if (isSelected) { if (isSelected) {
style += ' bg-gradient-to-r from-blue-100 to-indigo-100 border-l-4 border-blue-500 shadow-lg'; style +=
" bg-gradient-to-r from-blue-100 to-indigo-100 border-l-4 border-blue-500 shadow-lg";
} }
switch (row.type) { switch (row.type) {
case 'grupo': case "grupo":
return `${style} bg-gradient-to-r from-blue-50/20 to-indigo-50/20 font-bold text-gray-900 border-b-2 border-blue-200`; return `${style} bg-gradient-to-r from-blue-50/20 to-indigo-50/20 font-bold text-gray-900 border-b-2 border-blue-200`;
case 'subgrupo': case "subgrupo":
return `${style} bg-gradient-to-r from-gray-50/30 to-blue-50/20 font-semibold text-gray-800`; return `${style} bg-gradient-to-r from-gray-50/30 to-blue-50/20 font-semibold text-gray-800`;
case 'centro_custo': case "centro_custo":
return `${style} bg-gradient-to-r from-gray-50/20 to-gray-100/10 font-medium text-gray-700`; return `${style} bg-gradient-to-r from-gray-50/20 to-gray-100/10 font-medium text-gray-700`;
case 'conta': case "conta":
return `${style} bg-white font-normal text-gray-600`; return `${style} bg-white font-normal text-gray-600`;
default: default:
return style; return style;
@ -473,7 +481,7 @@ export default function Teste() {
const renderCellContent = (row: HierarchicalRow) => { const renderCellContent = (row: HierarchicalRow) => {
switch (row.type) { switch (row.type) {
case 'grupo': case "grupo":
return ( return (
<div className="flex items-center gap-3 whitespace-nowrap"> <div className="flex items-center gap-3 whitespace-nowrap">
<button <button
@ -481,7 +489,7 @@ export default function Teste() {
className="p-2 hover:bg-blue-100 rounded-lg transition-all duration-200 flex items-center justify-center w-8 h-8 flex-shrink-0" className="p-2 hover:bg-blue-100 rounded-lg transition-all duration-200 flex items-center justify-center w-8 h-8 flex-shrink-0"
> >
<span className="text-blue-600 font-bold text-sm"> <span className="text-blue-600 font-bold text-sm">
{row.isExpanded ? '▼' : '▶'} {row.isExpanded ? "▼" : "▶"}
</span> </span>
</button> </button>
<button <button
@ -492,7 +500,7 @@ export default function Teste() {
</button> </button>
</div> </div>
); );
case 'subgrupo': case "subgrupo":
return ( return (
<div className="flex items-center gap-3 whitespace-nowrap"> <div className="flex items-center gap-3 whitespace-nowrap">
<button <button
@ -500,18 +508,20 @@ export default function Teste() {
className="p-2 hover:bg-blue-100 rounded-lg transition-all duration-200 flex items-center justify-center w-8 h-8 flex-shrink-0" className="p-2 hover:bg-blue-100 rounded-lg transition-all duration-200 flex items-center justify-center w-8 h-8 flex-shrink-0"
> >
<span className="text-blue-600 font-bold text-sm"> <span className="text-blue-600 font-bold text-sm">
{row.isExpanded ? '▼' : '▶'} {row.isExpanded ? "▼" : "▶"}
</span> </span>
</button> </button>
<button <button
onClick={() => handleRowClick(row)} onClick={() => handleRowClick(row)}
className="flex-1 text-left hover:bg-blue-50/50 p-2 rounded-lg cursor-pointer transition-all duration-200 truncate" className="flex-1 text-left hover:bg-blue-50/50 p-2 rounded-lg cursor-pointer transition-all duration-200 truncate"
> >
<span className="font-semibold text-gray-800">{row.subgrupo}</span> <span className="font-semibold text-gray-800">
{row.subgrupo}
</span>
</button> </button>
</div> </div>
); );
case 'centro_custo': case "centro_custo":
return ( return (
<div className="flex items-center gap-3 whitespace-nowrap"> <div className="flex items-center gap-3 whitespace-nowrap">
<button <button
@ -521,18 +531,20 @@ export default function Teste() {
className="p-2 hover:bg-blue-100 rounded-lg transition-all duration-200 flex items-center justify-center w-8 h-8 flex-shrink-0" className="p-2 hover:bg-blue-100 rounded-lg transition-all duration-200 flex items-center justify-center w-8 h-8 flex-shrink-0"
> >
<span className="text-blue-600 font-bold text-sm"> <span className="text-blue-600 font-bold text-sm">
{row.isExpanded ? '▼' : '▶'} {row.isExpanded ? "▼" : "▶"}
</span> </span>
</button> </button>
<button <button
onClick={() => handleRowClick(row)} onClick={() => handleRowClick(row)}
className="flex-1 text-left hover:bg-blue-50/50 p-2 rounded-lg cursor-pointer transition-all duration-200 truncate" className="flex-1 text-left hover:bg-blue-50/50 p-2 rounded-lg cursor-pointer transition-all duration-200 truncate"
> >
<span className="font-medium text-gray-700">{row.centro_custo}</span> <span className="font-medium text-gray-700">
{row.centro_custo}
</span>
</button> </button>
</div> </div>
); );
case 'conta': case "conta":
return ( return (
<div className="flex items-center gap-3 whitespace-nowrap"> <div className="flex items-center gap-3 whitespace-nowrap">
<div className="w-8 h-8 flex items-center justify-center flex-shrink-0"> <div className="w-8 h-8 flex items-center justify-center flex-shrink-0">
@ -556,14 +568,28 @@ export default function Teste() {
<div className="w-full max-w-7xl mx-auto p-6"> <div className="w-full max-w-7xl mx-auto p-6">
<div className="mb-6"> <div className="mb-6">
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-3 mb-4">
<div className="w-12 h-12 bg-gradient-to-r from-blue-600 to-indigo-600 rounded-xl flex items-center justify-center shadow-lg"> {/* <div className="w-12 h-12 bg-gradient-to-r from-blue-600 to-indigo-600 rounded-xl flex items-center justify-center shadow-lg">
<svg className="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={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" /> className="w-7 h-7 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={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"
/>
</svg> </svg>
</div> </div> */}
<div> <div>
<h1 className="text-2xl font-bold text-gray-900">DRE Gerencial</h1> <h1 className="text-2xl font-bold text-gray-900">
<p className="text-sm text-gray-500">Demonstração do Resultado do Exercício</p> DRE Gerencial
</h1>
<p className="text-sm text-gray-500">
Demonstração do Resultado do Exercício
</p>
</div> </div>
</div> </div>
</div> </div>
@ -573,8 +599,12 @@ export default function Teste() {
<div className="w-16 h-16 bg-gradient-to-r from-blue-100 to-indigo-100 rounded-full flex items-center justify-center mb-4"> <div className="w-16 h-16 bg-gradient-to-r from-blue-100 to-indigo-100 rounded-full flex items-center justify-center mb-4">
<LoaderPinwheel className="h-8 w-8 text-blue-600 animate-spin" /> <LoaderPinwheel className="h-8 w-8 text-blue-600 animate-spin" />
</div> </div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">Carregando dados...</h3> <h3 className="text-lg font-semibold text-gray-900 mb-2">
<p className="text-sm text-gray-500">Aguarde enquanto processamos as informações</p> Carregando dados...
</h3>
<p className="text-sm text-gray-500">
Aguarde enquanto processamos as informações
</p>
</div> </div>
</div> </div>
</div> </div>
@ -586,14 +616,28 @@ export default function Teste() {
<div className="w-full max-w-7xl mx-auto p-6"> <div className="w-full max-w-7xl mx-auto p-6">
<div className="mb-6"> <div className="mb-6">
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-3 mb-4">
<div className="w-12 h-12 bg-gradient-to-r from-red-600 to-red-500 rounded-xl flex items-center justify-center shadow-lg"> {/* <div className="w-12 h-12 bg-gradient-to-r from-red-600 to-red-500 rounded-xl flex items-center justify-center shadow-lg">
<svg className="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" /> className="w-7 h-7 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
/>
</svg> </svg>
</div> </div> */}
<div> <div>
<h1 className="text-2xl font-bold text-gray-900">DRE Gerencial</h1> <h1 className="text-2xl font-bold text-gray-900">
<p className="text-sm text-gray-500">Demonstração do Resultado do Exercício</p> DRE Gerencial
</h1>
<p className="text-sm text-gray-500">
Demonstração do Resultado do Exercício
</p>
</div> </div>
</div> </div>
</div> </div>
@ -601,11 +645,23 @@ export default function Teste() {
<div className="bg-white rounded-xl shadow-xl border border-red-200 p-8"> <div className="bg-white rounded-xl shadow-xl border border-red-200 p-8">
<div className="flex flex-col items-center text-center"> <div className="flex flex-col items-center text-center">
<div className="w-16 h-16 bg-gradient-to-r from-red-100 to-red-50 rounded-full flex items-center justify-center mb-4"> <div className="w-16 h-16 bg-gradient-to-r from-red-100 to-red-50 rounded-full flex items-center justify-center mb-4">
<svg className="w-8 h-8 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" /> className="w-8 h-8 text-red-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
/>
</svg> </svg>
</div> </div>
<h3 className="text-lg font-semibold text-red-900 mb-2">Erro ao carregar DRE Gerencial</h3> <h3 className="text-lg font-semibold text-red-900 mb-2">
Erro ao carregar DRE Gerencial
</h3>
<p className="text-sm text-red-600 bg-red-50 border border-red-200 rounded-lg p-3 max-w-md"> <p className="text-sm text-red-600 bg-red-50 border border-red-200 rounded-lg p-3 max-w-md">
{error} {error}
</p> </p>
@ -622,14 +678,26 @@ export default function Teste() {
{/* Header Section */} {/* Header Section */}
<div className="mb-6"> <div className="mb-6">
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-3 mb-4">
<div className="w-12 h-12 bg-gradient-to-r from-blue-600 to-indigo-600 rounded-xl flex items-center justify-center shadow-lg"> {/* <div className="w-12 h-12 bg-gradient-to-r from-blue-600 to-indigo-600 rounded-xl flex items-center justify-center shadow-lg">
<svg className="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={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" /> className="w-7 h-7 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={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"
/>
</svg> </svg>
</div> </div> */}
<div> <div>
<h1 className="text-2xl font-bold text-gray-900">DRE Gerencial</h1> <h1 className="text-2xl font-bold text-gray-900">DRE Gerencial</h1>
<p className="text-sm text-gray-500">Demonstração do Resultado do Exercício</p> <p className="text-sm text-gray-500">
Demonstração do Resultado do Exercício
</p>
</div> </div>
</div> </div>
</div> </div>
@ -643,7 +711,9 @@ export default function Teste() {
{mesesDisponiveis.map((mes) => ( {mesesDisponiveis.map((mes) => (
<div key={mes} className="flex min-w-[200px] max-w-[250px]"> <div key={mes} className="flex min-w-[200px] max-w-[250px]">
<div className="flex-1 min-w-[100px] text-right">{mes}</div> <div className="flex-1 min-w-[100px] text-right">{mes}</div>
<div className="flex-1 min-w-[100px] text-left text-xs text-gray-500">%</div> <div className="flex-1 min-w-[100px] text-left text-xs text-gray-500">
%
</div>
</div> </div>
))} ))}
<div className="flex-1 min-w-[120px] text-right">Total</div> <div className="flex-1 min-w-[120px] text-right">Total</div>
@ -653,8 +723,16 @@ export default function Teste() {
{/* Table Body */} {/* Table Body */}
<div className="max-h-[500px] overflow-auto scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100"> <div className="max-h-[500px] overflow-auto scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100">
{hierarchicalData.map((row, index) => ( {hierarchicalData.map((row, index) => (
<div key={index} className={`flex items-center gap-4 px-4 py-3 text-sm border-b border-gray-100 hover:bg-gray-50 transition-colors ${getRowStyle(row)}`}> <div
<div className="flex-1 min-w-[300px] max-w-[400px] whitespace-nowrap overflow-hidden" style={getIndentStyle(row.level)}> key={index}
className={`flex items-center gap-4 px-4 py-3 text-sm border-b border-gray-100 hover:bg-gray-50 transition-colors ${getRowStyle(
row
)}`}
>
<div
className="flex-1 min-w-[300px] max-w-[400px] whitespace-nowrap overflow-hidden"
style={getIndentStyle(row.level)}
>
{renderCellContent(row)} {renderCellContent(row)}
</div> </div>
{mesesDisponiveis.map((mes) => ( {mesesDisponiveis.map((mes) => (
@ -662,39 +740,62 @@ export default function Teste() {
<div <div
className="flex-1 min-w-[100px] text-right font-semibold cursor-pointer hover:bg-blue-50/50 transition-colors duration-200 whitespace-nowrap overflow-hidden" className="flex-1 min-w-[100px] text-right font-semibold cursor-pointer hover:bg-blue-50/50 transition-colors duration-200 whitespace-nowrap overflow-hidden"
onClick={() => handleRowClick(row, mes)} onClick={() => handleRowClick(row, mes)}
title={row.valoresPorMes && row.valoresPorMes[mes] ? formatCurrency(row.valoresPorMes[mes]) : '-'} title={
row.valoresPorMes && row.valoresPorMes[mes]
? formatCurrency(row.valoresPorMes[mes])
: "-"
}
> >
{row.valoresPorMes && row.valoresPorMes[mes] {row.valoresPorMes && row.valoresPorMes[mes]
? (() => { ? (() => {
const { formatted, isNegative } = formatCurrencyWithColor(row.valoresPorMes[mes]); const { formatted, isNegative } =
formatCurrencyWithColor(row.valoresPorMes[mes]);
return ( return (
<span className={isNegative ? 'text-red-600 font-bold' : 'text-gray-900'}> <span
className={
isNegative
? "text-red-600 font-bold"
: "text-gray-900"
}
>
{formatted} {formatted}
</span> </span>
); );
})() })()
: '-'} : "-"}
</div> </div>
<div <div
className="flex-1 min-w-[100px] text-left font-medium cursor-pointer hover:bg-blue-50/50 transition-colors duration-200 whitespace-nowrap overflow-hidden" className="flex-1 min-w-[100px] text-left font-medium cursor-pointer hover:bg-blue-50/50 transition-colors duration-200 whitespace-nowrap overflow-hidden"
onClick={() => handleRowClick(row, mes)} onClick={() => handleRowClick(row, mes)}
title={row.percentuaisPorMes && row.percentuaisPorMes[mes] !== undefined ? `${row.percentuaisPorMes[mes].toFixed(1)}%` : '-'} title={
> row.percentuaisPorMes &&
{row.percentuaisPorMes && row.percentuaisPorMes[mes] !== undefined row.percentuaisPorMes[mes] !== undefined
? `${row.percentuaisPorMes[mes].toFixed(1)}%` ? `${row.percentuaisPorMes[mes].toFixed(1)}%`
: '-'} : "-"
}
>
{row.percentuaisPorMes &&
row.percentuaisPorMes[mes] !== undefined
? `${row.percentuaisPorMes[mes].toFixed(1)}%`
: "-"}
</div> </div>
</div> </div>
))} ))}
<div <div
className="flex-1 min-w-[120px] text-right font-semibold cursor-pointer hover:bg-blue-50/50 transition-colors duration-200 whitespace-nowrap overflow-hidden" className="flex-1 min-w-[120px] text-right font-semibold cursor-pointer hover:bg-blue-50/50 transition-colors duration-200 whitespace-nowrap overflow-hidden"
onClick={() => handleRowClick(row)} onClick={() => handleRowClick(row)}
title={row.total ? formatCurrency(row.total) : '-'} title={row.total ? formatCurrency(row.total) : "-"}
> >
{(() => { {(() => {
const { formatted, isNegative } = formatCurrencyWithColor(row.total!); const { formatted, isNegative } = formatCurrencyWithColor(
row.total!
);
return ( return (
<span className={isNegative ? 'text-red-600 font-bold' : 'text-gray-900'}> <span
className={
isNegative ? "text-red-600 font-bold" : "text-gray-900"
}
>
{formatted} {formatted}
</span> </span>
); );