ikoncomponents 1.7.1 → 1.7.2
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.
- package/dist/ikoncomponents/fileUpload/index.js +1 -1
- package/dist/ikoncomponents/fileUploadApi/index.js +1 -1
- package/dist/ikoncomponents/table/DataTable/index.d.ts +1 -1
- package/dist/ikoncomponents/table/DataTable/index.js +65 -6
- package/dist/ikoncomponents/table/index.d.ts +1 -1
- package/dist/ikoncomponents/table/index.js +153 -5
- package/dist/ikoncomponents/table/type.d.ts +21 -12
- package/dist/ikoncomponents/table/type.js +7 -0
- package/dist/styles.css +309 -109
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/utils/api/file-upload copy/index.js +1 -1
- package/package.json +4 -3
|
@@ -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 "
|
|
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 "
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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,
|
|
2
|
+
export declare function DataTableLayout<T>({ data, columns, extraTools, }: DataTableLayoutProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,12 +1,160 @@
|
|
|
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
|
-
|
|
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, } = extraTools !== null && extraTools !== void 0 ? extraTools : {};
|
|
10
14
|
const [viewMode, setViewMode] = useState("list");
|
|
11
|
-
|
|
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
|
+
setTableFilters(prev => {
|
|
42
|
+
const currentValues = prev[column] || [];
|
|
43
|
+
const nextValues = currentValues.includes(value)
|
|
44
|
+
? currentValues.filter(v => v !== value)
|
|
45
|
+
: [...currentValues, value];
|
|
46
|
+
const next = Object.assign({}, prev);
|
|
47
|
+
if (nextValues.length === 0) {
|
|
48
|
+
delete next[column];
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
next[column] = nextValues;
|
|
52
|
+
}
|
|
53
|
+
return next;
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
const removeActiveFilterColumn = (column) => {
|
|
57
|
+
const nextActiveColumns = activeFilterColumns.filter(c => c !== column);
|
|
58
|
+
const nextFilters = Object.assign({}, tableFilters);
|
|
59
|
+
delete nextFilters[column];
|
|
60
|
+
setActiveFilterColumns(nextActiveColumns);
|
|
61
|
+
setTableFilters(nextFilters);
|
|
62
|
+
onFilterChange === null || onFilterChange === void 0 ? void 0 : onFilterChange(nextFilters);
|
|
63
|
+
};
|
|
64
|
+
const handleTogglePendingFilterColumn = (column) => {
|
|
65
|
+
setPendingFilterColumns(prev => prev.includes(column) ? prev.filter(c => c !== column) : [...prev, column]);
|
|
66
|
+
};
|
|
67
|
+
const getUniqueValuesForColumn = (header) => {
|
|
68
|
+
const col = columns.find(c => c.header === header);
|
|
69
|
+
if (!col || !col.accessorKey)
|
|
70
|
+
return [];
|
|
71
|
+
const values = data.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: () => setTableFilters(prev => { const next = Object.assign({}, prev); delete next[colHeader]; return next; }), children: "Clear Selections" }) }))] })] }, colHeader));
|
|
153
|
+
}) })), _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) => {
|
|
154
|
+
e.preventDefault();
|
|
155
|
+
const columnHeader = e.dataTransfer.getData("columnHeader");
|
|
156
|
+
if (columnHeader && !groupedColumns.includes(columnHeader)) {
|
|
157
|
+
handleToggleGroup(columnHeader);
|
|
158
|
+
}
|
|
159
|
+
}, 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
160
|
}
|
|
@@ -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,8 @@ export interface DataTableLayoutProps<T> {
|
|
|
10
26
|
gridComponent?: (data: T[]) => React.ReactNode;
|
|
11
27
|
isLoading?: boolean;
|
|
12
28
|
onReload?: () => void;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
+
};
|