PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 1.2.31
Booking for Appointments and Events Calendar – Amelia v1.2.31
2.4.9 2.4.8 2.4.7 2.4.6 2.4.5 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 / admin / customize / Customize.vue
ameliabooking / v3 / src / views / admin / customize Last commit date
fields 1 year ago navigation 4 years ago pages 1 year ago settings 1 year ago steps 1 year ago Customize.vue 1 year ago
Customize.vue
637 lines
1 <template>
2 <PageWrapper page-id="am-customize">
3 <template #header>
4 <PageHeader></PageHeader>
5 </template>
6
7 <template v-if="pageRenderKey !== 'main' && pageRenderKey !== 'cbf' && pageRenderKey !== 'elf' && pageRenderKey !== 'ecf'" #filter>
8 <!-- Step by step select for page type -->
9 <div
10 class="am-customize__fs-flow"
11 :class="[
12 {'am-customize__capc-panel': pageRenderKey === 'capc' && pagesType === 'panel'},
13 {'am-customize__capc-auth': pageRenderKey === 'capc' && pagesType === 'auth'}
14 ]"
15 >
16 <div
17 v-if="!licence.isBasic && !licence.isStarter && !licence.isLite && pageRenderKey !== 'capc' && pageRenderKey !== 'cape'"
18 class="am-customize__fs-flow__inner"
19 >
20 <div class="am-customize__fs-flow__label">
21 {{amLabels.steps}}:
22 </div>
23 <AmSelect
24 v-model="bookableType"
25 @change="handleClick('menu', 0)"
26 >
27 <AmOption value="appointment" :label="amLabels.service_option" />
28 <AmOption value="package" :label="amLabels.package_option" />
29 </AmSelect>
30 </div>
31 <!--/ Step by step select for page type -->
32
33 <!-- Cabinet select for page type -->
34 <div
35 v-if="!licence.isLite && (pageRenderKey === 'capc' || pageRenderKey === 'cape')"
36 class="am-customize__fs-flow__inner"
37 >
38 <div class="am-customize__fs-flow__label">
39 {{amLabels.steps}}:
40 </div>
41 <AmSelect
42 v-model="pagesType"
43 @change="handleClick('menu', 0)"
44 >
45 <AmOption value="panel" label="Panel" />
46 <AmOption value="auth" label="Login" />
47 </AmSelect>
48 </div>
49 <!-- /Cabinet select for page type -->
50
51 <!-- Button that will redirect to Catalog form -->
52 <AmButton
53 v-if="urlParams.get('current') !== 'sbsNew' && pageRenderKey !== 'capc' && pageRenderKey !== 'cape'"
54 :style="{margin: '0 0 0 auto'}"
55 @click="handleClick('menu', 0, urlParams.get('current'))"
56 >
57 Go Back To Catalog Form
58 </AmButton>
59 <!-- /Button that will redirect to Catalog form -->
60 </div>
61 </template>
62
63 <template #default>
64 <component :is="pagesObject[pageRenderKey]"></component>
65 </template>
66
67 <template #sidebar>
68 <SettingsSidebar v-if="pageRenderKey !== 'main'"></SettingsSidebar>
69 </template>
70
71 <template #menu>
72 <SettingsSidebar v-if="pageRenderKey !== 'main'" ref="sidebarRef"></SettingsSidebar>
73 </template>
74 </PageWrapper>
75 </template>
76
77 <script setup>
78 // * Components
79 import AmSelect from '../../_components/select/AmSelect.vue'
80 import AmOption from '../../_components/select/AmOption.vue'
81
82 // * Page Shell
83 import PageWrapper from '../parts/PageWrapper';
84 import PageHeader from '../parts/PageHeader';
85 import SettingsSidebar from "./settings/SettingsSidebar.vue";
86
87 // * Pages
88 import CustomizeMainPage from './navigation/CustomizeMainPage.vue';
89 import CustomizeStepNew from "./pages/CustomizeStepNew.vue";
90 import CustomizeCatalog from "./pages/CustomizeCatalog.vue";
91 import CustomizeEventList from "./pages/CustomizeEventList.vue";
92 import CustomizeEventCalendar from "./pages/CustomizeEventCalendar.vue";
93 import CustomizeCustomerPanel from "./pages/CustomizeCustomerPanel.vue";
94 import CustomizeEmployeePanel from "./pages/CustomizeEmployeePanel.vue";
95
96 // * Import form Vue
97 import {
98 markRaw,
99 inject,
100 provide,
101 ref,
102 reactive,
103 computed,
104 onMounted,
105 watchEffect
106 } from 'vue';
107 import { ElNotification } from 'element-plus'
108 // * Deepmerge
109 import deepMerge from 'deepmerge'
110 // * Import for axios
111 import httpClient from '../../../plugins/axios'
112 // * Import Composables
113 import {
114 usePopulateMultiDimensionalObject,
115 useOverrideValuesOfOneObjectWithAnother
116 } from '../../../assets/js/common/objectAndArrayManipulation'
117 // * Default Customize Settings
118 import { defaultCustomizeSettings, defaultTranslations } from '../../../assets/js/common/defaultCustomize.js'
119 import AmButton from "../../_components/button/AmButton";
120
121 // * Plugin Licence
122 let licence = inject('licence')
123
124 function notify (title, message, type, customClass) {
125 if (typeof customClass === 'undefined') {
126 customClass = ''
127 }
128 ElNotification({
129 title: title,
130 message: message,
131 type: type,
132 offset: 125,
133 position: 'top-left',
134 appendTo: '#am-customize'
135 })
136 }
137
138 let bookableType = ref('appointment')
139 provide('bookableType', bookableType)
140
141 let pagesType = ref('auth')
142 provide('pagesType', pagesType)
143
144 // * Settings data
145 let settings = inject('settings')
146
147 // * Labels
148 let amLabels = inject('labels')
149
150 // * key that depends on selected language in customize header
151 let langKey = ref('default')
152 provide('langKey', langKey)
153
154 // * Object of all languages
155 let amLanguages = inject('languages')
156
157 // * Languages that were selected in general settings
158 let selectedLanguages = ref([])
159 settings.general.usedLanguages.forEach(lang => {
160 selectedLanguages.value.push(amLanguages[lang])
161 })
162
163 provide('languageFunctionality', {
164 langKey,
165 selectedLanguages
166 })
167
168 // * Loading state
169 let loading = ref(false)
170 provide('loading', loading)
171
172 // * Sidebar Reference
173 let sidebarRef = ref(null)
174
175 // * Page Header block
176 let pageName = ref('Customize')
177
178 let stepIndex = ref(0)
179 provide('stepIndex', stepIndex)
180
181 // * Change name of the page in page header
182 function pageNameHandler (name = '') {
183 pageName.value = name ? name : 'Customize'
184 }
185
186 provide('headerFunctionality', {
187 pageNameHandler,
188 pageName
189 })
190
191 // * Step name
192 let stepName = ref('')
193 provide('stepName', stepName)
194 let subStepName = ref('')
195 provide('subStepName', subStepName)
196
197 // * Sidebar functionality
198 let componentKey = ref('menu')
199 let goBackPath = ref('menu')
200 let parentPath = ref('menu')
201
202 function handleClick (data = '', index, pageKey = '') {
203 componentKey.value = data ? data : goBackPath.value
204 goBackPath.value = 'menu'
205
206 if (pageKey) {
207 pageRenderKey.value = pageKey
208 }
209 if (componentKey.value === 'menu') {
210 subStepName.value = ''
211 }
212 if (index !== undefined) {
213 stepIndex.value = index
214 }
215 }
216
217 provide('sidebarFunctionality', {
218 componentKey,
219 parentPath,
220 goBackPath,
221 handleClick
222 })
223
224 // * Pages that represents all form types
225 let pagesObject = {
226 main: markRaw(CustomizeMainPage),
227 sbsNew: markRaw(CustomizeStepNew),
228 cbf: markRaw(CustomizeCatalog),
229 elf: markRaw(CustomizeEventList),
230 ecf: markRaw(CustomizeEventCalendar),
231 capc: markRaw(CustomizeCustomerPanel),
232 cape: markRaw(CustomizeEmployeePanel)
233 }
234
235 let urlParams = new URLSearchParams(window.location.search)
236
237 // * Key that reference to page from type
238 let pageRenderKey = ref(urlParams.get('current') ? urlParams.get('current') : 'sbsNew')
239 provide('pageRenderKey', pageRenderKey)
240
241 // * Function that changes the pages ( value of pageRenderKey )
242 function changePages(pageString) {
243 pageRenderKey.value = pageString
244 }
245 provide('pageFunctions', {
246 changePages
247 })
248
249 watchEffect(() => {
250 if (pageRenderKey.value === 'elf' || pageRenderKey.value === 'ecf') {
251 bookableType.value = 'event'
252 }
253 })
254
255 // * Customize Object settings
256 let amCustomize = ref({})
257 const combineMerge = (target, source, options) => {
258 const destination = target.slice()
259 source.forEach((item, index) => {
260 if (typeof destination[index] === 'undefined') {
261 destination[index] = options.cloneUnlessOtherwiseSpecified(item, options)
262 } else if (options.isMergeableObject(item)) {
263 destination[index] = deepMerge(target[index], item, options)
264 } else if (target.indexOf(item) === -1) {
265 destination.push(item)
266 }
267 })
268 return destination
269 }
270
271 if (settings.customizedData) {
272 amCustomize.value = deepMerge.all([settings.customizedData, JSON.parse(JSON.stringify({...defaultCustomizeSettings})), settings.customizedData], {arrayMerge: combineMerge})
273 } else {
274 amCustomize.value = JSON.parse(JSON.stringify({...defaultCustomizeSettings}))
275 }
276
277 // * Customize data
278 provide('customize', amCustomize)
279
280 // * Labels
281 // * amTranslations object is used in sidebar segments
282 let amTranslations = reactive(JSON.parse(JSON.stringify({...defaultTranslations})))
283
284 // * Set selected languages and data from server
285 function labelsKeysLanguages(labelObj) {
286 Object.keys(labelObj).forEach((item) => {
287 labelObj[item] = {
288 default : ''
289 }
290 settings.general.usedLanguages.forEach(lang => {
291 labelObj[item][lang] = ''
292 })
293 })
294 }
295 usePopulateMultiDimensionalObject('labels', amTranslations, labelsKeysLanguages)
296
297 provide('translations', amTranslations)
298
299 function changeNamesInObject (a, b) {
300 b.name = a.name
301 }
302 onMounted(() => {
303 useOverrideValuesOfOneObjectWithAnother('name', defaultCustomizeSettings, amCustomize.value, changeNamesInObject)
304 })
305
306 let stepKey = computed(() => {
307 return subStepName.value ? subStepName.value : stepName.value
308 })
309
310 let filterByKey = (key) => {
311 return computed(() =>
312 amCustomize.value[key] ? { [key]: amCustomize.value[key], 'fonts': amCustomize.value['fonts'] } : amCustomize.value
313 )
314 }
315
316 function updateCustomizeLabel (labelObj) {
317 let parentObjKey = stepKey.value
318 if (parentPath.value === 'sidebar') parentObjKey = parentPath.value
319
320 if (
321 amCustomize.value[pageRenderKey.value][parentObjKey].translations === null
322 || (Array.isArray(amCustomize.value[pageRenderKey.value][parentObjKey].translations)
323 && amCustomize.value[pageRenderKey.value][parentObjKey].translations.length === 0)
324 ) {
325 amCustomize.value[pageRenderKey.value][parentObjKey].translations = {}
326 }
327
328 let labelLangObj = {}
329 Object.keys(labelObj).forEach((label) => {
330 Object.keys(labelObj[label]).forEach((lang) => {
331 if (labelObj[label][lang]) {
332 labelLangObj[label] = {}
333 labelLangObj[label][lang] = labelObj[label][lang]
334 if (amCustomize.value[pageRenderKey.value][parentObjKey].translations[label]) {
335 Object.assign(amCustomize.value[pageRenderKey.value][parentObjKey].translations[label], labelLangObj[label])
336 } else {
337 amCustomize.value[pageRenderKey.value][parentObjKey].translations[label] = labelLangObj[label]
338 }
339 } else if (
340 !labelObj[label][lang] &&
341 amCustomize.value[pageRenderKey.value][parentObjKey].translations &&
342 Object.keys(amCustomize.value[pageRenderKey.value][parentObjKey].translations).includes(label) &&
343 Object.keys(amCustomize.value[pageRenderKey.value][parentObjKey].translations[label]).includes(lang)
344 ) {
345
346 delete amCustomize.value[pageRenderKey.value][parentObjKey].translations[label][lang]
347
348 if (!Object.keys(amCustomize.value[pageRenderKey.value][parentObjKey].translations[label]).length) {
349 delete amCustomize.value[pageRenderKey.value][parentObjKey].translations[label]
350 }
351 }
352 })
353 })
354
355 if (!Object.keys(amCustomize.value[pageRenderKey.value][parentObjKey].translations).length) {
356 amCustomize.value[pageRenderKey.value][parentObjKey].translations = null
357 }
358 }
359
360 function updateLabelObject () {
361 let parentObjKey = stepKey.value
362 if (parentPath.value === 'sidebar') parentObjKey = parentPath.value
363
364 usePopulateMultiDimensionalObject('labels', amTranslations[pageRenderKey.value][parentObjKey], updateCustomizeLabel)
365 }
366
367 provide('updateLabelObject', updateLabelObject)
368
369 // * Save Changes function
370 function saveChanges () {
371 let currentForm = filterByKey(pageRenderKey.value)
372
373 httpClient.post(
374 '/settings',
375 {customizedData: currentForm.value}
376 )
377 .then(() => {
378 notify(amLabels.success, amLabels.settings_saved, 'success')
379 })
380 .catch(e => {
381 notify(amLabels.error, e.message, 'error')
382 })
383 }
384
385 function resetChanges () {
386 amCustomize.value[pageRenderKey.value] = JSON.parse(JSON.stringify({...defaultCustomizeSettings[pageRenderKey.value]}))
387 amTranslations[pageRenderKey.value] = JSON.parse(JSON.stringify({...defaultTranslations[[pageRenderKey.value]]}))
388 amCustomize.value.fonts = JSON.parse(JSON.stringify({...defaultCustomizeSettings.fonts}))
389 usePopulateMultiDimensionalObject('labels', amTranslations[pageRenderKey.value], labelsKeysLanguages)
390
391 saveChanges()
392 }
393
394 provide('customizeSaveFunctionality', {
395 saveChanges,
396 resetChanges
397 })
398 </script>
399
400 <script>
401 export default {
402 name: 'AmeliaCustomize'
403 }
404 </script>
405
406 <style lang="scss">
407 @import '../../../../src/assets/scss/common/icon-fonts/style';
408
409 :root {
410 // Colors
411 // shortcuts
412 // -c- color
413 // -bgr- background
414 // -prim- primary
415 // -sec- secondary
416 // primitive colors
417 --am-c-primary: #{$blue-1000};
418 --am-c-success: #{$green-1000};
419 --am-c-error: #{$red-900};
420 --am-c-warning: #{$yellow-1000};
421 // main container colors - right part of the form
422 --am-c-main-bgr: #{$am-white};
423 --am-c-main-heading-text: #{$shade-800};
424 --am-c-main-text: #{$shade-900};
425 // sidebar container colors - left part of the form
426 --am-c-sb-bgr: #17295A;
427 --am-c-sb-text: #{$am-white};
428 // input global colors - usage input, textarea, checkbox, radio button, select input, adv select input
429 --am-c-inp-bgr: #{$am-white};
430 --am-c-inp-border: #{$shade-250};
431 --am-c-inp-text: #{$shade-900};
432 --am-c-inp-placeholder: #{$shade-500};
433 --am-c-checkbox-border: #{$shade-300};
434 --am-c-checkbox-border-disabled: #{$blue-600};
435 --am-c-checkbox-border-focused: #{$blue-700};
436 --am-c-checkbox-label-disabled: #{$shade-600};
437 // dropdown global colors - usage select dropdown, adv select dropdown
438 --am-c-drop-bgr: #{$am-white};
439 --am-c-drop-text: #{$shade-1000};
440 // card global colors
441 --am-c-card-bgr: #{$am-white};
442 --am-c-card-border: #{$shade-250};
443 --am-c-card-text: #{$shade-900};
444 // button global colors
445 --am-c-btn-prim: #{$blue-900};
446 --am-c-btn-prim-text: #{$am-white};
447 --am-c-btn-sec: #{$am-white};
448 --am-c-btn-sec-text: #{$shade-900};
449
450 // Properties
451 // shortcuts
452 // -h- height
453 // -fs- font size
454 // -rad- border radius
455 --am-h-inp: 40px;
456 --am-fs-inp: 15px;
457 --am-rad-inp: 6px;
458 --am-fs-label: 15px;
459 --am-fs-btn: 15px;
460
461 // Font
462 --am-font-family: 'Amelia Roboto', sans-serif;
463 }
464
465 .am-customize {
466 &__fs {
467 &-flow {
468 max-width: 760px;
469 width: 100%;
470 margin: 16px auto;
471 display: flex;
472 flex-direction: row;
473 align-items: center;
474 justify-content: space-between;
475
476 &.am-customize__capc-panel {
477 max-width: 1024px;
478 }
479
480 &.am-customize__capc-auth {
481 max-width: 400px;
482 }
483
484 &__inner {
485 display: flex;
486 align-items: center;
487 gap: 10px;
488 }
489
490 .am-select-wrapper {
491 max-width: 180px;
492 width: 100%;
493 flex: 0 0 180px;
494 }
495 }
496 }
497 }
498
499 .el-notification {
500 --el-notification-width: 330px;
501 --el-notification-padding: 14px 26px 14px 13px;
502 --el-notification-radius: 8px;
503 --el-notification-shadow: var(--el-box-shadow-light);
504 --el-notification-border-color: var(--el-border-color-lighter);
505 --el-notification-icon-size: 24px;
506 --el-notification-close-font-size: var(--el-message-close-size, 16px);
507 --el-notification-group-margin-left: 13px;
508 --el-notification-group-margin-right: 8px;
509 --el-notification-content-font-size: var(--el-font-size-base);
510 --el-notification-content-color: var(--el-text-color-regular);
511 --el-notification-title-font-size: 16px;
512 --el-notification-title-color: var(--el-text-color-primary);
513 --el-notification-close-color: var(--el-text-color-secondary);
514 --el-notification-close-hover-color: var(--el-text-color-regular)
515 }
516
517 .el-notification {
518 display: flex;
519 width: var(--el-notification-width);
520 padding: var(--el-notification-padding);
521 border-radius: var(--el-notification-radius);
522 box-sizing: border-box;
523 border: 1px solid var(--el-notification-border-color);
524 position: fixed;
525 background-color: #FFFFFF;
526 box-shadow: var(--el-notification-shadow);
527 transition: opacity var(--el-transition-duration),transform var(--el-transition-duration),left var(--el-transition-duration),right var(--el-transition-duration),top .4s,bottom var(--el-transition-duration);
528 overflow-wrap: anywhere;
529 overflow: hidden;
530 z-index: 9999
531 }
532
533 .el-notification.right {
534 right: 16px
535 }
536
537 .el-notification.left {
538 left: 175px;
539 @include media-breakpoint-down($am-small-max) {
540 --el-notification-width: 300;
541 left: 30px
542 }
543 }
544
545 .el-notification__group {
546 margin-left: var(--el-notification-group-margin-left);
547 margin-right: var(--el-notification-group-margin-right)
548 }
549
550 .el-notification__title {
551 font-weight: 700;
552 font-size: var(--el-notification-title-font-size);
553 line-height: var(--el-notification-icon-size);
554 color: var(--el-notification-title-color);
555 margin: 0
556 }
557
558 .el-notification__content {
559 font-size: var(--el-notification-content-font-size);
560 line-height: 24px;
561 margin: 6px 0 0;
562 color: var(--el-notification-content-color);
563 text-align: justify
564 }
565
566 .el-notification__content p {
567 margin: 0
568 }
569
570 .el-notification .el-notification__icon {
571 height: var(--el-notification-icon-size);
572 width: var(--el-notification-icon-size);
573 font-size: var(--el-notification-icon-size)
574 }
575
576 .el-notification .el-notification__closeBtn {
577 position: absolute;
578 top: 18px;
579 right: 15px;
580 cursor: pointer;
581 color: var(--el-notification-close-color);
582 font-size: var(--el-notification-close-font-size)
583 }
584
585 .el-notification .el-notification__closeBtn:hover {
586 color: var(--el-notification-close-hover-color)
587 }
588
589 .el-notification .el-notification--success {
590 --el-notification-icon-color: var(--el-color-success);
591 color: var(--el-notification-icon-color)
592 }
593
594 .el-notification .el-notification--info {
595 --el-notification-icon-color: var(--el-color-info);
596 color: var(--el-notification-icon-color)
597 }
598
599 .el-notification .el-notification--warning {
600 --el-notification-icon-color: var(--el-color-warning);
601 color: var(--el-notification-icon-color)
602 }
603
604 .el-notification .el-notification--error {
605 --el-notification-icon-color: var(--el-color-error);
606 color: var(--el-notification-icon-color)
607 }
608
609 .el-notification-fade-enter-from.right {
610 right: 0;
611 transform: translate(100%)
612 }
613
614 .el-notification-fade-enter-from.left {
615 left: 0;
616 transform: translate(-100%)
617 }
618
619 .el-notification-fade-leave-to {
620 opacity: 0
621 }
622
623 #am-customize{
624 .am-lite-version {
625 display: none !important;
626 }
627
628 .am-basic-version {
629 display: none !important;
630 }
631
632 .am-starter-version {
633 display: none !important;
634 }
635 }
636 </style>
637