@eturnity/eturnity_reusable_components 8.19.8-EPDM-13664.0 → 8.19.8-EPDM-14690.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eturnity/eturnity_reusable_components",
3
- "version": "8.19.8-EPDM-13664.0",
3
+ "version": "8.19.8-EPDM-14690.1",
4
4
  "files": [
5
5
  "dist",
6
6
  "src"
@@ -0,0 +1,229 @@
1
+ <template>
2
+ <div
3
+ style="
4
+ margin-top: 100px;
5
+ margin-left: 80px;
6
+ padding-bottom: 100px;
7
+ display: flex;
8
+ flex-direction: column;
9
+ "
10
+ >
11
+ <!-- Simple bar chart -->
12
+ <BarChart
13
+ :bar-width="60"
14
+ chart-controls-position="bottom"
15
+ :data="monthlyData"
16
+ height="400px"
17
+ :is-bottom-fields-shown="true"
18
+ :is-scrollable="false"
19
+ :is-selection-enabled="true"
20
+ :selection-size="3"
21
+ :value-formatter="valueFormatter"
22
+ width="700px"
23
+ @selection-change="handleSelectionChange"
24
+ />
25
+ <br />
26
+ <br />
27
+
28
+ <!-- Stacked bar chart -->
29
+ <BarChart
30
+ :bar-width="60"
31
+ :data="monthLabels"
32
+ height="400px"
33
+ :is-bottom-fields-shown="true"
34
+ :is-legend-shown="true"
35
+ :legends-item-per-row="4"
36
+ :selected-split-button="selectedTimeFrame"
37
+ :series="tariffZones"
38
+ :show-percentage-on-tooltip="true"
39
+ :split-button-options="options"
40
+ :value-formatter="valueFormatter"
41
+ width="700px"
42
+ y-axis-title="Energy (kWh)"
43
+ @input-blur="handleInputBlur"
44
+ @select-split-button="handleSelectSplitButton"
45
+ />
46
+
47
+ <!-- Stacked bar chart -->
48
+ <BarChart
49
+ :bar-width="60"
50
+ :data="monthLabels"
51
+ field-mode="percentage"
52
+ height="400px"
53
+ :is-bottom-fields-shown="true"
54
+ :is-legend-shown="false"
55
+ :legends-item-per-row="4"
56
+ :series="tariffZones"
57
+ :value-formatter="valueFormatter"
58
+ width="700px"
59
+ @input-blur="handleInputBlur"
60
+ @select-split-button="handleSelectSplitButton"
61
+ >
62
+ <!-- <template #tooltip="{ item, segment }">
63
+ <div style="display: flex; flex-direction: column">
64
+ {{ $c.log(item, segment) }}
65
+ <div>{{ item.label }}</div>
66
+ <div>{{ item.segments[0].value }} kWh</div>
67
+ </div>
68
+ </template> -->
69
+ </BarChart>
70
+ </div>
71
+ </template>
72
+
73
+ <script setup>
74
+ import { ref } from 'vue'
75
+ import BarChart from '@/components/barchart/index.vue'
76
+
77
+ const options = [
78
+ { label: 'Day', value: 'day' },
79
+ { label: 'Month', value: 'month' },
80
+ { label: 'Year', value: 'year' },
81
+ ]
82
+
83
+ const selectedTimeFrame = ref('day')
84
+
85
+ const handleSelectSplitButton = (value) => {
86
+ selectedTimeFrame.value = value
87
+ }
88
+
89
+ const monthlyData = [
90
+ { label: 'Jan', value: 300 },
91
+ { label: 'Feb', value: 600 },
92
+ { label: 'Mar', value: 1000 },
93
+ { label: 'Apr', value: 1200 },
94
+ { label: 'May', value: 1400 },
95
+ { label: 'Jun', value: 1810 },
96
+ { label: 'Jul', value: 1400 },
97
+ { label: 'Aug', value: 1200 },
98
+ { label: 'Sep', value: 1000 },
99
+ // { label: 'Oct', value: 800 },
100
+ // { label: 'Nov', value: 600 },
101
+ // { label: 'Dec', value: 400 },
102
+ // { label: 'Jan', value: 300 },
103
+ // { label: 'Feb', value: 600 },
104
+ // { label: 'Mar', value: 1000 },
105
+ // { label: 'Apr', value: 1200 },
106
+ // { label: 'May', value: 1400 },
107
+ // { label: 'Jun', value: 1810 },
108
+ // { label: 'Jul', value: 1400 },
109
+ // { label: 'Aug', value: 1200 },
110
+ // { label: 'Sep', value: 1000 },
111
+ // { label: 'Oct', value: 800 },
112
+ // { label: 'Nov', value: 600 },
113
+ // { label: 'Dec', value: 400 },
114
+
115
+ // ... more months
116
+ ]
117
+
118
+ const monthLabels = [
119
+ { label: 'Jan' },
120
+ { label: 'Feb' },
121
+ { label: 'Mar' },
122
+ { label: 'Apr' },
123
+ { label: 'May' },
124
+ { label: 'Jun' },
125
+ // ... more months
126
+ ]
127
+
128
+ const tariffZones = ref([
129
+ {
130
+ name: 'Tariff Zone 1',
131
+ data: [
132
+ { label: 'Jan', value: 200 },
133
+ { label: 'Feb', value: 130 },
134
+ { label: 'Mar', value: 220 },
135
+ { label: 'Apr', value: 230 },
136
+ { label: 'May', value: 200 },
137
+ { label: 'Jun', value: 210 },
138
+ // ... more months
139
+ ],
140
+ },
141
+ {
142
+ name: 'Tariff Zone 2',
143
+ data: [
144
+ { label: 'Jan', value: 200 },
145
+ { label: 'Feb', value: 100 },
146
+ { label: 'Mar', value: 270 },
147
+ { label: 'Apr', value: 180 },
148
+ { label: 'May', value: 300 },
149
+ { label: 'Jun', value: 250 },
150
+ // ... more months
151
+ ],
152
+ },
153
+ {
154
+ name: 'Tariff Zone 3',
155
+ data: [
156
+ { label: 'Jan', value: 200 },
157
+ { label: 'Feb', value: 100 },
158
+ { label: 'Mar', value: 210 },
159
+ { label: 'Apr', value: 220 },
160
+ { label: 'May', value: 300 },
161
+ { label: 'Jun', value: 190 },
162
+ // ... more months
163
+ ],
164
+ },
165
+ {
166
+ name: 'Tariff Zone 4',
167
+ data: [
168
+ { label: 'Jan', value: 200 },
169
+ { label: 'Feb', value: 100 },
170
+ { label: 'Mar', value: 210 },
171
+ { label: 'Apr', value: 220 },
172
+ { label: 'May', value: 300 },
173
+ { label: 'Jun', value: 190 },
174
+ // ... more months
175
+ ],
176
+ },
177
+ {
178
+ name: 'Tariff Zone 5',
179
+ data: [
180
+ { label: 'Jan', value: 200 },
181
+ { label: 'Feb', value: 100 },
182
+ { label: 'Mar', value: 210 },
183
+ { label: 'Apr', value: 220 },
184
+ { label: 'May', value: 300 },
185
+ { label: 'Jun', value: 190 },
186
+ // ... more months
187
+ ],
188
+ },
189
+ {
190
+ name: 'Tariff Zone 6',
191
+ data: [
192
+ { label: 'Jan', value: 200 },
193
+ { label: 'Feb', value: 100 },
194
+ { label: 'Mar', value: 210 },
195
+ { label: 'Apr', value: 220 },
196
+ { label: 'May', value: 300 },
197
+ { label: 'Jun', value: 190 },
198
+ // ... more months
199
+ ],
200
+ },
201
+ // ... more tariff zones
202
+ ])
203
+
204
+ const valueFormatter = (value) => {
205
+ return `${value} kWh`
206
+ }
207
+
208
+ const handleSelectionChange = (selectedBars) => {
209
+ console.log('selectedBars', selectedBars)
210
+ }
211
+
212
+ const handleInputBlur = (payload) => {
213
+ const newVal = [...tariffZones.value].map((zone) => {
214
+ if (zone.name === payload.seriesName) {
215
+ zone.data = zone.data.map((item) => {
216
+ if (item.label === payload.label) {
217
+ item.value = payload.value
218
+ }
219
+ return item
220
+ })
221
+ }
222
+ return zone
223
+ })
224
+
225
+ tariffZones.value = newVal
226
+ }
227
+ </script>
228
+
229
+ <style lang="scss" scoped></style>
@@ -1,14 +1,14 @@
1
1
  <template>
2
2
  <Container :is-chart-controls-shown-in-bottom="isChartControlsShownInBottom">
3
3
  <LabelsColumn :width="yAxisWidth">
4
- <LabelRow v-for="series in props.series" :key="series.name">
4
+ <LabelRow v-for="series in seriesData" :key="series.name">
5
5
  {{ series.name }}
6
6
  </LabelRow>
7
- <TotalRow v-if="props.series.length && fieldMode === 'percentage'">
8
- {{ $gettext ? $gettext('Total (%)') : 'Total (%)' }}
7
+ <TotalRow v-if="seriesData.length && fieldMode === 'percentage'">
8
+ {{ $gettext ? `${$gettext('Total')} (%)` : 'Total (%)' }}
9
9
  </TotalRow>
10
- <TotalRow v-if="props.series.length">
11
- {{ $gettext ? $gettext('Total (kWh)') : 'Total (kWh)' }}
10
+ <TotalRow v-if="seriesData.length">
11
+ {{ $gettext ? `${$gettext('Total')} (kWh)` : 'Total (kWh)' }}
12
12
  </TotalRow>
13
13
  </LabelsColumn>
14
14
 
@@ -18,27 +18,35 @@
18
18
  >
19
19
  <FieldsWrapper>
20
20
  <!-- For stacked bar chart -->
21
- <template v-if="props.series.length">
21
+ <template v-if="seriesData.length">
22
22
  <InputRow
23
- v-for="series in props.series"
23
+ v-for="series in seriesData"
24
24
  :key="series.name"
25
25
  :data-series-name="series.name"
26
26
  >
27
27
  <InputGroup
28
28
  v-for="(item, index) in props.data"
29
+ :key="index"
29
30
  :bar-width="barWidth"
30
31
  :is-scrollable="isScrollable"
31
- :key="index"
32
32
  >
33
33
  <InputNumber
34
34
  :allow-negative="false"
35
+ :disabled="isInputsDisabled"
35
36
  input-height="36px"
36
- :number-precision="0"
37
+ :is-info-border="
38
+ fieldMode === 'percentage'
39
+ ? calculatePercentageTotal(item.label) !== 100
40
+ : false
41
+ "
37
42
  :min-decimals="0"
43
+ :number-precision="fieldMode === 'percentage' ? 2 : 0"
38
44
  text-align="center"
39
45
  :unit-name="fieldMode === 'percentage' ? '%' : ''"
40
46
  :value="getDisplayValue(series.data, item.label)"
41
- @input-blur="handleInputBlur($event, series.name, item.label)"
47
+ @input-blur="
48
+ handleInputBlur($event, series.name, item.label, series.data)
49
+ "
42
50
  @input-focus="handleInputFocus(series.name, item.label)"
43
51
  />
44
52
  </InputGroup>
@@ -47,16 +55,17 @@
47
55
  <TotalInputRow v-if="fieldMode === 'percentage'">
48
56
  <InputGroup
49
57
  v-for="(item, index) in props.data"
58
+ :key="index"
50
59
  :bar-width="barWidth"
51
60
  :is-scrollable="isScrollable"
52
- :key="index"
53
61
  >
54
62
  <InputNumber
55
63
  :allow-negative="false"
64
+ :disabled="isInputsDisabled"
56
65
  input-height="36px"
57
66
  :is-read-only="true"
58
- :number-precision="0"
59
67
  :min-decimals="0"
68
+ :number-precision="fieldMode === 'percentage' ? 2 : 0"
60
69
  text-align="center"
61
70
  :unit-name="fieldMode === 'percentage' ? '%' : ''"
62
71
  :value="calculatePercentageTotal(item.label)"
@@ -67,17 +76,23 @@
67
76
  <TotalInputRow>
68
77
  <InputGroup
69
78
  v-for="(item, index) in props.data"
79
+ :key="index"
70
80
  :bar-width="barWidth"
71
81
  :is-scrollable="isScrollable"
72
- :key="index"
73
82
  >
74
83
  <InputNumber
75
84
  input-height="36px"
85
+ :is-border-error-only="true"
86
+ :is-info-border="
87
+ fieldMode === 'percentage'
88
+ ? calculatePercentageTotal(item.label) !== 100
89
+ : false
90
+ "
76
91
  :is-read-only="true"
77
- :number-precision="2"
78
92
  :min-decimals="0"
93
+ :number-precision="0"
79
94
  text-align="center"
80
- :value="calculateTotal(item.label)"
95
+ :value="calculateTotalValue(item.label)"
81
96
  />
82
97
  </InputGroup>
83
98
  </TotalInputRow>
@@ -88,17 +103,19 @@
88
103
  <InputRow>
89
104
  <InputGroup
90
105
  v-for="(item, index) in props.data"
106
+ :key="index"
91
107
  :bar-width="barWidth"
92
108
  :is-scrollable="isScrollable"
93
- :key="index"
94
109
  >
95
110
  <InputNumber
111
+ :allow-negative="false"
112
+ :disabled="isInputsDisabled"
96
113
  input-height="36px"
97
114
  :min-decimals="0"
98
- :number-precision="2"
115
+ :number-precision="0"
99
116
  text-align="center"
100
117
  :value="item.value"
101
- @input-blur="handleInputBlur($event, null, item.label)"
118
+ @input-blur="handleInputBlur($event, null, item.label, null)"
102
119
  @input-focus="handleInputFocus(null, item.label)"
103
120
  />
104
121
  </InputGroup>
@@ -107,11 +124,22 @@
107
124
  </FieldsWrapper>
108
125
  </FieldsContainer>
109
126
  </Container>
127
+ <InfoCardContainer
128
+ v-if="hasAnySegmentNotTotatTo100Percent && fieldMode === 'percentage'"
129
+ :yAxisWidth="yAxisWidth"
130
+ >
131
+ <InfoCard align-items="center" type="info">
132
+ <InfoCardBody>
133
+ {{ $gettext('load_profile_not_add_up_to_100') }}
134
+ </InfoCardBody>
135
+ </InfoCard>
136
+ </InfoCardContainer>
110
137
  </template>
111
138
 
112
139
  <script setup>
113
- import { ref } from 'vue'
140
+ import { ref, computed, watchEffect } from 'vue'
114
141
  import InputNumber from '../inputs/inputNumber'
142
+ import InfoCard from '../infoCard'
115
143
 
116
144
  import {
117
145
  Container,
@@ -123,6 +151,8 @@
123
151
  InputRow,
124
152
  TotalInputRow,
125
153
  InputGroup,
154
+ InfoCardContainer,
155
+ InfoCardBody,
126
156
  } from './styles/bottomFields'
127
157
 
128
158
  const props = defineProps({
@@ -159,6 +189,47 @@
159
189
  default: 'absolute',
160
190
  validator: (value) => ['absolute', 'percentage'].includes(value),
161
191
  },
192
+ isInputsDisabled: {
193
+ type: Boolean,
194
+ default: false,
195
+ },
196
+ })
197
+
198
+ const seriesData = ref([])
199
+
200
+ watchEffect(() => {
201
+ let isNewSetOfSeries = false
202
+ const seriesDataCopy = [...seriesData.value]
203
+ if (
204
+ !seriesDataCopy.length ||
205
+ !props.series.length ||
206
+ seriesDataCopy.length !== props.series.length ||
207
+ !seriesDataCopy.some((item) => {
208
+ return props.series.map((s) => s.name).includes(item.name)
209
+ })
210
+ ) {
211
+ isNewSetOfSeries = true
212
+ }
213
+ const currentSeriesData = !isNewSetOfSeries ? seriesDataCopy : []
214
+ const newSeriesData = []
215
+
216
+ props.series.forEach((item, itemIndex) => {
217
+ const data = item.data.map((d, dIndex) => ({
218
+ label: d.label,
219
+ value: d.value,
220
+ percentage: d.percentage,
221
+ originalValue: currentSeriesData.length
222
+ ? currentSeriesData[itemIndex].data[dIndex].originalValue
223
+ : d.value,
224
+ }))
225
+
226
+ newSeriesData.push({
227
+ name: item.name,
228
+ data,
229
+ })
230
+ })
231
+
232
+ seriesData.value = [...newSeriesData]
162
233
  })
