@mwater/visualization 5.4.5 → 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.
- package/.storybook/head.html +0 -1
- package/lib/MWaterContextComponent.js +1 -1
- package/lib/MWaterLoaderComponent.d.ts +2 -2
- package/lib/dashboards/DashboardComponent.js +2 -1
- package/lib/dashboards/LayoutOptionsComponent.js +18 -11
- package/lib/dashboards/ServerDashboardDataSource.d.ts +10 -1
- package/lib/dashboards/ServerDashboardDataSource.js +29 -0
- package/lib/dashboards/layoutOptions.d.ts +5 -1
- package/lib/datagrids/DatagridComponent.js +1 -1
- package/lib/datagrids/ExprCellComponent.d.ts +1 -0
- package/lib/datagrids/ExprCellComponent.js +22 -20
- package/lib/maps/BufferLayer.d.ts +18 -0
- package/lib/maps/BufferLayer.js +24 -14
- package/lib/maps/ChoroplethLayer.d.ts +18 -0
- package/lib/maps/ChoroplethLayer.js +34 -25
- package/lib/maps/ChoroplethLayerDesign.d.ts +3 -2
- package/lib/maps/ChoroplethLayerDesigner.d.ts +11 -1
- package/lib/maps/DirectMapDataSource.js +17 -0
- package/lib/maps/EditHoverOver.d.ts +1 -1
- package/lib/maps/EditHoverOver.js +62 -33
- package/lib/maps/HoverContent.d.ts +10 -5
- package/lib/maps/HoverContent.js +6 -35
- package/lib/maps/Layer.d.ts +37 -0
- package/lib/maps/Layer.js +30 -4
- package/lib/maps/MWaterServerLayer.d.ts +2 -2
- package/lib/maps/MWaterServerLayer.js +6 -6
- package/lib/maps/MapLayerDataSource.d.ts +9 -0
- package/lib/maps/MapUtils.d.ts +19 -1
- package/lib/maps/MapUtils.js +71 -1
- package/lib/maps/MarkersLayer.d.ts +18 -0
- package/lib/maps/MarkersLayer.js +24 -24
- package/lib/maps/MarkersLayerDesignerComponent.d.ts +14 -1
- package/lib/maps/RasterMapViewComponent.js +1 -1
- package/lib/maps/ServerMapDataSource.d.ts +9 -0
- package/lib/maps/ServerMapDataSource.js +29 -0
- package/lib/maps/VectorMapViewComponent.js +6 -6
- package/lib/maps/maps.d.ts +4 -2
- package/lib/mwater_table_selection/FormsListComponent.d.ts +33 -0
- package/lib/mwater_table_selection/FormsListComponent.js +141 -0
- package/lib/mwater_table_selection/IndicatorsListComponent.d.ts +47 -0
- package/lib/mwater_table_selection/IndicatorsListComponent.js +182 -0
- package/lib/mwater_table_selection/IssuesListComponent.d.ts +29 -0
- package/lib/mwater_table_selection/IssuesListComponent.js +123 -0
- package/lib/mwater_table_selection/MWaterAccountingSystemListComponent.d.ts +20 -0
- package/lib/mwater_table_selection/MWaterAccountingSystemListComponent.js +157 -0
- package/lib/mwater_table_selection/MWaterAssetSystemsListComponent.d.ts +17 -0
- package/lib/mwater_table_selection/MWaterAssetSystemsListComponent.js +79 -0
- package/lib/mwater_table_selection/MWaterCompleteTableSelectComponent.d.ts +37 -0
- package/lib/mwater_table_selection/MWaterCompleteTableSelectComponent.js +275 -0
- package/lib/mwater_table_selection/MWaterCustomTablesetListComponent.d.ts +17 -0
- package/lib/mwater_table_selection/MWaterCustomTablesetListComponent.js +94 -0
- package/lib/mwater_table_selection/MWaterMetricsTableListComponent.d.ts +17 -0
- package/lib/mwater_table_selection/MWaterMetricsTableListComponent.js +80 -0
- package/lib/mwater_table_selection/MWaterTableSelectComponent.d.ts +32 -0
- package/lib/mwater_table_selection/MWaterTableSelectComponent.js +158 -0
- package/lib/widgets/charts/Chart.d.ts +11 -0
- package/lib/widgets/charts/Chart.js +15 -0
- package/lib/widgets/charts/ChartWidgetComponent.d.ts +1 -0
- package/lib/widgets/charts/ChartWidgetComponent.js +27 -1
- package/lib/widgets/charts/layered/LayeredChartDesign.d.ts +1 -1
- package/lib/widgets/charts/layered/LayeredChartDesignerComponent.d.ts +1 -1
- package/lib/widgets/charts/layered/LayeredChartDesignerComponent.js +5 -12
- package/lib/widgets/charts/layered/LayeredChartLayerDesignerComponent.d.ts +43 -57
- package/lib/widgets/charts/layered/LayeredChartLayerDesignerComponent.js +113 -110
- package/lib/widgets/charts/layered/LayeredChartUtils.d.ts +2 -1
- package/lib/widgets/charts/layered/LayeredChartUtils.js +0 -2
- package/lib/widgets/charts/pivot/PivotChart.d.ts +2 -0
- package/lib/widgets/charts/pivot/PivotChart.js +156 -0
- package/lib/widgets/charts/pivot/PivotChartDesignerComponent.d.ts +5 -20
- package/lib/widgets/charts/pivot/PivotChartDesignerComponent.js +31 -61
- package/lib/widgets/charts/pivot/PivotChartLayoutBuilder.d.ts +4 -0
- package/lib/widgets/charts/pivot/PivotChartLayoutBuilder.js +4 -2
- package/lib/widgets/charts/pivot/PivotChartLayoutComponent.d.ts +5 -44
- package/lib/widgets/charts/pivot/PivotChartLayoutComponent.js +38 -63
- package/lib/widgets/charts/pivot/SegmentDesignerComponent.d.ts +7 -68
- package/lib/widgets/charts/pivot/SegmentDesignerComponent.js +58 -106
- package/lib/widgets/charts/table/TableChart.d.ts +2 -0
- package/lib/widgets/charts/table/TableChart.js +172 -1
- package/lib/widgets/charts/table/TableChartDesignerComponent.d.ts +7 -17
- package/lib/widgets/charts/table/TableChartDesignerComponent.js +79 -95
- package/lib/widgets/charts/table/TableChartViewComponent.d.ts +1 -7
- package/lib/widgets/charts/table/TableChartViewComponent.js +19 -27
- package/package.json +3 -8
- package/src/MWaterContextComponent.tsx +1 -1
- package/src/MWaterLoaderComponent.ts +1 -1
- package/src/dashboards/DashboardComponent.tsx +2 -1
- package/src/dashboards/LayoutOptionsComponent.tsx +22 -10
- package/src/dashboards/ServerDashboardDataSource.ts +36 -1
- package/src/dashboards/layoutOptions.tsx +5 -1
- package/src/datagrids/DatagridComponent.tsx +1 -1
- package/src/datagrids/ExprCellComponent.tsx +23 -20
- package/src/maps/BufferLayer.ts +35 -20
- package/src/maps/ChoroplethLayer.ts +51 -33
- package/src/maps/ChoroplethLayerDesign.ts +3 -2
- package/src/maps/ChoroplethLayerDesigner.tsx +2 -2
- package/src/maps/DirectMapDataSource.ts +21 -1
- package/src/maps/EditHoverOver.tsx +91 -51
- package/src/maps/HoverContent.tsx +16 -47
- package/src/maps/Layer.ts +42 -4
- package/src/maps/MWaterServerLayer.ts +6 -6
- package/src/maps/MapLayerDataSource.ts +8 -0
- package/src/maps/MapUtils.ts +70 -3
- package/src/maps/MarkersLayer.ts +34 -24
- package/src/maps/RasterMapViewComponent.ts +1 -1
- package/src/maps/ServerMapDataSource.ts +35 -0
- package/src/maps/VectorMapViewComponent.tsx +6 -6
- package/src/maps/maps.ts +4 -2
- package/src/mwater_table_selection/FormsListComponent.tsx +188 -0
- package/src/mwater_table_selection/IndicatorsListComponent.tsx +283 -0
- package/src/mwater_table_selection/IssuesListComponent.tsx +167 -0
- package/src/mwater_table_selection/MWaterAccountingSystemListComponent.tsx +225 -0
- package/src/{MWaterAssetSystemsListComponent.tsx → mwater_table_selection/MWaterAssetSystemsListComponent.tsx} +2 -2
- package/src/mwater_table_selection/MWaterCompleteTableSelectComponent.tsx +377 -0
- package/src/{MWaterCustomTablesetListComponent.tsx → mwater_table_selection/MWaterCustomTablesetListComponent.tsx} +1 -1
- package/src/{MWaterMetricsTableListComponent.tsx → mwater_table_selection/MWaterMetricsTableListComponent.tsx} +1 -1
- package/src/{MWaterTableSelectComponent.tsx → mwater_table_selection/MWaterTableSelectComponent.tsx} +83 -86
- package/src/widgets/charts/Chart.ts +17 -0
- package/src/widgets/charts/ChartWidgetComponent.tsx +36 -1
- package/src/widgets/charts/layered/LayeredChartDesign.ts +1 -1
- package/src/widgets/charts/layered/LayeredChartDesignerComponent.tsx +23 -24
- package/src/widgets/charts/layered/LayeredChartLayerDesignerComponent.tsx +260 -211
- package/src/widgets/charts/layered/LayeredChartUtils.ts +7 -7
- package/src/widgets/charts/pivot/PivotChart.ts +191 -0
- package/src/widgets/charts/pivot/PivotChartDesignerComponent.tsx +124 -129
- package/src/widgets/charts/pivot/PivotChartLayoutBuilder.ts +4 -2
- package/src/widgets/charts/pivot/PivotChartLayoutComponent.tsx +120 -149
- package/src/widgets/charts/pivot/SegmentDesignerComponent.tsx +178 -198
- package/src/widgets/charts/table/TableChart.ts +177 -1
- package/src/widgets/charts/table/TableChartDesignerComponent.tsx +422 -0
- package/src/widgets/charts/table/{TableChartViewComponent.ts → TableChartViewComponent.tsx} +65 -60
- package/src/MWaterCompleteTableSelectComponent.tsx +0 -975
- 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
|
+
{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
|
+
{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
|
+
{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 "
|
|
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 == `
|
|
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),
|