@eturnity/eturnity_reusable_components 8.7.4--EPDM-12729.2 → 8.7.4--EPDM-12729.4

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eturnity/eturnity_reusable_components",
3
- "version": "8.7.4--EPDM-12729.2",
3
+ "version": "8.7.4--EPDM-12729.4",
4
4
  "files": [
5
5
  "dist",
6
6
  "src"
@@ -0,0 +1,4 @@
1
+ <svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M22 10V12L0 12L1.19209e-07 10L22 10Z" fill="white"/>
3
+ <path d="M10 0H12V22H10V0Z" fill="white"/>
4
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M6 9.46199L9.708 11.7L8.724 7.48199L12 4.64399L7.686 4.27799L6 0.299988L4.314 4.27799L0 4.64399L3.276 7.48199L2.292 11.7L6 9.46199Z" fill="#FDB813"/>
3
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg width="18" height="18" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M3.50008 1V3L5.50008 3V4L3.50008 4V6H2.50008V4L0.500075 4L0.500076 3L2.50008 3V1H3.50008Z" fill="#263238"/>
3
+ <path d="M8.50008 3L13.5001 3V4L8.50008 4V3Z" fill="#263238"/>
4
+ <path d="M3.70718 8.5L2.20718 10H11.793L10.293 8.5L11.0001 7.79289L13.7072 10.5L11.0001 13.2071L10.293 12.5L11.793 11H2.20718L3.70718 12.5L3.00008 13.2071L0.292969 10.5L3.00008 7.79289L3.70718 8.5Z" fill="#263238"/>
5
+ </svg>
@@ -11,6 +11,7 @@ const theme = {
11
11
  lightGray: '#f2f2f2',
12
12
  white: '#ffffff',
13
13
  blue: '#48a2d0',
14
+ blue2: '#6CD4D4',
14
15
  red: '#ff5656',
15
16
  pureRed: '#ff0000',
16
17
  blue1: '#e4efff',
@@ -0,0 +1,4 @@
1
+ export const TEXT_OVERLAY_TOP_OFFSET = 35
2
+ export const TEXT_OVERLAY_LEFT_OFFSET = 3
3
+ export const TEXT_OVERLAY_ARROW_LEFT_OFFSET = 62
4
+ export const TEXT_OVERLAY_ARROW_RIGHT_NEGATIVE_OFFSET = 68
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <PageContainer ref="container">
2
+ <PageContainer ref="container" :type="type">
3
3
  <div
4
4
  ref="icon"
5
5
  data-test-id="infoText_trigger"
@@ -7,7 +7,13 @@
7
7
  @mouseenter="!isMobile && openTrigger === 'onHover' && showInfo()"
8
8
  @mouseleave="!isMobile && openTrigger === 'onHover' && hideInfo()"
9
9
  >
10
+ <Dot
11
+ v-if="type === 'dot'"
12
+ :color="dotColor"
13
+ data-test-id="infoText_dot"
14
+ />
10
15
  <IconComponent
16
+ v-else
11
17
  :color="iconColor || computedIconColor"
12
18
  :cursor="isDisabled ? 'not-allowed' : 'pointer'"
13
19
  :disabled="isDisabled"
@@ -94,7 +100,7 @@
94
100
  `
95
101
 
96
102
  const PageContainer = styled('div')`
97
- display: inline-block;
103
+ display: ${(props) => (props.type === 'dot' ? 'unset' : 'inline-block')};
98
104
  position: relative;
99
105
  `
100
106
 
@@ -108,12 +114,20 @@
108
114
  height: auto;
109
115
  `
110
116
 
117
+ const Dot = styled('div')`
118
+ width: 5px;
119
+ height: 5px;
120
+ background-color: ${(props) => props.color};
121
+ border-radius: 50%;
122
+ `
123
+
111
124
  export default {
112
125
  name: 'InfoText',
113
126
  components: {
114
127
  IconComponent,
115
128
  TextOverlay,
116
129
  Arrow,
130
+ Dot,
117
131
  PageContainer,
118
132
  TextWrapper,
119
133
  OverlayImage,
@@ -165,6 +179,16 @@
165
179
  default: false,
166
180
  required: false,
167
181
  },
182
+ dotColor: {
183
+ type: String,
184
+ required: false,
185
+ default: theme.colors.blue2,
186
+ },
187
+ type: {
188
+ type: String,
189
+ required: false,
190
+ default: 'info', // info, dot
191
+ },
168
192
  },
169
193
  setup(props) {
170
194
  const isVisible = ref(false)
@@ -354,7 +378,6 @@
354
378
  }
355
379
 
356
380
  const updatePosition = async () => {
357
- console.log('updatePosition')
358
381
  if (!infoBox.value || !textContent.value) return
359
382
 
360
383
  // First make the content visible but hidden to get accurate measurements
@@ -0,0 +1,62 @@
1
+ /* eslint-disable */
2
+ import { mount } from '@vue/test-utils'
3
+ import InfoText from '@/components/infoText'
4
+ import theme from '@/assets/theme'
5
+
6
+ jest.mock('@/components/icon/iconCache.mjs', () => ({
7
+ // need to mock this due to how jest handles import.meta
8
+ fetchIcon: jest.fn(() => Promise.resolve('close_for_modals,_tool_tips.svg')),
9
+ }))
10
+
11
+ describe('InfoText Component', () => {
12
+ let wrapper
13
+
14
+ beforeEach(() => {
15
+ wrapper = mount(InfoText, {
16
+ props: {
17
+ text: 'default text',
18
+ size: '14px',
19
+ infoPosition: 'bottom',
20
+ alignArrow: 'center',
21
+ openTrigger: 'onHover',
22
+ width: '165px',
23
+ maxWidth: '165px',
24
+ shouldUseTeleport: false,
25
+ dotColor: '#00009f',
26
+ type: 'info',
27
+ },
28
+ global: {
29
+ provide: {
30
+ theme,
31
+ },
32
+ },
33
+ })
34
+ })
35
+
36
+ test('renders InfoText component with default props', () => {
37
+ expect(wrapper.find('[data-test-id="infoText_info"]').exists()).toBe(true)
38
+ expect(wrapper.vm.text).toContain('default text')
39
+ expect(wrapper.vm.size).toContain('14px')
40
+ expect(wrapper.vm.infoPosition).toContain('bottom')
41
+ expect(wrapper.vm.alignArrow).toContain('center')
42
+ })
43
+
44
+ test('openTrigger prop is set to onClick', async () => {
45
+ await wrapper.setProps({ openTrigger: 'onClick' })
46
+
47
+ //should not see text upon hover
48
+ expect(wrapper.find('[data-test-id="info_text_wrapper"]').exists()).toBe(
49
+ false
50
+ )
51
+ //should see text upon click
52
+ await wrapper.find('[data-test-id="infoText_trigger"]').trigger('click')
53
+ expect(wrapper.find('[data-test-id="info_text_wrapper"]').exists()).toBe(
54
+ true
55
+ )
56
+ })
57
+
58
+ test('position type is dot', async () => {
59
+ await wrapper.setProps({ type: 'dot' })
60
+ expect(wrapper.find('[data-test-id="infoText_dot"]').exists()).toBe(true)
61
+ })
62
+ })
@@ -0,0 +1,48 @@
1
+ import InfoText from './index.vue'
2
+
3
+ export default {
4
+ title: 'infoText',
5
+ component: InfoText,
6
+ tags: ['autodocs'],
7
+ parameters: {
8
+ layout: 'centered',
9
+ },
10
+ }
11
+
12
+ // import InfoText from "@eturnity/eturnity_reusable_components/src/components/infoText"
13
+ //To use:
14
+ // <info-text
15
+ // text="Veritatis et quasi architecto beatae vitae"
16
+ // size="20px"
17
+ // alignArrow="right" // which side the arrow should be on
18
+ // />
19
+
20
+ export const Default = {
21
+ args: {
22
+ text: 'default text',
23
+ size: '14px',
24
+ infoPosition: 'bottom',
25
+ alignArrow: 'center',
26
+ openTrigger: 'onHover',
27
+ width: '165px',
28
+ maxWidth: '165px',
29
+ shouldUseTeleport: false,
30
+ dotColor: '#00009f',
31
+ type: 'info',
32
+ },
33
+ }
34
+
35
+ export const Dot = {
36
+ args: {
37
+ ...Default.args,
38
+ type: 'dot',
39
+ },
40
+ }
41
+
42
+ //will only show the info text on click
43
+ export const OnClick = {
44
+ args: {
45
+ ...Default.args,
46
+ openTrigger: 'onClick',
47
+ },
48
+ }
@@ -137,7 +137,7 @@
137
137
  </Caret>
138
138
  </SelectButton>
139
139
  <DropdownWrapper ref="dropdownWrapperRef" :no-relative="noRelative">
140
- <Teleport to="#portal-target">
140
+ <Teleport to="body">
141
141
  <SelectDropdown
142
142
  v-show="isSelectDropdownShown"
143
143
  ref="dropdown"
@@ -150,12 +150,11 @@
150
150
  computed: {},
151
151
  methods: {
152
152
  clickHandler(e) {
153
- if (this.isDisabled) {
153
+ if (this.isDisabled || !!this.$attrs?.onClick) {
154
154
  // prevent emitter if the option is disabled
155
155
  return
156
- } else {
157
- this.$parent.$emit('option-selected', this.value, e)
158
156
  }
157
+ this.$parent.$emit('option-selected', this.value, e)
159
158
  },
160
159
  hoverHandler() {
161
160
  this.$parent.$emit('option-hovered', this.value)
@@ -9,10 +9,12 @@
9
9
  v-if="!item.children"
10
10
  :key="idx"
11
11
  :data-id="`sub_menu_settings_${item.key}`"
12
- :is-active="activeTab === item.key"
12
+ :is-active="activeTab === item.key || activeParentTab === item.key"
13
13
  @click="$emit('tab-click', { activeKey: item.key })"
14
14
  >
15
- <IconContainer :is-active="activeTab === item.key">
15
+ <IconContainer
16
+ :is-active="activeTab === item.key || activeParentTab === item.key"
17
+ >
16
18
  <Icon color="#fff" cursor="pointer" :name="item.icon" size="18px" />
17
19
  </IconContainer>
18
20
  <ListText>{{ $gettext(item.label) }}</ListText>
@@ -59,8 +59,10 @@
59
59
  </NoTemplate>
60
60
  <InputContainer
61
61
  v-if="item.type === 'input'"
62
+ :has-message="showInfoText"
62
63
  @click.stop="onInputClick()"
63
64
  >
65
+ <InfoText v-if="showInfoText" :text="infoTextMessage" type="dot" />
64
66
  <TextContainer
65
67
  v-if="customInputDisabled"
66
68
  class="input-placeholder"
@@ -387,7 +389,13 @@
387
389
  padding-left: 15px;
388
390
  `
389
391
 
390
- const InputContainer = styled.div`
392
+ const inputContainerAttrs = { hasMessage: Boolean }
393
+ const InputContainer = styled('div', inputContainerAttrs)`
394
+ margin-left: ${(props) => (props.hasMessage ? '10px' : '0')};
395
+ display: flex;
396
+ align-items: center;
397
+ justify-content: flex-start;
398
+
391
399
  .inputField input {
392
400
  border-radius: 4px 0 0 4px;
393
401
  }
@@ -523,6 +531,14 @@
523
531
  required: false,
524
532
  default: false,
525
533
  },
534
+ showInfoText: {
535
+ required: false,
536
+ default: false,
537
+ },
538
+ infoTextMessage: {
539
+ required: false,
540
+ default: '',
541
+ },
526
542
  },
527
543
  data() {
528
544
  return {
@@ -1,1030 +0,0 @@
1
- <template>
2
- <PageContainer>
3
- <PageTitleContainer data-id="string_design_expand_collapse_section">
4
- <SectionTitleText>{{ $gettext('inverters') }}</SectionTitleText>
5
- <TitleButtonsContainer>
6
- <ButtonIcon
7
- v-if="dataListToDisplay.length > 1"
8
- app-theme="dark"
9
- icon-name="reorder_string"
10
- :is-active="showReorder"
11
- :text="$gettext('reorder')"
12
- @click="toggleShowReorder()"
13
- />
14
- <ButtonIcon
15
- app-theme="dark"
16
- :icon-name="hasExpandedSection ? 'collapse_all' : 'expand_all'"
17
- :text="$gettext(hasExpandedSection ? 'collapse_all' : 'expand_all')"
18
- @click="toggleAllSections"
19
- />
20
- </TitleButtonsContainer>
21
- </PageTitleContainer>
22
- <SectionContainer
23
- v-for="(item, index) in dataListToDisplay"
24
- :key="item.inverterId"
25
- >
26
- <TopContainer>
27
- <LeftContainer>
28
- <TitleContainer>
29
- <IconWrapper
30
- v-if="item.type != 'optimizer'"
31
- size="32px"
32
- @click="toggleSection(item.inverterId)"
33
- >
34
- <RCIcon
35
- v-if="isItemCollapsible(item)"
36
- color="white"
37
- :name="isExpanded(item.inverterId) ? 'arrow_up' : 'arrow_down'"
38
- size="10px"
39
- />
40
- <IconPlaceholder v-else />
41
- </IconWrapper>
42
- <TextContainer
43
- :is-archived="
44
- item.statusActive === false && !!item.companyComponentLibraryId
45
- "
46
- :style="{ marginLeft: item.type == 'optimizer' ? '32px' : '0' }"
47
- >
48
- <TitleText :title="item.model">
49
- {{
50
- (item.type === 'optimizer' || !hasExpandedSection) &&
51
- item.quantity
52
- ? item.quantity + ' x'
53
- : ''
54
- }}
55
- {{ item.model }}
56
- <InfoTextWrapper>
57
- <RCInfoText
58
- v-if="
59
- item.statusActive === false &&
60
- item.companyComponentLibraryId
61
- "
62
- button-type="error"
63
- :text="
64
- $gettext(
65
- `Component has been archived and shouldn't be used`
66
- )
67
- "
68
- />
69
- </InfoTextWrapper>
70
- </TitleText>
71
- <TitleSubText>
72
- <span>{{ item.brandName }}</span>
73
- <template
74
- v-if="itemHasStrings(item) || item.type === 'optimizer'"
75
- >
76
- <ContainerValue
77
- v-if="
78
- item.getkWp() > 1 &&
79
- item.type !== 'optimizer' &&
80
- hasExpandedSection
81
- "
82
- >
83
- |
84
- {{
85
- numberToString({
86
- value: item.getkWp(),
87
- numberPrecision: 2,
88
- })
89
- }}
90
- kWp
91
- </ContainerValue>
92
- <ContainerValue
93
- v-else-if="item.type !== 'optimizer' && hasExpandedSection"
94
- >
95
- |
96
- {{
97
- numberToString({
98
- value: 1000 * item.getkWp(),
99
- numberPrecision: 2,
100
- minDecimals: 2,
101
- })
102
- }}
103
- Wp
104
- </ContainerValue>
105
- </template>
106
- </TitleSubText>
107
- </TextContainer>
108
- </TitleContainer>
109
- <MarkersContainer>
110
- <MarkerItem
111
- v-if="item.mppts.length && hasExpandedSection"
112
- :background-color="isTargetRatioInRange(item) ? 'green' : 'red'"
113
- >{{
114
- numberToString({
115
- value: 100 * item.getTargetRatio(),
116
- numberPrecision: 0,
117
- minDecimals: 0,
118
- })
119
- }}%
120
- </MarkerItem>
121
- <MarkerItem v-if="item.hasTemplate && !item.isLoading">
122
- <span
123
- :title="
124
- item.companyProductTemplateName
125
- ? item.companyProductTemplateName
126
- : $gettext('no_template_selected')
127
- "
128
- >
129
- {{
130
- item.companyProductTemplateName
131
- ? item.companyProductTemplateName
132
- : $gettext('no_template_selected')
133
- }}
134
- </span>
135
- </MarkerItem>
136
- <MarkerItem>
137
- <RCIcon color="white" :name="getIconName(item)" size="14px" />
138
- <MarkerText :title="getTypeName(item.type)">
139
- {{ getTypeName(item.type) }}
140
- </MarkerText>
141
- </MarkerItem>
142
- <MarkerItem
143
- v-if="item.type !== 'optimizer' && item.type !== 'storage'"
144
- :title="$gettext('AC power is the nominal AC output power.')"
145
- >
146
- {{
147
- numberToString({
148
- value: item.pacKw,
149
- numberPrecision: getNumberPrecision(item.pacKw),
150
- minDecimals: 0,
151
- })
152
- }}
153
- {{ $gettext('kWAC') }}
154
- </MarkerItem>
155
- <MarkerItem
156
- v-if="
157
- (!itemHasStrings(item) || !hasExpandedSection) &&
158
- item.type !== 'storage'
159
- "
160
- :title="$gettext('The DC power is the maximum DC input power.')"
161
- >
162
- {{
163
- numberToString({
164
- value: item.inputMaxPowerKw,
165
- numberPrecision: getNumberPrecision(item.inputMaxPowerKw),
166
- minDecimals: 0,
167
- })
168
- }}
169
- {{ $gettext('kWDC') }}
170
- </MarkerItem>
171
- <MarkerItem
172
- v-if="item.type == 'storage'"
173
- :title="$gettext('charging_ac_power_kva_marker_title')"
174
- >
175
- {{
176
- numberToString({
177
- value: item.chargingAcPowerKva,
178
- numberPrecision: getNumberPrecision(item.chargingAcPowerKva),
179
- minDecimals: 0,
180
- })
181
- }}
182
- {{ $gettext('kWAC') }}
183
- </MarkerItem>
184
- </MarkersContainer>
185
- <IconsContainer>
186
- <IconWrapper
187
- v-if="nonOptimizerInverterCount > 1 || item.type === 'optimizer'"
188
- @click="
189
- !item.isLoading && hasGroupedInverters(item)
190
- ? $emit('on-delete-grouped', item)
191
- : $emit('on-delete', item)
192
- "
193
- >
194
- <RCIcon
195
- :color="item.isLoading ? 'grey' : 'red'"
196
- :cursor="item.isLoading ? 'not-allowed' : 'pointer'"
197
- :is-disabled="item.isLoading"
198
- name="delete"
199
- size="14px"
200
- />
201
- </IconWrapper>
202
- <IconWrapper @click="!item.isLoading && $emit('on-edit', item)">
203
- <RCIcon
204
- :color="item.isLoading ? 'grey' : 'white'"
205
- :cursor="item.isLoading ? 'not-allowed' : 'pointer'"
206
- :is-disabled="item.isLoading"
207
- name="edit_button"
208
- size="14px"
209
- />
210
- </IconWrapper>
211
- <IconWrapper @click="$emit('on-datasheet', item)">
212
- <RCIcon
213
- color="white"
214
- cursor="pointer"
215
- name="document"
216
- size="14px"
217
- />
218
- </IconWrapper>
219
- </IconsContainer>
220
- </LeftContainer>
221
- <SortingContainer v-if="dataListToDisplay.length > 1 && showReorder">
222
- <SortingIconWrapper
223
- :data-test-id="'move_up_' + index"
224
- :is-disabled="index === 0"
225
- @click="index > 0 && handleMoveClick('up', index, item)"
226
- >
227
- <RCIcon
228
- :color="index === 0 ? 'grey6' : 'grey3'"
229
- :cursor="index === 0 ? 'not-allowed' : 'pointer'"
230
- name="move_up"
231
- size="14px"
232
- />
233
- </SortingIconWrapper>
234
- <SortingIconWrapper
235
- :data-test-id="'move_down_' + index"
236
- :is-disabled="index === dataListToDisplay.length - 1"
237
- @click="
238
- index < dataListToDisplay.length - 1 &&
239
- handleMoveClick('down', index, item)
240
- "
241
- >
242
- <RCIcon
243
- :color="
244
- index === dataListToDisplay.length - 1 ? 'grey6' : 'grey3'
245
- "
246
- :cursor="
247
- index === dataListToDisplay.length - 1
248
- ? 'not-allowed'
249
- : 'pointer'
250
- "
251
- :is-disabled="index === dataListToDisplay.length - 1"
252
- name="move_down"
253
- size="14px"
254
- />
255
- </SortingIconWrapper>
256
- </SortingContainer>
257
- </TopContainer>
258
- <BoxContainer
259
- v-for="mppt in item.mppts"
260
- v-show="isExpanded(item.inverterId)"
261
- :key="mppt.mpptId"
262
- >
263
- <BoxTitleWrapper>
264
- <IconWrapper
265
- v-if="itemHasStrings(item)"
266
- margin-left="4px"
267
- size="8px"
268
- @click="toggleMppt(mppt.mpptId)"
269
- >
270
- <RCIcon
271
- color="white"
272
- cursor="pointer"
273
- :name="isMpptExpanded(mppt.mpptId) ? 'arrow_up' : 'arrow_down'"
274
- size="10px"
275
- />
276
- </IconWrapper>
277
- <BoxTitleText>{{ mppt.name }}</BoxTitleText>
278
- <BoxIconsContainer>
279
- <BoxIconWrapper>
280
- <RCIcon
281
- color="white"
282
- cursor="pointer"
283
- name="string_design"
284
- size="11px"
285
- />
286
- <div>
287
- {{ mppt.strings.length }}/{{ mppt.getNumberOfTerminals() }}
288
- </div>
289
- </BoxIconWrapper>
290
- <BoxIconWrapper>
291
- <RCIcon
292
- color="white"
293
- cursor="pointer"
294
- name="panels_tool"
295
- size="11px"
296
- />
297
- <div>{{ getNumberOfMpptModules(mppt.strings) }}</div>
298
- </BoxIconWrapper>
299
- </BoxIconsContainer>
300
- </BoxTitleWrapper>
301
-
302
- <div v-show="isMpptExpanded(mppt.mpptId) && itemHasStrings(item)">
303
- <StringBox v-for="string in mppt.strings" :key="string.id">
304
- <StringColorContainer :background-color="string.color" />
305
- <StringContainer>
306
- <div>{{ string.name }}</div>
307
- <StringIconContainer>
308
- <div>{{ string.modules.length }}</div>
309
- <RCIcon color="white" name="module" size="14px" />
310
- </StringIconContainer>
311
- </StringContainer>
312
- </StringBox>
313
- </div>
314
- </BoxContainer>
315
- <template v-if="availableMPPTData(item)">
316
- <BoxContainer
317
- v-for="availableItem in availableMPPTData(item)"
318
- v-show="isExpanded(item.inverterId)"
319
- :key="availableItem.mpptId"
320
- >
321
- <BoxTitleWrapper>
322
- <IconWrapper
323
- v-if="itemHasStrings(item)"
324
- margin-left="4px"
325
- size="8px"
326
- @click="toggleMppt(availableItem.mpptId)"
327
- >
328
- <RCIcon
329
- color="white"
330
- cursor="pointer"
331
- :name="
332
- isMpptExpanded(availableItem.mpptId)
333
- ? 'arrow_up'
334
- : 'arrow_down'
335
- "
336
- size="10px"
337
- />
338
- </IconWrapper>
339
- <BoxTitleText>{{ availableItem.name }}</BoxTitleText>
340
- <BoxIconsContainer>
341
- <BoxIconWrapper>
342
- <RCIcon
343
- color="white"
344
- cursor="pointer"
345
- name="string_design"
346
- size="11px"
347
- />
348
- <div>0/{{ availableItem.numberOfTerminals }}</div>
349
- </BoxIconWrapper>
350
- <BoxIconWrapper>
351
- <RCIcon
352
- color="white"
353
- cursor="pointer"
354
- name="panels_tool"
355
- size="11px"
356
- />
357
- <div>0</div>
358
- </BoxIconWrapper>
359
- </BoxIconsContainer>
360
- </BoxTitleWrapper>
361
- <EmptyStringBox
362
- v-show="
363
- isMpptExpanded(availableItem.mpptId) && itemHasStrings(item)
364
- "
365
- />
366
- </BoxContainer>
367
- </template>
368
- <BoxContainer
369
- v-if="item.storageSystem && Object.keys(item.storageSystem).length > 0"
370
- v-show="isExpanded(item.inverterId)"
371
- :key="item.storageSystem.storage_system_id"
372
- >
373
- <BoxTitleWrapper>
374
- <IconWrapper
375
- margin-left="4px"
376
- size="8px"
377
- @click="toggleMppt(item.storageSystem.storage_system_id)"
378
- >
379
- <RCIcon
380
- color="white"
381
- cursor="pointer"
382
- :name="
383
- isMpptExpanded(item.storageSystem.storage_system_id)
384
- ? 'arrow_up'
385
- : 'arrow_down'
386
- "
387
- size="10px"
388
- />
389
- </IconWrapper>
390
- <BoxTitleText>{{ $gettext('battery') }}</BoxTitleText>
391
- </BoxTitleWrapper>
392
- <div v-show="isMpptExpanded(item.storageSystem.storage_system_id)">
393
- <BatteryBox>
394
- <RCIcon color="white" name="battery" size="14px" />
395
- <BatteryDetailsContainer>
396
- <BatteryType>{{ item.storageSystem.brand_name }}</BatteryType>
397
- <BatteryModel>{{ item.storageSystem.model }}</BatteryModel>
398
- </BatteryDetailsContainer>
399
- <BatteryValue>
400
- {{
401
- numberToString({
402
- value: item.storageSystem.nominal_capacity_kWh,
403
- numberPrecision: 2,
404
- minDecimals: 0,
405
- })
406
- }}
407
- kWh
408
- </BatteryValue>
409
- </BatteryBox>
410
- </div>
411
- </BoxContainer>
412
- </SectionContainer>
413
- <DividerContainer v-if="batteryData.length" />
414
- <UnassignedContainer v-if="batteryData.length">
415
- <SectionTitleText>{{ $gettext('battery_information') }}</SectionTitleText>
416
- <BatteryBox
417
- v-for="battery in batteryData"
418
- :key="battery.id"
419
- :is-unassigned="true"
420
- >
421
- <RCIcon color="black" name="battery" size="14px" />
422
- <BatteryDetailsContainer>
423
- <UnassignedType>{{ battery.brand_name }}</UnassignedType>
424
- <UnassignedModel>{{ battery.model }}</UnassignedModel>
425
- </BatteryDetailsContainer>
426
- <BatteryValue>
427
- {{
428
- numberToString({
429
- value: battery.nominal_capacity_kWh,
430
- numberPrecision: 2,
431
- minDecimals: 0,
432
- })
433
- }}
434
- kWh
435
- </BatteryValue>
436
- </BatteryBox>
437
- </UnassignedContainer>
438
- </PageContainer>
439
- </template>
440
-
441
- <script>
442
- // import DropdownMenu from '@eturnity/eturnity_reusable_components/src/components/stringDesign/DropdownMenu'
443
- import styled from 'vue3-styled-components'
444
- import RCIcon from '../../icon'
445
- import ButtonIcon from '../../buttons/buttonIcon'
446
- import { numberToString } from '../../../helpers/numberConverter'
447
- import RCInfoText from '../../infoText'
448
- const PageContainer = styled.div`
449
- position: relative;
450
- `
451
-
452
- const SectionContainer = styled.div`
453
- &:not(:last-child) {
454
- margin-bottom: 8px;
455
- }
456
- background-color: ${(props) => props.theme.colors.black};
457
- padding: 8px;
458
- border-radius: 4px;
459
- color: ${(props) => props.theme.colors.white};
460
- `
461
-
462
- const TitleContainer = styled.div`
463
- display: flex;
464
- align-items: center;
465
- gap: 4px;
466
- `
467
-
468
- const IconsContainer = styled.div`
469
- display: flex;
470
- align-items: center;
471
- margin-left: 32px;
472
- margin-top: 4px;
473
- `
474
- const textContainerAttr = { isArchived: Boolean }
475
- const TextContainer = styled('div', textContainerAttr)`
476
- display: grid;
477
- color: ${(props) => (props.isArchived ? props.theme.colors.red : 'white')};
478
- `
479
- const InfoTextWrapper = styled.div`
480
- display: flex;
481
- align-items: center;
482
- justify-content: center;
483
- padding-left: 8px;
484
- `
485
- const TitleText = styled.div`
486
- font-size: 14px;
487
- white-space: nowrap;
488
- overflow: hidden;
489
- text-overflow: ellipsis;
490
- display: flex;
491
- `
492
-
493
- const TitleSubText = styled.div`
494
- font-size: 12px;
495
- display: flex;
496
- gap: 4px;
497
- `
498
-
499
- const MarkersContainer = styled.div`
500
- display: flex;
501
- flex-wrap: wrap;
502
- gap: 8px;
503
- margin: 4px 0 2px 32px;
504
- align-items: end;
505
- `
506
-
507
- const MarkerAttrs = { backgroundColor: String }
508
- const MarkerItem = styled('div', MarkerAttrs)`
509
- display: flex;
510
- gap: 5px;
511
- align-items: center;
512
- padding: 2px 7px;
513
- border-radius: 4px;
514
- background-color: ${(props) =>
515
- props.backgroundColor
516
- ? props.theme.colors[props.backgroundColor]
517
- : props.theme.colors.grey6};
518
- font-size: 11px;
519
- color: ${(props) => props.theme.colors.white};
520
- & > span {
521
- overflow: hidden;
522
- white-space: nowrap;
523
- text-overflow: ellipsis;
524
- max-width: 11ch;
525
- }
526
- `
527
-
528
- const MarkerText = styled.div`
529
- max-width: 11ch;
530
- white-space: nowrap;
531
- overflow: hidden;
532
- text-overflow: ellipsis;
533
- `
534
-
535
- const ContainerValue = styled.div`
536
- font-size: 12px;
537
- `
538
-
539
- const IconAttrs = {
540
- size: { type: String, default: '32px' },
541
- marginLeft: String,
542
- }
543
- const IconWrapper = styled('div', IconAttrs)`
544
- display: flex;
545
- align-items: center;
546
- justify-content: center;
547
- border-radius: 4px;
548
- cursor: pointer;
549
- width: ${(props) => props.size};
550
- height: ${(props) => props.size};
551
- margin-left: ${(props) => props.marginLeft};
552
- &:hover {
553
- background: ${(props) =>
554
- props.marginLeft ? 'transparent' : 'rgba(255, 255, 255, 0.1)'};
555
- }
556
-
557
- &:active {
558
- background: rgba(255, 255, 255, 0.2);
559
- }
560
- `
561
-
562
- const BoxContainer = styled.div`
563
- border-radius: 4px;
564
- background: ${(props) => props.theme.colors.grey6};
565
- padding: 8px;
566
- margin-top: 8px;
567
- margin-left: 30px;
568
- `
569
-
570
- const BoxTitleWrapper = styled.div`
571
- display: flex;
572
- align-items: center;
573
- gap: 8px;
574
- `
575
-
576
- const BoxTitleText = styled.div`
577
- font-size: 13px;
578
- `
579
-
580
- const BoxIconsContainer = styled.div`
581
- display: flex;
582
- gap: 8px;
583
- align-items: center;
584
- margin-left: auto;
585
- font-size: 11px;
586
- `
587
-
588
- const BoxIconWrapper = styled.div`
589
- display: flex;
590
- align-items: center;
591
- justify-content: center;
592
- gap: 4px;
593
- padding: 4px;
594
- `
595
-
596
- const StringBox = styled.div`
597
- display: grid;
598
- grid-template-columns: auto 1fr;
599
- margin-top: 8px;
600
- border-radius: 4px;
601
- border: 1px solid ${(props) => props.theme.colors.grey3};
602
- `
603
-
604
- const StringContainer = styled.div`
605
- display: flex;
606
- justify-content: space-between;
607
- gap: 8px;
608
- padding: 8px;
609
- font-size: 11px;
610
- `
611
-
612
- const StringColorAttrs = { backgroundColor: String }
613
- const StringColorContainer = styled('div', StringColorAttrs)`
614
- background-color: ${(props) => props.backgroundColor};
615
- width: 22px;
616
- min-height: 100%;
617
- border-top-left-radius: 2px;
618
- border-bottom-left-radius: 2px;
619
- `
620
-
621
- const StringIconContainer = styled.div`
622
- display: flex;
623
- align-items: center;
624
- justify-content: center;
625
- gap: 2px;
626
- padding: 0 4px;
627
- `
628
-
629
- const PageTitleContainer = styled.div`
630
- position: sticky;
631
- top: 0;
632
- background-color: ${(props) => props.theme.colors.black};
633
- z-index: 99;
634
- padding: 8px;
635
- display: grid;
636
- gap: 6px;
637
- align-items: center;
638
- `
639
-
640
- const TitleButtonsContainer = styled.div`
641
- display: flex;
642
- align-items: center;
643
- gap: 4px;
644
- padding-bottom: 8px;
645
- `
646
-
647
- const SectionTitleText = styled.div`
648
- font-size: 14px;
649
- font-weight: 700;
650
- color: ${(props) => props.theme.colors.white};
651
- `
652
-
653
- const BatteryBoxAttrs = { isUnassigned: Boolean }
654
- const BatteryBox = styled('div', BatteryBoxAttrs)`
655
- display: flex;
656
- align-items: center;
657
- gap: 8px;
658
- border: 1px solid ${(props) => props.theme.colors.white};
659
- border-radius: 4px;
660
- padding: 8px;
661
- margin-top: 8px;
662
- background-color: ${(props) =>
663
- props.isUnassigned ? props.theme.colors.grey2 : ''};
664
- `
665
-
666
- const BatteryDetailsContainer = styled.div`
667
- display: flex;
668
- flex-direction: column;
669
- gap: 4px;
670
- `
671
-
672
- const BatteryType = styled.div`
673
- font-size: 11px;
674
- `
675
-
676
- const BatteryModel = styled.div`
677
- font-size: 10px;
678
- `
679
-
680
- const BatteryValue = styled.div`
681
- font-size: 10px;
682
- align-self: flex-end;
683
- margin-left: auto;
684
- `
685
- const TopContainer = styled.div`
686
- display: flex;
687
- justify-content: space-between;
688
- `
689
-
690
- const LeftContainer = styled.div``
691
-
692
- const SortingContainer = styled.div`
693
- display: flex;
694
- flex-direction: column;
695
- align-items: center;
696
- `
697
-
698
- const SortingIconWrapperAttrs = { isDisabled: Boolean }
699
- const SortingIconWrapper = styled('div', SortingIconWrapperAttrs)`
700
- cursor: ${(props) => (props.isDisabled ? 'not-allowed' : 'pointer')};
701
- width: 30px;
702
- height: 30px;
703
- display: flex;
704
- align-items: center;
705
- justify-content: center;
706
-
707
- &:hover {
708
- background-color: ${(props) =>
709
- props.isDisabled ? 'transparent' : 'rgba(255, 255, 255, 0.1)'};
710
- border-radius: 4px;
711
- }
712
- `
713
-
714
- const UnassignedContainer = styled.div`
715
- margin-top: 8px;
716
- `
717
-
718
- const DividerContainer = styled.div`
719
- height: 0.5px;
720
- width: 100%;
721
- background-color: ${(props) => props.theme.colors.grey4};
722
- opacity: 0.6;
723
- `
724
-
725
- const UnassignedType = styled.div`
726
- font-size: 12px;
727
- line-height: 150%;
728
- `
729
-
730
- const UnassignedModel = styled.div`
731
- font-size: 10px;
732
- line-height: 150%;
733
- `
734
-
735
- const IconPlaceholder = styled.div`
736
- width: 32px;
737
- `
738
-
739
- const EmptyStringBox = styled.div`
740
- border: 0.8px dashed ${(props) => props.theme.colors.black};
741
- border-radius: 4px;
742
- padding: 8px 8px 8px 0;
743
- height: 32px;
744
- width: 233px;
745
- margin-top: 8px;
746
- `
747
-
748
- export default {
749
- name: 'DropdownMenu',
750
- components: {
751
- InfoTextWrapper,
752
- RCInfoText,
753
- SectionContainer,
754
- TitleContainer,
755
- IconsContainer,
756
- TextContainer,
757
- TitleText,
758
- TitleSubText,
759
- RCIcon,
760
- MarkersContainer,
761
- MarkerItem,
762
- ContainerValue,
763
- IconWrapper,
764
- BoxContainer,
765
- BoxTitleWrapper,
766
- BoxTitleText,
767
- BoxIconsContainer,
768
- BoxIconWrapper,
769
- StringBox,
770
- StringContainer,
771
- StringColorContainer,
772
- StringIconContainer,
773
- PageTitleContainer,
774
- TitleButtonsContainer,
775
- SectionTitleText,
776
- ButtonIcon,
777
- PageContainer,
778
- BatteryBox,
779
- BatteryDetailsContainer,
780
- BatteryType,
781
- BatteryModel,
782
- BatteryValue,
783
- TopContainer,
784
- LeftContainer,
785
- SortingContainer,
786
- SortingIconWrapper,
787
- UnassignedContainer,
788
- DividerContainer,
789
- UnassignedType,
790
- UnassignedModel,
791
- IconPlaceholder,
792
- EmptyStringBox,
793
- MarkerText,
794
- },
795
- props: {
796
- dataList: {
797
- required: true,
798
- type: Array,
799
- },
800
- batteryData: {
801
- required: true,
802
- type: Array,
803
- },
804
- inverterParameters: {
805
- required: true,
806
- type: Object,
807
- },
808
- isPvAndBatteryActive: {
809
- required: false,
810
- type: Boolean,
811
- },
812
- },
813
- emits: ['on-edit', 'on-move', 'on-delete', 'on-delete-grouped'],
814
- data() {
815
- return {
816
- expandedInverters: [],
817
- expandedMppts: [],
818
- showReorder: false,
819
- numberToString,
820
- }
821
- },
822
- computed: {
823
- dataListToDisplay() {
824
- let data = this.hasExpandedSection
825
- ? this.dataList
826
- : this.compressedDataList
827
- return data.sort((a, b) => a.lineNr - b.lineNr)
828
- },
829
- hasExpandedSection() {
830
- return this.expandedInverters.length > 0
831
- },
832
- hasStringsOrStorage() {
833
- return this.dataList.some((item) => {
834
- return (
835
- item.mppts.some((mppt) => mppt.strings.length > 0) ||
836
- (item.storageSystem && Object.keys(item.storageSystem).length > 0)
837
- )
838
- })
839
- },
840
- nonOptimizerInverterCount() {
841
- return this.dataList.filter((item) => item.type !== 'optimizer').length
842
- },
843
- compressedDataList() {
844
- return this.dataList.reduce((acc, item) => {
845
- if (item.type == 'optimizer') {
846
- acc.push(item)
847
- return acc
848
- }
849
- const existingInverter = acc.find((inverter) => {
850
- return inverter.componentId == item.componentId
851
- })
852
- if (!existingInverter) {
853
- acc.push(item)
854
- item.quantity = 1
855
- } else {
856
- existingInverter.quantity++
857
- }
858
- return acc
859
- }, [])
860
- },
861
- },
862
- created() {
863
- // Expand all items on creation
864
- if (this.hasStringsOrStorage) {
865
- this.expandedInverters = this.dataList.map((item) => item.inverterId)
866
- this.expandedMppts = this.dataList.flatMap((item) => {
867
- const availableMppts = this.availableMPPTData(item)
868
- return [
869
- ...item.mppts.map((mppt) => mppt.mpptId),
870
- ...(item.storageSystem
871
- ? [item.storageSystem.storage_system_id]
872
- : []),
873
- ...(availableMppts?.map((mppt) => mppt.mpptId) || []),
874
- ]
875
- })
876
- }
877
- },
878
- methods: {
879
- hasGroupedInverters(item) {
880
- return item.type != 'optimizer' && item.quantity > 1
881
- },
882
- toggleShowReorder() {
883
- this.showReorder = !this.showReorder
884
- },
885
- isTargetRatioInRange(inverter) {
886
- const currentTargetRatio = inverter.getTargetRatio()
887
- return (
888
- this.inverterParameters.target_power_ratio - 20 <=
889
- 100 * currentTargetRatio &&
890
- this.inverterParameters.target_power_ratio + 20 >=
891
- 100 * currentTargetRatio
892
- )
893
- },
894
- getNumberPrecision(value) {
895
- if (typeof value === 'string') {
896
- value = Number(value)
897
- }
898
- if (value < 10) {
899
- return 2
900
- } else if (value >= 10 && value < 100) {
901
- return 1
902
- } else {
903
- return 0
904
- }
905
- },
906
- getNumberOfMpptModules(strings) {
907
- return strings.reduce((acc, curr) => acc + curr.modules.length, 0)
908
- },
909
- availableMPPTData(item) {
910
- if (item.type === 'optimizer' || item.type === 'storage') {
911
- return []
912
- }
913
- const existingTrackerNumbers = item.mppts.map(
914
- (mppt) => mppt.trackerNumber
915
- )
916
- const filteredAvailableMPPTs = item.availableMPPTs.filter(
917
- (mppt) => !existingTrackerNumbers.includes(mppt.tracker_number)
918
- )
919
-
920
- let mpptData = []
921
- filteredAvailableMPPTs.forEach((mppt) => {
922
- mpptData.push({
923
- mpptId:
924
- 'available_mppt_' +
925
- item.companyComponentLibraryId +
926
- '_' +
927
- mppt.tracker_number,
928
- numberOfTerminals: mppt.number_of_terminals,
929
- name:
930
- 'MPPT ' +
931
- (item.mppts.length + filteredAvailableMPPTs.indexOf(mppt) + 1),
932
- })
933
- })
934
- return mpptData
935
- },
936
- isExpanded(id) {
937
- return this.expandedInverters.includes(id)
938
- },
939
- isMpptExpanded(id) {
940
- return this.expandedMppts.includes(id)
941
- },
942
- toggleSection(id) {
943
- const index = this.expandedInverters.indexOf(id)
944
- if (index === -1) {
945
- this.expandedInverters.push(id)
946
- } else {
947
- this.expandedInverters.splice(index, 1)
948
- }
949
- },
950
- toggleMppt(id) {
951
- const index = this.expandedMppts.indexOf(id)
952
- if (index === -1) {
953
- this.expandedMppts.push(id)
954
- } else {
955
- this.expandedMppts.splice(index, 1)
956
- }
957
- },
958
- toggleAllSections() {
959
- if (this.hasExpandedSection) {
960
- this.expandedInverters = []
961
- this.expandedMppts = []
962
- } else {
963
- this.expandedInverters = this.dataList.map((item) => item.inverterId)
964
- this.expandedMppts = this.dataList.flatMap((item) => {
965
- const hasStrings = !!item.getStrings().length
966
- return [
967
- ...(hasStrings ? item.mppts.map((mppt) => mppt.mpptId) : []),
968
- ...(item.storageSystem
969
- ? [item.storageSystem.storage_system_id]
970
- : []),
971
- ]
972
- })
973
- }
974
- },
975
- isItemCollapsible(item) {
976
- return (
977
- item.mppts.some((mppt) => mppt.strings.length > 0) ||
978
- (item.storageSystem && Object.keys(item.storageSystem).length > 0) ||
979
- item.availableMPPTs.length
980
- )
981
- },
982
- itemHasStrings(item) {
983
- return item.mppts.some((mppt) => mppt.strings.length > 0)
984
- },
985
- getTypeName(type) {
986
- const value = type.toLowerCase()
987
- switch (value) {
988
- case 'pv':
989
- return this.$gettext('PV')
990
- case 'pv_storage':
991
- return this.$gettext('hybrid')
992
- case 'storage':
993
- return this.$gettext('battery')
994
- case 'optimizer':
995
- return this.$gettext('inverter_type_optimizer')
996
- default:
997
- return this.$gettext('PV')
998
- }
999
- },
1000
- getIconName(item) {
1001
- const value =
1002
- item.type === 'pv_storage'
1003
- ? item.iconName.technology_choice
1004
- : item.type
1005
- switch (value) {
1006
- case 'photovoltaics':
1007
- return 'pv'
1008
- case 'pv':
1009
- return 'pv'
1010
- case 'storage':
1011
- return 'battery'
1012
- case 'battery':
1013
- return 'battery'
1014
- case 'optimizer':
1015
- return 'optimizer'
1016
- default:
1017
- return 'pv'
1018
- }
1019
- },
1020
- handleMoveClick(direction, index, item) {
1021
- this.$emit('on-move', {
1022
- direction: direction,
1023
- index: index,
1024
- item: item,
1025
- isExpanded: this.hasExpandedSection,
1026
- })
1027
- },
1028
- },
1029
- }
1030
- </script>