mig-schema-table 3.0.27 → 3.0.29
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/README.md +1 -0
- package/dist/SchemaTable/ColumnFilterRow/index.d.ts +7 -3
- package/dist/SchemaTable/ColumnFilterRow/index.js +65 -15
- package/dist/SchemaTable/SchemaColumnFilterPopover/index.d.ts +2 -6
- package/dist/SchemaTable/SchemaColumnFilterPopover/index.js +32 -48
- package/dist/SchemaTable/Th/index.js +2 -10
- package/dist/SchemaTable/index.js +1 -1
- package/dist/component/SchemaTable/SchemaColumnFilterPopover/FilterFormComponent/index.d.ts +11 -0
- package/dist/component/SchemaTable/SchemaColumnFilterPopover/FilterFormComponent/index.js +76 -0
- package/dist/component/SchemaTable/SchemaColumnFilterPopover/index.d.ts +15 -0
- package/dist/component/SchemaTable/SchemaColumnFilterPopover/index.js +37 -0
- package/dist/component/SchemaTable/Td/index.d.ts +22 -0
- package/dist/component/SchemaTable/Td/index.js +78 -0
- package/dist/component/SchemaTable/Th/index.d.ts +27 -0
- package/dist/component/SchemaTable/Th/index.js +78 -0
- package/dist/component/SchemaTable/constants.d.ts +2 -0
- package/dist/component/SchemaTable/constants.js +2 -0
- package/dist/component/SchemaTable/index.d.ts +34 -0
- package/dist/component/SchemaTable/index.js +343 -0
- package/dist/component/SchemaTable/index.test.d.ts +1 -0
- package/dist/component/SchemaTable/index.test.js +17 -0
- package/dist/component/inc/constant.d.ts +2 -0
- package/dist/component/inc/constant.js +2 -0
- package/dist/component/inc/date.d.ts +1 -0
- package/dist/component/inc/date.js +3 -0
- package/dist/component/inc/schema.d.ts +2 -0
- package/dist/component/inc/schema.js +32 -0
- package/dist/component/inc/string.d.ts +1 -0
- package/dist/component/inc/string.js +17 -0
- package/dist/component/index.d.ts +4 -0
- package/dist/component/index.js +2 -0
- package/dist/component/types.d.ts +21 -0
- package/dist/component/types.js +1 -0
- package/dist/exampleData.d.ts +29 -0
- package/dist/exampleData.js +177 -0
- package/dist/index.css +16 -89
- package/package.json +4 -3
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { uncamel } from "../../inc/string";
|
|
4
|
+
import { SELECT_ALL_COLUMN_NAME } from "../constants";
|
|
5
|
+
export var EColumnFilterStatus;
|
|
6
|
+
(function (EColumnFilterStatus) {
|
|
7
|
+
EColumnFilterStatus["UNAVAILABLE"] = "UNAVAILABLE";
|
|
8
|
+
EColumnFilterStatus["AVAILABLE"] = "AVAILABLE";
|
|
9
|
+
EColumnFilterStatus["ACTIVE"] = "ACTIVE";
|
|
10
|
+
})(EColumnFilterStatus || (EColumnFilterStatus = {}));
|
|
11
|
+
const Th = ({ isAllChecked, columnFilterStatus, disableColumnFilter, isSortable, numberOfSelectedRows, onSelectAllIndexesHandler, propConfig, propName, schema, setPopoverConfig, setSortAsc, setSortColumn, sortAsc, style, }) => {
|
|
12
|
+
const thDivProps = {
|
|
13
|
+
style,
|
|
14
|
+
className: `schema-table__th schema-table__th--column-filter-status-${columnFilterStatus} ${isSortable ? "schema-table__th--sortable" : "schema-table__th--unsortable"}`,
|
|
15
|
+
};
|
|
16
|
+
const onSortButtonClick = React.useCallback(() => {
|
|
17
|
+
if (sortAsc === undefined) {
|
|
18
|
+
setSortColumn(propName);
|
|
19
|
+
setSortAsc(!(propConfig === null || propConfig === void 0 ? void 0 : propConfig.defaultSortDesc));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
setSortAsc((sortAsc) => !sortAsc);
|
|
23
|
+
}, [
|
|
24
|
+
propConfig === null || propConfig === void 0 ? void 0 : propConfig.defaultSortDesc,
|
|
25
|
+
propName,
|
|
26
|
+
setSortAsc,
|
|
27
|
+
setSortColumn,
|
|
28
|
+
sortAsc,
|
|
29
|
+
]);
|
|
30
|
+
const onFilterButtonClick = React.useCallback((e) => {
|
|
31
|
+
if (columnFilterStatus === EColumnFilterStatus.ACTIVE &&
|
|
32
|
+
disableColumnFilter) {
|
|
33
|
+
disableColumnFilter(propName);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!setPopoverConfig) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const referenceElement = e.currentTarget;
|
|
40
|
+
setPopoverConfig((popoverConfig) => {
|
|
41
|
+
if (popoverConfig) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
referenceElement,
|
|
46
|
+
propName,
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
}, [columnFilterStatus, disableColumnFilter, propName, setPopoverConfig]);
|
|
50
|
+
if (propName === SELECT_ALL_COLUMN_NAME) {
|
|
51
|
+
return (_jsx("div", Object.assign({}, thDivProps, { children: _jsx("div", Object.assign({ style: {
|
|
52
|
+
width: "100%",
|
|
53
|
+
textAlign: "center",
|
|
54
|
+
}, title: `${numberOfSelectedRows || 0} selected` }, { children: _jsx("input", { type: "checkbox", checked: isAllChecked, onChange: onSelectAllIndexesHandler }) })) })));
|
|
55
|
+
}
|
|
56
|
+
if (!schema) {
|
|
57
|
+
return _jsx("div", Object.assign({}, thDivProps));
|
|
58
|
+
}
|
|
59
|
+
switch (schema.type) {
|
|
60
|
+
case "boolean":
|
|
61
|
+
thDivProps.className += ` text-${(propConfig === null || propConfig === void 0 ? void 0 : propConfig.align) || "center"} justify-content-${(propConfig === null || propConfig === void 0 ? void 0 : propConfig.align) || "center"}`;
|
|
62
|
+
break;
|
|
63
|
+
case "integer":
|
|
64
|
+
case "number":
|
|
65
|
+
thDivProps.className += ` text-${(propConfig === null || propConfig === void 0 ? void 0 : propConfig.align) || "end"} justify-content-${(propConfig === null || propConfig === void 0 ? void 0 : propConfig.align) || "end"}`;
|
|
66
|
+
break;
|
|
67
|
+
default:
|
|
68
|
+
if (propConfig === null || propConfig === void 0 ? void 0 : propConfig.align) {
|
|
69
|
+
thDivProps.className += ` text-${propConfig.align}`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return (_jsxs("div", Object.assign({}, thDivProps, { children: [isSortable ? (_jsxs("button", Object.assign({ className: "px-0", disabled: (propConfig === null || propConfig === void 0 ? void 0 : propConfig.sortable) === false, onClick: onSortButtonClick }, { children: [(propConfig === null || propConfig === void 0 ? void 0 : propConfig.title) === undefined
|
|
73
|
+
? uncamel(propName)
|
|
74
|
+
: propConfig === null || propConfig === void 0 ? void 0 : propConfig.title, sortAsc === undefined ? null : sortAsc ? "▲" : "▼"] }))) : (_jsx("div", Object.assign({ style: { lineHeight: "44px" } }, { children: (propConfig === null || propConfig === void 0 ? void 0 : propConfig.title) === undefined
|
|
75
|
+
? uncamel(propName)
|
|
76
|
+
: propConfig === null || propConfig === void 0 ? void 0 : propConfig.title }))), columnFilterStatus !== EColumnFilterStatus.UNAVAILABLE ? (_jsx("button", Object.assign({ onClick: onFilterButtonClick, "data-bs-toggle": "dropdown" }, { children: _jsx("svg", Object.assign({ viewBox: "0 0 36 36", xmlns: "http://www.w3.org/2000/svg", height: 16, width: 16, style: { display: "block" }, fill: "currentColor" }, { children: _jsx("polygon", { points: "14,30 22,25 22,17 35.999,0 17.988,0 0,0 14,17 " }) })) }))) : null] })));
|
|
77
|
+
};
|
|
78
|
+
export default React.memo(Th);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { oas31 } from "openapi3-ts";
|
|
3
|
+
import { IColumnConfig } from "../types";
|
|
4
|
+
export interface ISchemaTableProps<T> {
|
|
5
|
+
Heading?: any;
|
|
6
|
+
checkedIndexes?: number[];
|
|
7
|
+
config?: {
|
|
8
|
+
[propName: string]: IColumnConfig<T>;
|
|
9
|
+
};
|
|
10
|
+
customElement?: React.ReactNode;
|
|
11
|
+
data: T[];
|
|
12
|
+
defaultSortAsc?: boolean;
|
|
13
|
+
defaultSortColumn?: keyof T;
|
|
14
|
+
getRowClassName?: (rowData: T, dataIndex: number) => string;
|
|
15
|
+
getRowSelected?: (rowData: T) => boolean;
|
|
16
|
+
maxHeight?: number;
|
|
17
|
+
isSearchable?: boolean;
|
|
18
|
+
isColumnFilterable?: boolean;
|
|
19
|
+
isSortable?: boolean;
|
|
20
|
+
onCheckedIndexesChange?: (dataIndex: number[]) => void;
|
|
21
|
+
onRowClick?: (rowData: T, dataIndex: number, event: React.MouseEvent) => void;
|
|
22
|
+
rowHeight?: number;
|
|
23
|
+
schema: oas31.SchemaObject;
|
|
24
|
+
searchPlaceholder?: string;
|
|
25
|
+
style?: React.CSSProperties;
|
|
26
|
+
width: number;
|
|
27
|
+
}
|
|
28
|
+
export type TColumnFilterValue = string | number | boolean | {
|
|
29
|
+
from?: Date;
|
|
30
|
+
to?: Date;
|
|
31
|
+
};
|
|
32
|
+
declare function SchemaTable<T>({ Heading, checkedIndexes, config, customElement, data, defaultSortAsc, defaultSortColumn, getRowClassName, getRowSelected, maxHeight, isSearchable, isColumnFilterable, isSortable, onCheckedIndexesChange, onRowClick, rowHeight, schema, searchPlaceholder, style, width, }: ISchemaTableProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
declare const _default: typeof SchemaTable;
|
|
34
|
+
export default _default;
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { VariableSizeGrid, VariableSizeList } from "react-window";
|
|
4
|
+
import { localeFormat } from "../inc/date";
|
|
5
|
+
import { uncamel } from "../inc/string";
|
|
6
|
+
import Th, { EColumnFilterStatus } from "./Th";
|
|
7
|
+
import { SELECT_ALL_COLUMN_NAME, SELECT_ALL_COLUMN_WIDTH } from "./constants";
|
|
8
|
+
import Td from "./Td";
|
|
9
|
+
import { DEFAULT_DATE_FORMAT, DEFAULT_DATE_TIME_FORMAT } from "../inc/constant";
|
|
10
|
+
import SchemaColumnFilterPopover from "./SchemaColumnFilterPopover";
|
|
11
|
+
import { sum } from "lodash";
|
|
12
|
+
function SchemaTable({ Heading = VariableSizeList, checkedIndexes, config, customElement, data, defaultSortAsc = false, defaultSortColumn, getRowClassName, getRowSelected, maxHeight, isSearchable, isColumnFilterable, isSortable, onCheckedIndexesChange, onRowClick, rowHeight = 36, schema, searchPlaceholder, style, width, }) {
|
|
13
|
+
const [sortColumn, setSortColumn] = React.useState(defaultSortColumn);
|
|
14
|
+
const [sortAsc, setSortAsc] = React.useState(defaultSortAsc);
|
|
15
|
+
const [searchQuery, setSearchQuery] = React.useState("");
|
|
16
|
+
const [columnFilterMap, setColumnFilterMap] = React.useState({});
|
|
17
|
+
const [popoverConfig, setPopoverConfig] = React.useState();
|
|
18
|
+
const { properties = {} } = schema;
|
|
19
|
+
const columnNames = React.useMemo(() => {
|
|
20
|
+
const columns = Object.keys(properties);
|
|
21
|
+
if (onCheckedIndexesChange) {
|
|
22
|
+
columns.unshift(SELECT_ALL_COLUMN_NAME);
|
|
23
|
+
}
|
|
24
|
+
if (!config) {
|
|
25
|
+
return columns;
|
|
26
|
+
}
|
|
27
|
+
const invisibleColumns = Object.entries(config).reduce((prev, [propName, propConfig]) => {
|
|
28
|
+
if (propConfig.hidden) {
|
|
29
|
+
prev.push(propName);
|
|
30
|
+
}
|
|
31
|
+
return prev;
|
|
32
|
+
}, []);
|
|
33
|
+
return columns
|
|
34
|
+
.filter((key) => !invisibleColumns.includes(key))
|
|
35
|
+
.sort((columnA, columnB) => {
|
|
36
|
+
let orderA = config[columnA] ? config[columnA].order : undefined;
|
|
37
|
+
if (orderA === undefined) {
|
|
38
|
+
orderA = Object.keys(properties).findIndex((propName) => propName === columnA);
|
|
39
|
+
}
|
|
40
|
+
let orderB = config[columnB] ? config[columnB].order : undefined;
|
|
41
|
+
if (orderB === undefined) {
|
|
42
|
+
orderB = Object.keys(properties).findIndex((propName) => propName === columnB);
|
|
43
|
+
}
|
|
44
|
+
if (columnB === SELECT_ALL_COLUMN_NAME ||
|
|
45
|
+
columnA === SELECT_ALL_COLUMN_NAME) {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
if (orderA === -1) {
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
if (orderB === -1) {
|
|
52
|
+
return -1;
|
|
53
|
+
}
|
|
54
|
+
return orderA - orderB;
|
|
55
|
+
});
|
|
56
|
+
}, [config, onCheckedIndexesChange, properties]);
|
|
57
|
+
const renderData = React.useMemo(() => data
|
|
58
|
+
? data.map((object, rowIndex) => columnNames.reduce((prev, propName) => {
|
|
59
|
+
var _a;
|
|
60
|
+
const schema = properties[propName];
|
|
61
|
+
const propConfig = config ? config[propName] : undefined;
|
|
62
|
+
if (propConfig === null || propConfig === void 0 ? void 0 : propConfig.renderData) {
|
|
63
|
+
prev[propName] = propConfig.renderData(object, rowIndex);
|
|
64
|
+
return prev;
|
|
65
|
+
}
|
|
66
|
+
if (!schema || propName === SELECT_ALL_COLUMN_NAME) {
|
|
67
|
+
prev[propName] = "?";
|
|
68
|
+
return prev;
|
|
69
|
+
}
|
|
70
|
+
const rawValue = object[propName];
|
|
71
|
+
switch (schema.type) {
|
|
72
|
+
case "array":
|
|
73
|
+
prev[propName] =
|
|
74
|
+
((_a = schema.items) === null || _a === void 0 ? void 0 : _a.type) === "string" &&
|
|
75
|
+
rawValue
|
|
76
|
+
? rawValue.map(uncamel).join(", ")
|
|
77
|
+
: JSON.stringify(rawValue);
|
|
78
|
+
return prev;
|
|
79
|
+
case "boolean":
|
|
80
|
+
prev[propName] = rawValue ? "✓" : "✕";
|
|
81
|
+
return prev;
|
|
82
|
+
case "integer":
|
|
83
|
+
prev[propName] = [undefined, null].includes(rawValue)
|
|
84
|
+
? ""
|
|
85
|
+
: `${rawValue}`;
|
|
86
|
+
return prev;
|
|
87
|
+
// @ts-ignore
|
|
88
|
+
case "string":
|
|
89
|
+
if (schema.format === "date" && rawValue) {
|
|
90
|
+
prev[propName] =
|
|
91
|
+
rawValue === "2999-12-31"
|
|
92
|
+
? "-"
|
|
93
|
+
: localeFormat(new Date(rawValue), (propConfig === null || propConfig === void 0 ? void 0 : propConfig.dateFormat) || DEFAULT_DATE_FORMAT);
|
|
94
|
+
return prev;
|
|
95
|
+
}
|
|
96
|
+
if (schema.format === "date-time" && rawValue) {
|
|
97
|
+
prev[propName] = localeFormat(new Date(rawValue), (propConfig === null || propConfig === void 0 ? void 0 : propConfig.dateFormat) || DEFAULT_DATE_TIME_FORMAT);
|
|
98
|
+
return prev;
|
|
99
|
+
}
|
|
100
|
+
if (schema.enum) {
|
|
101
|
+
prev[propName] = rawValue ? uncamel(rawValue) : "";
|
|
102
|
+
return prev;
|
|
103
|
+
}
|
|
104
|
+
// fallthrough
|
|
105
|
+
default:
|
|
106
|
+
prev[propName] = rawValue ? `${rawValue}` : "";
|
|
107
|
+
return prev;
|
|
108
|
+
}
|
|
109
|
+
}, { _index: rowIndex }))
|
|
110
|
+
: undefined, [columnNames, config, data, properties]);
|
|
111
|
+
const columnCount = columnNames.length;
|
|
112
|
+
const { dynamicWidthColumnCount, fixedWidthColumnsWidth } = React.useMemo(() => {
|
|
113
|
+
let fixedWidthColumnsWidth = 0;
|
|
114
|
+
let dynamicWidthColumnCount = 0;
|
|
115
|
+
columnNames.forEach((propName) => {
|
|
116
|
+
if (propName === SELECT_ALL_COLUMN_NAME) {
|
|
117
|
+
fixedWidthColumnsWidth += SELECT_ALL_COLUMN_WIDTH;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const propConfig = config ? config[propName] : undefined;
|
|
121
|
+
if (propConfig === null || propConfig === void 0 ? void 0 : propConfig.width) {
|
|
122
|
+
fixedWidthColumnsWidth += propConfig.width;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
dynamicWidthColumnCount += 1;
|
|
126
|
+
}
|
|
127
|
+
}, 0);
|
|
128
|
+
return { dynamicWidthColumnCount, fixedWidthColumnsWidth };
|
|
129
|
+
}, [columnNames, config]);
|
|
130
|
+
const columnWidths = React.useMemo(() => {
|
|
131
|
+
const dynamicColumnWidth = Math.floor((width - fixedWidthColumnsWidth) / dynamicWidthColumnCount);
|
|
132
|
+
const result = columnNames.map((propName) => {
|
|
133
|
+
if (propName === SELECT_ALL_COLUMN_NAME) {
|
|
134
|
+
return SELECT_ALL_COLUMN_WIDTH;
|
|
135
|
+
}
|
|
136
|
+
const propConfig = config ? config[propName] : undefined;
|
|
137
|
+
return (propConfig === null || propConfig === void 0 ? void 0 : propConfig.width) || dynamicColumnWidth;
|
|
138
|
+
});
|
|
139
|
+
const totalWidth = sum(result);
|
|
140
|
+
if (totalWidth < width && dynamicWidthColumnCount) {
|
|
141
|
+
// find the last dynamic width column and adjust to overcome rounding differences
|
|
142
|
+
const lastDynamicColumnName = [...columnNames]
|
|
143
|
+
.reverse()
|
|
144
|
+
.find((propName) => !config || !config[propName].width);
|
|
145
|
+
if (lastDynamicColumnName) {
|
|
146
|
+
const lastDynamicColumnIndex = columnNames.indexOf(lastDynamicColumnName);
|
|
147
|
+
result[lastDynamicColumnIndex] += width - totalWidth;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
}, [
|
|
152
|
+
columnNames,
|
|
153
|
+
config,
|
|
154
|
+
dynamicWidthColumnCount,
|
|
155
|
+
fixedWidthColumnsWidth,
|
|
156
|
+
width,
|
|
157
|
+
]);
|
|
158
|
+
const getColumnWidth = React.useCallback((columnIndex) => (columnWidths ? columnWidths[columnIndex] : 1), [columnWidths]);
|
|
159
|
+
const filteredRenderData = React.useMemo(() => {
|
|
160
|
+
if (!renderData || (!isColumnFilterable && !isSearchable)) {
|
|
161
|
+
return renderData;
|
|
162
|
+
}
|
|
163
|
+
return renderData.filter((item) => {
|
|
164
|
+
const lcSearchQuery = searchQuery.toLowerCase();
|
|
165
|
+
if (searchQuery &&
|
|
166
|
+
!columnNames.find((columnName) => `${item[columnName]}`.toLowerCase().includes(lcSearchQuery))) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
let result = true;
|
|
170
|
+
Object.entries(columnFilterMap).forEach(([propName, columnFilterValue]) => {
|
|
171
|
+
if (!result || columnFilterValue === undefined) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const propSchema = properties[propName];
|
|
175
|
+
// @ts-ignore
|
|
176
|
+
const rawValue = data[item._index][propName];
|
|
177
|
+
switch (propSchema.type) {
|
|
178
|
+
case "boolean":
|
|
179
|
+
case "integer":
|
|
180
|
+
result = rawValue === columnFilterValue;
|
|
181
|
+
break;
|
|
182
|
+
// @ts-ignore
|
|
183
|
+
case "string":
|
|
184
|
+
if (typeof columnFilterValue === "object" &&
|
|
185
|
+
(propSchema.format === "date" ||
|
|
186
|
+
propSchema.format === "date-time")) {
|
|
187
|
+
const { from, to } = columnFilterValue;
|
|
188
|
+
const rawDate = rawValue ? new Date(rawValue) : undefined;
|
|
189
|
+
if ((from && (!rawDate || rawDate < from)) ||
|
|
190
|
+
(to && (!rawDate || rawDate > to))) {
|
|
191
|
+
result = false;
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// fall through
|
|
196
|
+
default:
|
|
197
|
+
// fallback by looking at the render value
|
|
198
|
+
result = `${item[propName]}`
|
|
199
|
+
.toLowerCase()
|
|
200
|
+
.includes(`${columnFilterValue}`.toLowerCase());
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
return result;
|
|
204
|
+
});
|
|
205
|
+
}, [
|
|
206
|
+
renderData,
|
|
207
|
+
isColumnFilterable,
|
|
208
|
+
isSearchable,
|
|
209
|
+
searchQuery,
|
|
210
|
+
columnNames,
|
|
211
|
+
columnFilterMap,
|
|
212
|
+
properties,
|
|
213
|
+
data,
|
|
214
|
+
]);
|
|
215
|
+
// Sort the filtered data
|
|
216
|
+
const sortedRenderData = React.useMemo(() => {
|
|
217
|
+
if (!sortColumn || !filteredRenderData) {
|
|
218
|
+
return filteredRenderData;
|
|
219
|
+
}
|
|
220
|
+
const sortSchema = properties[sortColumn];
|
|
221
|
+
const propConfig = config ? config[sortColumn] : undefined;
|
|
222
|
+
const columnSort = propConfig === null || propConfig === void 0 ? void 0 : propConfig.sort;
|
|
223
|
+
if (columnSort) {
|
|
224
|
+
return filteredRenderData.sort((a, b) => {
|
|
225
|
+
const aData = data[a._index];
|
|
226
|
+
const bData = data[b._index];
|
|
227
|
+
if (!aData) {
|
|
228
|
+
return 1;
|
|
229
|
+
}
|
|
230
|
+
if (!bData) {
|
|
231
|
+
return -1;
|
|
232
|
+
}
|
|
233
|
+
return columnSort(aData, bData, sortAsc);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return filteredRenderData.sort((a, b) => {
|
|
237
|
+
const sortByValue = (propConfig === null || propConfig === void 0 ? void 0 : propConfig.sortByValue) === undefined
|
|
238
|
+
? !sortSchema ||
|
|
239
|
+
sortSchema.type === "boolean" ||
|
|
240
|
+
sortSchema.type === "integer" ||
|
|
241
|
+
sortSchema.format === "date" ||
|
|
242
|
+
sortSchema.format === "date-time" ||
|
|
243
|
+
(propConfig === null || propConfig === void 0 ? void 0 : propConfig.renderCell)
|
|
244
|
+
: propConfig.sortByValue;
|
|
245
|
+
const x = sortByValue && data[a._index]
|
|
246
|
+
? // @ts-ignore
|
|
247
|
+
data[a._index][sortColumn]
|
|
248
|
+
: a[sortColumn].toLowerCase();
|
|
249
|
+
const y = sortByValue && data[b._index]
|
|
250
|
+
? // @ts-ignore
|
|
251
|
+
data[b._index][sortColumn]
|
|
252
|
+
: b[sortColumn].toLowerCase();
|
|
253
|
+
if (x === y) {
|
|
254
|
+
return 0;
|
|
255
|
+
}
|
|
256
|
+
return (x < y ? 1 : -1) * (sortAsc ? -1 : 1);
|
|
257
|
+
});
|
|
258
|
+
}, [config, data, filteredRenderData, properties, sortAsc, sortColumn]);
|
|
259
|
+
const onSelectAllIndexesHandler = React.useCallback(() => {
|
|
260
|
+
if (!onCheckedIndexesChange || !filteredRenderData) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
onCheckedIndexesChange(filteredRenderData.map((el) => el._index));
|
|
264
|
+
}, [onCheckedIndexesChange, filteredRenderData]);
|
|
265
|
+
const isAllRowsChecked = React.useMemo(() => {
|
|
266
|
+
return ((checkedIndexes === null || checkedIndexes === void 0 ? void 0 : checkedIndexes.length) !== 0 &&
|
|
267
|
+
(filteredRenderData === null || filteredRenderData === void 0 ? void 0 : filteredRenderData.length) === (checkedIndexes === null || checkedIndexes === void 0 ? void 0 : checkedIndexes.length) &&
|
|
268
|
+
(checkedIndexes === null || checkedIndexes === void 0 ? void 0 : checkedIndexes.every((checkedIndex) => filteredRenderData === null || filteredRenderData === void 0 ? void 0 : filteredRenderData.some((el) => el._index === checkedIndex))));
|
|
269
|
+
}, [checkedIndexes, filteredRenderData]);
|
|
270
|
+
const disableColumnFilter = React.useCallback((propName) => {
|
|
271
|
+
const newColumnFilterMap = Object.assign({}, columnFilterMap);
|
|
272
|
+
delete newColumnFilterMap[propName];
|
|
273
|
+
setColumnFilterMap(newColumnFilterMap);
|
|
274
|
+
}, [columnFilterMap]);
|
|
275
|
+
const SchemaTableTh = React.useCallback(({ style, index }) => {
|
|
276
|
+
const propName = columnNames[index];
|
|
277
|
+
const propConfig = config ? config[propName] : undefined;
|
|
278
|
+
let columnFilterStatus = isColumnFilterable && (propConfig === null || propConfig === void 0 ? void 0 : propConfig.isFilterable) !== false
|
|
279
|
+
? EColumnFilterStatus.AVAILABLE
|
|
280
|
+
: EColumnFilterStatus.UNAVAILABLE;
|
|
281
|
+
if (columnFilterMap[propName] !== undefined) {
|
|
282
|
+
columnFilterStatus = EColumnFilterStatus.ACTIVE;
|
|
283
|
+
}
|
|
284
|
+
return (_jsx(Th, { isAllChecked: isAllRowsChecked, columnFilterStatus: columnFilterStatus, disableColumnFilter: disableColumnFilter, isSortable: !!isSortable, numberOfSelectedRows: checkedIndexes === null || checkedIndexes === void 0 ? void 0 : checkedIndexes.length, onSelectAllIndexesHandler: onSelectAllIndexesHandler, propConfig: propConfig, propName: propName, schema: properties[propName], setPopoverConfig: setPopoverConfig, setSortAsc: setSortAsc, setSortColumn: setSortColumn, sortAsc: sortColumn === propName ? sortAsc : undefined, style: style }));
|
|
285
|
+
}, [
|
|
286
|
+
checkedIndexes === null || checkedIndexes === void 0 ? void 0 : checkedIndexes.length,
|
|
287
|
+
columnFilterMap,
|
|
288
|
+
columnNames,
|
|
289
|
+
config,
|
|
290
|
+
disableColumnFilter,
|
|
291
|
+
isAllRowsChecked,
|
|
292
|
+
isColumnFilterable,
|
|
293
|
+
isSortable,
|
|
294
|
+
onSelectAllIndexesHandler,
|
|
295
|
+
properties,
|
|
296
|
+
sortAsc,
|
|
297
|
+
sortColumn,
|
|
298
|
+
]);
|
|
299
|
+
const SchemaTableTd = React.useCallback(({ columnIndex, rowIndex, style }) => {
|
|
300
|
+
const propName = columnNames[columnIndex];
|
|
301
|
+
return (_jsx(Td, { checkedIndexes: checkedIndexes, columnIndex: columnIndex, data: data, getRowClassName: getRowClassName, getRowSelected: getRowSelected, onCheckedIndexesChange: onCheckedIndexesChange, onRowClick: onRowClick, propConfig: config ? config[propName] : undefined, propName: propName, propSchema: properties[propName], rowHeight: rowHeight, rowIndex: rowIndex, sortedRenderData: sortedRenderData, style: style }));
|
|
302
|
+
}, [
|
|
303
|
+
checkedIndexes,
|
|
304
|
+
columnNames,
|
|
305
|
+
config,
|
|
306
|
+
data,
|
|
307
|
+
getRowClassName,
|
|
308
|
+
getRowSelected,
|
|
309
|
+
onCheckedIndexesChange,
|
|
310
|
+
onRowClick,
|
|
311
|
+
properties,
|
|
312
|
+
rowHeight,
|
|
313
|
+
sortedRenderData,
|
|
314
|
+
]);
|
|
315
|
+
const onSearchChange = React.useCallback((e) => {
|
|
316
|
+
setSearchQuery(e.currentTarget.value);
|
|
317
|
+
}, []);
|
|
318
|
+
const getRowHeight = React.useCallback(() => rowHeight, [rowHeight]);
|
|
319
|
+
const rowWidth = dynamicWidthColumnCount ? width : fixedWidthColumnsWidth;
|
|
320
|
+
const rowCount = React.useMemo(() => (sortedRenderData ? sortedRenderData.length : 0), [sortedRenderData]);
|
|
321
|
+
const tableBodyHeight = React.useMemo(() => {
|
|
322
|
+
const rowsHeight = rowHeight * rowCount;
|
|
323
|
+
const rowsMaxHeight = maxHeight ? maxHeight - (isSearchable ? 50 : 0) : 0;
|
|
324
|
+
return rowsMaxHeight && rowsMaxHeight < rowsHeight
|
|
325
|
+
? rowsMaxHeight
|
|
326
|
+
: rowsHeight;
|
|
327
|
+
}, [maxHeight, isSearchable, rowCount, rowHeight]);
|
|
328
|
+
const onPopoverClose = React.useCallback(() => {
|
|
329
|
+
setPopoverConfig(undefined);
|
|
330
|
+
}, []);
|
|
331
|
+
const onSchemaColumnFilterChange = React.useCallback((newColumnFilterValue) => {
|
|
332
|
+
if (!popoverConfig) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (newColumnFilterValue === undefined) {
|
|
336
|
+
disableColumnFilter(popoverConfig.propName);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
setColumnFilterMap((columnFilterMap) => (Object.assign(Object.assign({}, columnFilterMap), { [popoverConfig.propName]: newColumnFilterValue })));
|
|
340
|
+
}, [disableColumnFilter, popoverConfig]);
|
|
341
|
+
return (_jsxs("div", Object.assign({ className: `schema-table${onRowClick ? " schema-table--clickable-rows" : ""}`, style: Object.assign(Object.assign({}, style), { width: rowWidth }), role: "table" }, { children: [_jsxs("div", Object.assign({ className: "schema-table__action-container" }, { children: [_jsx("div", Object.assign({ style: { flex: 1 } }, { children: isSearchable ? (_jsx("input", { type: "text", placeholder: searchPlaceholder || "Search...", value: searchQuery, onChange: onSearchChange, autoFocus: true })) : null })), customElement] })), _jsx(Heading, Object.assign({ height: 50, itemCount: columnCount, itemSize: getColumnWidth, layout: "horizontal", width: rowWidth, sortAsc: sortAsc, setSortAsc: setSortAsc, setSortColumn: setSortColumn, sortColumn: sortColumn, sortedRenderData: sortedRenderData, className: "schema-table__th-row" }, { children: SchemaTableTh }), `thead_${rowWidth}_${sortColumn}_${sortAsc}_${searchQuery}`), _jsx(VariableSizeGrid, Object.assign({ className: "schema-table__tbody", height: tableBodyHeight, width: rowWidth, columnWidth: getColumnWidth, rowHeight: getRowHeight, columnCount: columnCount, rowCount: rowCount }, { children: SchemaTableTd }), `tbody_${tableBodyHeight}_${rowWidth}_${sortColumn}_${sortAsc}_${searchQuery}_${columnCount}`), popoverConfig ? (_jsx(SchemaColumnFilterPopover, { referenceElement: popoverConfig.referenceElement, onClose: onPopoverClose, onChange: onSchemaColumnFilterChange, propName: popoverConfig.propName, propSchema: schema.properties[popoverConfig.propName], value: columnFilterMap[popoverConfig.propName] })) : null] })));
|
|
342
|
+
}
|
|
343
|
+
export default React.memo(SchemaTable);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import "@testing-library/jest-dom";
|
|
3
|
+
import { render, screen } from "@testing-library/react";
|
|
4
|
+
import SchemaTable from "./index";
|
|
5
|
+
import exampleData, { exampleDataSchema } from "../../exampleData";
|
|
6
|
+
it("renders an empty table", () => {
|
|
7
|
+
render(_jsx(SchemaTable, { data: [], schema: {}, width: 1 }));
|
|
8
|
+
const table = screen.getByRole("table");
|
|
9
|
+
expect(table).toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
it("should match the configured width without configuration", () => {
|
|
12
|
+
render(_jsx(SchemaTable, { data: exampleData, schema: exampleDataSchema, width: 1000 }));
|
|
13
|
+
const table = screen.getByRole("table");
|
|
14
|
+
// eslint-disable-next-line testing-library/no-node-access
|
|
15
|
+
const renderedWidth = [...table.querySelectorAll(".schema-table__th")].reduce((prev, node) => prev + parseInt(node.style.width), 0);
|
|
16
|
+
expect(renderedWidth).toBe(1000);
|
|
17
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const localeFormat: (date: Date | number, dateFormat: string) => string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function getEmptyObject(schema) {
|
|
2
|
+
const { properties = {} } = schema;
|
|
3
|
+
return Object.keys(properties).reduce((prev, propName) => {
|
|
4
|
+
const prop = properties[propName];
|
|
5
|
+
let propValue;
|
|
6
|
+
switch (prop.type) {
|
|
7
|
+
case "string":
|
|
8
|
+
propValue = prop.enum ? prop.enum[0] : prop.default || "";
|
|
9
|
+
break;
|
|
10
|
+
case "array":
|
|
11
|
+
propValue = prop.default || [];
|
|
12
|
+
break;
|
|
13
|
+
case "number":
|
|
14
|
+
case "integer":
|
|
15
|
+
propValue = prop.default || 0;
|
|
16
|
+
break;
|
|
17
|
+
case "boolean":
|
|
18
|
+
propValue = prop.default || false;
|
|
19
|
+
break;
|
|
20
|
+
case "object":
|
|
21
|
+
propValue = prop.default || getEmptyObject(prop);
|
|
22
|
+
break;
|
|
23
|
+
// eslint-disable-next-line no-fallthrough
|
|
24
|
+
default:
|
|
25
|
+
console.log(prop);
|
|
26
|
+
throw new Error("Unsupported property");
|
|
27
|
+
}
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
prev[propName] = propValue;
|
|
30
|
+
return prev;
|
|
31
|
+
}, {});
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function uncamel(camel: string): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { upperFirst } from "lodash";
|
|
2
|
+
function isUpperCase(letter) {
|
|
3
|
+
return !!letter.match(/[A-Z]/);
|
|
4
|
+
}
|
|
5
|
+
export function uncamel(camel) {
|
|
6
|
+
return upperFirst(camel.replaceAll(/[A-Z]/g, (letter, position) => {
|
|
7
|
+
const prevLetter = camel[position - 1];
|
|
8
|
+
const prevIsUppercase = prevLetter ? isUpperCase(prevLetter) : false;
|
|
9
|
+
const nextLetter = camel[position + 1];
|
|
10
|
+
const nextIsUppercase = nextLetter ? isUpperCase(nextLetter) : false;
|
|
11
|
+
if (position === 0 ||
|
|
12
|
+
(prevIsUppercase && (!nextLetter || nextIsUppercase))) {
|
|
13
|
+
return letter;
|
|
14
|
+
}
|
|
15
|
+
return ` ${nextIsUppercase ? letter : letter.toLowerCase()}`;
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface IColumnConfig<T> {
|
|
3
|
+
align?: "start" | "center" | "end";
|
|
4
|
+
defaultSortDesc?: boolean;
|
|
5
|
+
hidden?: boolean;
|
|
6
|
+
hoverTitle?: string;
|
|
7
|
+
renderCell?: (rowData: T, dataIndex: number, rowIndex: number) => React.ReactElement | null;
|
|
8
|
+
renderData?: (rowData: T, dataIndex: number) => string;
|
|
9
|
+
sort?: (a: T, b: T, sortAsc: boolean) => number;
|
|
10
|
+
sortByValue?: boolean;
|
|
11
|
+
sortable?: boolean;
|
|
12
|
+
title?: string | React.ReactElement;
|
|
13
|
+
width?: number;
|
|
14
|
+
order?: number;
|
|
15
|
+
isFilterable?: boolean;
|
|
16
|
+
dateFormat?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface IRenderData {
|
|
19
|
+
_index: number;
|
|
20
|
+
[key: string]: string | any;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { oas31 } from "openapi3-ts";
|
|
2
|
+
export interface IExampleData {
|
|
3
|
+
jobId: string;
|
|
4
|
+
/** @enum {string} */
|
|
5
|
+
jobType: "scrape" | "clean";
|
|
6
|
+
topLevelId: number;
|
|
7
|
+
domainId: string;
|
|
8
|
+
hostId: string;
|
|
9
|
+
sourceId: string;
|
|
10
|
+
/** @enum {string} */
|
|
11
|
+
sources: ("fromIndexPage" | "manual")[];
|
|
12
|
+
priority: number;
|
|
13
|
+
/** Format: date-time */
|
|
14
|
+
insertDateTime: string;
|
|
15
|
+
/** Format: date-time */
|
|
16
|
+
scrapeStartDateTime?: string;
|
|
17
|
+
/** Format: date-time */
|
|
18
|
+
scrapeEndDateTime?: string;
|
|
19
|
+
scraperHost?: string;
|
|
20
|
+
scraperContainer?: string;
|
|
21
|
+
errorMessage?: string;
|
|
22
|
+
/** Format: url */
|
|
23
|
+
url?: string;
|
|
24
|
+
retryCount: number;
|
|
25
|
+
isError: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare const exampleDataSchema: oas31.SchemaObject;
|
|
28
|
+
declare const exampleData: IExampleData[];
|
|
29
|
+
export default exampleData;
|