@teselagen/ui 0.0.9 → 0.0.12

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.
Files changed (126) hide show
  1. package/README.md +7 -0
  2. package/cypress.config.ts +6 -0
  3. package/index.html +12 -0
  4. package/package.json +2 -2
  5. package/project.json +74 -0
  6. package/src/AdvancedOptions.js +33 -0
  7. package/src/AdvancedOptions.spec.js +24 -0
  8. package/src/AssignDefaultsModeContext.js +21 -0
  9. package/src/AsyncValidateFieldSpinner/index.js +12 -0
  10. package/src/BlueprintError/index.js +14 -0
  11. package/src/BounceLoader/index.js +16 -0
  12. package/src/BounceLoader/style.css +45 -0
  13. package/src/CollapsibleCard/index.js +92 -0
  14. package/src/CollapsibleCard/style.css +21 -0
  15. package/src/DNALoader/index.js +20 -0
  16. package/src/DNALoader/style.css +251 -0
  17. package/src/DataTable/CellDragHandle.js +130 -0
  18. package/src/DataTable/DisabledLoadingComponent.js +15 -0
  19. package/src/DataTable/DisplayOptions.js +218 -0
  20. package/src/DataTable/FilterAndSortMenu.js +397 -0
  21. package/src/DataTable/PagingTool.js +232 -0
  22. package/src/DataTable/SearchBar.js +57 -0
  23. package/src/DataTable/SortableColumns.js +53 -0
  24. package/src/DataTable/TableFormTrackerContext.js +10 -0
  25. package/src/DataTable/dataTableEnhancer.js +291 -0
  26. package/src/DataTable/defaultFormatters.js +32 -0
  27. package/src/DataTable/defaultProps.js +45 -0
  28. package/src/DataTable/defaultValidators.js +40 -0
  29. package/src/DataTable/editCellHelper.js +44 -0
  30. package/src/DataTable/getCellVal.js +20 -0
  31. package/src/DataTable/getVals.js +8 -0
  32. package/src/DataTable/index.js +3537 -0
  33. package/src/DataTable/isTruthy.js +12 -0
  34. package/src/DataTable/isValueEmpty.js +3 -0
  35. package/src/DataTable/style.css +600 -0
  36. package/src/DataTable/utils/computePresets.js +42 -0
  37. package/src/DataTable/utils/convertSchema.js +69 -0
  38. package/src/DataTable/utils/getIdOrCodeOrIndex.js +9 -0
  39. package/src/DataTable/utils/getTableConfigFromStorage.js +5 -0
  40. package/src/DataTable/utils/queryParams.js +1032 -0
  41. package/src/DataTable/utils/rowClick.js +156 -0
  42. package/src/DataTable/utils/selection.js +8 -0
  43. package/src/DataTable/utils/withSelectedEntities.js +65 -0
  44. package/src/DataTable/utils/withTableParams.js +328 -0
  45. package/src/DataTable/validateTableWideErrors.js +135 -0
  46. package/src/DataTable/viewColumn.js +37 -0
  47. package/src/DialogFooter/index.js +79 -0
  48. package/src/DialogFooter/style.css +9 -0
  49. package/src/DropdownButton.js +36 -0
  50. package/src/FillWindow.css +6 -0
  51. package/src/FillWindow.js +69 -0
  52. package/src/FormComponents/Uploader.js +1197 -0
  53. package/src/FormComponents/getNewName.js +31 -0
  54. package/src/FormComponents/index.js +1384 -0
  55. package/src/FormComponents/itemUpload.js +84 -0
  56. package/src/FormComponents/sortify.js +73 -0
  57. package/src/FormComponents/style.css +247 -0
  58. package/src/FormComponents/tryToMatchSchemas.js +222 -0
  59. package/src/FormComponents/utils.js +6 -0
  60. package/src/HotkeysDialog/index.js +79 -0
  61. package/src/HotkeysDialog/style.css +54 -0
  62. package/src/InfoHelper/index.js +83 -0
  63. package/src/InfoHelper/style.css +7 -0
  64. package/src/IntentText/index.js +18 -0
  65. package/src/Loading/index.js +74 -0
  66. package/src/Loading/style.css +4 -0
  67. package/src/MatchHeaders.js +223 -0
  68. package/src/MenuBar/index.js +416 -0
  69. package/src/MenuBar/style.css +45 -0
  70. package/src/PromptUnsavedChanges/index.js +40 -0
  71. package/src/ResizableDraggableDialog/index.js +138 -0
  72. package/src/ResizableDraggableDialog/style.css +42 -0
  73. package/src/ScrollToTop/index.js +72 -0
  74. package/src/SimpleStepViz.js +26 -0
  75. package/src/TgSelect/index.js +465 -0
  76. package/src/TgSelect/style.css +34 -0
  77. package/src/TgSuggest/index.js +121 -0
  78. package/src/Timeline/TimelineEvent.js +31 -0
  79. package/src/Timeline/index.js +22 -0
  80. package/src/Timeline/style.css +29 -0
  81. package/src/UploadCsvWizard.css +4 -0
  82. package/src/UploadCsvWizard.js +731 -0
  83. package/src/autoTooltip.js +89 -0
  84. package/src/constants.js +1 -0
  85. package/src/customIcons.js +361 -0
  86. package/src/enhancers/withDialog/index.js +196 -0
  87. package/src/enhancers/withDialog/tg_modalState.js +46 -0
  88. package/src/enhancers/withField.js +20 -0
  89. package/src/enhancers/withFields.js +11 -0
  90. package/src/enhancers/withLocalStorage.js +11 -0
  91. package/src/index.js +76 -0
  92. package/src/rerenderOnWindowResize.js +27 -0
  93. package/src/showAppSpinner.js +12 -0
  94. package/src/showConfirmationDialog/index.js +116 -0
  95. package/src/showDialogOnDocBody.js +37 -0
  96. package/src/style.css +214 -0
  97. package/src/toastr.js +92 -0
  98. package/src/typeToCommonType.js +6 -0
  99. package/src/useDialog.js +64 -0
  100. package/src/utils/S3Download.js +14 -0
  101. package/src/utils/adHoc.js +10 -0
  102. package/src/utils/basicHandleActionsWithFullState.js +14 -0
  103. package/src/utils/combineReducersWithFullState.js +14 -0
  104. package/src/utils/commandControls.js +83 -0
  105. package/src/utils/commandUtils.js +112 -0
  106. package/src/utils/determineBlackOrWhiteTextColor.js +4 -0
  107. package/src/utils/getDayjsFormatter.js +35 -0
  108. package/src/utils/getTextFromEl.js +28 -0
  109. package/src/utils/handlerHelpers.js +30 -0
  110. package/src/utils/hotkeyUtils.js +129 -0
  111. package/src/utils/menuUtils.js +402 -0
  112. package/src/utils/popoverOverflowModifiers.js +11 -0
  113. package/src/utils/pureNoFunc.js +31 -0
  114. package/src/utils/renderOnDoc.js +29 -0
  115. package/src/utils/showProgressToast.js +22 -0
  116. package/src/utils/tagUtils.js +45 -0
  117. package/src/utils/tgFormValues.js +32 -0
  118. package/src/utils/withSelectTableRecords.js +38 -0
  119. package/src/utils/withStore.js +10 -0
  120. package/src/wrapDialog.js +112 -0
  121. package/tsconfig.json +4 -0
  122. package/vite.config.ts +7 -0
  123. package/index.js +0 -80652
  124. package/index.mjs +0 -80636
  125. package/index.umd.js +0 -80649
  126. package/style.css +0 -10421
