2026-01-30 19:59:44 +00:00
|
|
|
"use client";
|
|
|
|
|
|
2026-02-06 19:51:04 +00:00
|
|
|
import { Search, X, SlidersHorizontal, Download } from "lucide-react";
|
2026-01-30 19:59:44 +00:00
|
|
|
|
|
|
|
|
import { Input } from "@/components/ui/input";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
import { Badge } from "@/components/ui/badge";
|
|
|
|
|
import {
|
|
|
|
|
DropdownMenu,
|
|
|
|
|
DropdownMenuCheckboxItem,
|
|
|
|
|
DropdownMenuContent,
|
|
|
|
|
DropdownMenuLabel,
|
|
|
|
|
DropdownMenuSeparator,
|
|
|
|
|
DropdownMenuTrigger,
|
|
|
|
|
} from "@/components/ui/dropdown-menu";
|
|
|
|
|
import {
|
|
|
|
|
Select,
|
|
|
|
|
SelectContent,
|
|
|
|
|
SelectItem,
|
|
|
|
|
SelectTrigger,
|
|
|
|
|
SelectValue,
|
|
|
|
|
} from "@/components/ui/select";
|
2026-02-06 19:51:04 +00:00
|
|
|
import { ProjectStatus, STATUS_CONFIG, Project } from "@/types/project";
|
|
|
|
|
import { ColumnVisibilityInfo } from "./data-table";
|
|
|
|
|
|
|
|
|
|
// Mapeo de IDs de columna a nombres legibles
|
|
|
|
|
const COLUMN_NAMES: Record<string, string> = {
|
|
|
|
|
ref: "Referencia",
|
|
|
|
|
name: "Nombre",
|
|
|
|
|
client: "Cliente",
|
|
|
|
|
status: "Estado",
|
|
|
|
|
progress: "Progreso",
|
|
|
|
|
budget: "Presupuesto",
|
|
|
|
|
startDate: "Fecha inicio",
|
|
|
|
|
endDate: "Fecha fin",
|
|
|
|
|
};
|
2026-01-30 19:59:44 +00:00
|
|
|
|
|
|
|
|
interface DataTableToolbarProps {
|
|
|
|
|
searchValue: string;
|
|
|
|
|
onSearchChange: (value: string) => void;
|
|
|
|
|
statusFilter: string;
|
|
|
|
|
onStatusFilterChange: (value: string) => void;
|
|
|
|
|
selectedCount: number;
|
|
|
|
|
onClearFilters: () => void;
|
2026-02-06 19:51:04 +00:00
|
|
|
columns?: ColumnVisibilityInfo[];
|
|
|
|
|
onExport?: (projects: Project[]) => void;
|
|
|
|
|
getSelectedProjects?: () => Project[];
|
|
|
|
|
getAllVisibleProjects?: () => Project[];
|
2026-01-30 19:59:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function DataTableToolbar({
|
|
|
|
|
searchValue,
|
|
|
|
|
onSearchChange,
|
|
|
|
|
statusFilter,
|
|
|
|
|
onStatusFilterChange,
|
|
|
|
|
selectedCount,
|
|
|
|
|
onClearFilters,
|
2026-02-06 19:51:04 +00:00
|
|
|
columns,
|
|
|
|
|
onExport,
|
|
|
|
|
getSelectedProjects,
|
|
|
|
|
getAllVisibleProjects,
|
2026-01-30 19:59:44 +00:00
|
|
|
}: DataTableToolbarProps) {
|
|
|
|
|
const isFiltered = searchValue !== "" || statusFilter !== "all";
|
|
|
|
|
|
2026-02-06 19:51:04 +00:00
|
|
|
// Obtener proyectos para exportar
|
|
|
|
|
const handleExport = () => {
|
|
|
|
|
if (!onExport) return;
|
|
|
|
|
|
|
|
|
|
if (selectedCount > 0 && getSelectedProjects) {
|
|
|
|
|
// Exportar solo los seleccionados
|
|
|
|
|
onExport(getSelectedProjects());
|
|
|
|
|
} else if (getAllVisibleProjects) {
|
|
|
|
|
// Exportar todos los visibles (filtrados)
|
|
|
|
|
onExport(getAllVisibleProjects());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-30 19:59:44 +00:00
|
|
|
return (
|
|
|
|
|
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
|
|
|
<div className="flex flex-1 items-center gap-2">
|
|
|
|
|
{/* Búsqueda */}
|
|
|
|
|
<div className="relative w-full sm:w-[300px]">
|
|
|
|
|
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
|
|
|
|
<Input
|
|
|
|
|
placeholder="Buscar proyectos..."
|
|
|
|
|
value={searchValue}
|
|
|
|
|
onChange={(e) => onSearchChange(e.target.value)}
|
|
|
|
|
className="pl-8"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Filtro por estado */}
|
|
|
|
|
<Select value={statusFilter} onValueChange={onStatusFilterChange}>
|
|
|
|
|
<SelectTrigger className="w-[150px]">
|
|
|
|
|
<SelectValue placeholder="Estado" />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
<SelectItem value="all">Todos</SelectItem>
|
|
|
|
|
{(Object.entries(STATUS_CONFIG) as [ProjectStatus, { label: string }][]).map(
|
|
|
|
|
([value, { label }]) => (
|
|
|
|
|
<SelectItem key={value} value={value}>
|
|
|
|
|
{label}
|
|
|
|
|
</SelectItem>
|
|
|
|
|
)
|
|
|
|
|
)}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
|
|
|
|
|
{/* Botón limpiar filtros */}
|
|
|
|
|
{isFiltered && (
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
onClick={onClearFilters}
|
|
|
|
|
className="h-9 px-2 lg:px-3"
|
|
|
|
|
>
|
|
|
|
|
Limpiar
|
|
|
|
|
<X className="ml-2 h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
{/* Contador de seleccionados */}
|
|
|
|
|
{selectedCount > 0 && (
|
|
|
|
|
<Badge variant="secondary" className="rounded-sm px-2 font-normal">
|
|
|
|
|
{selectedCount} seleccionado(s)
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-02-06 19:51:04 +00:00
|
|
|
{/* Opciones de vista - columnas visibles */}
|
|
|
|
|
{columns && columns.length > 0 && (
|
|
|
|
|
<DropdownMenu>
|
|
|
|
|
<DropdownMenuTrigger asChild>
|
|
|
|
|
<Button variant="outline" size="sm" className="h-9">
|
|
|
|
|
<SlidersHorizontal className="mr-2 h-4 w-4" />
|
|
|
|
|
Vista
|
|
|
|
|
</Button>
|
|
|
|
|
</DropdownMenuTrigger>
|
|
|
|
|
<DropdownMenuContent align="end" className="w-[180px]">
|
|
|
|
|
<DropdownMenuLabel>Columnas visibles</DropdownMenuLabel>
|
|
|
|
|
<DropdownMenuSeparator />
|
|
|
|
|
{columns
|
|
|
|
|
.filter((col) => col.canHide)
|
|
|
|
|
.map((column) => (
|
|
|
|
|
<DropdownMenuCheckboxItem
|
|
|
|
|
key={column.id}
|
|
|
|
|
checked={column.isVisible}
|
|
|
|
|
onCheckedChange={(checked) => {
|
|
|
|
|
column.toggleVisibility(checked);
|
|
|
|
|
}}
|
|
|
|
|
onSelect={(e) => e.preventDefault()}
|
|
|
|
|
>
|
|
|
|
|
{COLUMN_NAMES[column.id] || column.id}
|
|
|
|
|
</DropdownMenuCheckboxItem>
|
|
|
|
|
))}
|
|
|
|
|
</DropdownMenuContent>
|
|
|
|
|
</DropdownMenu>
|
|
|
|
|
)}
|
2026-01-30 19:59:44 +00:00
|
|
|
|
|
|
|
|
{/* Exportar */}
|
2026-02-06 19:51:04 +00:00
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
size="sm"
|
|
|
|
|
className="h-9"
|
|
|
|
|
onClick={handleExport}
|
|
|
|
|
disabled={!onExport}
|
|
|
|
|
>
|
2026-01-30 19:59:44 +00:00
|
|
|
<Download className="mr-2 h-4 w-4" />
|
|
|
|
|
Exportar
|
2026-02-06 19:51:04 +00:00
|
|
|
{selectedCount > 0 && (
|
|
|
|
|
<Badge variant="secondary" className="ml-2 px-1.5 py-0 text-xs">
|
|
|
|
|
{selectedCount}
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
2026-01-30 19:59:44 +00:00
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|