diff --git a/src/app/DRE/analitico.tsx b/src/app/DRE/analitico.tsx index 7745a36..3fc47d2 100644 --- a/src/app/DRE/analitico.tsx +++ b/src/app/DRE/analitico.tsx @@ -1,7 +1,7 @@ "use client"; import * as React from "react"; -import { DataGrid, GridToolbar } from "@mui/x-data-grid"; +import { DataGrid, GridToolbar, GridColDef, GridFilterModel } from "@mui/x-data-grid"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -11,6 +11,7 @@ import { DialogHeader, DialogTitle, DialogFooter, + DialogTrigger, } from "@/components/ui/dialog"; import { Select, @@ -19,7 +20,8 @@ import { SelectContent, SelectItem, } from "@/components/ui/select"; -import { Download, Filter, X } from "lucide-react"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Download, Filter, X, Search, ArrowUpDown, ArrowUp, ArrowDown } from "lucide-react"; import * as XLSX from "xlsx"; interface AnaliticoItem { @@ -65,11 +67,207 @@ interface AnaliticoProps { }; } +// Componente de filtro customizado estilo Excel +interface ExcelFilterProps { + column: GridColDef; + data: any[]; + onFilterChange: (field: string, values: string[]) => void; + onSortChange: (field: string, direction: 'asc' | 'desc' | null) => void; + currentFilter?: string[]; + currentSort?: 'asc' | 'desc' | null; +} + +const ExcelFilter: React.FC = ({ + column, + data, + onFilterChange, + onSortChange, + currentFilter = [], + currentSort = null, +}) => { + const [isOpen, setIsOpen] = React.useState(false); + const [searchTerm, setSearchTerm] = React.useState(""); + const [selectedValues, setSelectedValues] = React.useState(currentFilter); + const [selectAll, setSelectAll] = React.useState(false); + + // Obter valores únicos da coluna + const uniqueValues = React.useMemo(() => { + const values = data + .map((row) => { + const value = row[column.field]; + if (value === null || value === undefined) return ""; + return String(value); + }) + .filter((value, index, self) => self.indexOf(value) === index && value !== "") + .sort(); + + return values; + }, [data, column.field]); + + // Filtrar valores baseado na busca + const filteredValues = React.useMemo(() => { + if (!searchTerm) return uniqueValues; + return uniqueValues.filter((value) => + value.toLowerCase().includes(searchTerm.toLowerCase()) + ); + }, [uniqueValues, searchTerm]); + + // Verificar se todos estão selecionados + React.useEffect(() => { + setSelectAll(selectedValues.length === filteredValues.length && filteredValues.length > 0); + }, [selectedValues, filteredValues]); + + const handleSelectAll = (checked: boolean) => { + if (checked) { + setSelectedValues(filteredValues); + } else { + setSelectedValues([]); + } + }; + + const handleValueToggle = (value: string, checked: boolean) => { + if (checked) { + setSelectedValues([...selectedValues, value]); + } else { + setSelectedValues(selectedValues.filter((v) => v !== value)); + } + }; + + const handleApply = () => { + onFilterChange(column.field, selectedValues); + setIsOpen(false); + }; + + const handleClear = () => { + setSelectedValues([]); + onFilterChange(column.field, []); + setIsOpen(false); + }; + + const handleSort = (direction: 'asc' | 'desc') => { + onSortChange(column.field, direction); + setIsOpen(false); + }; + + return ( + + + + + + + + Filtrar por "{column.headerName}" + + + +
+ {/* Opções de ordenação */} +
+
Ordenar
+
+ + +
+
+ +
+ +
+ + {/* Barra de pesquisa */} +
+ + setSearchTerm(e.target.value)} + className="pl-8 h-8 text-sm" + /> +
+ + {/* Lista de valores com checkboxes */} +
+
+
+ + +
+ + {filteredValues.map((value) => ( +
+ handleValueToggle(value, checked)} + /> + +
+ ))} +
+
+ + {/* Botões de ação */} + + + + +
+
+
+ ); +}; + export default function AnaliticoComponent({ filtros }: AnaliticoProps) { const [data, setData] = React.useState([]); const [loading, setLoading] = React.useState(false); const [globalFilter, setGlobalFilter] = React.useState(""); const [open, setOpen] = React.useState(false); + const [columnFilters, setColumnFilters] = React.useState>({}); + const [columnSorts, setColumnSorts] = React.useState>({}); const [conditions, setConditions] = React.useState([ { column: "", operator: "contains", value: "" }, ]); @@ -77,6 +275,21 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) { // Estado para armazenar filtros externos (vindos do teste.tsx) const [filtrosExternos, setFiltrosExternos] = React.useState(filtros); + // Funções para gerenciar filtros customizados + const handleColumnFilterChange = React.useCallback((field: string, values: string[]) => { + setColumnFilters(prev => ({ + ...prev, + [field]: values + })); + }, []); + + const handleColumnSortChange = React.useCallback((field: string, direction: 'asc' | 'desc' | null) => { + setColumnSorts(prev => ({ + ...prev, + [field]: direction + })); + }, []); + // Atualizar filtros externos quando os props mudarem React.useEffect(() => { console.log('🔄 Analítico - useEffect dos filtros chamado'); @@ -145,199 +358,219 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) { }, [fetchData]); // Definir colunas do DataGridPro - const columns = React.useMemo(() => [ - { - field: "data_vencimento", - headerName: "Dt Venc", - width: 150, - sortable: true, - resizable: true, - renderCell: (params: any) => { - if (!params.value) return "-"; - try { - return new Date(params.value).toLocaleDateString("pt-BR"); - } catch (error) { - return params.value; - } + const columns = React.useMemo(() => { + const baseColumns = [ + { + field: "data_vencimento", + headerName: "Dt Venc", + width: 150, + sortable: true, + resizable: true, + renderCell: (params: any) => { + if (!params.value) return "-"; + try { + return new Date(params.value).toLocaleDateString("pt-BR"); + } catch (error) { + return params.value; + } + }, }, - }, - { - field: "data_caixa", - headerName: "Dt Caixa", - width: 130, - sortable: true, - resizable: true, - renderCell: (params: any) => { - if (!params.value) return "-"; - try { - return new Date(params.value).toLocaleDateString("pt-BR"); - } catch (error) { - return params.value; - } + { + field: "data_caixa", + headerName: "Dt Caixa", + width: 130, + sortable: true, + resizable: true, + renderCell: (params: any) => { + if (!params.value) return "-"; + try { + return new Date(params.value).toLocaleDateString("pt-BR"); + } catch (error) { + return params.value; + } + }, }, - }, - { - field: "entidade", - headerName: "Entidade", - width: 100, - sortable: true, - resizable: true, - renderCell: (params: any) => params.value || "-", - }, - { - field: "codigo_fornecedor", - headerName: "Cod.Fornec", - width: 140, - sortable: true, - resizable: true, - }, - { - field: "nome_fornecedor", - headerName: "Fornecedor", - flex: 1, - minWidth: 200, - sortable: true, - resizable: true, - }, - { - field: "codigo_centrocusto", - headerName: "C Custo", - width: 130, - sortable: true, - resizable: true, - }, - { - field: "codigo_conta", - headerName: "Cod.Conta", - width: 150, - sortable: true, - resizable: true, - }, - { - field: "conta", - headerName: "Conta", - flex: 1, - minWidth: 180, - sortable: true, - resizable: true, - }, - { - field: "valor", - headerName: "Vl.Realizado", - type: "number" as const, - width: 140, - sortable: true, - resizable: true, - renderCell: (params: any) => { - const value = params.value; - if (value === null || value === undefined || value === "") return "-"; - const numValue = typeof value === "string" ? parseFloat(value) : Number(value); - if (isNaN(numValue)) return "-"; - const formatted = new Intl.NumberFormat("pt-BR", { - style: "currency", - currency: "BRL", - }).format(numValue); - return ( - - {formatted} - - ); + { + field: "entidade", + headerName: "Entidade", + width: 100, + sortable: true, + resizable: true, + renderCell: (params: any) => params.value || "-", }, - }, - { - field: "valor_previsto", - headerName: "Vl.Previsto", - type: "number" as const, - width: 130, - sortable: true, - resizable: true, - renderCell: (params: any) => { - const value = params.value; - if (value === null || value === undefined || value === "" || value === 0) return "-"; - const numValue = typeof value === "string" ? parseFloat(value) : Number(value); - if (isNaN(numValue)) return "-"; - const formatted = new Intl.NumberFormat("pt-BR", { - style: "currency", - currency: "BRL", - }).format(numValue); - return ( - - {formatted} - - ); + { + field: "codigo_fornecedor", + headerName: "Cod.Fornec", + width: 140, + sortable: true, + resizable: true, }, - }, - { - field: "valor_confirmado", - headerName: "Vl.Confirmado", - type: "number" as const, - width: 140, - sortable: true, - resizable: true, - renderCell: (params: any) => { - const value = params.value; - if (value === null || value === undefined || value === "" || value === 0) return "-"; - const numValue = typeof value === "string" ? parseFloat(value) : Number(value); - if (isNaN(numValue)) return "-"; - const formatted = new Intl.NumberFormat("pt-BR", { - style: "currency", - currency: "BRL", - }).format(numValue); - return ( - - {formatted} - - ); + { + field: "nome_fornecedor", + headerName: "Fornecedor", + flex: 1, + minWidth: 200, + sortable: true, + resizable: true, }, - }, - { - field: "valor_pago", - headerName: "Vl.Pago", - type: "number" as const, - width: 130, - sortable: true, - resizable: true, - renderCell: (params: any) => { - const value = params.value; - if (value === null || value === undefined || value === "" || value === 0) return "-"; - const numValue = typeof value === "string" ? parseFloat(value) : Number(value); - if (isNaN(numValue)) return "-"; - const formatted = new Intl.NumberFormat("pt-BR", { - style: "currency", - currency: "BRL", - }).format(numValue); - return ( - - {formatted} - - ); + { + field: "codigo_centrocusto", + headerName: "C Custo", + width: 130, + sortable: true, + resizable: true, }, - }, - { - field: "historico", - headerName: "Historico", - flex: 1, - minWidth: 250, - sortable: true, - resizable: true, - }, - { - field: "historico2", - headerName: "Historico 2", - flex: 1, - minWidth: 300, - sortable: true, - resizable: true, - }, - { - field: "numero_lancamento", - headerName: "Num.Lanc", - width: 80, - sortable: true, - resizable: true, - renderCell: (params: any) => params.value || "-", - }, - ] as any, []); + { + field: "codigo_conta", + headerName: "Cod.Conta", + width: 150, + sortable: true, + resizable: true, + }, + { + field: "conta", + headerName: "Conta", + flex: 1, + minWidth: 180, + sortable: true, + resizable: true, + }, + { + field: "valor", + headerName: "Vl.Realizado", + type: "number" as const, + width: 140, + sortable: true, + resizable: true, + renderCell: (params: any) => { + const value = params.value; + if (value === null || value === undefined || value === "") return "-"; + const numValue = typeof value === "string" ? parseFloat(value) : Number(value); + if (isNaN(numValue)) return "-"; + const formatted = new Intl.NumberFormat("pt-BR", { + style: "currency", + currency: "BRL", + }).format(numValue); + return ( + + {formatted} + + ); + }, + }, + { + field: "valor_previsto", + headerName: "Vl.Previsto", + type: "number" as const, + width: 130, + sortable: true, + resizable: true, + renderCell: (params: any) => { + const value = params.value; + if (value === null || value === undefined || value === "" || value === 0) return "-"; + const numValue = typeof value === "string" ? parseFloat(value) : Number(value); + if (isNaN(numValue)) return "-"; + const formatted = new Intl.NumberFormat("pt-BR", { + style: "currency", + currency: "BRL", + }).format(numValue); + return ( + + {formatted} + + ); + }, + }, + { + field: "valor_confirmado", + headerName: "Vl.Confirmado", + type: "number" as const, + width: 140, + sortable: true, + resizable: true, + renderCell: (params: any) => { + const value = params.value; + if (value === null || value === undefined || value === "" || value === 0) return "-"; + const numValue = typeof value === "string" ? parseFloat(value) : Number(value); + if (isNaN(numValue)) return "-"; + const formatted = new Intl.NumberFormat("pt-BR", { + style: "currency", + currency: "BRL", + }).format(numValue); + return ( + + {formatted} + + ); + }, + }, + { + field: "valor_pago", + headerName: "Vl.Pago", + type: "number" as const, + width: 130, + sortable: true, + resizable: true, + renderCell: (params: any) => { + const value = params.value; + if (value === null || value === undefined || value === "" || value === 0) return "-"; + const numValue = typeof value === "string" ? parseFloat(value) : Number(value); + if (isNaN(numValue)) return "-"; + const formatted = new Intl.NumberFormat("pt-BR", { + style: "currency", + currency: "BRL", + }).format(numValue); + return ( + + {formatted} + + ); + }, + }, + { + field: "historico", + headerName: "Historico", + flex: 1, + minWidth: 250, + sortable: true, + resizable: true, + }, + { + field: "historico2", + headerName: "Historico 2", + flex: 1, + minWidth: 300, + sortable: true, + resizable: true, + }, + { + field: "numero_lancamento", + headerName: "Num.Lanc", + width: 80, + sortable: true, + resizable: true, + renderCell: (params: any) => params.value || "-", + }, + ]; + + // Adicionar renderHeader com filtro Excel para todas as colunas + return baseColumns.map((col) => ({ + ...col, + renderHeader: (params: any) => ( +
+ {col.headerName} + +
+ ), + })); + }, [data, columnFilters, columnSorts, handleColumnFilterChange, handleColumnSortChange]); // Calcular totais das colunas de valores const columnTotals = React.useMemo(() => {