@isoftdata/utility-dashboard-backend 1.3.0 → 1.4.1
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/README.md +164 -164
- package/chart-helper.js +254 -254
- package/formatter-templates.json +14 -8
- package/index.js +208 -208
- package/package.json +19 -19
- package/schema.md +120 -120
package/chart-helper.js
CHANGED
|
@@ -1,254 +1,254 @@
|
|
|
1
|
-
const { formatterTemplates } = require('./formatter-templates.json')
|
|
2
|
-
const toSnakeCase = require('to-snake-case')
|
|
3
|
-
const mysqlDataTypes = require('mysql/lib/protocol/constants/types')
|
|
4
|
-
const klona = require('klona')
|
|
5
|
-
|
|
6
|
-
const mysqlToGoogleChartsDataTypeMap = mysqlDataType => {
|
|
7
|
-
switch (mysqlDataType) {
|
|
8
|
-
case 'VAR_STRING':
|
|
9
|
-
case 'STRING':
|
|
10
|
-
case 'ENUM':
|
|
11
|
-
case 'VARCHAR':
|
|
12
|
-
return 'string'
|
|
13
|
-
case 'DECIMAL':
|
|
14
|
-
case 'NEWDECIMAL':
|
|
15
|
-
case 'BIT':
|
|
16
|
-
case 'TINY':
|
|
17
|
-
case 'SHORT':
|
|
18
|
-
case 'LONG':
|
|
19
|
-
case 'FLOAT':
|
|
20
|
-
case 'DOUBLE':
|
|
21
|
-
case 'LONGLONG':
|
|
22
|
-
case 'INT24':
|
|
23
|
-
case 'YEAR':
|
|
24
|
-
return 'number'
|
|
25
|
-
case 'TIMESTAMP':
|
|
26
|
-
case 'DATETIME':
|
|
27
|
-
case 'TIMESTAMP2':
|
|
28
|
-
case 'DATETIME2':
|
|
29
|
-
return 'datetime'
|
|
30
|
-
case 'DATE':
|
|
31
|
-
case 'NEWDATE':
|
|
32
|
-
return 'date'
|
|
33
|
-
case 'TIME':
|
|
34
|
-
case 'TIME2':
|
|
35
|
-
return 'timeofday'
|
|
36
|
-
default:
|
|
37
|
-
return 'string'
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const makeDataTableCol = (key, fields, role = null) => {
|
|
42
|
-
return {
|
|
43
|
-
id: toSnakeCase(key).toLowerCase(),
|
|
44
|
-
label: key,
|
|
45
|
-
type: mysqlToGoogleChartsDataTypeMap(fields[key]),
|
|
46
|
-
role,
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
module.exports = {
|
|
51
|
-
queryWithFields: (connection, query) => { // the only function here that talks to the server at all
|
|
52
|
-
return new Promise((resolve, reject) => {
|
|
53
|
-
connection.query(query, (err, results, fields) => {
|
|
54
|
-
if (err) {
|
|
55
|
-
reject(err)
|
|
56
|
-
} else {
|
|
57
|
-
resolve({
|
|
58
|
-
results,
|
|
59
|
-
fields: fields.reduce((sum, field) => {
|
|
60
|
-
return {
|
|
61
|
-
...sum,
|
|
62
|
-
[field.name]: mysqlDataTypes[field.type] || field.type,
|
|
63
|
-
}
|
|
64
|
-
}, {}),
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
})
|
|
69
|
-
},
|
|
70
|
-
getMultiSeriesData: ({ data: queryResult, seriesOptions, formatting, fields }) => {
|
|
71
|
-
const { groupXAxisBy, series, value, tooltip } = seriesOptions
|
|
72
|
-
//Get an array of all of the keys for the series
|
|
73
|
-
const allOfTheKeysInTheSeries = new Set(queryResult.map(row => row[series]))
|
|
74
|
-
|
|
75
|
-
let objectOfAllKeysWithNullValues = {}
|
|
76
|
-
for (const key of allOfTheKeysInTheSeries) {
|
|
77
|
-
objectOfAllKeysWithNullValues[key] = null
|
|
78
|
-
//add in a spot for the tooltips for each column to go
|
|
79
|
-
if (tooltip) {
|
|
80
|
-
objectOfAllKeysWithNullValues[`${tooltip} ${key}`] = null
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
let grouped = {}
|
|
85
|
-
|
|
86
|
-
//create objects for each thing they're grouping by
|
|
87
|
-
// console.time(`create objects for each thing they're grouping by`)
|
|
88
|
-
for (const row of queryResult) {
|
|
89
|
-
//if we don't yet have a prop for what we're grouping by, then we need to initialize an object of all null values for it
|
|
90
|
-
if (!grouped[row[groupXAxisBy]]) {
|
|
91
|
-
grouped[row[groupXAxisBy]] = klona(objectOfAllKeysWithNullValues) //give nulls for all values by default
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
grouped[row[groupXAxisBy]][row[series]] = row[value]
|
|
95
|
-
// add the tooltips for each series in each row
|
|
96
|
-
if (tooltip) {
|
|
97
|
-
grouped[row[groupXAxisBy]][`${tooltip} ${row[series]}`] = row[tooltip]
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
// console.timeEnd(`create objects for each thing they're grouping by`)
|
|
101
|
-
|
|
102
|
-
//The mysql field types need to map to our output data
|
|
103
|
-
let seriesFields = { [groupXAxisBy]: fields[groupXAxisBy] }
|
|
104
|
-
let seriesFormatting = {}
|
|
105
|
-
for (const key of allOfTheKeysInTheSeries) {
|
|
106
|
-
seriesFields[key] = fields[value]
|
|
107
|
-
//add tooltips here, so a tooltip column is created for every series column
|
|
108
|
-
if (tooltip) {
|
|
109
|
-
seriesFields[`${tooltip} ${key}`] = fields[tooltip]
|
|
110
|
-
}
|
|
111
|
-
//If there is any formatting applied to the value, we need to apply it to all of the other keys
|
|
112
|
-
if (formatting && value in formatting) {
|
|
113
|
-
seriesFormatting[key] = formatting[value]
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
//turn it back to an array and make sure the grouping column is first as required by a pie chart(at least)
|
|
118
|
-
// console.time(`group it back together`)
|
|
119
|
-
let multiSeriesData = []
|
|
120
|
-
for (const key in grouped) {
|
|
121
|
-
multiSeriesData.push({
|
|
122
|
-
[groupXAxisBy]: key,
|
|
123
|
-
...grouped[key],
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
// console.timeEnd(`group it back together`)
|
|
127
|
-
// console.log(`${multiSeriesData.length} rows with ${Object.keys(multiSeriesData[0]).length} props each.`)
|
|
128
|
-
return {
|
|
129
|
-
data: multiSeriesData,
|
|
130
|
-
formatting: {
|
|
131
|
-
...formatting,
|
|
132
|
-
...seriesFormatting,
|
|
133
|
-
},
|
|
134
|
-
fields: seriesFields,
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
makeNegativeNumbericValuesZero: resultSet => {
|
|
138
|
-
const numberCols = Object.keys(resultSet[0]).filter(key => typeof resultSet[0][key] === 'number')
|
|
139
|
-
|
|
140
|
-
return resultSet.map(row => {
|
|
141
|
-
numberCols.forEach(col => {
|
|
142
|
-
if (row[col] < 0) {
|
|
143
|
-
row[col] = 0
|
|
144
|
-
}
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
return row
|
|
148
|
-
})
|
|
149
|
-
},
|
|
150
|
-
coerceNullColumn: (columnProp, data, destinationType) => {
|
|
151
|
-
const hasNullValue = data.find(row => row[columnProp] === null)
|
|
152
|
-
|
|
153
|
-
if (hasNullValue) {
|
|
154
|
-
const rowWithValidValue = data.find(row => row[columnProp] !== null)
|
|
155
|
-
let nullReplacement = ''
|
|
156
|
-
|
|
157
|
-
if ((destinationType && destinationType === 'number') || (typeof rowWithValidValue[columnProp] === 'number')) {
|
|
158
|
-
nullReplacement = 0
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return data.map(row => {
|
|
162
|
-
return {
|
|
163
|
-
...row,
|
|
164
|
-
[columnProp]: row[columnProp] ?? nullReplacement,
|
|
165
|
-
}
|
|
166
|
-
})
|
|
167
|
-
} else {
|
|
168
|
-
return data
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
handleFormattingTemplates: formatting => {
|
|
172
|
-
if (formatting && typeof formatting === 'object') {
|
|
173
|
-
for (const key in formatting) {
|
|
174
|
-
if (typeof formatting[key].format === 'string') {
|
|
175
|
-
if (formatting[key].format in formatterTemplates) {
|
|
176
|
-
formatting[key].format = formatterTemplates[formatting[key].format]
|
|
177
|
-
} else {
|
|
178
|
-
formatting[key].format = {}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return formatting
|
|
185
|
-
},
|
|
186
|
-
getDataTableFormat: (data, fields, multiSeries) => {
|
|
187
|
-
let cols = []
|
|
188
|
-
// domain column for multiseries charts
|
|
189
|
-
if (multiSeries) {
|
|
190
|
-
cols = [ makeDataTableCol(multiSeries.groupXAxisBy, fields) ]
|
|
191
|
-
}
|
|
192
|
-
// All other columns (including domain for non multiseries charts)
|
|
193
|
-
for (const key in fields) {
|
|
194
|
-
if (!multiSeries || key !== multiSeries.groupXAxisBy) {
|
|
195
|
-
if (multiSeries && multiSeries.tooltip && key.includes(multiSeries.tooltip)) {
|
|
196
|
-
// If it's a tooltip, set the column's role exlicitly
|
|
197
|
-
cols.push(makeDataTableCol(key, fields, 'tooltip'))
|
|
198
|
-
} else {
|
|
199
|
-
// Otherwise, role is implicitly assigned
|
|
200
|
-
cols.push(makeDataTableCol(key, fields))
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const rows = data.map(row => {
|
|
206
|
-
let items = []
|
|
207
|
-
|
|
208
|
-
for (const column in row) {
|
|
209
|
-
items.push({ v: row[column] })
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
c: items,
|
|
214
|
-
}
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
return { cols, rows }
|
|
218
|
-
},
|
|
219
|
-
makeDataCumulative: dataTable => {
|
|
220
|
-
dataTable.rows.forEach((row, rowIndex) => {
|
|
221
|
-
if (rowIndex > 0) {
|
|
222
|
-
row.c.forEach((col, colIndex) => {
|
|
223
|
-
if (colIndex > 0 && col.v == null) {
|
|
224
|
-
col.v = dataTable.rows[rowIndex - 1].c[colIndex].v
|
|
225
|
-
} else if (colIndex > 0) {
|
|
226
|
-
col.v = col.v + dataTable.rows[rowIndex - 1].c[colIndex].v
|
|
227
|
-
}
|
|
228
|
-
})
|
|
229
|
-
}
|
|
230
|
-
})
|
|
231
|
-
return dataTable
|
|
232
|
-
},
|
|
233
|
-
getRactiveTableDataFormat: (data, fields, formatting) => {
|
|
234
|
-
const columns = Object.keys(fields).map(field => {
|
|
235
|
-
// right align currency values
|
|
236
|
-
if (formatting[field]?.type === 'NumberFormat' && formatting?.[field].format.prefix === '$') {
|
|
237
|
-
return {
|
|
238
|
-
property: field,
|
|
239
|
-
name: field,
|
|
240
|
-
class: 'text-right',
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
return {
|
|
244
|
-
property: field,
|
|
245
|
-
name: field,
|
|
246
|
-
}
|
|
247
|
-
})
|
|
248
|
-
return {
|
|
249
|
-
columns,
|
|
250
|
-
rows: data,
|
|
251
|
-
sortedRows: [],
|
|
252
|
-
}
|
|
253
|
-
},
|
|
254
|
-
}
|
|
1
|
+
const { formatterTemplates } = require('./formatter-templates.json')
|
|
2
|
+
const toSnakeCase = require('to-snake-case')
|
|
3
|
+
const mysqlDataTypes = require('mysql/lib/protocol/constants/types')
|
|
4
|
+
const klona = require('klona')
|
|
5
|
+
|
|
6
|
+
const mysqlToGoogleChartsDataTypeMap = mysqlDataType => {
|
|
7
|
+
switch (mysqlDataType) {
|
|
8
|
+
case 'VAR_STRING':
|
|
9
|
+
case 'STRING':
|
|
10
|
+
case 'ENUM':
|
|
11
|
+
case 'VARCHAR':
|
|
12
|
+
return 'string'
|
|
13
|
+
case 'DECIMAL':
|
|
14
|
+
case 'NEWDECIMAL':
|
|
15
|
+
case 'BIT':
|
|
16
|
+
case 'TINY':
|
|
17
|
+
case 'SHORT':
|
|
18
|
+
case 'LONG':
|
|
19
|
+
case 'FLOAT':
|
|
20
|
+
case 'DOUBLE':
|
|
21
|
+
case 'LONGLONG':
|
|
22
|
+
case 'INT24':
|
|
23
|
+
case 'YEAR':
|
|
24
|
+
return 'number'
|
|
25
|
+
case 'TIMESTAMP':
|
|
26
|
+
case 'DATETIME':
|
|
27
|
+
case 'TIMESTAMP2':
|
|
28
|
+
case 'DATETIME2':
|
|
29
|
+
return 'datetime'
|
|
30
|
+
case 'DATE':
|
|
31
|
+
case 'NEWDATE':
|
|
32
|
+
return 'date'
|
|
33
|
+
case 'TIME':
|
|
34
|
+
case 'TIME2':
|
|
35
|
+
return 'timeofday'
|
|
36
|
+
default:
|
|
37
|
+
return 'string'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const makeDataTableCol = (key, fields, role = null) => {
|
|
42
|
+
return {
|
|
43
|
+
id: toSnakeCase(key).toLowerCase(),
|
|
44
|
+
label: key,
|
|
45
|
+
type: mysqlToGoogleChartsDataTypeMap(fields[key]),
|
|
46
|
+
role,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
queryWithFields: (connection, query) => { // the only function here that talks to the server at all
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
connection.query(query, (err, results, fields) => {
|
|
54
|
+
if (err) {
|
|
55
|
+
reject(err)
|
|
56
|
+
} else {
|
|
57
|
+
resolve({
|
|
58
|
+
results,
|
|
59
|
+
fields: fields.reduce((sum, field) => {
|
|
60
|
+
return {
|
|
61
|
+
...sum,
|
|
62
|
+
[field.name]: mysqlDataTypes[field.type] || field.type,
|
|
63
|
+
}
|
|
64
|
+
}, {}),
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
},
|
|
70
|
+
getMultiSeriesData: ({ data: queryResult, seriesOptions, formatting, fields }) => {
|
|
71
|
+
const { groupXAxisBy, series, value, tooltip } = seriesOptions
|
|
72
|
+
//Get an array of all of the keys for the series
|
|
73
|
+
const allOfTheKeysInTheSeries = new Set(queryResult.map(row => row[series]))
|
|
74
|
+
|
|
75
|
+
let objectOfAllKeysWithNullValues = {}
|
|
76
|
+
for (const key of allOfTheKeysInTheSeries) {
|
|
77
|
+
objectOfAllKeysWithNullValues[key] = null
|
|
78
|
+
//add in a spot for the tooltips for each column to go
|
|
79
|
+
if (tooltip) {
|
|
80
|
+
objectOfAllKeysWithNullValues[`${tooltip} ${key}`] = null
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let grouped = {}
|
|
85
|
+
|
|
86
|
+
//create objects for each thing they're grouping by
|
|
87
|
+
// console.time(`create objects for each thing they're grouping by`)
|
|
88
|
+
for (const row of queryResult) {
|
|
89
|
+
//if we don't yet have a prop for what we're grouping by, then we need to initialize an object of all null values for it
|
|
90
|
+
if (!grouped[row[groupXAxisBy]]) {
|
|
91
|
+
grouped[row[groupXAxisBy]] = klona(objectOfAllKeysWithNullValues) //give nulls for all values by default
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
grouped[row[groupXAxisBy]][row[series]] = row[value]
|
|
95
|
+
// add the tooltips for each series in each row
|
|
96
|
+
if (tooltip) {
|
|
97
|
+
grouped[row[groupXAxisBy]][`${tooltip} ${row[series]}`] = row[tooltip]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// console.timeEnd(`create objects for each thing they're grouping by`)
|
|
101
|
+
|
|
102
|
+
//The mysql field types need to map to our output data
|
|
103
|
+
let seriesFields = { [groupXAxisBy]: fields[groupXAxisBy] }
|
|
104
|
+
let seriesFormatting = {}
|
|
105
|
+
for (const key of allOfTheKeysInTheSeries) {
|
|
106
|
+
seriesFields[key] = fields[value]
|
|
107
|
+
//add tooltips here, so a tooltip column is created for every series column
|
|
108
|
+
if (tooltip) {
|
|
109
|
+
seriesFields[`${tooltip} ${key}`] = fields[tooltip]
|
|
110
|
+
}
|
|
111
|
+
//If there is any formatting applied to the value, we need to apply it to all of the other keys
|
|
112
|
+
if (formatting && value in formatting) {
|
|
113
|
+
seriesFormatting[key] = formatting[value]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
//turn it back to an array and make sure the grouping column is first as required by a pie chart(at least)
|
|
118
|
+
// console.time(`group it back together`)
|
|
119
|
+
let multiSeriesData = []
|
|
120
|
+
for (const key in grouped) {
|
|
121
|
+
multiSeriesData.push({
|
|
122
|
+
[groupXAxisBy]: key,
|
|
123
|
+
...grouped[key],
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
// console.timeEnd(`group it back together`)
|
|
127
|
+
// console.log(`${multiSeriesData.length} rows with ${Object.keys(multiSeriesData[0]).length} props each.`)
|
|
128
|
+
return {
|
|
129
|
+
data: multiSeriesData,
|
|
130
|
+
formatting: {
|
|
131
|
+
...formatting,
|
|
132
|
+
...seriesFormatting,
|
|
133
|
+
},
|
|
134
|
+
fields: seriesFields,
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
makeNegativeNumbericValuesZero: resultSet => {
|
|
138
|
+
const numberCols = Object.keys(resultSet[0]).filter(key => typeof resultSet[0][key] === 'number')
|
|
139
|
+
|
|
140
|
+
return resultSet.map(row => {
|
|
141
|
+
numberCols.forEach(col => {
|
|
142
|
+
if (row[col] < 0) {
|
|
143
|
+
row[col] = 0
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
return row
|
|
148
|
+
})
|
|
149
|
+
},
|
|
150
|
+
coerceNullColumn: (columnProp, data, destinationType) => {
|
|
151
|
+
const hasNullValue = data.find(row => row[columnProp] === null)
|
|
152
|
+
|
|
153
|
+
if (hasNullValue) {
|
|
154
|
+
const rowWithValidValue = data.find(row => row[columnProp] !== null)
|
|
155
|
+
let nullReplacement = ''
|
|
156
|
+
|
|
157
|
+
if ((destinationType && destinationType === 'number') || (typeof rowWithValidValue[columnProp] === 'number')) {
|
|
158
|
+
nullReplacement = 0
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return data.map(row => {
|
|
162
|
+
return {
|
|
163
|
+
...row,
|
|
164
|
+
[columnProp]: row[columnProp] ?? nullReplacement,
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
} else {
|
|
168
|
+
return data
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
handleFormattingTemplates: formatting => {
|
|
172
|
+
if (formatting && typeof formatting === 'object') {
|
|
173
|
+
for (const key in formatting) {
|
|
174
|
+
if (typeof formatting[key].format === 'string') {
|
|
175
|
+
if (formatting[key].format in formatterTemplates) {
|
|
176
|
+
formatting[key].format = formatterTemplates[formatting[key].format]
|
|
177
|
+
} else {
|
|
178
|
+
formatting[key].format = {}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return formatting
|
|
185
|
+
},
|
|
186
|
+
getDataTableFormat: (data, fields, multiSeries) => {
|
|
187
|
+
let cols = []
|
|
188
|
+
// domain column for multiseries charts
|
|
189
|
+
if (multiSeries) {
|
|
190
|
+
cols = [ makeDataTableCol(multiSeries.groupXAxisBy, fields) ]
|
|
191
|
+
}
|
|
192
|
+
// All other columns (including domain for non multiseries charts)
|
|
193
|
+
for (const key in fields) {
|
|
194
|
+
if (!multiSeries || key !== multiSeries.groupXAxisBy) {
|
|
195
|
+
if (multiSeries && multiSeries.tooltip && key.includes(multiSeries.tooltip)) {
|
|
196
|
+
// If it's a tooltip, set the column's role exlicitly
|
|
197
|
+
cols.push(makeDataTableCol(key, fields, 'tooltip'))
|
|
198
|
+
} else {
|
|
199
|
+
// Otherwise, role is implicitly assigned
|
|
200
|
+
cols.push(makeDataTableCol(key, fields))
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const rows = data.map(row => {
|
|
206
|
+
let items = []
|
|
207
|
+
|
|
208
|
+
for (const column in row) {
|
|
209
|
+
items.push({ v: row[column] })
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
c: items,
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
return { cols, rows }
|
|
218
|
+
},
|
|
219
|
+
makeDataCumulative: dataTable => {
|
|
220
|
+
dataTable.rows.forEach((row, rowIndex) => {
|
|
221
|
+
if (rowIndex > 0) {
|
|
222
|
+
row.c.forEach((col, colIndex) => {
|
|
223
|
+
if (colIndex > 0 && col.v == null) {
|
|
224
|
+
col.v = Number(dataTable.rows[rowIndex - 1].c[colIndex].v)
|
|
225
|
+
} else if (colIndex > 0) {
|
|
226
|
+
col.v = Number(col.v) + Number(dataTable.rows[rowIndex - 1].c[colIndex].v)
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
return dataTable
|
|
232
|
+
},
|
|
233
|
+
getRactiveTableDataFormat: (data, fields, formatting) => {
|
|
234
|
+
const columns = Object.keys(fields).map(field => {
|
|
235
|
+
// right align currency values
|
|
236
|
+
if (formatting[field]?.type === 'NumberFormat' && formatting?.[field].format.prefix === '$') {
|
|
237
|
+
return {
|
|
238
|
+
property: field,
|
|
239
|
+
name: field,
|
|
240
|
+
class: 'text-right',
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
property: field,
|
|
245
|
+
name: field,
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
return {
|
|
249
|
+
columns,
|
|
250
|
+
rows: data,
|
|
251
|
+
sortedRows: [],
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
}
|
package/formatter-templates.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
{
|
|
2
|
-
"formatterTemplates": {
|
|
3
|
-
"CURRENCY": {
|
|
4
|
-
"prefix": "$",
|
|
5
|
-
"fractionDigits": 2,
|
|
6
|
-
"groupingSymbol": ","
|
|
7
|
-
}
|
|
8
|
-
|
|
1
|
+
{
|
|
2
|
+
"formatterTemplates": {
|
|
3
|
+
"CURRENCY": {
|
|
4
|
+
"prefix": "$",
|
|
5
|
+
"fractionDigits": 2,
|
|
6
|
+
"groupingSymbol": ","
|
|
7
|
+
},
|
|
8
|
+
"MEDIUMDATE": {
|
|
9
|
+
"format": "medium"
|
|
10
|
+
},
|
|
11
|
+
"LONGDATE": {
|
|
12
|
+
"format": "long"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
9
15
|
}
|