@webitel/ui-sdk 3.0.4 → 3.0.6

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.
Files changed (31) hide show
  1. package/dist/ui-sdk.common.js +2 -2
  2. package/dist/ui-sdk.common.js.map +1 -1
  3. package/dist/ui-sdk.umd.js +2 -2
  4. package/dist/ui-sdk.umd.js.map +1 -1
  5. package/dist/ui-sdk.umd.min.js +1 -1
  6. package/dist/ui-sdk.umd.min.js.map +1 -1
  7. package/package.json +4 -1
  8. package/src/locale/en/en.js +13 -0
  9. package/src/locale/es/es.js +282 -0
  10. package/src/locale/i18n.js +2 -0
  11. package/src/locale/ru/ru.js +13 -0
  12. package/src/locale/ua/ua.js +13 -0
  13. package/src/mixins/validationMixin/validationMixin.js +12 -12
  14. package/src/modules/AuditForm/components/__tests__/audit-form-question-read-wrapper.spec.js +14 -0
  15. package/src/modules/AuditForm/components/__tests__/audit-form-question-write-wrapper.spec.js +59 -0
  16. package/src/modules/AuditForm/components/__tests__/audit-form-question.spec.js +14 -0
  17. package/src/modules/AuditForm/components/__tests__/audit-form.spec.js +66 -0
  18. package/src/modules/AuditForm/components/audit-form-question-read-wrapper.vue +100 -0
  19. package/src/modules/AuditForm/components/audit-form-question-write-wrapper.vue +151 -0
  20. package/src/modules/AuditForm/components/audit-form-question.vue +102 -0
  21. package/src/modules/AuditForm/components/audit-form.vue +142 -0
  22. package/src/modules/AuditForm/components/questions/__tests__/audit-form-question-options.spec.js +46 -0
  23. package/src/modules/AuditForm/components/questions/__tests__/audit-form-question-score.spec.js +30 -0
  24. package/src/modules/AuditForm/components/questions/audit-form-question-options.vue +124 -0
  25. package/src/modules/AuditForm/components/questions/audit-form-question-score.vue +89 -0
  26. package/src/modules/AuditForm/composables/useDestroyableSortable.js +66 -0
  27. package/src/modules/AuditForm/schemas/AuditFormQuestionOptionsSchema.js +11 -0
  28. package/src/modules/AuditForm/schemas/AuditFormQuestionSchema.js +8 -0
  29. package/src/modules/AuditForm/schemas/AuditFormQuestionScoreSchema.js +6 -0
  30. package/src/scripts/__tests__/updateObject.spec.js +19 -0
  31. package/src/scripts/updateObject.js +6 -0
