@mwater/visualization 5.4.4 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/.storybook/head.html +0 -1
  2. package/lib/MWaterContextComponent.js +1 -1
  3. package/lib/MWaterLoaderComponent.d.ts +2 -2
  4. package/lib/dashboards/DashboardComponent.js +2 -1
  5. package/lib/dashboards/LayoutOptionsComponent.js +18 -11
  6. package/lib/dashboards/ServerDashboardDataSource.d.ts +10 -1
  7. package/lib/dashboards/ServerDashboardDataSource.js +29 -0
  8. package/lib/dashboards/layoutOptions.d.ts +5 -1
  9. package/lib/datagrids/DatagridComponent.js +1 -1
  10. package/lib/datagrids/ExprCellComponent.d.ts +1 -0
  11. package/lib/datagrids/ExprCellComponent.js +22 -20
  12. package/lib/maps/BufferLayer.d.ts +18 -0
  13. package/lib/maps/BufferLayer.js +24 -14
  14. package/lib/maps/ChoroplethLayer.d.ts +18 -0
  15. package/lib/maps/ChoroplethLayer.js +34 -25
  16. package/lib/maps/ChoroplethLayerDesign.d.ts +3 -2
  17. package/lib/maps/ChoroplethLayerDesigner.d.ts +11 -1
  18. package/lib/maps/DirectMapDataSource.js +17 -0
  19. package/lib/maps/EditHoverOver.d.ts +1 -1
  20. package/lib/maps/EditHoverOver.js +62 -33
  21. package/lib/maps/HoverContent.d.ts +10 -5
  22. package/lib/maps/HoverContent.js +6 -35
  23. package/lib/maps/Layer.d.ts +37 -0
  24. package/lib/maps/Layer.js +30 -4
  25. package/lib/maps/MWaterServerLayer.d.ts +2 -2
  26. package/lib/maps/MWaterServerLayer.js +6 -6
  27. package/lib/maps/MapLayerDataSource.d.ts +9 -0
  28. package/lib/maps/MapUtils.d.ts +19 -1
  29. package/lib/maps/MapUtils.js +71 -1
  30. package/lib/maps/MarkersLayer.d.ts +18 -0
  31. package/lib/maps/MarkersLayer.js +24 -24
  32. package/lib/maps/MarkersLayerDesignerComponent.d.ts +14 -1
  33. package/lib/maps/RasterMapViewComponent.js +1 -1
  34. package/lib/maps/ServerMapDataSource.d.ts +9 -0
  35. package/lib/maps/ServerMapDataSource.js +29 -0
  36. package/lib/maps/VectorMapViewComponent.js +6 -6
  37. package/lib/maps/maps.d.ts +4 -2
  38. package/lib/mwater_table_selection/FormsListComponent.d.ts +33 -0
  39. package/lib/mwater_table_selection/FormsListComponent.js +141 -0
  40. package/lib/mwater_table_selection/IndicatorsListComponent.d.ts +47 -0
  41. package/lib/mwater_table_selection/IndicatorsListComponent.js +182 -0
  42. package/lib/mwater_table_selection/IssuesListComponent.d.ts +29 -0
  43. package/lib/mwater_table_selection/IssuesListComponent.js +123 -0
  44. package/lib/mwater_table_selection/MWaterAccountingSystemListComponent.d.ts +20 -0
  45. package/lib/mwater_table_selection/MWaterAccountingSystemListComponent.js +157 -0
  46. package/lib/mwater_table_selection/MWaterAssetSystemsListComponent.d.ts +17 -0
  47. package/lib/mwater_table_selection/MWaterAssetSystemsListComponent.js +79 -0
  48. package/lib/mwater_table_selection/MWaterCompleteTableSelectComponent.d.ts +37 -0
  49. package/lib/mwater_table_selection/MWaterCompleteTableSelectComponent.js +275 -0
  50. package/lib/mwater_table_selection/MWaterCustomTablesetListComponent.d.ts +17 -0
  51. package/lib/mwater_table_selection/MWaterCustomTablesetListComponent.js +94 -0
  52. package/lib/mwater_table_selection/MWaterMetricsTableListComponent.d.ts +17 -0
  53. package/lib/mwater_table_selection/MWaterMetricsTableListComponent.js +80 -0
  54. package/lib/mwater_table_selection/MWaterTableSelectComponent.d.ts +32 -0
  55. package/lib/mwater_table_selection/MWaterTableSelectComponent.js +158 -0
  56. package/lib/quickfilter/Quickfilter.d.ts +2 -0
  57. package/lib/quickfilter/QuickfiltersDesignComponent.js +18 -10
  58. package/lib/widgets/charts/Chart.d.ts +11 -0
  59. package/lib/widgets/charts/Chart.js +15 -0
  60. package/lib/widgets/charts/ChartWidgetComponent.d.ts +1 -0
  61. package/lib/widgets/charts/ChartWidgetComponent.js +27 -1
  62. package/lib/widgets/charts/layered/LayeredChartDesign.d.ts +1 -1
  63. package/lib/widgets/charts/layered/LayeredChartDesignerComponent.d.ts +1 -1
  64. package/lib/widgets/charts/layered/LayeredChartDesignerComponent.js +5 -12
  65. package/lib/widgets/charts/layered/LayeredChartLayerDesignerComponent.d.ts +43 -57
  66. package/lib/widgets/charts/layered/LayeredChartLayerDesignerComponent.js +113 -110
  67. package/lib/widgets/charts/layered/LayeredChartUtils.d.ts +2 -1
  68. package/lib/widgets/charts/layered/LayeredChartUtils.js +0 -2
  69. package/lib/widgets/charts/pivot/PivotChart.d.ts +2 -0
  70. package/lib/widgets/charts/pivot/PivotChart.js +156 -0
  71. package/lib/widgets/charts/pivot/PivotChartDesignerComponent.d.ts +5 -20
  72. package/lib/widgets/charts/pivot/PivotChartDesignerComponent.js +31 -61
  73. package/lib/widgets/charts/pivot/PivotChartLayoutBuilder.d.ts +4 -0
  74. package/lib/widgets/charts/pivot/PivotChartLayoutBuilder.js +4 -2
  75. package/lib/widgets/charts/pivot/PivotChartLayoutComponent.d.ts +5 -44
  76. package/lib/widgets/charts/pivot/PivotChartLayoutComponent.js +38 -63
  77. package/lib/widgets/charts/pivot/SegmentDesignerComponent.d.ts +7 -68
  78. package/lib/widgets/charts/pivot/SegmentDesignerComponent.js +58 -106
  79. package/lib/widgets/charts/table/TableChart.d.ts +2 -0
  80. package/lib/widgets/charts/table/TableChart.js +172 -1
  81. package/lib/widgets/charts/table/TableChartDesignerComponent.d.ts +7 -17
  82. package/lib/widgets/charts/table/TableChartDesignerComponent.js +79 -95
  83. package/lib/widgets/charts/table/TableChartViewComponent.d.ts +1 -7
  84. package/lib/widgets/charts/table/TableChartViewComponent.js +19 -27
  85. package/package.json +3 -8
  86. package/src/MWaterContextComponent.tsx +1 -1
  87. package/src/MWaterLoaderComponent.ts +1 -1
  88. package/src/dashboards/DashboardComponent.tsx +2 -1
  89. package/src/dashboards/LayoutOptionsComponent.tsx +22 -10
  90. package/src/dashboards/ServerDashboardDataSource.ts +36 -1
  91. package/src/dashboards/layoutOptions.tsx +5 -1
  92. package/src/datagrids/DatagridComponent.tsx +1 -1
  93. package/src/datagrids/ExprCellComponent.tsx +23 -20
  94. package/src/maps/BufferLayer.ts +35 -20
  95. package/src/maps/ChoroplethLayer.ts +51 -33
  96. package/src/maps/ChoroplethLayerDesign.ts +3 -2
  97. package/src/maps/ChoroplethLayerDesigner.tsx +2 -2
  98. package/src/maps/DirectMapDataSource.ts +21 -1
  99. package/src/maps/EditHoverOver.tsx +91 -51
  100. package/src/maps/HoverContent.tsx +16 -47
  101. package/src/maps/Layer.ts +42 -4
  102. package/src/maps/MWaterServerLayer.ts +6 -6
  103. package/src/maps/MapLayerDataSource.ts +8 -0
  104. package/src/maps/MapUtils.ts +70 -3
  105. package/src/maps/MarkersLayer.ts +34 -24
  106. package/src/maps/RasterMapViewComponent.ts +1 -1
  107. package/src/maps/ServerMapDataSource.ts +35 -0
  108. package/src/maps/VectorMapViewComponent.tsx +6 -6
  109. package/src/maps/maps.ts +4 -2
  110. package/src/mwater_table_selection/FormsListComponent.tsx +188 -0
  111. package/src/mwater_table_selection/IndicatorsListComponent.tsx +283 -0
  112. package/src/mwater_table_selection/IssuesListComponent.tsx +167 -0
  113. package/src/mwater_table_selection/MWaterAccountingSystemListComponent.tsx +225 -0
  114. package/src/{MWaterAssetSystemsListComponent.tsx → mwater_table_selection/MWaterAssetSystemsListComponent.tsx} +2 -2
  115. package/src/mwater_table_selection/MWaterCompleteTableSelectComponent.tsx +377 -0
  116. package/src/{MWaterCustomTablesetListComponent.tsx → mwater_table_selection/MWaterCustomTablesetListComponent.tsx} +1 -1
  117. package/src/{MWaterMetricsTableListComponent.tsx → mwater_table_selection/MWaterMetricsTableListComponent.tsx} +1 -1
  118. package/src/{MWaterTableSelectComponent.tsx → mwater_table_selection/MWaterTableSelectComponent.tsx} +83 -86
  119. package/src/quickfilter/Quickfilter.ts +3 -0
  120. package/src/quickfilter/QuickfiltersDesignComponent.tsx +19 -14
  121. package/src/widgets/charts/Chart.ts +17 -0
  122. package/src/widgets/charts/ChartWidgetComponent.tsx +36 -1
  123. package/src/widgets/charts/layered/LayeredChartDesign.ts +1 -1
  124. package/src/widgets/charts/layered/LayeredChartDesignerComponent.tsx +23 -24
  125. package/src/widgets/charts/layered/LayeredChartLayerDesignerComponent.tsx +260 -211
  126. package/src/widgets/charts/layered/LayeredChartUtils.ts +7 -7
  127. package/src/widgets/charts/pivot/PivotChart.ts +191 -0
  128. package/src/widgets/charts/pivot/PivotChartDesignerComponent.tsx +124 -129
  129. package/src/widgets/charts/pivot/PivotChartLayoutBuilder.ts +4 -2
  130. package/src/widgets/charts/pivot/PivotChartLayoutComponent.tsx +120 -149
  131. package/src/widgets/charts/pivot/SegmentDesignerComponent.tsx +178 -198
  132. package/src/widgets/charts/table/TableChart.ts +177 -1
  133. package/src/widgets/charts/table/TableChartDesignerComponent.tsx +422 -0
  134. package/src/widgets/charts/table/{TableChartViewComponent.ts → TableChartViewComponent.tsx} +65 -60
  135. package/src/MWaterCompleteTableSelectComponent.tsx +0 -975
  136. package/src/widgets/charts/table/TableChartDesignerComponent.ts +0 -441
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MWaterCustomTablesetListComponent = void 0;
7
+ const lodash_1 = __importDefault(require("lodash"));
8
+ const expressions_1 = require("@mwater/expressions");
9
+ const react_1 = require("react");
10
+ const react_2 = __importDefault(require("react"));
11
+ const UIComponents_1 = require("../UIComponents");
12
+ const bootstrap_1 = require("@mwater/react-library/lib/bootstrap");
13
+ /** Searchable list of custom tables */
14
+ const MWaterCustomTablesetListComponent = (props) => {
15
+ const [tablesets, setTablesets] = (0, react_1.useState)();
16
+ const [search, setSearch] = (0, react_1.useState)("");
17
+ const [extraTableNeeded, setExtraTableNeeded] = (0, react_1.useState)();
18
+ const [showSystem, setShowSystem] = (0, react_1.useState)(false);
19
+ // Get list of all tablesets
20
+ (0, react_1.useEffect)(() => {
21
+ fetch(`${props.apiUrl}custom_tablesets?client=${props.client || ""}`)
22
+ .then((response) => response.json())
23
+ .then((tablesets) => {
24
+ // Filter out non-normal
25
+ tablesets = tablesets.filter((ts) => ts.type === "normal");
26
+ // Put included ones first
27
+ setTablesets(lodash_1.default.sortByAll(tablesets, [
28
+ (ts) => (props.extraTables.some((t) => (t || "").startsWith(`custom.${ts.code}.`)) ? 0 : 1),
29
+ (ts) => expressions_1.ExprUtils.localizeString(ts.design.name, props.locale)
30
+ ]));
31
+ });
32
+ }, []);
33
+ (0, react_1.useEffect)(() => {
34
+ if (extraTableNeeded && props.schema.getTable(extraTableNeeded)) {
35
+ props.onChange(extraTableNeeded);
36
+ }
37
+ });
38
+ const selectTable = (ts, tableId) => {
39
+ const qualifiedTableId = `custom.${ts.code}.${tableId}`;
40
+ // If already included, select it
41
+ if (props.schema.getTable(qualifiedTableId)) {
42
+ props.onChange(qualifiedTableId);
43
+ return;
44
+ }
45
+ // Request extra tables as wildcard
46
+ setExtraTableNeeded(qualifiedTableId);
47
+ props.onExtraTableAdd(`custom.${ts.code}.*`);
48
+ };
49
+ const handleRemove = (ts) => {
50
+ // Remove from extra tables
51
+ const match = props.extraTables.find((t) => (t || "").startsWith(`custom.${ts.code}.`));
52
+ if (match) {
53
+ if (confirm(T `Remove this set of tables? Some widgets may not work correctly.`)) {
54
+ props.onChange(null);
55
+ props.onExtraTableRemove(match);
56
+ }
57
+ }
58
+ };
59
+ if (!tablesets || extraTableNeeded) {
60
+ return (react_2.default.createElement("div", null,
61
+ react_2.default.createElement("i", { className: "fa fa-spin fa-spinner" }),
62
+ " ",
63
+ T `Loading...`));
64
+ }
65
+ const renderTableset = (ts) => {
66
+ const name = expressions_1.ExprUtils.localizeString(ts.design.name, props.locale) || "";
67
+ // Check search
68
+ if (search &&
69
+ !name.toLowerCase().includes(search.toLowerCase()) &&
70
+ !ts.design.tables.some((t) => expressions_1.ExprUtils.localizeString(t.name).toLowerCase().includes(search.toLowerCase()))) {
71
+ return null;
72
+ }
73
+ const items = ts.design.tables
74
+ .filter((t) => !t.deprecated)
75
+ .map((t) => ({
76
+ name: expressions_1.ExprUtils.localizeString(t.name, props.locale),
77
+ onClick: () => selectTable(ts, t.id)
78
+ }));
79
+ const alreadyIncluded = props.extraTables.some((t) => (t || "").startsWith(`custom.${ts.code}.`));
80
+ return (react_2.default.createElement("div", { key: ts.code },
81
+ alreadyIncluded ? (react_2.default.createElement("div", { style: { float: "right" } },
82
+ react_2.default.createElement("button", { className: "btn btn-sm btn-link", type: "button", onClick: () => handleRemove(ts) },
83
+ react_2.default.createElement("i", { className: "fa fa-remove" })))) : null,
84
+ react_2.default.createElement("h4", { className: "text-muted" }, name),
85
+ react_2.default.createElement(UIComponents_1.OptionListComponent, { items: items })));
86
+ };
87
+ const visibleTablesets = tablesets.filter((ts) => (showSystem || !ts.design.system) && !ts.design.deprecated);
88
+ return (react_2.default.createElement("div", null,
89
+ react_2.default.createElement(bootstrap_1.TextInput, { value: search, onChange: setSearch, placeholder: T `Search...` }),
90
+ visibleTablesets.map((ts) => renderTableset(ts)),
91
+ react_2.default.createElement("div", null,
92
+ react_2.default.createElement("button", { className: "btn btn-link btn-sm", onClick: () => setShowSystem(!showSystem) }, showSystem ? T `Hide system tables` : T `Show system tables`))));
93
+ };
94
+ exports.MWaterCustomTablesetListComponent = MWaterCustomTablesetListComponent;
@@ -0,0 +1,17 @@
1
+ import { Schema } from "@mwater/expressions";
2
+ import React from "react";
3
+ /** Searchable list of metric tables */
4
+ export declare const MWaterMetricsTableListComponent: (props: {
5
+ apiUrl: string;
6
+ schema: Schema;
7
+ client?: string;
8
+ /** User id */
9
+ user?: string;
10
+ /** Called with table selected */
11
+ onChange: (tableId: string | null) => void;
12
+ extraTables: string[];
13
+ onExtraTableAdd: (tableId: string) => void;
14
+ onExtraTableRemove: (tableId: string) => void;
15
+ /** e.g. "en" */
16
+ locale?: string;
17
+ }) => React.JSX.Element;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MWaterMetricsTableListComponent = void 0;
7
+ const lodash_1 = __importDefault(require("lodash"));
8
+ const expressions_1 = require("@mwater/expressions");
9
+ const react_1 = require("react");
10
+ const react_2 = __importDefault(require("react"));
11
+ const UIComponents_1 = require("../UIComponents");
12
+ const bootstrap_1 = require("@mwater/react-library/lib/bootstrap");
13
+ /** Searchable list of metric tables */
14
+ const MWaterMetricsTableListComponent = (props) => {
15
+ const [metrics, setMetrics] = (0, react_1.useState)();
16
+ const [search, setSearch] = (0, react_1.useState)("");
17
+ const [extraTableNeeded, setExtraTableNeeded] = (0, react_1.useState)();
18
+ // Get list of all metrics
19
+ (0, react_1.useEffect)(() => {
20
+ fetch(`${props.apiUrl}metrics?client=${props.client || ""}`)
21
+ .then((response) => response.json())
22
+ .then((body) => {
23
+ // Put included ones first
24
+ setMetrics(lodash_1.default.sortByAll(body, [
25
+ (m) => (props.extraTables.some((t) => (t = `metrics:${m._id}`)) ? 0 : 1),
26
+ (m) => expressions_1.ExprUtils.localizeString(m.design.name, props.locale)
27
+ ]));
28
+ });
29
+ }, []);
30
+ (0, react_1.useEffect)(() => {
31
+ if (extraTableNeeded && props.schema.getTable(extraTableNeeded)) {
32
+ props.onChange(extraTableNeeded);
33
+ }
34
+ });
35
+ const selectTable = (metric) => {
36
+ const qualifiedTableId = `metrics:${metric._id}`;
37
+ // If already included, select it
38
+ if (props.schema.getTable(qualifiedTableId)) {
39
+ props.onChange(qualifiedTableId);
40
+ return;
41
+ }
42
+ // Request extra tables as wildcard
43
+ setExtraTableNeeded(qualifiedTableId);
44
+ props.onExtraTableAdd(qualifiedTableId);
45
+ };
46
+ const handleRemove = (metric) => {
47
+ // Remove from extra tables
48
+ const match = props.extraTables.find((t) => t == `metrics:${metric._id}`);
49
+ if (match) {
50
+ if (confirm(T `Remove this table? Some widgets may not work correctly.`)) {
51
+ props.onChange(null);
52
+ props.onExtraTableRemove(match);
53
+ }
54
+ }
55
+ };
56
+ if (!metrics || extraTableNeeded) {
57
+ return (react_2.default.createElement("div", null,
58
+ react_2.default.createElement("i", { className: "fa fa-spin fa-spinner" }),
59
+ " ",
60
+ T `Loading...`));
61
+ }
62
+ const renderMetrics = () => {
63
+ const items = metrics
64
+ .filter((m) => !m.design.deprecated)
65
+ .map((m) => {
66
+ const alreadyIncluded = props.extraTables.some((t) => t == `metrics:${m._id}`);
67
+ return {
68
+ name: expressions_1.ExprUtils.localizeString(m.design.name, props.locale) || "",
69
+ onClick: () => selectTable(m),
70
+ onRemove: alreadyIncluded ? handleRemove.bind(null, m) : undefined
71
+ };
72
+ })
73
+ .filter((item) => !search || !item.name.toLowerCase().includes(search.toLowerCase()));
74
+ return react_2.default.createElement(UIComponents_1.OptionListComponent, { items: items });
75
+ };
76
+ return (react_2.default.createElement("div", null,
77
+ react_2.default.createElement(bootstrap_1.TextInput, { value: search, onChange: setSearch, placeholder: T `Search...` }),
78
+ renderMetrics()));
79
+ };
80
+ exports.MWaterMetricsTableListComponent = MWaterMetricsTableListComponent;
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ import { ToggleEditComponent } from "../UIComponents";
3
+ import { Schema } from "@mwater/expressions";
4
+ export interface MWaterTableSelectComponentProps {
5
+ /** Url to hit api */
6
+ apiUrl: string;
7
+ /** Optional client */
8
+ client?: string;
9
+ schema: Schema;
10
+ /** User id */
11
+ user?: string;
12
+ table?: string;
13
+ /** Called with table selected */
14
+ onChange: (table: string | null) => void;
15
+ extraTables: string[];
16
+ onExtraTablesChange: (tables: string[]) => void;
17
+ /** Can also perform filtering for some types. Include these props to enable this */
18
+ filter?: any;
19
+ onFilterChange?: (filter: any) => void;
20
+ }
21
+ interface MWaterTableSelectComponentState {
22
+ pendingExtraTable: string | null;
23
+ }
24
+ export default class MWaterTableSelectComponent extends React.Component<MWaterTableSelectComponentProps, MWaterTableSelectComponentState> {
25
+ toggleEdit: ToggleEditComponent | null;
26
+ constructor(props: MWaterTableSelectComponentProps);
27
+ componentDidUpdate(prevProps: MWaterTableSelectComponentProps): void;
28
+ handleChange: (tableId: string | null) => void;
29
+ handleTableChange: (tableId: string | null) => void;
30
+ render(): React.JSX.Element;
31
+ }
32
+ export {};
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const lodash_1 = __importDefault(require("lodash"));
7
+ const react_1 = __importDefault(require("react"));
8
+ const UIComponents_1 = require("../UIComponents");
9
+ const expressions_1 = require("@mwater/expressions");
10
+ const MWaterResponsesFilterComponent_1 = __importDefault(require("../MWaterResponsesFilterComponent"));
11
+ const ModalPopupComponent_1 = __importDefault(require("@mwater/react-library/lib/ModalPopupComponent"));
12
+ const MWaterCompleteTableSelectComponent_1 = __importDefault(require("./MWaterCompleteTableSelectComponent"));
13
+ const expressions_ui_1 = require("@mwater/expressions-ui");
14
+ // Allows selection of a mwater-visualization table. Loads forms as well and calls event if modified
15
+ class MWaterTableSelectComponent extends react_1.default.Component {
16
+ toggleEdit = null;
17
+ constructor(props) {
18
+ super(props);
19
+ this.state = {
20
+ pendingExtraTable: null // Set when waiting for a table to load
21
+ };
22
+ }
23
+ componentDidUpdate(prevProps) {
24
+ // If received new schema with pending extra table, select it
25
+ let table = null;
26
+ if (this.state.pendingExtraTable) {
27
+ table = this.state.pendingExtraTable;
28
+ if (this.props.schema.getTable(table)) {
29
+ // No longer waiting
30
+ this.setState({ pendingExtraTable: null });
31
+ // Close toggle edit
32
+ this.toggleEdit?.close();
33
+ // Fire change
34
+ this.props.onChange(table);
35
+ }
36
+ }
37
+ // If table is newly selected and is a responses table and no filters, set filters to final only
38
+ if (this.props.table &&
39
+ this.props.table.match(/responses:/) &&
40
+ this.props.table !== prevProps.table &&
41
+ !this.props.filter &&
42
+ this.props.onFilterChange) {
43
+ this.props.onFilterChange({
44
+ type: "op",
45
+ op: "= any",
46
+ table: this.props.table,
47
+ exprs: [
48
+ { type: "field", table: this.props.table, column: "status" },
49
+ { type: "literal", valueType: "enumset", value: ["final"] }
50
+ ]
51
+ });
52
+ }
53
+ }
54
+ handleChange = (tableId) => {
55
+ // Close toggle edit
56
+ this.toggleEdit?.close();
57
+ // Call onChange if different
58
+ if (tableId !== this.props.table) {
59
+ return this.props.onChange(tableId);
60
+ }
61
+ };
62
+ handleTableChange = (tableId) => {
63
+ // If not part of extra tables, add it and wait for new schema
64
+ if (tableId && !this.props.schema.getTable(tableId)) {
65
+ return this.setState({ pendingExtraTable: tableId }, () => {
66
+ return this.props.onExtraTablesChange(lodash_1.default.union(this.props.extraTables, [tableId]));
67
+ });
68
+ }
69
+ else {
70
+ return this.handleChange(tableId);
71
+ }
72
+ };
73
+ render() {
74
+ const editor = react_1.default.createElement(EditModeTableSelectComponent, { apiUrl: this.props.apiUrl, client: this.props.client, schema: this.props.schema, user: this.props.user, table: this.props.table, onChange: this.handleTableChange, extraTables: this.props.extraTables, onExtraTablesChange: this.props.onExtraTablesChange });
75
+ return (react_1.default.createElement("div", null,
76
+ this.state.pendingExtraTable ? (react_1.default.createElement("div", { className: "alert alert-info", key: "pendingExtraTable" },
77
+ react_1.default.createElement("i", { className: "fa fa-spinner fa-spin" }),
78
+ "\u00A0",
79
+ T `Please wait...`)) : undefined,
80
+ react_1.default.createElement(UIComponents_1.ToggleEditComponent, { ref: (c) => {
81
+ this.toggleEdit = c;
82
+ }, forceOpen: !this.props.table, label: this.props.table
83
+ ? expressions_1.ExprUtils.localizeString(this.props.schema.getTable(this.props.table)?.name, T.locale)
84
+ : "", editor: editor, onRemove: () => {
85
+ this.props.onChange(null);
86
+ } }),
87
+ this.props.table &&
88
+ this.props.onFilterChange &&
89
+ this.props.table.match(/^responses:/) &&
90
+ this.props.schema.getTable(this.props.table) ? (react_1.default.createElement(MWaterResponsesFilterComponent_1.default, { schema: this.props.schema, table: this.props.table, filter: this.props.filter, onFilterChange: this.props.onFilterChange })) : undefined));
91
+ }
92
+ }
93
+ exports.default = MWaterTableSelectComponent;
94
+ // Is the table select component when in edit mode. Toggles between complete list and simplified list
95
+ class EditModeTableSelectComponent extends react_1.default.Component {
96
+ constructor(props) {
97
+ super(props);
98
+ this.state = {
99
+ // True when in popup mode that shows all tables
100
+ completeMode: false
101
+ };
102
+ }
103
+ handleShowMore = () => {
104
+ return this.setState({ completeMode: true });
105
+ };
106
+ // Get list of tables that should be included in shortlist
107
+ // This is all active tables and all responses tables in schema (so as to include rosters) and all extra tables
108
+ // Also includes current table
109
+ getTableShortlist(activeTables) {
110
+ let tables = activeTables;
111
+ // Remove dead tables
112
+ tables = tables.filter((t) => this.props.schema.getTable(t) != null && !this.props.schema.getTable(t)?.deprecated);
113
+ tables = lodash_1.default.union(tables, lodash_1.default.filter(lodash_1.default.pluck(this.props.schema.getTables(), "id"), (t) => t.match(/^responses:/)));
114
+ if (this.props.table) {
115
+ tables = lodash_1.default.union(tables, [this.props.table]);
116
+ }
117
+ for (let extraTable of this.props.extraTables || []) {
118
+ // Check if wildcard
119
+ if (extraTable.match(/\*$/)) {
120
+ for (let table of this.props.schema.getTables()) {
121
+ if (table.id.startsWith(extraTable.substr(0, extraTable.length - 1)) && !table.deprecated) {
122
+ tables = lodash_1.default.union(tables, [table.id]);
123
+ }
124
+ }
125
+ }
126
+ else {
127
+ // Add if exists
128
+ if (this.props.schema.getTable(extraTable) && !this.props.schema.getTable(extraTable)?.deprecated) {
129
+ tables = lodash_1.default.union(tables, [extraTable]);
130
+ }
131
+ }
132
+ }
133
+ // Sort by name
134
+ tables = lodash_1.default.sortBy(tables, (tableId) => expressions_1.ExprUtils.localizeString(this.props.schema.getTable(tableId)?.name, T.locale));
135
+ return tables;
136
+ }
137
+ handleCompleteChange = (tableId) => {
138
+ this.setState({ completeMode: false });
139
+ return this.props.onChange(tableId);
140
+ };
141
+ render() {
142
+ return (react_1.default.createElement(expressions_ui_1.ActiveTablesContext.Consumer, null, (activeTables) => (react_1.default.createElement("div", null,
143
+ this.state.completeMode ? (react_1.default.createElement(ModalPopupComponent_1.default, { header: T `Select Data Source`, onClose: () => this.setState({ completeMode: false }), showCloseX: true, size: "x-large" },
144
+ react_1.default.createElement(MWaterCompleteTableSelectComponent_1.default, { apiUrl: this.props.apiUrl, client: this.props.client, schema: this.props.schema, user: this.props.user, table: this.props.table, onChange: this.handleCompleteChange, extraTables: this.props.extraTables, onExtraTablesChange: this.props.onExtraTablesChange }))) : null,
145
+ this.getTableShortlist(activeTables).length > 0 ? (react_1.default.createElement(react_1.default.Fragment, null,
146
+ react_1.default.createElement("div", { className: "text-muted" }, T `Select Data Source:`),
147
+ react_1.default.createElement(UIComponents_1.OptionListComponent, { items: this.getTableShortlist(activeTables).map((tableId) => {
148
+ const table = this.props.schema.getTable(tableId);
149
+ return {
150
+ name: expressions_1.ExprUtils.localizeString(table?.name, T.locale),
151
+ desc: expressions_1.ExprUtils.localizeString(table?.desc, T.locale),
152
+ onClick: () => table && this.props.onChange(table.id)
153
+ };
154
+ }) }),
155
+ react_1.default.createElement("div", null,
156
+ react_1.default.createElement("button", { type: "button", className: "btn btn-link btn-sm", onClick: this.handleShowMore }, T `Show All Available Data Sources...`)))) : (react_1.default.createElement("button", { type: "button", className: "btn btn-link", onClick: this.handleShowMore }, T `Select Data Source...`))))));
157
+ }
158
+ }
@@ -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 edited. */
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 */
@@ -29,6 +29,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.default = QuickfiltersDesignComponent;
30
30
  const lodash_1 = __importDefault(require("lodash"));
