@mwater/visualization 5.4.2 → 5.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/dashboards/DashboardComponent.d.ts +6 -20
- package/lib/dashboards/DashboardComponent.js +86 -78
- package/lib/languages.js +6 -1
- package/lib/quickfilter/Quickfilter.d.ts +2 -0
- package/lib/quickfilter/QuickfiltersDesignComponent.d.ts +4 -29
- package/lib/quickfilter/QuickfiltersDesignComponent.js +38 -29
- package/package.json +1 -1
- package/src/dashboards/DashboardComponent.tsx +158 -159
- package/src/languages.ts +6 -1
- package/src/quickfilter/Quickfilter.ts +3 -0
- package/src/quickfilter/QuickfiltersDesignComponent.tsx +73 -56
|
@@ -2,7 +2,6 @@ import React, { ReactNode } from "react";
|
|
|
2
2
|
import { DataSource, Schema } from "@mwater/expressions";
|
|
3
3
|
import UndoStack from "../UndoStack";
|
|
4
4
|
import { DashboardViewComponentHandle } from "./DashboardViewComponent";
|
|
5
|
-
import QuickfiltersComponent from "../quickfilter/QuickfiltersComponent";
|
|
6
5
|
import SettingsModalComponent from "./SettingsModalComponent";
|
|
7
6
|
import { DashboardDesign } from "./DashboardDesign";
|
|
8
7
|
import DashboardDataSource from "./DashboardDataSource";
|
|
@@ -78,25 +77,12 @@ export default class DashboardComponent extends React.Component<DashboardCompone
|
|
|
78
77
|
getCompiledFilters(): JsonQLFilter[];
|
|
79
78
|
/** Translate function to use for display. Do not use when editing. */
|
|
80
79
|
translate: (input: string) => string;
|
|
81
|
-
renderEditingSwitch(): React.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
type: string;
|
|
88
|
-
key: string;
|
|
89
|
-
className: string;
|
|
90
|
-
onClick: () => void;
|
|
91
|
-
}, HTMLElement>;
|
|
92
|
-
renderActionLinks(): React.DetailedReactHTMLElement<React.HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
93
|
-
renderTitleBar(): React.DetailedReactHTMLElement<{
|
|
94
|
-
style: {
|
|
95
|
-
height: number;
|
|
96
|
-
padding: number;
|
|
97
|
-
};
|
|
98
|
-
}, HTMLElement>;
|
|
99
|
-
renderQuickfilter(): React.CElement<import("../quickfilter/QuickfiltersComponent").QuickfiltersComponentProps, QuickfiltersComponent>;
|
|
80
|
+
renderEditingSwitch(): React.JSX.Element;
|
|
81
|
+
renderStyle(): React.JSX.Element;
|
|
82
|
+
renderActionLinks(): React.JSX.Element;
|
|
83
|
+
renderTitleBar(): React.JSX.Element;
|
|
84
|
+
renderQuickfilter(): React.JSX.Element;
|
|
85
|
+
renderFloatingShowQuickfiltersButton(): React.JSX.Element | null;
|
|
100
86
|
refDashboardView: (el: any) => void;
|
|
101
87
|
render(): React.JSX.Element;
|
|
102
88
|
}
|
|
@@ -28,7 +28,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
29
|
const lodash_1 = __importDefault(require("lodash"));
|
|
30
30
|
const react_1 = __importDefault(require("react"));
|
|
31
|
-
const R = react_1.default.createElement;
|
|
32
31
|
const UndoStack_1 = __importDefault(require("../UndoStack"));
|
|
33
32
|
const DashboardUtils = __importStar(require("./DashboardUtils"));
|
|
34
33
|
const DashboardViewComponent_1 = __importDefault(require("./DashboardViewComponent"));
|
|
@@ -162,66 +161,98 @@ class DashboardComponent extends react_1.default.Component {
|
|
|
162
161
|
return this.props.design.translations?.[displayLocale]?.[input] ?? input;
|
|
163
162
|
};
|
|
164
163
|
renderEditingSwitch() {
|
|
165
|
-
return
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}, R("span", { className: "fas fa-pencil-alt" }), " ", this.state.editing ? T `Editing` : T `Edit`);
|
|
164
|
+
return (react_1.default.createElement("a", { key: "edit", className: `btn btn-primary btn-sm ${this.state.editing ? "active" : ""}`, onClick: this.handleToggleEditing },
|
|
165
|
+
react_1.default.createElement("span", { className: "fas fa-pencil-alt" }),
|
|
166
|
+
" ",
|
|
167
|
+
this.state.editing ? T `Editing` : T `Edit`));
|
|
170
168
|
}
|
|
171
169
|
renderStyle() {
|
|
172
|
-
return
|
|
170
|
+
return (react_1.default.createElement("button", { type: "button", key: "style", className: "btn btn-link btn-sm", onClick: this.handleOpenLayoutOptions },
|
|
171
|
+
react_1.default.createElement("span", { className: "fa fa-mobile" }),
|
|
172
|
+
react_1.default.createElement("span", { className: "hide-600px" },
|
|
173
|
+
" ",
|
|
174
|
+
T `Layout`)));
|
|
173
175
|
}
|
|
174
176
|
renderActionLinks() {
|
|
175
|
-
return
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
key: "undo",
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
key: "redo",
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
"
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
177
|
+
return (react_1.default.createElement("div", null,
|
|
178
|
+
this.state.editing
|
|
179
|
+
? [
|
|
180
|
+
react_1.default.createElement("a", { key: "undo", className: `btn btn-link btn-sm ${!this.state.undoStack.canUndo() ? "disabled" : ""}`, onClick: this.handleUndo },
|
|
181
|
+
react_1.default.createElement("span", { className: "fas fa-caret-left" }),
|
|
182
|
+
react_1.default.createElement("span", { className: "hide-600px" },
|
|
183
|
+
" ",
|
|
184
|
+
T `Undo`)),
|
|
185
|
+
" ",
|
|
186
|
+
react_1.default.createElement("a", { key: "redo", className: `btn btn-link btn-sm ${!this.state.undoStack.canRedo() ? "disabled" : ""}`, onClick: this.handleRedo },
|
|
187
|
+
react_1.default.createElement("span", { className: "fas fa-caret-right" }),
|
|
188
|
+
react_1.default.createElement("span", { className: "hide-600px" },
|
|
189
|
+
" ",
|
|
190
|
+
T `Redo`))
|
|
191
|
+
]
|
|
192
|
+
: undefined,
|
|
193
|
+
!this.state.editing && this.props.design.otherLocales && this.props.design.otherLocales.length > 0
|
|
194
|
+
? react_1.default.createElement("div", { key: "translations", className: "dropdown d-inline-block" },
|
|
195
|
+
react_1.default.createElement("a", { className: "btn btn-link btn-sm dropdown-toggle", "data-bs-toggle": "dropdown" },
|
|
196
|
+
react_1.default.createElement("span", { className: "fal fa-globe" }),
|
|
197
|
+
" ",
|
|
198
|
+
this.state.locale),
|
|
199
|
+
react_1.default.createElement("ul", { className: "dropdown-menu dropdown-menu-end" }, [this.props.design.locale || "en", ...this.props.design.otherLocales].map(locale => react_1.default.createElement("li", { key: locale },
|
|
200
|
+
react_1.default.createElement("a", { className: "dropdown-item", onClick: () => this.setState({ locale: locale }) }, __1.languages.find(l => l.code === locale)?.name || locale)))))
|
|
201
|
+
: undefined,
|
|
202
|
+
react_1.default.createElement("a", { key: "print", className: "btn btn-link btn-sm", onClick: this.handlePrint },
|
|
203
|
+
react_1.default.createElement("span", { className: "fas fa-print" }),
|
|
204
|
+
react_1.default.createElement("span", { className: "hide-600px" },
|
|
205
|
+
" ",
|
|
206
|
+
T `Print`)),
|
|
207
|
+
react_1.default.createElement("a", { key: "refresh", className: "btn btn-link btn-sm", onClick: this.handleRefreshData },
|
|
208
|
+
react_1.default.createElement("span", { className: "fas fa-sync" }),
|
|
209
|
+
react_1.default.createElement("span", { className: "hide-600px" },
|
|
210
|
+
" ",
|
|
211
|
+
T `Refresh`)),
|
|
212
|
+
this.state.hideQuickfilters && this.props.design.quickfilters && this.props.design.quickfilters.length > 0
|
|
213
|
+
? react_1.default.createElement("a", { key: "showQuickfilters", className: "btn btn-link btn-sm", onClick: this.handleShowQuickfilters },
|
|
214
|
+
react_1.default.createElement("span", { className: "fa fa-filter" }),
|
|
215
|
+
react_1.default.createElement("span", { className: "hide-600px" },
|
|
216
|
+
" ",
|
|
217
|
+
T `Show Quickfilters`))
|
|
218
|
+
: undefined,
|
|
219
|
+
this.state.editing
|
|
220
|
+
? react_1.default.createElement("a", { key: "settings", className: "btn btn-link btn-sm", onClick: this.handleSettings },
|
|
221
|
+
react_1.default.createElement("span", { className: "fas fa-cog" }),
|
|
222
|
+
react_1.default.createElement("span", { className: "hide-600px" },
|
|
223
|
+
" ",
|
|
224
|
+
T `Settings`))
|
|
225
|
+
: undefined,
|
|
226
|
+
this.state.editing ? this.renderStyle() : undefined,
|
|
227
|
+
this.props.extraTitleButtonsElem,
|
|
228
|
+
this.props.onDesignChange != null ? this.renderEditingSwitch() : undefined));
|
|
206
229
|
}
|
|
207
230
|
renderTitleBar() {
|
|
208
|
-
return
|
|
231
|
+
return (react_1.default.createElement("div", { style: { height: 40, padding: 4 } },
|
|
232
|
+
react_1.default.createElement("div", { style: { float: "right" } }, this.renderActionLinks()),
|
|
233
|
+
this.props.titleElem));
|
|
209
234
|
}
|
|
210
235
|
renderQuickfilter() {
|
|
211
|
-
return
|
|
212
|
-
design: this.props.design.quickfilters || [],
|
|
213
|
-
schema: this.props.schema,
|
|
214
|
-
dataSource: this.props.dataSource,
|
|
215
|
-
quickfiltersDataSource: this.props.dashboardDataSource.getQuickfiltersDataSource(),
|
|
216
|
-
values: this.state.quickfiltersValues || undefined,
|
|
217
|
-
onValuesChange: (values) => this.setState({ quickfiltersValues: values }),
|
|
218
|
-
locks: this.props.quickfilterLocks,
|
|
219
|
-
filters: this.getCompiledFilters(),
|
|
220
|
-
hideTopBorder: this.props.hideTitleBar,
|
|
236
|
+
return react_1.default.createElement(QuickfiltersComponent_1.default, { design: this.props.design.quickfilters || [], schema: this.props.schema, dataSource: this.props.dataSource, quickfiltersDataSource: this.props.dashboardDataSource.getQuickfiltersDataSource(), values: this.state.quickfiltersValues || undefined, onValuesChange: (values) => this.setState({ quickfiltersValues: values }), locks: this.props.quickfilterLocks, filters: this.getCompiledFilters(), hideTopBorder: this.props.hideTitleBar,
|
|
221
237
|
// Don't hide if title bar is hidden as it can't be shown again
|
|
222
|
-
onHide: () =>
|
|
223
|
-
|
|
224
|
-
|
|
238
|
+
onHide: () => this.setState({ hideQuickfilters: true }), translate: this.translate });
|
|
239
|
+
}
|
|
240
|
+
renderFloatingShowQuickfiltersButton() {
|
|
241
|
+
// Only show if:
|
|
242
|
+
// 1. Quick filters exist
|
|
243
|
+
// 2. Quick filters are hidden
|
|
244
|
+
// 3. Title bar is hidden (since otherwise button is in title bar)
|
|
245
|
+
if (!this.props.design.quickfilters?.length || !this.state.hideQuickfilters || !this.props.hideTitleBar) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
return (react_1.default.createElement("div", { style: {
|
|
249
|
+
position: "absolute",
|
|
250
|
+
top: 5,
|
|
251
|
+
right: 20,
|
|
252
|
+
zIndex: 1000
|
|
253
|
+
} },
|
|
254
|
+
react_1.default.createElement("button", { className: "btn btn-link btn-sm", onClick: () => this.setState({ hideQuickfilters: false }) },
|
|
255
|
+
react_1.default.createElement("i", { className: "fa fa-angle-double-down" }))));
|
|
225
256
|
}
|
|
226
257
|
refDashboardView = (el) => {
|
|
227
258
|
this.dashboardView = el;
|
|
@@ -231,32 +262,8 @@ class DashboardComponent extends react_1.default.Component {
|
|
|
231
262
|
// Compile quickfilters
|
|
232
263
|
filters = filters.concat(new QuickfilterCompiler_1.default(this.props.schema).compile(this.props.design.quickfilters || [], this.state.quickfiltersValues, this.props.quickfilterLocks));
|
|
233
264
|
const displayLocale = this.state.editing ? this.props.design.locale || "en" : this.state.locale;
|
|
234
|
-
const dashboardView =
|
|
235
|
-
|
|
236
|
-
dataSource: this.props.dataSource,
|
|
237
|
-
dashboardDataSource: this.props.dashboardDataSource,
|
|
238
|
-
ref: this.refDashboardView,
|
|
239
|
-
design: this.props.design,
|
|
240
|
-
onDesignChange: this.state.editing ? this.props.onDesignChange : undefined,
|
|
241
|
-
filters,
|
|
242
|
-
onRowClick: this.props.onRowClick,
|
|
243
|
-
namedStrings: this.props.namedStrings,
|
|
244
|
-
hideScopes: this.state.hideQuickfilters,
|
|
245
|
-
refreshKey: this.state.refreshKey,
|
|
246
|
-
locale: displayLocale
|
|
247
|
-
});
|
|
248
|
-
const readonlyDashboardView = R(DashboardViewComponent_1.default, {
|
|
249
|
-
schema: this.props.schema,
|
|
250
|
-
dataSource: this.props.dataSource,
|
|
251
|
-
dashboardDataSource: this.props.dashboardDataSource,
|
|
252
|
-
ref: this.refDashboardView,
|
|
253
|
-
design: this.props.design,
|
|
254
|
-
filters,
|
|
255
|
-
onRowClick: this.props.onRowClick,
|
|
256
|
-
namedStrings: this.props.namedStrings,
|
|
257
|
-
hideScopes: this.state.hideQuickfilters,
|
|
258
|
-
locale: displayLocale
|
|
259
|
-
});
|
|
265
|
+
const dashboardView = react_1.default.createElement(DashboardViewComponent_1.default, { schema: this.props.schema, dataSource: this.props.dataSource, dashboardDataSource: this.props.dashboardDataSource, ref: this.refDashboardView, design: this.props.design, onDesignChange: this.state.editing ? this.props.onDesignChange : undefined, filters: filters, onRowClick: this.props.onRowClick, namedStrings: this.props.namedStrings, hideScopes: this.state.hideQuickfilters, refreshKey: this.state.refreshKey, locale: displayLocale });
|
|
266
|
+
const readonlyDashboardView = react_1.default.createElement(DashboardViewComponent_1.default, { schema: this.props.schema, dataSource: this.props.dataSource, dashboardDataSource: this.props.dashboardDataSource, ref: this.refDashboardView, design: this.props.design, filters: filters, onRowClick: this.props.onRowClick, namedStrings: this.props.namedStrings, hideScopes: this.state.hideQuickfilters, locale: displayLocale });
|
|
260
267
|
// Pass active tables down to table select components so they can present a shorter list
|
|
261
268
|
return react_1.default.createElement(expressions_ui_1.ActiveTablesContext.Provider, { value: DashboardUtils.getFilterableTables(this.props.design, this.props.schema) },
|
|
262
269
|
react_1.default.createElement(expressions_ui_1.LocaleContext.Provider, { value: displayLocale },
|
|
@@ -268,6 +275,7 @@ class DashboardComponent extends react_1.default.Component {
|
|
|
268
275
|
!this.props.hideTitleBar ? this.renderTitleBar() : undefined,
|
|
269
276
|
react_1.default.createElement("div", null, !this.state.hideQuickfilters ? this.renderQuickfilter() : undefined),
|
|
270
277
|
dashboardView,
|
|
278
|
+
this.renderFloatingShowQuickfiltersButton(),
|
|
271
279
|
this.props.onDesignChange != null && (react_1.default.createElement(SettingsModalComponent_1.default, { onDesignChange: this.handleDesignChange, schema: this.props.schema, dataSource: this.props.dataSource, ref: (c) => {
|
|
272
280
|
this.settings = c;
|
|
273
281
|
} })),
|
package/lib/languages.js
CHANGED
|
@@ -3,6 +3,8 @@ import { Expr } from "@mwater/expressions";
|
|
|
3
3
|
design is an array of quick filters (user-selectable filters).
|
|
4
4
|
*/
|
|
5
5
|
export interface Quickfilter {
|
|
6
|
+
/** Optional id for the quickfilter. If not present, a random id will be generated the first time the quickfilter is modified. */
|
|
7
|
+
id?: string;
|
|
6
8
|
/** filter expression (left hand side only. Usually enum, enumset, text, date, datetime, id[], text[]) */
|
|
7
9
|
expr: Expr;
|
|
8
10
|
/** optional label */
|
|
@@ -12,35 +12,10 @@ export interface QuickfiltersDesignComponentProps {
|
|
|
12
12
|
tables: string[];
|
|
13
13
|
}
|
|
14
14
|
export default class QuickfiltersDesignComponent extends React.Component<QuickfiltersDesignComponentProps> {
|
|
15
|
-
handleDesignChange: (design:
|
|
16
|
-
isMergeable(design:
|
|
17
|
-
renderQuickfilter: (item:
|
|
15
|
+
handleDesignChange: (design: Quickfilter[]) => void;
|
|
16
|
+
isMergeable(design: Quickfilter[], index: number): boolean;
|
|
17
|
+
renderQuickfilter: (item: Quickfilter, index: number) => React.JSX.Element;
|
|
18
18
|
handleAdd: () => void;
|
|
19
|
-
handleRemove: (index:
|
|
20
|
-
render(): React.DetailedReactHTMLElement<React.HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
21
|
-
}
|
|
22
|
-
interface QuickfilterDesignComponentProps {
|
|
23
|
-
/** Design of a single quickfilters. See README.md */
|
|
24
|
-
design: Quickfilter;
|
|
25
|
-
onChange: (design: Quickfilter) => void;
|
|
26
|
-
onRemove: () => void;
|
|
27
|
-
/** True if can be merged */
|
|
28
|
-
mergeable?: boolean;
|
|
29
|
-
schema: Schema;
|
|
30
|
-
dataSource: DataSource;
|
|
31
|
-
tables: string[];
|
|
32
|
-
}
|
|
33
|
-
interface QuickfilterDesignComponentState {
|
|
34
|
-
table: any;
|
|
35
|
-
}
|
|
36
|
-
/** Single quickfilter design component */
|
|
37
|
-
declare class QuickfilterDesignComponent extends React.Component<QuickfilterDesignComponentProps, QuickfilterDesignComponentState> {
|
|
38
|
-
constructor(props: any);
|
|
39
|
-
handleTableChange: (table: any) => void;
|
|
40
|
-
handleExprChange: (expr: any) => void;
|
|
41
|
-
handleLabelChange: (ev: any) => void;
|
|
42
|
-
handleMergedChange: (merged: any) => void;
|
|
43
|
-
handleMultiChange: (multi: any) => void;
|
|
19
|
+
handleRemove: (index: number) => void;
|
|
44
20
|
render(): React.JSX.Element;
|
|
45
21
|
}
|
|
46
|
-
export {};
|
|
@@ -28,8 +28,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
29
|
const lodash_1 = __importDefault(require("lodash"));
|
|
30
30
|
const react_1 = __importDefault(require("react"));
|
|
31
|
-
const
|
|
32
|
-
const
|
|
31
|
+
const uuid_1 = __importDefault(require("uuid"));
|
|
32
|
+
const immer_1 = require("immer");
|
|
33
33
|
const expressions_ui_1 = require("@mwater/expressions-ui");
|
|
34
34
|
const expressions_1 = require("@mwater/expressions");
|
|
35
35
|
const ui = __importStar(require("@mwater/react-library/lib/bootstrap"));
|
|
@@ -37,14 +37,22 @@ const ListEditorComponent_1 = require("@mwater/react-library/lib/ListEditorCompo
|
|
|
37
37
|
// Displays quick filters and allows their value to be modified
|
|
38
38
|
class QuickfiltersDesignComponent extends react_1.default.Component {
|
|
39
39
|
handleDesignChange = (design) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
// Use immer produce to create immutable update
|
|
41
|
+
const newDesign = (0, immer_1.produce)(design, draft => {
|
|
42
|
+
// Add UUID if missing
|
|
43
|
+
for (let i = 0; i < draft.length; i++) {
|
|
44
|
+
if (!draft[i].id) {
|
|
45
|
+
draft[i].id = uuid_1.default.v4();
|
|
46
|
+
}
|
|
45
47
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
// Update merged flag, clearing if not mergeable
|
|
49
|
+
for (let i = 0; i < draft.length; i++) {
|
|
50
|
+
if (draft[i].merged && !this.isMergeable(draft, i)) {
|
|
51
|
+
draft[i].merged = false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
this.props.onDesignChange(newDesign);
|
|
48
56
|
};
|
|
49
57
|
// Determine if quickfilter at index is mergeable with previous (same type, id table and enum values)
|
|
50
58
|
isMergeable(design, index) {
|
|
@@ -75,35 +83,28 @@ class QuickfiltersDesignComponent extends react_1.default.Component {
|
|
|
75
83
|
return true;
|
|
76
84
|
}
|
|
77
85
|
renderQuickfilter = (item, index) => {
|
|
78
|
-
return
|
|
79
|
-
key: index,
|
|
80
|
-
design: item,
|
|
81
|
-
schema: this.props.schema,
|
|
82
|
-
dataSource: this.props.dataSource,
|
|
83
|
-
tables: this.props.tables,
|
|
84
|
-
mergeable: this.isMergeable(this.props.design, index),
|
|
85
|
-
onChange: (newItem) => {
|
|
86
|
+
return react_1.default.createElement(QuickfilterDesignComponent, { key: index, design: item, schema: this.props.schema, dataSource: this.props.dataSource, tables: this.props.tables, mergeable: this.isMergeable(this.props.design, index), onChange: (newItem) => {
|
|
86
87
|
const design = this.props.design.slice();
|
|
87
88
|
design[index] = newItem;
|
|
88
89
|
return this.handleDesignChange(design);
|
|
89
|
-
},
|
|
90
|
-
onRemove: this.handleRemove.bind(null, index)
|
|
91
|
-
});
|
|
90
|
+
}, onRemove: this.handleRemove.bind(null, index) });
|
|
92
91
|
};
|
|
93
92
|
handleAdd = () => {
|
|
94
93
|
// Add blank to end
|
|
95
94
|
const design = this.props.design.concat([{ expr: null }]);
|
|
96
|
-
|
|
95
|
+
this.props.onDesignChange(design);
|
|
97
96
|
};
|
|
98
97
|
handleRemove = (index) => {
|
|
99
98
|
const design = this.props.design.slice();
|
|
100
99
|
design.splice(index, 1);
|
|
101
|
-
|
|
100
|
+
this.props.onDesignChange(design);
|
|
102
101
|
};
|
|
103
102
|
render() {
|
|
104
|
-
return
|
|
105
|
-
|
|
106
|
-
:
|
|
103
|
+
return (react_1.default.createElement("div", null,
|
|
104
|
+
react_1.default.createElement(ListEditorComponent_1.ListEditorComponent, { items: this.props.design, onItemsChange: this.handleDesignChange, renderItem: this.renderQuickfilter, getReorderableKey: (item, index) => item.id || index }),
|
|
105
|
+
this.props.tables.length > 0 ? (react_1.default.createElement("button", { type: "button", className: "btn btn-sm btn-link", onClick: this.handleAdd },
|
|
106
|
+
react_1.default.createElement("span", { className: "fas fa-plus me-1" }),
|
|
107
|
+
T `Add Quick Filter`)) : undefined));
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
exports.default = QuickfiltersDesignComponent;
|
|
@@ -124,16 +125,24 @@ class QuickfilterDesignComponent extends react_1.default.Component {
|
|
|
124
125
|
return this.props.onChange(design);
|
|
125
126
|
};
|
|
126
127
|
handleExprChange = (expr) => {
|
|
127
|
-
|
|
128
|
+
this.props.onChange((0, immer_1.produce)(this.props.design, draft => {
|
|
129
|
+
draft.expr = expr;
|
|
130
|
+
}));
|
|
128
131
|
};
|
|
129
132
|
handleLabelChange = (ev) => {
|
|
130
|
-
|
|
133
|
+
this.props.onChange((0, immer_1.produce)(this.props.design, draft => {
|
|
134
|
+
draft.label = ev.target.value;
|
|
135
|
+
}));
|
|
131
136
|
};
|
|
132
137
|
handleMergedChange = (merged) => {
|
|
133
|
-
|
|
138
|
+
this.props.onChange((0, immer_1.produce)(this.props.design, draft => {
|
|
139
|
+
draft.merged = merged;
|
|
140
|
+
}));
|
|
134
141
|
};
|
|
135
142
|
handleMultiChange = (multi) => {
|
|
136
|
-
|
|
143
|
+
this.props.onChange((0, immer_1.produce)(this.props.design, draft => {
|
|
144
|
+
draft.multi = multi;
|
|
145
|
+
}));
|
|
137
146
|
};
|
|
138
147
|
render() {
|
|
139
148
|
// Determine type of expression
|
package/package.json
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import _ from "lodash"
|
|
2
2
|
import React, { ReactNode } from "react"
|
|
3
|
-
const R = React.createElement
|
|
4
|
-
|
|
5
3
|
import { DataSource, ExprCompiler, Schema } from "@mwater/expressions"
|
|
6
4
|
import UndoStack from "../UndoStack"
|
|
7
5
|
import * as DashboardUtils from "./DashboardUtils"
|
|
@@ -233,153 +231,154 @@ export default class DashboardComponent extends React.Component<DashboardCompone
|
|
|
233
231
|
}
|
|
234
232
|
|
|
235
233
|
renderEditingSwitch() {
|
|
236
|
-
return
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
234
|
+
return (
|
|
235
|
+
<a
|
|
236
|
+
key="edit"
|
|
237
|
+
className={`btn btn-primary btn-sm ${this.state.editing ? "active" : ""}`}
|
|
238
|
+
onClick={this.handleToggleEditing}
|
|
239
|
+
>
|
|
240
|
+
<span className="fas fa-pencil-alt"/>
|
|
241
|
+
{" "}
|
|
242
|
+
{this.state.editing ? T`Editing` : T`Edit`}
|
|
243
|
+
</a>
|
|
246
244
|
)
|
|
247
245
|
}
|
|
248
246
|
|
|
249
247
|
renderStyle() {
|
|
250
|
-
return
|
|
251
|
-
"button"
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
248
|
+
return (
|
|
249
|
+
<button type="button" key="style" className="btn btn-link btn-sm" onClick={this.handleOpenLayoutOptions}>
|
|
250
|
+
<span className="fa fa-mobile"/>
|
|
251
|
+
<span className="hide-600px"> {T`Layout`}</span>
|
|
252
|
+
</button>
|
|
255
253
|
)
|
|
256
254
|
}
|
|
257
255
|
|
|
258
256
|
renderActionLinks() {
|
|
259
|
-
return
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
className
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
R("span", { className: "fas fa-caret-left" }),
|
|
272
|
-
R("span", { className: "hide-600px" }, " ", T`Undo`)
|
|
273
|
-
),
|
|
274
|
-
" ",
|
|
275
|
-
R(
|
|
276
|
-
"a",
|
|
277
|
-
{
|
|
278
|
-
key: "redo",
|
|
279
|
-
className: `btn btn-link btn-sm ${!this.state.undoStack.canRedo() ? "disabled" : ""}`,
|
|
280
|
-
onClick: this.handleRedo
|
|
281
|
-
},
|
|
282
|
-
R("span", { className: "fas fa-caret-right" }),
|
|
283
|
-
R("span", { className: "hide-600px" }, " ", T`Redo`)
|
|
284
|
-
)
|
|
285
|
-
]
|
|
286
|
-
: undefined,
|
|
287
|
-
!this.state.editing && this.props.design.otherLocales && this.props.design.otherLocales.length > 0
|
|
288
|
-
? R(
|
|
289
|
-
"div",
|
|
290
|
-
{ key: "translations", className: "dropdown d-inline-block" },
|
|
291
|
-
R(
|
|
292
|
-
"a",
|
|
293
|
-
{
|
|
294
|
-
className: "btn btn-link btn-sm dropdown-toggle",
|
|
295
|
-
"data-bs-toggle": "dropdown"
|
|
296
|
-
},
|
|
297
|
-
R("span", { className: "fal fa-globe" }),
|
|
257
|
+
return (
|
|
258
|
+
<div>
|
|
259
|
+
{this.state.editing
|
|
260
|
+
? [
|
|
261
|
+
<a
|
|
262
|
+
key="undo"
|
|
263
|
+
className={`btn btn-link btn-sm ${!this.state.undoStack.canUndo() ? "disabled" : ""}`}
|
|
264
|
+
onClick={this.handleUndo}
|
|
265
|
+
>
|
|
266
|
+
<span className="fas fa-caret-left"/>
|
|
267
|
+
<span className="hide-600px"> {T`Undo`}</span>
|
|
268
|
+
</a>,
|
|
298
269
|
" ",
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
"
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
270
|
+
<a
|
|
271
|
+
key="redo"
|
|
272
|
+
className={`btn btn-link btn-sm ${!this.state.undoStack.canRedo() ? "disabled" : ""}`}
|
|
273
|
+
onClick={this.handleRedo}
|
|
274
|
+
>
|
|
275
|
+
<span className="fas fa-caret-right"/>
|
|
276
|
+
<span className="hide-600px"> {T`Redo`}</span>
|
|
277
|
+
</a>
|
|
278
|
+
]
|
|
279
|
+
: undefined}
|
|
280
|
+
{!this.state.editing && this.props.design.otherLocales && this.props.design.otherLocales.length > 0
|
|
281
|
+
? <div key="translations" className="dropdown d-inline-block">
|
|
282
|
+
<a
|
|
283
|
+
className="btn btn-link btn-sm dropdown-toggle"
|
|
284
|
+
data-bs-toggle="dropdown"
|
|
285
|
+
>
|
|
286
|
+
<span className="fal fa-globe"/>
|
|
287
|
+
{" "}
|
|
288
|
+
{this.state.locale}
|
|
289
|
+
</a>
|
|
290
|
+
<ul className="dropdown-menu dropdown-menu-end">
|
|
291
|
+
{[this.props.design.locale || "en", ...this.props.design.otherLocales].map(locale =>
|
|
292
|
+
<li key={locale}>
|
|
293
|
+
<a
|
|
294
|
+
className="dropdown-item"
|
|
295
|
+
onClick={() => this.setState({ locale: locale })}
|
|
296
|
+
>
|
|
297
|
+
{languages.find(l => l.code === locale)?.name || locale}
|
|
298
|
+
</a>
|
|
299
|
+
</li>
|
|
300
|
+
)}
|
|
301
|
+
</ul>
|
|
302
|
+
</div>
|
|
303
|
+
: undefined}
|
|
304
|
+
<a key="print" className="btn btn-link btn-sm" onClick={this.handlePrint}>
|
|
305
|
+
<span className="fas fa-print"/>
|
|
306
|
+
<span className="hide-600px"> {T`Print`}</span>
|
|
307
|
+
</a>
|
|
308
|
+
<a key="refresh" className="btn btn-link btn-sm" onClick={this.handleRefreshData}>
|
|
309
|
+
<span className="fas fa-sync"/>
|
|
310
|
+
<span className="hide-600px"> {T`Refresh`}</span>
|
|
311
|
+
</a>
|
|
312
|
+
{this.state.hideQuickfilters && this.props.design.quickfilters && this.props.design.quickfilters.length > 0
|
|
313
|
+
? <a key="showQuickfilters" className="btn btn-link btn-sm" onClick={this.handleShowQuickfilters}>
|
|
314
|
+
<span className="fa fa-filter"/>
|
|
315
|
+
<span className="hide-600px"> {T`Show Quickfilters`}</span>
|
|
316
|
+
</a>
|
|
317
|
+
: undefined}
|
|
318
|
+
|
|
319
|
+
{this.state.editing
|
|
320
|
+
? <a key="settings" className="btn btn-link btn-sm" onClick={this.handleSettings}>
|
|
321
|
+
<span className="fas fa-cog"/>
|
|
322
|
+
<span className="hide-600px"> {T`Settings`}</span>
|
|
323
|
+
</a>
|
|
324
|
+
: undefined}
|
|
325
|
+
{this.state.editing ? this.renderStyle() : undefined}
|
|
326
|
+
{this.props.extraTitleButtonsElem}
|
|
327
|
+
{this.props.onDesignChange != null ? this.renderEditingSwitch() : undefined}
|
|
328
|
+
</div>
|
|
356
329
|
)
|
|
357
330
|
}
|
|
358
331
|
|
|
359
332
|
renderTitleBar() {
|
|
360
|
-
return
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
333
|
+
return (
|
|
334
|
+
<div style={{ height: 40, padding: 4 }}>
|
|
335
|
+
<div style={{ float: "right" }}>{this.renderActionLinks()}</div>
|
|
336
|
+
{this.props.titleElem}
|
|
337
|
+
</div>
|
|
365
338
|
)
|
|
366
339
|
}
|
|
367
340
|
|
|
368
341
|
renderQuickfilter() {
|
|
369
|
-
return
|
|
370
|
-
design
|
|
371
|
-
schema
|
|
372
|
-
dataSource
|
|
373
|
-
quickfiltersDataSource
|
|
374
|
-
values
|
|
375
|
-
onValuesChange
|
|
376
|
-
locks
|
|
377
|
-
filters
|
|
378
|
-
hideTopBorder
|
|
342
|
+
return <QuickfiltersComponent
|
|
343
|
+
design={this.props.design.quickfilters || []}
|
|
344
|
+
schema={this.props.schema}
|
|
345
|
+
dataSource={this.props.dataSource}
|
|
346
|
+
quickfiltersDataSource={this.props.dashboardDataSource.getQuickfiltersDataSource()}
|
|
347
|
+
values={this.state.quickfiltersValues || undefined}
|
|
348
|
+
onValuesChange={(values: any) => this.setState({ quickfiltersValues: values })}
|
|
349
|
+
locks={this.props.quickfilterLocks}
|
|
350
|
+
filters={this.getCompiledFilters()}
|
|
351
|
+
hideTopBorder={this.props.hideTitleBar}
|
|
379
352
|
// Don't hide if title bar is hidden as it can't be shown again
|
|
380
|
-
onHide
|
|
381
|
-
translate
|
|
382
|
-
|
|
353
|
+
onHide={() => this.setState({ hideQuickfilters: true })}
|
|
354
|
+
translate={this.translate}
|
|
355
|
+
/>
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
renderFloatingShowQuickfiltersButton() {
|
|
359
|
+
// Only show if:
|
|
360
|
+
// 1. Quick filters exist
|
|
361
|
+
// 2. Quick filters are hidden
|
|
362
|
+
// 3. Title bar is hidden (since otherwise button is in title bar)
|
|
363
|
+
if (!this.props.design.quickfilters?.length || !this.state.hideQuickfilters || !this.props.hideTitleBar) {
|
|
364
|
+
return null
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return (
|
|
368
|
+
<div style={{
|
|
369
|
+
position: "absolute",
|
|
370
|
+
top: 5,
|
|
371
|
+
right: 20,
|
|
372
|
+
zIndex: 1000
|
|
373
|
+
}}>
|
|
374
|
+
<button
|
|
375
|
+
className="btn btn-link btn-sm"
|
|
376
|
+
onClick={() => this.setState({ hideQuickfilters: false })}
|
|
377
|
+
>
|
|
378
|
+
<i className="fa fa-angle-double-down"/>
|
|
379
|
+
</button>
|
|
380
|
+
</div>
|
|
381
|
+
)
|
|
383
382
|
}
|
|
384
383
|
|
|
385
384
|
refDashboardView = (el: any) => {
|
|
@@ -400,34 +399,33 @@ export default class DashboardComponent extends React.Component<DashboardCompone
|
|
|
400
399
|
|
|
401
400
|
const displayLocale = this.state.editing ? this.props.design.locale || "en" : this.state.locale
|
|
402
401
|
|
|
403
|
-
const dashboardView =
|
|
404
|
-
schema
|
|
405
|
-
dataSource
|
|
406
|
-
dashboardDataSource
|
|
407
|
-
ref
|
|
408
|
-
design
|
|
409
|
-
onDesignChange
|
|
410
|
-
filters
|
|
411
|
-
onRowClick
|
|
412
|
-
namedStrings
|
|
413
|
-
hideScopes
|
|
414
|
-
refreshKey
|
|
415
|
-
locale
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const readonlyDashboardView =
|
|
419
|
-
schema
|
|
420
|
-
dataSource
|
|
421
|
-
dashboardDataSource
|
|
422
|
-
ref
|
|
423
|
-
design
|
|
424
|
-
filters
|
|
425
|
-
onRowClick
|
|
426
|
-
namedStrings
|
|
427
|
-
hideScopes
|
|
428
|
-
locale
|
|
429
|
-
|
|
430
|
-
|
|
402
|
+
const dashboardView = <DashboardViewComponent
|
|
403
|
+
schema={this.props.schema}
|
|
404
|
+
dataSource={this.props.dataSource}
|
|
405
|
+
dashboardDataSource={this.props.dashboardDataSource}
|
|
406
|
+
ref={this.refDashboardView}
|
|
407
|
+
design={this.props.design}
|
|
408
|
+
onDesignChange={this.state.editing ? this.props.onDesignChange : undefined}
|
|
409
|
+
filters={filters}
|
|
410
|
+
onRowClick={this.props.onRowClick}
|
|
411
|
+
namedStrings={this.props.namedStrings}
|
|
412
|
+
hideScopes={this.state.hideQuickfilters}
|
|
413
|
+
refreshKey={this.state.refreshKey}
|
|
414
|
+
locale={displayLocale}
|
|
415
|
+
/>
|
|
416
|
+
|
|
417
|
+
const readonlyDashboardView = <DashboardViewComponent
|
|
418
|
+
schema={this.props.schema}
|
|
419
|
+
dataSource={this.props.dataSource}
|
|
420
|
+
dashboardDataSource={this.props.dashboardDataSource}
|
|
421
|
+
ref={this.refDashboardView}
|
|
422
|
+
design={this.props.design}
|
|
423
|
+
filters={filters}
|
|
424
|
+
onRowClick={this.props.onRowClick}
|
|
425
|
+
namedStrings={this.props.namedStrings}
|
|
426
|
+
hideScopes={this.state.hideQuickfilters}
|
|
427
|
+
locale={displayLocale}
|
|
428
|
+
/>
|
|
431
429
|
|
|
432
430
|
// Pass active tables down to table select components so they can present a shorter list
|
|
433
431
|
return <ActiveTablesContext.Provider
|
|
@@ -441,6 +439,7 @@ export default class DashboardComponent extends React.Component<DashboardCompone
|
|
|
441
439
|
{!this.props.hideTitleBar ? this.renderTitleBar() : undefined}
|
|
442
440
|
<div>{!this.state.hideQuickfilters ? this.renderQuickfilter() : undefined}</div>
|
|
443
441
|
{dashboardView}
|
|
442
|
+
{this.renderFloatingShowQuickfiltersButton()}
|
|
444
443
|
{this.props.onDesignChange != null && (
|
|
445
444
|
<SettingsModalComponent
|
|
446
445
|
onDesignChange={this.handleDesignChange}
|
package/src/languages.ts
CHANGED
|
@@ -4,6 +4,9 @@ import { Expr } from "@mwater/expressions"
|
|
|
4
4
|
design is an array of quick filters (user-selectable filters).
|
|
5
5
|
*/
|
|
6
6
|
export interface Quickfilter {
|
|
7
|
+
/** Optional id for the quickfilter. If not present, a random id will be generated the first time the quickfilter is modified. */
|
|
8
|
+
id?: string
|
|
9
|
+
|
|
7
10
|
// `table`: (deprecated) table of filter
|
|
8
11
|
|
|
9
12
|
/** filter expression (left hand side only. Usually enum, enumset, text, date, datetime, id[], text[]) */
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import _ from "lodash"
|
|
2
2
|
import React from "react"
|
|
3
|
-
|
|
4
|
-
import
|
|
3
|
+
import uuid from "uuid"
|
|
4
|
+
import { produce } from "immer"
|
|
5
5
|
import { ExprComponent } from "@mwater/expressions-ui"
|
|
6
|
-
import { DataSource, ExprUtils, Schema } from "@mwater/expressions"
|
|
6
|
+
import { DataSource, Expr, ExprUtils, FieldExpr, Schema } from "@mwater/expressions"
|
|
7
7
|
import * as ui from "@mwater/react-library/lib/bootstrap"
|
|
8
8
|
import { ListEditorComponent } from "@mwater/react-library/lib/ListEditorComponent"
|
|
9
9
|
import { Quickfilter } from "./Quickfilter"
|
|
@@ -21,21 +21,29 @@ export interface QuickfiltersDesignComponentProps {
|
|
|
21
21
|
|
|
22
22
|
// Displays quick filters and allows their value to be modified
|
|
23
23
|
export default class QuickfiltersDesignComponent extends React.Component<QuickfiltersDesignComponentProps> {
|
|
24
|
-
handleDesignChange = (design:
|
|
25
|
-
|
|
24
|
+
handleDesignChange = (design: Quickfilter[]) => {
|
|
25
|
+
// Use immer produce to create immutable update
|
|
26
|
+
const newDesign = produce(design, draft => {
|
|
27
|
+
// Add UUID if missing
|
|
28
|
+
for (let i = 0; i < draft.length; i++) {
|
|
29
|
+
if (!draft[i].id) {
|
|
30
|
+
draft[i].id = uuid.v4()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
26
33
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
// Update merged flag, clearing if not mergeable
|
|
35
|
+
for (let i = 0; i < draft.length; i++) {
|
|
36
|
+
if (draft[i].merged && !this.isMergeable(draft, i)) {
|
|
37
|
+
draft[i].merged = false
|
|
38
|
+
}
|
|
31
39
|
}
|
|
32
|
-
}
|
|
40
|
+
})
|
|
33
41
|
|
|
34
|
-
|
|
42
|
+
this.props.onDesignChange(newDesign)
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
// Determine if quickfilter at index is mergeable with previous (same type, id table and enum values)
|
|
38
|
-
isMergeable(design:
|
|
46
|
+
isMergeable(design: Quickfilter[], index: number) {
|
|
39
47
|
if (index === 0) {
|
|
40
48
|
return false
|
|
41
49
|
}
|
|
@@ -73,54 +81,51 @@ export default class QuickfiltersDesignComponent extends React.Component<Quickfi
|
|
|
73
81
|
return true
|
|
74
82
|
}
|
|
75
83
|
|
|
76
|
-
renderQuickfilter = (item:
|
|
77
|
-
return
|
|
78
|
-
key
|
|
79
|
-
design
|
|
80
|
-
schema
|
|
81
|
-
dataSource
|
|
82
|
-
tables
|
|
83
|
-
mergeable
|
|
84
|
-
onChange
|
|
84
|
+
renderQuickfilter = (item: Quickfilter, index: number) => {
|
|
85
|
+
return <QuickfilterDesignComponent
|
|
86
|
+
key={index}
|
|
87
|
+
design={item}
|
|
88
|
+
schema={this.props.schema}
|
|
89
|
+
dataSource={this.props.dataSource}
|
|
90
|
+
tables={this.props.tables}
|
|
91
|
+
mergeable={this.isMergeable(this.props.design, index)}
|
|
92
|
+
onChange={(newItem: Quickfilter) => {
|
|
85
93
|
const design = this.props.design.slice()
|
|
86
94
|
design[index] = newItem
|
|
87
95
|
return this.handleDesignChange(design)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
})
|
|
96
|
+
}}
|
|
97
|
+
onRemove={this.handleRemove.bind(null, index)}
|
|
98
|
+
/>
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
handleAdd = () => {
|
|
95
102
|
// Add blank to end
|
|
96
103
|
const design = this.props.design.concat([{ expr: null }])
|
|
97
|
-
|
|
104
|
+
this.props.onDesignChange(design)
|
|
98
105
|
}
|
|
99
106
|
|
|
100
|
-
handleRemove = (index:
|
|
107
|
+
handleRemove = (index: number) => {
|
|
101
108
|
const design = this.props.design.slice()
|
|
102
109
|
design.splice(index, 1)
|
|
103
|
-
|
|
110
|
+
this.props.onDesignChange(design)
|
|
104
111
|
}
|
|
105
112
|
|
|
106
113
|
render() {
|
|
107
|
-
return
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
)
|
|
123
|
-
: undefined
|
|
114
|
+
return (
|
|
115
|
+
<div>
|
|
116
|
+
<ListEditorComponent
|
|
117
|
+
items={this.props.design}
|
|
118
|
+
onItemsChange={this.handleDesignChange}
|
|
119
|
+
renderItem={this.renderQuickfilter}
|
|
120
|
+
getReorderableKey={(item, index) => item.id || index}
|
|
121
|
+
/>
|
|
122
|
+
{this.props.tables.length > 0 ? (
|
|
123
|
+
<button type="button" className="btn btn-sm btn-link" onClick={this.handleAdd}>
|
|
124
|
+
<span className="fas fa-plus me-1" />
|
|
125
|
+
{T`Add Quick Filter`}
|
|
126
|
+
</button>
|
|
127
|
+
) : undefined}
|
|
128
|
+
</div>
|
|
124
129
|
)
|
|
125
130
|
}
|
|
126
131
|
}
|
|
@@ -138,7 +143,7 @@ interface QuickfilterDesignComponentProps {
|
|
|
138
143
|
}
|
|
139
144
|
|
|
140
145
|
interface QuickfilterDesignComponentState {
|
|
141
|
-
table:
|
|
146
|
+
table: string
|
|
142
147
|
}
|
|
143
148
|
|
|
144
149
|
/** Single quickfilter design component */
|
|
@@ -146,16 +151,16 @@ class QuickfilterDesignComponent extends React.Component<
|
|
|
146
151
|
QuickfilterDesignComponentProps,
|
|
147
152
|
QuickfilterDesignComponentState
|
|
148
153
|
> {
|
|
149
|
-
constructor(props:
|
|
154
|
+
constructor(props: QuickfilterDesignComponentProps) {
|
|
150
155
|
super(props)
|
|
151
156
|
|
|
152
157
|
// Store table to allow selecting table first, then expression
|
|
153
158
|
this.state = {
|
|
154
|
-
table: props.design.expr?.table || props.tables[0]
|
|
159
|
+
table: (props.design.expr as FieldExpr)?.table || props.tables[0]
|
|
155
160
|
}
|
|
156
161
|
}
|
|
157
162
|
|
|
158
|
-
handleTableChange = (table:
|
|
163
|
+
handleTableChange = (table: string) => {
|
|
159
164
|
this.setState({ table })
|
|
160
165
|
const design = {
|
|
161
166
|
expr: null
|
|
@@ -163,18 +168,30 @@ class QuickfilterDesignComponent extends React.Component<
|
|
|
163
168
|
return this.props.onChange(design)
|
|
164
169
|
}
|
|
165
170
|
|
|
166
|
-
handleExprChange = (expr:
|
|
167
|
-
|
|
171
|
+
handleExprChange = (expr: Expr) => {
|
|
172
|
+
this.props.onChange(produce(this.props.design, draft => {
|
|
173
|
+
draft.expr = expr
|
|
174
|
+
}))
|
|
168
175
|
}
|
|
176
|
+
|
|
169
177
|
handleLabelChange = (ev: any) => {
|
|
170
|
-
|
|
178
|
+
this.props.onChange(produce(this.props.design, draft => {
|
|
179
|
+
draft.label = ev.target.value
|
|
180
|
+
}))
|
|
171
181
|
}
|
|
172
|
-
|
|
173
|
-
|
|
182
|
+
|
|
183
|
+
handleMergedChange = (merged: boolean) => {
|
|
184
|
+
this.props.onChange(produce(this.props.design, draft => {
|
|
185
|
+
draft.merged = merged
|
|
186
|
+
}))
|
|
174
187
|
}
|
|
175
|
-
|
|
176
|
-
|
|
188
|
+
|
|
189
|
+
handleMultiChange = (multi: boolean) => {
|
|
190
|
+
this.props.onChange(produce(this.props.design, draft => {
|
|
191
|
+
draft.multi = multi
|
|
192
|
+
}))
|
|
177
193
|
}
|
|
194
|
+
|
|
178
195
|
render() {
|
|
179
196
|
// Determine type of expression
|
|
180
197
|
const exprType = new ExprUtils(this.props.schema).getExprType(this.props.design.expr)
|