better types of questions support on answers page

This commit is contained in:
grey-cat-1908 2024-08-30 19:23:48 +03:00
parent cebd6adbd2
commit 63ccc17cc0
11 changed files with 409 additions and 62 deletions

View file

@ -0,0 +1,133 @@
<template>
<div class="default-card" v-if="modelValue">
<div class="view-form-q-title">
<h3 class="form-q-title">{{ label }}</h3>
<p class="form-q-description">{{ description }}</p>
</div>
<div class="rating">
<div class="rating-options">
<label v-for="n in range" :key="n" class="rating-option">
<input type="radio" :value="n" v-model="selectedValue" class="rating-option--btn" />
<span>{{ n }}</span>
</label>
</div>
</div>
</div>
</template>
<script setup>
import '@/styles/form/view.scss'
import { computed, ref } from 'vue'
const props = defineProps({
label: {
type: String,
default: ''
},
description: {
type: String,
default: null
},
min: {
type: Number,
required: true
},
max: {
type: Number,
required: true
},
modelValue: {
type: Number
}
})
const selectedValue = ref(props.modelValue)
const range = computed(() => {
return Array.from({ length: props.max - props.min + 1 }, (_, i) => props.min + i)
})
</script>
<style scoped lang="scss">
.rating {
display: flex;
flex-direction: column;
//align-items: center;
&-options {
display: flex;
justify-content: space-evenly;
gap: 0 50px;
width: 100%;
@media (max-width: 810px) {
flex-direction: column;
gap: 20px 0;
}
}
&-option {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px 0;
//&--btn {
// width: 20px;
// height: 20px;
// cursor: pointer;
//}
&--btn:after {
width: 25px;
height: 25px;
border-radius: 15px;
top: -6px;
left: -6px;
position: relative;
background: var(--color-input-background);
border: 2px solid var(--color-main-border);
content: '';
display: inline-block;
visibility: visible;
cursor: pointer;
transition: 0.25s ease;
}
&--btn:after:hover {
border: 2px solid var(--color-secondary-border);
background: var(--color-main-background);
}
&--btn:checked:after {
width: 25px;
height: 25px;
border-radius: 15px;
top: -6px;
left: -6px;
position: relative;
border: 2px solid var(--color-main);
background: var(--color-main-toned);
content: '';
display: inline-block;
visibility: visible;
}
@media (max-width: 810px) {
flex-direction: initial;
gap: 0 25px;
}
}
&-delete {
width: 100%;
border-radius: 1rem;
padding: 10px 30px;
margin-top: 25px;
border: 1px solid var(--color-third-border);
&:hover {
border: 1px solid var(--color-main);
}
}
}
</style>

View file

