fix: ajuste no fechamento do sheet
This commit is contained in:
parent
b80e4ab970
commit
be48b04ee7
|
|
@ -9,6 +9,7 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
|
|
@ -1726,6 +1727,29 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-label": {
|
||||||
|
"version": "2.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz",
|
||||||
|
"integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-primitive": "2.1.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-popper": {
|
"node_modules/@radix-ui/react-popper": {
|
||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,27 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { LoaderPinwheel, ChevronDown, ChevronRight } from "lucide-react";
|
import { LoaderPinwheel, ChevronDown, ChevronRight, Filter } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import AnaliticoComponent from "./analitico";
|
import AnaliticoComponent from "./analitico";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Sheet,
|
||||||
|
SheetContent,
|
||||||
|
SheetDescription,
|
||||||
|
SheetFooter,
|
||||||
|
SheetHeader,
|
||||||
|
SheetTitle,
|
||||||
|
SheetTrigger,
|
||||||
|
} from "@/components/ui/sheet";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
|
||||||
interface DREItem {
|
interface DREItem {
|
||||||
codfilial: string;
|
codfilial: string;
|
||||||
|
|
@ -34,7 +53,7 @@ interface HierarchicalRow {
|
||||||
|
|
||||||
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(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
|
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
|
||||||
const [expandedSubgrupos, setExpandedSubgrupos] = useState<Set<string>>(
|
const [expandedSubgrupos, setExpandedSubgrupos] = useState<Set<string>>(
|
||||||
|
|
@ -45,6 +64,28 @@ export default function Teste() {
|
||||||
);
|
);
|
||||||
const [mesesDisponiveis, setMesesDisponiveis] = useState<string[]>([]);
|
const [mesesDisponiveis, setMesesDisponiveis] = useState<string[]>([]);
|
||||||
|
|
||||||
|
// Estados para filtros
|
||||||
|
const [filtros, setFiltros] = useState({
|
||||||
|
periodoDe: "",
|
||||||
|
periodoAte: "",
|
||||||
|
grupo: "Todos",
|
||||||
|
subgrupo: "Todos",
|
||||||
|
centroCusto: "Todos",
|
||||||
|
conta: "Todas",
|
||||||
|
valorMin: "",
|
||||||
|
valorMax: "",
|
||||||
|
buscaTextual: ""
|
||||||
|
});
|
||||||
|
const [isFilterOpen, setIsFilterOpen] = useState(false);
|
||||||
|
const [dadosFiltrados, setDadosFiltrados] = useState<DREItem[]>([]);
|
||||||
|
const [filtrosAplicados, setFiltrosAplicados] = useState(false);
|
||||||
|
|
||||||
|
// Estados para opções dos filtros
|
||||||
|
const [opcoesGrupos, setOpcoesGrupos] = useState<string[]>([]);
|
||||||
|
const [opcoesSubgrupos, setOpcoesSubgrupos] = useState<string[]>([]);
|
||||||
|
const [opcoesCentrosCusto, setOpcoesCentrosCusto] = useState<string[]>([]);
|
||||||
|
const [opcoesContas, setOpcoesContas] = useState<string[]>([]);
|
||||||
|
|
||||||
// Estados para analítico
|
// Estados para analítico
|
||||||
const [analiticoFiltros, setAnaliticoFiltros] = useState({
|
const [analiticoFiltros, setAnaliticoFiltros] = useState({
|
||||||
dataInicio: "",
|
dataInicio: "",
|
||||||
|
|
@ -57,9 +98,56 @@ export default function Teste() {
|
||||||
const [linhaSelecionada, setLinhaSelecionada] = useState<string | null>(null);
|
const [linhaSelecionada, setLinhaSelecionada] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
// Carregar períodos disponíveis da API
|
||||||
|
carregarPeriodosDisponiveis();
|
||||||
|
|
||||||
|
// Inicializar filtros com período atual
|
||||||
|
const agora = new Date();
|
||||||
|
const anoAtual = agora.getFullYear();
|
||||||
|
const mesAtual = String(agora.getMonth() + 1).padStart(2, '0');
|
||||||
|
const periodoAtual = `${anoAtual}-${mesAtual}`;
|
||||||
|
|
||||||
|
setFiltros(prev => ({
|
||||||
|
...prev,
|
||||||
|
periodoDe: `${anoAtual}-01`,
|
||||||
|
periodoAte: periodoAtual
|
||||||
|
}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const carregarPeriodosDisponiveis = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/dre-oracle");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Erro HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dadosCompletos = await response.json();
|
||||||
|
|
||||||
|
// Extrair períodos únicos dos dados
|
||||||
|
const periodosUnicos = [...new Set(dadosCompletos.map((item: DREItem) => item.data_competencia))].sort();
|
||||||
|
setMesesDisponiveis(periodosUnicos);
|
||||||
|
|
||||||
|
// Extrair grupos únicos
|
||||||
|
const gruposUnicos = [...new Set(dadosCompletos.map((item: DREItem) => item.grupo))].sort();
|
||||||
|
setOpcoesGrupos(gruposUnicos);
|
||||||
|
|
||||||
|
// Extrair subgrupos únicos
|
||||||
|
const subgruposUnicos = [...new Set(dadosCompletos.map((item: DREItem) => item.subgrupo))].sort();
|
||||||
|
setOpcoesSubgrupos(subgruposUnicos);
|
||||||
|
|
||||||
|
// Extrair centros de custo únicos
|
||||||
|
const centrosCustoUnicos = [...new Set(dadosCompletos.map((item: DREItem) => item.centro_custo))].sort();
|
||||||
|
setOpcoesCentrosCusto(centrosCustoUnicos);
|
||||||
|
|
||||||
|
// Extrair contas únicas
|
||||||
|
const contasUnicas = [...new Set(dadosCompletos.map((item: DREItem) => item.conta))].sort();
|
||||||
|
setOpcoesContas(contasUnicas);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erro ao carregar períodos:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -195,6 +283,139 @@ export default function Teste() {
|
||||||
setExpandedCentros(newExpanded);
|
setExpandedCentros(newExpanded);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleFiltroChange = (campo: string, valor: string) => {
|
||||||
|
setFiltros(prev => ({
|
||||||
|
...prev,
|
||||||
|
[campo]: valor
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const limparFiltros = () => {
|
||||||
|
const agora = new Date();
|
||||||
|
const anoAtual = agora.getFullYear();
|
||||||
|
const mesAtual = String(agora.getMonth() + 1).padStart(2, '0');
|
||||||
|
const periodoAtual = `${anoAtual}-${mesAtual}`;
|
||||||
|
|
||||||
|
setFiltros({
|
||||||
|
periodoDe: `${anoAtual}-01`,
|
||||||
|
periodoAte: periodoAtual,
|
||||||
|
grupo: "Todos",
|
||||||
|
subgrupo: "Todos",
|
||||||
|
centroCusto: "Todos",
|
||||||
|
conta: "Todas",
|
||||||
|
valorMin: "",
|
||||||
|
valorMax: "",
|
||||||
|
buscaTextual: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
// Limpar dados da tabela
|
||||||
|
setData([]);
|
||||||
|
setDadosFiltrados([]);
|
||||||
|
setFiltrosAplicados(false);
|
||||||
|
setMesesDisponiveis([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const aplicarFiltros = async () => {
|
||||||
|
// Fechar o Sheet primeiro
|
||||||
|
setIsFilterOpen(false);
|
||||||
|
|
||||||
|
// Aguardar um pouco para a animação de fechamento
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
// Carregar dados da API
|
||||||
|
const response = await fetch("/api/dre-oracle");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Erro HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dadosCompletos = await response.json();
|
||||||
|
|
||||||
|
// Aplicar filtros nos dados
|
||||||
|
let dadosFiltrados = dadosCompletos;
|
||||||
|
|
||||||
|
// Filtro por período
|
||||||
|
if (filtros.periodoDe && filtros.periodoAte) {
|
||||||
|
dadosFiltrados = dadosFiltrados.filter((item: DREItem) => {
|
||||||
|
const dataItem = item.data_competencia;
|
||||||
|
return dataItem >= filtros.periodoDe && dataItem <= filtros.periodoAte;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtro por grupo
|
||||||
|
if (filtros.grupo !== "Todos") {
|
||||||
|
dadosFiltrados = dadosFiltrados.filter((item: DREItem) =>
|
||||||
|
item.grupo === filtros.grupo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtro por subgrupo
|
||||||
|
if (filtros.subgrupo !== "Todos") {
|
||||||
|
dadosFiltrados = dadosFiltrados.filter((item: DREItem) =>
|
||||||
|
item.subgrupo === filtros.subgrupo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtro por centro de custo
|
||||||
|
if (filtros.centroCusto !== "Todos") {
|
||||||
|
dadosFiltrados = dadosFiltrados.filter((item: DREItem) =>
|
||||||
|
item.centro_custo === filtros.centroCusto
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtro por conta
|
||||||
|
if (filtros.conta !== "Todas") {
|
||||||
|
dadosFiltrados = dadosFiltrados.filter((item: DREItem) =>
|
||||||
|
item.conta === filtros.conta
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtro por valor mínimo
|
||||||
|
if (filtros.valorMin) {
|
||||||
|
const valorMin = parseFloat(filtros.valorMin.replace(',', '.'));
|
||||||
|
dadosFiltrados = dadosFiltrados.filter((item: DREItem) =>
|
||||||
|
parseFloat(item.valor) >= valorMin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtro por valor máximo
|
||||||
|
if (filtros.valorMax) {
|
||||||
|
const valorMax = parseFloat(filtros.valorMax.replace(',', '.'));
|
||||||
|
dadosFiltrados = dadosFiltrados.filter((item: DREItem) =>
|
||||||
|
parseFloat(item.valor) <= valorMax
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtro por busca textual
|
||||||
|
if (filtros.buscaTextual) {
|
||||||
|
const termoBusca = filtros.buscaTextual.toLowerCase();
|
||||||
|
dadosFiltrados = dadosFiltrados.filter((item: DREItem) =>
|
||||||
|
item.grupo.toLowerCase().includes(termoBusca) ||
|
||||||
|
item.subgrupo.toLowerCase().includes(termoBusca) ||
|
||||||
|
item.centro_custo.toLowerCase().includes(termoBusca) ||
|
||||||
|
item.conta.toLowerCase().includes(termoBusca)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setData(dadosFiltrados);
|
||||||
|
setDadosFiltrados(dadosFiltrados);
|
||||||
|
setFiltrosAplicados(true);
|
||||||
|
|
||||||
|
// Extrair meses únicos dos dados filtrados
|
||||||
|
const mesesUnicos = [...new Set(dadosFiltrados.map(item => item.data_competencia))].sort();
|
||||||
|
setMesesDisponiveis(mesesUnicos);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erro ao aplicar filtros:", error);
|
||||||
|
setError(error instanceof Error ? error.message : "Erro desconhecido");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, 300); // Aguardar 300ms para a animação de fechamento
|
||||||
|
};
|
||||||
|
|
||||||
const calcularValoresPorMes = (items: DREItem[]): Record<string, number> => {
|
const calcularValoresPorMes = (items: DREItem[]): Record<string, number> => {
|
||||||
const valoresPorMes: Record<string, number> = {};
|
const valoresPorMes: Record<string, number> = {};
|
||||||
|
|
||||||
|
|
@ -587,113 +808,9 @@ export default function Teste() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
// Loading será tratado dentro do componente principal
|
||||||
return (
|
|
||||||
<div className="w-full max-w-7xl mx-auto p-6">
|
|
||||||
<div className="mb-6">
|
|
||||||
<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">
|
|
||||||
<svg
|
|
||||||
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>
|
|
||||||
</div> */}
|
|
||||||
<div>
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-white rounded-xl shadow-lg border border-gray-200 p-12">
|
// Error será tratado dentro do componente principal
|
||||||
<div className="flex flex-col items-center justify-center">
|
|
||||||
<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" />
|
|
||||||
</div>
|
|
||||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
|
||||||
Carregando dados...
|
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
Aguarde enquanto processamos as informações
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return (
|
|
||||||
<div className="w-full max-w-7xl mx-auto p-6">
|
|
||||||
<div className="mb-6">
|
|
||||||
<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">
|
|
||||||
<svg
|
|
||||||
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>
|
|
||||||
</div> */}
|
|
||||||
<div>
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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="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"
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
<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">
|
|
||||||
{error}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const hierarchicalData = buildHierarchicalData();
|
const hierarchicalData = buildHierarchicalData();
|
||||||
|
|
||||||
|
|
@ -701,22 +818,8 @@ export default function Teste() {
|
||||||
<div className="w-full max-w-none mx-auto p-6">
|
<div className="w-full max-w-none mx-auto p-6">
|
||||||
{/* 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 justify-between 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="flex items-center gap-3">
|
||||||
<svg
|
|
||||||
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>
|
|
||||||
</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">
|
<p className="text-sm text-gray-500">
|
||||||
|
|
@ -724,9 +827,245 @@ export default function Teste() {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Botão de Filtro */}
|
||||||
|
<Sheet open={isFilterOpen} onOpenChange={setIsFilterOpen}>
|
||||||
|
<SheetTrigger asChild>
|
||||||
|
<Button variant="outline" className="flex items-center gap-2">
|
||||||
|
<Filter className="w-4 h-4" />
|
||||||
|
Filtros
|
||||||
|
</Button>
|
||||||
|
</SheetTrigger>
|
||||||
|
<SheetContent className="w-[400px] sm:w-[540px]">
|
||||||
|
<SheetHeader>
|
||||||
|
<SheetTitle>Filtros</SheetTitle>
|
||||||
|
<SheetDescription>
|
||||||
|
Ajuste os critérios e clique em Pesquisar para atualizar a visão.
|
||||||
|
</SheetDescription>
|
||||||
|
</SheetHeader>
|
||||||
|
|
||||||
|
<div className="grid flex-1 auto-rows-min gap-6 py-4">
|
||||||
|
{/* Período */}
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label>Período</Label>
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="periodo-de" className="text-xs text-gray-500">DE</Label>
|
||||||
|
<Select value={filtros.periodoDe} onValueChange={(value) => handleFiltroChange('periodoDe', value)}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{mesesDisponiveis.map(mes => (
|
||||||
|
<SelectItem key={mes} value={mes}>{mes}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="periodo-ate" className="text-xs text-gray-500">ATÉ</Label>
|
||||||
|
<Select value={filtros.periodoAte} onValueChange={(value) => handleFiltroChange('periodoAte', value)}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{mesesDisponiveis.map(mes => (
|
||||||
|
<SelectItem key={mes} value={mes}>{mes}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Grupo */}
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="grupo">GRUPO</Label>
|
||||||
|
<Select value={filtros.grupo} onValueChange={(value) => handleFiltroChange('grupo', value)}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Todos">Todos</SelectItem>
|
||||||
|
{opcoesGrupos.map(grupo => (
|
||||||
|
<SelectItem key={grupo} value={grupo}>{grupo}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Subgrupo */}
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="subgrupo">SUBGRUPO</Label>
|
||||||
|
<Select value={filtros.subgrupo} onValueChange={(value) => handleFiltroChange('subgrupo', value)}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Todos">Todos</SelectItem>
|
||||||
|
{opcoesSubgrupos.map(subgrupo => (
|
||||||
|
<SelectItem key={subgrupo} value={subgrupo}>{subgrupo}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Centro de Custo */}
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="centro-custo">CENTRO DE CUSTO</Label>
|
||||||
|
<Select value={filtros.centroCusto} onValueChange={(value) => handleFiltroChange('centroCusto', value)}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Todos">Todos</SelectItem>
|
||||||
|
{opcoesCentrosCusto.map(centro => (
|
||||||
|
<SelectItem key={centro} value={centro}>{centro}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Conta */}
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="conta">CONTA</Label>
|
||||||
|
<Select value={filtros.conta} onValueChange={(value) => handleFiltroChange('conta', value)}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Todas">Todas</SelectItem>
|
||||||
|
{opcoesContas.map(conta => (
|
||||||
|
<SelectItem key={conta} value={conta}>{conta}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Valor */}
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label>Valor</Label>
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="valor-min" className="text-xs text-gray-500">VALOR MÍN.</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<span className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500">R$</span>
|
||||||
|
<Input
|
||||||
|
id="valor-min"
|
||||||
|
value={filtros.valorMin}
|
||||||
|
onChange={(e) => handleFiltroChange('valorMin', e.target.value)}
|
||||||
|
className="pl-8"
|
||||||
|
placeholder="0,00"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="valor-max" className="text-xs text-gray-500">VALOR MÁX.</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<span className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500">R$</span>
|
||||||
|
<Input
|
||||||
|
id="valor-max"
|
||||||
|
value={filtros.valorMax}
|
||||||
|
onChange={(e) => handleFiltroChange('valorMax', e.target.value)}
|
||||||
|
className="pl-8"
|
||||||
|
placeholder="0,00"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Busca Textual */}
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="busca-textual">BUSCA TEXTUAL</Label>
|
||||||
|
<Input
|
||||||
|
id="busca-textual"
|
||||||
|
value={filtros.buscaTextual}
|
||||||
|
onChange={(e) => handleFiltroChange('buscaTextual', e.target.value)}
|
||||||
|
placeholder="Pesquise por grupo, subgrupo, centro ou conta"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SheetFooter className="flex gap-3">
|
||||||
|
<Button variant="outline" onClick={limparFiltros} className="flex-1">
|
||||||
|
Limpar filtros
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" onClick={() => setIsFilterOpen(false)} className="flex-1">
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
<Button onClick={aplicarFiltros} className="flex-1">
|
||||||
|
Pesquisar
|
||||||
|
</Button>
|
||||||
|
</SheetFooter>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Loading quando aplicando filtros */}
|
||||||
|
{loading && (
|
||||||
|
<div className="bg-white rounded-xl shadow-lg border border-gray-200 p-8 text-center">
|
||||||
|
<div className="flex flex-col items-center gap-4">
|
||||||
|
<div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center">
|
||||||
|
<LoaderPinwheel className="w-8 h-8 text-blue-600 animate-spin" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||||
|
Aplicando filtros...
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-500">
|
||||||
|
Aguarde enquanto processamos os dados.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Erro */}
|
||||||
|
{error && !loading && (
|
||||||
|
<div className="bg-white rounded-xl shadow-lg border border-red-200 p-8 text-center">
|
||||||
|
<div className="flex flex-col items-center gap-4">
|
||||||
|
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center">
|
||||||
|
<svg 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>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-red-900 mb-2">
|
||||||
|
Erro ao carregar dados
|
||||||
|
</h3>
|
||||||
|
<p className="text-red-600 mb-4">{error}</p>
|
||||||
|
<Button onClick={() => aplicarFiltros()} className="flex items-center gap-2">
|
||||||
|
Tentar novamente
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Mensagem quando não há dados */}
|
||||||
|
{!filtrosAplicados && !loading && !error && (
|
||||||
|
<div className="bg-white rounded-xl shadow-lg border border-gray-200 p-8 text-center">
|
||||||
|
<div className="flex flex-col items-center gap-4">
|
||||||
|
<div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center">
|
||||||
|
<Filter className="w-8 h-8 text-gray-400" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||||
|
Nenhum dado exibido
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-500 mb-4">
|
||||||
|
Clique no botão "Filtros" para definir os critérios de busca e visualizar os dados do DRE.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Table Container */}
|
{/* Table Container */}
|
||||||
|
{filtrosAplicados && !loading && !error && (
|
||||||
<div className="bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden">
|
<div className="bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden">
|
||||||
{/* Table Header */}
|
{/* Table Header */}
|
||||||
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-gray-200 sticky top-0 z-20">
|
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-gray-200 sticky top-0 z-20">
|
||||||
|
|
@ -829,6 +1168,7 @@ export default function Teste() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Componente Analítico */}
|
{/* Componente Analítico */}
|
||||||
{!loading && data.length > 0 && (
|
{!loading && data.length > 0 && (
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const labelVariants = cva(
|
||||||
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Label = React.forwardRef<
|
||||||
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||||
|
VariantProps<typeof labelVariants>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(labelVariants(), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Label.displayName = LabelPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Label }
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
import { X } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Sheet = SheetPrimitive.Root
|
||||||
|
|
||||||
|
const SheetTrigger = SheetPrimitive.Trigger
|
||||||
|
|
||||||
|
const SheetClose = SheetPrimitive.Close
|
||||||
|
|
||||||
|
const SheetPortal = SheetPrimitive.Portal
|
||||||
|
|
||||||
|
const SheetOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Overlay
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const sheetVariants = cva(
|
||||||
|
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
side: {
|
||||||
|
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
||||||
|
bottom:
|
||||||
|
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||||
|
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
||||||
|
right:
|
||||||
|
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
side: "right",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
interface SheetContentProps
|
||||||
|
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||||
|
VariantProps<typeof sheetVariants> {}
|
||||||
|
|
||||||
|
const SheetContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||||
|
SheetContentProps
|
||||||
|
>(({ side = "right", className, children, ...props }, ref) => (
|
||||||
|
<SheetPortal>
|
||||||
|
<SheetOverlay />
|
||||||
|
<SheetPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(sheetVariants({ side }), className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary z-50">
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</SheetPrimitive.Close>
|
||||||
|
</SheetPrimitive.Content>
|
||||||
|
</SheetPortal>
|
||||||
|
))
|
||||||
|
SheetContent.displayName = SheetPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const SheetHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col space-y-2 text-center sm:text-left",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
SheetHeader.displayName = "SheetHeader"
|
||||||
|
|
||||||
|
const SheetFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
SheetFooter.displayName = "SheetFooter"
|
||||||
|
|
||||||
|
const SheetTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-lg font-semibold text-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const SheetDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SheetDescription.displayName = SheetPrimitive.Description.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Sheet,
|
||||||
|
SheetPortal,
|
||||||
|
SheetOverlay,
|
||||||
|
SheetTrigger,
|
||||||
|
SheetClose,
|
||||||
|
SheetContent,
|
||||||
|
SheetHeader,
|
||||||
|
SheetFooter,
|
||||||
|
SheetTitle,
|
||||||
|
SheetDescription,
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue