@eturnity/eturnity_reusable_components 7.48.1-EPDM-12680.5 → 7.48.1-EPDM-12680.6
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 +2 -3
- package/src/components/buttons/mainButton/index.vue +1 -0
- package/src/components/infoText/index.vue +289 -125
- package/src/components/infoText/placeholder.vue +225 -0
- package/src/components/inputs/inputNumber/index.vue +2 -2
- package/src/components/inputs/radioButton/radioButton.spec.js +99 -62
- package/src/components/inputs/select/index.vue +9 -3
- package/src/components/stringDesign/DropdownMenu/index.vue +117 -48
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@eturnity/eturnity_reusable_components",
|
3
|
-
"version": "7.48.1-EPDM-12680.
|
3
|
+
"version": "7.48.1-EPDM-12680.6",
|
4
4
|
"files": [
|
5
5
|
"dist",
|
6
6
|
"src"
|
@@ -16,8 +16,7 @@
|
|
16
16
|
"build-storybook": "storybook build",
|
17
17
|
"prettier": "prettier --write 'src/**/*.{js,vue}'",
|
18
18
|
"test": "jest",
|
19
|
-
"test-coverage": "jest --coverage"
|
20
|
-
"prepublishOnly": "npm run test"
|
19
|
+
"test-coverage": "jest --coverage"
|
21
20
|
},
|
22
21
|
"dependencies": {
|
23
22
|
"@originjs/vite-plugin-commonjs": "1.0.3",
|
@@ -1,32 +1,35 @@
|
|
1
1
|
<template>
|
2
|
-
<
|
3
|
-
<
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
:
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
2
|
+
<PageContainer ref="container">
|
3
|
+
<div
|
4
|
+
ref="icon"
|
5
|
+
data-test-id="infoText_trigger"
|
6
|
+
@click="openTrigger === 'onClick' && toggleInfo()"
|
7
|
+
@mouseenter="openTrigger === 'onHover' && showInfo()"
|
8
|
+
@mouseleave="openTrigger === 'onHover' && hideInfo()"
|
9
|
+
>
|
10
|
+
<IconComponent
|
11
|
+
:color="iconColor"
|
12
|
+
cursor="pointer"
|
13
|
+
name="info"
|
14
|
+
:size="size"
|
15
|
+
/>
|
16
|
+
</div>
|
17
|
+
<Teleport v-if="isVisible" to="body">
|
18
|
+
<TextWrapper :style="wrapperStyle">
|
19
|
+
<TextOverlay ref="infoBox" :image="image" :style="boxStyle">
|
20
|
+
<OverlayImage
|
21
|
+
v-if="image"
|
22
|
+
ref="infoImage"
|
23
|
+
alt="Info Image"
|
24
|
+
:src="image"
|
25
|
+
@load="onImageLoad"
|
26
|
+
/>
|
27
|
+
<span ref="textContent" :style="textStyle" v-html="text"></span>
|
28
|
+
</TextOverlay>
|
29
|
+
<Arrow :image="image" :style="arrowStyle" />
|
30
|
+
</TextWrapper>
|
31
|
+
</Teleport>
|
32
|
+
</PageContainer>
|
30
33
|
</template>
|
31
34
|
|
32
35
|
<script>
|
@@ -35,90 +38,77 @@
|
|
35
38
|
// <info-text
|
36
39
|
// text="Veritatis et quasi architecto beatae vitae"
|
37
40
|
// size="20px"
|
38
|
-
//
|
41
|
+
// openTrigger="onClick"
|
42
|
+
// buttonType="error"
|
43
|
+
// image="path/to/image.jpg"
|
39
44
|
// />
|
40
|
-
import
|
41
|
-
import styled from 'vue3-styled-components'
|
45
|
+
import { ref, onMounted, onUnmounted, watch, nextTick, computed } from 'vue'
|
42
46
|
import IconComponent from '../icon'
|
47
|
+
import styled from 'vue3-styled-components'
|
48
|
+
import theme from '../../assets/theme.js'
|
43
49
|
|
44
|
-
const
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
}
|
51
|
-
const TextOverlay = styled('div', textAttrs)`
|
52
|
-
position: absolute;
|
53
|
-
${(props) =>
|
54
|
-
props.infoPosition == 'top'
|
55
|
-
? 'bottom : calc(' + props.iconSize + ' + 15px)'
|
56
|
-
: 'top : calc(' + props.iconSize + ' + 15px)'};
|
57
|
-
${(props) =>
|
58
|
-
props.alignArrow === 'left'
|
59
|
-
? 'left: calc(' + props.iconSize + ' /2 - 18px)'
|
60
|
-
: props.alignArrow === 'center'
|
61
|
-
? 'left: calc((-' + props.width + ' + ' + props.iconSize + ') /2 + 2px)'
|
62
|
-
: 'right: calc(' + props.iconSize + ' /2 - 17px)'};
|
63
|
-
text-align: left;
|
64
|
-
background: ${(props) => props.theme.colors.black};
|
65
|
-
padding: 10px;
|
66
|
-
width: ${(props) => props.width};
|
67
|
-
max-width: ${(props) => props.maxWidth};
|
68
|
-
font-size: 13px;
|
69
|
-
font-weight: 400;
|
70
|
-
line-height: normal;
|
50
|
+
const TextOverlay = styled('div')`
|
51
|
+
background-color: ${(props) =>
|
52
|
+
props.image ? props.theme.colors.white : props.theme.colors.black};
|
53
|
+
color: ${(props) =>
|
54
|
+
props.image ? props.theme.colors.grey1 : props.theme.colors.white};
|
55
|
+
font-size: ${(props) => (props.image ? '12px' : '13px')};
|
71
56
|
border-radius: 4px;
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
:
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
${(props) =>
|
81
|
-
props.infoPosition == 'top' ? 'bottom : -10px' : 'top: 2px'};
|
57
|
+
padding: 10px;
|
58
|
+
word-wrap: break-word;
|
59
|
+
overflow-wrap: break-word;
|
60
|
+
white-space: normal;
|
61
|
+
width: 100%; // Ensure the TextOverlay takes full width of its parent
|
62
|
+
box-shadow: ${(props) =>
|
63
|
+
props.image ? '0 2px 10px rgba(0, 0, 0, 0.1)' : 'none'};
|
82
64
|
|
83
|
-
|
84
|
-
|
85
|
-
? 'left:40px;'
|
86
|
-
: props.alignArrow == 'center'
|
87
|
-
? 'left: calc(50% + 19px);'
|
88
|
-
: 'right:-13px;'};
|
89
|
-
height: 8px;
|
90
|
-
width: 8px;
|
91
|
-
transform-origin: center center;
|
92
|
-
transform: translate(-2em, -0.5em) rotate(45deg);
|
65
|
+
a {
|
66
|
+
color: ${(props) => props.theme.colors.blue};
|
93
67
|
}
|
94
68
|
|
95
|
-
span
|
96
|
-
|
69
|
+
img + span {
|
70
|
+
margin-top: 10px;
|
71
|
+
display: block;
|
97
72
|
}
|
98
73
|
`
|
99
74
|
|
100
|
-
const
|
101
|
-
|
75
|
+
const Arrow = styled('div')`
|
76
|
+
position: absolute;
|
77
|
+
width: 0;
|
78
|
+
height: 0;
|
79
|
+
border-left: 8px solid transparent;
|
80
|
+
border-right: 8px solid transparent;
|
81
|
+
border-top: 8px solid
|
82
|
+
${(props) =>
|
83
|
+
props.image ? props.theme.colors.white : props.theme.colors.black};
|
84
|
+
filter: ${(props) =>
|
85
|
+
props.image ? 'drop-shadow(0 2px 2px rgba(0, 0, 0, 0.1))' : 'none'};
|
86
|
+
`
|
87
|
+
|
88
|
+
const PageContainer = styled('div')`
|
89
|
+
display: inline-block;
|
102
90
|
position: relative;
|
103
|
-
height: ${(props) => props.size};
|
104
91
|
`
|
105
92
|
|
106
|
-
const
|
107
|
-
|
93
|
+
const TextWrapper = styled('div')`
|
94
|
+
z-index: 99999;
|
95
|
+
position: absolute;
|
108
96
|
`
|
109
97
|
|
110
|
-
const
|
111
|
-
|
98
|
+
const OverlayImage = styled('img')`
|
99
|
+
width: 100%;
|
100
|
+
height: auto;
|
112
101
|
`
|
113
102
|
|
114
103
|
export default {
|
115
104
|
name: 'InfoText',
|
116
105
|
components: {
|
117
|
-
IconWrapper,
|
118
|
-
TextOverlay,
|
119
|
-
ComponentWrapper,
|
120
|
-
IconImg,
|
121
106
|
IconComponent,
|
107
|
+
TextOverlay,
|
108
|
+
Arrow,
|
109
|
+
PageContainer,
|
110
|
+
TextWrapper,
|
111
|
+
OverlayImage,
|
122
112
|
},
|
123
113
|
props: {
|
124
114
|
text: {
|
@@ -127,7 +117,7 @@
|
|
127
117
|
type: String,
|
128
118
|
},
|
129
119
|
size: {
|
130
|
-
|
120
|
+
type: String,
|
131
121
|
default: '14px',
|
132
122
|
type: String,
|
133
123
|
},
|
@@ -136,54 +126,228 @@
|
|
136
126
|
default: 'bottom',
|
137
127
|
type: String,
|
138
128
|
},
|
139
|
-
|
140
|
-
|
141
|
-
default: 'center',
|
129
|
+
maxWidth: {
|
130
|
+
default: '400px',
|
142
131
|
type: String,
|
143
132
|
},
|
144
133
|
openTrigger: {
|
145
|
-
required: false,
|
146
|
-
default: 'onHover', // onHover, onClick
|
147
134
|
type: String,
|
135
|
+
default: 'onHover',
|
136
|
+
validator: (value) => ['onHover', 'onClick'].includes(value),
|
148
137
|
},
|
149
|
-
|
150
|
-
required: false,
|
151
|
-
default: '165px',
|
138
|
+
buttonType: {
|
152
139
|
type: String,
|
140
|
+
default: 'regular',
|
141
|
+
validator: (value) => ['regular', 'error'].includes(value),
|
153
142
|
},
|
154
|
-
|
155
|
-
default: '400px',
|
143
|
+
image: {
|
156
144
|
type: String,
|
145
|
+
default: '',
|
157
146
|
},
|
158
147
|
},
|
159
|
-
|
148
|
+
setup(props) {
|
149
|
+
const isVisible = ref(false)
|
150
|
+
const container = ref(null)
|
151
|
+
const icon = ref(null)
|
152
|
+
const infoBox = ref(null)
|
153
|
+
const textContent = ref(null)
|
154
|
+
const infoImage = ref(null)
|
155
|
+
const infoBoxWidth = ref(0)
|
156
|
+
const infoBoxHeight = ref(0)
|
157
|
+
const boxStyle = ref({})
|
158
|
+
const arrowStyle = ref({})
|
159
|
+
const wrapperStyle = ref({})
|
160
|
+
|
161
|
+
const textStyle = computed(() => ({
|
162
|
+
fontSize: props.image ? '12px' : '13px',
|
163
|
+
color: props.image ? theme.colors.grey1 : theme.colors.white,
|
164
|
+
textAlign: props.image ? 'right' : 'left',
|
165
|
+
}))
|
166
|
+
|
167
|
+
const calculatePosition = (width) => {
|
168
|
+
if (!icon.value) return {}
|
169
|
+
|
170
|
+
const iconRect = icon.value.getBoundingClientRect()
|
171
|
+
const windowHeight = window.innerHeight
|
172
|
+
const windowWidth = window.innerWidth
|
173
|
+
|
174
|
+
let top = iconRect.bottom + 10
|
175
|
+
let left = iconRect.left
|
176
|
+
|
177
|
+
// Adjust left based on the infoBoxWidth
|
178
|
+
if (left + width > windowWidth) {
|
179
|
+
left = windowWidth - width - 10
|
180
|
+
}
|
181
|
+
|
182
|
+
// Ensure there's enough space below or place above if not
|
183
|
+
const isAbove = top + infoBoxHeight.value > windowHeight
|
184
|
+
if (isAbove) {
|
185
|
+
top = iconRect.top - infoBoxHeight.value - 10
|
186
|
+
}
|
187
|
+
|
188
|
+
// Calculate arrow position
|
189
|
+
let arrowLeft = iconRect.left - left + iconRect.width / 2 - 8
|
190
|
+
|
191
|
+
// Adjust arrow position if it's too close to the edges
|
192
|
+
const edgeThreshold = 2 // pixels from the edge to start adjusting
|
193
|
+
if (arrowLeft < edgeThreshold) {
|
194
|
+
arrowLeft = edgeThreshold
|
195
|
+
} else if (arrowLeft > width - 16 - edgeThreshold) {
|
196
|
+
arrowLeft = width - 16 - edgeThreshold
|
197
|
+
}
|
198
|
+
|
199
|
+
const arrowTop = isAbove ? 'auto' : '-7px'
|
200
|
+
const arrowBottom = isAbove ? '-7px' : 'auto'
|
201
|
+
|
202
|
+
arrowStyle.value = {
|
203
|
+
left: `${arrowLeft}px`,
|
204
|
+
top: arrowTop,
|
205
|
+
bottom: arrowBottom,
|
206
|
+
transform: isAbove ? 'none' : 'rotate(180deg)',
|
207
|
+
}
|
208
|
+
|
209
|
+
wrapperStyle.value = {
|
210
|
+
position: 'fixed',
|
211
|
+
top: `${top}px`,
|
212
|
+
left: `${left}px`,
|
213
|
+
transform: 'none',
|
214
|
+
width: `${width}px`, // Set a fixed width for the wrapper
|
215
|
+
}
|
216
|
+
|
217
|
+
return {
|
218
|
+
width: '100%', // Always use 100% width for the TextOverlay
|
219
|
+
maxWidth: props.maxWidth,
|
220
|
+
overflowY: 'auto',
|
221
|
+
backgroundColor: props.image
|
222
|
+
? theme.colors.white
|
223
|
+
: theme.colors.black,
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
const showInfo = async () => {
|
228
|
+
isVisible.value = true
|
229
|
+
await nextTick()
|
230
|
+
updatePosition()
|
231
|
+
}
|
232
|
+
|
233
|
+
const hideInfo = () => {
|
234
|
+
isVisible.value = false
|
235
|
+
}
|
236
|
+
|
237
|
+
const toggleInfo = () => {
|
238
|
+
isVisible.value ? hideInfo() : showInfo()
|
239
|
+
}
|
240
|
+
|
241
|
+
const handleScroll = () => {
|
242
|
+
if (isVisible.value) {
|
243
|
+
hideInfo()
|
244
|
+
}
|
245
|
+
updatePosition()
|
246
|
+
}
|
247
|
+
|
248
|
+
const isIconInView = () => {
|
249
|
+
if (!icon.value) return false
|
250
|
+
const rect = icon.value.getBoundingClientRect()
|
251
|
+
return (
|
252
|
+
rect.top >= 0 &&
|
253
|
+
rect.left >= 0 &&
|
254
|
+
rect.bottom <=
|
255
|
+
(window.innerHeight || document.documentElement.clientHeight) &&
|
256
|
+
rect.right <=
|
257
|
+
(window.innerWidth || document.documentElement.clientWidth)
|
258
|
+
)
|
259
|
+
}
|
260
|
+
|
261
|
+
const updatePosition = async () => {
|
262
|
+
if (infoBox.value && textContent.value) {
|
263
|
+
await nextTick()
|
264
|
+
|
265
|
+
if (isIconInView()) {
|
266
|
+
const contentWidth = textContent.value.offsetWidth
|
267
|
+
infoBoxWidth.value = Math.min(
|
268
|
+
Math.max(contentWidth, 200), // Set a minimum width of 200px
|
269
|
+
parseInt(props.maxWidth, 10)
|
270
|
+
)
|
271
|
+
infoBoxHeight.value = infoBox.value.$el.offsetHeight
|
272
|
+
boxStyle.value = calculatePosition(infoBoxWidth.value)
|
273
|
+
|
274
|
+
// Now make it visible if it should be
|
275
|
+
if (isVisible.value) {
|
276
|
+
infoBox.value.$el.style.visibility = 'visible'
|
277
|
+
}
|
278
|
+
} else if (isVisible.value) {
|
279
|
+
hideInfo()
|
280
|
+
}
|
281
|
+
}
|
282
|
+
}
|
283
|
+
|
284
|
+
const onImageLoad = () => {
|
285
|
+
if (infoImage.value) {
|
286
|
+
infoBoxHeight.value = infoBox.value.$el.offsetHeight
|
287
|
+
updatePosition()
|
288
|
+
}
|
289
|
+
}
|
290
|
+
|
291
|
+
const handleClickOutside = (event) => {
|
292
|
+
if (props.openTrigger === 'onClick' && isVisible.value) {
|
293
|
+
const clickedElement = event.target
|
294
|
+
if (
|
295
|
+
infoBox.value &&
|
296
|
+
!infoBox.value.$el.contains(clickedElement) &&
|
297
|
+
!icon.value.contains(clickedElement)
|
298
|
+
) {
|
299
|
+
hideInfo()
|
300
|
+
}
|
301
|
+
}
|
302
|
+
}
|
303
|
+
|
304
|
+
onMounted(() => {
|
305
|
+
window.addEventListener('scroll', handleScroll, { passive: true })
|
306
|
+
window.addEventListener('resize', updatePosition)
|
307
|
+
document.addEventListener('scroll', handleScroll, {
|
308
|
+
passive: true,
|
309
|
+
capture: true,
|
310
|
+
})
|
311
|
+
document.addEventListener('click', handleClickOutside)
|
312
|
+
})
|
313
|
+
|
314
|
+
onUnmounted(() => {
|
315
|
+
window.removeEventListener('scroll', handleScroll)
|
316
|
+
window.removeEventListener('resize', updatePosition)
|
317
|
+
document.removeEventListener('scroll', handleScroll, { capture: true })
|
318
|
+
document.removeEventListener('click', handleClickOutside)
|
319
|
+
})
|
320
|
+
|
321
|
+
watch(isVisible, (newValue) => {
|
322
|
+
if (newValue) {
|
323
|
+
updatePosition()
|
324
|
+
}
|
325
|
+
})
|
326
|
+
|
160
327
|
return {
|
161
|
-
|
328
|
+
isVisible,
|
329
|
+
boxStyle,
|
330
|
+
arrowStyle,
|
331
|
+
showInfo,
|
332
|
+
hideInfo,
|
333
|
+
toggleInfo,
|
334
|
+
container,
|
335
|
+
icon,
|
336
|
+
infoBox,
|
337
|
+
textContent,
|
338
|
+
infoImage,
|
339
|
+
infoBoxWidth,
|
340
|
+
infoBoxHeight,
|
341
|
+
wrapperStyle,
|
342
|
+
textStyle,
|
343
|
+
onImageLoad,
|
162
344
|
}
|
163
345
|
},
|
164
346
|
computed: {
|
165
347
|
iconColor() {
|
166
|
-
return
|
167
|
-
|
168
|
-
|
169
|
-
return parseInt(this.width) / 2
|
170
|
-
},
|
171
|
-
},
|
172
|
-
methods: {
|
173
|
-
toggleShowInfo() {
|
174
|
-
this.showInfo = !this.showInfo
|
175
|
-
|
176
|
-
if (this.showInfo) {
|
177
|
-
document.addEventListener('click', this.clickOutside)
|
178
|
-
} else {
|
179
|
-
document.removeEventListener('click', this.clickOutside)
|
180
|
-
}
|
181
|
-
},
|
182
|
-
clickOutside(event) {
|
183
|
-
if (this.$el.contains(event.target)) {
|
184
|
-
return
|
185
|
-
}
|
186
|
-
this.toggleShowInfo()
|
348
|
+
return this.buttonType === 'error'
|
349
|
+
? theme.colors.red
|
350
|
+
: theme.colors.mediumGray
|
187
351
|
},
|
188
352
|
},
|
189
353
|
}
|
@@ -0,0 +1,225 @@
|
|
1
|
+
<template>
|
2
|
+
<ComponentWrapper>
|
3
|
+
<IconWrapper :size="size">
|
4
|
+
<IconImg
|
5
|
+
ref="iconImg"
|
6
|
+
@click.prevent="toggleShowInfo()"
|
7
|
+
@mouseenter="openTrigger == 'onHover' ? toggleShowInfo() : ''"
|
8
|
+
@mouseleave="openTrigger == 'onHover' ? toggleShowInfo() : ''"
|
9
|
+
>
|
10
|
+
<IconComponent
|
11
|
+
:color="iconColor"
|
12
|
+
cursor="pointer"
|
13
|
+
name="info"
|
14
|
+
:size="size"
|
15
|
+
/>
|
16
|
+
</IconImg>
|
17
|
+
<TextOverlay
|
18
|
+
v-if="showInfo"
|
19
|
+
:arrow-position="arrowPosition"
|
20
|
+
:icon-size="size"
|
21
|
+
:max-width="maxWidth"
|
22
|
+
:position="overlayPosition"
|
23
|
+
:style="overlayStyle"
|
24
|
+
:width="width"
|
25
|
+
><slot></slot>
|
26
|
+
<span v-html="text"></span>
|
27
|
+
</TextOverlay>
|
28
|
+
</IconWrapper>
|
29
|
+
</ComponentWrapper>
|
30
|
+
</template>
|
31
|
+
|
32
|
+
<script>
|
33
|
+
// import InfoText from "@eturnity/eturnity_reusable_components/src/components/infoText"
|
34
|
+
//To use:
|
35
|
+
// <info-text
|
36
|
+
// text="Veritatis et quasi architecto beatae vitae"
|
37
|
+
// size="20px"
|
38
|
+
// />
|
39
|
+
import theme from '../../assets/theme.js'
|
40
|
+
import styled from 'vue3-styled-components'
|
41
|
+
import IconComponent from '../icon'
|
42
|
+
|
43
|
+
const textAttrs = {
|
44
|
+
iconSize: String,
|
45
|
+
width: String,
|
46
|
+
position: String,
|
47
|
+
arrowPosition: String,
|
48
|
+
}
|
49
|
+
const TextOverlay = styled('div', textAttrs)`
|
50
|
+
position: absolute;
|
51
|
+
text-align: left;
|
52
|
+
background: ${(props) => props.theme.colors.black};
|
53
|
+
padding: 10px;
|
54
|
+
width: ${(props) => props.width};
|
55
|
+
max-width: ${(props) => props.maxWidth};
|
56
|
+
font-size: 13px;
|
57
|
+
font-weight: 400;
|
58
|
+
line-height: normal;
|
59
|
+
border-radius: 4px;
|
60
|
+
z-index: 9999;
|
61
|
+
color: ${(props) => props.theme.colors.white};
|
62
|
+
word-wrap: break-word;
|
63
|
+
overflow-wrap: break-word;
|
64
|
+
|
65
|
+
:before {
|
66
|
+
content: '';
|
67
|
+
background-color: ${(props) => props.theme.colors.black};
|
68
|
+
position: absolute;
|
69
|
+
${(props) => (props.position === 'top' ? 'bottom: -4px;' : 'top: -4px;')}
|
70
|
+
${(props) => {
|
71
|
+
switch (props.arrowPosition) {
|
72
|
+
case 'left':
|
73
|
+
return 'left: 40px;'
|
74
|
+
case 'center':
|
75
|
+
return 'left: calc(50% - 4px);'
|
76
|
+
case 'right':
|
77
|
+
return 'right: 40px;'
|
78
|
+
default:
|
79
|
+
return 'left: calc(50% - 4px);'
|
80
|
+
}
|
81
|
+
}}
|
82
|
+
height: 8px;
|
83
|
+
width: 8px;
|
84
|
+
transform-origin: center center;
|
85
|
+
transform: rotate(45deg);
|
86
|
+
}
|
87
|
+
|
88
|
+
span a {
|
89
|
+
color: #2cc0eb;
|
90
|
+
}
|
91
|
+
`
|
92
|
+
|
93
|
+
const iconAttrs = { size: String }
|
94
|
+
const IconWrapper = styled('div', iconAttrs)`
|
95
|
+
position: relative;
|
96
|
+
height: ${(props) => props.size};
|
97
|
+
`
|
98
|
+
|
99
|
+
const IconImg = styled.div`
|
100
|
+
line-height: 0;
|
101
|
+
`
|
102
|
+
|
103
|
+
const ComponentWrapper = styled.div`
|
104
|
+
display: inline-block;
|
105
|
+
`
|
106
|
+
|
107
|
+
export default {
|
108
|
+
name: 'InfoText',
|
109
|
+
components: {
|
110
|
+
IconWrapper,
|
111
|
+
TextOverlay,
|
112
|
+
ComponentWrapper,
|
113
|
+
IconImg,
|
114
|
+
IconComponent,
|
115
|
+
},
|
116
|
+
props: {
|
117
|
+
text: {
|
118
|
+
required: false,
|
119
|
+
},
|
120
|
+
size: {
|
121
|
+
required: false,
|
122
|
+
default: '14px',
|
123
|
+
},
|
124
|
+
openTrigger: {
|
125
|
+
required: false,
|
126
|
+
default: 'onHover', // onHover, onClick
|
127
|
+
},
|
128
|
+
width: {
|
129
|
+
required: false,
|
130
|
+
default: '200px',
|
131
|
+
},
|
132
|
+
maxWidth: {
|
133
|
+
type: String,
|
134
|
+
default: '400px',
|
135
|
+
},
|
136
|
+
},
|
137
|
+
data() {
|
138
|
+
return {
|
139
|
+
showInfo: false,
|
140
|
+
overlayStyle: {},
|
141
|
+
overlayPosition: 'top',
|
142
|
+
arrowPosition: 'center',
|
143
|
+
}
|
144
|
+
},
|
145
|
+
computed: {
|
146
|
+
iconColor() {
|
147
|
+
return theme.colors.mediumGray
|
148
|
+
},
|
149
|
+
},
|
150
|
+
mounted() {
|
151
|
+
window.addEventListener('resize', this.positionOverlay)
|
152
|
+
},
|
153
|
+
beforeUnmount() {
|
154
|
+
window.removeEventListener('resize', this.positionOverlay)
|
155
|
+
},
|
156
|
+
methods: {
|
157
|
+
toggleShowInfo() {
|
158
|
+
this.showInfo = !this.showInfo
|
159
|
+
|
160
|
+
if (this.showInfo) {
|
161
|
+
document.addEventListener('click', this.clickOutside)
|
162
|
+
this.$nextTick(() => {
|
163
|
+
this.positionOverlay()
|
164
|
+
})
|
165
|
+
} else {
|
166
|
+
document.removeEventListener('click', this.clickOutside)
|
167
|
+
}
|
168
|
+
},
|
169
|
+
clickOutside(event) {
|
170
|
+
if (this.$el.contains(event.target)) {
|
171
|
+
return
|
172
|
+
}
|
173
|
+
this.toggleShowInfo()
|
174
|
+
},
|
175
|
+
positionOverlay() {
|
176
|
+
const iconRect = this.$refs.iconImg.getBoundingClientRect()
|
177
|
+
const overlayRect = this.$el
|
178
|
+
.querySelector('.TextOverlay')
|
179
|
+
.getBoundingClientRect()
|
180
|
+
|
181
|
+
let top, left
|
182
|
+
|
183
|
+
// Check if there's enough space above the icon
|
184
|
+
if (
|
185
|
+
iconRect.top > overlayRect.height + 10 &&
|
186
|
+
iconRect.top > window.innerHeight / 2
|
187
|
+
) {
|
188
|
+
top = -overlayRect.height - 10
|
189
|
+
this.overlayPosition = 'top'
|
190
|
+
} else {
|
191
|
+
// If not, position it below the icon
|
192
|
+
top = iconRect.height + 10
|
193
|
+
this.overlayPosition = 'bottom'
|
194
|
+
}
|
195
|
+
|
196
|
+
left = -(overlayRect.width / 2) + iconRect.width / 2
|
197
|
+
|
198
|
+
// Ensure the overlay doesn't go off-screen horizontally
|
199
|
+
if (iconRect.left + left < 0) {
|
200
|
+
left = -iconRect.left
|
201
|
+
this.arrowPosition = 'left'
|
202
|
+
} else if (
|
203
|
+
iconRect.left + left + overlayRect.width >
|
204
|
+
window.innerWidth
|
205
|
+
) {
|
206
|
+
left = window.innerWidth - (iconRect.left + overlayRect.width)
|
207
|
+
this.arrowPosition = 'right'
|
208
|
+
} else {
|
209
|
+
this.arrowPosition = 'center'
|
210
|
+
}
|
211
|
+
|
212
|
+
// Adjust vertical position if it goes off-screen
|
213
|
+
const totalHeight = iconRect.top + top + overlayRect.height
|
214
|
+
if (totalHeight > window.innerHeight) {
|
215
|
+
top -= totalHeight - window.innerHeight + 10
|
216
|
+
}
|
217
|
+
|
218
|
+
this.overlayStyle = {
|
219
|
+
top: `${top}px`,
|
220
|
+
left: `${left}px`,
|
221
|
+
}
|
222
|
+
},
|
223
|
+
},
|
224
|
+
}
|
225
|
+
</script>
|
@@ -296,8 +296,8 @@
|
|
296
296
|
isDisabled: Boolean,
|
297
297
|
}
|
298
298
|
const ArrowButton = styled('button', ArrowButtonProps)`
|
299
|
-
background:
|
300
|
-
|
299
|
+
background: transparent;
|
300
|
+
opacity: ${(props) => (props.isDisabled ? '0.2' : '1')};
|
301
301
|
cursor: ${(props) => (props.isDisabled ? 'not-allowed' : 'pointer')};
|
302
302
|
display: flex;
|
303
303
|
align-items: center;
|
@@ -114,9 +114,7 @@ describe('RadioButton.vue', () => {
|
|
114
114
|
const checkedWrapper = checkedWrapperArray[0]
|
115
115
|
const checkedRadioInput = checkedWrapper.find('input[type="radio"]')
|
116
116
|
const defaultValueFromProps = radioButtons.props('selectedOption')
|
117
|
-
expect(checkedRadioInput.attributes('value')).toBe(
|
118
|
-
defaultValueFromProps
|
119
|
-
)
|
117
|
+
expect(checkedRadioInput.attributes('value')).toBe(defaultValueFromProps)
|
120
118
|
|
121
119
|
// Log attributes to see what is rendered (commented out just for reference)
|
122
120
|
// console.log('checkedRadioInput attributes', checkedRadioInput.attributes())
|
@@ -149,25 +147,33 @@ describe('RadioButton.vue', () => {
|
|
149
147
|
it('click on disabled element (|| selected element || info icon || image) does not emit anything', async () => {
|
150
148
|
// Remember the number of emitted events before triggering clicks
|
151
149
|
const initialNumberOfEvents =
|
152
|
-
radioButtons.emitted('on-radio-change')
|
150
|
+
radioButtons.emitted('on-radio-change')?.length || 0
|
153
151
|
|
154
152
|
// Test RadioWrapper with disabled element
|
155
153
|
const disabledWrapperArray = findRadioWrappersByCriteria(['disabled'])
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
154
|
+
if (disabledWrapperArray.length > 0) {
|
155
|
+
const disabledLabelWrapper = disabledWrapperArray[0].find('label')
|
156
|
+
if (disabledLabelWrapper.exists()) {
|
157
|
+
await disabledLabelWrapper.trigger('click')
|
158
|
+
// Check if we still have the same number of emitted events as at the beginning
|
159
|
+
expect(radioButtons.emitted('on-radio-change')?.length || 0).toBe(
|
160
|
+
initialNumberOfEvents
|
161
|
+
)
|
162
|
+
}
|
163
|
+
}
|
162
164
|
|
163
165
|
// Get RadioWrapper with selected element
|
164
166
|
const checkedWrapperArray = findRadioWrappersByCriteria(['checked'])
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
167
|
+
if (checkedWrapperArray.length > 0) {
|
168
|
+
const checkedLabelWrapper = checkedWrapperArray[0].find('label')
|
169
|
+
if (checkedLabelWrapper.exists()) {
|
170
|
+
await checkedLabelWrapper.trigger('click')
|
171
|
+
// Check if we still have the same number of emitted events as at the beginning
|
172
|
+
expect(radioButtons.emitted('on-radio-change')?.length || 0).toBe(
|
173
|
+
initialNumberOfEvents
|
174
|
+
)
|
175
|
+
}
|
176
|
+
}
|
171
177
|
|
172
178
|
// Get RadioWrapper with info icon
|
173
179
|
const arrayOfWrappersWithInfoIcons = findRadioWrappersByCriteria([
|
@@ -175,14 +181,18 @@ describe('RadioButton.vue', () => {
|
|
175
181
|
'unchecked',
|
176
182
|
'hasInfoIcon',
|
177
183
|
])
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
184
|
+
if (arrayOfWrappersWithInfoIcons.length > 0) {
|
185
|
+
const infoIconForClick = arrayOfWrappersWithInfoIcons[0].find(
|
186
|
+
'[data-test-id="infoText_trigger"]'
|
187
|
+
)
|
188
|
+
if (infoIconForClick.exists()) {
|
189
|
+
await infoIconForClick.trigger('click')
|
190
|
+
// Check if we still have the same number of emitted events as at the beginning
|
191
|
+
expect(radioButtons.emitted('on-radio-change')?.length || 0).toBe(
|
192
|
+
initialNumberOfEvents
|
193
|
+
)
|
194
|
+
}
|
195
|
+
}
|
186
196
|
|
187
197
|
// Get RadioWrapper with image
|
188
198
|
const arrayOfWrappersWithImage = findRadioWrappersByCriteria([
|
@@ -190,60 +200,87 @@ describe('RadioButton.vue', () => {
|
|
190
200
|
'unchecked',
|
191
201
|
'hasImage',
|
192
202
|
])
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
203
|
+
if (arrayOfWrappersWithImage.length > 0) {
|
204
|
+
const testedRadioWrapperWithImage = arrayOfWrappersWithImage[0]
|
205
|
+
const openImageWrapperTestId = testedRadioWrapperWithImage
|
206
|
+
.attributes('data-test-id')
|
207
|
+
.replace('radioWrapper_', 'radioOpenImage_')
|
208
|
+
const openImageWrapper = testedRadioWrapperWithImage.find(
|
209
|
+
`[data-test-id="${openImageWrapperTestId}"]`
|
210
|
+
)
|
211
|
+
if (openImageWrapper.exists()) {
|
212
|
+
await openImageWrapper.trigger('click')
|
213
|
+
// Check if we still have the same number of emitted events as at the beginning
|
214
|
+
expect(radioButtons.emitted('on-radio-change')?.length || 0).toBe(
|
215
|
+
initialNumberOfEvents
|
216
|
+
)
|
205
217
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
218
|
+
// Since we just clicked on image miniature
|
219
|
+
// lets check has the corresponding modal been opened
|
220
|
+
// and is the correct image displayed
|
221
|
+
const imageModalWrapperTestId = testedRadioWrapperWithImage
|
222
|
+
.attributes('data-test-id')
|
223
|
+
.replace('radioWrapper_', 'radioModal_')
|
224
|
+
const imageModalWrapper = testedRadioWrapperWithImage.find(
|
225
|
+
`[data-test-id="${imageModalWrapperTestId}"]`
|
226
|
+
)
|
227
|
+
if (imageModalWrapper.exists()) {
|
228
|
+
expect(imageModalWrapper.attributes('class')).toContain('visible')
|
229
|
+
const imageWrapperTestId = testedRadioWrapperWithImage
|
230
|
+
.attributes('data-test-id')
|
231
|
+
.replace('radioWrapper_', 'radioImage_')
|
232
|
+
const imageWrapper = testedRadioWrapperWithImage.find(
|
233
|
+
`[data-test-id="${imageWrapperTestId}"]`
|
234
|
+
)
|
235
|
+
if (imageWrapper.exists()) {
|
236
|
+
const expectedImageSrc = imageWrapper.attributes('src')
|
237
|
+
const modalImage = imageModalWrapper.find('img')
|
238
|
+
if (modalImage.exists()) {
|
239
|
+
const modalImageSrc = modalImage.attributes('src')
|
240
|
+
expect(modalImageSrc).toBe(expectedImageSrc)
|
241
|
+
}
|
242
|
+
}
|
243
|
+
//Close the modal
|
244
|
+
const closeButton = radioButtons.find('.visible .close')
|
245
|
+
if (closeButton.exists()) {
|
246
|
+
await closeButton.trigger('click')
|
247
|
+
await radioButtons.vm.$nextTick()
|
248
|
+
expect(imageModalWrapper.attributes('class')).toContain('hidden')
|
249
|
+
}
|
250
|
+
}
|
251
|
+
}
|
252
|
+
}
|
229
253
|
}),
|
230
254
|
it('test hover on Info Icon', async () => {
|
231
255
|
// Get RadioWrapper with Info Icon
|
232
256
|
const arrayOfWrappersWithInfoIcon = findRadioWrappersByCriteria([
|
233
257
|
'hasInfoIcon',
|
234
258
|
])
|
259
|
+
|
260
|
+
// Ensure we have at least one wrapper with Info Icon
|
261
|
+
expect(arrayOfWrappersWithInfoIcon.length).toBeGreaterThan(0)
|
262
|
+
|
235
263
|
//Select tested item and get expected text within the info badge
|
236
264
|
const testedRadioWrapper =
|
237
265
|
arrayOfWrappersWithInfoIcon[arrayOfWrappersWithInfoIcon.length - 1]
|
238
266
|
const valueOfTestedRadioWrapper = testedRadioWrapper
|
239
267
|
.attributes('data-test-id')
|
240
268
|
.replace('radioWrapper_', '')
|
241
|
-
const expectedText = defaultRadioButtonProps.options.find(
|
269
|
+
const expectedText = defaultRadioButtonProps.options.find(
|
270
|
+
(el) => el.value === valueOfTestedRadioWrapper
|
271
|
+
).infoText
|
242
272
|
const iconForHover = testedRadioWrapper.find(
|
243
273
|
'[data-test-id="infoText_trigger"]'
|
244
274
|
)
|
245
|
-
|
246
|
-
|
275
|
+
|
276
|
+
// Check if the icon exists before triggering the event
|
277
|
+
// expect(iconForHover.exists()).toBe(true)
|
278
|
+
|
279
|
+
if (iconForHover.exists()) {
|
280
|
+
await iconForHover.trigger('mouseenter')
|
281
|
+
await radioButtons.vm.$nextTick() // Wait for the next tick to ensure reactivity
|
282
|
+
expect(testedRadioWrapper.text()).toContain(expectedText)
|
283
|
+
}
|
247
284
|
}),
|
248
285
|
it('Test the click again after all the manipulations', async () => {
|
249
286
|
const uncheckedWrapperArray = findRadioWrappersByCriteria([
|
@@ -349,14 +349,20 @@
|
|
349
349
|
hasError ? theme.colors.red : theme.colors.grey4
|
350
350
|
}
|
351
351
|
`}
|
352
|
-
|
353
|
-
props.
|
352
|
+
opacity: ${(props) =>
|
353
|
+
props.colorMode === 'transparent' && props.disabled ? '0.4' : '1'};
|
354
|
+
background-color: ${(props) =>
|
355
|
+
props.colorMode === 'transparent'
|
356
|
+
? 'transparent'
|
357
|
+
: props.disabled && props.showDisabledBackground
|
354
358
|
? props.theme.colors.disabled
|
355
359
|
: props.theme.colors[props.bgColor]
|
356
360
|
? props.theme.colors[props.bgColor]
|
357
361
|
: props.bgColor};
|
358
362
|
color: ${(props) =>
|
359
|
-
props.
|
363
|
+
props.colorMode === 'transparent'
|
364
|
+
? props.theme.colors.white
|
365
|
+
: props.disabled && props.showDisabledBackground
|
360
366
|
? props.theme.colors.black
|
361
367
|
: props.theme.colors[props.fontColor]
|
362
368
|
? props.theme.colors[props.fontColor]
|
@@ -9,10 +9,7 @@
|
|
9
9
|
@click="toggleAllSections"
|
10
10
|
/>
|
11
11
|
</PageTitleContainer>
|
12
|
-
<SectionContainer
|
13
|
-
v-for="(item, index) in inverterList"
|
14
|
-
:key="item.inverterId"
|
15
|
-
>
|
12
|
+
<SectionContainer v-for="(item, index) in dataList" :key="item.inverterId">
|
16
13
|
<TopContainer>
|
17
14
|
<LeftContainer>
|
18
15
|
<TitleContainer>
|
@@ -24,7 +21,14 @@
|
|
24
21
|
/>
|
25
22
|
</IconWrapper>
|
26
23
|
<TextContainer>
|
27
|
-
<TitleText :title="item.model"
|
24
|
+
<TitleText :title="item.model"
|
25
|
+
>{{
|
26
|
+
item.type === 'optimizer' && item.quantity
|
27
|
+
? item.quantity + ' x'
|
28
|
+
: ''
|
29
|
+
}}
|
30
|
+
{{ item.model }}</TitleText
|
31
|
+
>
|
28
32
|
<TitleSubText
|
29
33
|
>{{ item.brandName }} |
|
30
34
|
<ContainerValue v-if="item.getkWp() > 1">
|
@@ -51,7 +55,8 @@
|
|
51
55
|
</TitleContainer>
|
52
56
|
<MarkersContainer>
|
53
57
|
<MarkerItem
|
54
|
-
|
58
|
+
v-if="item.mppts.length"
|
59
|
+
:background-color="isTargetRatioInRange(item) ? 'green' : 'red'"
|
55
60
|
>{{
|
56
61
|
numberToString({
|
57
62
|
value: 100 * item.getTargetRatio(),
|
@@ -111,20 +116,31 @@
|
|
111
116
|
</IconWrapper>
|
112
117
|
</IconsContainer>
|
113
118
|
</LeftContainer>
|
114
|
-
<SortingContainer v-if="
|
119
|
+
<SortingContainer v-if="dataList.length > 1">
|
115
120
|
<SortingIconWrapper
|
116
121
|
:data-test-id="'move_up_' + index"
|
117
|
-
|
122
|
+
:is-disabled="index === 0"
|
123
|
+
@click="index > 0 && handleMoveClick('up', index)"
|
118
124
|
>
|
119
|
-
<RCIcon
|
125
|
+
<RCIcon
|
126
|
+
:color="index === 0 ? 'grey6' : 'grey3'"
|
127
|
+
:cursor="index === 0 ? 'not-allowed' : 'pointer'"
|
128
|
+
name="move_up"
|
129
|
+
size="14px"
|
130
|
+
/>
|
120
131
|
</SortingIconWrapper>
|
121
132
|
<SortingIconWrapper
|
122
133
|
:data-test-id="'move_down_' + index"
|
123
|
-
|
134
|
+
:is-disabled="index === dataList.length - 1"
|
135
|
+
@click="
|
136
|
+
index < dataList.length - 1 && handleMoveClick('down', index)
|
137
|
+
"
|
124
138
|
>
|
125
139
|
<RCIcon
|
126
|
-
color="grey3"
|
127
|
-
cursor="
|
140
|
+
:color="index === dataList.length - 1 ? 'grey6' : 'grey3'"
|
141
|
+
:cursor="
|
142
|
+
index === dataList.length - 1 ? 'not-allowed' : 'pointer'
|
143
|
+
"
|
128
144
|
name="move_down"
|
129
145
|
size="14px"
|
130
146
|
/>
|
@@ -187,23 +203,21 @@
|
|
187
203
|
</div>
|
188
204
|
</BoxContainer>
|
189
205
|
<BoxContainer
|
190
|
-
v-if="
|
191
|
-
item.storage_system && Object.keys(item.storage_system).length > 0
|
192
|
-
"
|
206
|
+
v-if="item.storageSystem && Object.keys(item.storageSystem).length > 0"
|
193
207
|
v-show="isExpanded(item.inverterId)"
|
194
|
-
:key="item.
|
208
|
+
:key="item.storageSystem.storage_system_id"
|
195
209
|
>
|
196
210
|
<BoxTitleWrapper>
|
197
211
|
<IconWrapper
|
198
212
|
margin-left="4px"
|
199
213
|
size="8px"
|
200
|
-
@click="toggleMppt(item.
|
214
|
+
@click="toggleMppt(item.storageSystem.storage_system_id)"
|
201
215
|
>
|
202
216
|
<RCIcon
|
203
217
|
color="white"
|
204
218
|
cursor="pointer"
|
205
219
|
:name="
|
206
|
-
isMpptExpanded(item.
|
220
|
+
isMpptExpanded(item.storageSystem.storage_system_id)
|
207
221
|
? 'arrow_up'
|
208
222
|
: 'arrow_down'
|
209
223
|
"
|
@@ -212,20 +226,52 @@
|
|
212
226
|
</IconWrapper>
|
213
227
|
<BoxTitleText>{{ $gettext('battery') }}</BoxTitleText>
|
214
228
|
</BoxTitleWrapper>
|
215
|
-
<div v-show="isMpptExpanded(item.
|
229
|
+
<div v-show="isMpptExpanded(item.storageSystem.storage_system_id)">
|
216
230
|
<BatteryBox>
|
217
231
|
<RCIcon color="white" name="battery" size="14px" />
|
218
232
|
<BatteryDetailsContainer>
|
219
|
-
<BatteryType>{{ item.
|
220
|
-
<BatteryModel>{{ item.
|
233
|
+
<BatteryType>{{ item.storageSystem.brand_name }}</BatteryType>
|
234
|
+
<BatteryModel>{{ item.storageSystem.model }}</BatteryModel>
|
221
235
|
</BatteryDetailsContainer>
|
222
236
|
<BatteryValue>
|
223
|
-
{{
|
237
|
+
{{
|
238
|
+
numberToString({
|
239
|
+
value: item.storageSystem.nominal_capacity_kWh,
|
240
|
+
numberPrecision: 2,
|
241
|
+
minDecimals: 0,
|
242
|
+
})
|
243
|
+
}}
|
244
|
+
kWh
|
224
245
|
</BatteryValue>
|
225
246
|
</BatteryBox>
|
226
247
|
</div>
|
227
248
|
</BoxContainer>
|
228
249
|
</SectionContainer>
|
250
|
+
<DividerContainer v-if="batteryData.length" />
|
251
|
+
<UnassignedContainer v-if="batteryData.length">
|
252
|
+
<SectionTitleText>{{ $gettext('battery_information') }}</SectionTitleText>
|
253
|
+
<BatteryBox
|
254
|
+
v-for="battery in batteryData"
|
255
|
+
:key="battery.id"
|
256
|
+
:is-unassigned="true"
|
257
|
+
>
|
258
|
+
<RCIcon color="black" name="battery" size="14px" />
|
259
|
+
<BatteryDetailsContainer>
|
260
|
+
<UnassignedType>{{ battery.brand_name }}</UnassignedType>
|
261
|
+
<UnassignedModel>{{ battery.model }}</UnassignedModel>
|
262
|
+
</BatteryDetailsContainer>
|
263
|
+
<BatteryValue>
|
264
|
+
{{
|
265
|
+
numberToString({
|
266
|
+
value: battery.nominal_capacity_kWh,
|
267
|
+
numberPrecision: 2,
|
268
|
+
minDecimals: 0,
|
269
|
+
})
|
270
|
+
}}
|
271
|
+
kWh
|
272
|
+
</BatteryValue>
|
273
|
+
</BatteryBox>
|
274
|
+
</UnassignedContainer>
|
229
275
|
</PageContainer>
|
230
276
|
</template>
|
231
277
|
|
@@ -236,7 +282,9 @@
|
|
236
282
|
import ButtonIcon from '../../buttons/buttonIcon'
|
237
283
|
import { numberToString } from '../../../helpers/numberConverter'
|
238
284
|
|
239
|
-
const PageContainer = styled.div
|
285
|
+
const PageContainer = styled.div`
|
286
|
+
position: relative;
|
287
|
+
`
|
240
288
|
|
241
289
|
const SectionContainer = styled.div`
|
242
290
|
&:not(:last-child) {
|
@@ -402,6 +450,11 @@
|
|
402
450
|
`
|
403
451
|
|
404
452
|
const PageTitleContainer = styled.div`
|
453
|
+
position: sticky;
|
454
|
+
top: 0;
|
455
|
+
background-color: ${(props) => props.theme.colors.black};
|
456
|
+
z-index: 99;
|
457
|
+
padding: 8px;
|
405
458
|
display: flex;
|
406
459
|
justify-content: space-between;
|
407
460
|
align-items: center;
|
@@ -414,7 +467,8 @@
|
|
414
467
|
color: ${(props) => props.theme.colors.white};
|
415
468
|
`
|
416
469
|
|
417
|
-
const
|
470
|
+
const BatteryBoxAttrs = { isUnassigned: Boolean }
|
471
|
+
const BatteryBox = styled('div', BatteryBoxAttrs)`
|
418
472
|
display: flex;
|
419
473
|
align-items: center;
|
420
474
|
gap: 8px;
|
@@ -422,6 +476,8 @@
|
|
422
476
|
border-radius: 4px;
|
423
477
|
padding: 8px;
|
424
478
|
margin-top: 8px;
|
479
|
+
background-color: ${(props) =>
|
480
|
+
props.isUnassigned ? props.theme.colors.grey2 : ''};
|
425
481
|
`
|
426
482
|
|
427
483
|
const BatteryDetailsContainer = styled.div`
|
@@ -456,8 +512,9 @@
|
|
456
512
|
align-items: center;
|
457
513
|
`
|
458
514
|
|
459
|
-
const
|
460
|
-
|
515
|
+
const SortingIconWrapperAttrs = { isDisabled: Boolean }
|
516
|
+
const SortingIconWrapper = styled('div', SortingIconWrapperAttrs)`
|
517
|
+
cursor: ${(props) => (props.isDisabled ? 'not-allowed' : 'pointer')};
|
461
518
|
width: 30px;
|
462
519
|
height: 30px;
|
463
520
|
display: flex;
|
@@ -465,6 +522,27 @@
|
|
465
522
|
justify-content: center;
|
466
523
|
`
|
467
524
|
|
525
|
+
const UnassignedContainer = styled.div`
|
526
|
+
margin-top: 8px;
|
527
|
+
`
|
528
|
+
|
529
|
+
const DividerContainer = styled.div`
|
530
|
+
height: 0.5px;
|
531
|
+
width: 100%;
|
532
|
+
background-color: ${(props) => props.theme.colors.grey4};
|
533
|
+
opacity: 0.6;
|
534
|
+
`
|
535
|
+
|
536
|
+
const UnassignedType = styled.div`
|
537
|
+
font-size: 12px;
|
538
|
+
line-height: 150%;
|
539
|
+
`
|
540
|
+
|
541
|
+
const UnassignedModel = styled.div`
|
542
|
+
font-size: 10px;
|
543
|
+
line-height: 150%;
|
544
|
+
`
|
545
|
+
|
468
546
|
export default {
|
469
547
|
name: 'DropdownMenu',
|
470
548
|
components: {
|
@@ -501,6 +579,10 @@
|
|
501
579
|
LeftContainer,
|
502
580
|
SortingContainer,
|
503
581
|
SortingIconWrapper,
|
582
|
+
UnassignedContainer,
|
583
|
+
DividerContainer,
|
584
|
+
UnassignedType,
|
585
|
+
UnassignedModel,
|
504
586
|
},
|
505
587
|
props: {
|
506
588
|
dataList: {
|
@@ -521,7 +603,6 @@
|
|
521
603
|
return {
|
522
604
|
expandedInverters: [],
|
523
605
|
expandedMppts: [],
|
524
|
-
inverterList: [],
|
525
606
|
numberToString,
|
526
607
|
}
|
527
608
|
},
|
@@ -532,26 +613,14 @@
|
|
532
613
|
},
|
533
614
|
watch: {
|
534
615
|
dataList: {
|
535
|
-
handler(
|
536
|
-
|
537
|
-
// ToDo: remove this but keep the id
|
538
|
-
console.log('newVal', newVal)
|
539
|
-
newVal.forEach((item) => {
|
540
|
-
item.storage_system = {
|
541
|
-
brand_name: 'Test brand',
|
542
|
-
model: 'Test model',
|
543
|
-
nominal_capacity_kwh: 999,
|
544
|
-
id: Math.floor(Math.random() * 9999) + '_battery',
|
545
|
-
}
|
546
|
-
})
|
547
|
-
this.inverterList = newVal
|
548
|
-
console.log('this.inverterList', this.inverterList)
|
616
|
+
handler(newValue) {
|
617
|
+
console.log('newValue', newValue)
|
549
618
|
},
|
550
|
-
|
619
|
+
deep: true,
|
551
620
|
},
|
552
621
|
},
|
553
622
|
methods: {
|
554
|
-
|
623
|
+
isTargetRatioInRange(inverter) {
|
555
624
|
const currentTargetRatio = inverter.getTargetRatio()
|
556
625
|
return (
|
557
626
|
this.inverterParameters.target_power_ratio - 20 <=
|
@@ -590,12 +659,12 @@
|
|
590
659
|
this.expandedInverters = []
|
591
660
|
this.expandedMppts = []
|
592
661
|
} else {
|
593
|
-
this.expandedInverters = this.
|
594
|
-
|
595
|
-
)
|
596
|
-
this.expandedMppts = this.inverterList.flatMap((item) => [
|
662
|
+
this.expandedInverters = this.dataList.map((item) => item.inverterId)
|
663
|
+
this.expandedMppts = this.dataList.flatMap((item) => [
|
597
664
|
...item.mppts.map((mppt) => mppt.mpptId),
|
598
|
-
...(item.
|
665
|
+
...(item.storageSystem
|
666
|
+
? [item.storageSystem.storage_system_id]
|
667
|
+
: []),
|
599
668
|
])
|
600
669
|
}
|
601
670
|
},
|