163
234
 
164
235
  const emit = defineEmits([
@@ -175,11 +246,13 @@
175
246
  emit('input-focus', { seriesName, label })
176
247
  }
177
248
 
178
- const calculateTotal = (label) => {
179
- return props.series.reduce((sum, series) => {
249
+ const calculateTotalValue = (label) => {
250
+ const total = seriesData.value.reduce((sum, series) => {
180
251
  const value = series.data.find((d) => d.label === label)?.value || 0
181
252
  return sum + value
182
253
  }, 0)
254
+
255
+ return Math.round(total)
183
256
  }
184
257
 
185
258
  const syncScroll = (scrollLeft) => {
@@ -190,34 +263,43 @@
190
263
  container.scrollLeft = scrollLeft
191
264
  }
192
265
  }
193
- const getDisplayValue = (seriesData, label) => {
266
+
267
+ const calculateTotalOriginalValue = (label) => {
268
+ return seriesData.value.reduce((sum, series) => {
269
+ const value =
270
+ series.data.find((d) => d.label === label)?.originalValue || 0
271
+ return sum + value
272
+ }, 0)
273
+ }
274
+
275
+ const getDisplayValue = (data, label, shouldRound = true) => {
194
276
  if (props.fieldMode === 'absolute') {
195
- return seriesData.find((d) => d.label === label)?.value || ''
277
+ return data.find((d) => d.label === label)?.value
196
278
  }
197
279
 
198
- const value = seriesData.find((d) => d.label === label)?.value || 0
199
- const total = calculateTotal(label)
200
- return total ? Number(((value / total) * 100).toFixed(0)) : 0
280
+ return data.find((d) => d.label === label)?.percentage
201
281
  }
202
282
 
203
283
  const calculatePercentageTotal = (label) => {
204
- return props.series.reduce((sum, series) => {
205
- const value = series.data.find((d) => d.label === label)?.value || 0
206
- const total = calculateTotal(label)
207
- const percentage = total ? Number(((value / total) * 100).toFixed(0)) : 0
284
+ const percentageTotal = seriesData.value.reduce((sum, series) => {
285
+ const percentage =
286
+ series.data.find((d) => d.label === label)?.percentage || 0
208
287
  return sum + percentage
209
288
  }, 0)
289
+
290
+ return Math.round(percentageTotal)
210
291
  }
211
292
 
212
- const handleInputBlur = (_value, seriesName, label) => {
293
+ const handleInputBlur = (_value, seriesName, label, currentSeriesData) => {
213
294
  let value = Number(_value)
214
295
 
215
- if (props.fieldMode === 'percentage') {
216
- const total = calculateTotal(label)
217
- value = (value / 100) * total
218
- }
219
-
220
- const payload = seriesName ? { seriesName, label, value } : { label, value }
296
+ const payload = seriesName
297
+ ? {
298
+ seriesName,
299
+ label,
300
+ value,
301
+ }
302
+ : { label, value }
221
303
  emit('input-blur', payload)
222
304
  focusedInput.value = null
223
305
 
@@ -243,6 +325,12 @@
243
325
  }
244
326
  }
245
327
 
328
+ const hasAnySegmentNotTotatTo100Percent = computed(() => {
329
+ return props.data.some((d) => {
330
+ return calculatePercentageTotal(d.label) !== 100
331
+ })
332
+ })
333
+
246
334
  const handleFieldsScroll = (event) => {
247
335
  emit('sync-scroll', event.target.scrollLeft)
248
336
  }
@@ -84,7 +84,7 @@ export function useAxisCalculations(props, maxValue) {
84
84
  })
85
85
 
86
86
  const yAxisWidth = computed(() => {
87
- return !!props.yAxisTitle || props.isBottomFieldsShown ? '70px' : '60px'
87
+ return !!props.yAxisTitle || props.isBottomFieldsShown ? '80px' : '60px'
88
88
  })
89
89
 
90
90
  const isChartControlsShown = (position) => {
@@ -66,7 +66,7 @@ export function useChartData(props, paddedMaxValue) {
66
66
  let accumulated = 0
67
67
  return {
68
68
  label: item.label,
69
- segments: [...props.series].reverse().map((series, index) => {
69
+ segments: [...props.series].map((series, index) => {
70
70
  const value =
71
71
  series.data.find((d) => d.label === item.label)?.value || 0
72
72
  accumulated += value
@@ -16,7 +16,21 @@ export function useTooltip(chartId, normalizedData) {
16
16
  if (!showTooltipContent.value) {
17
17
  showTooltipContent.value = true
18
18
  }
19
- if (isObjectEqual(item, tooltipData.value)) return
19
+ if (isObjectEqual(item, tooltipData.value)) {
20
+ return
21
+ }
22
+
23
+ const totalValue = item.segments.reduce((acc, segment) => {
24
+ return acc + segment.value
25
+ }, 0)
26
+
27
+ const segments = item.segments.map((segment) => {
28
+ let valuePercentage = (segment.value / totalValue) * 100
29
+ segment.valuePercentage = Math.round(valuePercentage)
30
+
31
+ return segment
32
+ })
33
+ item.segments = segments
20
34
 
21
35
  tooltipData.value = { ...item }
22
36
 
@@ -41,16 +55,27 @@ export function useTooltip(chartId, normalizedData) {
41
55
 
42
56
  isInputFocused.value = true
43
57
  const barData = normalizedData.value.find((item) => item.label === label)
44
-
45
58
  if (!barData) return
59
+
60
+ const totalValue = barData.segments.reduce((acc, segment) => {
61
+ return acc + segment.value
62
+ }, 0)
63
+ const segments = barData.segments.map((segment) => {
64
+ let valuePercentage = (segment.value / totalValue) * 100
65
+ segment.valuePercentage = Math.round(valuePercentage)
66
+
67
+ return segment
68
+ })
69
+ barData.segments = segments
46
70
  focusedBarData.value = barData
71
+
47
72
  const barElement = document.querySelector(
48
73
  `.barchart-${chartId} .bar-group:nth-child(${
49
74
  normalizedData.value.indexOf(barData) + 1
50
75
  })`
51
76
  )
52
-
53
77
  if (!barElement) return
78
+
54
79
  // Get the last bar segment, samee as hover behavior
55
80
  const targetElement = barElement.querySelector('.bar-segment:last-child')
56
81
  const rect = targetElement.getBoundingClientRect()
@@ -17,7 +17,7 @@
17
17
  />
18
18
  </ChartControlsWrapper>
19
19
  <GraphSection :height="height" :width="width">
20
- <YAxis :width="yAxisWidth" :height="height">
20
+ <YAxis :height="height" :width="yAxisWidth">
21
21
  <YAxisTitleWrapper v-if="yAxisTitle" :height="yAxisHeight">
22
22
  {{ yAxisTitle }}
23
23
  </YAxisTitleWrapper>
@@ -30,15 +30,15 @@
30
30
  )
31
31
  "
32
32
  >
33
- <YAxisLabel>{{ label }}</YAxisLabel>
33
+ <YAxisLabel>{{ getYAxisLabel(label) }}</YAxisLabel>
34
34
  <YAxisLine :y-axis-width="yAxisWidth" />
35
35
  </YAxisRow>
36
36
  </YAxis>
37
37
 
38
38
  <ScrollContainer
39
39
  :class="`chart-scroll-container-${chartId}`"
40
- :is-scrollable="isScrollable"
41
40
  :height="height"
41
+ :is-scrollable="isScrollable"
42
42
  @scroll="handleChartScroll"
43
43
  >
44
44
  <ChartContent
@@ -64,19 +64,19 @@
64
64
  <BarsContainer>
65
65
  <BarGroup
66
66
  v-for="(item, index) in normalizedData"
67
+ :key="index"
67
68
  :bar-width="barWidth"
68
69
  class="bar-group"
69
70
  :is-scrollable="isScrollable"
70
- :key="index"
71
71
  >
72
72
  <BarWrapper>
73
73
  <BarSegment
74
74
  v-for="(segment, segIndex) in item.segments"
75
+ :key="segIndex"
75
76
  class="bar-segment"
76
77
  :gradient-from="getSegmentGradient(index, segment).from"
77
78
  :gradient-to="getSegmentGradient(index, segment).to"
78
79
  :height="`${segment.percentage}%`"
79
- :key="segIndex"
80
80
  :z-index="item.segments.length - segIndex"
81
81
  @mouseenter="showTooltip(item, $event, series)"
82
82
  @mouseleave="hideTooltip"
@@ -96,7 +96,7 @@
96
96
  :left="tooltipStyle.left"
97
97
  :top="tooltipStyle.top"
98
98
  >
99
- <slot :item="tooltipData" name="tooltip" />
99
+ <slot :item="tooltipData" name="tooltip"></slot>
100
100
  <TooltipTextWrapper v-if="!slots.tooltip && tooltipData">
101
101
  <template v-if="!series.length">
102
102
  <TooltipText font-weight="500">{{ tooltipData.label }}</TooltipText>
@@ -128,7 +128,11 @@
128
128
  :gradient-to="segment.gradientTo"
129
129
  />
130
130
  <TooltipText>
131
- {{ handleValueFormatter(segment.value) }}
131
+ {{
132
+ fieldMode === 'absolute' && showPercentageOnTooltip
133
+ ? `${segment.valuePercentage}%`
134
+ : handleValueFormatter(segment.value)
135
+ }}
132
136
  </TooltipText>
133
137
  </TooltipRow>
134
138
  </template>
@@ -159,6 +163,7 @@
159
163
  :data="data"
160
164
  :field-mode="fieldMode"
161
165
  :is-chart-controls-shown-in-bottom="isChartControlsShown('bottom')"
166
+ :is-inputs-disabled="isLoading"
162
167
  :is-scrollable="isScrollable"
163
168
  :series="series"
164
169
  :y-axis-width="yAxisWidth"
@@ -171,12 +176,13 @@
171
176
  </template>
172
177
 
173
178
  <script setup>
174
- import { useSlots, computed } from 'vue'
179
+ import { useSlots, computed, ref } from 'vue'
175
180
 
176
181
  import ChartControls from './ChartControls'
177
182
  import BottomFields from './BottomFields'
178
183
  import SelectionBox from './SelectionBox'
179
184
  import Spinner from '../spinner'
185
+ import { numberToString } from '../../helpers/numberConverter'
180
186
 
181
187
  import {
182
188
  useTooltip,
@@ -246,10 +252,6 @@
246
252
  type: String,
247
253
  default: '',
248
254
  },
249
- valueFormatter: {
250
- type: Function,
251
- default: null,
252
- },
253
255
  isLegendShown: {
254
256
  type: Boolean,
255
257
  default: false,
@@ -296,6 +298,10 @@
296
298
  type: Boolean,
297
299
  default: false,
298
300
  },
301
+ showPercentageOnTooltip: {
302
+ type: Boolean,
303
+ default: false,
304
+ },
299
305
  })
300
306
 
301
307
  const generateChartId = () =>
@@ -378,8 +384,40 @@
378
384
  }
379
385
 
380
386
  const handleValueFormatter = (value) => {
381
- return props.valueFormatter
382
- ? props.valueFormatter(Math.round(value))
383
- : value
387
+ let formattedValue = value
388
+ if (value < 1000) {
389
+ formattedValue = numberToString({
390
+ value: formattedValue,
391
+ numberPrecision: 0,
392
+ minDecimals: 0,
393
+ })
394
+ } else if (value < 1000000) {
395
+ formattedValue = numberToString({
396
+ value: Number(formattedValue / 1000),
397
+ numberPrecision: 2,
398
+ minDecimals: 2,
399
+ })
400
+ } else {
401
+ formattedValue = numberToString({
402
+ value: Number(formattedValue / 1000000),
403
+ numberPrecision: 2,
404
+ minDecimals: 2,
405
+ })
406
+ }
407
+ if (value < 1000) {
408
+ return `${formattedValue} kWh`
409
+ } else if (value < 1000000) {
410
+ return `${formattedValue} MWh`
411
+ } else {
412
+ return `${formattedValue} GWh`
413
+ }
414
+ }
415
+
416
+ const getYAxisLabel = (label) => {
417
+ return numberToString({
418
+ value: label,
419
+ numberPrecision: 0,
420
+ minDecimals: 0,
421
+ })
384
422
  }
385
423
  </script>
@@ -17,18 +17,18 @@ export const LabelsColumn = styled('div', { width: String })`
17
17
 
18
18
  export const LabelRow = styled.div`
19
19
  height: 32px;
20
+ padding-top: 5px;
20
21
  font-size: 12px;
21
22
  font-weight: 500;
22
23
  color: ${(props) => props.theme.semanticColors.teal[600]};
23
24
  display: flex;
24
- align-items: flex-start;
25
+ align-items: center;
25
26
  `
26
27
 
27
28
  export const TotalRow = styled(LabelRow)``
28
29
 
29
30
  export const FieldsContainer = styled.div`
30
31
  flex: 1;
31
- overflow-x: auto;
32
32
  scrollbar-width: none;
33
33
 
34
34
  &::-webkit-scrollbar {
@@ -59,8 +59,22 @@ export const InputGroup = styled('div', {
59
59
  barWidth: Number,
60
60
  isScrollable: Boolean,
61
61
  })`
62
- ${(props) => (props.isScrollable ? 'min-width' : 'width')}:${(props) =>
63
- props.barWidth}px;
62
+ ${(props) => (props.isScrollable ? 'min-width' : 'width')}: 70px;
64
63
  display: flex;
65
64
  justify-content: center;
65
+ position: relative;
66
+
67
+ input[readonly] {
68
+ border: 1px solid ${(props) => props.theme.colors.grey4} !important;
69
+ }
70
+ `
71
+
72
+ export const InfoCardContainer = styled('div', { yAxisWidth: String })`
73
+ margin-left: ${(props) => props.yAxisWidth};
74
+ padding: 12px;
75
+ margin-top: 12px;
76
+ `
77
+
78
+ export const InfoCardBody = styled.div`
79
+ padding: 8px 0;
66
80
  `
@@ -127,6 +127,7 @@ export const BarWrapper = styled.div`
127
127
  height: 100%;
128
128
  width: 100%;
129
129
  position: relative;
130
+ overflow: hidden;
130
131
  `
131
132
 
132
133
  export const BarSegment = styled('div', {
@@ -19,7 +19,7 @@
19
19
  background: ${(props) => props.theme.colors.red};
20
20
  padding: 10px;
21
21
  width: max-content;
22
- max-width: 100%;
22
+ /* max-width: 100%; */
23
23
  min-width: min-content;
24
24
  font-size: 11px;
25
25
  font-weight: 400;
@@ -73,7 +73,12 @@
73
73
  type: {
74
74
  required: false,
75
75
  type: String,
76
- default: 'info',
76
+ default: 'info_simple',
77
+ validator(value) {
78
+ return ['info_simple', 'warning', 'error_minor', 'info'].includes(
79
+ value
80
+ )
81
+ },
77
82
  },
78
83
  minWidth: {
79
84
  required: false,
@@ -112,8 +117,11 @@
112
117
  },
113
118
  },
114
119
  computed: {
115
- isInfo() {
120
+ isInfoSimple() {
116
121
  // this property is used for tests
122
+ return this.type === 'info_simple'
123
+ },
124
+ isInfo() {
117
125
  return this.type === 'info'
118
126
  },
119
127
  isWarning() {
@@ -148,9 +156,13 @@
148
156
  stylesCollection.borderStyle = 'dashed'
149
157
  stylesCollection.borderColor = theme.colors.grey4
150
158
  stylesCollection.iconColor = theme.colors.red
151
- } else {
159
+ } else if (this.isInfoSimple) {
152
160
  stylesCollection.borderStyle = 'dashed'
153
161
  stylesCollection.borderColor = theme.colors.grey4
162
+ } else {
163
+ stylesCollection.color = theme.semanticColors.teal[800]
164
+ stylesCollection.backgroundColor = theme.semanticColors.blue[300]
165
+ stylesCollection.iconColor = theme.semanticColors.teal[800]
154
166
  }
155
167
 
156
168
  return stylesCollection
@@ -45,19 +45,19 @@ describe('RCInfoCard.vue', () => {
45
45
  },
46
46
  })
47
47
 
48
- expect(wrapper.vm.isInfo).toBe(true)
48
+ expect(wrapper.vm.isInfoSimple).toBe(true)
49
49
  expect(wrapper.vm.isWarning).toBe(false)
50
50
  expect(wrapper.vm.isErrorMinor).toBe(false)
51
51
 
52
52
  await wrapper.setProps({ type: 'warning' })
53
53
 
54
- expect(wrapper.vm.isInfo).toBe(false)
54
+ expect(wrapper.vm.isInfoSimple).toBe(false)
55
55
  expect(wrapper.vm.isWarning).toBe(true)
56
56
  expect(wrapper.vm.isErrorMinor).toBe(false)
57
57
 
58
58
  await wrapper.setProps({ type: 'error_minor' })
59
59
 
60
- expect(wrapper.vm.isInfo).toBe(false)
60
+ expect(wrapper.vm.isInfoSimple).toBe(false)
61
61
  expect(wrapper.vm.isWarning).toBe(false)
62
62
  expect(wrapper.vm.isErrorMinor).toBe(true)
63
63
  })
@@ -15,37 +15,40 @@
15
15
  :is-disabled="isDisabled"
16
16
  :padding="padding"
17
17
  >
18
- <LabelWrapper
19
- v-if="labelText && labelAlign === 'left'"
20
- :color="iconColor || computedIconColor"
21
- :size="labelSize"
22
- >
23
- {{ labelText }}
24
- </LabelWrapper>
25
- <Dot
26
- v-if="type === 'dot'"
27
- :color="dotColor"
28
- data-test-id="infoText_dot"
29
- />
30
- <IconComponent
31
- v-else-if="!noIcon"
32
- :color="iconColor || computedIconColor"
33
- :cursor="isDisabled ? 'not-allowed' : 'pointer'"
34
- :disabled="isDisabled"
35
- :hovered-color="iconColor || computedIconColor"
36
- :name="iconName"
37
- :size="size"
38
- />
39
- <LabelWrapper
40
- v-if="labelText && labelAlign === 'right'"
41
- :color="iconColor || computedIconColor"
42
- :size="labelSize"
43
- >
44
- {{ labelText }}
45
- </LabelWrapper>
18
+ <template v-if="!$slots.trigger">
19
+ <LabelWrapper
20
+ v-if="labelText && labelAlign === 'left'"
21
+ :color="iconColor || computedIconColor"
22
+ :size="labelSize"
23
+ >
24
+ {{ labelText }}
25
+ </LabelWrapper>
26
+ <Dot
27
+ v-if="type === 'dot'"
28
+ :color="dotColor"
29
+ data-test-id="infoText_dot"
30
+ />
31
+ <IconComponent
32
+ v-else-if="!noIcon"
33
+ :color="iconColor || computedIconColor"
34
+ :cursor="isDisabled ? 'not-allowed' : 'pointer'"
35
+ :disabled="isDisabled"
36
+ :hovered-color="iconColor || computedIconColor"
37
+ :name="iconName"
38
+ :size="size"
39
+ />
40
+ <LabelWrapper
41
+ v-if="labelText && labelAlign === 'right'"
42
+ :color="iconColor || computedIconColor"
43
+ :size="labelSize"
44
+ >
45
+ {{ labelText }}
46
+ </LabelWrapper>
47
+ </template>
48
+ <slot name="trigger"></slot>
46
49
  </IconWrapper>
47
50
  </div>
48
- <Teleport v-if="isVisible" to="body">
51
+ <Teleport v-if="isVisible && !!text" to="body">
49
52
  <TextWrapper data-test-id="info_text_wrapper" :style="wrapperStyle">
50
53
  <TextOverlay
51
54
  ref="infoBox"
@@ -227,7 +230,7 @@
227
230
  },
228
231
  infoPosition: {
229
232
  required: false,
230
- default: 'bottom',
233
+ default: null,
231
234
  type: String,
232
235
  },
233
236
  maxWidth: {
@@ -287,11 +290,6 @@
287
290
  default: '',
288
291
  required: false,
289
292
  },
290
- contentBackgroundColor: {
291
- type: String,
292
- default: '',
293
- required: false,
294
- },
295
293
  borderRadius: {
296
294
  type: String,
297
295
  default: '',
@@ -368,7 +366,9 @@
368
366
  { position: 'left', space: spaceLeft },
369
367
  ].sort((a, b) => b.space - a.space)
370
368
 
371
- const bestPosition = positions[0].position
369
+ const bestPosition = props.infoPosition
370
+ ? props.infoPosition
371
+ : positions[0].position
372
372
 
373
373
  let top, left, arrowPosition
374
374
 
@@ -471,9 +471,7 @@
471
471
  width: '100%',
472
472
  maxWidth: props.maxWidth,
473
473
  overflowY: 'auto',
474
- backgroundColor: props.contentBackgroundColor
475
- ? props.contentBackgroundColor
476
- : props.image
474
+ backgroundColor: props.image
477
475
  ? theme.colors.white
478
476
  : props.appTheme === 'light'
479
477
  ? theme.colors.black
@@ -30,7 +30,61 @@
30
30
  />
31
31
  </LabelWrapper>
32
32
  <InputWrapper>
33
+ <InfoText
34
+ v-if="!!inputInfoText"
35
+ info-position="bottom"
36
+ :text="inputInfoText"
37
+ >
38
+ <template #trigger>
39
+ <InputContainer
40
+ v-bind="$attrs"
41
+ ref="inputField1"
42
+ :align-items="alignItems"
43
+ :background-color="
44
+ colorMode === 'transparent' ? 'transparent' : backgroundColor
45
+ "
46
+ :border-color="
47
+ colorMode === 'transparent' && !borderColor
48
+ ? 'white'
49
+ : borderColor
50
+ "
51
+ :color-mode="colorMode"
52
+ :data-id="inputDataId"
53
+ :data-qa-id="dataQaId"
54
+ :disabled="disabled"
55
+ :font-color="colorMode === 'transparent' ? 'white' : fontColor"
56
+ :font-size="fontSize"
57
+ :has-label-slot="hasLabelSlot"
58
+ :has-slot="hasSlot"
59
+ :has-unit="unitName && !!unitName.length"
60
+ :input-height="inputHeight"
61
+ :is-border-error-only="isBorderErrorOnly"
62
+ :is-disabled="disabled"
63
+ :is-error="isError"
64
+ :is-info-border="isInfoBorder"
65
+ :is-interactive="isInteractive"
66
+ :min-width="minWidth"
67
+ :no-border="noBorder"
68
+ :placeholder="displayedPlaceholder"
69
+ :read-only="isReadOnly"
70
+ :readonly="isReadOnly"
71
+ :show-arrow-controls="showArrowControls"
72
+ :show-linear-unit-name="showLinearUnitName"
73
+ :slot-size="slotSize"
74
+ :text-align="textAlign"
75
+ :value="formatWithCurrency(value)"
76
+ @blur="onInputBlur($event)"
77
+ @focus="focusInput()"
78
+ @input="onInput($event)"
79
+ @keydown.down="decrementValue"
80
+ @keydown.up="incrementValue"
81
+ @keyup.enter="onEnterPress"
82
+ />
83
+ </template>
84
+ </InfoText>
85
+
33
86
  <InputContainer
87
+ v-else
34
88
  v-bind="$attrs"
35
89
  ref="inputField1"
36
90
  :align-items="alignItems"
@@ -50,13 +104,16 @@
50
104
  :has-slot="hasSlot"
51
105
  :has-unit="unitName && !!unitName.length"
52
106
  :input-height="inputHeight"
107
+ :is-border-error-only="isBorderErrorOnly"
53
108
  :is-disabled="disabled"
54
109
  :is-error="isError"
110
+ :is-info-border="isInfoBorder"
55
111
  :is-interactive="isInteractive"
56
112
  :min-width="minWidth"
57
113
  :no-border="noBorder"
58
114
  :placeholder="displayedPlaceholder"
59
115
  :read-only="isReadOnly"
116
+ :readonly="isReadOnly"
60
117
  :show-arrow-controls="showArrowControls"
61
118
  :show-linear-unit-name="showLinearUnitName"
62
119
  :slot-size="slotSize"
@@ -69,6 +126,7 @@
69
126
  @keydown.up="incrementValue"
70
127
  @keyup.enter="onEnterPress"
71
128
  />
129
+
72
130
  <SlotContainer v-if="hasSlot" :is-error="isError" :slot-size="slotSize">
73
131
  <slot></slot>
74
132
  </SlotContainer>
@@ -80,7 +138,7 @@
80
138
  >{{ unitName }}</UnitContainer
81
139
  >
82
140
  <IconWrapper
83
- v-if="isError && !showLinearUnitName"
141
+ v-if="isError && !showLinearUnitName && !isBorderErrorOnly"
84
142
  :margin-right="showSelect ? selectWidth : 0"
85
143
  size="16px"
86
144
  >
@@ -134,7 +192,9 @@
134
192
  </ArrowButton>
135
193
  </ArrowControls>
136
194
  </InputWrapper>
137
- <ErrorMessage v-if="isError">{{ errorMessage }}</ErrorMessage>
195
+ <ErrorMessage v-if="isError && errorMessage">{{
196
+ errorMessage
197
+ }}</ErrorMessage>
138
198
  </Container>
139
199
  </template>
140
200
 
@@ -205,6 +265,8 @@
205
265
  colorMode: String,
206
266
  showArrowControls: Boolean,
207
267
  readOnly: Boolean,
268
+ isBorderErrorOnly: Boolean,
269
+ isInfoBorder: Boolean,
208
270
  }
209
271
 
210
272
  const Container = styled('div', inputProps)`
@@ -217,7 +279,9 @@
217
279
 
218
280
  const InputContainer = styled('input', inputProps)`
219
281
  border: ${(props) =>
220
- props.isError
282
+ props.isInfoBorder
283
+ ? '1px solid ' + props.theme.semanticColors.blue[500]
284
+ : props.isError
221
285
  ? '1px solid ' + props.theme.colors.red
222
286
  : props.noBorder
223
287
  ? 'none'
@@ -236,16 +300,17 @@
236
300
  showLinearUnitName,
237
301
  colorMode,
238
302
  showArrowControls,
303
+ isBorderErrorOnly,
239
304
  }) =>
240
305
  showArrowControls
241
306
  ? '40px'
242
307
  : colorMode === 'transparent'
243
308
  ? '0'
244
309
  : slotSize
245
- ? isError && !showLinearUnitName
310
+ ? isError && !showLinearUnitName && !isBorderErrorOnly
246
311
  ? 'calc(' + slotSize + ' + 24px)'
247
312
  : 'calc(' + slotSize + ' + 10px)'
248
- : isError && !showLinearUnitName
313
+ : isError && !showLinearUnitName && !isBorderErrorOnly
249
314
  ? '24px'
250
315
  : '5px'};
251
316
  border-radius: ${(props) =>
@@ -699,6 +764,18 @@
699
764
  type: Boolean,
700
765
  default: false,
701
766
  },
767
+ isBorderErrorOnly: {
768
+ type: Boolean,
769
+ default: false,
770
+ },
771
+ isInfoBorder: {
772
+ type: Boolean,
773
+ default: false,
774
+ },
775
+ inputInfoText: {
776
+ type: String,
777
+ default: '',
778
+ },
702
779
  },
703
780
  data() {
704
781
  return {
@@ -46,7 +46,6 @@
46
46
  color: ${(props) => (props.color ? props.color : props.theme.colors.black)};
47
47
  font-weight: 600;
48
48
  font-size: ${(props) => (props.fontSize ? props.fontSize : '20px')};
49
- text-transform: ${(props) => (props.uppercase ? 'uppercase' : 'none')};
50
49
  `
51
50
 
52
51
  export default {
@@ -1,92 +0,0 @@
1
- <template>
2
- <Container>
3
- <RCIcon :color="iconColor" :name="iconName" :size="iconSize" />
4
- <TextWrapper>
5
- {{ text }}
6
- <slot></slot>
7
- </TextWrapper>
8
- </Container>
9
- </template>
10
-
11
- <script>
12
- // import InfoText from "@eturnity/eturnity_reusable_components/src/components/infoText"
13
- // import IconTextContent from "@eturnity/eturnity_reusable_components/src/components/infoText/templates/iconTextContent"
14
- //To use:
15
- // <InfoText
16
- // icon-color="red"
17
- // icon-name="error"
18
- // size="20px"
19
- // open-trigger="onClick"
20
- // >
21
- // <IconTextContent
22
- // icon-name="error"
23
- // text="Text"
24
- // icon-size="18px"
25
- // icon-color="red"
26
- // />
27
- // </InfoText>
28
-
29
- import styled from 'vue3-styled-components'
30
- import theme from '../../../assets/theme.js'
31
- import RCIcon from '../../icon'
32
-
33
- const Container = styled('div')`
34
- display: flex;
35
- flex-direction: row;
36
- align-items: flex-start;
37
- gap: 8px;
38
- `
39
-
40
- const TextAttrs = {
41
- fontSize: String,
42
- fontColor: String,
43
- }
44
- const TextWrapper = styled('div', TextAttrs)`
45
- font-size: ${(props) => props.fontSize};
46
- color: ${(props) => props.fontColor};
47
- overflow: hidden;
48
- display: flex;
49
- flex-direction: column;
50
- align-items: center;
51
- gap: 8px;
52
- `
53
-
54
- export default {
55
- name: 'IconTextContent',
56
- components: {
57
- Container,
58
- TextWrapper,
59
- RCIcon,
60
- },
61
- props: {
62
- iconName: {
63
- type: String,
64
- required: true,
65
- },
66
- text: {
67
- type: String,
68
- required: true,
69
- },
70
- iconSize: {
71
- type: String,
72
- required: false,
73
- default: '18px',
74
- },
75
- iconColor: {
76
- type: String,
77
- required: false,
78
- default: '',
79
- },
80
- fontSize: {
81
- type: String,
82
- required: false,
83
- default: '11px',
84
- },
85
- fontColor: {
86
- type: String,
87
- required: false,
88
- default: theme.colors.white,
89
- },
90
- },
91
- }
92
- </script>