@@ -0,0 +1,100 @@
1
+ <template>
2
+ <article
3
+ class="audit-form-question-read"
4
+ :class="{
5
+ 'audit-form-question-read--filled': result && result.score != null,
6
+ }"
7
+ @keyup.enter="emit('activate')"
8
+ @click="emit('activate')"
9
+ >
10
+ <header class="audit-form-question-read-header">
11
+ <p
12
+ class="audit-form-question-read-text"
13
+ :class="{
14
+ 'audit-form-question-read-text--required': question.required,
15
+ }"
16
+ >{{ question.text }}</p>
17
+ <wt-icon
18
+ class="audit-form-question-read__drag-icon"
19
+ v-if="!disableDragging"
20
+ icon="move"
21
+ color="secondary"
22
+ ></wt-icon>
23
+ </header>
24
+ <section class="audit-form-question-read-content">
25
+ <component
26
+ :is="QuestionTypeComponent"
27
+ :question="question"
28
+ :result="result"
29
+ mode="read"
30
+ @change:result="emit('change:result', $event)"
31
+ ></component>
32
+ </section>
33
+ </article>
34
+ </template>
35
+
36
+ <script setup>
37
+ import { computed } from 'vue';
38
+ import AuditFormQuestionOptions from './questions/audit-form-question-options.vue';
39
+ import AuditFormQuestionScore from './questions/audit-form-question-score.vue';
40
+ import WtIcon from '../../../components/atoms/wt-icon/wt-icon.vue';
41
+
42
+ const props = defineProps({
43
+ question: {
44
+ type: Object,
45
+ required: true,
46
+ },
47
+ result: {
48
+ type: Object,
49
+ required: true,
50
+ },
51
+ disableDragging: {
52
+ type: Boolean,
53
+ default: false,
54
+ },
55
+ });
56
+
57
+ const emit = defineEmits([
58
+ 'change:result',
59
+ 'activate',
60
+ ]);
61
+
62
+ const QuestionTypeComponent = computed(() => {
63
+ if (props.question.type === 'options') return AuditFormQuestionOptions;
64
+ if (props.question.type === 'score') return AuditFormQuestionScore;
65
+ return null;
66
+ });
67
+ </script>
68
+
69
+ <style lang="scss" scoped>
70
+ .audit-form-question-read {
71
+ position: relative;
72
+ display: flex;
73
+ flex-direction: column;
74
+ gap: var(--spacing-sm);
75
+
76
+ &--filled {
77
+ background: var(--secondary-color-50);
78
+ }
79
+ }
80
+
81
+ .audit-form-question-read__drag-icon {
82
+ position: absolute;
83
+ top: 0;
84
+ left: 50%;
85
+ transform: translateX(-50%);
86
+ }
87
+
88
+ .audit-form-question-read-header {
89
+ margin-bottom: var(--spacing-xs);
90
+ }
91
+
92
+ .audit-form-question-read-text {
93
+ &--required {
94
+ &:after {
95
+ content: '*';
96
+ color: var(--false-color);
97
+ }
98
+ }
99
+ }
100
+ </style>
@@ -0,0 +1,151 @@
1
+ <template>
2
+ <article class="audit-form-question-write">
3
+ <header class="audit-form-question-write-header">
4
+ <wt-switcher
5
+ :value="question.required"
6
+ :label="$t('reusable.required')"
7
+ @change="updateQuestion({ path: 'required', value: $event })"
8
+ ></wt-switcher>
9
+ <div class="audit-form-question-write-header__actions">
10
+ <wt-tooltip>
11
+ <template v-slot:activator>
12
+ <wt-icon-btn
13
+ icon="copy"
14
+ @click="emit('copy')"
15
+ ></wt-icon-btn>
16
+ </template>
17
+ {{ $t('reusable.copy') }}
18
+ </wt-tooltip>
19
+ <wt-tooltip>
20
+ <template v-slot:activator>
21
+ <wt-icon-btn
22
+ icon="bucket"
23
+ :disabled="disableDelete"
24
+ @click="emit('delete')"
25
+ ></wt-icon-btn>
26
+ </template>
27
+ {{ $t('reusable.delete') }}
28
+ </wt-tooltip>
29
+ </div>
30
+ </header>
31
+ <section class="audit-form-question-write-content">
32
+ <div class="audit-form-question-write-content-question">
33
+ <wt-input
34
+ :value="question.text"
35
+ :label="$t('webitelUI.auditForm.question')"
36
+ :v="v.question.text"
37
+ required
38
+ @input="updateQuestion({ path: 'text', value: $event })"
39
+ ></wt-input>
40
+ <wt-select
41
+ :value="prettifiedQuestionType"
42
+ :options="QuestionType"
43
+ track-by="value"
44
+ :clearable="false"
45
+ :label="$t('vocabulary.type')"
46
+ @input="handleQuestionTypeChange($event.value)"
47
+ ></wt-select>
48
+ </div>
49
+ <component
50
+ :is="QuestionTypeComponent"
51
+ :question="question"
52
+ mode="write"
53
+ @change:question="emit('change:question', $event)"
54
+ ></component>
55
+ </section>
56
+ </article>
57
+ </template>
58
+
59
+ <script setup>
60
+ import set from 'lodash/set';
61
+ import cloneDeep from 'lodash/cloneDeep';
62
+ import { computed } from 'vue';
63
+ import WtTooltip from '../../../components/atoms/wt-tooltip/wt-tooltip.vue';
64
+ import WtSwitcher from '../../../components/molecules/wt-switcher/wt-switcher.vue';
65
+ import WtIconBtn from '../../../components/molecules/wt-icon-btn/wt-icon-btn.vue';
66
+ import WtInput from '../../../components/molecules/wt-input/wt-input.vue';
67
+ import WtSelect from '../../../components/molecules/wt-select/wt-select.vue';
68
+
69
+ import AuditFormQuestionOptions from './questions/audit-form-question-options.vue';
70
+ import AuditFormQuestionScore from './questions/audit-form-question-score.vue';
71
+
72
+ import { generateQuestionScoreSchema } from '../schemas/AuditFormQuestionScoreSchema';
73
+ import { generateQuestionOptionsSchema } from '../schemas/AuditFormQuestionOptionsSchema';
74
+
75
+ const props = defineProps({
76
+ question: {
77
+ type: Object,
78
+ required: true,
79
+ },
80
+ disableDelete: {
81
+ type: Boolean,
82
+ default: true,
83
+ },
84
+ v: {
85
+ type: Object,
86
+ },
87
+ });
88
+
89
+ const emit = defineEmits([
90
+ 'change:question',
91
+ 'copy',
92
+ 'delete',
93
+ ]);
94
+
95
+ const QuestionType = [
96
+ { value: 'options', locale: 'webitelUI.auditForm.type.options' },
97
+ { value: 'score', locale: 'webitelUI.auditForm.type.score' },
98
+ ];
99
+
100
+ const prettifiedQuestionType = computed(() => QuestionType.find(({ value }) => value === props.question.type));
101
+
102
+ const QuestionTypeComponent = computed(() => {
103
+ if (props.question.type === 'options') return AuditFormQuestionOptions;
104
+ if (props.question.type === 'score') return AuditFormQuestionScore;
105
+ return null;
106
+ });
107
+
108
+ function updateQuestion({ path, value }) {
109
+ const question = set(cloneDeep(props.question), path, value);
110
+ emit('change:question', question);
111
+ }
112
+
113
+ function handleQuestionTypeChange(type) {
114
+ const question = cloneDeep(props.question);
115
+ if (type === 'options') {
116
+ Object.assign(question, generateQuestionOptionsSchema());
117
+ } else if (type === 'score') {
118
+ Object.assign(question, generateQuestionScoreSchema());
119
+ }
120
+ emit('change:question', question);
121
+ }
122
+
123
+ </script>
124
+
125
+ <style lang="scss" scoped>
126
+ .audit-form-question.audit-form-question-write {
127
+ display: flex;
128
+ flex-direction: column;
129
+ gap: var(--spacing-sm);
130
+ box-shadow: var(--elevation-3);
131
+ }
132
+
133
+ .audit-form-question-write-header {
134
+ display: flex;
135
+ justify-content: space-between;
136
+ align-items: center;
137
+
138
+ &__actions {
139
+ display: flex;
140
+ gap: var(--spacing-sm);
141
+ }
142
+ }
143
+
144
+ .audit-form-question-write-content-question {
145
+ display: grid;
146
+ grid-template-columns: 3fr 1fr;
147
+ gap: var(--spacing-sm);
148
+ margin-right: calc(24px + var(--spacing-sm)); // delete icon action for type "options"
149
+ margin-bottom: var(--spacing-sm);
150
+ }
151
+ </style>
@@ -0,0 +1,102 @@
1
+ <template>
2
+ <component
3
+ class="audit-form-question"
4
+ :class="[
5
+ `audit-form-question--mode-${mode}`
6
+ ]"
7
+ v-clickaway="saveQuestion"
8
+ :is="component"
9
+ :question="question"
10
+ :result="result"
11
+ :v="v$"
12
+ :disable-dragging="mode === 'fill'"
13
+ :disable-delete="disableDelete"
14
+ @copy="emits('copy')"
15
+ @delete="emits('delete')"
16
+ @activate="activateQuestion"
17
+ @change:question="emits('update:question', $event)"
18
+ @change:result="emits('update:result', $event)"
19
+ ></component>
20
+ </template>
21
+
22
+ <script setup>
23
+ import { useVuelidate } from '@vuelidate/core';
24
+ import { required } from '@vuelidate/validators';
25
+ import { computed, ref, toRefs } from 'vue';
26
+ import vClickaway from '../../../directives/clickaway/clickaway';
27
+ import QuestionWrite from './audit-form-question-write-wrapper.vue';
28
+ import QuestionRead from './audit-form-question-read-wrapper.vue';
29
+
30
+ const props = defineProps({
31
+ question: {
32
+ type: Object,
33
+ required: true,
34
+ },
35
+ result: {
36
+ type: Object,
37
+ },
38
+ mode: {
39
+ type: String,
40
+ },
41
+ disableDelete: {
42
+ type: Boolean,
43
+ default: true,
44
+ },
45
+ });
46
+
47
+ const emits = defineEmits([
48
+ 'copy',
49
+ 'delete',
50
+ 'update:question',
51
+ 'update:result',
52
+ ]);
53
+
54
+ const QuestionState = {
55
+ SAVED: 'saved',
56
+ EDIT: 'edit',
57
+ };
58
+
59
+ const state = ref(QuestionState.SAVED);
60
+
61
+ const { question } = toRefs(props);
62
+
63
+ // validate only "create" mode
64
+ const v$ = useVuelidate(computed(() => (
65
+ (props.mode === 'create')
66
+ ? {
67
+ question: {
68
+ text: { required },
69
+ },
70
+ $autoDirty: true,
71
+ } : {})), { question });
72
+
73
+ const component = computed(() => {
74
+ if (props.mode === 'create') {
75
+ if (state.value === QuestionState.SAVED) return QuestionRead;
76
+ return QuestionWrite;
77
+ }
78
+ return QuestionRead;
79
+ });
80
+
81
+ function saveQuestion() {
82
+ state.value = QuestionState.SAVED;
83
+ }
84
+
85
+ function activateQuestion() {
86
+ if (props.mode !== 'create') return;
87
+ state.value = QuestionState.EDIT;
88
+ }
89
+ </script>
90
+
91
+ <style lang="scss" scoped>
92
+ .audit-form-question {
93
+ padding: var(--spacing-sm);
94
+ background: var(--main-color);
95
+ border-radius: var(--border-radius);
96
+ box-shadow: var(--elevation-1);
97
+
98
+ &--mode-create.audit-form-question-read {
99
+ cursor: pointer;
100
+ }
101
+ }
102
+ </style>
@@ -0,0 +1,142 @@
1
+ <template>
2
+ <section class="audit-form">
3
+ <div
4
+ class="audit-form__sortable-wrapper"
5
+ ref="sortableWrapper"
6
+ v-if="!reloadSortable"
7
+ >
8
+ <audit-form-question
9
+ v-for="(question, key) of questions"
10
+ :key="key"
11
+ :question="question"
12
+ :result="(result && result[key]) ? result[key] : null"
13
+ :mode="mode"
14
+ :disable-delete="questions.length <= 1"
15
+ @copy="copyQuestion({ question, key })"
16
+ @delete="deleteQuestion({ question, key})"
17
+ @update:question="handleQuestionUpdate({ key, value: $event })"
18
+ @update:result="handleResultUpdate({ key, value: $event })"
19
+ ></audit-form-question>
20
+ </div>
21
+ <wt-button
22
+ class="audit-form__add-button"
23
+ v-if="mode === 'create'"
24
+ :disabled="isInvalidForm"
25
+ @click="addQuestion"
26
+ >{{ $t('webitelUI.auditForm.addQuestion') }}
27
+ </wt-button>
28
+ </section>
29
+ </template>
30
+
31
+ <script setup>
32
+ import cloneDeep from 'lodash/cloneDeep';
33
+ import {
34
+ watch, watchEffect, ref, computed,
35
+ } from 'vue';
36
+ import { useVuelidate } from '@vuelidate/core';
37
+ import { useDestroyableSortable } from '../composables/useDestroyableSortable';
38
+ import AuditFormQuestion from './audit-form-question.vue';
39
+ import WtButton from '../../../components/atoms/wt-button/wt-button.vue';
40
+ import { generateQuestionSchema } from '../schemas/AuditFormQuestionSchema';
41
+
42
+ const props = defineProps({
43
+ mode: {
44
+ type: String,
45
+ required: true,
46
+ /*
47
+ * Available options: ['create', 'fill']
48
+ * */
49
+ },
50
+ questions: {
51
+ type: Array,
52
+ required: true,
53
+ },
54
+ result: {
55
+ type: Array,
56
+ },
57
+ });
58
+
59
+ const emit = defineEmits([
60
+ 'update:questions',
61
+ 'update:result',
62
+ 'update:validation',
63
+ ]);
64
+
65
+ const v$ = useVuelidate();
66
+
67
+ const isInvalidForm = computed(() => !!v$.value.$errors.length);
68
+
69
+ function addQuestion({ index, question } = {}) {
70
+ const questions = [...props.questions];
71
+ const newQuestion = question || generateQuestionSchema();
72
+ if (index != null) questions.splice(index, 0, newQuestion);
73
+ else questions.push(newQuestion);
74
+ emit('update:questions', questions);
75
+ }
76
+
77
+ function handleQuestionUpdate({ key, value }) {
78
+ const questions = [...props.questions];
79
+ questions[key] = value;
80
+ emit('update:questions', questions);
81
+ }
82
+
83
+ function copyQuestion({ question, key }) {
84
+ const questions = [...props.questions];
85
+ questions.splice(key + 1, 0, cloneDeep(question));
86
+ emit('update:questions', questions);
87
+ }
88
+
89
+ function deleteQuestion({ key }) {
90
+ const questions = [...props.questions];
91
+ questions.splice(key, 1);
92
+ emit('update:questions', questions);
93
+ }
94
+
95
+ function changeQuestionsOrder({ oldIndex, newIndex }) {
96
+ const questions = [...props.questions];
97
+ const [el] = questions.splice(oldIndex, 1);
98
+ questions.splice(newIndex, 0, el);
99
+ emit('update:questions', questions);
100
+ }
101
+
102
+ function handleResultUpdate({ key, value }) {
103
+ const result = [...props.result];
104
+ result[key] = value;
105
+ emit('update:result', result);
106
+ }
107
+
108
+ function initResult() {
109
+ const result = props.questions.map(() => null);
110
+ emit('update:result', result);
111
+ }
112
+
113
+ const sortableWrapper = ref(null);
114
+
115
+ const { reloadSortable } = useDestroyableSortable(sortableWrapper, {
116
+ handle: '.audit-form-question-read__drag-icon',
117
+ disabled: props.mode !== 'create',
118
+ onEnd: ({ newIndex, oldIndex }) => {
119
+ if (newIndex === oldIndex) return;
120
+ changeQuestionsOrder({ oldIndex, newIndex });
121
+ },
122
+ });
123
+
124
+ watch(v$, () => emit('update:validation', v$));
125
+ watchEffect(initResult);
126
+ </script>
127
+
128
+ <style lang="scss" scoped>
129
+ .audit-form {
130
+ display: flex;
131
+ flex-direction: column;
132
+ gap: var(--spacing-sm);
133
+
134
+ &__sortable-wrapper {
135
+ display: contents;
136
+ }
137
+ }
138
+
139
+ .audit-form__add-button {
140
+ align-self: flex-end;
141
+ }
142
+ </style>
@@ -0,0 +1,46 @@
1
+ import { shallowMount, mount } from '@vue/test-utils';
2
+ import AuditFormQuestionOptions from '../audit-form-question-options.vue';
3
+
4
+ describe('AuditFormQuestionOptions', () => {
5
+ it('renders a component', () => {
6
+ const wrapper = shallowMount(AuditFormQuestionOptions, {
7
+ props: {
8
+ question: {},
9
+ },
10
+ });
11
+ expect(wrapper.isVisible()).toBe(true);
12
+ });
13
+ it('adds new question option at "add" button click', () => {
14
+ const wrapper = shallowMount(AuditFormQuestionOptions, {
15
+ props: {
16
+ question: { options: [{}] },
17
+ mode: 'write',
18
+ },
19
+ });
20
+ wrapper.findComponent('.audit-form-question-options-write__add-button').vm.$emit('click');
21
+ expect(wrapper.emitted()['change:question'][0][0].options.length).toBe(2);
22
+ });
23
+ it('deletes existing question option at "delete" icon-btn click', () => {
24
+ const wrapper = mount(AuditFormQuestionOptions, {
25
+ props: {
26
+ question: { options: [{}] },
27
+ mode: 'write',
28
+ },
29
+ });
30
+ const deleteBtn = wrapper.findComponent({ name: 'wt-icon-btn' });
31
+ expect(deleteBtn.props().icon).toBe('bucket');
32
+ deleteBtn.vm.$emit('click');
33
+ expect(wrapper.emitted()['change:question'][0][0].options.length).toBe(0);
34
+ });
35
+ it('emits result change with selected radio option score', () => {
36
+ const score = 11;
37
+ const wrapper = shallowMount(AuditFormQuestionOptions, {
38
+ props: {
39
+ question: { options: [{ score }] },
40
+ mode: 'read',
41
+ },
42
+ });
43
+ wrapper.findComponent({ name: 'wt-radio' }).vm.$emit('input', { score });
44
+ expect(wrapper.emitted()['change:result'][0][0]).toEqual({ score });
45
+ });
46
+ });
@@ -0,0 +1,30 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import AuditFormQuestionScore from '../audit-form-question-score.vue';
3
+
4
+ describe('AuditFormQuestionScore', () => {
5
+ it('renders a component', () => {
6
+ const wrapper = shallowMount(AuditFormQuestionScore, {
7
+ props: {
8
+ question: {
9
+ min: 0,
10
+ max: 10,
11
+ },
12
+ },
13
+ });
14
+ expect(wrapper.isVisible()).toBe(true);
15
+ });
16
+ it('emits result change with selected radio score', () => {
17
+ const min = 8;
18
+ const wrapper = shallowMount(AuditFormQuestionScore, {
19
+ props: {
20
+ question: {
21
+ min,
22
+ max: 10,
23
+ },
24
+ mode: 'read',
25
+ },
26
+ });
27
+ wrapper.findComponent({ name: 'wt-radio' }).vm.$emit('input');
28
+ expect(wrapper.emitted()['change:result'][0][0]).toEqual({ score: min });
29
+ });
30
+ });
@@ -0,0 +1,124 @@
1
+ <template>
2
+ <article class="audit-form-question-options">
3
+ <div
4
+ v-if="mode === 'write'"
5
+ class="audit-form-question-options-write"
6
+ >
7
+ <div
8
+ v-for="({ text, score }, key) of question.options"
9
+ :key="key"
10
+ class="audit-form-question-options-write-row"
11
+ >
12
+ <wt-input
13
+ :value="text"
14
+ :label="$tc('webitelUI.auditForm.option', 1)"
15
+ @input="updateQuestion({ path: `options[${key}].text`, value: $event })"
16
+ ></wt-input>
17
+ <wt-input
18
+ :value="score"
19
+ :label="$tc('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>
33
+ <wt-button
34
+ class="audit-form-question-options-write__add-button"
35
+ @click="addQuestionOption"
36
+ >{{ $t('reusable.add') }}
37
+ </wt-button>
38
+ </div>
39
+ <div
40
+ v-else-if="mode === 'read'"
41
+ class="audit-form-question-options-read"
42
+ >
43
+ <wt-radio
44
+ v-for="({ text, score }) of question.options"
45
+ :key="score"
46
+ :label="text"
47
+ :value="score"
48
+ :selected="result ? result.score : result"
49
+ @input="emit('change:result', { score })"
50
+ ></wt-radio>
51
+ </div>
52
+ <div v-else>Unknown mode: {{ mode }}</div>
53
+ </article>
54
+ </template>
55
+
56
+ <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';
64
+
65
+ const props = defineProps({
66
+ question: {
67
+ type: Object,
68
+ required: true,
69
+ },
70
+ result: {
71
+ type: Object,
72
+ },
73
+ mode: {
74
+ // options: ['read', 'write']
75
+ type: String,
76
+ default: 'read',
77
+ },
78
+ });
79
+
80
+ const emit = defineEmits([
81
+ 'change:question',
82
+ 'change:result',
83
+ ]);
84
+
85
+ function updateQuestion({ path, value }) {
86
+ emit('change:question', updateObject({ obj: props.question, path, value }));
87
+ }
88
+
89
+ function addQuestionOption() {
90
+ const options = [...props.question.options, generateOption()];
91
+ return updateQuestion({ path: 'options', value: options });
92
+ }
93
+
94
+ function deleteQuestionOption({ key }) {
95
+ const options = [...props.question.options];
96
+ options.splice(key, 1);
97
+ return updateQuestion({ path: 'options', value: options });
98
+ }
99
+ </script>
100
+
101
+ <style lang="scss" scoped>
102
+ .audit-form-question-options-write {
103
+ display: flex;
104
+ flex-direction: column;
105
+ gap: var(--spacing-sm);
106
+
107
+ &__add-button {
108
+ align-self: flex-start;
109
+ }
110
+ }
111
+
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
+ .audit-form-question-options-read {
120
+ display: flex;
121
+ flex-direction: column;
122
+ gap: var(--spacing-sm);
123
+ }
124
+ </style>