31
31
  const react_1 = __importStar(require("react"));
32
+ const uuid_1 = __importDefault(require("uuid"));
32
33
  const immer_1 = require("immer");
33
34
  const expressions_ui_1 = require("@mwater/expressions-ui");
34
35
  const expressions_1 = require("@mwater/expressions");
@@ -36,10 +37,19 @@ const ui = __importStar(require("@mwater/react-library/lib/bootstrap"));
36
37
  const ListEditorComponent_1 = require("@mwater/react-library/lib/ListEditorComponent");
37
38
  // Displays quick filters and allows their value to be modified
38
39
  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);
40
+ // Add ids if not present
41
+ (0, react_1.useEffect)(() => {
42
+ if (props.design.some(qf => !qf.id)) {
43
+ const design = (0, immer_1.produce)(props.design, draft => {
44
+ for (let i = 0; i < draft.length; i++) {
45
+ if (!draft[i].id) {
46
+ draft[i].id = uuid_1.default.v4();
47
+ }
48
+ }
49
+ });
50
+ props.onDesignChange(design);
51
+ }
52
+ }, [props.design]);
43
53
  const handleDesignChange = (design) => {
44
54
  const newDesign = (0, immer_1.produce)(design, draft => {
45
55
  // Update merged flag, clearing if not mergeable
@@ -51,10 +61,6 @@ function QuickfiltersDesignComponent(props) {
51
61
  });
52
62
  props.onDesignChange(newDesign);
53
63
  };
54
- const handleReorder = (design) => {
55
- setReorderCounter(c => c + 1);
56
- handleDesignChange(design);
57
- };
58
64
  // Determine if quickfilter at index is mergeable with previous (same type, id table and enum values)
59
65
  const isMergeable = (design, index) => {
60
66
  if (index === 0) {
@@ -92,7 +98,9 @@ function QuickfiltersDesignComponent(props) {
92
98
  };
93
99
  const handleAdd = () => {
94
100
  // Add blank to end
95
- const design = props.design.concat([{ expr: null }]);
101
+ const design = (0, immer_1.produce)(props.design, draft => {
102
+ draft.push({ expr: null, id: uuid_1.default.v4() });
103
+ });
96
104
  props.onDesignChange(design);
97
105
  };
98
106
  const handleRemove = (index) => {
@@ -101,7 +109,7 @@ function QuickfiltersDesignComponent(props) {
101
109
  props.onDesignChange(design);
102
110
  };
103
111
  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 }),
112
+ react_1.default.createElement(ListEditorComponent_1.ListEditorComponent, { items: props.design, onItemsChange: handleDesignChange, renderItem: renderQuickfilter, getReorderableKey: (item, index) => item.id ?? index }),
105
113
  props.tables.length > 0 ? (react_1.default.createElement("button", { type: "button", className: "btn btn-sm btn-link", onClick: handleAdd },
106
114
  react_1.default.createElement("span", { className: "fas fa-plus me-1" }),
107
115
  T `Add Quick Filter`)) : undefined));
