@webitel/ui-sdk 3.2.133 → 3.2.135

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": "3.2.133",
3
+ "version": "3.2.135",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve-lib": "vue-cli-service serve",
@@ -142,10 +142,10 @@ export default {
142
142
 
143
143
  <style lang="scss">
144
144
  @import './variables.scss';
145
- @import './multiselect.scss';
146
145
  </style>
147
146
 
148
147
  <style lang="scss" scoped>
148
+ @import './multiselect.scss';
149
149
 
150
150
  .wt-select {
151
151
  width: 100%;
@@ -99,36 +99,35 @@ export default {
99
99
  </style>
100
100
 
101
101
  <style lang="scss" scoped>
102
-
103
- .wt-status-select.wt-select ::v-deep {
102
+ .wt-status-select.wt-select {
104
103
  box-shadow: var(--elevation-5);
105
104
 
106
- .multiselect {
105
+ :deep(.multiselect) {
107
106
  min-height: 0;
108
- }
109
107
 
110
- .multiselect__tags {
111
- min-height: 0;
112
- padding: var(--status-select-padding);
113
- border: none;
114
- }
108
+ .multiselect__tags {
109
+ min-height: 0;
110
+ padding: var(--status-select-padding);
111
+ border: none;
112
+ }
115
113
 
116
- .multiselect__select {
117
- top: 0;
118
- right: 0;
119
- }
114
+ .multiselect__select {
115
+ top: 0;
116
+ right: 0;
117
+ }
120
118
 
121
- .multiselect__content-wrapper {
122
- border: none;
119
+ .multiselect__content-wrapper {
120
+ border: none;
123
121
 
124
- .multiselect__option {
125
- min-height: 0;
126
- padding: 0;
127
- color: var(--status-select-option-color);
128
- }
122
+ .multiselect__option {
123
+ min-height: 0;
124
+ padding: 0;
125
+ color: var(--status-select-option-color);
126
+ }
129
127
 
130
- .multiselect__option--highlight {
131
- background: var(--status-select-bg--hover-color);
128
+ .multiselect__option--highlight {
129
+ background: var(--status-select-bg--hover-color);
130
+ }
132
131
  }
133
132
  }
134
133
  }
@@ -157,10 +157,11 @@ export default {
157
157
 
158
158
  <style lang="scss">
159
159
  @import './variables.scss';
160
- @import '../wt-select/multiselect.scss';
161
160
  </style>
162
161
 
163
162
  <style lang="scss" scoped>
163
+ @import '../wt-select/multiselect.scss';
164
+
164
165
  .wt-tags-input {
165
166
  width: 100%;
166
167
  }
package/src/css/main.scss CHANGED
@@ -1,7 +1,6 @@
1
1
  @import 'styleguide/styleguide';
2
2
  @import 'styleguide/display-helpers/display-helpers';
3
3
 
4
-
5
4
  @include wt-placeholder;
6
5
 
7
6
  :root {
@@ -0,0 +1,24 @@
1
+ import { AgentServiceApiFactory } from 'webitel-sdk';
2
+
3
+ const AgentStatusAPIFactory = ({ instance, OpenAPIConfig }) => {
4
+ const service = new AgentServiceApiFactory(OpenAPIConfig, '', instance);
5
+
6
+ const patchAgentStatus = async ({ agentId, status, pauseCause }) => {
7
+ try {
8
+ const res = await service.updateAgentStatus(agentId, {
9
+ status,
10
+ id: agentId,
11
+ payload: pauseCause,
12
+ });
13
+ return { success: !!res };
14
+ } catch (err) {
15
+ throw err;
16
+ }
17
+ };
18
+
19
+ return {
20
+ patch: patchAgentStatus,
21
+ };
22
+ };
23
+
24
+ export default AgentStatusAPIFactory;
@@ -0,0 +1,47 @@
1
+ import { AgentServiceApiFactory } from 'webitel-sdk';
2
+ import applyTransform, {
3
+ merge, mergeEach, notify,
4
+ snakeToCamel,
5
+ } from '../../../api/transformers';
6
+ import { getDefaultGetListResponse } from '../../../api/defaults';
7
+
8
+ const PauseCauseAPIFactory = ({ instance, OpenAPIConfig }) => {
9
+
10
+ const service = new AgentServiceApiFactory(OpenAPIConfig, '', instance);
11
+
12
+ const getList = async ({ agentId }) => {
13
+ const defaultObject = {
14
+ name: '',
15
+ durationMin: 0,
16
+ limitMin: 0,
17
+ };
18
+
19
+ const allowChange = true;
20
+ try {
21
+ const response = await service.searchPauseCauseForAgent(
22
+ agentId,
23
+ allowChange,
24
+ );
25
+ const { items, next } = applyTransform(response.data, [
26
+ snakeToCamel(),
27
+ merge(getDefaultGetListResponse()),
28
+ ]);
29
+ return {
30
+ items: applyTransform(items, [
31
+ mergeEach(defaultObject),
32
+ ]),
33
+ next,
34
+ };
35
+ } catch (err) {
36
+ throw applyTransform(err, [
37
+ notify,
38
+ ]);
39
+ }
40
+ };
41
+
42
+ return {
43
+ getList,
44
+ };
45
+ };
46
+
47
+ export default PauseCauseAPIFactory;
@@ -0,0 +1,83 @@
1
+ import { shallowMount, mount } from '@vue/test-utils';
2
+ import { createStore } from 'vuex';
3
+ import AgentStatus from '../../../../enums/AgentStatus/AgentStatus.enum';
4
+ import * as AgentStatusAPIFactory from '../../api/agent-status';
5
+ import * as PauseCauseAPIFactory from '../../api/pause-cause';
6
+ import WtCcAgentStatusSelect from '../wt-cc-agent-status-select.vue';
7
+
8
+ const pauseCauses = [{ name: 'jest1' }, { name: 'jest2' }];
9
+ const getAgentPauseCausesMock = jest.fn(() => ({ items: pauseCauses }));
10
+ // https://stackoverflow.com/a/63727813
11
+ jest.spyOn(PauseCauseAPIFactory, 'default').mockImplementation(() => ({ getList: getAgentPauseCausesMock }));
12
+
13
+ const agentStatusMock = jest.fn(() => {});
14
+ // https://stackoverflow.com/a/63727813
15
+ jest.spyOn(AgentStatusAPIFactory, 'default').mockImplementation(() => ({ patch: agentStatusMock }));
16
+
17
+ const agent = {
18
+ status: '',
19
+ agentId: 1,
20
+ };
21
+
22
+ const mountOptions = {
23
+ props: {
24
+ agentId: agent.agentId,
25
+ status: agent.status,
26
+ },
27
+ global: {
28
+ plugins: [createStore({})],
29
+ },
30
+ shallow: true,
31
+ };
32
+
33
+ describe('Wt Cc Agent Status Select', () => {
34
+ beforeEach(() => {
35
+ getAgentPauseCausesMock.mockClear();
36
+ });
37
+ it('renders a component', () => {
38
+ const wrapper = shallowMount(WtCcAgentStatusSelect, mountOptions);
39
+ expect(wrapper.exists())
40
+ .toBe(true);
41
+ });
42
+ it(`at wt-status-select "change" to "online" event, triggers agent status patch
43
+ with "online" status`, () => {
44
+ const wrapper = shallowMount(WtCcAgentStatusSelect, mountOptions);
45
+ wrapper.findComponent({ name: 'wt-status-select' })
46
+ .vm.$emit('change', AgentStatus.ONLINE);
47
+ const reqPayload = {
48
+ status: AgentStatus.ONLINE, agentId: agent.agentId, payload: undefined,
49
+ };
50
+ expect(agentStatusMock).toHaveBeenCalledWith(reqPayload);
51
+ });
52
+ it('at wt-status-select "change" to "pause" event, pause causes are loaded', async () => {
53
+ const wrapper = shallowMount(WtCcAgentStatusSelect, mountOptions);
54
+ wrapper.findComponent({ name: 'wt-status-select' })
55
+ .vm.$emit('change', AgentStatus.PAUSE);
56
+ await wrapper.vm.$nextTick();
57
+ expect(getAgentPauseCausesMock).toHaveBeenCalled();
58
+ });
59
+ it(`at wt-status-select "change" to "pause" event and pause causes truthy response,
60
+ pause-cause-popup appears`, async () => {
61
+ const wrapper = mount(WtCcAgentStatusSelect, mountOptions);
62
+ wrapper.findComponent({ name: 'wt-status-select' })
63
+ .vm.$emit('change', AgentStatus.PAUSE);
64
+ await wrapper.vm.$nextTick(); // load pause causes
65
+ await wrapper.vm.$nextTick();
66
+ await wrapper.vm.$nextTick(); // render popup
67
+ expect(wrapper.findComponent({ name: 'pause-cause-popup' }).isVisible()).toBe(true);
68
+ });
69
+ it(`at pause-cause-popup "change" event, triggers patch agent status
70
+ with "pause" status and passed pauseCause`, async () => {
71
+ const pauseCause = 'jest';
72
+ const wrapper = mount(WtCcAgentStatusSelect, mountOptions);
73
+ wrapper.vm.openPauseCausePopup();
74
+ await wrapper.vm.$nextTick();
75
+ await wrapper.vm.$nextTick();
76
+ wrapper.findComponent({ name: 'pause-cause-popup' })
77
+ .vm.$emit('change', pauseCause);
78
+ const reqPayload = {
79
+ status: AgentStatus.PAUSE, agentId: agent.agentId, pauseCause,
80
+ };
81
+ expect(agentStatusMock).toHaveBeenCalledWith(reqPayload);
82
+ });
83
+ });
@@ -0,0 +1,18 @@
1
+ import { shallowMount, mount } from '@vue/test-utils';
2
+ import WtCcPauseCausePopup from '../_internals/wt-cc-pause-cause-popup.vue';
3
+
4
+ describe('Pause cause popup', () => {
5
+ it('renders a component', () => {
6
+ const wrapper = shallowMount(WtCcPauseCausePopup);
7
+ expect(wrapper.exists())
8
+ .toBe(true);
9
+ });
10
+ it('at option input event, sets "selected" its value', () => {
11
+ const value = 'jest';
12
+ const wrapper = mount(WtCcPauseCausePopup, {
13
+ props: { options: [{ value }] },
14
+ });
15
+ wrapper.findComponent({ name: 'wt-radio' }).vm.$emit('input');
16
+ expect(wrapper.vm.selected.value).toEqual(value);
17
+ });
18
+ });
@@ -0,0 +1,122 @@
1
+ <template>
2
+ <wt-popup
3
+ class="wt-cc-pause-cause-popup"
4
+ min-width="480"
5
+ @close="close"
6
+ >
7
+ <template v-slot:title>
8
+ {{ $t('webitelUI.agentStatusSelect.pauseCausePopup.title') }}
9
+ </template>
10
+ <template v-slot:main>
11
+ <form @submit.prevent="setPause">
12
+ <ul>
13
+ <li
14
+ class="wt-cc-pause-cause-popup-option"
15
+ v-for="(option) of pauseCause"
16
+ :key="option.id"
17
+ >
18
+ <wt-radio
19
+ class="wt-cc-pause-cause-popup-option__radio"
20
+ :selected="selected.id"
21
+ :value="option.id"
22
+ :label="option.name"
23
+ @input="select(option)"
24
+ ></wt-radio>
25
+ <div class="wt-cc-pause-cause-popup-option__limits-wrapper">
26
+ <span
27
+ :class="{
28
+ 'wt-cc-pause-cause-popup-option__duration--overflow': option.isOverflow,
29
+ }"
30
+ >
31
+ {{ option.duration }}
32
+ </span>
33
+ /
34
+ <span>{{ option.limit }}</span>
35
+ </div>
36
+ </li>
37
+ </ul>
38
+ </form>
39
+ </template>
40
+ <template v-slot:actions>
41
+ <wt-button
42
+ :disabled="!selected"
43
+ @click="setPause"
44
+ >{{ $t('reusable.ok') }}
45
+ </wt-button>
46
+ <wt-button
47
+ color="secondary"
48
+ @click="close"
49
+ >{{ $t('reusable.cancel') }}
50
+ </wt-button>
51
+ </template>
52
+ </wt-popup>
53
+ </template>
54
+
55
+ <script setup>
56
+ import { computed, ref, toRef } from 'vue';
57
+ import { useI18n } from 'vue-i18n';
58
+ import { useRepresentableAgentPauseCause } from '../../../../composables/useRepresentableAgentPauseCause/useRepresentableAgentPauseCause';
59
+
60
+ const props = defineProps({
61
+ options: {
62
+ type: Array,
63
+ default: () => [],
64
+ },
65
+ });
66
+
67
+ const emit = defineEmits(['change', 'close']);
68
+
69
+ const options = toRef(props, 'options');
70
+
71
+ const selected = ref('');
72
+
73
+ const { t } = useI18n();
74
+
75
+ const { representablePauseCause } = useRepresentableAgentPauseCause(options);
76
+
77
+ const pauseCause = computed(() => representablePauseCause.value.map((cause) => ({
78
+ ...cause,
79
+ duration: cause.isOverflow
80
+ ? `-${cause.durationMin - cause.limitMin} ${t('webitelUI.agentStatusSelect.pauseCausePopup.min')}`
81
+ : `${cause.durationMin} ${t('webitelUI.agentStatusSelect.pauseCausePopup.min')}`,
82
+ limit: cause.limitMin
83
+ ? `${cause.limitMin} ${t('webitelUI.agentStatusSelect.pauseCausePopup.min')}`
84
+ : t('webitelUI.agentStatusSelect.pauseCausePopup.unlimited'),
85
+ })));
86
+
87
+ function select(option) {
88
+ selected.value = option;
89
+ }
90
+
91
+ function close() {
92
+ emit('close');
93
+ }
94
+
95
+ function setPause() {
96
+ emit('change', selected.value.name);
97
+ close();
98
+ }
99
+ </script>
100
+
101
+ <style lang="scss" scoped>
102
+ .wt-cc-pause-cause-popup-option {
103
+ display: flex;
104
+ justify-content: space-between;
105
+ align-items: center;
106
+
107
+ &:not(:last-child) {
108
+ margin-bottom: var(--spacing-sm);
109
+ }
110
+ }
111
+
112
+ .wt-cc-pause-cause-popup-option__limits-wrapper {
113
+ & > span {
114
+ @extend %typo-caption;
115
+ padding: 6px 10px;
116
+ }
117
+
118
+ .wt-cc-pause-cause-popup-option__duration--overflow {
119
+ color: var(--false-color);
120
+ }
121
+ }
122
+ </style>
@@ -0,0 +1,56 @@
1
+ <template>
2
+ <wt-popup
3
+ class="wt-cc-status-select-error-popup"
4
+ min-width="480"
5
+ @close="close"
6
+ >
7
+ <template v-slot:header>
8
+ {{ $t('webitelUI.agentStatusSelect.statusSelectErrorPopup.title') }}
9
+ </template>
10
+ <template v-slot:main>
11
+ <article class="wt-cc-status-select-error-popup__main-wrapper">
12
+ <wt-icon
13
+ icon="attention"
14
+ color="danger"
15
+ ></wt-icon>
16
+ <p class="wt-cc-status-select-error-popup__message">
17
+ {{ $t(`webitelUI.agentStatusSelect.statusSelectErrorPopup.${'message'}`) }}
18
+ </p>
19
+ </article>
20
+ </template>
21
+ <template v-slot:actions>
22
+ <wt-button
23
+ color="secondary"
24
+ @click="close"
25
+ >{{ $t('reusable.ok') }}
26
+ </wt-button>
27
+ </template>
28
+ </wt-popup>
29
+ </template>
30
+
31
+ <script setup>
32
+
33
+ const props = defineProps({
34
+ error: {
35
+ type: Object,
36
+ required: true,
37
+ },
38
+ });
39
+
40
+ const emit = defineEmits(['close']);
41
+
42
+ function close() {
43
+ emit('close');
44
+ }
45
+
46
+ </script>
47
+
48
+ <style lang="scss" scoped>
49
+ .wt-cc-status-select-error-popup__main-wrapper {
50
+ display: flex;
51
+ flex-direction: column;
52
+ justify-content: center;
53
+ align-items: center;
54
+ gap: var(--spacing-sm);
55
+ }
56
+ </style>
@@ -0,0 +1,106 @@
1
+ <template>
2
+ <article class="wt-cc-agent-status-select">
3
+ <wt-status-select
4
+ :status="status"
5
+ :status-duration="statusDuration"
6
+ @change="handleStatusSelectInput"
7
+ ></wt-status-select>
8
+ <pause-cause-popup
9
+ v-if="isPauseCausePopup"
10
+ :options="pauseCauses"
11
+ @change="handlePauseCauseInput"
12
+ @close="closePauseCausePopup"
13
+ ></pause-cause-popup>
14
+ <status-select-error-popup
15
+ v-if="error"
16
+ :error="error"
17
+ @close="error = null"
18
+ ></status-select-error-popup>
19
+ </article>
20
+ </template>
21
+
22
+ <script setup>
23
+ import AgentStatus from '../../../enums/AgentStatus/AgentStatus.enum';
24
+ import { ref } from 'vue';
25
+ import { useStore } from 'vuex';
26
+ import AgentStatusAPIFactory from '../api/agent-status';
27
+ import PauseCauseAPIFactory from '../api/pause-cause';
28
+ import PauseCausePopup from './_internals/wt-cc-pause-cause-popup.vue';
29
+ import StatusSelectErrorPopup from './_internals/wt-cc-status-select-error-popup.vue';
30
+
31
+ const props = defineProps({
32
+ agentId: {
33
+ type: [String, Number],
34
+ required: true,
35
+ },
36
+ status: {
37
+ // can be undefined, is agent wasn't loaded yet
38
+ default: AgentStatus.OFFLINE,
39
+ },
40
+ statusDuration: {
41
+ type: [Number, String],
42
+ default: 0,
43
+ },
44
+ });
45
+
46
+ const emit = defineEmits(['changed']);
47
+
48
+ const { api } = useStore().state;
49
+ const AgentStatusAPI = AgentStatusAPIFactory(api);
50
+ const PauseCauseAPI = PauseCauseAPIFactory(api);
51
+
52
+ const isPauseCausePopup = ref(false);
53
+ const pauseCauses = ref([]);
54
+ const error = ref(null);
55
+
56
+ function openPauseCausePopup() {
57
+ isPauseCausePopup.value = true;
58
+ }
59
+
60
+ function closePauseCausePopup() {
61
+ isPauseCausePopup.value = false;
62
+ }
63
+
64
+ async function loadPauseCauses() {
65
+ const response = await PauseCauseAPI.getList({ agentId: props.agentId });
66
+ pauseCauses.value = response.items;
67
+ }
68
+
69
+ async function updateStatus({ agentId, status, pauseCause }) {
70
+ return AgentStatusAPI.patch({ agentId, status, pauseCause });
71
+ }
72
+
73
+ async function changeStatus({ status, pauseCause }) {
74
+ try {
75
+ const statusPayload = { agentId: props.agentId, status, pauseCause };
76
+ await updateStatus(statusPayload);
77
+ emit('changed', statusPayload);
78
+ } catch (err) {
79
+ error.value = err;
80
+ throw err;
81
+ }
82
+ }
83
+
84
+ async function handleStatusSelectInput(status) {
85
+ if (status === AgentStatus.PAUSE) {
86
+ await loadPauseCauses();
87
+ if (pauseCauses.value.length) {
88
+ openPauseCausePopup();
89
+ return;
90
+ }
91
+ }
92
+ if (status === props.status) return;
93
+ await changeStatus({ status });
94
+ }
95
+
96
+ function handlePauseCauseInput(pauseCause) {
97
+ const status = AgentStatus.PAUSE;
98
+ return changeStatus({ status, pauseCause });
99
+ }
100
+ </script>
101
+
102
+ <style lang="scss" scoped>
103
+ .wt-cc-agent-status-select .wt-status-select {
104
+ max-width: 200px;
105
+ }
106
+ </style>
@@ -7,9 +7,9 @@
7
7
  </template>
8
8
 
9
9
  <script setup>
10
- import getNamespacedState from '@webitel/ui-sdk/src/store/helpers/getNamespacedState';
11
10
  import { computed } from 'vue';
12
11
  import { useStore } from 'vuex';
12
+ import getNamespacedState from '../../../store/helpers/getNamespacedState';
13
13
 
14
14
  const props = defineProps({
15
15
  namespace: {