@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webitel/ui-sdk",
3
- "version": "24.10.81",
3
+ "version": "24.10.82",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "dev": "vite",
@@ -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]).reverse(),
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]).reverse(),
73
+ items: applyTransform({ messages, peers, chats }, [mergeMessagesData]),
74
74
  next,
75
75
  };
76
76
  } catch (err) {
@@ -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
- &.wt-page-wrapper {
3
- width: 100%;
4
- }
2
+ width: 100%;
3
+
4
+ &.wt-page-wrapper,
5
+ &.wt-dual-panel {
6
+ width: 100%;
7
+ }
5
8
 
6
- &-header {
7
- position: relative;
8
- display: flex;
9
- align-items: center;
10
- flex-wrap: wrap;
11
- justify-content: space-between;
12
- margin: var(--spacing-sm) 0;
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
- &__title {
15
- @extend %typo-heading-3;
17
+ &__title {
18
+ @extend %typo-heading-3;
16
19
 
17
- + .hint {
18
- top: -2px;
19
- }
20
+ + .hint {
21
+ top: -2px;
20
22
  }
21
23
  }
24
+ }
22
25
 
23
- &-form {
24
- display: flex;
25
- flex-direction: column;
26
- width: 100%;
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
- &--w50 {
41
- width: 50%;
42
- }
43
- &--w100{
44
- width: 100%;
45
- }
44
+ &--w50 {
45
+ width: 50%;
46
+ }
47
+
48
+ &--w100 {
49
+ width: 100%;
46
50
  }
51
+ }
47
52
  }
@@ -10,6 +10,8 @@ const IconAction = Object.freeze({
10
10
  COLUMNS: 'columns',
11
11
  HISTORY: 'history',
12
12
  EDIT: 'edit',
13
+ COLLAPSE: 'collapse',
14
+ EXPAND: 'expand',
13
15
  });
14
16
 
15
17
  export default IconAction;
@@ -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 = this.headers.map((header) => {
44
- if (header.sort === undefined) return header;
45
-
46
- // reset all headers by default
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,