@mwater/visualization 5.4.2 → 5.4.4
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/QuickfiltersDesignComponent.d.ts +1 -33
- package/lib/quickfilter/QuickfiltersDesignComponent.js +55 -49
- package/package.json +1 -1
- package/src/dashboards/DashboardComponent.tsx +158 -159
- package/src/languages.ts +6 -1
- package/src/quickfilter/QuickfiltersDesignComponent.tsx +87 -70
|
@@ -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
|
@@ -11,36 +11,4 @@ export interface QuickfiltersDesignComponentProps {
|
|
|
11
11
|
/** List of possible table ids to use */
|
|
12
12
|
tables: string[];
|
|
13
13
|
}
|
|
14
|
-
export default
|
|
15
|
-
handleDesignChange: (design: any) => void;
|
|
16
|
-
isMergeable(design: any, index: any): boolean;
|
|
17
|
-
renderQuickfilter: (item: any, index: any) => React.CElement<any, QuickfilterDesignComponent>;
|
|
18
|
-
handleAdd: () => void;
|
|
19
|
-
handleRemove: (index: any) => void;
|
|
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;
|
|
44
|
-
render(): React.JSX.Element;
|
|
45
|
-
}
|
|
46
|
-
export {};
|
|
14
|
+
export default function QuickfiltersDesignComponent(props: QuickfiltersDesignComponentProps): React.JSX.Element;
|
|
@@ -26,32 +26,41 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
26
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
27
|
};
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.default = QuickfiltersDesignComponent;
|
|
29
30
|
const lodash_1 = __importDefault(require("lodash"));
|
|
30
|
-
const react_1 =
|
|
31
|
-
const
|
|
32
|
-
const update_object_1 = __importDefault(require("update-object"));
|
|
31
|
+
const react_1 = __importStar(require("react"));
|
|
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"));
|
|
36
36
|
const ListEditorComponent_1 = require("@mwater/react-library/lib/ListEditorComponent");
|
|
37
37
|
// Displays quick filters and allows their value to be modified
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
function QuickfiltersDesignComponent(props) {
|
|
39
|
+
// This counter is used to force a complete remount of the ListEditorComponent when items are reordered
|
|
40
|
+
// This is necessary because the ExprComponent inside the list has internal state that can get corrupted
|
|
41
|
+
// during reordering operations. By remounting, we ensure a clean slate for all expression components.
|
|
42
|
+
const [reorderCounter, setReorderCounter] = (0, react_1.useState)(0);
|
|
43
|
+
const handleDesignChange = (design) => {
|
|
44
|
+
const newDesign = (0, immer_1.produce)(design, draft => {
|
|
45
|
+
// Update merged flag, clearing if not mergeable
|
|
46
|
+
for (let i = 0; i < draft.length; i++) {
|
|
47
|
+
if (draft[i].merged && !isMergeable(draft, i)) {
|
|
48
|
+
draft[i].merged = false;
|
|
49
|
+
}
|
|
45
50
|
}
|
|
46
|
-
}
|
|
47
|
-
|
|
51
|
+
});
|
|
52
|
+
props.onDesignChange(newDesign);
|
|
53
|
+
};
|
|
54
|
+
const handleReorder = (design) => {
|
|
55
|
+
setReorderCounter(c => c + 1);
|
|
56
|
+
handleDesignChange(design);
|
|
48
57
|
};
|
|
49
58
|
// Determine if quickfilter at index is mergeable with previous (same type, id table and enum values)
|
|
50
|
-
isMergeable(design, index) {
|
|
59
|
+
const isMergeable = (design, index) => {
|
|
51
60
|
if (index === 0) {
|
|
52
61
|
return false;
|
|
53
62
|
}
|
|
54
|
-
const exprUtils = new expressions_1.ExprUtils(
|
|
63
|
+
const exprUtils = new expressions_1.ExprUtils(props.schema);
|
|
55
64
|
const type = exprUtils.getExprType(design[index].expr);
|
|
56
65
|
const prevType = exprUtils.getExprType(design[index - 1].expr);
|
|
57
66
|
const idTable = exprUtils.getExprIdTable(design[index].expr);
|
|
@@ -73,40 +82,30 @@ class QuickfiltersDesignComponent extends react_1.default.Component {
|
|
|
73
82
|
return false;
|
|
74
83
|
}
|
|
75
84
|
return true;
|
|
76
|
-
}
|
|
77
|
-
renderQuickfilter = (item, index) => {
|
|
78
|
-
return
|
|
79
|
-
|
|
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
|
-
const design = this.props.design.slice();
|
|
85
|
+
};
|
|
86
|
+
const renderQuickfilter = (item, index) => {
|
|
87
|
+
return react_1.default.createElement(QuickfilterDesignComponent, { key: index, design: item, schema: props.schema, dataSource: props.dataSource, tables: props.tables, mergeable: isMergeable(props.design, index), onChange: (newItem) => {
|
|
88
|
+
const design = props.design.slice();
|
|
87
89
|
design[index] = newItem;
|
|
88
|
-
|
|
89
|
-
},
|
|
90
|
-
onRemove: this.handleRemove.bind(null, index)
|
|
91
|
-
});
|
|
90
|
+
handleDesignChange(design);
|
|
91
|
+
}, onRemove: () => handleRemove(index) });
|
|
92
92
|
};
|
|
93
|
-
handleAdd = () => {
|
|
93
|
+
const handleAdd = () => {
|
|
94
94
|
// Add blank to end
|
|
95
|
-
const design =
|
|
96
|
-
|
|
95
|
+
const design = props.design.concat([{ expr: null }]);
|
|
96
|
+
props.onDesignChange(design);
|
|
97
97
|
};
|
|
98
|
-
handleRemove = (index) => {
|
|
99
|
-
const design =
|
|
98
|
+
const handleRemove = (index) => {
|
|
99
|
+
const design = props.design.slice();
|
|
100
100
|
design.splice(index, 1);
|
|
101
|
-
|
|
101
|
+
props.onDesignChange(design);
|
|
102
102
|
};
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
:
|
|
107
|
-
|
|
103
|
+
return (react_1.default.createElement("div", null,
|
|
104
|
+
react_1.default.createElement(ListEditorComponent_1.ListEditorComponent, { key: reorderCounter, items: props.design, onItemsChange: handleReorder, renderItem: renderQuickfilter, getReorderableKey: (item, index) => index }),
|
|
105
|
+
props.tables.length > 0 ? (react_1.default.createElement("button", { type: "button", className: "btn btn-sm btn-link", onClick: handleAdd },
|
|
106
|
+
react_1.default.createElement("span", { className: "fas fa-plus me-1" }),
|
|
107
|
+
T `Add Quick Filter`)) : undefined));
|
|
108
108
|
}
|
|
109
|
-
exports.default = QuickfiltersDesignComponent;
|
|
110
109
|
/** Single quickfilter design component */
|
|
111
110
|
class QuickfilterDesignComponent extends react_1.default.Component {
|
|
112
111
|
constructor(props) {
|
|
@@ -118,22 +117,29 @@ class QuickfilterDesignComponent extends react_1.default.Component {
|
|
|
118
117
|
}
|
|
119
118
|
handleTableChange = (table) => {
|
|
120
119
|
this.setState({ table });
|
|
121
|
-
|
|
122
|
-
expr
|
|
123
|
-
};
|
|
124
|
-
return this.props.onChange(design);
|
|
120
|
+
this.props.onChange((0, immer_1.produce)(this.props.design, draft => {
|
|
121
|
+
draft.expr = null;
|
|
122
|
+
}));
|
|
125
123
|
};
|
|
126
124
|
handleExprChange = (expr) => {
|
|
127
|
-
|
|
125
|
+
this.props.onChange((0, immer_1.produce)(this.props.design, draft => {
|
|
126
|
+
draft.expr = expr;
|
|
127
|
+
}));
|
|
128
128
|
};
|
|
129
129
|
handleLabelChange = (ev) => {
|
|
130
|
-
|
|
130
|
+
this.props.onChange((0, immer_1.produce)(this.props.design, draft => {
|
|
131
|
+
draft.label = ev.target.value;
|
|
132
|
+
}));
|
|
131
133
|
};
|
|
132
134
|
handleMergedChange = (merged) => {
|
|
133
|
-
|
|
135
|
+
this.props.onChange((0, immer_1.produce)(this.props.design, draft => {
|
|
136
|
+
draft.merged = merged;
|
|
137
|
+
}));
|
|
134
138
|
};
|
|
135
139
|
handleMultiChange = (multi) => {
|
|
136
|
-
|
|
140
|
+
this.props.onChange((0, immer_1.produce)(this.props.design, draft => {
|
|
141
|
+
draft.multi = multi;
|
|
142
|
+
}));
|
|
137
143
|
};
|
|
138
144
|
render() {
|
|
139
145
|
// 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
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import _ from "lodash"
|
|
2
|
-
import React from "react"
|
|
3
|
-
|
|
4
|
-
import
|
|
2
|
+
import React, { useState } from "react"
|
|
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"
|
|
@@ -20,27 +20,37 @@ export interface QuickfiltersDesignComponentProps {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// Displays quick filters and allows their value to be modified
|
|
23
|
-
export default
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
export default function QuickfiltersDesignComponent(props: QuickfiltersDesignComponentProps) {
|
|
24
|
+
// This counter is used to force a complete remount of the ListEditorComponent when items are reordered
|
|
25
|
+
// This is necessary because the ExprComponent inside the list has internal state that can get corrupted
|
|
26
|
+
// during reordering operations. By remounting, we ensure a clean slate for all expression components.
|
|
27
|
+
const [reorderCounter, setReorderCounter] = useState(0)
|
|
28
|
+
|
|
29
|
+
const handleDesignChange = (design: Quickfilter[]) => {
|
|
30
|
+
const newDesign = produce(design, draft => {
|
|
31
|
+
// Update merged flag, clearing if not mergeable
|
|
32
|
+
for (let i = 0; i < draft.length; i++) {
|
|
33
|
+
if (draft[i].merged && !isMergeable(draft, i)) {
|
|
34
|
+
draft[i].merged = false
|
|
35
|
+
}
|
|
31
36
|
}
|
|
32
|
-
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
props.onDesignChange(newDesign)
|
|
40
|
+
}
|
|
33
41
|
|
|
34
|
-
|
|
42
|
+
const handleReorder = (design: Quickfilter[]) => {
|
|
43
|
+
setReorderCounter(c => c + 1)
|
|
44
|
+
handleDesignChange(design)
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
// Determine if quickfilter at index is mergeable with previous (same type, id table and enum values)
|
|
38
|
-
isMergeable(design:
|
|
48
|
+
const isMergeable = (design: Quickfilter[], index: number) => {
|
|
39
49
|
if (index === 0) {
|
|
40
50
|
return false
|
|
41
51
|
}
|
|
42
52
|
|
|
43
|
-
const exprUtils = new ExprUtils(
|
|
53
|
+
const exprUtils = new ExprUtils(props.schema)
|
|
44
54
|
|
|
45
55
|
const type = exprUtils.getExprType(design[index].expr)
|
|
46
56
|
const prevType = exprUtils.getExprType(design[index - 1].expr)
|
|
@@ -73,56 +83,52 @@ export default class QuickfiltersDesignComponent extends React.Component<Quickfi
|
|
|
73
83
|
return true
|
|
74
84
|
}
|
|
75
85
|
|
|
76
|
-
renderQuickfilter = (item:
|
|
77
|
-
return
|
|
78
|
-
key
|
|
79
|
-
design
|
|
80
|
-
schema
|
|
81
|
-
dataSource
|
|
82
|
-
tables
|
|
83
|
-
mergeable
|
|
84
|
-
onChange
|
|
85
|
-
const design =
|
|
86
|
+
const renderQuickfilter = (item: Quickfilter, index: number) => {
|
|
87
|
+
return <QuickfilterDesignComponent
|
|
88
|
+
key={index}
|
|
89
|
+
design={item}
|
|
90
|
+
schema={props.schema}
|
|
91
|
+
dataSource={props.dataSource}
|
|
92
|
+
tables={props.tables}
|
|
93
|
+
mergeable={isMergeable(props.design, index)}
|
|
94
|
+
onChange={(newItem: Quickfilter) => {
|
|
95
|
+
const design = props.design.slice()
|
|
86
96
|
design[index] = newItem
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
})
|
|
97
|
+
handleDesignChange(design)
|
|
98
|
+
}}
|
|
99
|
+
onRemove={() => handleRemove(index)}
|
|
100
|
+
/>
|
|
92
101
|
}
|
|
93
102
|
|
|
94
|
-
handleAdd = () => {
|
|
103
|
+
const handleAdd = () => {
|
|
95
104
|
// Add blank to end
|
|
96
|
-
const design =
|
|
97
|
-
|
|
105
|
+
const design = props.design.concat([{ expr: null }])
|
|
106
|
+
props.onDesignChange(design)
|
|
98
107
|
}
|
|
99
108
|
|
|
100
|
-
handleRemove = (index:
|
|
101
|
-
const design =
|
|
109
|
+
const handleRemove = (index: number) => {
|
|
110
|
+
const design = props.design.slice()
|
|
102
111
|
design.splice(index, 1)
|
|
103
|
-
|
|
112
|
+
props.onDesignChange(design)
|
|
104
113
|
}
|
|
105
114
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
"div",
|
|
109
|
-
null,
|
|
115
|
+
return (
|
|
116
|
+
<div>
|
|
110
117
|
<ListEditorComponent
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
key={reorderCounter}
|
|
119
|
+
items={props.design}
|
|
120
|
+
onItemsChange={handleReorder}
|
|
121
|
+
renderItem={renderQuickfilter}
|
|
114
122
|
getReorderableKey={(item, index) => index}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
)
|
|
125
|
-
}
|
|
123
|
+
/>
|
|
124
|
+
{props.tables.length > 0 ? (
|
|
125
|
+
<button type="button" className="btn btn-sm btn-link" onClick={handleAdd}>
|
|
126
|
+
<span className="fas fa-plus me-1" />
|
|
127
|
+
{T`Add Quick Filter`}
|
|
128
|
+
</button>
|
|
129
|
+
) : undefined}
|
|
130
|
+
</div>
|
|
131
|
+
)
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
interface QuickfilterDesignComponentProps {
|
|
@@ -138,7 +144,7 @@ interface QuickfilterDesignComponentProps {
|
|
|
138
144
|
}
|
|
139
145
|
|
|
140
146
|
interface QuickfilterDesignComponentState {
|
|
141
|
-
table:
|
|
147
|
+
table: string
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
/** Single quickfilter design component */
|
|
@@ -146,35 +152,46 @@ class QuickfilterDesignComponent extends React.Component<
|
|
|
146
152
|
QuickfilterDesignComponentProps,
|
|
147
153
|
QuickfilterDesignComponentState
|
|
148
154
|
> {
|
|
149
|
-
constructor(props:
|
|
155
|
+
constructor(props: QuickfilterDesignComponentProps) {
|
|
150
156
|
super(props)
|
|
151
157
|
|
|
152
158
|
// Store table to allow selecting table first, then expression
|
|
153
159
|
this.state = {
|
|
154
|
-
table: props.design.expr?.table || props.tables[0]
|
|
160
|
+
table: (props.design.expr as FieldExpr)?.table || props.tables[0]
|
|
155
161
|
}
|
|
156
162
|
}
|
|
157
163
|
|
|
158
|
-
handleTableChange = (table:
|
|
164
|
+
handleTableChange = (table: string) => {
|
|
159
165
|
this.setState({ table })
|
|
160
|
-
|
|
161
|
-
expr
|
|
162
|
-
}
|
|
163
|
-
return this.props.onChange(design)
|
|
166
|
+
this.props.onChange(produce(this.props.design, draft => {
|
|
167
|
+
draft.expr = null
|
|
168
|
+
}))
|
|
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)
|