@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,283 @@
1
+ import _ from "lodash"
2
+ import $ from "jquery"
3
+ import React from "react"
4
+ import querystring from "querystring"
5
+ import * as uiComponents from "../UIComponents"
6
+ import { ExprUtils, Schema } from "@mwater/expressions"
7
+ import ModalPopupComponent from "@mwater/react-library/lib/ModalPopupComponent"
8
+
9
+ interface IndicatorsListComponentProps {
10
+ /** Url to hit api */
11
+ apiUrl: string
12
+ /** Optional client */
13
+ client?: string
14
+ schema: Schema
15
+ /** User id */
16
+ user?: string
17
+ /** Called with table selected */
18
+ onChange: any
19
+ extraTables: any
20
+ onExtraTableAdd: any
21
+ onExtraTableRemove: any
22
+ }
23
+
24
+ interface IndicatorsListComponentState {
25
+ error?: any
26
+ search: any
27
+ indicators: any[] | null
28
+ }
29
+
30
+ // Searchable list of indicators
31
+ export class IndicatorsListComponent extends React.Component<IndicatorsListComponentProps, IndicatorsListComponentState> {
32
+ addIndicatorConfirmPopup: AddIndicatorConfirmPopupComponent | null
33
+
34
+ constructor(props: any) {
35
+ super(props)
36
+ this.state = {
37
+ indicators: null,
38
+ search: ""
39
+ }
40
+ }
41
+
42
+ componentDidMount() {
43
+ // Get names and basic of forms
44
+ const query: any = {}
45
+ query.fields = JSON.stringify({ "design.name": 1, "design.desc": 1, "design.recommended": 1, deprecated: 1 })
46
+ query.client = this.props.client
47
+
48
+ // Get list of all indicator names
49
+ return $.getJSON(this.props.apiUrl + "indicators?" + querystring.stringify(query), (indicators: any[]) => {
50
+ // Remove deprecated
51
+ indicators = _.filter(indicators, (indicator) => !indicator.deprecated)
52
+
53
+ // Sort by name
54
+ indicators = _.sortByOrder(
55
+ indicators,
56
+ [
57
+ (indicator: any) => ((this.props.extraTables || []).includes("indicator_values:" + indicator._id) ? 0 : 1),
58
+ (indicator: any) => (indicator.design.recommended ? 0 : 1),
59
+ (indicator: any) => ExprUtils.localizeString(indicator.design.name, T.locale)
60
+ ],
61
+ ["asc", "asc", "asc"]
62
+ )
63
+
64
+ return this.setState({
65
+ indicators: _.map(indicators, (indicator) => ({
66
+ id: indicator._id,
67
+ name: ExprUtils.localizeString(indicator.design.name, T.locale),
68
+ desc: ExprUtils.localizeString(indicator.design.desc, T.locale)
69
+ }))
70
+ })
71
+ }).fail((xhr: any) => {
72
+ return this.setState({ error: xhr.responseText })
73
+ })
74
+ }
75
+
76
+ handleTableRemove = (table: any) => {
77
+ if (
78
+ confirm(
79
+ T`Remove ${ExprUtils.localizeString(
80
+ table.name,
81
+ T.locale
82
+ )}? Any widgets that depend on it will no longer work properly.`
83
+ )
84
+ ) {
85
+ return this.props.onExtraTableRemove(table.id)
86
+ }
87
+ }
88
+
89
+ searchRef = (comp: any) => {
90
+ // Focus
91
+ if (comp) {
92
+ return comp.focus()
93
+ }
94
+ }
95
+
96
+ handleSelect = (tableId: any) => {
97
+ // Add table if not present
98
+ if (!this.props.schema.getTable(tableId)) {
99
+ this.props.onExtraTableAdd(tableId)
100
+ }
101
+
102
+ this.addIndicatorConfirmPopup!.show(tableId)
103
+ }
104
+
105
+ render() {
106
+ let indicators
107
+ if (this.state.error) {
108
+ return <div className="alert alert-danger">{this.state.error}</div>
109
+ }
110
+
111
+ // Filter indicators
112
+ if (this.state.search) {
113
+ const escapeRegExp = (s: any) => s.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&")
114
+
115
+ const searchStringRegExp = new RegExp(escapeRegExp(this.state.search), "i")
116
+
117
+ indicators = _.filter(this.state.indicators || [], (indicator) => indicator.name.match(searchStringRegExp))
118
+ } else {
119
+ ;({ indicators } = this.state)
120
+ }
121
+
122
+ // Remove if already included
123
+ indicators = _.filter(indicators || [], (f) => !(this.props.extraTables || []).includes(`indicator_values:${f.id}`))
124
+
125
+ let tables = _.filter(
126
+ this.props.schema.getTables(),
127
+ (table) => table.id.match(/^indicator_values:/) && !table.deprecated
128
+ )
129
+ tables = _.sortBy(tables, (t) => t.name.en)
130
+
131
+ return (
132
+ <div>
133
+ <AddIndicatorConfirmPopupComponent
134
+ schema={this.props.schema}
135
+ onChange={this.props.onChange}
136
+ onExtraTableAdd={this.props.onExtraTableAdd}
137
+ ref={(c: AddIndicatorConfirmPopupComponent | null) => {
138
+ this.addIndicatorConfirmPopup = c
139
+ }}
140
+ />
141
+
142
+ <label>{T`Included Indicators:`}</label>
143
+ {tables.length > 0 ? (
144
+ <uiComponents.OptionListComponent
145
+ items={_.map(tables, (table) => {
146
+ return {
147
+ name: ExprUtils.localizeString(table.name, T.locale),
148
+ desc: ExprUtils.localizeString(table.desc, T.locale),
149
+ onClick: this.handleSelect.bind(null, table.id),
150
+ onRemove: this.handleTableRemove.bind(null, table)
151
+ }
152
+ })}
153
+ />
154
+ ) : (
155
+ <div>{T`None`}</div>
156
+ )}
157
+
158
+ <br />
159
+
160
+ <label>{T`All Indicators:`}</label>
161
+ {!this.state.indicators || this.state.indicators.length === 0 ? (
162
+ <div className="alert alert-info">
163
+ <i className="fa fa-spinner fa-spin" />
164
+ &nbsp;{T`Loading...`}
165
+ </div>
166
+ ) : (
167
+ <>
168
+ <input
169
+ type="text"
170
+ className="form-control form-control-sm"
171
+ placeholder={T`Search...`}
172
+ key="search"
173
+ ref={this.searchRef}
174
+ style={{ maxWidth: "20em", marginBottom: 10 }}
175
+ value={this.state.search}
176
+ onChange={(ev: any) => this.setState({ search: ev.target.value })}
177
+ />
178
+
179
+ <uiComponents.OptionListComponent
180
+ items={_.map(indicators, (indicator) => ({
181
+ name: indicator.name,
182
+ desc: indicator.desc,
183
+ onClick: this.handleSelect.bind(null, "indicator_values:" + indicator.id)
184
+ }))}
185
+ />
186
+ </>
187
+ )}
188
+ </div>
189
+ )
190
+ }
191
+ }
192
+
193
+ interface AddIndicatorConfirmPopupComponentProps {
194
+ schema: Schema
195
+ /** Called with table selected */
196
+ onChange: any
197
+ onExtraTableAdd: any
198
+ }
199
+
200
+ interface AddIndicatorConfirmPopupComponentState {
201
+ indicatorTable: any
202
+ visible: any
203
+ }
204
+
205
+ class AddIndicatorConfirmPopupComponent extends React.Component<
206
+ AddIndicatorConfirmPopupComponentProps,
207
+ AddIndicatorConfirmPopupComponentState
208
+ > {
209
+ constructor(props: any) {
210
+ super(props)
211
+ this.state = {
212
+ visible: false,
213
+ indicatorTable: null
214
+ }
215
+ }
216
+
217
+ show(indicatorTable: any) {
218
+ return this.setState({ visible: true, indicatorTable })
219
+ }
220
+
221
+ renderContents() {
222
+ // Show loading if table not loaded
223
+ if (!this.props.schema.getTable(this.state.indicatorTable)) {
224
+ return (
225
+ <div className="alert alert-info">
226
+ <i className="fa fa-spinner fa-spin" />
227
+ &nbsp;{T`Loading...`}
228
+ </div>
229
+ )
230
+ }
231
+
232
+ // Find entity links
233
+ const entityColumns = _.filter(this.props.schema.getColumns(this.state.indicatorTable), (col) =>
234
+ col.join?.toTable?.match(/^entities\./)
235
+ )
236
+
237
+ return (
238
+ <div>
239
+ <p>
240
+ {T`In general, it is better to get indicator values from the related site. Please select the site
241
+ below, then find the indicator values in the 'Related Indicators' section. Or click on 'Use Raw Indicator' if you
242
+ are certain that you want to use the raw indicator table`}
243
+ </p>
244
+
245
+ <uiComponents.OptionListComponent
246
+ items={_.map(entityColumns, (entityColumn) => ({
247
+ name: ExprUtils.localizeString(entityColumn.name, T.locale),
248
+ desc: ExprUtils.localizeString(entityColumn.desc, T.locale),
249
+ onClick: () => {
250
+ // Select table
251
+ this.props.onChange(entityColumn.join!.toTable)
252
+ return this.setState({ visible: false })
253
+ }
254
+ }))}
255
+ />
256
+
257
+ <br />
258
+
259
+ <div>
260
+ <a className="link-plain" onClick={this.props.onChange.bind(null, this.state.indicatorTable)}>
261
+ {T`Use Raw Indicator`}
262
+ </a>
263
+ </div>
264
+ </div>
265
+ )
266
+ }
267
+
268
+ render() {
269
+ if (!this.state.visible) {
270
+ return null
271
+ }
272
+
273
+ return (
274
+ <ModalPopupComponent
275
+ showCloseX={true}
276
+ onClose={() => this.setState({ visible: false })}
277
+ header={T`Add Indicator`}
278
+ >
279
+ {this.renderContents()}
280
+ </ModalPopupComponent>
281
+ )
282
+ }
283
+ }
@@ -0,0 +1,167 @@
1
+ import _ from "lodash"
2
+ import $ from "jquery"
3
+ import React from "react"
4
+ import querystring from "querystring"
5
+ import * as uiComponents from "../UIComponents"
6
+ import { ExprUtils, Schema } from "@mwater/expressions"
7
+
8
+ interface IssuesListComponentProps {
9
+ /** Url to hit api */
10
+ apiUrl: string
11
+ /** Optional client */
12
+ client?: string
13
+ schema: Schema
14
+ /** User id */
15
+ user?: string
16
+ /** Called with table selected */
17
+ onChange: any
18
+ extraTables: any
19
+ onExtraTableAdd: any
20
+ onExtraTableRemove: any
21
+ }
22
+
23
+ interface IssuesListComponentState {
24
+ error?: any
25
+ search: any
26
+ issueTypes: any[] | null
27
+ }
28
+
29
+ // Searchable list of issue types
30
+ export class IssuesListComponent extends React.Component<IssuesListComponentProps, IssuesListComponentState> {
31
+ constructor(props: any) {
32
+ super(props)
33
+ this.state = {
34
+ issueTypes: null,
35
+ search: ""
36
+ }
37
+ }
38
+
39
+ componentDidMount() {
40
+ // Get names and basic of issueTypes
41
+ const query: any = {}
42
+ query.fields = JSON.stringify({ name: 1, desc: 1, roles: 1, created: 1, modified: 1 })
43
+ query.client = this.props.client
44
+
45
+ // Get list of all issueType names
46
+ return $.getJSON(this.props.apiUrl + "issue_types?" + querystring.stringify(query), (issueTypes: any[]) => {
47
+ // Sort by modified.on desc but first by user
48
+ issueTypes = _.sortByOrder(
49
+ issueTypes,
50
+ [
51
+ (issueType) => ((this.props.extraTables || []).includes("issues:" + issueType._id) ? 0 : 1),
52
+ (issueType) => (issueType.created.by === this.props.user ? 0 : 1),
53
+ (issueType) => ExprUtils.localizeString(issueType.name, T.locale)
54
+ ],
55
+ ["asc", "asc", "asc"]
56
+ )
57
+
58
+ return this.setState({
59
+ issueTypes: _.map(issueTypes, (issueType) => ({
60
+ id: issueType._id,
61
+ name: ExprUtils.localizeString(issueType.name, T.locale),
62
+ desc: ExprUtils.localizeString(issueType.desc, T.locale)
63
+ }))
64
+ })
65
+ }).fail((xhr: any) => {
66
+ return this.setState({ error: xhr.responseText })
67
+ })
68
+ }
69
+
70
+ handleTableRemove = (table: any) => {
71
+ if (
72
+ confirm(
73
+ T`Remove ${ExprUtils.localizeString(
74
+ table.name,
75
+ T.locale
76
+ )}? Any widgets that depend on it will no longer work properly.`
77
+ )
78
+ ) {
79
+ return this.props.onExtraTableRemove(table.id)
80
+ }
81
+ }
82
+
83
+ searchRef = (comp: any) => {
84
+ // Focus
85
+ if (comp) {
86
+ return comp.focus()
87
+ }
88
+ }
89
+
90
+ render() {
91
+ let issueTypes
92
+ if (this.state.error) {
93
+ return <div className="alert alert-danger">{this.state.error}</div>
94
+ }
95
+
96
+ // Filter issueTypes
97
+ if (this.state.search) {
98
+ const escapeRegExp = (s: any) => s.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&")
99
+
100
+ const searchStringRegExp = new RegExp(escapeRegExp(this.state.search), "i")
101
+
102
+ issueTypes = _.filter(this.state.issueTypes || [], (issueType) => issueType.name.match(searchStringRegExp))
103
+ } else {
104
+ ;({ issueTypes } = this.state)
105
+ }
106
+
107
+ // Remove if already included
108
+ issueTypes = _.filter(issueTypes || [], (f) => !(this.props.extraTables || []).includes(`issues:${f.id}`))
109
+
110
+ let tables = _.filter(
111
+ this.props.schema.getTables(),
112
+ (table) => (table.id.match(/^issues:/) || table.id.match(/^issue_events:/)) && !table.deprecated
113
+ )
114
+ tables = _.sortBy(tables, (t) => t.name.en)
115
+
116
+ return (
117
+ <div>
118
+ <label>{T`Included Issues:`}</label>
119
+ {tables.length > 0 ? (
120
+ <uiComponents.OptionListComponent
121
+ items={_.map(tables, (table) => {
122
+ return {
123
+ name: ExprUtils.localizeString(table.name, T.locale),
124
+ desc: ExprUtils.localizeString(table.desc, T.locale),
125
+ onClick: this.props.onChange.bind(null, table.id),
126
+ onRemove: this.handleTableRemove.bind(null, table)
127
+ }
128
+ })}
129
+ />
130
+ ) : (
131
+ <div>{T`None`}</div>
132
+ )}
133
+
134
+ <br />
135
+
136
+ <label>{T`All Issues:`}</label>
137
+ {!this.state.issueTypes || this.state.issueTypes.length === 0 ? (
138
+ <div className="alert alert-info">
139
+ <i className="fa fa-spinner fa-spin" />
140
+ &nbsp;{T`Loading...`}
141
+ </div>
142
+ ) : (
143
+ <>
144
+ <input
145
+ type="text"
146
+ className="form-control form-control-sm"
147
+ placeholder={T`Search...`}
148
+ key="search"
149
+ ref={this.searchRef}
150
+ style={{ maxWidth: "20em", marginBottom: 10 }}
151
+ value={this.state.search}
152
+ onChange={(ev: any) => this.setState({ search: ev.target.value })}
153
+ />
154
+
155
+ <uiComponents.OptionListComponent
156
+ items={_.map(issueTypes, (issueType) => ({
157
+ name: issueType.name,
158
+ desc: issueType.desc,
159
+ onClick: this.props.onChange.bind(null, "issues:" + issueType.id)
160
+ }))}
161
+ />
162
+ </>
163
+ )}
164
+ </div>
165
+ )
166
+ }
167
+ }
@@ -0,0 +1,225 @@
1
+ import _ from "lodash"
2
+ import { Schema, LocalizedString, ExprUtils } from "@mwater/expressions"
3
+ import { CustomTablesetDesign, CustomTable } from "@mwater/common/lib/tables/custom_tablesets"
4
+ import { Partition as AccountingSystemPartitionType } from "@mwater/common/lib/accounting/accountingTypes"
5
+ import { useState, useEffect, useMemo } from "react"
6
+ import React from "react"
7
+ import { OptionListComponent } from "../UIComponents"
8
+ import { TextInput } from "@mwater/react-library/lib/bootstrap"
9
+
10
+ export interface MWaterAccountingSystemListComponentProps {
11
+ apiUrl: string
12
+ schema: Schema
13
+ client?: string
14
+ /** User id (not currently used but good for consistency) */
15
+ user?: string
16
+ /** Called with table selected, e.g. "custom.accounting:123.accounts" */
17
+ onChange: (tableId: string | null) => void
18
+ /** Current list of extra table wildcards, e.g. ["accounting:123.*"] */
19
+ extraTables: string[]
20
+ /** Called to request loading tables for an accounting system, e.g. "accounting:123.*" */
21
+ onExtraTableAdd: (wildcardTableId: string) => void
22
+ /** Called to request unloading tables for an accounting system */
23
+ onExtraTableRemove: (wildcardTableId: string) => void
24
+ /** e.g. "en" */
25
+ locale?: string
26
+ }
27
+
28
+ export function MWaterAccountingSystemListComponent(props: MWaterAccountingSystemListComponentProps) {
29
+ const {
30
+ apiUrl,
31
+ schema,
32
+ client,
33
+ onChange,
34
+ extraTables,
35
+ onExtraTableAdd,
36
+ onExtraTableRemove,
37
+ locale
38
+ } = props
39
+
40
+ const [rawAccountingSystems, setRawAccountingSystems] = useState<AccountingSystemPartitionType[]>()
41
+ const [accountingTablesetDesign, setAccountingTablesetDesign] = useState<CustomTablesetDesign>()
42
+ const [search, setSearch] = useState<string>("")
43
+ // Stores the fully qualified table ID that we want to select after its system is loaded
44
+ const [targetSelection, setTargetSelection] = useState<string>()
45
+
46
+ // Fetch accounting systems (partitions) and the main accounting tableset design
47
+ useEffect(() => {
48
+ // Fetch rows from the 'custom.accounting.partitions' table
49
+ fetch(`${apiUrl}custom/accounting/partitions?client=${client || ""}`)
50
+ .then((response) => {
51
+ if (!response.ok) {
52
+ console.error(`Error fetching accounting system partitions: ${response.status} ${response.statusText}`)
53
+ return [] // Return empty array on error
54
+ }
55
+ return response.json()
56
+ })
57
+ .then((systems: AccountingSystemPartitionType[]) => {
58
+ setRawAccountingSystems(systems)
59
+ })
60
+ .catch(error => {
61
+ console.error("Failed to load accounting systems:", error)
62
+ setRawAccountingSystems([])
63
+ })
64
+
65
+ // Fetch the design of the 'custom.accounting' tableset
66
+ const filter = encodeURIComponent(JSON.stringify({ code: "accounting" }))
67
+ fetch(`${apiUrl}custom_tablesets?filter=${filter}&client=${client || ""}`)
68
+ .then((response) => {
69
+ if (!response.ok) {
70
+ console.error(`Error fetching accounting tableset design: ${response.status} ${response.statusText}`)
71
+ return null
72
+ }
73
+ return response.json()
74
+ })
75
+ .then((tablesets: { design: CustomTablesetDesign }[] | null) => {
76
+ if (tablesets && tablesets.length > 0 && tablesets[0].design) {
77
+ setAccountingTablesetDesign(tablesets[0].design)
78
+ } else {
79
+ console.error("Accounting tableset design not found or invalid structure.")
80
+ }
81
+ })
82
+ .catch(error => {
83
+ console.error("Failed to load accounting tableset design:", error)
84
+ })
85
+ }, [apiUrl, client])
86
+
87
+ const sortedAccountingSystems = useMemo(() => {
88
+ if (!rawAccountingSystems) {
89
+ return undefined
90
+ }
91
+ return _.sortByAll(
92
+ rawAccountingSystems,
93
+ [
94
+ (sys: AccountingSystemPartitionType) => {
95
+ const wildcard = `custom.accounting:${String(sys._partition)}.*`
96
+ return !extraTables.includes(wildcard) // false (0) for included, true (1) for not
97
+ },
98
+ (sys: AccountingSystemPartitionType) => sys.name
99
+ ]
100
+ )
101
+ }, [rawAccountingSystems, extraTables])
102
+
103
+ // Effect to handle selecting a table once its accounting system's tables are loaded into the schema
104
+ useEffect(() => {
105
+ if (targetSelection && schema.getTable(targetSelection)) {
106
+ onChange(targetSelection)
107
+ setTargetSelection(undefined) // Clear the target selection
108
+ }
109
+ }, [targetSelection, schema, onChange])
110
+
111
+ const handleSelectTable = (partitionId: string | number, table: CustomTable) => {
112
+ const qualifiedTableId = `custom.accounting:${String(partitionId)}.${table.id}`
113
+ const accountingSystemWildcard = `custom.accounting:${String(partitionId)}.*`
114
+
115
+ if (!extraTables.includes(accountingSystemWildcard)) {
116
+ onExtraTableAdd(accountingSystemWildcard)
117
+ }
118
+
119
+ if (schema.getTable(qualifiedTableId)) {
120
+ onChange(qualifiedTableId)
121
+ } else {
122
+ // If not yet in schema, ensure add request is made and set for future selection
123
+ if (!extraTables.includes(accountingSystemWildcard)) {
124
+ onExtraTableAdd(accountingSystemWildcard)
125
+ }
126
+ setTargetSelection(qualifiedTableId)
127
+ }
128
+ }
129
+
130
+ const handleRemoveAccountingSystem = (partitionId: string | number) => {
131
+ const accountingSystemWildcard = `custom.accounting:${String(partitionId)}.*`
132
+ if (confirm(T`Remove this accounting system? Some widgets may not work correctly.`)) {
133
+ onExtraTableRemove(accountingSystemWildcard)
134
+ }
135
+ }
136
+
137
+ if (!sortedAccountingSystems || !accountingTablesetDesign) {
138
+ return (
139
+ <div>
140
+ <i className="fa fa-spin fa-spinner" /> {T`Loading accounting data...`}
141
+ </div>
142
+ )
143
+ }
144
+
145
+ const filteredSystems = sortedAccountingSystems.filter(system => {
146
+ if (search === "") {
147
+ return true
148
+ }
149
+ const systemName = ExprUtils.localizeString(system.name, locale) || ""
150
+ if (systemName.toLowerCase().includes(search.toLowerCase())) {
151
+ return true
152
+ }
153
+
154
+ return accountingTablesetDesign.tables.some(t =>
155
+ !t.deprecated && (ExprUtils.localizeString(t.name, locale) || "").toLowerCase().includes(search.toLowerCase())
156
+ )
157
+ })
158
+
159
+ const renderSingleAccountingSystem = (system: AccountingSystemPartitionType) => {
160
+ const systemName = ExprUtils.localizeString(system.name, locale) || T`Unnamed System (${system._partition})`
161
+ const accountingSystemWildcard = `custom.accounting:${String(system._partition)}.*`
162
+ const isIncluded = extraTables.includes(accountingSystemWildcard)
163
+
164
+ const tablesForThisSystem = accountingTablesetDesign.tables
165
+ .filter(t => !t.deprecated)
166
+ .filter(t => t.id != "partitions" && t.id != "roles")
167
+ .filter(t => {
168
+ if (search === "") {
169
+ return true
170
+ }
171
+ if ((ExprUtils.localizeString(system.name, locale) || "").toLowerCase().includes(search.toLowerCase())) {
172
+ return true
173
+ }
174
+ return (ExprUtils.localizeString(t.name, locale) || "").toLowerCase().includes(search.toLowerCase())
175
+ })
176
+ .map(t => ({
177
+ name: ExprUtils.localizeString(t.name, locale)!,
178
+ onClick: () => handleSelectTable(system._partition, t)
179
+ }))
180
+
181
+ if (search !== "" && !(ExprUtils.localizeString(system.name, locale) || "").toLowerCase().includes(search.toLowerCase()) && tablesForThisSystem.length === 0) {
182
+ return null
183
+ }
184
+
185
+ return (
186
+ <div key={system._partition} className="mt-3">
187
+ <div className="d-flex justify-content-between align-items-center">
188
+ <h4 className="text-muted mb-1">{systemName}</h4>
189
+ {isIncluded && (
190
+ <button
191
+ className="btn btn-sm btn-link"
192
+ type="button"
193
+ onClick={() => handleRemoveAccountingSystem(system._partition)}
194
+ title={T`Remove this accounting system`}
195
+ >
196
+ <i className="fa fa-times" />
197
+ </button>
198
+ )}
199
+ </div>
200
+ <OptionListComponent items={tablesForThisSystem} />
201
+ </div>
202
+ )
203
+ }
204
+
205
+ return (
206
+ <div>
207
+ <TextInput
208
+ value={search}
209
+ onChange={setSearch}
210
+ placeholder={T`Search accounting systems or tables...`}
211
+ />
212
+ {targetSelection && !schema.getTable(targetSelection) && (
213
+ <div className="alert alert-info my-2">
214
+ <i className="fa fa-spinner fa-spin me-2" />
215
+ {T`Loading tables for selected accounting system...`}
216
+ </div>
217
+ )}
218
+ {filteredSystems.length > 0
219
+ ? filteredSystems.map(system => renderSingleAccountingSystem(system))
220
+ : (sortedAccountingSystems.length > 0 && search !== "" && <p>{T`No accounting systems or tables match your search.`}</p>)
221
+ }
222
+ {sortedAccountingSystems.length === 0 && !targetSelection && <p>{T`No accounting systems found.`}</p>}
223
+ </div>
224
+ )
225
+ }
@@ -2,7 +2,7 @@ import _ from "lodash"
2
2
  import { Schema, LocalizedString, Table, ExprUtils } from "@mwater/expressions"
3
3
  import { useState, useEffect } from "react"
4
4
  import React from "react"
5
- import { OptionListComponent } from "./UIComponents"
5
+ import { OptionListComponent } from "../UIComponents"
6
6
  import { TextInput } from "@mwater/react-library/lib/bootstrap"
7
7
 
8
8
  /** Searchable list of asset system tables */
@@ -84,7 +84,7 @@ export const MWaterAssetSystemsListComponent = (props: {
84
84
  const renderAssetSystems = () => {
85
85
  const items = systems
86
86
  .map((m) => {
87
- const alreadyIncluded = props.extraTables.some((t) => t == `systems:${m.sid}`)
87
+ const alreadyIncluded = props.extraTables.some((t) => t == `assets:${m.sid}`)
88
88
  return {
89
89
  name: ExprUtils.localizeString(m.design.name, props.locale) || "",
90
90
  onClick: () => selectTable(m),