PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.4
Booking for Appointments and Events Calendar – Amelia v2.4.4
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / v3 / src / views / public / CatalogForm / CatalogForm.vue
ameliabooking / v3 / src / views / public / CatalogForm Last commit date
CategoriesList 5 days ago CategoryBooking 5 days ago CategoryItem 5 days ago CategoryItemsList 5 days ago Parts 5 days ago CatalogForm.vue 5 days ago
CatalogForm.vue
598 lines
1 <template>
2 <template v-if="!amFonts.customFontSelected">
3 <link rel="preconnect" href="https://fonts.googleapis.com" />
4 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
5 <link
6 rel="stylesheet"
7 type="text/css"
8 :href="`${baseUrls.wpAmeliaPluginURL}v3/src/assets/scss/common/fonts/font.css`"
9 media="all"
10 />
11 </template>
12 <div id="amelia-container" ref="ameliaContainer" class="am-fc__wrapper" :style="cssVars">
13 <template v-if="ready && pagesArray.length">
14 <component :is="pagesArray[pageIndex]"></component>
15 </template>
16 <CatalogSkeleton v-else />
17 </div>
18 <BackLink />
19 </template>
20
21 <script setup>
22 // * Pages
23 import CategoriesList from './CategoriesList/CategoriesList.vue'
24 import CategoryItemsList from './CategoryItemsList/CategoryItemsList.vue'
25 import CategoryService from './CategoryItem/CategoryService.vue'
26 import CategoryPackage from './CategoryItem/CategoryPackage.vue'
27
28 // * Parts
29 import CatalogSkeleton from './Parts/Skeleton/CatalogSkeleton.vue'
30 import BackLink from '../Parts/BackLink'
31
32 // * import from Vue
33 import {
34 inject,
35 provide,
36 markRaw,
37 ref,
38 watch,
39 nextTick,
40 computed,
41 onMounted,
42 onBeforeUnmount,
43 } from 'vue'
44
45 // * import from Vuex
46 import { useStore } from 'vuex'
47
48 // * import composable
49 import { defaultCustomizeSettings } from '../../../assets/js/common/defaultCustomize'
50 import { useColorTransparency } from '../../../assets/js/common/colorManipulation'
51 import useRestore from '../../../assets/js/public/restore'
52 import useAction from '../../../assets/js/public/actions'
53 import { useAvailableCategories } from '../../../assets/js/public/catalog'
54 import { useRenderAction } from '../../../assets/js/public/renderActions'
55 import { getStickyOrFixedHeaderHeight } from '@/assets/js/common/utilHeaderHeight'
56
57 // * Plugin Licence
58 let licence = inject('licence')
59
60 // * Component reference
61 let ameliaContainer = ref(null)
62
63 // * Component offset scroll
64 let offsetFromTop = ref(20)
65
66 // * Root Urls
67 const baseUrls = inject('baseUrls')
68
69 // * Plugin wrapper width
70 let containerWidth = ref(0)
71 provide('containerWidth', containerWidth)
72
73 // * resize function
74 function resize() {
75 if (ameliaContainer.value) {
76 containerWidth.value = ameliaContainer.value.offsetWidth
77 }
78 }
79
80 onMounted(() => {
81 // * window resize listener
82 window.addEventListener('resize', resize)
83
84 store.commit('shortcodeParams/setForm', 'catalogForm')
85
86 document
87 .getElementById('amelia-v2-booking-' + shortcodeData.value.counter)
88 .classList.add('amelia-v2-booking-' + shortcodeData.value.counter + '-loaded')
89
90 useAction(store, {}, 'ViewContent', 'appointment', null, null)
91
92 resize()
93
94 useRenderAction('scrollForm', {
95 offsetFromTop,
96 })
97 })
98
99 onBeforeUnmount(() => {
100 window.removeEventListener('resize', resize)
101 })
102
103 // * Empty State
104 let empty = ref(false)
105
106 // * Root Settings
107 const amSettings = inject('settings')
108
109 // * Shortcode
110 const shortcodeData = inject('shortcodeData')
111
112 // * Define store
113 const store = useStore()
114
115 store.commit('entities/setPreselected', shortcodeData.value)
116
117 // * Get Entities from server
118 store.dispatch('entities/getEntities', {
119 types: [
120 'employees',
121 'categories',
122 'locations',
123 'packages',
124 'entitiesRelations',
125 'customFields',
126 'taxes',
127 ],
128 licence: licence,
129 loadEntities: shortcodeData.value.hasApiCall || shortcodeData.value.trigger,
130 showHidden: false,
131 isPanel: false,
132 })
133
134 // * Components
135 const categoriesList = markRaw(CategoriesList)
136 const categoryItemsList = markRaw(CategoryItemsList)
137 const categoryService = markRaw(CategoryService)
138 const categoryPackage = markRaw(CategoryPackage)
139
140 let pagesArray = ref([categoriesList, categoryItemsList])
141 let pageIndex = ref(0)
142
143 let categorySelected = ref(null)
144 provide('categorySelected', categorySelected)
145
146 let availableCategories = ref([])
147 provide('availableCategories', availableCategories)
148
149 let itemType = ref('')
150 provide('itemType', itemType)
151
152 watch(itemType, () => {
153 if (itemType.value === 'appointment') {
154 pagesArray.value = pagesArray.value.filter((p) => p !== categoryPackage)
155 if (!pagesArray.value.includes(categoryService)) {
156 pagesArray.value.push(categoryService)
157 }
158 }
159
160 if (itemType.value === 'package') {
161 pagesArray.value = pagesArray.value.filter((p) => p !== categoryService)
162 if (!pagesArray.value.includes(categoryPackage)) {
163 pagesArray.value.push(categoryPackage)
164 }
165 }
166
167 if (itemType.value === '') {
168 pagesArray.value.forEach((a, index) => {
169 if (a.name === 'CategoryService') removeItemFromStepArray(pagesArray.value, index)
170 if (a.name === 'CategoryPackage') removeItemFromStepArray(pagesArray.value, index)
171 })
172 }
173 })
174
175 function removeItemFromStepArray(arr, identifier) {
176 arr.splice(identifier, 1)
177 }
178
179 function iOS() {
180 return (
181 ['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'].includes(
182 navigator.platform,
183 ) ||
184 // iPad on iOS 13 detection
185 (navigator.userAgent.includes('Mac') && 'ontouchend' in document)
186 )
187 }
188
189 function nextPage() {
190 pageIndex.value = pageIndex.value + 1
191
192 const measureAndScroll = () => {
193 if (!ameliaContainer.value) {
194 return
195 }
196
197 let headerHeight = getStickyOrFixedHeaderHeight({
198 root: document.body,
199 })
200
201 let scrollTop =
202 ameliaContainer.value.getBoundingClientRect().top +
203 window.pageYOffset -
204 offsetFromTop.value -
205 headerHeight
206
207 window.scrollTo({
208 top: Math.max(0, scrollTop),
209 behavior: 'smooth',
210 })
211 }
212
213 nextTick(() => {
214 if (iOS()) {
215 setTimeout(measureAndScroll, 500)
216 } else {
217 measureAndScroll()
218 }
219 })
220 }
221
222 function previousPage() {
223 pageIndex.value = Math.max(pageIndex.value - 1, 0)
224 }
225
226 provide('changingPageFunctions', {
227 nextPage,
228 previousPage,
229 })
230
231 // * When data is ready
232 let ready = computed(() => store.getters['entities/getReady'])
233
234 // * Entities
235 let amEntities = computed(() => {
236 return store.state.entities
237 })
238
239 provide('amEntities', amEntities)
240
241 // * Collect shortcode data
242 function setShortcodeParams() {
243 let preselected = store.getters['entities/getPreselected']
244
245 if (preselected.category.length !== 1) {
246 if (shortcodeData.value.categories_hidden) {
247 availableCategories.value = JSON.parse(
248 JSON.stringify(useAvailableCategories(amEntities.value, shortcodeData.value)),
249 )
250
251 nextTick(() => {
252 let componentIndex = pagesArray.value.findIndex((a) => a.name === 'CategoriesList')
253 pagesArray.value.splice(componentIndex, 1)
254 })
255 }
256
257 if (!shortcodeData.value.categories_hidden) {
258 availableCategories.value = JSON.parse(
259 JSON.stringify(useAvailableCategories(amEntities.value, shortcodeData.value)),
260 )
261
262 nextTick(() => {
263 if (availableCategories.value.length === 1) {
264 store.commit('booking/setCategoryId', parseInt(availableCategories.value[0].id))
265 categorySelected.value = parseInt(availableCategories.value[0].id)
266 let componentIndex = pagesArray.value.findIndex((a) => a.name === 'CategoriesList')
267 pagesArray.value.splice(componentIndex, 1)
268 }
269 })
270 }
271 }
272
273 if (preselected.category.length === 1) {
274 store.commit('booking/setCategoryId', parseInt(preselected.category))
275
276 categorySelected.value = parseInt(preselected.category[0])
277
278 availableCategories.value = JSON.parse(
279 JSON.stringify(
280 useAvailableCategories(amEntities.value, shortcodeData.value).filter(
281 (a) => a.id === parseInt(preselected.category[0]),
282 ),
283 ),
284 )
285
286 nextTick(() => {
287 let componentIndex = pagesArray.value.findIndex((a) => a.name === 'CategoriesList')
288 pagesArray.value.splice(componentIndex, 1)
289 })
290 }
291
292 if (preselected.service.length === 1) {
293 store.commit('booking/setServiceId', parseInt(preselected.service[0]))
294 let service = store.getters['entities/getService'](parseInt(preselected.service[0]))
295 categorySelected.value = service ? parseInt(service.categoryId) : null
296 store.commit('booking/setCategoryId', service ? parseInt(service.categoryId) : null)
297
298 nextTick(() => {
299 pagesArray.value = []
300 pagesArray.value.push(categoryService)
301 })
302 }
303
304 if (preselected.employee.length === 1) {
305 store.commit('booking/setEmployeeId', parseInt(preselected.employee[0]))
306 }
307
308 if (preselected.location.length === 1) {
309 store.commit('booking/setLocationId', parseInt(preselected.location[0]))
310 }
311
312 if (preselected.package.length === 1 && store.getters['entities/getPackages'].length) {
313 pagesArray.value = []
314 store.commit('booking/setPackageId', parseInt(preselected.package[0]))
315
316 nextTick(() => {
317 pagesArray.value.push(categoryPackage)
318 })
319 }
320
321 if (preselected.show === 'packages') {
322 store.commit('booking/setBookableType', 'package')
323 } else {
324 store.commit('booking/setBookableType', 'appointment')
325 }
326 }
327
328 let restore = computed(() => {
329 return ready.value ? useRestore(store, shortcodeData.value) : null
330 })
331 provide('restoreFormData', restore)
332
333 // * Watch when data is ready
334 watch(ready, (current) => {
335 if (current) {
336 setShortcodeParams()
337 empty.value =
338 store.getters['entities/getServices'].length === 0 ||
339 store.getters['entities/getEmployees'].length === 0
340
341 if (restore.value) {
342 itemType.value = store.state.booking.appointment.type
343 nextTick(() => {
344 pageIndex.value = pagesArray.value.length - 1
345 categorySelected.value = store.state.booking.appointment.categoryId
346 })
347 }
348 }
349 })
350
351 // * Customized data form
352 let customizedDataForm = computed(() => {
353 return amSettings.customizedData && 'cbf' in amSettings.customizedData
354 ? amSettings.customizedData.cbf
355 : defaultCustomizeSettings.cbf
356 })
357
358 provide('customizedDataForm', customizedDataForm)
359
360 // * Fonts
361 const amFonts = ref(
362 amSettings.customizedData ? amSettings.customizedData.fonts : defaultCustomizeSettings.fonts,
363 )
364 provide('amFonts', amFonts)
365
366 // * Colors block
367 let amColors = computed(() => {
368 return amSettings.customizedData && 'cbf' in amSettings.customizedData
369 ? amSettings.customizedData.cbf.colors
370 : defaultCustomizeSettings.cbf.colors
371 })
372
373 provide('amColors', amColors)
374
375 let cssVars = computed(() => {
376 return {
377 '--am-c-primary': amColors.value.colorPrimary,
378 '--am-c-success': amColors.value.colorSuccess,
379 '--am-c-error': amColors.value.colorError,
380 '--am-c-warning': amColors.value.colorWarning,
381 '--am-c-main-bgr': amColors.value.colorMainBgr,
382 '--am-c-main-heading-text': amColors.value.colorMainHeadingText,
383 '--am-c-main-text': amColors.value.colorMainText,
384 '--am-c-sb-bgr': amColors.value.colorSbBgr,
385 '--am-c-sb-text': amColors.value.colorSbText,
386 '--am-c-inp-bgr': amColors.value.colorInpBgr,
387 '--am-c-inp-border': amColors.value.colorInpBorder,
388 '--am-c-inp-text': amColors.value.colorInpText,
389 '--am-c-inp-placeholder': amColors.value.colorInpPlaceHolder,
390 '--am-c-drop-bgr': amColors.value.colorDropBgr,
391 '--am-c-drop-text': amColors.value.colorDropText,
392 '--am-c-card-bgr': amColors.value.colorCardBgr,
393 '--am-c-card-text': amColors.value.colorCardText,
394 '--am-c-card-border': amColors.value.colorCardBorder,
395 '--am-c-btn-prim': amColors.value.colorBtnPrim,
396 '--am-c-btn-prim-text': amColors.value.colorBtnPrimText,
397 '--am-c-btn-sec': amColors.value.colorBtnSec,
398 '--am-c-btn-sec-text': amColors.value.colorBtnSecText,
399 '--am-c-skeleton-op20': useColorTransparency(amColors.value.colorMainText, 0.2),
400 '--am-c-skeleton-op60': useColorTransparency(amColors.value.colorMainText, 0.6),
401 '--am-font-family': amFonts.value.fontFamily,
402 }
403 })
404
405 function activateCustomFontStyles() {
406 let head = document.head || document.getElementsByTagName('head')[0]
407 if (head.querySelector('#amCustomFont')) {
408 head.querySelector('#amCustomFont').remove()
409 }
410
411 let css = `@font-face {font-family: '${amFonts.value.fontFamily}'; src: url(${amFonts.value.fontUrl});}`
412 let style = document.createElement('style')
413 head.appendChild(style)
414 style.setAttribute('type', 'text/css')
415 style.setAttribute('id', 'amCustomFont')
416 style.appendChild(document.createTextNode(css))
417 }
418
419 if (amFonts.value.customFontSelected) activateCustomFontStyles()
420
421 // * Design Properties
422 let amDesignProperties = computed(() => {
423 return {
424 colorInputBorderRadius: '6px',
425 }
426 })
427 provide('amDesignProperties', amDesignProperties)
428 </script>
429
430 <script>
431 export default {
432 name: 'CatalogFormWrapper',
433 }
434 </script>
435
436 <style lang="scss">
437 @import '../../../../src/assets/scss/public/overides/overides';
438 @import '../../../../src/assets/scss/common/reset/reset';
439 @import '../../../../src/assets/scss/common/icon-fonts/style';
440 @import '../../../../src/assets/scss/common/skeleton/skeleton.scss';
441 @import '../../../../src/assets/scss/common/empty-state/_empty-state-mixin.scss';
442
443 :root {
444 // Colors
445 // shortcuts
446 // -c- color
447 // -bgr- background
448 // -prim- primary
449 // -sec- secondary
450 // primitive colors
451 --am-c-primary: #{$blue-1000};
452 --am-c-success: #{$green-1000};
453 --am-c-error: #{$red-900};
454 --am-c-warning: #{$yellow-1000};
455 // main container colors - right part of the form
456 --am-c-main-bgr: #{$am-white};
457 --am-c-main-heading-text: #{$shade-800};
458 --am-c-main-text: #{$shade-900};
459 // sidebar container colors - left part of the form
460 --am-c-sb-bgr: #17295a;
461 --am-c-sb-text: #{$am-white};
462 // input global colors - usage input, textarea, checkbox, radio button, select input, adv select input
463 --am-c-inp-bgr: #{$am-white};
464 --am-c-inp-border: #{$shade-250};
465 --am-c-inp-text: #{$shade-900};
466 --am-c-inp-placeholder: #{$shade-500};
467 // dropdown global colors - usage select dropdown, adv select dropdown
468 --am-c-drop-bgr: #{$am-white};
469 --am-c-drop-text: #{$shade-1000};
470 // button global colors
471 --am-c-btn-prim: #{$blue-900};
472 --am-c-btn-prim-text: #{$am-white};
473 --am-c-btn-sec: #{$am-white};
474 --am-c-btn-sec-text: #{$shade-900};
475
476 // Properties
477 // shortcuts
478 // -h- height
479 // -fs- font size
480 // -rad- border radius
481 --am-h-inp: 40px;
482 --am-fs-inp: 15px;
483 --am-rad-inp: 6px;
484 --am-fs-label: 15px;
485 --am-fs-btn: 15px;
486
487 // Font
488 --am-font-family: 'Amelia Roboto', sans-serif;
489 }
490
491 // am -- amelia
492 // fc -- form catalog
493 // sb -- sidebar
494 .amelia-v2-booking {
495 background-color: transparent;
496
497 #amelia-container {
498 background-color: transparent;
499
500 * {
501 font-family: var(--am-font-family), sans-serif;
502 font-style: initial;
503 box-sizing: border-box;
504 word-break: break-word;
505 }
506
507 &.am-fc {
508 // Container Wrapper
509 &__wrapper {
510 display: flex;
511 justify-content: center;
512 max-width: 100%;
513 width: 100%;
514 height: auto;
515 margin: 0;
516 padding: 0;
517 border-radius: 8px;
518 box-shadow: none;
519
520 // Form
521 .el-form {
522 &-item {
523 display: block;
524 font-family: var(--am-font-family), sans-serif;
525 font-size: var(--am-fs-label);
526 margin-bottom: 24px;
527
528 &__label {
529 flex: 0 0 auto;
530 text-align: left;
531 font-size: var(--am-fs-label);
532 line-height: 1.3;
533 color: var(--am-c-main-text);
534 box-sizing: border-box;
535 margin: 0;
536
537 &:before {
538 color: var(--am-c-error);
539 }
540 }
541
542 &__content {
543 display: flex;
544 flex-wrap: wrap;
545 align-items: center;
546 flex: 1;
547 position: relative;
548 font-size: var(--am-fs-inp);
549 min-width: 0;
550 color: var(--am-c-main-text);
551 }
552
553 &__error {
554 font-size: 12px;
555 color: var(--am-c-error);
556 padding-top: 4px;
557 }
558 }
559 }
560
561 * {
562 font-family: var(--am-font-family), sans-serif;
563 box-sizing: border-box;
564 }
565 }
566 }
567
568 @include empty-state;
569 }
570 }
571
572 // General popup styles
573 .am-dialog-popup.amelia-v2-booking.amelia-v2-booking-dialog {
574 .el-dialog {
575 .el-dialog {
576 &__header {
577 padding: 0;
578 }
579
580 &__headerbtn {
581 width: 19px;
582 height: 19px;
583 top: 16px;
584 right: 16px;
585 }
586
587 &__body {
588 padding: 0;
589
590 #amelia-container {
591 margin: 0 auto;
592 }
593 }
594 }
595 }
596 }
597 </style>
598