@webitel/ui-sdk 3.1.60 → 3.1.61

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.1.60",
3
+ "version": "3.1.61",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve-lib": "vue-cli-service serve",
@@ -170,7 +170,7 @@ export default {
170
170
  default: false,
171
171
  },
172
172
  },
173
- emits: ['update:modelValue', 'input'],
173
+ emits: ['update:modelValue', 'input', 'keyup'],
174
174
  data: () => ({
175
175
  inputType: '',
176
176
  isPasswordVisible: false,
@@ -19,21 +19,21 @@ export default {
19
19
  validationText() {
20
20
  let validationText = '';
21
21
  if (this.isValidation && this.invalid) {
22
- if (this.v.required.$invalid) validationText = this.$t('validation.required');
23
- else if (this.v.numeric.$invalid) validationText = this.$t('validation.numeric');
24
- else if (this.v.email.$invalid) validationText = this.$t('validation.email');
25
- else if (this.v.gatewayHostValidator.$invalid) validationText = this.$t('validation.gatewayHostValidator');
26
- else if (this.v.ipValidator.$invalid) validationText = this.$t('validation.ipValidator');
27
- else if (this.v.macValidator.$invalid) validationText = this.$t('validation.macValidator');
28
- else if (this.v.minValue.$invalid) validationText = `${this.$t('validation.minValue')} ${this.v.$params.minValue.min}`;
29
- else if (this.v.maxValue.$invalid) validationText = `${this.$t('validation.maxValue')} ${this.v.$params.maxValue.max}`;
30
- else if (this.v.sipAccountValidator.$invalid) validationText = this.$t('validation.sipAccountValidator');
31
- else if (this.v.minLength.$invalid) validationText = `${this.$t('validation.minLength')} ${this.v.$params.minLength.min}`;
32
- else if (this.v.url.$invalid) validationText = `${this.$t('validation.url')}`;
22
+ if (this.v.required?.$invalid) validationText = this.$t('validation.required');
23
+ else if (this.v.numeric?.$invalid) validationText = this.$t('validation.numeric');
24
+ else if (this.v.email?.$invalid) validationText = this.$t('validation.email');
25
+ else if (this.v.gatewayHostValidator?.$invalid) validationText = this.$t('validation.gatewayHostValidator');
26
+ else if (this.v.ipValidator?.$invalid) validationText = this.$t('validation.ipValidator');
27
+ else if (this.v.macValidator?.$invalid) validationText = this.$t('validation.macValidator');
28
+ else if (this.v.minValue?.$invalid) validationText = `${this.$t('validation.minValue')} ${this.v.minValue.$params.min}`;
29
+ else if (this.v.maxValue?.$invalid) validationText = `${this.$t('validation.maxValue')} ${this.v.maxValue.$params.max}`;
30
+ else if (this.v.sipAccountValidator?.$invalid) validationText = this.$t('validation.sipAccountValidator');
31
+ else if (this.v.minLength?.$invalid) validationText = `${this.$t('validation.minLength')} ${this.v.minLength.$params.min}`;
32
+ else if (this.v.url?.$invalid) validationText = `${this.$t('validation.url')}`;
33
33
  }
34
34
  // eslint-disable-next-line no-restricted-syntax
35
35
  for (const { name, text } of this.customValidators) {
36
- if (this.v[name].$invalid) validationText = text;
36
+ if (this.v[name]?.$invalid) validationText = text;
37
37
  }
38
38
  return validationText;
39
39
  },
@@ -37,8 +37,8 @@
37
37
  <script setup>
38
38
  import { computed } from 'vue';
39
39
  import { EngineAuditQuestionType } from 'webitel-sdk';
40
- import AuditFormQuestionOptions from './questions/audit-form-question-options.vue';
41
- import AuditFormQuestionScore from './questions/audit-form-question-score.vue';
40
+ import AuditFormQuestionOptions from './questions/options/audit-form-question-options.vue';
41
+ import AuditFormQuestionScore from './questions/score/audit-form-question-score.vue';
42
42
  import WtIcon from '../../../components/atoms/wt-icon/wt-icon.vue';
43
43
 
44
44
  const props = defineProps({
@@ -47,7 +47,7 @@ const props = defineProps({
47
47
  required: true,
48
48
  },
49
49
  result: {
50
- type: Object,
50
+ type: [Object, null],
51
51
  required: true,
52
52
  },
53
53
  disableDragging: {
@@ -69,8 +69,8 @@ import WtIconBtn from '../../../components/molecules/wt-icon-btn/wt-icon-btn.vue
69
69
  import WtInput from '../../../components/molecules/wt-input/wt-input.vue';
70
70
  import WtSelect from '../../../components/molecules/wt-select/wt-select.vue';
71
71
 
72
- import AuditFormQuestionOptions from './questions/audit-form-question-options.vue';
73
- import AuditFormQuestionScore from './questions/audit-form-question-score.vue';
72
+ import AuditFormQuestionOptions from './questions/options/audit-form-question-options.vue';
73
+ import AuditFormQuestionScore from './questions/score/audit-form-question-score.vue';
74
74
 
75
75
  import { generateQuestionScoreSchema } from '../schemas/AuditFormQuestionScoreSchema';
76
76
  import { generateQuestionOptionsSchema } from '../schemas/AuditFormQuestionOptionsSchema';
@@ -1,20 +1,20 @@
1
1
  <template>
2
2
  <component
3
- class="audit-form-question"
3
+ :is="component"
4
+ v-clickaway="saveQuestion"
4
5
  :class="[
5
6
  `audit-form-question--mode-${mode}`
6
7
  ]"
7
- v-clickaway="saveQuestion"
8
- :is="component"
8
+ :disable-dragging="mode === 'fill'"
9
+ :first="first"
9
10
  :question="question"
11
+ :readonly="readonly"
10
12
  :result="result"
11
13
  :v="v$"
12
- :disable-dragging="mode === 'fill'"
13
- :disable-delete="disableDelete"
14
- :readonly="readonly"
14
+ class="audit-form-question"
15
+ @activate="activateQuestion"
15
16
  @copy="emits('copy')"
16
17
  @delete="emits('delete')"
17
- @activate="activateQuestion"
18
18
  @change:question="emits('update:question', $event)"
19
19
  @change:result="emits('update:result', $event)"
20
20
  ></component>
@@ -23,10 +23,10 @@
23
23
  <script setup>
24
24
  import { useVuelidate } from '@vuelidate/core';
25
25
  import { required } from '@vuelidate/validators';
26
- import { computed, ref, toRefs } from 'vue';
26
+ import { computed, onMounted, ref, toRefs } from 'vue';
27
27
  import vClickaway from '../../../directives/clickaway/clickaway';
28
- import QuestionWrite from './audit-form-question-write-wrapper.vue';
29
28
  import QuestionRead from './audit-form-question-read-wrapper.vue';
29
+ import QuestionWrite from './audit-form-question-write-wrapper.vue';
30
30
 
31
31
  const props = defineProps({
32
32
  question: {
@@ -39,9 +39,9 @@ const props = defineProps({
39
39
  mode: {
40
40
  type: String,
41
41
  },
42
- disableDelete: {
42
+ first: {
43
43
  type: Boolean,
44
- default: true,
44
+ default: false,
45
45
  },
46
46
  readonly: {
47
47
  type: Boolean,
@@ -63,17 +63,20 @@ const QuestionState = {
63
63
 
64
64
  const state = ref(QuestionState.SAVED);
65
65
 
66
- const { question } = toRefs(props);
66
+ // is needed for useVuelidate, because props.question/props.result isn't reactive
67
+ const { question, result } = toRefs(props);
67
68
 
68
- // validate only "create" mode
69
69
  const v$ = useVuelidate(computed(() => (
70
70
  (props.mode === 'create')
71
71
  ? {
72
72
  question: {
73
73
  question: { required },
74
74
  },
75
- $autoDirty: true,
76
- } : {})), { question });
75
+ } : {
76
+ result: {
77
+ required: (value) => (question.value.required ? !!value : true),
78
+ },
79
+ })), { question, result }, { $autoDirty: true });
77
80
 
78
81
  const component = computed(() => {
79
82
  if (props.readonly) return QuestionRead;
@@ -92,13 +95,19 @@ function activateQuestion() {
92
95
  if (props.mode !== 'create') return;
93
96
  state.value = QuestionState.EDIT;
94
97
  }
98
+
99
+ // initialize validations
100
+ onMounted(() => {
101
+ v$.value.$touch();
102
+ activateQuestion();
103
+ });
95
104
  </script>
96
105
 
97
106
  <style lang="scss" scoped>
98
107
  .audit-form-question {
99
108
  padding: var(--spacing-sm);
100
- background: var(--main-color);
101
109
  border-radius: var(--border-radius);
110
+ background: var(--main-color);
102
111
  box-shadow: var(--elevation-1);
103
112
 
104
113
  &--mode-create.audit-form-question-read {
@@ -117,7 +117,7 @@ function initResult() {
117
117
 
118
118
  function initQuestions() {
119
119
  if (props.mode === 'create' && !props.questions.length) {
120
- addQuestion(generateQuestionSchema({ required: true }));
120
+ addQuestion({ question: generateQuestionSchema({ required: true }) });
121
121
  }
122
122
  }
123
123
 
@@ -132,7 +132,7 @@ const { reloadSortable } = useDestroyableSortable(sortableWrapper, {
132
132
  },
133
133
  });
134
134
 
135
- watch(v$, () => emit('update:validation', v$));
135
+ watch(v$, () => emit('update:validation', { invalid: isInvalidForm, v$ }));
136
136
  watchEffect(initResult);
137
137
 
138
138
  onMounted(() => {
@@ -0,0 +1,24 @@
1
+ import { shallowMount, mount } from '@vue/test-utils';
2
+ import AuditFormQuestionOptionsWriteRow from '../audit-form-question-options-write-row.vue';
3
+
4
+ describe('AuditFormQuestionOptionsWriteRow', () => {
5
+ it('renders a component', () => {
6
+ const wrapper = shallowMount(AuditFormQuestionOptionsWriteRow, {
7
+ props: {
8
+ option: {},
9
+ },
10
+ });
11
+ expect(wrapper.isVisible()).toBe(true);
12
+ });
13
+ it('emits "delete" event at "delete" icon-btn click', () => {
14
+ const wrapper = mount(AuditFormQuestionOptionsWriteRow, {
15
+ props: {
16
+ option: {},
17
+ },
18
+ });
19
+ const deleteBtn = wrapper.findComponent({ name: 'wt-icon-btn' });
20
+ expect(deleteBtn.props().icon).toBe('bucket');
21
+ deleteBtn.vm.$emit('click');
22
+ expect(wrapper.emitted().delete).toBeTruthy();
23
+ });
24
+ });
@@ -27,9 +27,8 @@ describe('AuditFormQuestionOptions', () => {
27
27
  mode: 'write',
28
28
  },
29
29
  });
30
- const deleteBtn = wrapper.findComponent({ name: 'wt-icon-btn' });
31
- expect(deleteBtn.props().icon).toBe('bucket');
32
- deleteBtn.vm.$emit('click');
30
+ const writeRow = wrapper.findComponent({ name: 'audit-form-question-options-write-row' });
31
+ writeRow.vm.$emit('delete');
33
32
  expect(wrapper.emitted()['change:question'][0][0].options.length).toBe(0);
34
33
  });
35
34
  it('emits result change with selected radio option score', () => {
@@ -0,0 +1,83 @@
1
+ <template>
2
+ <div class="audit-form-question-options-write-row">
3
+ <wt-input
4
+ :label="$t('webitelUI.auditForm.option', 1)"
5
+ :value="option.name"
6
+ :v="v$.option.name"
7
+ @input="emit('update:option', { name: $event, score: option.score })"
8
+ ></wt-input>
9
+ <wt-input
10
+ :label="$t('webitelUI.auditForm.score', 1)"
11
+ :value="option.score"
12
+ :v="v$.option.score"
13
+ type="number"
14
+ @input="emit('update:option', { name: option.name, score: $event })"
15
+ ></wt-input>
16
+ <wt-tooltip>
17
+ <template v-slot:activator>
18
+ <wt-icon-btn
19
+ :disabled="first"
20
+ icon="bucket"
21
+ @click="emit('delete')"
22
+ ></wt-icon-btn>
23
+ </template>
24
+ {{ $t('reusable.delete') }}
25
+ </wt-tooltip>
26
+ </div>
27
+ </template>
28
+
29
+ <script setup>
30
+ import { useVuelidate } from '@vuelidate/core';
31
+ import { required, minValue, maxValue } from '@vuelidate/validators';
32
+ import { computed, onMounted, toRefs } from 'vue';
33
+ import WtTooltip from '../../../../../components/atoms/wt-tooltip/wt-tooltip.vue';
34
+ import WtIconBtn from '../../../../../components/molecules/wt-icon-btn/wt-icon-btn.vue';
35
+ import WtInput from '../../../../../components/molecules/wt-input/wt-input.vue';
36
+
37
+ const props = defineProps({
38
+ option: {
39
+ type: Object,
40
+ required: true,
41
+ },
42
+ first: {
43
+ type: Boolean,
44
+ default: false,
45
+ },
46
+ });
47
+
48
+ const emit = defineEmits([
49
+ 'change:option',
50
+ 'delete',
51
+ ]);
52
+
53
+ // is needed for useVuelidate, because props.question/props.result isn't reactive
54
+ const { option } = toRefs(props);
55
+
56
+ const v$ = useVuelidate(
57
+ computed(() => (
58
+ {
59
+ option: {
60
+ name: { required },
61
+ score: {
62
+ required,
63
+ minValue: minValue(0),
64
+ maxValue: maxValue(10),
65
+ },
66
+ },
67
+ })),
68
+ { option },
69
+ { $autoDirty: true },
70
+ );
71
+
72
+ // init validation
73
+ onMounted(() => v$.value.$touch());
74
+ </script>
75
+
76
+ <style lang="scss" scoped>
77
+ .audit-form-question-options-write-row {
78
+ display: grid;
79
+ align-items: center;
80
+ grid-template-columns: 3fr 1fr 24px;
81
+ gap: var(--spacing-sm);
82
+ }
83
+ </style>
@@ -4,32 +4,14 @@
4
4
  v-if="mode === 'write'"
5
5
  class="audit-form-question-options-write"
6
6
  >
7
- <div
8
- v-for="({ name, score }, key) of question.options"
7
+ <options-write-row
8
+ v-for="(option, key) of question.options"
9
9
  :key="key"
10
- class="audit-form-question-options-write-row"
11
- >
12
- <wt-input
13
- :value="name"
14
- :label="$t('webitelUI.auditForm.option', 1)"
15
- @input="updateQuestion({ path: `options[${key}].name`, value: $event })"
16
- ></wt-input>
17
- <wt-input
18
- :value="score"
19
- :label="$t('webitelUI.auditForm.score', 1)"
20
- type="number"
21
- @input="updateQuestion({ path: `options[${key}].score`, value: $event })"
22
- ></wt-input>
23
- <wt-tooltip>
24
- <template v-slot:activator>
25
- <wt-icon-btn
26
- icon="bucket"
27
- @click="deleteQuestionOption({ key })"
28
- ></wt-icon-btn>
29
- </template>
30
- {{ $t('reusable.delete') }}
31
- </wt-tooltip>
32
- </div>
10
+ :option="option"
11
+ :first="key === 0"
12
+ @update:option="updateQuestion({ path: `options[${key}]`, value: $event })"
13
+ @delete="deleteQuestionOption({ key })"
14
+ ></options-write-row>
33
15
  <wt-button
34
16
  class="audit-form-question-options-write__add-button"
35
17
  @click="addQuestionOption"
@@ -44,8 +26,8 @@
44
26
  v-for="({ name, score }) of question.options"
45
27
  :key="score"
46
28
  :label="name"
47
- :value="score"
48
29
  :selected="result ? result.score : result"
30
+ :value="score"
49
31
  @input="emit('change:result', { score })"
50
32
  ></wt-radio>
51
33
  </div>
@@ -54,13 +36,11 @@
54
36
  </template>
55
37
 
56
38
  <script setup>
57
- import updateObject from '../../../../scripts/updateObject';
58
- import WtTooltip from '../../../../components/atoms/wt-tooltip/wt-tooltip.vue';
59
- import WtIconBtn from '../../../../components/molecules/wt-icon-btn/wt-icon-btn.vue';
60
- import WtInput from '../../../../components/molecules/wt-input/wt-input.vue';
61
- import WtButton from '../../../../components/atoms/wt-button/wt-button.vue';
62
- import WtRadio from '../../../../components/molecules/wt-radio/wt-radio.vue';
63
- import { generateOption } from '../../schemas/AuditFormQuestionOptionsSchema';
39
+ import OptionsWriteRow from './audit-form-question-options-write-row.vue';
40
+ import WtButton from '../../../../../components/atoms/wt-button/wt-button.vue';
41
+ import WtRadio from '../../../../../components/molecules/wt-radio/wt-radio.vue';
42
+ import updateObject from '../../../../../scripts/updateObject';
43
+ import { generateOption } from '../../../schemas/AuditFormQuestionOptionsSchema';
64
44
 
65
45
  const props = defineProps({
66
46
  question: {
@@ -109,13 +89,6 @@ function deleteQuestionOption({ key }) {
109
89
  }
110
90
  }
111
91
 
112
- .audit-form-question-options-write-row {
113
- display: grid;
114
- grid-template-columns: 3fr 1fr 24px;
115
- gap: var(--spacing-sm);
116
- align-items: center;
117
- }
118
-
119
92
  .audit-form-question-options-read {
120
93
  display: flex;
121
94
  flex-direction: column;
@@ -6,12 +6,20 @@
6
6
  >
7
7
  <wt-input
8
8
  :value="question.min"
9
+ :v="v$.question.min"
10
+ :number-min="0"
11
+ :number-max="19"
9
12
  type="number"
13
+ required
10
14
  @input="updateQuestion({ path: 'min', value: $event })"
11
15
  ></wt-input>
12
16
  <wt-input
13
17
  :value="question.max"
18
+ :v="v$.question.max"
19
+ :number-min="1"
20
+ :number-max="20"
14
21
  type="number"
22
+ required
15
23
  @input="updateQuestion({ path: 'max', value: $event })"
16
24
  ></wt-input>
17
25
  </div>
@@ -32,10 +40,12 @@
32
40
  </template>
33
41
 
34
42
  <script setup>
35
- import { computed } from 'vue';
36
- import updateObject from '../../../../scripts/updateObject';
37
- import WtRadio from '../../../../components/molecules/wt-radio/wt-radio.vue';
38
- import WtInput from '../../../../components/molecules/wt-input/wt-input.vue';
43
+ import { useVuelidate } from '@vuelidate/core';
44
+ import { maxValue, minValue, required } from '@vuelidate/validators';
45
+ import { computed, onMounted, toRefs } from 'vue';
46
+ import updateObject from '../../../../../scripts/updateObject';
47
+ import WtRadio from '../../../../../components/molecules/wt-radio/wt-radio.vue';
48
+ import WtInput from '../../../../../components/molecules/wt-input/wt-input.vue';
39
49
 
40
50
  const props = defineProps({
41
51
  question: {
@@ -57,6 +67,29 @@ const emit = defineEmits([
57
67
  'change:result',
58
68
  ]);
59
69
 
70
+ // is needed for useVuelidate, because props.question/props.result isn't reactive
71
+ const { question } = toRefs(props);
72
+
73
+ const v$ = useVuelidate(
74
+ computed(() => (
75
+ {
76
+ question: {
77
+ min: {
78
+ minValue: minValue(0),
79
+ maxValue: maxValue(9),
80
+ required,
81
+ },
82
+ max: {
83
+ minValue: minValue(1),
84
+ maxValue: maxValue(10),
85
+ required,
86
+ },
87
+ },
88
+ })),
89
+ { question },
90
+ { $autoDirty: true },
91
+ );
92
+
60
93
  const scoreRange = computed(() => {
61
94
  if (props.question.min > props.question.max) return [];
62
95
  const result = [];
@@ -71,6 +104,9 @@ const scoreRange = computed(() => {
71
104
  function updateQuestion({ path, value }) {
72
105
  emit('change:question', updateObject({ obj: props.question, path, value }));
73
106
  }
107
+
108
+ // init validation
109
+ onMounted(() => v$.value.$touch());
74
110
  </script>
75
111
 
76
112
  <style lang="scss" scoped>