@absreim/react-bootstrap-data-grid 1.0.0 → 1.1.0
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/FilterOptionsTable/DateFilterRow.d.ts +10 -0
- package/FilterOptionsTable/DateFilterRow.jsx +63 -0
- package/FilterOptionsTable/FilterOptionsTable.d.ts +8 -0
- package/FilterOptionsTable/FilterOptionsTable.jsx +118 -0
- package/FilterOptionsTable/NumberFilterRow.d.ts +9 -0
- package/FilterOptionsTable/NumberFilterRow.jsx +47 -0
- package/FilterOptionsTable/StringFilterRow.d.ts +9 -0
- package/FilterOptionsTable/StringFilterRow.jsx +47 -0
- package/FilterOptionsTable/types.d.ts +13 -0
- package/FilterOptionsTable/types.js +1 -0
- package/FilterOptionsTable/useFormStateFromTableFilterState.d.ts +4 -0
- package/FilterOptionsTable/useFormStateFromTableFilterState.js +72 -0
- package/Grid.d.ts +2 -1
- package/Grid.jsx +22 -5
- package/ToggleButton.d.ts +8 -0
- package/ToggleButton.jsx +13 -0
- package/hooks/useEditableFromFilterState.d.ts +3 -0
- package/hooks/useEditableFromFilterState.js +14 -0
- package/hooks/useFilter.d.ts +3 -0
- package/hooks/useFilter.js +82 -0
- package/hooks/useFilterStateFromEditable.d.ts +3 -0
- package/hooks/useFilterStateFromEditable.js +18 -0
- package/models/descriptions.d.ts +2 -0
- package/models/descriptions.js +6 -0
- package/package.json +3 -2
- package/types.d.ts +51 -0
- package/types.js +30 -1
- package/util/datetime.d.ts +3 -0
- package/util/datetime.js +9 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
import { DateFormFilterState } from "./types";
|
|
3
|
+
interface DateFilterRowProps {
|
|
4
|
+
includeTime: boolean;
|
|
5
|
+
columnLabel: string;
|
|
6
|
+
filterState: DateFormFilterState;
|
|
7
|
+
setFilterState: (filterState: DateFormFilterState) => void;
|
|
8
|
+
}
|
|
9
|
+
declare const DateFilterRow: FC<DateFilterRowProps>;
|
|
10
|
+
export default DateFilterRow;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
import { useMemo } from "react";
|
|
13
|
+
import { dateFilterSchemeNames, dateFilterSchemes, } from "../types";
|
|
14
|
+
import { nanoid } from "nanoid/non-secure";
|
|
15
|
+
var DateFilterRow = function (_a) {
|
|
16
|
+
var includeTime = _a.includeTime, columnLabel = _a.columnLabel, filterState = _a.filterState, setFilterState = _a.setFilterState;
|
|
17
|
+
var handleOpChange = function (_a) {
|
|
18
|
+
var target = _a.target;
|
|
19
|
+
setFilterState(__assign(__assign({}, filterState), { scheme: target.value }));
|
|
20
|
+
};
|
|
21
|
+
var handleEnabledChange = function (_a) {
|
|
22
|
+
var target = _a.target;
|
|
23
|
+
setFilterState(__assign(__assign({}, filterState), { enabled: target.checked }));
|
|
24
|
+
};
|
|
25
|
+
var handleStartValueChange = function (_a) {
|
|
26
|
+
var target = _a.target;
|
|
27
|
+
setFilterState(__assign(__assign({}, filterState), { startDate: target.value }));
|
|
28
|
+
};
|
|
29
|
+
var handleEndValueChange = function (_a) {
|
|
30
|
+
var target = _a.target;
|
|
31
|
+
setFilterState(__assign(__assign({}, filterState), { endDate: target.value }));
|
|
32
|
+
};
|
|
33
|
+
var enabled = filterState.enabled, scheme = filterState.scheme, startDate = filterState.startDate, endDate = filterState.endDate;
|
|
34
|
+
var inputType = includeTime ? "datetime-local" : "date";
|
|
35
|
+
var inputId = useMemo(function () { return nanoid(); }, []);
|
|
36
|
+
var startDateInputId = "$startDate-".concat(inputId);
|
|
37
|
+
var endDateInputId = "$endDate-".concat(inputId);
|
|
38
|
+
return (<tr>
|
|
39
|
+
<td>
|
|
40
|
+
<input type="checkbox" checked={enabled} name="enabled" onChange={handleEnabledChange}/>
|
|
41
|
+
</td>
|
|
42
|
+
<td>{columnLabel}</td>
|
|
43
|
+
<td>{filterState.type === "date" ? "Date" : "Datetime"}</td>
|
|
44
|
+
<td>
|
|
45
|
+
<select disabled={!enabled} className="form-select" value={scheme} onChange={handleOpChange}>
|
|
46
|
+
{dateFilterSchemes.map(function (scheme) { return (<option key={scheme} value={scheme}>
|
|
47
|
+
{dateFilterSchemeNames[scheme]}
|
|
48
|
+
</option>); })}
|
|
49
|
+
</select>
|
|
50
|
+
</td>
|
|
51
|
+
<td>
|
|
52
|
+
{scheme !== "endAt" && (<>
|
|
53
|
+
{scheme === "between" && (<label htmlFor={startDateInputId}>Start Date</label>)}
|
|
54
|
+
<input id={startDateInputId} className="form-control" type={inputType} required={enabled} disabled={!enabled} value={startDate} onChange={handleStartValueChange} aria-label="Start Date"/>
|
|
55
|
+
</>)}
|
|
56
|
+
{scheme !== "startFrom" && (<>
|
|
57
|
+
{scheme === "between" && (<label htmlFor={endDateInputId}>End Date</label>)}
|
|
58
|
+
<input className="form-control" type={inputType} required={enabled} disabled={!enabled} value={endDate} onChange={handleEndValueChange} aria-label="End Date"/>
|
|
59
|
+
</>)}
|
|
60
|
+
</td>
|
|
61
|
+
</tr>);
|
|
62
|
+
};
|
|
63
|
+
export default DateFilterRow;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
import { EditableTableFilterState, TableFilterState } from "../types";
|
|
3
|
+
interface FilterOptionsTableProps {
|
|
4
|
+
filterState: TableFilterState;
|
|
5
|
+
setFilterState: (filterState: EditableTableFilterState) => void;
|
|
6
|
+
}
|
|
7
|
+
declare const FilterOptionsTable: FC<FilterOptionsTableProps>;
|
|
8
|
+
export default FilterOptionsTable;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
import { useState } from "react";
|
|
13
|
+
import StringFilterRow from "./StringFilterRow";
|
|
14
|
+
import { datetimeInputStrToUtc } from "../util/datetime";
|
|
15
|
+
import NumberFilterRow from "./NumberFilterRow";
|
|
16
|
+
import useFormStateFromTableFilterState from "./useFormStateFromTableFilterState";
|
|
17
|
+
import DateFilterRow from "./DateFilterRow";
|
|
18
|
+
var convertFilterFormStateToEditableState = function (filterFormState) {
|
|
19
|
+
return Object.keys(filterFormState).reduce(function (editableState, colName) {
|
|
20
|
+
var rowFilterFormState = filterFormState[colName];
|
|
21
|
+
switch (rowFilterFormState.type) {
|
|
22
|
+
case "string": {
|
|
23
|
+
editableState[colName] = __assign({}, rowFilterFormState);
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
case "number": {
|
|
27
|
+
editableState[colName] = {
|
|
28
|
+
type: rowFilterFormState.type,
|
|
29
|
+
enabled: rowFilterFormState.enabled,
|
|
30
|
+
scheme: rowFilterFormState.scheme,
|
|
31
|
+
numValue: rowFilterFormState.inputValue === ""
|
|
32
|
+
? null
|
|
33
|
+
: Number(rowFilterFormState.inputValue),
|
|
34
|
+
};
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
default: {
|
|
38
|
+
// date or datetime
|
|
39
|
+
var partialFilterState = {
|
|
40
|
+
type: rowFilterFormState.type,
|
|
41
|
+
enabled: rowFilterFormState.enabled,
|
|
42
|
+
};
|
|
43
|
+
var strModifierFn_1 = rowFilterFormState.type === "date"
|
|
44
|
+
? function (str) { return str; }
|
|
45
|
+
: datetimeInputStrToUtc;
|
|
46
|
+
var inputStrToDate = function (str) {
|
|
47
|
+
return str === "" ? null : new Date(strModifierFn_1(str));
|
|
48
|
+
};
|
|
49
|
+
switch (rowFilterFormState.scheme) {
|
|
50
|
+
case "startFrom": {
|
|
51
|
+
editableState[colName] = __assign(__assign({}, partialFilterState), { scheme: rowFilterFormState.scheme, startDate: inputStrToDate(rowFilterFormState.startDate) });
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case "endAt": {
|
|
55
|
+
editableState[colName] = __assign(__assign({}, partialFilterState), { scheme: rowFilterFormState.scheme, endDate: inputStrToDate(rowFilterFormState.endDate) });
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
default: {
|
|
59
|
+
editableState[colName] = __assign(__assign({}, partialFilterState), { scheme: rowFilterFormState.scheme, startDate: inputStrToDate(rowFilterFormState.startDate), endDate: inputStrToDate(rowFilterFormState.endDate) });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return editableState;
|
|
65
|
+
}, {});
|
|
66
|
+
};
|
|
67
|
+
var FilterOptionsTable = function (_a) {
|
|
68
|
+
var filterState = _a.filterState, setFilterState = _a.setFilterState;
|
|
69
|
+
var formFilterState = useFormStateFromTableFilterState(filterState);
|
|
70
|
+
var _b = useState(formFilterState), formState = _b[0], setFormState = _b[1];
|
|
71
|
+
var getRows = function () {
|
|
72
|
+
return Object.keys(formState).map(function (colName) {
|
|
73
|
+
function getColStateSetter(colName) {
|
|
74
|
+
return function (rowState) {
|
|
75
|
+
var _a;
|
|
76
|
+
return setFormState(__assign(__assign({}, formState), (_a = {}, _a[colName] = rowState, _a)));
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
var colLabel = filterState[colName].label;
|
|
80
|
+
var colFilterState = formState[colName];
|
|
81
|
+
switch (colFilterState.type) {
|
|
82
|
+
case "string": {
|
|
83
|
+
return (<StringFilterRow key={colName} columnLabel={colLabel} filterState={colFilterState} setFilterState={getColStateSetter(colName)}/>);
|
|
84
|
+
}
|
|
85
|
+
case "number": {
|
|
86
|
+
return (<NumberFilterRow key={colName} columnLabel={colLabel} filterState={colFilterState} setFilterState={getColStateSetter(colName)}/>);
|
|
87
|
+
}
|
|
88
|
+
default: {
|
|
89
|
+
// date or datetime
|
|
90
|
+
return (<DateFilterRow key={colName} includeTime={colFilterState.type === "datetime"} columnLabel={colLabel} filterState={colFilterState} setFilterState={getColStateSetter(colName)}/>);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
var onSubmit = function (event) {
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
var editableTableFilterState = convertFilterFormStateToEditableState(formState);
|
|
98
|
+
setFilterState(editableTableFilterState);
|
|
99
|
+
};
|
|
100
|
+
return (<form onSubmit={onSubmit}>
|
|
101
|
+
<table className="table">
|
|
102
|
+
<thead>
|
|
103
|
+
<tr>
|
|
104
|
+
<th>Enabled</th>
|
|
105
|
+
<th>Column</th>
|
|
106
|
+
<th>Type</th>
|
|
107
|
+
<th>Operator</th>
|
|
108
|
+
<th>Value</th>
|
|
109
|
+
</tr>
|
|
110
|
+
</thead>
|
|
111
|
+
<tbody>{getRows()}</tbody>
|
|
112
|
+
</table>
|
|
113
|
+
<button className="btn btn-secondary" type="submit">
|
|
114
|
+
Submit
|
|
115
|
+
</button>
|
|
116
|
+
</form>);
|
|
117
|
+
};
|
|
118
|
+
export default FilterOptionsTable;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
import { NumberFormFilterState } from "./types";
|
|
3
|
+
interface NumberFilterRowProps {
|
|
4
|
+
columnLabel: string;
|
|
5
|
+
filterState: NumberFormFilterState;
|
|
6
|
+
setFilterState: (filterState: NumberFormFilterState) => void;
|
|
7
|
+
}
|
|
8
|
+
declare const NumberFilterRow: FC<NumberFilterRowProps>;
|
|
9
|
+
export default NumberFilterRow;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
import { numberFilterSchemeNames, numberFilterSchemes, } from "../types";
|
|
13
|
+
var NumberFilterRow = function (_a) {
|
|
14
|
+
var columnLabel = _a.columnLabel, filterState = _a.filterState, setFilterState = _a.setFilterState;
|
|
15
|
+
var handleOpChange = function (_a) {
|
|
16
|
+
var target = _a.target;
|
|
17
|
+
setFilterState(__assign(__assign({}, filterState), { scheme: target.value }));
|
|
18
|
+
};
|
|
19
|
+
var handleEnabledChange = function (_a) {
|
|
20
|
+
var target = _a.target;
|
|
21
|
+
setFilterState(__assign(__assign({}, filterState), { enabled: target.checked }));
|
|
22
|
+
};
|
|
23
|
+
var handleNumInputValueChange = function (_a) {
|
|
24
|
+
var target = _a.target;
|
|
25
|
+
setFilterState(__assign(__assign({}, filterState), { inputValue: target.value }));
|
|
26
|
+
};
|
|
27
|
+
var enabled = filterState.enabled, scheme = filterState.scheme, inputValue = filterState.inputValue;
|
|
28
|
+
// TODO: Input labelling for accessibility
|
|
29
|
+
return (<tr>
|
|
30
|
+
<td>
|
|
31
|
+
<input type="checkbox" checked={enabled} name="enabled" onChange={handleEnabledChange}/>
|
|
32
|
+
</td>
|
|
33
|
+
<td>{columnLabel}</td>
|
|
34
|
+
<td>Number</td>
|
|
35
|
+
<td>
|
|
36
|
+
<select disabled={!enabled} className="form-select" value={scheme} onChange={handleOpChange}>
|
|
37
|
+
{numberFilterSchemes.map(function (scheme) { return (<option key={scheme} value={scheme}>
|
|
38
|
+
{numberFilterSchemeNames[scheme]}
|
|
39
|
+
</option>); })}
|
|
40
|
+
</select>
|
|
41
|
+
</td>
|
|
42
|
+
<td>
|
|
43
|
+
<input className="form-control" type="number" required={enabled} disabled={!enabled} value={inputValue} onChange={handleNumInputValueChange}/>
|
|
44
|
+
</td>
|
|
45
|
+
</tr>);
|
|
46
|
+
};
|
|
47
|
+
export default NumberFilterRow;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
import { StringFilterState } from "../types";
|
|
3
|
+
interface StringFilterRowProps {
|
|
4
|
+
columnLabel: string;
|
|
5
|
+
filterState: StringFilterState;
|
|
6
|
+
setFilterState: (filterState: StringFilterState) => void;
|
|
7
|
+
}
|
|
8
|
+
declare const StringFilterRow: FC<StringFilterRowProps>;
|
|
9
|
+
export default StringFilterRow;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
import { stringFilterSchemeNames, stringFilterSchemes, } from "../types";
|
|
13
|
+
var StringFilterRow = function (_a) {
|
|
14
|
+
var columnLabel = _a.columnLabel, filterState = _a.filterState, setFilterState = _a.setFilterState;
|
|
15
|
+
var handleOpChange = function (_a) {
|
|
16
|
+
var target = _a.target;
|
|
17
|
+
setFilterState(__assign(__assign({}, filterState), { scheme: target.value }));
|
|
18
|
+
};
|
|
19
|
+
var handleEnabledChange = function (_a) {
|
|
20
|
+
var target = _a.target;
|
|
21
|
+
setFilterState(__assign(__assign({}, filterState), { enabled: target.checked }));
|
|
22
|
+
};
|
|
23
|
+
var handleSearchStringChange = function (_a) {
|
|
24
|
+
var target = _a.target;
|
|
25
|
+
setFilterState(__assign(__assign({}, filterState), { searchString: target.value }));
|
|
26
|
+
};
|
|
27
|
+
var enabled = filterState.enabled, scheme = filterState.scheme, searchString = filterState.searchString;
|
|
28
|
+
// TODO: Input labelling for accessibility
|
|
29
|
+
return (<tr>
|
|
30
|
+
<td>
|
|
31
|
+
<input type="checkbox" checked={enabled} name="enabled" onChange={handleEnabledChange}/>
|
|
32
|
+
</td>
|
|
33
|
+
<td>{columnLabel}</td>
|
|
34
|
+
<td>String</td>
|
|
35
|
+
<td>
|
|
36
|
+
<select disabled={!enabled} className="form-select" value={scheme} onChange={handleOpChange}>
|
|
37
|
+
{stringFilterSchemes.map(function (scheme) { return (<option key={scheme} value={scheme}>
|
|
38
|
+
{stringFilterSchemeNames[scheme]}
|
|
39
|
+
</option>); })}
|
|
40
|
+
</select>
|
|
41
|
+
</td>
|
|
42
|
+
<td>
|
|
43
|
+
<input className="form-control" required={enabled} disabled={!enabled} value={searchString} onChange={handleSearchStringChange}/>
|
|
44
|
+
</td>
|
|
45
|
+
</tr>);
|
|
46
|
+
};
|
|
47
|
+
export default StringFilterRow;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AbstractDateFilterState, AbstractFilterState, DateFilterScheme, NumberFilterScheme, StringFilterState } from "../types";
|
|
2
|
+
export interface NumberFormFilterState extends AbstractFilterState {
|
|
3
|
+
type: "number";
|
|
4
|
+
scheme: NumberFilterScheme;
|
|
5
|
+
inputValue: string;
|
|
6
|
+
}
|
|
7
|
+
export interface DateFormFilterState extends AbstractDateFilterState {
|
|
8
|
+
scheme: DateFilterScheme;
|
|
9
|
+
startDate: string;
|
|
10
|
+
endDate: string;
|
|
11
|
+
}
|
|
12
|
+
export type FilterFormRowState = StringFilterState | NumberFormFilterState | DateFormFilterState;
|
|
13
|
+
export type FilterFormState = Record<string, FilterFormRowState>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
import { useMemo } from "react";
|
|
13
|
+
import { dateToDatetimeInputStr, dateToInputStr } from "../util/datetime";
|
|
14
|
+
var useFormStateFromTableFilterState = function (tableFilterState) {
|
|
15
|
+
return useMemo(function () {
|
|
16
|
+
return Object.keys(tableFilterState).reduce(function (filterFormState, colName) {
|
|
17
|
+
var editableState = tableFilterState[colName].editableState;
|
|
18
|
+
switch (editableState.type) {
|
|
19
|
+
case "string": {
|
|
20
|
+
filterFormState[colName] = __assign({}, editableState);
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
case "number": {
|
|
24
|
+
filterFormState[colName] = {
|
|
25
|
+
type: "number",
|
|
26
|
+
scheme: editableState.scheme,
|
|
27
|
+
enabled: editableState.enabled,
|
|
28
|
+
inputValue: editableState.numValue === null
|
|
29
|
+
? ""
|
|
30
|
+
: String(editableState.numValue),
|
|
31
|
+
};
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
default: {
|
|
35
|
+
// date or datetime
|
|
36
|
+
var partialFormState = {
|
|
37
|
+
type: editableState.type,
|
|
38
|
+
enabled: editableState.enabled,
|
|
39
|
+
};
|
|
40
|
+
var dateToStrConverter = editableState.type === "date"
|
|
41
|
+
? dateToInputStr
|
|
42
|
+
: dateToDatetimeInputStr;
|
|
43
|
+
switch (editableState.scheme) {
|
|
44
|
+
case "startFrom": {
|
|
45
|
+
filterFormState[colName] = __assign(__assign({}, partialFormState), { scheme: "startFrom", startDate: editableState.startDate === null
|
|
46
|
+
? ""
|
|
47
|
+
: dateToStrConverter(editableState.startDate), endDate: "" });
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case "endAt": {
|
|
51
|
+
filterFormState[colName] = __assign(__assign({}, partialFormState), { scheme: "endAt", startDate: "", endDate: editableState.endDate === null
|
|
52
|
+
? ""
|
|
53
|
+
: dateToStrConverter(editableState.endDate) });
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
default: {
|
|
57
|
+
// between
|
|
58
|
+
filterFormState[colName] = __assign(__assign({}, partialFormState), { scheme: "between", startDate: editableState.startDate === null
|
|
59
|
+
? ""
|
|
60
|
+
: dateToStrConverter(editableState.startDate), endDate: editableState.endDate === null
|
|
61
|
+
? ""
|
|
62
|
+
: dateToStrConverter(editableState.endDate) });
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return filterFormState;
|
|
69
|
+
}, {});
|
|
70
|
+
}, [tableFilterState]);
|
|
71
|
+
};
|
|
72
|
+
export default useFormStateFromTableFilterState;
|
package/Grid.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FC } from "react";
|
|
2
|
-
import { ColDef, RowDef, Size, TableSortModel } from "./types";
|
|
2
|
+
import { ColDef, FilterModel, RowDef, Size, TableSortModel } from "./types";
|
|
3
3
|
export interface GridPaginationState {
|
|
4
4
|
pageSizeOptions: number[];
|
|
5
5
|
pageSizeIndex: number;
|
|
@@ -14,6 +14,7 @@ export interface GridProps {
|
|
|
14
14
|
cols: ColDef[];
|
|
15
15
|
pagination?: GridPaginationState;
|
|
16
16
|
sortModel?: TableSortModel;
|
|
17
|
+
filterModel?: FilterModel;
|
|
17
18
|
}
|
|
18
19
|
declare const Grid: FC<GridProps>;
|
|
19
20
|
export default Grid;
|
package/Grid.jsx
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useMemo } from "react";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
3
|
import Pagination from "./Pagination";
|
|
4
4
|
import classNames from "classnames";
|
|
5
5
|
import ColHeaderCell from "./ColHeaderCell";
|
|
6
|
+
import useFilter from "./hooks/useFilter";
|
|
7
|
+
import ToggleButton from "./ToggleButton";
|
|
8
|
+
import FilterOptionsTable from "./FilterOptionsTable/FilterOptionsTable";
|
|
9
|
+
import useFilterStateFromEditable from "./hooks/useFilterStateFromEditable";
|
|
6
10
|
var getTypeComparator = function (typeStr) {
|
|
7
11
|
if (typeStr === "date" || typeStr === "datetime") {
|
|
8
12
|
return function (a, b) { return a.valueOf() - b.valueOf(); };
|
|
@@ -24,10 +28,13 @@ var getRowComparator = function (comparator, fieldName) {
|
|
|
24
28
|
return function (rowA, rowB) { return comparator(rowA[fieldName], rowB[fieldName]); };
|
|
25
29
|
};
|
|
26
30
|
var Grid = function (_a) {
|
|
27
|
-
var rows = _a.rows, cols = _a.cols, pagination = _a.pagination, sortModel = _a.sortModel;
|
|
31
|
+
var rows = _a.rows, cols = _a.cols, pagination = _a.pagination, sortModel = _a.sortModel, filterModel = _a.filterModel;
|
|
32
|
+
var editableFilterState = (filterModel === null || filterModel === void 0 ? void 0 : filterModel.tableFilterState) || null;
|
|
33
|
+
var filterState = useFilterStateFromEditable(cols, editableFilterState);
|
|
34
|
+
var filteredRows = useFilter(rows, editableFilterState);
|
|
28
35
|
var sortedRows = useMemo(function () {
|
|
29
36
|
if (!sortModel || !sortModel.sortColDef) {
|
|
30
|
-
return
|
|
37
|
+
return filteredRows;
|
|
31
38
|
}
|
|
32
39
|
var sortFieldName = sortModel.sortColDef.name;
|
|
33
40
|
var sortOrder = sortModel.sortColDef.order;
|
|
@@ -47,8 +54,8 @@ var Grid = function (_a) {
|
|
|
47
54
|
};
|
|
48
55
|
rowComparator = getRowComparator(descComparator, sortFieldName);
|
|
49
56
|
}
|
|
50
|
-
return
|
|
51
|
-
}, [
|
|
57
|
+
return filteredRows.slice().sort(rowComparator);
|
|
58
|
+
}, [filteredRows, cols, sortModel]);
|
|
52
59
|
var currentPageRows = useMemo(function () {
|
|
53
60
|
if (pagination === undefined) {
|
|
54
61
|
return sortedRows;
|
|
@@ -107,6 +114,7 @@ var Grid = function (_a) {
|
|
|
107
114
|
return displayRow;
|
|
108
115
|
});
|
|
109
116
|
}, [currentPageRows, cols]);
|
|
117
|
+
var _b = useState(false), filterOptionsVisible = _b[0], setFilterOptionsVisible = _b[1];
|
|
110
118
|
var handleSetPageNum = function (pageNum) {
|
|
111
119
|
if (pagination === undefined) {
|
|
112
120
|
return;
|
|
@@ -119,11 +127,20 @@ var Grid = function (_a) {
|
|
|
119
127
|
}
|
|
120
128
|
pagination.setPageSizeIndex(Number(event.target.value));
|
|
121
129
|
};
|
|
130
|
+
var handleToggleFilterOptions = function () {
|
|
131
|
+
setFilterOptionsVisible(!filterOptionsVisible);
|
|
132
|
+
};
|
|
122
133
|
// Once this component implements selection state, and if such interactivity is enabled, (conditionally) change the
|
|
123
134
|
// aria role to "grid".
|
|
124
135
|
// Array index is okay for the key for rows until some type of feature involving changing the index of rows, such as
|
|
125
136
|
// sorting or pagination, is implemented.
|
|
137
|
+
// TODO: implement the above described features: conditionally changing aria role to grid and a key field other than
|
|
138
|
+
// index
|
|
126
139
|
return (<div>
|
|
140
|
+
{filterState && filterModel && (<div>
|
|
141
|
+
<ToggleButton isActive={filterOptionsVisible} label={"".concat(filterOptionsVisible ? "Hide" : "Show ", " Filter Options")} onClick={handleToggleFilterOptions}/>
|
|
142
|
+
{filterOptionsVisible && (<FilterOptionsTable filterState={filterState} setFilterState={filterModel.setTableFilterState}/>)}
|
|
143
|
+
</div>)}
|
|
127
144
|
<table className="table">
|
|
128
145
|
<thead>
|
|
129
146
|
<tr aria-rowindex={1}>
|
package/ToggleButton.jsx
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
var ToggleButton = function (_a) {
|
|
4
|
+
var isActive = _a.isActive, label = _a.label, onClick = _a.onClick;
|
|
5
|
+
var baseClasses = ["btn", "btn-primary"];
|
|
6
|
+
var variableClasses = {
|
|
7
|
+
active: isActive,
|
|
8
|
+
};
|
|
9
|
+
return (<button type="button" className={classNames(baseClasses, variableClasses)} aria-pressed={isActive} onClick={onClick}>
|
|
10
|
+
{label}
|
|
11
|
+
</button>);
|
|
12
|
+
};
|
|
13
|
+
export default ToggleButton;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
var useEditableFromFilterState = function (filterState) {
|
|
3
|
+
return useMemo(function () {
|
|
4
|
+
if (filterState === null) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
var editableState = {};
|
|
8
|
+
Object.keys(filterState).forEach(function (columnName) {
|
|
9
|
+
editableState[columnName] = filterState[columnName].editableState;
|
|
10
|
+
});
|
|
11
|
+
return editableState;
|
|
12
|
+
}, [filterState]);
|
|
13
|
+
};
|
|
14
|
+
export default useEditableFromFilterState;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
var useFilter = function (rows, filterState) {
|
|
3
|
+
return useMemo(function () {
|
|
4
|
+
if (filterState === null) {
|
|
5
|
+
return rows;
|
|
6
|
+
}
|
|
7
|
+
return rows.filter(function (row) {
|
|
8
|
+
function checkIfPassesStringFilter(value, state) {
|
|
9
|
+
switch (state.scheme) {
|
|
10
|
+
case "contains": {
|
|
11
|
+
return value.includes(state.searchString);
|
|
12
|
+
}
|
|
13
|
+
case "startsWith": {
|
|
14
|
+
return value.startsWith(state.searchString);
|
|
15
|
+
}
|
|
16
|
+
default: {
|
|
17
|
+
return value.endsWith(state.searchString);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function checkIfPassesNumberFilter(value, state) {
|
|
22
|
+
var numValue = Number(state.numValue); // Note that a blank string becomes 0
|
|
23
|
+
switch (state.scheme) {
|
|
24
|
+
case "equals":
|
|
25
|
+
return value === numValue;
|
|
26
|
+
case "greaterThan":
|
|
27
|
+
return value > numValue;
|
|
28
|
+
case "lessThan":
|
|
29
|
+
return value < numValue;
|
|
30
|
+
case "greaterOrEqual":
|
|
31
|
+
return value >= numValue;
|
|
32
|
+
default:
|
|
33
|
+
return value <= numValue;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function checkIfPassesDateFilter(value, state) {
|
|
37
|
+
switch (state.scheme) {
|
|
38
|
+
case "startFrom":
|
|
39
|
+
return state.startDate === null || value >= state.startDate;
|
|
40
|
+
case "endAt":
|
|
41
|
+
return state.endDate === null || value <= state.endDate;
|
|
42
|
+
case "between":
|
|
43
|
+
return (state.startDate === null ||
|
|
44
|
+
state.endDate === null ||
|
|
45
|
+
(value >= state.startDate && value <= state.endDate));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
var columnNames = Object.keys(row);
|
|
49
|
+
for (var _i = 0, columnNames_1 = columnNames; _i < columnNames_1.length; _i++) {
|
|
50
|
+
var columnName = columnNames_1[_i];
|
|
51
|
+
if (!(columnName in filterState)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (!filterState[columnName].enabled) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
var columnFilterState = filterState[columnName];
|
|
58
|
+
switch (columnFilterState.type) {
|
|
59
|
+
case "string": {
|
|
60
|
+
if (!checkIfPassesStringFilter(row[columnName], columnFilterState)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case "number": {
|
|
66
|
+
if (!checkIfPassesNumberFilter(row[columnName], columnFilterState)) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
default: {
|
|
72
|
+
if (!checkIfPassesDateFilter(row[columnName], columnFilterState)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
});
|
|
80
|
+
}, [rows, filterState]);
|
|
81
|
+
};
|
|
82
|
+
export default useFilter;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
var useFilterStateFromEditable = function (colDefs, editableFilterState) {
|
|
3
|
+
return useMemo(function () {
|
|
4
|
+
if (editableFilterState === null) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
var filterState = {};
|
|
8
|
+
colDefs.forEach(function (_a) {
|
|
9
|
+
var name = _a.name, label = _a.label;
|
|
10
|
+
filterState[name] = {
|
|
11
|
+
editableState: editableFilterState[name],
|
|
12
|
+
label: label,
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
return filterState;
|
|
16
|
+
}, [colDefs, editableFilterState]);
|
|
17
|
+
};
|
|
18
|
+
export default useFilterStateFromEditable;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@absreim/react-bootstrap-data-grid",
|
|
3
3
|
"description": "Data grid UI component for use with web apps using React and Bootstrap",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Brook Li",
|
|
7
7
|
"homepage": "https://react-bootstrap-data-grid.vercel.app/",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"grid"
|
|
15
15
|
],
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"classnames": "^2.5.1"
|
|
17
|
+
"classnames": "^2.5.1",
|
|
18
|
+
"dayjs": "^1.11.13"
|
|
18
19
|
},
|
|
19
20
|
"peerDependencies": {
|
|
20
21
|
"react": "^19",
|
package/types.d.ts
CHANGED
|
@@ -23,3 +23,54 @@ export interface TableSortModel {
|
|
|
23
23
|
sortColDef: SortColDef | null;
|
|
24
24
|
setSortColDef: (sortColDef: SortColDef | null) => void;
|
|
25
25
|
}
|
|
26
|
+
export type TableFilterState = Record<string, ColFilterState>;
|
|
27
|
+
export type EditableTableFilterState = Record<string, FilterState>;
|
|
28
|
+
export interface ColFilterState {
|
|
29
|
+
editableState: FilterState;
|
|
30
|
+
label: string;
|
|
31
|
+
}
|
|
32
|
+
export interface AbstractFilterState {
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
}
|
|
35
|
+
export declare const stringFilterSchemes: readonly ["contains", "startsWith", "endsWith"];
|
|
36
|
+
export type StringFilterScheme = (typeof stringFilterSchemes)[number];
|
|
37
|
+
export declare const stringFilterSchemeNames: Record<StringFilterScheme, string>;
|
|
38
|
+
export interface StringFilterState extends AbstractFilterState {
|
|
39
|
+
type: "string";
|
|
40
|
+
scheme: StringFilterScheme;
|
|
41
|
+
searchString: string;
|
|
42
|
+
}
|
|
43
|
+
export declare const numberFilterSchemes: readonly ["equals", "greaterThan", "lessThan", "greaterOrEqual", "lessOrEqual"];
|
|
44
|
+
export type NumberFilterScheme = (typeof numberFilterSchemes)[number];
|
|
45
|
+
export declare const numberFilterSchemeNames: Record<NumberFilterScheme, string>;
|
|
46
|
+
export interface NumberFilterState extends AbstractFilterState {
|
|
47
|
+
type: "number";
|
|
48
|
+
scheme: NumberFilterScheme;
|
|
49
|
+
numValue: number | null;
|
|
50
|
+
}
|
|
51
|
+
export declare const dateFilterSchemes: readonly ["startFrom", "endAt", "between"];
|
|
52
|
+
export type DateFilterScheme = (typeof dateFilterSchemes)[number];
|
|
53
|
+
export declare const dateFilterSchemeNames: Record<DateFilterScheme, string>;
|
|
54
|
+
export interface AbstractDateFilterState extends AbstractFilterState {
|
|
55
|
+
type: "date" | "datetime";
|
|
56
|
+
scheme: DateFilterScheme;
|
|
57
|
+
}
|
|
58
|
+
export interface StartDateFilterState extends AbstractDateFilterState {
|
|
59
|
+
scheme: "startFrom";
|
|
60
|
+
startDate: Date | null;
|
|
61
|
+
}
|
|
62
|
+
export interface EndDateFilterState extends AbstractDateFilterState {
|
|
63
|
+
scheme: "endAt";
|
|
64
|
+
endDate: Date | null;
|
|
65
|
+
}
|
|
66
|
+
export interface BetweenDatesFilterState extends AbstractDateFilterState {
|
|
67
|
+
scheme: "between";
|
|
68
|
+
startDate: Date | null;
|
|
69
|
+
endDate: Date | null;
|
|
70
|
+
}
|
|
71
|
+
export type DateFilterState = StartDateFilterState | EndDateFilterState | BetweenDatesFilterState;
|
|
72
|
+
export type FilterState = StringFilterState | NumberFilterState | DateFilterState;
|
|
73
|
+
export interface FilterModel {
|
|
74
|
+
tableFilterState: EditableTableFilterState;
|
|
75
|
+
setTableFilterState: (state: EditableTableFilterState) => void;
|
|
76
|
+
}
|
package/types.js
CHANGED
|
@@ -1 +1,30 @@
|
|
|
1
|
-
export
|
|
1
|
+
export var stringFilterSchemes = [
|
|
2
|
+
"contains",
|
|
3
|
+
"startsWith",
|
|
4
|
+
"endsWith",
|
|
5
|
+
];
|
|
6
|
+
export var stringFilterSchemeNames = {
|
|
7
|
+
contains: "Contains",
|
|
8
|
+
startsWith: "Starts With",
|
|
9
|
+
endsWith: "Ends With",
|
|
10
|
+
};
|
|
11
|
+
export var numberFilterSchemes = [
|
|
12
|
+
"equals",
|
|
13
|
+
"greaterThan",
|
|
14
|
+
"lessThan",
|
|
15
|
+
"greaterOrEqual",
|
|
16
|
+
"lessOrEqual",
|
|
17
|
+
];
|
|
18
|
+
export var numberFilterSchemeNames = {
|
|
19
|
+
equals: "=",
|
|
20
|
+
greaterThan: ">",
|
|
21
|
+
lessThan: "<",
|
|
22
|
+
greaterOrEqual: ">=",
|
|
23
|
+
lessOrEqual: "<=",
|
|
24
|
+
};
|
|
25
|
+
export var dateFilterSchemes = ["startFrom", "endAt", "between"];
|
|
26
|
+
export var dateFilterSchemeNames = {
|
|
27
|
+
startFrom: "Start Form",
|
|
28
|
+
endAt: "End At",
|
|
29
|
+
between: "Between",
|
|
30
|
+
};
|
package/util/datetime.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import dayjs from "dayjs";
|
|
2
|
+
export var dateToInputStr = function (date) { return dayjs(date).format("YYYY-MM-DD"); };
|
|
3
|
+
export var dateToDatetimeInputStr = function (date) {
|
|
4
|
+
return dayjs(date).format("YYYY-MM-DDTHH:mm");
|
|
5
|
+
};
|
|
6
|
+
// All dates and datetimes that the grid displays are in UTC. The datetime
|
|
7
|
+
// string in the value attribute of an input element of type datetime-local
|
|
8
|
+
// is based on the local timezone of the client.
|
|
9
|
+
export var datetimeInputStrToUtc = function (datetimeStr) { return datetimeStr + "Z"; };
|