@eturnity/eturnity_reusable_components 7.4.4-EPDM-9606 → 7.4.4-EPDM-7260.2

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.
@@ -0,0 +1,488 @@
1
+ <template>
2
+ <RangeSliderContainer>
3
+ <Labels v-if="topLabels && topLabels.length > 0">
4
+ <Label v-for="label in topLabels" :key="label">
5
+ {{ label }}
6
+ </Label>
7
+ </Labels>
8
+
9
+ <BarContainer v-if="leftLabels && leftLabels.length > 0">
10
+ <BarWrapper v-for="(label, index) in leftLabels" :key="index">
11
+ <VerticalLabel>{{ label.name }}</VerticalLabel>
12
+ <BarSlider>
13
+ <Bar
14
+ @contextmenu="
15
+ onBarRightClick({ event: $event, label, type: 'add' })
16
+ "
17
+ >
18
+ <SliderWrapper>
19
+ <Slider
20
+ v-for="(bar, index) in label.selectedTariffs"
21
+ :key="index"
22
+ :active="!disabled"
23
+ :draggable="!disabled"
24
+ :resizable="!disabled"
25
+ :color="
26
+ hasOverlap && OverlapId === label.id ? '#ff5656' : bar.color
27
+ "
28
+ :minWidth="minWidth"
29
+ :min="bar.min"
30
+ :max="bar.max"
31
+ :z="index"
32
+ :step="step"
33
+ :stepCount="stepCount"
34
+ :subStepCount="subStepCount"
35
+ @dragStop="
36
+ onChange(
37
+ 'drag',
38
+ {
39
+ itemId: bar.id,
40
+ parentId: bar.parentId,
41
+ entityId: label.id,
42
+ ...$event
43
+ },
44
+ label.id,
45
+ label.selectedTariffs
46
+ )
47
+ "
48
+ @resizeStop="
49
+ onChange(
50
+ 'resize',
51
+ {
52
+ itemId: bar.id,
53
+ entityId: label.id,
54
+ ...$event
55
+ },
56
+ label.id,
57
+ label.selectedTariffs
58
+ )
59
+ "
60
+ @contextmenu.native.stop="
61
+ onBarRightClick({
62
+ event: $event,
63
+ label,
64
+ type: 'delete',
65
+ bar
66
+ })
67
+ "
68
+ @activated="onActivateBar({ item: bar })"
69
+ @deactivated="onDeactivateBar()"
70
+ >
71
+ <template #mr>
72
+ <HandleIcon name="handle" size="50px" cursor="pointer" />
73
+ </template>
74
+ <template #ml>
75
+ <HandleIcon name="handle" size="50px" cursor="pointer" />
76
+ </template>
77
+ </Slider>
78
+ </SliderWrapper>
79
+ </Bar>
80
+ <Ruler>
81
+ <RulerRule v-for="n in stepCount" :key="n"></RulerRule>
82
+ </Ruler>
83
+ <SubRuler>
84
+ <RulerSubRule v-for="n in subStepCount" :key="n"></RulerSubRule>
85
+ </SubRuler>
86
+ <ErrorMessage
87
+ v-if="!canOverlap && hasOverlap && OverlapId === label.id"
88
+ >
89
+ *{{ $gettext('overlap_error_message') }}
90
+ </ErrorMessage>
91
+ </BarSlider>
92
+ </BarWrapper>
93
+ </BarContainer>
94
+ <bar-options-container
95
+ v-if="showBarOptions"
96
+ :top="barOptionsPosition.y"
97
+ :left="barOptionsPosition.x"
98
+ ref="barDropdown"
99
+ >
100
+ <bar-item-container
101
+ v-for="item in barOptionsList"
102
+ :key="item.id"
103
+ @click="onBarTariffClick({ item })"
104
+ >
105
+ <icon
106
+ :name="barOptionsType === 'add' ? 'add_icon' : 'delete'"
107
+ size="14px"
108
+ />
109
+ <bar-item-text>{{ item.name }}</bar-item-text>
110
+ </bar-item-container>
111
+ </bar-options-container>
112
+ </RangeSliderContainer>
113
+ </template>
114
+
115
+ <script>
116
+ import styled from 'vue-styled-components'
117
+
118
+ import SliderComponent from './Slider'
119
+ import Icon from '../icon'
120
+
121
+ const wrapperAttrs = { width: String, height: String }
122
+ const SliderWrapper = styled('div', wrapperAttrs)`
123
+ position: relative;
124
+ display: flex;
125
+ height: ${(props) => props.height || '24px'};
126
+ width: ${(props) => props.width || '100%'};
127
+ `
128
+
129
+ const sliderAttrs = { color: String }
130
+ const Slider = styled(SliderComponent, sliderAttrs)`
131
+ cursor: pointer;
132
+ opacity: 0.7;
133
+ display: flex;
134
+ justify-content: space-between;
135
+ align-items: center;
136
+ border: unset !important;
137
+ background-color: ${(props) => props.color};
138
+ `
139
+
140
+ const ErrorMessage = styled.p`
141
+ color: ${(props) => props.theme.colors.red};
142
+ font-style: italic;
143
+ font-size: 13px;
144
+ margin-top: 5px;
145
+ `
146
+
147
+ const Bar = styled.div`
148
+ display: flex;
149
+ margin-bottom: -30px;
150
+ margin-left: 1px;
151
+ background-color: #f6faff;
152
+ width: 100%;
153
+ background-color: ${(props) => props.theme.colors.gray1};
154
+ `
155
+
156
+ const Ruler = styled.div`
157
+ margin: 10px 0px -5px 0px;
158
+ display: flex;
159
+ overflow: hidden;
160
+ `
161
+
162
+ const RulerRule = styled.div`
163
+ border-left: solid 1px ${(props) => props.theme.colors.grey2};
164
+ border-bottom: solid 1px ${(props) => props.theme.colors.grey2};
165
+ display: flex;
166
+ flex-grow: 1;
167
+ flex-shrink: 1;
168
+ padding: 10px 0px;
169
+
170
+ &:last-child {
171
+ border-right: solid 1px ${(props) => props.theme.colors.grey2};
172
+ }
173
+ `
174
+
175
+ const SubRuler = styled.div`
176
+ margin: -7px 0px -5px 0px;
177
+ display: flex;
178
+ `
179
+
180
+ const RulerSubRule = styled.div`
181
+ border-left: solid 1px ${(props) => props.theme.colors.grey2};
182
+ border-bottom: solid 1px ${(props) => props.theme.colors.grey2};
183
+ display: flex;
184
+ flex-grow: 1;
185
+ flex-shrink: 1;
186
+ padding: 3px 0px;
187
+
188
+ &:last-child {
189
+ border-right: solid 1px ${(props) => props.theme.colors.grey2};
190
+ }
191
+
192
+ &:nth-child(odd) {
193
+ margin-top: -5px;
194
+ padding: 5px 0px;
195
+ }
196
+ `
197
+
198
+ const BarSlider = styled.div`
199
+ width: 100%;
200
+ `
201
+
202
+ const BarContainer = styled.div`
203
+ display: flex;
204
+ flex-direction: column;
205
+ `
206
+
207
+ const BarWrapper = styled.div`
208
+ display: flex;
209
+ flex-direction: row;
210
+ margin: 20px 0px;
211
+ align-items: center;
212
+ `
213
+
214
+ const RangeSliderContainer = styled.div`
215
+ display: flex;
216
+ position: relative;
217
+ padding: 20px 10px;
218
+ flex-direction: column;
219
+ user-select: none;
220
+ width: 100%;
221
+ `
222
+
223
+ const Labels = styled.div`
224
+ display: flex;
225
+ justify-content: space-between;
226
+ padding: 0px;
227
+ margin: 10px -100px 0px 100px;
228
+ width: calc(100% - 100px);
229
+ `
230
+
231
+ const Label = styled.div`
232
+ font-size: 80%;
233
+ display: flex;
234
+ width: 1px;
235
+ justify-content: center;
236
+ `
237
+
238
+ const VerticalLabel = styled.div`
239
+ text-align: left;
240
+ min-width: 100px;
241
+ font-size: 13px;
242
+ `
243
+
244
+ const HandleIcon = styled(Icon)`
245
+ width: auto;
246
+ min-width: 0;
247
+ margin: -12px;
248
+ `
249
+
250
+ const BarOptionAttrs = { top: Number, left: Number }
251
+ const BarOptionsContainer = styled('div', BarOptionAttrs)`
252
+ display: grid;
253
+ position: fixed;
254
+ z-index: 9999;
255
+ border-radius: 4px;
256
+ background-color: ${(props) => props.theme.colors.white};
257
+ padding: 6px 10px;
258
+ border: 1px solid ${(props) => props.theme.colors.grey4};
259
+ top: ${(props) => props.top + 'px'};
260
+ left: ${(props) => props.left + 'px'};
261
+ `
262
+
263
+ const BarItemContainer = styled.div`
264
+ display: grid;
265
+ grid-template-columns: auto 1fr;
266
+ grid-gap: 12px;
267
+ align-items: center;
268
+ cursor: pointer;
269
+ padding: 6px 10px;
270
+ border-radius: 4px;
271
+
272
+ &:hover {
273
+ background-color: ${(props) => props.theme.colors.lightGray};
274
+ }
275
+ `
276
+
277
+ const BarItemText = styled.div`
278
+ font-size: 13px;
279
+ `
280
+
281
+ export default {
282
+ name: 'RangeSlider',
283
+ props: {
284
+ tariffItems: { type: Array, default: () => [] },
285
+ labels: { type: Array, default: () => [] },
286
+ rangeMargin: { type: Number, default: 1 },
287
+ step: { type: Number, default: 1 },
288
+ minWidth: { type: Number, default: 1 },
289
+ canOverlap: { type: Boolean, default: true },
290
+ disabled: { type: Boolean, default: false }
291
+ },
292
+ components: {
293
+ Bar,
294
+ Label,
295
+ Labels,
296
+ Ruler,
297
+ Slider,
298
+ SubRuler,
299
+ BarSlider,
300
+ RulerRule,
301
+ HandleIcon,
302
+ BarWrapper,
303
+ ErrorMessage,
304
+ RulerSubRule,
305
+ BarContainer,
306
+ SliderWrapper,
307
+ VerticalLabel,
308
+ RangeSliderContainer,
309
+ BarOptionsContainer,
310
+ BarItemContainer,
311
+ BarItemText,
312
+ Icon
313
+ },
314
+ data() {
315
+ return {
316
+ showBarOptions: false,
317
+ barOptionsPosition: { x: 0, y: 0 },
318
+ barOptionsList: [],
319
+ barOptionsType: null, // can be "add" or "delete"
320
+ activeLabel: null,
321
+ activeItem: null,
322
+ OverlapId: null,
323
+ hasOverlap: false
324
+ }
325
+ },
326
+ computed: {
327
+ maximum() {
328
+ if (!this.labels.length) return
329
+
330
+ return this.topLabels.at(-1)
331
+ },
332
+ minimum() {
333
+ if (!this.labels.length) return
334
+
335
+ return this.topLabels[0]
336
+ },
337
+ topLabels() {
338
+ if (!this.labels.length) return
339
+ const labels = this.labels.find((label) => label.placement === 'top')
340
+
341
+ return labels ? labels.value : []
342
+ },
343
+ leftLabels() {
344
+ if (!this.labels.length) return
345
+ const labels = this.labels.find((label) => label.placement === 'left')
346
+ return labels ? labels.value : []
347
+ },
348
+ stepCount() {
349
+ let labels = this.topLabels || []
350
+
351
+ if (labels.length) {
352
+ return labels.length - 1
353
+ }
354
+
355
+ return Math.floor((this.maximum - this.minimum) / this.step)
356
+ },
357
+ subStepCount() {
358
+ let labels = this.topLabels || []
359
+
360
+ if (labels.length && this.step) {
361
+ return (this.maximum - this.minimum) / this.step
362
+ }
363
+
364
+ return 0
365
+ }
366
+ },
367
+ methods: {
368
+ onChange(type, value, entityId, tariffs) {
369
+ this.$emit(type, value)
370
+
371
+ this.hasOverlap = this.checkOverlap(value, tariffs)
372
+ this.OverlapId = this.hasOverlap ? entityId : null
373
+ },
374
+ checkOverlap(value, tariffs) {
375
+ // Check if the tariffs overlap
376
+ const { itemId, min, max } = value
377
+
378
+ return tariffs.some((tariff) => {
379
+ if (tariff.id === itemId) return false
380
+
381
+ return (
382
+ (min > tariff.min && min < tariff.max) ||
383
+ (max > tariff.min && max < tariff.max) ||
384
+ (min < tariff.min && max > tariff.max)
385
+ )
386
+ })
387
+ },
388
+ barOptionLabel(value) {
389
+ if (this.barOptionsType === 'add')
390
+ return `${this.$gettext('Add')} ${value}`
391
+
392
+ return `${this.$gettext('Remove')} ${value}`
393
+ },
394
+ onActivateBar({ item }) {
395
+ this.activeItem = item
396
+ document.addEventListener('keydown', this.onKeyDownDelete)
397
+ },
398
+ onDeactivateBar() {
399
+ this.activeItem = null
400
+ document.removeEventListener('keydown', this.onKeyDownDelete)
401
+ },
402
+ onKeyDownDelete(event) {
403
+ // Check if the pressed key is the Delete or Backspace key
404
+ if (
405
+ (event.key === 'Delete' || event.key === 'Backspace') &&
406
+ this.activeItem
407
+ ) {
408
+ this.$emit('on-bar-tariff-click', {
409
+ type: 'delete',
410
+ item: this.activeItem,
411
+ label: this.activeLabel
412
+ })
413
+ this.activeItem = null
414
+ }
415
+ },
416
+ onBarTariffClick({ item }) {
417
+ this.$emit('on-bar-tariff-click', {
418
+ type: this.barOptionsType,
419
+ item,
420
+ label: this.activeLabel
421
+ })
422
+ this.showBarOptions = false
423
+ },
424
+ setBarOptions(bar) {
425
+ this.barOptionsList = []
426
+
427
+ if (this.barOptionsType === 'add') {
428
+ this.tariffItems.forEach((item) => {
429
+ if (item.name && item.name.length) {
430
+ this.barOptionsList.push(item)
431
+ }
432
+ })
433
+ } else {
434
+ // add based on the index in the chart.
435
+ this.barOptionsList.push({
436
+ name: bar.name,
437
+ id: bar.id,
438
+ index: bar.index
439
+ })
440
+ }
441
+ },
442
+ onBarRightClick({ event, label, type, bar }) {
443
+ // type can be "add", "delete"
444
+ // if "add", show all tariffItems for the group
445
+ // if "delete", only show the delete with the tariff name
446
+ event.preventDefault()
447
+ this.activeLabel = label
448
+ this.barOptionsType = type
449
+ // need to pass the index to setBarOptions in case it's 'add'
450
+ this.setBarOptions(bar)
451
+ if (this.barOptionsList.length) {
452
+ this.showBarOptions = true
453
+ this.barOptionsPosition = { x: event.clientX, y: event.clientY }
454
+ document.addEventListener('click', this.handleOutsideClick)
455
+ } else {
456
+ this.showBarOptions = false
457
+ }
458
+ },
459
+ handleOutsideClick(event) {
460
+ // Check if the click occurred outside the dropdown
461
+ if (
462
+ this.$refs.barDropdown &&
463
+ !this.$refs.barDropdown.$el.contains(event.target)
464
+ ) {
465
+ // Hide the dropdown
466
+ this.showBarOptions = false
467
+ // Remove the global click event listener
468
+ document.removeEventListener('click', this.handleOutsideClick)
469
+ }
470
+ }
471
+ },
472
+ beforeDestroy() {
473
+ // Remove the global click event listener to prevent memory leaks
474
+ document.removeEventListener('click', this.handleOutsideClick)
475
+ document.removeEventListener('keydown', this.onKeyDownDelete)
476
+ },
477
+ mounted() {
478
+ // TODO: remove
479
+ // this.tariffItems.forEach((item) => {
480
+ // item.tariff_time_mfr.forEach((tariff) => {
481
+ // tariff.time_to = '12:00:00'
482
+ // tariff.time_up_to = '12:00:00'
483
+ // })
484
+ // })
485
+ console.log('mounted tariffItems', this.tariffItems)
486
+ }
487
+ }
488
+ </script>
@@ -0,0 +1,49 @@
1
+ import { isFunction } from './fns'
2
+
3
+ export function matchesSelectorToParentElements (el, selector, baseNode) {
4
+ let node = el
5
+
6
+ const matchesSelectorFunc = [
7
+ 'matches',
8
+ 'webkitMatchesSelector',
9
+ 'mozMatchesSelector',
10
+ 'msMatchesSelector',
11
+ 'oMatchesSelector'
12
+ ].find(func => isFunction(node[func]))
13
+
14
+ if (!isFunction(node[matchesSelectorFunc])) return false
15
+
16
+ do {
17
+ if (node[matchesSelectorFunc](selector)) return true
18
+ if (node === baseNode) return false
19
+ node = node.parentNode
20
+ } while (node)
21
+
22
+ return false
23
+ }
24
+
25
+ export function addEvent (el, event, handler) {
26
+ if (!el) {
27
+ return
28
+ }
29
+ if (el.attachEvent) {
30
+ el.attachEvent('on' + event, handler)
31
+ } else if (el.addEventListener) {
32
+ el.addEventListener(event, handler, true)
33
+ } else {
34
+ el['on' + event] = handler
35
+ }
36
+ }
37
+
38
+ export function removeEvent (el, event, handler) {
39
+ if (!el) {
40
+ return
41
+ }
42
+ if (el.detachEvent) {
43
+ el.detachEvent('on' + event, handler)
44
+ } else if (el.removeEventListener) {
45
+ el.removeEventListener(event, handler, true)
46
+ } else {
47
+ el['on' + event] = null
48
+ }
49
+ }
@@ -0,0 +1,26 @@
1
+ export function isFunction(func) {
2
+ return (
3
+ typeof func === 'function' ||
4
+ Object.prototype.toString.call(func) === '[object Function]'
5
+ )
6
+ }
7
+
8
+ export function snapToGrid(grid, pendingX) {
9
+ return Math.round(pendingX / grid) * grid
10
+ }
11
+
12
+ export function computeWidth(left, right) {
13
+ return right - left
14
+ }
15
+
16
+ export function restrictToBounds(value, min, max) {
17
+ if (min !== null && value < min) {
18
+ return min
19
+ }
20
+
21
+ if (max !== null && max < value) {
22
+ return max
23
+ }
24
+
25
+ return value
26
+ }
@@ -0,0 +1,145 @@
1
+ <template>
2
+ <page-container>
3
+ <box-container>
4
+ <selected-container
5
+ >{{ numberSelected }} {{ $gettext('selected') }}</selected-container
6
+ >
7
+ <list-container v-if="optionsList.length">
8
+ <list-item
9
+ v-for="item in optionsList"
10
+ :key="item.type"
11
+ :hoverColor="item.hoverColor"
12
+ @click="$emit('on-' + item.type)"
13
+ >
14
+ {{ item.name }}
15
+ </list-item>
16
+ </list-container>
17
+ <empty-text v-if="!optionsList.length">
18
+ {{ $gettext('no_batch_actions_available') }}
19
+ </empty-text>
20
+ <icon-container @click="$emit('on-close')">
21
+ <icon
22
+ name="close_for_modals,_tool_tips"
23
+ color="white"
24
+ size="14px"
25
+ cursor="pointer"
26
+ />
27
+ </icon-container>
28
+ </box-container>
29
+ </page-container>
30
+ </template>
31
+
32
+ <script>
33
+ // import SelectedOptions from "@eturnity/eturnity_reusable_components/src/components/selectedOptions"
34
+ // optionsList = [
35
+ // {
36
+ // type: 'export',
37
+ // name: 'Export'
38
+ // },
39
+ // {
40
+ // type: 'delete',
41
+ // name: 'Delete',
42
+ // hoverColor: 'red' // default is green
43
+ // }
44
+ // ]
45
+ // @on-${type}="function" should $emit the callback for the 'type' in the optionsList
46
+ // <selected-options
47
+ // :numberSelected="numberSelected"
48
+ // :optionsList="optionsList"
49
+ // @on-close="onCloseFunction()"
50
+ // @on-export="function()" @on-delete="function()"
51
+ // />
52
+ import styled from 'vue-styled-components'
53
+ import Icon from '../icon'
54
+
55
+ const PageContainer = styled.div`
56
+ position: fixed;
57
+ bottom: 30px;
58
+ left: 50%;
59
+ transform: translateX(-50%);
60
+ `
61
+
62
+ const SelectedContainer = styled.div`
63
+ display: grid;
64
+ align-items: center;
65
+ height: 100%;
66
+ padding-right: 20px;
67
+ border-right: 1px solid rgba(255, 255, 255, 0.2);
68
+ `
69
+
70
+ const BoxContainer = styled.div`
71
+ display: flex;
72
+ align-items: center;
73
+ background-color: ${(props) => props.theme.colors.black};
74
+ opacity: 90%;
75
+ color: ${(props) => props.theme.colors.white};
76
+ border-radius: 4px;
77
+ padding: 8px 10px 8px 20px;
78
+ font-size: 14px;
79
+ height: 40px;
80
+ `
81
+
82
+ const ListContainer = styled.div`
83
+ padding: 0 20px;
84
+ display: flex;
85
+ gap: 20px;
86
+ color: ${(props) => props.theme.colors.white};
87
+ `
88
+
89
+ const ListAttrs = {
90
+ hoverColor: String
91
+ }
92
+ const ListItem = styled('div', ListAttrs)`
93
+ cursor: pointer;
94
+ &:hover {
95
+ color: ${(props) =>
96
+ props.hoverColor
97
+ ? props.theme.colors[props.hoverColor]
98
+ : props.theme.colors.green};
99
+ }
100
+ `
101
+
102
+ const IconContainer = styled.div`
103
+ display: grid;
104
+ align-items: center;
105
+ justify-items: center;
106
+ height: 30px;
107
+ width: 30px;
108
+ cursor: pointer;
109
+ margin-left: 20px;
110
+
111
+ &:hover {
112
+ background: rgba(255, 255, 255, 0.1);
113
+ border-radius: 4px;
114
+ }
115
+ `
116
+
117
+ const EmptyText = styled.div`
118
+ color: ${(props) => props.theme.colors.white};
119
+ font-size: 13px;
120
+ padding-left: 16px;
121
+ `
122
+
123
+ export default {
124
+ name: 'selected-options',
125
+ components: {
126
+ PageContainer,
127
+ BoxContainer,
128
+ SelectedContainer,
129
+ ListContainer,
130
+ ListItem,
131
+ Icon,
132
+ IconContainer,
133
+ EmptyText
134
+ },
135
+ props: {
136
+ optionsList: {
137
+ required: true
138
+ },
139
+ numberSelected: {
140
+ required: true,
141
+ default: 0
142
+ }
143
+ }
144
+ }
145
+ </script>