@@ -0,0 +1,397 @@
1
+ import { DateInput, DateRangeInput } from "@blueprintjs/datetime";
2
+ import { camelCase } from "lodash";
3
+ import classNames from "classnames";
4
+ import React from "react";
5
+ import {
6
+ Menu,
7
+ Intent,
8
+ MenuDivider,
9
+ InputGroup,
10
+ Classes,
11
+ NumericInput,
12
+ MenuItem
13
+ } from "@blueprintjs/core";
14
+ import dayjs from "dayjs";
15
+
16
+ import getDayjsFormatter from "../utils/getDayjsFormatter";
17
+ import { onEnterHelper } from "../utils/handlerHelpers";
18
+ import DialogFooter from "../DialogFooter";
19
+ import TgSelect from "../TgSelect";
20
+ import "@teselagen/react-table/react-table.css";
21
+ import "./style.css";
22
+ import "../toastr";
23
+
24
+ const isInvalidFilterValue = value => {
25
+ if (Array.isArray(value) && value.length) {
26
+ return value.some(item => isInvalidFilterValue(item));
27
+ }
28
+ return value === "" || value === undefined || value.length === 0;
29
+ };
30
+
31
+ export default class FilterAndSortMenu extends React.Component {
32
+ constructor(props) {
33
+ super(props);
34
+ const selectedFilter = camelCase(getFilterMenuItems(props.dataType)[0]);
35
+ this.state = {
36
+ selectedFilter,
37
+ filterValue: "",
38
+ ...this.props.currentFilter
39
+ };
40
+ }
41
+ handleFilterChange = selectedFilter => {
42
+ this.setState({ selectedFilter: camelCase(selectedFilter) });
43
+ };
44
+ handleFilterValueChange = filterValue => {
45
+ this.setState({ filterValue });
46
+ };
47
+ handleFilterSubmit = () => {
48
+ const { filterValue, selectedFilter } = this.state;
49
+ const { togglePopover, dataType } = this.props;
50
+ const ccSelectedFilter = camelCase(selectedFilter);
51
+ let filterValToUse = filterValue;
52
+ if (ccSelectedFilter === "true" || ccSelectedFilter === "false") {
53
+ //manually set the filterValue because none is set when type=boolean
54
+ filterValToUse = ccSelectedFilter;
55
+ } else if (ccSelectedFilter === "notEmpty") {
56
+ // manually set filter value (nothing is selected by user)
57
+ filterValToUse = true;
58
+ } else if (ccSelectedFilter === "isEmpty") {
59
+ // manually set filter value (nothing is selected by user)
60
+ filterValToUse = false;
61
+ } else if (
62
+ ccSelectedFilter === "inList" ||
63
+ ccSelectedFilter === "notInList"
64
+ ) {
65
+ if (dataType === "number") {
66
+ filterValToUse =
67
+ filterValue &&
68
+ filterValue.map(val => parseFloat(val.replaceAll(",", "")));
69
+ }
70
+ }
71
+
72
+ const { filterOn, addFilters, removeSingleFilter } = this.props;
73
+ if (isInvalidFilterValue(filterValToUse)) {
74
+ togglePopover();
75
+ return removeSingleFilter(filterOn);
76
+ }
77
+ addFilters([
78
+ {
79
+ filterOn,
80
+ selectedFilter: ccSelectedFilter,
81
+ filterValue: filterValToUse
82
+ }
83
+ ]);
84
+ togglePopover();
85
+ };
86
+ // handleSubmit(event) {
87
+ // alert('A name was submitted: ' + this.state.value);
88
+ // event.preventDefault();
89
+ // }
90
+
91
+ render() {
92
+ const { selectedFilter, filterValue } = this.state;
93
+ const { dataType, currentFilter, removeSingleFilter } = this.props;
94
+ const {
95
+ handleFilterChange,
96
+ handleFilterValueChange,
97
+ handleFilterSubmit
98
+ } = this;
99
+ const filterTypesDictionary = {
100
+ none: "",
101
+ startsWith: "text",
102
+ endsWith: "text",
103
+ contains: "text",
104
+ notContains: "text",
105
+ isExactly: "text",
106
+ isEmpty: "text",
107
+ notEmpty: "text",
108
+ inList: "list",
109
+ notInList: "list",
110
+ true: "boolean",
111
+ false: "boolean",
112
+ dateIs: "date",
113
+ notBetween: "dateRange",
114
+ isBetween: "dateRange",
115
+ isBefore: "date",
116
+ isAfter: "date",
117
+ greaterThan: "number",
118
+ lessThan: "number",
119
+ inRange: "numberRange",
120
+ outsideRange: "numberRange",
121
+ equalTo: "number",
122
+ regex: "text"
123
+ };
124
+ const filterMenuItems = getFilterMenuItems(dataType);
125
+ const ccSelectedFilter = camelCase(selectedFilter);
126
+ const requiresValue = ccSelectedFilter && ccSelectedFilter !== "none";
127
+
128
+ return (
129
+ <Menu className="data-table-header-menu">
130
+ <div className="custom-menu-item">
131
+ <div className={classNames(Classes.SELECT, Classes.FILL)}>
132
+ <select
133
+ onChange={function(e) {
134
+ const ccSelectedFilter = camelCase(e.target.value);
135
+ handleFilterChange(ccSelectedFilter);
136
+ }}
137
+ value={ccSelectedFilter}
138
+ >
139
+ {filterMenuItems.map(function(menuItem, index) {
140
+ return (
141
+ <option key={index} value={camelCase(menuItem)}>
142
+ {menuItem}
143
+ </option>
144
+ );
145
+ })}
146
+ </select>
147
+ </div>
148
+ </div>
149
+ <div className="custom-menu-item">
150
+ <FilterInput
151
+ dataType={dataType}
152
+ requiresValue={requiresValue}
153
+ handleFilterSubmit={handleFilterSubmit}
154
+ filterValue={filterValue}
155
+ handleFilterValueChange={handleFilterValueChange}
156
+ filterSubType={camelCase(selectedFilter)}
157
+ filterType={filterTypesDictionary[camelCase(selectedFilter)]}
158
+ />
159
+ </div>
160
+ <MenuDivider />
161
+ <DialogFooter
162
+ secondaryClassName={Classes.POPOVER_DISMISS}
163
+ onClick={() => {
164
+ handleFilterSubmit();
165
+ }}
166
+ intent={Intent.SUCCESS}
167
+ text="Filter"
168
+ secondaryText="Clear"
169
+ secondaryAction={() => {
170
+ currentFilter && removeSingleFilter(currentFilter.filterOn);
171
+ }}
172
+ />
173
+ </Menu>
174
+ );
175
+ }
176
+ }
177
+
178
+ const dateMinMaxHelpers = {
179
+ minDate: dayjs()
180
+ .subtract(25, "years")
181
+ .toDate(),
182
+ maxDate: dayjs()
183
+ .add(25, "years")
184
+ .toDate()
185
+ };
186
+ const renderCreateNewOption = (query, active, handleClick) => (
187
+ <MenuItem
188
+ icon="add"
189
+ text={query}
190
+ active={active}
191
+ onClick={handleClick}
192
+ shouldDismissPopover={false}
193
+ />
194
+ );
195
+ class FilterInput extends React.Component {
196
+ render() {
197
+ const {
198
+ handleFilterValueChange,
199
+ handleFilterSubmit,
200
+ filterValue,
201
+ filterSubType,
202
+ filterType
203
+ } = this.props;
204
+ //Options: Text, Single number (before, after, equals), 2 numbers (range),
205
+ //Single Date (before, after, on), 2 dates (range)
206
+ let inputGroup = <div />;
207
+ switch (filterType) {
208
+ case "text":
209
+ inputGroup =
210
+ filterSubType === "notEmpty" || filterSubType === "isEmpty" ? (
211
+ <div />
212
+ ) : (
213
+ <div className="custom-menu-item">
214
+ <InputGroup
215
+ placeholder="Value"
216
+ onChange={function(e) {
217
+ handleFilterValueChange(e.target.value);
218
+ }}
219
+ autoFocus
220
+ {...onEnterHelper(handleFilterSubmit)}
221
+ value={filterValue}
222
+ />
223
+ </div>
224
+ );
225
+ break;
226
+ case "list":
227
+ inputGroup = (
228
+ <div className="custom-menu-item">
229
+ <TgSelect
230
+ placeholder="Add item"
231
+ renderCreateNewOption={renderCreateNewOption}
232
+ noResults={null}
233
+ multi={true}
234
+ creatable={true}
235
+ value={(filterValue || []).map(val => ({
236
+ label: val,
237
+ value: val
238
+ }))}
239
+ onChange={selectedOptions => {
240
+ selectedOptions.some(opt => opt.value === "")
241
+ ? handleFilterSubmit()
242
+ : handleFilterValueChange(
243
+ selectedOptions.map(opt => opt.value)
244
+ );
245
+ }}
246
+ options={[]}
247
+ />
248
+ </div>
249
+ );
250
+ break;
251
+ case "number":
252
+ inputGroup = (
253
+ <div className="custom-menu-item">
254
+ <NumericInput
255
+ placeholder="Value"
256
+ onValueChange={function(numVal) {
257
+ handleFilterValueChange(isNaN(numVal) ? 0 : numVal);
258
+ }}
259
+ autoFocus
260
+ {...onEnterHelper(handleFilterSubmit)}
261
+ value={filterValue}
262
+ />
263
+ </div>
264
+ );
265
+ break;
266
+ case "numberRange":
267
+ inputGroup = (
268
+ <div className="custom-menu-item">
269
+ <NumericInput
270
+ placeholder="Low"
271
+ onValueChange={function(numVal) {
272
+ handleFilterValueChange([
273
+ isNaN(numVal) ? 0 : numVal,
274
+ filterValue[1]
275
+ ]);
276
+ }}
277
+ {...onEnterHelper(handleFilterSubmit)}
278
+ value={filterValue && filterValue[0]}
279
+ />
280
+ <NumericInput
281
+ placeholder="High"
282
+ onValueChange={function(numVal) {
283
+ handleFilterValueChange([
284
+ filterValue[0],
285
+ isNaN(numVal) ? 0 : numVal
286
+ ]);
287
+ }}
288
+ {...onEnterHelper(handleFilterSubmit)}
289
+ value={filterValue && filterValue[1]}
290
+ />
291
+ </div>
292
+ );
293
+ break;
294
+ case "date":
295
+ inputGroup = (
296
+ <div className="custom-menu-item">
297
+ <DateInput
298
+ value={filterValue ? dayjs(filterValue).toDate() : undefined}
299
+ {...getDayjsFormatter("L")}
300
+ {...dateMinMaxHelpers}
301
+ onChange={selectedDates => {
302
+ handleFilterValueChange(selectedDates);
303
+ }}
304
+ />
305
+ </div>
306
+ );
307
+ break;
308
+ case "dateRange":
309
+ // eslint-disable-next-line no-case-declarations
310
+ let filterValueToUse;
311
+ if (Array.isArray(filterValue)) {
312
+ filterValueToUse = filterValue;
313
+ } else {
314
+ filterValueToUse =
315
+ filterValue && filterValue.split && filterValue.split(".");
316
+ }
317
+ inputGroup = (
318
+ <div className="custom-menu-item">
319
+ <DateRangeInput
320
+ value={
321
+ filterValueToUse && filterValueToUse[0] && filterValueToUse[1]
322
+ ? [
323
+ new Date(filterValueToUse[0]),
324
+ new Date(filterValueToUse[1])
325
+ ]
326
+ : undefined
327
+ }
328
+ popoverProps={{
329
+ captureDismiss: true
330
+ }}
331
+ {...{
332
+ formatDate: date =>
333
+ date == null ? "" : date.toLocaleDateString(),
334
+ parseDate: str => new Date(Date.parse(str)),
335
+ placeholder: "JS Date"
336
+ }}
337
+ {...dateMinMaxHelpers}
338
+ onChange={selectedDates => {
339
+ if (selectedDates[0] && selectedDates[1]) {
340
+ handleFilterValueChange(selectedDates);
341
+ }
342
+ }}
343
+ />
344
+ </div>
345
+ );
346
+ break;
347
+ default:
348
+ // to do
349
+ }
350
+ return inputGroup;
351
+ }
352
+ }
353
+
354
+ function getFilterMenuItems(dataType) {
355
+ let filterMenuItems = [];
356
+ if (dataType === "string") {
357
+ filterMenuItems = [
358
+ "Contains",
359
+ "Not Contains",
360
+ "Starts With",
361
+ "Ends With",
362
+ "Is Exactly",
363
+ "Regex",
364
+ "In List",
365
+ "Not In List",
366
+ "Is Empty",
367
+ "Not Empty"
368
+ ];
369
+ } else if (dataType === "lookup") {
370
+ filterMenuItems = [
371
+ "Contains",
372
+ "Not Contains",
373
+ "Starts With",
374
+ "Ends With",
375
+ "Is Exactly",
376
+ "Regex"
377
+ ];
378
+ } else if (dataType === "boolean") {
379
+ filterMenuItems = ["True", "False"];
380
+ } else if (dataType === "number" || dataType === "integer") {
381
+ // else if (dataType === "lookup") {
382
+ // filterMenuItems = ["None"];
383
+ // }
384
+ filterMenuItems = [
385
+ "Greater Than",
386
+ "Less Than",
387
+ "In Range",
388
+ "Outside Range",
389
+ "Equal To",
390
+ "In List",
391
+ "Not In List"
392
+ ];
393
+ } else if (dataType === "timestamp") {
394
+ filterMenuItems = ["Is Between", "Not Between", "Is Before", "Is After"];
395
+ }
396
+ return filterMenuItems;
397
+ }
@@ -0,0 +1,232 @@
1
+ import React, { useState, useEffect, useRef } from "react";
2
+ import { withProps, withHandlers, compose } from "recompose";
3
+ import classNames from "classnames";
4
+ import { noop, get, toInteger } from "lodash";
5
+ import { Button, Classes } from "@blueprintjs/core";
6
+ import { onEnterOrBlurHelper } from "../utils/handlerHelpers";
7
+ import { defaultPageSizes } from "./utils/queryParams";
8
+ import getIdOrCodeOrIndex from "./utils/getIdOrCodeOrIndex";
9
+
10
+ function PagingInput({ disabled, onBlur, defaultPage }) {
11
+ const [page, setPage] = useState(defaultPage);
12
+ const defaultValue = useRef(defaultPage);
13
+
14
+ useEffect(() => {
15
+ if (page !== defaultPage && defaultValue.current !== defaultPage) {
16
+ setPage(defaultPage);
17
+ }
18
+ defaultValue.current = defaultPage;
19
+ }, [page, defaultPage]);
20
+
21
+ return (
22
+ <input
23
+ style={{
24
+ marginLeft: 5,
25
+ width: page > 999 ? 65 : page > 99 ? 45 : 35,
26
+ marginRight: 8
27
+ }}
28
+ value={page}
29
+ disabled={disabled}
30
+ onChange={e => {
31
+ setPage(e.target.value);
32
+ }}
33
+ {...onEnterOrBlurHelper(onBlur)}
34
+ className={Classes.INPUT}
35
+ />
36
+ );
37
+ }
38
+
39
+ // Define the functional component and its props
40
+ const PagingTool = ({
41
+ onPageChange = noop,
42
+ onRefresh,
43
+ setPage,
44
+ setPageSize,
45
+ persistPageSize = noop,
46
+ paging: { pageSize, page, total },
47
+ hideTotalPages,
48
+ disabled,
49
+ controlled_hasNextPage,
50
+ disableSetPageSize,
51
+ hideSetPageSize
52
+ }) => {
53
+ // Define local state
54
+ const [refetching, setRefetching] = useState(false);
55
+
56
+ // Initialize additional page sizes
57
+ useEffect(() => {
58
+ const additionalPageSize =
59
+ window.frontEndConfig && window.frontEndConfig.additionalPageSize
60
+ ? [toInteger(window.frontEndConfig.additionalPageSize)]
61
+ : [];
62
+ window.tgPageSizes = [...defaultPageSizes, ...additionalPageSize];
63
+ }, []);
64
+
65
+ // Define event handlers for the component
66
+ const onRefreshHandler = async () => {
67
+ setRefetching(true);
68
+ await onRefresh();
69
+ setRefetching(false);
70
+ };
71
+
72
+ const setPageHandler = page => {
73
+ setPage(page);
74
+ onPageChange(page);
75
+ };
76
+
77
+ const setPageSizeHandler = e => {
78
+ const newPageSize = parseInt(e.target.value, 10);
79
+ setPageSize(newPageSize);
80
+ persistPageSize(newPageSize);
81
+ };
82
+
83
+ const pageBackHandler = () => {
84
+ setPageHandler(parseInt(page, 10) - 1);
85
+ };
86
+
87
+ const pageForwardHandler = () => {
88
+ setPageHandler(parseInt(page, 10) + 1);
89
+ };
90
+
91
+ const pageInputBlurHandler = e => {
92
+ const lastPage = Math.ceil(total / pageSize);
93
+ const pageValue = parseInt(e.target.value, 10);
94
+ const selectedPage =
95
+ pageValue > lastPage
96
+ ? lastPage
97
+ : pageValue < 1 || isNaN(pageValue)
98
+ ? 1
99
+ : pageValue;
100
+ setPageHandler(selectedPage);
101
+ };
102
+
103
+ // Define rendering logic
104
+ const pageStart = (page - 1) * pageSize + 1;
105
+ if (pageStart < 0) throw new Error("We should never have page be <0");
106
+ const backEnabled = page - 1 > 0;
107
+ const forwardEnabled = page * pageSize < total;
108
+ const lastPage = Math.ceil(total / pageSize);
109
+ const options = [...(window.tgPageSizes || defaultPageSizes)];
110
+ if (!options.includes(pageSize)) {
111
+ options.push(pageSize);
112
+ }
113
+
114
+ return (
115
+ <div className="paging-toolbar-container">
116
+ {onRefresh && (
117
+ <Button
118
+ minimal
119
+ loading={refetching}
120
+ icon="refresh"
121
+ disabled={disabled}
122
+ onClick={onRefreshHandler}
123
+ />
124
+ )}
125
+ {!hideSetPageSize && (
126
+ <div
127
+ title="Set Page Size"
128
+ className={classNames(Classes.SELECT, Classes.MINIMAL)}
129
+ >
130
+ <select
131
+ className="paging-page-size"
132
+ onChange={setPageSizeHandler}
133
+ disabled={disabled || disableSetPageSize}
134
+ value={pageSize}
135
+ >
136
+ {[
137
+ <option key="page-size-placeholder" disabled value="fake">
138
+ Size
139
+ </option>,
140
+ ...options.map(size => {
141
+ return (
142
+ <option key={size} value={size}>
143
+ {size}
144
+ </option>
145
+ );
146
+ })
147
+ ]}
148
+ </select>
149
+ </div>
150
+ )}
151
+ <Button
152
+ onClick={pageBackHandler}
153
+ disabled={!backEnabled || disabled}
154
+ minimal
155
+ className="paging-arrow-left"
156
+ icon="chevron-left"
157
+ />
158
+ <div>
159
+ {hideTotalPages ? (
160
+ page
161
+ ) : total ? (
162
+ <div>
163
+ <PagingInput
164
+ disabled={disabled}
165
+ onBlur={pageInputBlurHandler}
166
+ defaultPage={page}
167
+ lastPage={lastPage}
168
+ />
169
+ of {lastPage}
170
+ </div>
171
+ ) : (
172
+ "No Rows"
173
+ )}
174
+ </div>
175
+ <Button
176
+ style={{ marginLeft: 5 }}
177
+ disabled={
178
+ (controlled_hasNextPage === undefined
179
+ ? !forwardEnabled
180
+ : !controlled_hasNextPage) || disabled
181
+ }
182
+ icon="chevron-right"
183
+ minimal
184
+ className="paging-arrow-right"
185
+ onClick={pageForwardHandler}
186
+ />
187
+ </div>
188
+ );
189
+ };
190
+
191
+ export default compose(
192
+ withProps(props => {
193
+ const {
194
+ entityCount,
195
+ page,
196
+ pageSize,
197
+ disabled,
198
+ pagingDisabled,
199
+ onRefresh,
200
+ controlled_setPage,
201
+ controlled_setPageSize,
202
+ controlled_page,
203
+
204
+ controlled_total,
205
+ controlled_onRefresh,
206
+ setPage,
207
+ setPageSize
208
+ } = props;
209
+ return {
210
+ paging: {
211
+ total: controlled_total || entityCount,
212
+ page: controlled_page || page,
213
+ pageSize
214
+ },
215
+ disabled: disabled || pagingDisabled,
216
+ onRefresh: controlled_onRefresh || onRefresh,
217
+ setPage: controlled_setPage || setPage,
218
+ setPageSize: controlled_setPageSize || setPageSize
219
+ };
220
+ }),
221
+ withHandlers({
222
+ onPageChange: ({ entities, keepSelectionOnPageChange, change }) => () => {
223
+ const record = get(entities, "[0]");
224
+ if (
225
+ !keepSelectionOnPageChange &&
226
+ (!record || !getIdOrCodeOrIndex(record))
227
+ ) {
228
+ change("reduxFormSelectedEntityIdMap", {});
229
+ }
230
+ }
231
+ })
232
+ )(PagingTool);
@@ -0,0 +1,57 @@
1
+ import React from "react";
2
+ import { Button, Classes, Spinner } from "@blueprintjs/core";
3
+ import classNames from "classnames";
4
+ import { onEnterHelper } from "../utils/handlerHelpers";
5
+ import { InputField } from "../FormComponents";
6
+
7
+ const SearchBar = ({
8
+ reduxFormSearchInput,
9
+ setSearchTerm,
10
+ loading,
11
+ searchMenuButton,
12
+ disabled,
13
+ autoFocusSearch
14
+ }) => {
15
+ let rightElement;
16
+ // need to always render searchMenuButton so it doesn't close
17
+ if (searchMenuButton) {
18
+ rightElement = (
19
+ <div style={{ display: "flex" }}>
20
+ {loading && <Spinner size="18" />}
21
+ {searchMenuButton}
22
+ </div>
23
+ );
24
+ } else {
25
+ rightElement = loading ? (
26
+ <Spinner size="18" />
27
+ ) : (
28
+ <Button
29
+ minimal
30
+ icon="search"
31
+ onClick={() => {
32
+ setSearchTerm(reduxFormSearchInput);
33
+ }}
34
+ />
35
+ );
36
+ }
37
+ return (
38
+ <InputField
39
+ autoFocus={autoFocusSearch}
40
+ disabled={disabled}
41
+ loading={loading}
42
+ type="search"
43
+ name="reduxFormSearchInput"
44
+ className={classNames("datatable-search-input", Classes.ROUND)}
45
+ placeholder="Search..."
46
+ {...onEnterHelper(e => {
47
+ e.preventDefault();
48
+ e.stopPropagation();
49
+ setSearchTerm(reduxFormSearchInput);
50
+ e.nativeEvent.stopImmediatePropagation();
51
+ })}
52
+ rightElement={rightElement}
53
+ />
54
+ );
55
+ };
56
+
57
+ export default SearchBar;