@@ -62,4 +62,15 @@ export default class Chart {
62
62
  getTranslatableStrings(design: any, schema: Schema): string[];
63
63
  /** Translates the design. Override in subclasses. Design is already cleaned. */
64
64
  translateDesign(design: any, translate: (input: string) => string): any;
65
+ /** Determines if the chart supports exporting to XLSX format */
66
+ supportsXlsxExport(): boolean;
67
+ /** Creates a SheetJS workbook for the chart data
68
+ * @param design Chart design
69
+ * @param schema Schema to use
70
+ * @param dataSource Data source
71
+ * @param data Data from chart
72
+ * @param locale Locale to use
73
+ * @returns base64 encoded XLSX file
74
+ */
75
+ createXlsxWorkbook(design: any, schema: Schema, dataSource: DataSource, data: any, locale: string): string;
65
76
  }
@@ -90,5 +90,20 @@ class Chart {
90
90
  translateDesign(design, translate) {
91
91
  return design;
92
92
  }
93
+ /** Determines if the chart supports exporting to XLSX format */
94
+ supportsXlsxExport() {
95
+ return false;
96
+ }
97
+ /** Creates a SheetJS workbook for the chart data
98
+ * @param design Chart design
99
+ * @param schema Schema to use
100
+ * @param dataSource Data source
101
+ * @param data Data from chart
102
+ * @param locale Locale to use
103
+ * @returns base64 encoded XLSX file
104
+ */
105
+ createXlsxWorkbook(design, schema, dataSource, data, locale) {
106
+ throw new Error("Not implemented");
107
+ }
93
108
  }
