ikoncomponents 1.7.1 → 1.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useState, useRef } from "react";
4
4
  import { UploadCloud, FileUp, Upload, X } from "lucide-react";
5
5
  import { v4 as uuidv4 } from "uuid";
6
- import { Input } from "@/shadcn/input";
6
+ import { Input } from "../../shadcn/input";
7
7
  import { IconButtonWithTooltip } from "../buttons";
8
8
  /* ----------------------------------------------------
9
9
  SAFE Base64 Converter
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useState, useRef, useImperativeHandle, forwardRef } from "react";
4
4
  import { UploadCloud, FileUp, Upload } from "lucide-react";
5
5
  import { v4 as uuidv4 } from "uuid";
6
- import { Input } from "@/shadcn/input";
6
+ import { Input } from "../../shadcn/input";
7
7
  import { IconButtonWithTooltip } from "../buttons";
8
8
  import { uploadFilePublic } from "../../utils/api/file-upload copy";
9
9
  /* ----------------------------------------------------
@@ -1,2 +1,2 @@
1
1
  import { DataTableProps } from "../type";
2
- export declare function DataTable<T>({ data, columns, keyExtractor, onRowClick }: DataTableProps<T>): import("react/jsx-runtime").JSX.Element;
2
+ export declare function DataTable<T>({ data, columns, keyExtractor, onRowClick, groupedColumns, onToggleGroup }: DataTableProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -1,9 +1,68 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "../../../shadcn/table";
3
- export function DataTable({ data, columns, keyExtractor, onRowClick }) {
4
- return (_jsx("div", { className: "rounded-md border bg-background shadow-sm overflow-hidden", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsx(TableRow, { className: "bg-muted border-b border-border", children: columns.map((col, index) => (_jsx(TableHead, { className: "font-semibold text-muted-foreground", children: typeof col.header === "function" ? col.header() : col.header }, index))) }) }), _jsx(TableBody, { children: data && data.length > 0 ? (data.map((row) => (_jsx(TableRow, { onClick: () => onRowClick && onRowClick(row), className: `hover:bg-muted/50 transition-colors border-b border-border ${onRowClick ? "cursor-pointer" : ""}`, children: columns.map((col, colIndex) => (_jsx(TableCell, { className: colIndex === 0 ? "font-medium" : "", children: col.cell
5
- ? col.cell(row)
6
- : col.accessorKey
7
- ? String(row[col.accessorKey] || "N/A")
8
- : null }, colIndex))) }, keyExtractor(onRowClick ? row : row))))) : (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: columns.length, className: "h-24 text-center text-muted-foreground", children: "No records found." }) })) })] }) }));
3
+ import React, { useState } from "react";
4
+ import { ChevronRight, ChevronDown, GripVertical } from "lucide-react";
5
+ import { Badge } from "../../../shadcn/badge";
6
+ export function DataTable({ data, columns, keyExtractor, onRowClick, groupedColumns = [], onToggleGroup }) {
7
+ const [expandedGroups, setExpandedGroups] = useState(new Set());
8
+ const toggleGroup = (groupId) => {
9
+ const next = new Set(expandedGroups);
10
+ if (next.has(groupId)) {
11
+ next.delete(groupId);
12
+ }
13
+ else {
14
+ next.add(groupId);
15
+ }
16
+ setExpandedGroups(next);
17
+ };
18
+ // Helper to get accessor key from column header string
19
+ const getAccessor = (headerStr) => {
20
+ const col = columns.find(c => (typeof c.header === 'string' ? c.header : '') === headerStr);
21
+ return col === null || col === void 0 ? void 0 : col.accessorKey;
22
+ };
23
+ // Group data recursively
24
+ const getGroupedData = (items, groupIndices) => {
25
+ if (groupIndices.length === 0)
26
+ return items;
27
+ const [currentGroupHeader, ...remainingGroups] = groupIndices;
28
+ const accessor = getAccessor(currentGroupHeader);
29
+ if (!accessor)
30
+ return items;
31
+ const groups = new Map();
32
+ items.forEach(item => {
33
+ var _a;
34
+ const val = (_a = item[accessor]) !== null && _a !== void 0 ? _a : "None";
35
+ if (!groups.has(val))
36
+ groups.set(val, []);
37
+ groups.get(val).push(item);
38
+ });
39
+ return Array.from(groups.entries()).map(([value, groupItems]) => ({
40
+ isGroup: true,
41
+ header: currentGroupHeader,
42
+ value,
43
+ id: `${currentGroupHeader}:${value}`,
44
+ children: getGroupedData(groupItems, remainingGroups),
45
+ count: groupItems.length
46
+ }));
47
+ };
48
+ const groupedData = getGroupedData(data, groupedColumns);
49
+ const renderRows = (items, level = 0) => {
50
+ return items.map((item, index) => {
51
+ if (item.isGroup) {
52
+ const isExpanded = expandedGroups.has(item.id);
53
+ return (_jsxs(React.Fragment, { children: [_jsx(TableRow, { className: "bg-muted/40 hover:bg-muted/60 cursor-pointer border-l-4 border-l-primary/50", onClick: () => toggleGroup(item.id), children: _jsx(TableCell, { colSpan: columns.length, className: "py-2", style: { paddingLeft: `${level * 24 + 12}px` }, children: _jsxs("div", { className: "flex items-center gap-2", children: [isExpanded ? _jsx(ChevronDown, { className: "w-4 h-4" }) : _jsx(ChevronRight, { className: "w-4 h-4" }), _jsxs("span", { className: "text-xs font-bold uppercase tracking-wider text-muted-foreground", children: [item.header, ":"] }), _jsx("span", { className: "font-semibold", children: String(item.value) }), _jsxs(Badge, { variant: "outline", className: "ml-2 text-[10px] py-0 h-4", children: [item.count, " items"] })] }) }) }), isExpanded && renderRows(item.children, level + 1)] }, item.id));
54
+ }
55
+ return (_jsx(TableRow, { onClick: () => onRowClick && onRowClick(item), className: `hover:bg-muted/50 transition-colors border-b border-border ${onRowClick ? "cursor-pointer" : ""}`, children: columns.map((col, colIndex) => (_jsx(TableCell, { className: colIndex === 0 ? "font-medium" : "", style: { paddingLeft: colIndex === 0 ? `${level * 24 + 16}px` : undefined }, children: col.cell
56
+ ? col.cell(item)
57
+ : col.accessorKey
58
+ ? String(item[col.accessorKey] || "N/A")
59
+ : null }, colIndex))) }, keyExtractor(item)));
60
+ });
61
+ };
62
+ return (_jsx("div", { className: "rounded-md border bg-background shadow-sm overflow-hidden", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsx(TableRow, { className: "bg-muted border-b border-border", children: columns.map((col, index) => {
63
+ const headerText = typeof col.header === "string" ? col.header : "";
64
+ return (_jsx(TableHead, { className: "font-semibold text-muted-foreground group relative", children: _jsxs("div", { className: "flex items-center gap-2", children: [headerText && (_jsx("div", { className: "cursor-grab active:cursor-grabbing p-1 hover:bg-background/50 rounded transition-colors", draggable: true, onDragStart: (e) => {
65
+ e.dataTransfer.setData("columnHeader", headerText);
66
+ }, children: _jsx(GripVertical, { className: "w-3 h-3 opacity-0 group-hover:opacity-100 transition-opacity" }) })), typeof col.header === "function" ? col.header() : col.header] }) }, index));
67
+ }) }) }), _jsx(TableBody, { children: data && data.length > 0 ? (renderRows(groupedData)) : (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: columns.length, className: "h-24 text-center text-muted-foreground", children: "No records found." }) })) })] }) }));
9
68
  }
@@ -1,2 +1,2 @@
1
1
  import { DataTableLayoutProps } from "./type";
2
- export declare function DataTableLayout<T>({ data, columns, keyExtractor, totalPages, currentPage, filterComponent, actionNode, onRowClick, gridComponent, isLoading, onReload, }: DataTableLayoutProps<T>): import("react/jsx-runtime").JSX.Element;
2
+ export declare function DataTableLayout<T>({ data, columns, extraTools, }: DataTableLayoutProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -1,12 +1,165 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { LayoutGrid, List } from "lucide-react";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect, useRef } from "react";
3
+ import { LayoutGrid, List, ChevronDown, X, GripVertical, Settings2, Check, Download, RotateCcw, Search, ListFilter } from "lucide-react";
4
+ import { Badge } from "../../shadcn/badge";
4
5
  import { Reload } from "../reload-component";
5
6
  import { DataTable } from "./DataTable";
6
7
  import { DataTableSearch } from "./DataTableSearch";
7
8
  import { DataTablePagination } from "./DataTablePagination";
8
9
  import { DataTablePageSize } from "./DataTablePageSize";
9
- export function DataTableLayout({ data, columns, keyExtractor, totalPages, currentPage, filterComponent, actionNode, onRowClick, gridComponent, isLoading = false, onReload, }) {
10
+ import { Button } from "../../shadcn/button";
11
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, } from "../../shadcn/dropdown-menu";
12
+ export function DataTableLayout({ data, columns, extraTools, }) {
13
+ const { keyExtractor, totalPages, currentPage, filterComponent, actionNode, onRowClick, gridComponent, isLoading, onReload, themeColor, onLoadMore, hasMore, onFilterChange, unfilteredData, } = extraTools !== null && extraTools !== void 0 ? extraTools : {};
10
14
  const [viewMode, setViewMode] = useState("list");
11
- return (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "flex flex-col sm:flex-row items-center justify-between gap-4 mb-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(DataTableSearch, {}), filterComponent && filterComponent] }), _jsxs("div", { className: "flex items-center gap-2", children: [actionNode && actionNode, _jsxs("div", { className: "flex items-center border border-border rounded-md bg-background overflow-hidden h-9", children: [_jsx("button", { onClick: () => setViewMode("list"), className: `px-2.5 h-full flex items-center justify-center transition-colors ${viewMode === "list" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`, title: "List View", children: _jsx(List, { className: "w-4 h-4" }) }), _jsx("button", { onClick: () => setViewMode("grid"), className: `px-2.5 h-full flex items-center justify-center transition-colors ${viewMode === "grid" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`, title: "Grid View", children: _jsx(LayoutGrid, { className: "w-4 h-4" }) })] })] })] }), _jsxs("div", { className: "relative min-h-[300px]", children: [_jsx(Reload, { isLoading: isLoading, onReload: onReload || (() => window.location.reload()) }), _jsx("div", { className: `transition-all duration-300 ${isLoading ? 'opacity-50 pointer-events-none' : ''}`, children: viewMode === "list" ? (_jsx(DataTable, { data: data, columns: columns, keyExtractor: keyExtractor, onRowClick: onRowClick })) : ((gridComponent ? gridComponent(data) : (_jsx("div", { className: "p-12 text-center text-muted-foreground border border-border border-dashed rounded-md bg-muted/20", children: "Grid view not implemented for this component yet." })))) })] }), _jsxs("div", { className: "flex flex-col sm:flex-row items-center justify-between gap-4 pt-4 border-t border-border mt-4", children: [_jsx(DataTablePageSize, {}), _jsx(DataTablePagination, { totalPages: totalPages, currentPage: currentPage })] })] }));
15
+ const observerTarget = useRef(null);
16
+ useEffect(() => {
17
+ if (viewMode !== "grid" || !onLoadMore || !hasMore)
18
+ return;
19
+ const observer = new IntersectionObserver((entries) => {
20
+ if (entries[0].isIntersecting) {
21
+ onLoadMore();
22
+ }
23
+ }, { threshold: 0.1 });
24
+ if (observerTarget.current) {
25
+ observer.observe(observerTarget.current);
26
+ }
27
+ return () => observer.disconnect();
28
+ }, [viewMode, onLoadMore, hasMore]);
29
+ const [groupedColumns, setGroupedColumns] = useState([]);
30
+ const [tableFilters, setTableFilters] = useState({});
31
+ const [activeFilterColumns, setActiveFilterColumns] = useState([]);
32
+ const [isFilterDropdownOpen, setIsFilterDropdownOpen] = useState(false);
33
+ const [pendingFilterColumns, setPendingFilterColumns] = useState([]);
34
+ const [filterSearchQuery, setFilterSearchQuery] = useState("");
35
+ const [columnSearchQueries, setColumnSearchQueries] = useState({});
36
+ const [visibleColumns, setVisibleColumns] = useState(columns.map(col => typeof col.header === "string" ? col.header : ""));
37
+ const toggleColumn = (header) => {
38
+ setVisibleColumns(prev => prev.includes(header) ? prev.filter(h => h !== header) : [...prev, header]);
39
+ };
40
+ const handleToggleFilterValue = (column, value) => {
41
+ const currentValues = tableFilters[column] || [];
42
+ const nextValues = currentValues.includes(value)
43
+ ? currentValues.filter(v => v !== value)
44
+ : [...currentValues, value];
45
+ const nextFilters = Object.assign({}, tableFilters);
46
+ if (nextValues.length === 0) {
47
+ delete nextFilters[column];
48
+ }
49
+ else {
50
+ nextFilters[column] = nextValues;
51
+ }
52
+ setTableFilters(nextFilters);
53
+ onFilterChange === null || onFilterChange === void 0 ? void 0 : onFilterChange(nextFilters);
54
+ };
55
+ const removeActiveFilterColumn = (column) => {
56
+ const nextActiveColumns = activeFilterColumns.filter(c => c !== column);
57
+ const nextFilters = Object.assign({}, tableFilters);
58
+ delete nextFilters[column];
59
+ setActiveFilterColumns(nextActiveColumns);
60
+ setTableFilters(nextFilters);
61
+ onFilterChange === null || onFilterChange === void 0 ? void 0 : onFilterChange(nextFilters);
62
+ };
63
+ const handleTogglePendingFilterColumn = (column) => {
64
+ setPendingFilterColumns(prev => prev.includes(column) ? prev.filter(c => c !== column) : [...prev, column]);
65
+ };
66
+ const getUniqueValuesForColumn = (header) => {
67
+ const col = columns.find(c => c.header === header);
68
+ if (!col || !col.accessorKey)
69
+ return [];
70
+ const sourceData = unfilteredData || data;
71
+ const values = sourceData.map(row => String(row[col.accessorKey] || ""));
72
+ return Array.from(new Set(values)).filter(Boolean).sort();
73
+ };
74
+ const handleToggleGroup = (columnHeader) => {
75
+ setGroupedColumns(prev => prev.includes(columnHeader)
76
+ ? prev.filter(c => c !== columnHeader)
77
+ : [...prev, columnHeader]);
78
+ };
79
+ const exportToCSV = () => {
80
+ if (!data || data.length === 0)
81
+ return;
82
+ const headers = columns
83
+ .filter(col => typeof col.header === "string")
84
+ .map(col => col.header);
85
+ const csvRows = data.map(row => columns
86
+ .filter(col => typeof col.header === "string")
87
+ .map(col => {
88
+ const val = col.accessorKey ? row[col.accessorKey] : "";
89
+ return `"${String(val).replace(/"/g, '""')}"`;
90
+ })
91
+ .join(","));
92
+ const csvContent = [headers.join(","), ...csvRows].join("\n");
93
+ const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
94
+ const link = document.createElement("a");
95
+ const url = URL.createObjectURL(blob);
96
+ link.setAttribute("href", url);
97
+ link.setAttribute("download", `table-export-${new Date().toISOString().split('T')[0]}.csv`);
98
+ link.style.visibility = "hidden";
99
+ document.body.appendChild(link);
100
+ link.click();
101
+ document.body.removeChild(link);
102
+ };
103
+ // data is the current page from the backend — render it directly, no client-side filtering.
104
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-col sm:flex-row items-center justify-between gap-4 w-full", children: [_jsx("div", { className: "flex items-center gap-2 w-full sm:w-auto sm:max-w-[200px] shrink-0", children: _jsx(DataTableSearch, {}) }), _jsxs("div", { className: "flex items-center gap-3 flex-wrap shrink-0", children: [actionNode && actionNode, filterComponent && (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", className: "h-9 gap-2 border-border bg-background", children: [_jsx(ListFilter, { className: "w-4 h-4" }), "Filter"] }) }), _jsx(DropdownMenuContent, { align: "end", className: "p-4 min-w-[200px]", children: filterComponent })] })), _jsxs(DropdownMenu, { open: isFilterDropdownOpen, onOpenChange: (open) => {
105
+ setIsFilterDropdownOpen(open);
106
+ if (open) {
107
+ setPendingFilterColumns(activeFilterColumns);
108
+ }
109
+ }, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", className: "h-9 gap-2 border-border bg-background relative", children: [_jsx(ListFilter, { className: "w-4 h-4" }), "Filter", activeFilterColumns.length > 0 && (_jsx("span", { className: "absolute -top-1 -right-1 w-4 h-4 bg-foreground text-background text-[10px] rounded-full flex items-center justify-center font-bold", children: activeFilterColumns.length }))] }) }), _jsxs(DropdownMenuContent, { align: "end", className: "w-56 p-2 space-y-2", children: [_jsx("div", { className: "px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider", children: "Filter" }), _jsxs("div", { className: "px-2 relative", children: [_jsx(Search, { className: "absolute left-4 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground" }), _jsx("input", { type: "text", placeholder: "Search columns...", value: filterSearchQuery, onChange: (e) => setFilterSearchQuery(e.target.value), className: "w-full h-8 pl-8 pr-2 text-xs rounded-md border border-border bg-muted/20 focus:outline-none focus:ring-1 focus:ring-foreground/30" })] }), _jsx("div", { className: "space-y-1 max-h-[300px] overflow-y-auto pr-1", children: columns
110
+ .filter(col => {
111
+ const headerText = typeof col.header === "string" ? col.header : "";
112
+ return headerText && col.accessorKey && headerText.toLowerCase().includes(filterSearchQuery.toLowerCase());
113
+ })
114
+ .map((col, idx) => {
115
+ const headerText = typeof col.header === "string" ? col.header : "";
116
+ const isSelected = pendingFilterColumns.includes(headerText);
117
+ return (_jsxs("button", { onClick: () => handleTogglePendingFilterColumn(headerText), className: "flex w-full items-center gap-2 px-2 py-1.5 text-sm rounded-sm hover:bg-muted transition-colors", children: [_jsx("div", { className: `flex h-4 w-4 items-center justify-center rounded-sm border`, style: isSelected ? { backgroundColor: 'hsl(var(--foreground))', borderColor: 'hsl(var(--foreground))', color: 'hsl(var(--background))' } : { borderColor: 'hsl(var(--border))' }, children: isSelected && _jsx(Check, { className: "h-3 w-3" }) }), _jsx("span", { className: "truncate", children: headerText })] }, idx));
118
+ }) }), _jsxs("div", { className: "px-2 pt-2 mt-1 flex gap-2", children: [_jsx(Button, { variant: "default", size: "sm", className: "flex-1 text-xs font-medium bg-foreground text-background hover:bg-foreground/90", onClick: () => {
119
+ setActiveFilterColumns(pendingFilterColumns);
120
+ const removedColumns = activeFilterColumns.filter(c => !pendingFilterColumns.includes(c));
121
+ const nextFilters = Object.assign({}, tableFilters);
122
+ removedColumns.forEach(c => delete nextFilters[c]);
123
+ setTableFilters(nextFilters);
124
+ setIsFilterDropdownOpen(false);
125
+ // Notify parent so it can re-fetch from the backend
126
+ onFilterChange === null || onFilterChange === void 0 ? void 0 : onFilterChange(nextFilters);
127
+ }, children: "Apply" }), _jsxs(Button, { variant: "secondary", size: "sm", className: "flex-1 text-xs font-medium bg-secondary hover:bg-secondary/80 text-secondary-foreground", onClick: () => {
128
+ setPendingFilterColumns([]);
129
+ setActiveFilterColumns([]);
130
+ setTableFilters({});
131
+ setIsFilterDropdownOpen(false);
132
+ // Notify parent so it can re-fetch all data without filters
133
+ onFilterChange === null || onFilterChange === void 0 ? void 0 : onFilterChange({});
134
+ }, children: [_jsx(RotateCcw, { className: "w-3.5 h-3.5 mr-2 inline" }), " Reset"] })] })] })] }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", className: "h-9 gap-2 border-border bg-background", children: [_jsx(Settings2, { className: "w-4 h-4" }), "Columns"] }) }), _jsxs(DropdownMenuContent, { align: "end", className: "w-48 p-2", children: [_jsx("div", { className: "px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider", children: "Toggle Columns" }), columns.map((col, idx) => {
135
+ const headerText = typeof col.header === "string" ? col.header : `Column ${idx + 1}`;
136
+ const isVisible = visibleColumns.includes(headerText);
137
+ return (_jsxs("button", { onClick: () => toggleColumn(headerText), className: "flex w-full items-center gap-2 px-2 py-1.5 text-sm rounded-sm hover:bg-muted transition-colors", children: [_jsx("div", { className: `flex h-4 w-4 items-center justify-center rounded-sm border border-primary ${isVisible ? 'bg-primary text-primary-foreground' : 'bg-transparent'}`, children: isVisible && _jsx(Check, { className: "h-3 w-3" }) }), headerText] }, idx));
138
+ })] })] }), _jsx(Button, { variant: "outline", size: "sm", className: "h-9 gap-2 border-border bg-background", onClick: exportToCSV, children: _jsx(Download, { className: "w-4 h-4" }) }), _jsxs("div", { className: "flex items-center border border-border rounded-md bg-background overflow-hidden h-9", children: [_jsx("button", { onClick: () => setViewMode("list"), className: `px-2.5 h-full flex items-center justify-center transition-colors ${viewMode === "list" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`, title: "List View", children: _jsx(List, { className: "w-4 h-4" }) }), _jsx("button", { onClick: () => setViewMode("grid"), className: `px-2.5 h-full flex items-center justify-center transition-colors ${viewMode === "grid" ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50"}`, title: "Grid View", children: _jsx(LayoutGrid, { className: "w-4 h-4" }) })] })] })] }), activeFilterColumns.length > 0 && (_jsx("div", { className: "flex items-center gap-2 flex-1 min-w-0 overflow-x-auto", style: { scrollbarWidth: 'none', msOverflowStyle: 'none' }, children: activeFilterColumns.map(colHeader => {
139
+ const selectedCount = (tableFilters[colHeader] || []).length;
140
+ const searchQuery = columnSearchQueries[colHeader] || "";
141
+ const uniqueValues = getUniqueValuesForColumn(colHeader);
142
+ const filteredValues = uniqueValues.filter(v => v.toLowerCase().includes(searchQuery.toLowerCase()));
143
+ return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs("div", { className: "flex items-center gap-1.5 px-3 py-1.5 h-9 bg-muted/50 border border-border rounded-full text-xs font-medium cursor-pointer hover:bg-muted/80 transition-colors flex-1 shrink min-w-[140px] max-w-fit [&::-webkit-scrollbar]:hidden", title: `${colHeader}: ${selectedCount} Selected`, children: [_jsxs("span", { className: "uppercase truncate shrink font-semibold", children: [colHeader, ":"] }), _jsxs("span", { className: "text-muted-foreground whitespace-nowrap shrink-0", children: [selectedCount, " Selected"] }), _jsx(ChevronDown, { className: "w-3.5 h-3.5 ml-1 opacity-50 shrink-0" }), _jsx("div", { className: "p-0.5 ml-1 rounded-full hover:bg-background/80 shrink-0", onClick: (e) => {
144
+ e.preventDefault();
145
+ e.stopPropagation();
146
+ removeActiveFilterColumn(colHeader);
147
+ }, onPointerDown: (e) => {
148
+ e.stopPropagation();
149
+ }, children: _jsx(X, { className: "w-3.5 h-3.5" }) })] }) }), _jsxs(DropdownMenuContent, { align: "start", className: "w-56 p-2 space-y-2", children: [_jsxs("div", { className: "px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider", children: ["Filter ", colHeader] }), _jsxs("div", { className: "px-2 relative", children: [_jsx(Search, { className: "absolute left-4 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground" }), _jsx("input", { type: "text", placeholder: "Search items...", value: searchQuery, onChange: (e) => setColumnSearchQueries(prev => (Object.assign(Object.assign({}, prev), { [colHeader]: e.target.value }))), className: "w-full h-8 pl-8 pr-2 text-xs rounded-md border border-border bg-muted/20 focus:outline-none focus:ring-1 focus:ring-foreground/30" })] }), _jsxs("div", { className: "space-y-1 max-h-[200px] overflow-y-auto pr-1", children: [filteredValues.map(val => {
150
+ const isSelected = (tableFilters[colHeader] || []).includes(val);
151
+ return (_jsxs("button", { onClick: () => handleToggleFilterValue(colHeader, val), className: "flex w-full items-center gap-2 px-2 py-1.5 text-sm rounded-sm hover:bg-muted transition-colors", children: [_jsx("div", { className: `flex h-4 w-4 items-center justify-center rounded-sm border`, style: isSelected ? { backgroundColor: 'hsl(var(--foreground))', borderColor: 'hsl(var(--foreground))', color: 'hsl(var(--background))' } : { borderColor: 'hsl(var(--border))' }, children: isSelected && _jsx(Check, { className: "h-3 w-3" }) }), _jsx("span", { className: "truncate text-left", children: val })] }, val));
152
+ }), filteredValues.length === 0 && (_jsx("div", { className: "px-2 py-2 text-xs text-muted-foreground text-center", children: "No items found." }))] }), (tableFilters[colHeader] || []).length > 0 && (_jsx("div", { className: "px-2 pt-1 border-t", children: _jsx(Button, { variant: "ghost", size: "sm", className: "w-full text-xs", onClick: () => {
153
+ const nextFilters = Object.assign({}, tableFilters);
154
+ delete nextFilters[colHeader];
155
+ setTableFilters(nextFilters);
156
+ onFilterChange === null || onFilterChange === void 0 ? void 0 : onFilterChange(nextFilters);
157
+ }, children: "Clear Selections" }) }))] })] }, colHeader));
158
+ }) })), _jsxs("div", { className: "w-full flex items-center gap-2 p-2 border border-dashed border-border rounded-lg bg-muted/20 min-h-[48px] transition-all hover:bg-muted/40 group shadow-sm", onDragOver: (e) => e.preventDefault(), onDrop: (e) => {
159
+ e.preventDefault();
160
+ const columnHeader = e.dataTransfer.getData("columnHeader");
161
+ if (columnHeader && !groupedColumns.includes(columnHeader)) {
162
+ handleToggleGroup(columnHeader);
163
+ }
164
+ }, children: [_jsxs("div", { className: "flex items-center gap-2 text-muted-foreground text-xs px-2 select-none border-r pr-4 mr-2 border-border/50", children: [_jsx(GripVertical, { className: "w-4 h-4 opacity-40" }), _jsx("span", { className: "font-semibold uppercase tracking-wider opacity-70", children: "Grouping" })] }), _jsx("div", { className: "flex flex-wrap gap-2", children: groupedColumns.length === 0 ? (_jsx("div", { className: "flex items-center gap-2 text-muted-foreground/60 text-sm italic py-1", children: _jsx("span", { children: "Drag column headers here to group your data..." }) })) : (groupedColumns.map(col => (_jsxs(Badge, { variant: "secondary", className: "gap-2 pl-3 pr-1.5 h-8 text-xs font-semibold bg-background border shadow-sm animate-in fade-in zoom-in duration-200", children: [col, _jsx("button", { onClick: () => handleToggleGroup(col), className: "hover:bg-muted rounded-full p-0.5 transition-colors", children: _jsx(X, { className: "w-3.5 h-3.5" }) })] }, col)))) })] }), _jsxs("div", { className: "relative min-h-[300px] mt-2", children: [_jsx(Reload, { isLoading: isLoading !== null && isLoading !== void 0 ? isLoading : false, onReload: onReload || (() => window.location.reload()) }), _jsx("div", { className: `transition-all duration-300 ${isLoading ? 'opacity-50 pointer-events-none' : ''}`, children: viewMode === "list" ? (_jsx(DataTable, { data: data, columns: columns.filter(col => typeof col.header === "string" ? visibleColumns.includes(col.header) : true), keyExtractor: keyExtractor !== null && keyExtractor !== void 0 ? keyExtractor : (() => ""), onRowClick: onRowClick, groupedColumns: groupedColumns, onToggleGroup: handleToggleGroup })) : (_jsxs(_Fragment, { children: [gridComponent ? gridComponent(data) : (_jsx("div", { className: "p-12 text-center text-muted-foreground border border-border border-dashed rounded-md bg-muted/20", children: "Grid view not implemented for this component yet." })), viewMode === "grid" && hasMore && (_jsx("div", { ref: observerTarget, className: "flex justify-center p-6 w-full", children: _jsx("div", { className: "w-6 h-6 border-2 rounded-full animate-spin border-foreground border-t-transparent" }) }))] })) })] }), viewMode === "list" && (_jsxs("div", { className: "flex flex-col sm:flex-row items-center justify-between gap-4 pt-4 border-t border-border mt-4", children: [_jsx(DataTablePageSize, {}), _jsx(DataTablePagination, { totalPages: totalPages !== null && totalPages !== void 0 ? totalPages : 0, currentPage: currentPage !== null && currentPage !== void 0 ? currentPage : 0 })] }))] }));
12
165
  }
@@ -1,6 +1,22 @@
1
+ export interface ColumnDef<T> {
2
+ header: string | (() => React.ReactNode);
3
+ accessorKey?: keyof T;
4
+ cell?: (row: T) => React.ReactNode;
5
+ }
6
+ export interface DataTableProps<T> {
7
+ data: T[];
8
+ columns: ColumnDef<T>[];
9
+ keyExtractor: (row: T) => string | number;
10
+ onRowClick?: (row: T) => void;
11
+ groupedColumns?: string[];
12
+ onToggleGroup?: (columnHeader: string) => void;
13
+ }
1
14
  export interface DataTableLayoutProps<T> {
2
15
  data: T[];
3
16
  columns: ColumnDef<T>[];
17
+ extraTools?: ExtraPrams<T>;
18
+ }
19
+ export type ExtraPrams<T> = {
4
20
  keyExtractor: (row: T) => string | number;
5
21
  totalPages: number;
6
22
  currentPage: number;
@@ -10,15 +26,9 @@ export interface DataTableLayoutProps<T> {
10
26
  gridComponent?: (data: T[]) => React.ReactNode;
11
27
  isLoading?: boolean;
12
28
  onReload?: () => void;
13
- }
14
- export interface ColumnDef<T> {
15
- header: string | (() => React.ReactNode);
16
- accessorKey?: keyof T;
17
- cell?: (row: T) => React.ReactNode;
18
- }
19
- export interface DataTableProps<T> {
20
- data: T[];
21
- columns: ColumnDef<T>[];
22
- keyExtractor: (row: T) => string | number;
23
- onRowClick?: (row: T) => void;
24
- }
29
+ themeColor?: string;
30
+ onLoadMore?: () => void;
31
+ hasMore?: boolean;
32
+ onFilterChange?: (filters: Record<string, string[]>) => void;
33
+ unfilteredData?: T[];
34
+ };
@@ -1 +1,8 @@
1
+ // export interface DataTableLayoutProps<T> {
2
+ // data: T[];
3
+ // columns: ColumnDef<T>[];
4
+ // keyExtractor: (row: T) => string | number;
5
+ // totalPages: number;
6
+ // currentPage: number;
7
+ ;
1
8
  export {};