@ -0,0 +1,112 @@
<template>
<div class="default-card" v-if="modelValue.length > 0">
<div class="view-form-q-title">
<h3 class="form-q-title">{{ label }}</h3>
<p class="form-q-description">{{ description }}</p>
</div>
<div class="selector">
<div class="selector-options">
<div
v-for="(option, index) in options"
:key="index"
class="selector-option default-button"
:class="{ selected: isSelected(index) }"
>
<span>{{ option.label }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import '@/styles/form/view.scss'
const props = defineProps({
label: {
type: String,
default: ''
},
description: {
type: String,
default: null
},
options: {
type: Array,
required: true
},
modelValue: {
type: Array,
default: () => []
}
})
function isSelected(index) {
return props.modelValue.includes(index)
}
</script>
<style scoped lang="scss">
.selector {
display: flex;
flex-direction: column;
&-labels {
&-info {
display: flex;
align-items: center;
gap: 0 11px;
color: var(--color-description);
font-size: 16px;
font-weight: 400;
margin-bottom: 30px;
&--sign {
min-width: 23px;
min-height: 23px;
}
}
}
&-options {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
&-option {
border-radius: 0.5rem;
background: var(--color-input-background);
border: 1px solid var(--color-main-border);
padding: 10px;
cursor: pointer;
display: flex;
align-items: center;
&:hover {
border: 1px solid var(--color-secondary-border);
background: var(--color-main-background);
}
&.selected {
border: 1px solid var(--color-main);
background: var(--color-main-toned);
//color: var(--color-alternative-text);
}
}
&-error {
margin-top: 20px;
color: var(--color-red);
display: flex;
align-items: center;
gap: 0 13px;
font-size: 0.9em;
&--sign {
min-width: 23px;
min-height: 23px;
}
}
}
</style>

View file

@ -1,24 +1,73 @@
<template>
<div class="text-question-component">
<input type="text" :value="modelValue" readonly />
<div class="default-card" v-if="modelValue.length > 0">
<div class="view-form-q-title">
<h3 class="form-q-title">{{ label }}</h3>
<p class="form-q-description">{{ description }}</p>
</div>
<div class="text-question">
<input class="text-question-input" type="text" :value="modelValue" readonly />
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import '@/styles/form/view.scss'
const props = defineProps(['modelValue'])
const props = defineProps({
label: {
type: String,
default: ''
},
description: {
type: String,
default: null
},
modelValue: {
type: Array,
default: () => []
}
})
</script>
<style scoped>
.text-question-component {
.text-question {
display: flex;
flex-direction: column;
margin-bottom: 20px;
}
.error {
color: red;
margin-top: 5px;
&-error {
margin-top: 20px;
color: var(--color-red);
display: flex;
align-items: center;
gap: 0 13px;
font-size: 0.9em;
&--sign {
min-width: 23px;
min-height: 23px;
}
}
&-input {
width: 100% !important;
background: var(--color-main-background);
border: 1px solid var(--color-main-border);
padding: 10px 20px;
font-weight: 200;
border-radius: 0.5rem;
outline: 0;
transition:
border,
background 0.25s ease;
&:hover {
border: 1px solid var(--color-secondary-border);
}
&:focus {
border: 1px solid var(--color-third-border);
background: var(--color-input-background);
}
}
}
</style>

View file

@ -34,7 +34,7 @@
import { ref, computed } from 'vue'
import { PhCardsThree, PhCaretCircleUpDown } from '@phosphor-icons/vue'
import '@/styles/form/view.scss';
import '@/styles/form/view.scss'
const props = defineProps({
label: {

View file

@ -10,7 +10,14 @@
<div class="selector-labels-info">
<PhCaretCircleUpDown :size="23" class="selector-labels-info--sign" />
<div class="selector-labels-info--text">
Выберите от {{ minValues }} до {{ Math.min(maxValues, options.length) }} {{ normalizeCountForm(Math.min(maxValues, options.length), ['варианта', 'вариантов', 'вариантов']) }}
Выберите от {{ minValues }} до {{ Math.min(maxValues, options.length) }}
{{
normalizeCountForm(Math.min(maxValues, options.length), [
'варианта',
'вариантов',
'вариантов'
])
}}
</div>
</div>
</div>
@ -37,7 +44,7 @@ import { ref, watch } from 'vue'
import { PhCaretCircleUpDown, PhXCircle } from '@phosphor-icons/vue'
import { normalizeCountForm } from '@/utils/formation'
import '@/styles/form/view.scss';
import '@/styles/form/view.scss'
const props = defineProps({
label: {
@ -99,12 +106,12 @@ function validateSelection() {
error.value = ''
if (selectedIndexes.value.length < props.minValues) {
error.value = `Необходимо выбрать минимум ${props.minValues} ${normalizeCountForm(props.minValues, ['вариант', 'варианта', 'вариантов']) }.`
error.value = `Необходимо выбрать минимум ${props.minValues} ${normalizeCountForm(props.minValues, ['вариант', 'варианта', 'вариантов'])}.`
return
}
if (props.maxValues && selectedIndexes.value.length > props.maxValues) {
error.value = `Необходимо выбрать не больше ${props.maxValues} ${normalizeCountForm(props.maxValues, ['варианта', 'вариантов', 'вариантов']) }.`
error.value = `Необходимо выбрать не больше ${props.maxValues} ${normalizeCountForm(props.maxValues, ['варианта', 'вариантов', 'вариантов'])}.`
return
}

View file

@ -29,7 +29,7 @@ import { PhXCircle } from '@phosphor-icons/vue'
import { validateSNILS, validateTIN } from '@/utils/validators'
import { normalizeCountForm } from '@/utils/formation'
import '@/styles/form/view.scss';
import '@/styles/form/view.scss'
const props = defineProps({
label: {
@ -72,10 +72,10 @@ const error = ref('')
function validateInput() {
error.value = ''
if (props.isRequired || inputValue.value) {
if (props.minLength && inputValue.value.length < props.minLength) {
error.value = `Минимальная длина - ${props.minLength} ${normalizeCountForm(props.minValues, ['символ', 'символа', 'символов']) }`
error.value = `Минимальная длина - ${props.minLength} ${normalizeCountForm(props.minValues, ['символ', 'символа', 'символов'])}`
}
if (props.validator === 1 && !validateTIN(inputValue.value)) {
error.value = 'Некорректный ИНН'
@ -116,7 +116,9 @@ function validateInput() {
font-weight: 200;
border-radius: 0.5rem;
outline: 0;
transition: border, background 0.25s ease;
transition:
border,
background 0.25s ease;
&:hover {
border: 1px solid var(--color-secondary-border);

View file

@ -125,4 +125,4 @@
font-size: 1.17em;
}
}
}
}

View file

@ -1,4 +1,6 @@
export function normalizeCountForm (number: number, words_arr: Array<string>) {
let options = [2, 0, 1, 1, 1, 2];
return words_arr[(number % 100 > 4 && number % 100 < 20) ? 2 : options[(number % 10 < 5) ? number % 10 : 5]];
}
export function normalizeCountForm(number: number, words_arr: Array<string>) {
let options = [2, 0, 1, 1, 1, 2]
return words_arr[
number % 100 > 4 && number % 100 < 20 ? 2 : options[number % 10 < 5 ? number % 10 : 5]
]
}

View file

@ -7,14 +7,14 @@ export function validateTIN(value: String) {
if (len === 10) {
const checksum =
((2 * digits[0] +
4 * digits[1] +
10 * digits[2] +
3 * digits[3] +
5 * digits[4] +
9 * digits[5] +
4 * digits[6] +
6 * digits[7] +
8 * digits[8]) %
4 * digits[1] +
10 * digits[2] +
3 * digits[3] +
5 * digits[4] +
9 * digits[5] +
4 * digits[6] +
6 * digits[7] +
8 * digits[8]) %
11) %
10
return digits[9] === checksum
@ -23,29 +23,29 @@ export function validateTIN(value: String) {
if (len === 12) {
const checksum1 =
((7 * digits[0] +
2 * digits[1] +
4 * digits[2] +
10 * digits[3] +
3 * digits[4] +
5 * digits[5] +
9 * digits[6] +
4 * digits[7] +
6 * digits[8] +
8 * digits[9]) %
2 * digits[1] +
4 * digits[2] +
10 * digits[3] +
3 * digits[4] +
5 * digits[5] +
9 * digits[6] +
4 * digits[7] +
6 * digits[8] +
8 * digits[9]) %
11) %
10
const checksum2 =
((3 * digits[0] +
7 * digits[1] +
2 * digits[2] +
4 * digits[3] +
10 * digits[4] +
3 * digits[5] +
5 * digits[6] +
9 * digits[7] +
4 * digits[8] +
6 * digits[9] +
8 * digits[10]) %
7 * digits[1] +
2 * digits[2] +
4 * digits[3] +
10 * digits[4] +
3 * digits[5] +
5 * digits[6] +
9 * digits[7] +
4 * digits[8] +
6 * digits[9] +
8 * digits[10]) %
11) %
10
return digits[10] === checksum1 && digits[11] === checksum2

View file

@ -4,10 +4,13 @@ import { makeAPIRequest } from '@/utils/http'
import FormNotFound from '@/components/FormNotFound.vue'
import { useRoute } from 'vue-router'
import TextValue from '@/components/answers/TextValue.vue'
import SelectorValue from '@/components/answers/SelectorValue.vue'
import ScaleValue from '@/components/answers/ScaleValue.vue'
const route = useRoute()
const data = ref([])
const questionsData = ref({})
const currentPageNumber = ref(0)
const isAnswerNotFound = ref(true)
const mode = ref(0)
@ -27,6 +30,19 @@ onMounted(async () => {
if (data.value.length > 0) {
isAnswerNotFound.value = false
}
const questionsFormResponse = await makeAPIRequest('/form/get', 'GET', {
id: Number(route.params.id)
})
if (!questionsFormResponse.json || questionsFormResponse.status !== 200) {
return
}
questionsFormResponse.json.data.pages.forEach((p: any) => {
p.questions.forEach((q: any) => {
questionsData.value[q.id] = q
})
})
})
</script>
@ -41,7 +57,27 @@ onMounted(async () => {
</button>
<div class="" v-for="(value, index) in data[currentPageNumber].data.values">
<TextValue v-if="value.question_type === 1" v-model="value.value" />
<TextValue
v-if="questionsData[value.question_id].question_type === 1"
:label="questionsData[value.question_id].label"
:description="questionsData[value.question_id].description"
v-model="value.value"
/>
<SelectorValue
v-if="questionsData[value.question_id].question_type === 2"
:options="questionsData[value.question_id].options"
:label="questionsData[value.question_id].label"
:description="questionsData[value.question_id].description"
v-model="value.values"
></SelectorValue>
<ScaleValue
v-if="questionsData[value.question_id].question_type === 3"
:min="questionsData[value.question_id].min_value"
:max="questionsData[value.question_id].max_value"
:label="questionsData[value.question_id].label"
:description="questionsData[value.question_id].description"
v-model="value.value"
></ScaleValue>
</div>
</div>
</div>

View file

@ -10,7 +10,7 @@ import { useRoute } from 'vue-router'
import { PhInfo, PhCardsThree } from '@phosphor-icons/vue'
import { validateSNILS, validateTIN } from '@/utils/validators'
import '@/styles/form/view.scss';
import '@/styles/form/view.scss'
const route = useRoute()
@ -36,14 +36,18 @@ function beforeSubmitValidate() {
for (let question of currentPage.value.questions) {
const answer = answers.value[question.id]
if (!answer) return false;
if (question.required && !answer.value && !answer.values) return false;
if (!answer) return false
if (question.required && !answer.value && !answer.values) return false
if (question.question_type === 1 && answer.value) {
if (!question.validator && question.min_length && answer.value.length < question.min_length) return false;
if (question.validator === 1 && !validateTIN(answer.value)) return false;
if (question.validator === 2 && !validateSNILS(answer.value)) return false;
} else if (question.question_type === 2 && !(question.required && answer.values.length >= question.min_values)) {
if (!question.validator && question.min_length && answer.value.length < question.min_length)
return false
if (question.validator === 1 && !validateTIN(answer.value)) return false
if (question.validator === 2 && !validateSNILS(answer.value)) return false
} else if (
question.question_type === 2 &&
!(question.required && answer.values.length >= question.min_values)
) {
return false
}
}
@ -58,9 +62,9 @@ async function submitForm() {
} else {
for (let page of data.value.pages) {
page.questions.forEach((q) => {
let answer = answers.value[q.id];
let answer = answers.value[q.id]
if (!q.required && !(answer.value || answer.values)) {
delete answers.value[q.id];
delete answers.value[q.id]
}
})
}
@ -99,7 +103,9 @@ onMounted(async () => {
<div class="view-form-title view-form-container default-card">
<div class="view-form-info">
<PhCardsThree :size="23" class="view-form-info--sign" />
<div class="view-form-info--text">Страница {{ currentPageNumber + 1 }} из {{ data.pages.length }}</div>
<div class="view-form-info--text">
Страница {{ currentPageNumber + 1 }} из {{ data.pages.length }}
</div>
</div>
<h2 class="form-title">{{ data.name }}</h2>
<p class="form-description">{{ currentPage.text }}</p>