94
109
  exports.default = Chart;
@@ -44,6 +44,7 @@ export declare class ChartWidgetComponent extends React.PureComponent<ChartWidge
44
44
  static contextType: React.Context<string>;
45
45
  constructor(props: any);
46
46
  handleSaveCsvFile: () => void | AbortController | undefined;
47
+ handleSaveXlsxFile: () => void;
47
48
  handleStartEditing: () => void;
48
49
  handleEndEditing: () => void;
49
50
  handleCancelEditing: () => void;
@@ -69,6 +69,28 @@ class ChartWidgetComponent extends react_1.default.PureComponent {
69
69
  return FileSaver.saveAs(blob, "Exported Data.csv");
70
70
  });
71
71
  };
72
+ // Saves an xlsx file to disk
73
+ handleSaveXlsxFile = () => {
74
+ // Get the data
75
+ this.props.widgetDataSource.getData(this.props.design, this.props.filters, (err, data) => {
76
+ if (err) {
77
+ console.error(err);
78
+ alert(T `Failed to get data: ${err.message}`);
79
+ return;
80
+ }
81
+ try {
82
+ // Create workbook
83
+ const blob = this.props.chart.createXlsxWorkbook(this.props.design, this.props.schema, this.props.dataSource, data, this.context);
84
+ // Require at use as causes server problems
85
+ const FileSaver = require("file-saver");
86
+ FileSaver.saveAs(blob, "Exported Data.xlsx");
87
+ }
88
+ catch (ex) {
89
+ console.error(ex);
90
+ alert(T `Failed to export data: ${ex.message}`);
91
+ }
92
+ });
93
+ };
72
94
  handleStartEditing = () => {
73
95
  // Can't edit if already editing
74
96
  if (this.state.editDesign) {
@@ -149,7 +171,11 @@ class ChartWidgetComponent extends react_1.default.PureComponent {
149
171
  // Create dropdown items
150
172
  const dropdownItems = this.props.chart.createDropdownItems(design, this.props.schema, this.props.widgetDataSource, this.props.filters);
151
173
  if (!designError) {
152
- dropdownItems.push({ label: T `Export Data`, onClick: this.handleSaveCsvFile });
174
+ dropdownItems.push({ label: T `Export Data (CSV)`, onClick: this.handleSaveCsvFile });
175
+ // Add XLSX export option if supported
176
+ if (this.props.chart.supportsXlsxExport()) {
177
+ dropdownItems.push({ label: T `Export Data (XLSX)`, onClick: this.handleSaveXlsxFile });
178
+ }
153
179
  }
154
180
  if (this.props.onDesignChange != null) {
155
181
  dropdownItems.unshift({
@@ -45,7 +45,7 @@ export interface LayeredChartDesign {
45
45
  }
46
46
  export interface LayeredChartDesignLayer {
47
47
  /** bar/line/spline/scatter/area/pie/donut (overrides main one) */
48
- type: "bar" | "line" | "spline" | "scatter" | "area" | "pie" | "donut";
48
+ type?: "bar" | "line" | "spline" | "scatter" | "area" | "pie" | "donut";
49
49
  /** table of layer */
50
50
  table: string;
51
51
  /** label for layer (optional) */
@@ -36,7 +36,7 @@ export default class LayeredChartDesignerComponent extends React.Component<Layer
36
36
  handleXAxisTickMultilineChange: (value: boolean) => void;
37
37
  renderLabels(): React.DetailedReactHTMLElement<React.HTMLAttributes<HTMLElement>, HTMLElement> | undefined;
38
38
  renderChartOptions(): React.JSX.Element;
39
- renderType(): React.CElement<uiComponents.SectionComponentProps, uiComponents.SectionComponent>;
39
+ renderType(): React.JSX.Element;
40
40
  renderLayer: (index: any) => React.DetailedReactHTMLElement<{
41
41
  style: {
42
42
  paddingTop: number;
@@ -154,24 +154,17 @@ class LayeredChartDesignerComponent extends react_1.default.Component {
154
154
  { id: "scatter", name: T `Scatter`, desc: T `Show correlation between two number variables` },
155
155
  { id: "area", name: T `Area`, desc: T `For cumulative data over time` }
156
156
  ];
157
- const current = lodash_1.default.findWhere(chartTypes, { id: this.props.design.type });
158
- return R(uiComponents.SectionComponent, { icon: "glyphicon-th", label: T `Chart Type` }, R(uiComponents.ToggleEditComponent, {
159
- forceOpen: !this.props.design.type,
160
- label: current ? current.name : "",
161
- editor: (onClose) => {
162
- return R(uiComponents.OptionListComponent, {
163
- hint: T `Select a Chart Type`,
164
- items: lodash_1.default.map(chartTypes, ct => ({
157
+ const current = chartTypes.find(ct => ct.id === this.props.design.type);
158
+ return (react_1.default.createElement(uiComponents.SectionComponent, { label: T `Chart Type` },
159
+ react_1.default.createElement(uiComponents.ToggleEditComponent, { forceOpen: !this.props.design.type, label: current ? current.name : "", editor: (onClose) => (react_1.default.createElement(uiComponents.OptionListComponent, { hint: T `Select a Chart Type`, items: chartTypes.map(ct => ({
165
160
  name: ct.name,
166
161
  desc: ct.desc,
167
162
  onClick: () => {
168
163
  onClose(); // Close editor first
169
164
  return this.handleTypeChange(ct.id);
170
165
  }
171
- }))
172
- });
173
- }
174
- }), this.renderOptions());
166
+ })) })) }),
167
+ this.renderOptions()));
175
168
  }
176
169
  renderLayer = (index) => {
177
170
  const style = {