@webitel/ui-sdk 24.10.81 → 24.10.82
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/CHANGELOG.md +7 -0
- package/dist/ui-sdk.css +1 -1
- package/dist/ui-sdk.js +3961 -3882
- package/dist/ui-sdk.umd.cjs +17 -17
- package/package.json +1 -1
- package/src/api/clients//321/201ontacts/contactChatMessagesHistory.js +2 -2
- package/src/components/index.js +2 -0
- package/src/components/wt-dual-panel/__tests__/WtDualPanel.js +49 -0
- package/src/components/wt-dual-panel/_variables.scss +12 -0
- package/src/components/wt-dual-panel/wt-dual-panel.vue +131 -0
- package/src/components/wt-icon-action/IActionData.js +10 -0
- package/src/components/wt-table/wt-table.vue +28 -0
- package/src/css/pages/card-page.scss +41 -36
- package/src/enums/IconAction/IconAction.enum.js +2 -0
- package/src/modules/QueryFilters/mixins/sortFilterMixin.js +30 -14
package/package.json
CHANGED
|
@@ -29,7 +29,7 @@ const getChat = async ({ contactId, chatId }) => {
|
|
|
29
29
|
const response = await contactChatService.getContactChatHistory(contactId, chatId);
|
|
30
30
|
const { messages, peers } = applyTransform(response.data, [snakeToCamel()]);
|
|
31
31
|
return {
|
|
32
|
-
items: applyTransform({ messages, peers }, [mergeChatMessagesData])
|
|
32
|
+
items: applyTransform({ messages, peers }, [mergeChatMessagesData]),
|
|
33
33
|
peers,
|
|
34
34
|
};
|
|
35
35
|
} catch (err) {
|
|
@@ -70,7 +70,7 @@ const getAllMessages = async (params) => {
|
|
|
70
70
|
merge(getDefaultGetListResponse()),
|
|
71
71
|
]);
|
|
72
72
|
return {
|
|
73
|
-
items: applyTransform({ messages, peers, chats }, [mergeMessagesData])
|
|
73
|
+
items: applyTransform({ messages, peers, chats }, [mergeMessagesData]),
|
|
74
74
|
next,
|
|
75
75
|
};
|
|
76
76
|
} catch (err) {
|
package/src/components/index.js
CHANGED
|
@@ -42,6 +42,7 @@ import WtNotificationsBar
|
|
|
42
42
|
from './wt-notifications-bar/wt-notifications-bar.vue';
|
|
43
43
|
import WtPageHeader from './wt-page-header/wt-page-header.vue';
|
|
44
44
|
import WtPageWrapper from './wt-page-wrapper/wt-page-wrapper.vue';
|
|
45
|
+
import WtDualPanel from './wt-dual-panel/wt-dual-panel.vue';
|
|
45
46
|
import WtPagination from './wt-pagination/wt-pagination.vue';
|
|
46
47
|
import WtPlayer from './wt-player/wt-player.vue';
|
|
47
48
|
import WtPopup from './wt-popup/wt-popup.vue';
|
|
@@ -115,6 +116,7 @@ const Components = {
|
|
|
115
116
|
WtErrorPage,
|
|
116
117
|
WtNotificationsBar,
|
|
117
118
|
WtPageWrapper,
|
|
119
|
+
WtDualPanel,
|
|
118
120
|
WtPagination,
|
|
119
121
|
WtPlayer,
|
|
120
122
|
WtStatusSelect,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import WtDualPanel from '../wt-dual-panel.vue';
|
|
3
|
+
|
|
4
|
+
describe('WtDualPanel', () => {
|
|
5
|
+
it('renders a component', () => {
|
|
6
|
+
const wrapper = shallowMount(WtDualPanel);
|
|
7
|
+
expect(wrapper.classes('wt-dual-panel')).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('renders dual panel header via header slot', () => {
|
|
11
|
+
const content = 'Dual Panel header';
|
|
12
|
+
const wrapper = shallowMount(WtDualPanel, {
|
|
13
|
+
slots: { header: content },
|
|
14
|
+
});
|
|
15
|
+
expect(wrapper.find('.wt-dual-panel__header').text()).toBe(content);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('renders dual panel actions panel via actions panel slot when actionsPanel prop is true', () => {
|
|
19
|
+
const content = 'Dual Panel actions panel content';
|
|
20
|
+
const wrapper = shallowMount(WtDualPanel, {
|
|
21
|
+
props: { actionsPanel: true },
|
|
22
|
+
slots: { 'actions-panel': content },
|
|
23
|
+
});
|
|
24
|
+
expect(wrapper.find('.wt-dual-panel__actions-panel').text()).toBe(content);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('does not render actions panel when actionsPanel prop is false', () => {
|
|
28
|
+
const wrapper = shallowMount(WtDualPanel, {
|
|
29
|
+
props: { actionsPanel: false },
|
|
30
|
+
});
|
|
31
|
+
expect(wrapper.find('.wt-dual-panel__actions-panel').exists()).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders dual panel side panel via side panel slot', () => {
|
|
35
|
+
const content = 'Dual Panel side panel';
|
|
36
|
+
const wrapper = shallowMount(WtDualPanel, {
|
|
37
|
+
slots: { 'side': content },
|
|
38
|
+
});
|
|
39
|
+
expect(wrapper.find('.wt-dual-panel__side-panel').text()).toBe(content);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('renders dual panel main panel via main panel slot', () => {
|
|
43
|
+
const content = 'Dual Panel main panel';
|
|
44
|
+
const wrapper = shallowMount(WtDualPanel, {
|
|
45
|
+
slots: { 'main': content },
|
|
46
|
+
});
|
|
47
|
+
expect(wrapper.find('.wt-dual-panel__main-panel').text()).toBe(content);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--wt-dual-panel-padding: var(--spacing-sm);
|
|
3
|
+
--wt-dual-panel-section-padding: var(--spacing-sm);
|
|
4
|
+
--wt-dual-panel-section-gap: var(--spacing-sm);
|
|
5
|
+
|
|
6
|
+
--wt-dual-panel-background-color: var(--grey-lighten-5);
|
|
7
|
+
--wt-dual-panel-content-wrapper-color: var(--content-wrapper-color);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
:root.theme--dark {
|
|
11
|
+
--wt-dual-panel-background-color: var(--grey-darken-4);
|
|
12
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="wt-dual-panel">
|
|
3
|
+
<div class="wt-dual-panel__header">
|
|
4
|
+
<slot name="header" />
|
|
5
|
+
</div>
|
|
6
|
+
<div
|
|
7
|
+
v-if="actionsPanel"
|
|
8
|
+
class="wt-dual-panel__actions-panel"
|
|
9
|
+
>
|
|
10
|
+
<slot name="actions-panel" />
|
|
11
|
+
</div>
|
|
12
|
+
<div class="wt-dual-panel__content">
|
|
13
|
+
<div
|
|
14
|
+
:class="[
|
|
15
|
+
`wt-dual-panel__side-panel--${sidePanelSize}`
|
|
16
|
+
]"
|
|
17
|
+
class="wt-dual-panel__side-panel"
|
|
18
|
+
>
|
|
19
|
+
<wt-icon-action
|
|
20
|
+
v-if="!disableResize"
|
|
21
|
+
:action="sidePanelCollapsed ? IconAction.EXPAND : IconAction.COLLAPSE"
|
|
22
|
+
class="wt-dual-panel__icon-action"
|
|
23
|
+
size="sm"
|
|
24
|
+
@click="toggleSidePanel"
|
|
25
|
+
/>
|
|
26
|
+
<slot name="side-panel" />
|
|
27
|
+
</div>
|
|
28
|
+
<div class="wt-dual-panel__main">
|
|
29
|
+
<slot name="main" />
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</section>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<script setup>
|
|
36
|
+
import { computed, ref, defineEmits } from 'vue';
|
|
37
|
+
import IconAction from '../../enums/IconAction/IconAction.enum.js';
|
|
38
|
+
import { ComponentSize } from '../../enums/index.js';
|
|
39
|
+
|
|
40
|
+
const props = defineProps({
|
|
41
|
+
actionsPanel: {
|
|
42
|
+
type: Boolean,
|
|
43
|
+
default: true,
|
|
44
|
+
},
|
|
45
|
+
disableResize: {
|
|
46
|
+
type: Boolean,
|
|
47
|
+
default: false,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const emit = defineEmits(['update:size']);
|
|
52
|
+
|
|
53
|
+
const sidePanelCollapsed = ref(false);
|
|
54
|
+
|
|
55
|
+
const toggleSidePanel = () => {
|
|
56
|
+
sidePanelCollapsed.value = !sidePanelCollapsed.value;
|
|
57
|
+
emit('update:side-panel-size', sidePanelSize.value);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const sidePanelSize = computed(() => (sidePanelCollapsed.value ? ComponentSize.SM : ComponentSize.MD));
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<style lang="scss">
|
|
64
|
+
@import './variables.scss';
|
|
65
|
+
</style>
|
|
66
|
+
|
|
67
|
+
<style lang="scss" scoped>
|
|
68
|
+
@import '../../../src/css/main.scss';
|
|
69
|
+
$side-panel-md-width: 320px;
|
|
70
|
+
|
|
71
|
+
.wt-dual-panel {
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
box-sizing: border-box;
|
|
75
|
+
max-width: 100%;
|
|
76
|
+
height: 100%;
|
|
77
|
+
padding: var(--wt-dual-panel-padding);
|
|
78
|
+
background: var(--wt-dual-panel-background-color);
|
|
79
|
+
gap: var(--wt-dual-panel-section-gap);
|
|
80
|
+
|
|
81
|
+
&__header,
|
|
82
|
+
&__actions-panel,
|
|
83
|
+
&__main {
|
|
84
|
+
box-sizing: border-box;
|
|
85
|
+
padding: var(--wt-dual-panel-section-padding);
|
|
86
|
+
border-radius: var(--border-radius);
|
|
87
|
+
background: var(--wt-dual-panel-content-wrapper-color);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
&__main {
|
|
91
|
+
flex: 1 1 auto;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
&__header, &__actions-panel {
|
|
95
|
+
flex: 0 0 auto;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
&__content {
|
|
99
|
+
display: flex;
|
|
100
|
+
flex-grow: 1;
|
|
101
|
+
min-height: 0;
|
|
102
|
+
gap: var(--spacing-sm);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
&__side-panel {
|
|
106
|
+
@extend %wt-scrollbar;
|
|
107
|
+
overflow: auto;
|
|
108
|
+
display: flex;
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
min-width: 0;
|
|
111
|
+
padding: var(--wt-dual-panel-section-padding);
|
|
112
|
+
transition: var(--transition);
|
|
113
|
+
background: var(--wt-dual-panel-content-wrapper-color);
|
|
114
|
+
will-change: width;
|
|
115
|
+
gap: var(--wt-dual-panel-section-gap);
|
|
116
|
+
|
|
117
|
+
&--md {
|
|
118
|
+
flex: 0 0 $side-panel-md-width;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
&--sm {
|
|
122
|
+
flex: 0 0 min-content;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
&__icon-action {
|
|
127
|
+
width: fit-content;
|
|
128
|
+
line-height: 0;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
</style>
|
|
@@ -31,6 +31,16 @@ const IActionData = Object.freeze({
|
|
|
31
31
|
icon: 'refresh',
|
|
32
32
|
hint: 'reusable.refresh',
|
|
33
33
|
},
|
|
34
|
+
[IconAction.EXPAND]: {
|
|
35
|
+
value: IconAction.EXPAND,
|
|
36
|
+
icon: 'expand',
|
|
37
|
+
hint: 'reusable.expand',
|
|
38
|
+
},
|
|
39
|
+
[IconAction.COLLAPSE]: {
|
|
40
|
+
value: IconAction.COLLAPSE,
|
|
41
|
+
icon: 'collapse',
|
|
42
|
+
hint: 'reusable.collapse',
|
|
43
|
+
},
|
|
34
44
|
});
|
|
35
45
|
|
|
36
46
|
export { IActionData };
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
v-if="gridActions"
|
|
46
46
|
class="wt-table__th__actions"
|
|
47
47
|
>
|
|
48
|
+
<!-- @slot Table head actions row slot -->
|
|
48
49
|
<slot name="actions-header" />
|
|
49
50
|
</th>
|
|
50
51
|
</tr>
|
|
@@ -73,6 +74,10 @@
|
|
|
73
74
|
:key="headerKey"
|
|
74
75
|
class="wt-table__td"
|
|
75
76
|
>
|
|
77
|
+
<!--
|
|
78
|
+
@slot Customize data columns. Recommended for representing nested data structures like object or array, and adding specific elements like select or chip
|
|
79
|
+
@scope [ { "name": "item", "description": "Data row object" }, { "name": "index", "description": "Data row index" } ]
|
|
80
|
+
-->
|
|
76
81
|
<slot
|
|
77
82
|
:index="dataKey"
|
|
78
83
|
:item="row"
|
|
@@ -86,6 +91,10 @@
|
|
|
86
91
|
v-if="gridActions"
|
|
87
92
|
class="wt-table__td__actions"
|
|
88
93
|
>
|
|
94
|
+
<!--
|
|
95
|
+
@slot Table body actions row slot
|
|
96
|
+
@scope [ { "name": "item", "description": "Data row object" }, { "name": "index", "description": "Data row index" } ]
|
|
97
|
+
-->
|
|
89
98
|
<slot
|
|
90
99
|
:index="dataKey"
|
|
91
100
|
:item="row"
|
|
@@ -113,6 +122,10 @@
|
|
|
113
122
|
:key="headerKey"
|
|
114
123
|
class="wt-table__td"
|
|
115
124
|
>
|
|
125
|
+
<!--
|
|
126
|
+
@slot Add your custom aggregations for column in table footer. Table footer is rendered conditionally depending on templates with "-footer" name
|
|
127
|
+
@scope [ { "name": "header", "description": "header object" } ]
|
|
128
|
+
-->
|
|
116
129
|
<slot
|
|
117
130
|
:header="col"
|
|
118
131
|
:index="headerKey"
|
|
@@ -131,18 +144,30 @@ import getNextSortOrder from './_internals/getSortOrder.js';
|
|
|
131
144
|
export default {
|
|
132
145
|
name: 'WtTable',
|
|
133
146
|
props: {
|
|
147
|
+
/**
|
|
148
|
+
* 'Accepts list of header objects. Draws text depending on "text" property, looks for data values through "value", "show" boolean controls visibility of a column (if undefined, all visible by default). ' Column width is calculated by "width" param. By default, sets minmax(150px, 1fr). '
|
|
149
|
+
*/
|
|
134
150
|
headers: {
|
|
135
151
|
type: Array,
|
|
136
152
|
default: () => [],
|
|
137
153
|
},
|
|
154
|
+
/**
|
|
155
|
+
* 'List of data, represented by table. '
|
|
156
|
+
*/
|
|
138
157
|
data: {
|
|
139
158
|
type: Array,
|
|
140
159
|
default: () => [],
|
|
141
160
|
},
|
|
161
|
+
/**
|
|
162
|
+
* 'If true, draws sorting arrows and sends sorting events at header click. Draws a sorting arrow by "sort": "asc"/"desc" header value. '
|
|
163
|
+
*/
|
|
142
164
|
sortable: {
|
|
143
165
|
type: Boolean,
|
|
144
166
|
default: false,
|
|
145
167
|
},
|
|
168
|
+
/**
|
|
169
|
+
* 'If true, draws row selection checkboxes. Checkbox toggles data object _isSelected property. It's IMPORTANT to set this property before sending data to table. '
|
|
170
|
+
*/
|
|
146
171
|
selectable: {
|
|
147
172
|
type: Boolean,
|
|
148
173
|
default: true,
|
|
@@ -151,6 +176,9 @@ export default {
|
|
|
151
176
|
type: Array,
|
|
152
177
|
// no default! because we need to know if it's passed or not
|
|
153
178
|
},
|
|
179
|
+
/**
|
|
180
|
+
* 'If true, reserves space for 3 icon actions in the last column. Accessible by "actions" slot. '
|
|
181
|
+
*/
|
|
154
182
|
gridActions: {
|
|
155
183
|
type: Boolean,
|
|
156
184
|
default: true,
|
|
@@ -1,47 +1,52 @@
|
|
|
1
1
|
.opened-card {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
width: 100%;
|
|
3
|
+
|
|
4
|
+
&.wt-page-wrapper,
|
|
5
|
+
&.wt-dual-panel {
|
|
6
|
+
width: 100%;
|
|
7
|
+
}
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
&-header {
|
|
10
|
+
position: relative;
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
flex-wrap: wrap;
|
|
14
|
+
justify-content: space-between;
|
|
15
|
+
margin: var(--spacing-sm) 0;
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
&__title {
|
|
18
|
+
@extend %typo-heading-3;
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
+
+ .hint {
|
|
21
|
+
top: -2px;
|
|
20
22
|
}
|
|
21
23
|
}
|
|
24
|
+
}
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
&-form {
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
width: 100%;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&-input-grid {
|
|
33
|
+
display: grid;
|
|
34
|
+
align-items: flex-start;
|
|
35
|
+
grid-template-columns: 1fr 1fr;
|
|
36
|
+
grid-auto-rows: minmax(auto, auto);
|
|
37
|
+
grid-column-gap: var(--spacing-sm);
|
|
38
|
+
grid-row-gap: var(--spacing-xs);
|
|
39
|
+
|
|
40
|
+
&--1-col {
|
|
41
|
+
grid-template-columns: 1fr;
|
|
27
42
|
}
|
|
28
|
-
&-input-grid {
|
|
29
|
-
display: grid;
|
|
30
|
-
align-items: flex-start;
|
|
31
|
-
grid-template-columns: 1fr 1fr;
|
|
32
|
-
grid-auto-rows: minmax(auto, auto);
|
|
33
|
-
grid-column-gap: var(--spacing-sm);
|
|
34
|
-
grid-row-gap: var(--spacing-xs);
|
|
35
|
-
|
|
36
|
-
&--1-col {
|
|
37
|
-
grid-template-columns: 1fr;
|
|
38
|
-
}
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
&--w50 {
|
|
45
|
+
width: 50%;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&--w100 {
|
|
49
|
+
width: 100%;
|
|
46
50
|
}
|
|
51
|
+
}
|
|
47
52
|
}
|
|
@@ -28,6 +28,24 @@ const decodeSortQuery = ({ value }) => {
|
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
+
const changeHeadersSort = ({ headers, sortedHeader, order }) => {
|
|
32
|
+
return headers.map((header) => {
|
|
33
|
+
if (header.sort === undefined) return header;
|
|
34
|
+
|
|
35
|
+
// reset all headers by default
|
|
36
|
+
let newSort = null;
|
|
37
|
+
|
|
38
|
+
if (header.field === sortedHeader.field) {
|
|
39
|
+
newSort = order;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
...header,
|
|
44
|
+
sort: newSort,
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
31
49
|
export default {
|
|
32
50
|
mixins: [baseFilterMixin],
|
|
33
51
|
data: () => ({
|
|
@@ -40,21 +58,12 @@ export default {
|
|
|
40
58
|
},
|
|
41
59
|
|
|
42
60
|
setValue({ column, order }) {
|
|
43
|
-
const headers =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let newSort = null;
|
|
48
|
-
|
|
49
|
-
if (header.value === column.value) {
|
|
50
|
-
newSort = order;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
...header,
|
|
55
|
-
sort: newSort,
|
|
56
|
-
};
|
|
61
|
+
const headers = changeHeadersSort({
|
|
62
|
+
headers: this.headers,
|
|
63
|
+
sortedHeader: column,
|
|
64
|
+
order,
|
|
57
65
|
});
|
|
66
|
+
|
|
58
67
|
const value = encodeSortQuery({ column, order });
|
|
59
68
|
this.setHeaders(headers);
|
|
60
69
|
this.setValueToQuery({
|
|
@@ -65,6 +74,13 @@ export default {
|
|
|
65
74
|
|
|
66
75
|
restoreValue(value) {
|
|
67
76
|
const sortedColumns = decodeSortQuery({ value });
|
|
77
|
+
|
|
78
|
+
debugger;
|
|
79
|
+
|
|
80
|
+
// const sortedHeader =
|
|
81
|
+
|
|
82
|
+
// const headers = changeHeadersSort({ headers: this.headers, sortedHeader: { field: sortedColumns[0] }, order });
|
|
83
|
+
|
|
68
84
|
const headers = this.headers.map((header) => ({
|
|
69
85
|
...header,
|
|
70
86
|
sort: sortedColumns[header.field] || null,
|