fix: filtro customizado
This commit is contained in:
parent
9f7e19d80d
commit
a7adda84a5
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
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 { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
|
|
@ -19,7 +20,8 @@ import {
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
} from "@/components/ui/select";
|
} 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";
|
import * as XLSX from "xlsx";
|
||||||
|
|
||||||
interface AnaliticoItem {
|
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<ExcelFilterProps> = ({
|
||||||
|
column,
|
||||||
|
data,
|
||||||
|
onFilterChange,
|
||||||
|
onSortChange,
|
||||||
|
currentFilter = [],
|
||||||
|
currentSort = null,
|
||||||
|
}) => {
|
||||||
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
|
const [searchTerm, setSearchTerm] = React.useState("");
|
||||||
|
const [selectedValues, setSelectedValues] = React.useState<string[]>(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 (
|
||||||
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
onClick={() => setIsOpen(true)}
|
||||||
|
>
|
||||||
|
<ArrowUpDown className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="text-sm font-medium">
|
||||||
|
Filtrar por "{column.headerName}"
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Opções de ordenação */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="text-xs font-medium text-gray-600">Ordenar</div>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 text-xs"
|
||||||
|
onClick={() => handleSort('asc')}
|
||||||
|
>
|
||||||
|
<ArrowUp className="h-3 w-3 mr-1" />
|
||||||
|
A a Z
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 text-xs"
|
||||||
|
onClick={() => handleSort('desc')}
|
||||||
|
>
|
||||||
|
<ArrowDown className="h-3 w-3 mr-1" />
|
||||||
|
Z a A
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border-t pt-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 text-xs text-red-600 hover:text-red-700"
|
||||||
|
onClick={handleClear}
|
||||||
|
>
|
||||||
|
<X className="h-3 w-3 mr-1" />
|
||||||
|
Limpar Filtro
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Barra de pesquisa */}
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="absolute left-2 top-2.5 h-4 w-4 text-gray-400" />
|
||||||
|
<Input
|
||||||
|
placeholder="Pesquisar"
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
className="pl-8 h-8 text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Lista de valores com checkboxes */}
|
||||||
|
<div className="max-h-60 overflow-y-auto border rounded-md">
|
||||||
|
<div className="p-2">
|
||||||
|
<div className="flex items-center space-x-2 py-1">
|
||||||
|
<Checkbox
|
||||||
|
id="select-all"
|
||||||
|
checked={selectAll}
|
||||||
|
onCheckedChange={handleSelectAll}
|
||||||
|
/>
|
||||||
|
<label htmlFor="select-all" className="text-sm font-medium">
|
||||||
|
(Selecionar Tudo)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{filteredValues.map((value) => (
|
||||||
|
<div key={value} className="flex items-center space-x-2 py-1">
|
||||||
|
<Checkbox
|
||||||
|
id={`value-${value}`}
|
||||||
|
checked={selectedValues.includes(value)}
|
||||||
|
onCheckedChange={(checked: boolean) => handleValueToggle(value, checked)}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`value-${value}`} className="text-sm">
|
||||||
|
{value}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Botões de ação */}
|
||||||
|
<DialogFooter className="flex space-x-2">
|
||||||
|
<Button variant="outline" size="sm" onClick={() => setIsOpen(false)}>
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" onClick={handleApply}>
|
||||||
|
OK
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
const [data, setData] = React.useState<AnaliticoItem[]>([]);
|
const [data, setData] = React.useState<AnaliticoItem[]>([]);
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
const [globalFilter, setGlobalFilter] = React.useState("");
|
const [globalFilter, setGlobalFilter] = React.useState("");
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
|
const [columnFilters, setColumnFilters] = React.useState<Record<string, string[]>>({});
|
||||||
|
const [columnSorts, setColumnSorts] = React.useState<Record<string, 'asc' | 'desc' | null>>({});
|
||||||
const [conditions, setConditions] = React.useState([
|
const [conditions, setConditions] = React.useState([
|
||||||
{ column: "", operator: "contains", value: "" },
|
{ column: "", operator: "contains", value: "" },
|
||||||
]);
|
]);
|
||||||
|
|
@ -77,6 +275,21 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
// Estado para armazenar filtros externos (vindos do teste.tsx)
|
// Estado para armazenar filtros externos (vindos do teste.tsx)
|
||||||
const [filtrosExternos, setFiltrosExternos] = React.useState(filtros);
|
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
|
// Atualizar filtros externos quando os props mudarem
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
console.log('🔄 Analítico - useEffect dos filtros chamado');
|
console.log('🔄 Analítico - useEffect dos filtros chamado');
|
||||||
|
|
@ -145,7 +358,8 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
}, [fetchData]);
|
}, [fetchData]);
|
||||||
|
|
||||||
// Definir colunas do DataGridPro
|
// Definir colunas do DataGridPro
|
||||||
const columns = React.useMemo(() => [
|
const columns = React.useMemo(() => {
|
||||||
|
const baseColumns = [
|
||||||
{
|
{
|
||||||
field: "data_vencimento",
|
field: "data_vencimento",
|
||||||
headerName: "Dt Venc",
|
headerName: "Dt Venc",
|
||||||
|
|
@ -337,7 +551,26 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
resizable: true,
|
resizable: true,
|
||||||
renderCell: (params: any) => params.value || "-",
|
renderCell: (params: any) => params.value || "-",
|
||||||
},
|
},
|
||||||
] as any, []);
|
];
|
||||||
|
|
||||||
|
// Adicionar renderHeader com filtro Excel para todas as colunas
|
||||||
|
return baseColumns.map((col) => ({
|
||||||
|
...col,
|
||||||
|
renderHeader: (params: any) => (
|
||||||
|
<div className="flex items-center justify-between w-full">
|
||||||
|
<span className="text-sm font-medium">{col.headerName}</span>
|
||||||
|
<ExcelFilter
|
||||||
|
column={col}
|
||||||
|
data={data}
|
||||||
|
onFilterChange={handleColumnFilterChange}
|
||||||
|
onSortChange={handleColumnSortChange}
|
||||||
|
currentFilter={columnFilters[col.field] || []}
|
||||||
|
currentSort={columnSorts[col.field] || null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
}, [data, columnFilters, columnSorts, handleColumnFilterChange, handleColumnSortChange]);
|
||||||
|
|
||||||
// Calcular totais das colunas de valores
|
// Calcular totais das colunas de valores
|
||||||
const columnTotals = React.useMemo(() => {
|
const columnTotals = React.useMemo(() => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue