mns-mini-zabor/components/modal.vue

252 lines
9.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { getColorNameFromRal } from '@/components/ral'
import type { ralTypes } from '@/components/ral'
import { apiFetch } from '~/utils/apiFetch';
const config = useRuntimeConfig()
const apiBase = config.public.apiBase
const { data: calculatorData } = await apiFetch<ApiCalcType>(`calculator/5/`)
const isModalOpen = useState('modal_open', () => false)
const lamelle_height = useState<number>('lamelle_height')
const lamelles_count = useState<number>('lamelles_count')
const fence_section = useState<number>('fence_section')
const pillar_color = useState<ralTypes>('pillar_color')
const lamelle_color = useState<ralTypes>('lamelle_color')
const section_count = useState('section_count')
const extra_section = useState('extra_section')
const total_length = useState('total_length')
const remove_pillar = useState<boolean>('remove_pillar')
const toggleModal = () => {
modal_data.email = undefined
modal_data.phone = undefined
modal_data.name = undefined
modal_state.show_form = false
modal_state.show_status = null
isModalOpen.value = !isModalOpen.value
}
type modalDataType = {
phone?: string
name?: string
email?: string
policy: boolean
}
const modal_data = reactive<modalDataType>({
email: undefined,
phone: undefined,
name: undefined,
policy: true
})
const modal_form = reactive({
disabled: true,
errors: [],
})
const modal_state = reactive({
show_form: false,
show_status: null as null | 'loading' | 'success' | 'error',
})
const openForm = () => {
modal_state.show_form = !modal_state.show_form
goal('open_form', total_txt)
}
const validateInput = (evt: KeyboardEvent) => {
const valid_symbols = /[a-zA-Z0-9\+(\\)\ @\.]/
if (!valid_symbols.test(evt.key)) {
evt.preventDefault()
return
}
}
const validate = () => {
const phone_regexp = /^\+?[\d\s-()]{0,14}\d{11}$/
const email_regex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/
modal_form.disabled = true
if (
(
(modal_data.phone && phone_regexp.test(modal_data.phone))
|| (modal_data.email && email_regex.test(modal_data.email))
) && modal_data.policy == true
) {
modal_form.disabled = false
return
}
}
watch(modal_data, validate)
const submit = async (e: any) => {
goal('submit_form', modal_data)
modal_state.show_status = 'loading'
try {
const res = await fetch(`${apiBase}/custom_request/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: modal_data.name || `ref from site ${new Date}`,
phone: modal_data.phone,
email: modal_data.email,
fence_info: [
...Object.values(total_txt.value as object).map(el => el.join('\n')),
total_colors.value.join('\n')
].join('\n\n'),
})
})
modal_state.show_status = 'success'
} catch (error) {
modal_state.show_status = 'error'
}
}
const roubleSign = new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: 'RUB',
});
const total_colors = computed(() => {
return [
`Ламели ${lamelle_color.value} ${getColorNameFromRal(lamelle_color.value)}`,
`Столбы ${pillar_color.value} ${getColorNameFromRal(pillar_color.value)}`,
]
})
const total_txt = computed(() => {
if (!calculatorData.value) return
const pillar = parseFloat(calculatorData.value.pillar)
const pillar_base = parseFloat(calculatorData.value.pillar_base)
const lamelles_block = parseFloat(calculatorData.value.lamelles_block)
const { discount } = calculatorData.value
const sections = section_count.value as number
const extra_m = extra_section.value as number * 0.001
const length_m = fence_section.value as number
const lam_count = lamelles_count.value as number
let discountValue = 1
discount.forEach(element => {
if (sections >= element.min_quantity) {
if (element.max_quantitt && sections >= element.max_quantitt) return
discountValue -= element.percent * 0.01
}
});
const prices = {
pillar: discountValue * ((pillar * lam_count * lamelle_height.value) + pillar_base),
lam_quad_regular: discountValue * lamelles_block * lam_count * lamelle_height.value * length_m,
lam_quad_extra: discountValue * lamelles_block * lam_count * lamelle_height.value * extra_m,
}
// console.log(prices)
const extra = extra_section.value ? {
pillar: !remove_pillar.value && {
txt: `Дополнительная секция, столб, 1 шт`,
value: prices.pillar * 1
},
lamella: {
txt: `Блок ламелей с направляющей, 1 шт`,
value: prices.lam_quad_extra * 1
},
} : {}
const regular = {
pillar: !remove_pillar.value && {
txt: `Столб, ${(1 + sections)} шт`,
value: prices.pillar * (1 + sections)
},
lamella: {
txt: `Блок ламелей с направляющей, ${sections} шт`,
value: prices.lam_quad_regular * sections
},
};
const total = [extra, regular].map(item => Object.values(item).map(el => el ? el.value : 0)).flat().reduce((a, b) => a + b, 0)
const res_regular = Object.values(regular).map(item =>
Object.entries(item).map(el => el[0] == 'value' ? roubleSign.format(el[1] as number) : el[1]).join(': ')
).filter(Boolean)
const res_extra = extra_section.value ? Object.values(extra).map(item =>
Object.entries(item).map(el => el[0] == 'value' ? roubleSign.format(el[1] as number) : el[1]).join(': ')
).filter(Boolean) : []
const res_total = [`Итого ${roubleSign.format(total)}`]
return {
regular: res_regular,
extra: res_extra,
total: res_total
}
})
const goal = (target: string, params: object) => {
const nuxtApp = useNuxtApp()
if (nuxtApp.$metrika) {
(nuxtApp.$metrika as any).reachGoal(target, params || {})
}
}
const modalStatus = {
'loading': ["mdi:circle-arrows", 'Отправляем данные'],
'success': ["mdi:check-circle-outline", 'Данные отправлены'],
'error': ["mdi:close-circle-outline", 'Ошибка отправки'],
}
const policy = () => {
navigateTo('/policy', {
open: {
target: "_blank",
},
});
}
</script>
<template>
<div v-if="isModalOpen" class="modal-backdrop" @click.self="toggleModal">
<div class="modal">
<span class="modal-close" @click="toggleModal">
<Icon name="mdi:close" />
</span>
<div class="modal-status" v-if="modal_state.show_status" :class="[modal_state.show_status]">
<div class="modal-status-icon">
<Icon :name="modalStatus[modal_state.show_status][0]" />
</div>
<div class="modal-status-text">{{ modalStatus[modal_state.show_status][1] }}</div>
</div>
<template v-else-if="modal_state.show_form">
<h2>Оставьте контакты для связи </h2>
<form @submit.prevent="submit" ref="form" class="modal-content">
<input type="text" placeholder="Ваше имя" v-model="modal_data.name" />
<input type="phone" placeholder="Ваш номер телефона" v-model="modal_data.phone"
@keypress="validateInput" />
<input type="e-mail" placeholder="Ваш e-mail" v-model="modal_data.email"
@keypress="validateInput" />
<div class="flex gap-4 justify-between items-center">
<input type="checkbox" id="policy" v-model="modal_data.policy" />
<label for="policy">Нажимая кнопку "Отправить" Вы также даете согласие на <NuxtLink to="policy" @click="policy">обработку персональных
данных</NuxtLink>.
</label>
</div>
{{ total_txt && total_txt.total[0] }}
<div class="flex gap-4">
<button class="not-prose" :disabled="modal_form.disabled" type="submit">Отправить</button>
<button class="not-prose" type="reset" @click="toggleModal">Отмена</button>
</div>
</form>
</template>
<template v-else>
<h2>данные расчета</h2>
<div class="flex gap-4 flex-col mb-4 modal-content">
<p>Общая длина: {{ total_length }}<br />
Ламелей: {{ lamelles_count }}<br />
Длина секции: {{ (fence_section * 1000).toFixed(0) }}<br />
Секций: {{ section_count }}<br />
Цвет столба: {{ getColorNameFromRal(pillar_color) }}<br />
Цвет ламелей: {{ getColorNameFromRal(lamelle_color) }}
</p>
<template v-for="item in total_txt">
<p v-if="item.length">
<template v-for="i in item">{{ i }}<br /></template>
</p>
</template>
</div>
<div class="flex gap-4 justify-center">
<button class="not-prose" @click="openForm">Отправить расчет на <nobr>e-mail</nobr></button>
</div>
</template>
</div>
</div>
</template>