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 / StepForm / LocationStep / LocationStep.vue
ameliabooking / v3 / src / views / public / StepForm / LocationStep Last commit date
LocationStep.vue 6 days ago
LocationStep.vue
385 lines
1 <template>
2 <StepCardLayout
3 ref="stepCardLayoutReference"
4 :custom-class="[props.globalClass, 'am-fs-location-step']"
5 :card-selected="store.getters['booking/getLocationId'] !== null || anyLocation"
6 :allow-popup="
7 serviceStepsIndex < 0 && stepIndex === 0 && store.getters['booking/getServiceId'] !== null
8 "
9 >
10 <!-- Any Location -->
11 <div
12 v-if="customizeOptions.location.required === false"
13 class="am-fs__init-item"
14 :class="{
15 'am--selected': anyLocation,
16 }"
17 :tabindex="keyboardSelectionDisabled ? -1 : 0"
18 @click="chooseAnyLocation"
19 >
20 <div class="am-fs__init-item__img am-item-any">
21 <span class="am-icon-locations" />
22 </div>
23 <div class="am-fs__init-item__content am-item-any">
24 <div class="am-fs__init-item__heading" :class="responsiveClass">
25 <div class="am-fs__init-item__name" :class="responsiveClass">
26 {{ amLabels.any_location }}
27 </div>
28 </div>
29 </div>
30 </div>
31 <!-- /Any Location -->
32
33 <!-- Card -->
34 <StepCard
35 v-for="location in locationOptions"
36 :key="location.id"
37 :item="location"
38 :item-name="location.name"
39 :selected="store.getters['booking/getLocationId'] === location.id"
40 :disabled="disabledLocation(location.id)"
41 :is-person="false"
42 :labels="amLabels"
43 :info-items="
44 location.address && customizeOptions.address.visibility
45 ? [
46 {
47 icon: 'locations',
48 name: location.address,
49 isLink: true,
50 },
51 ]
52 : []
53 "
54 :parent-width="componentWidth"
55 :keyboard-selection-disabled="keyboardSelectionDisabled"
56 :info-btn-visibility="!!location.description && customizeOptions.moreBtn.visibility"
57 @select-item="selectLocation(location)"
58 @trigger-info-popup="
59 () => {
60 moreInfoVisibility = true
61 selectedLocation = location
62 }
63 "
64 />
65 <!-- /Card -->
66 </StepCardLayout>
67
68 <!-- More Info -->
69 <MoreInfoPopup
70 v-if="moreInfoVisibility"
71 v-model:visibility="moreInfoVisibility"
72 :heading="amLabels.location_information"
73 :labels="amLabels"
74 :item="selectedLocation"
75 :item-name="selectedLocation.name"
76 :is-person="false"
77 :plain-text="true"
78 />
79 <!-- /More Info -->
80 </template>
81
82 <script setup>
83 import StepCardLayout from '../common/StepCardLayout.vue'
84 import StepCard from '../common/StepCard.vue'
85 import MoreInfoPopup from '../common/MoreInfoPopup.vue'
86
87 // * Import from Vue
88 import { computed, inject, ref, reactive, onMounted, onUnmounted, watch } from 'vue'
89
90 // * Import from Vuex
91 import { useStore } from 'vuex'
92
93 // * Composables
94 import { useCartItem } from '../../../../assets/js/public/cart'
95 import useAction from '../../../../assets/js/public/actions'
96 import { useResponsiveClass } from '../../../../assets/js/common/responsive'
97 import { useElementSize } from '@vueuse/core'
98 import { defaultCustomizeSettings } from '../../../../assets/js/common/defaultCustomize'
99
100 // * Store
101 const store = useStore()
102
103 // * Props
104 const props = defineProps({
105 globalClass: {
106 type: String,
107 default: '',
108 },
109 cardSelected: {
110 type: Boolean,
111 default: false,
112 },
113 })
114
115 // * Amelia Settings
116 const amSettings = inject('settings')
117
118 // * Entities
119 let amEntities = computed(() => {
120 return store.state.entities
121 })
122
123 // * Customize
124 const amCustomize = inject('amCustomize')
125
126 // * Component References
127 const stepCardLayoutReference = ref(null)
128 const stepCardLayoutDom = computed(() => stepCardLayoutReference.value?.stepCardLayoutRef || null)
129
130 // * Component width
131 const { width: componentWidth } = useElementSize(stepCardLayoutDom)
132
133 // * Responsive Class
134 let responsiveClass = computed(() => {
135 return useResponsiveClass(componentWidth.value)
136 })
137
138 // * Steps Array
139 const stepsArray = inject('stepsArray', ref([]))
140 const serviceStepsIndex = computed(() => {
141 return stepsArray.value.findIndex((step) => step.name === 'ServiceStep')
142 })
143
144 // * Step Index
145 const stepIndex = inject('stepIndex', 0)
146
147 // * Labels
148 let labels = inject('labels')
149
150 // * local language short code
151 const localLanguage = inject('localLanguage')
152
153 // * if local lang is in settings lang
154 let langDetection = computed(() => amSettings.general.usedLanguages.includes(localLanguage.value))
155
156 // * Labels
157 const amLabels = computed(() => {
158 let computedLabels = reactive({ ...labels })
159
160 const customizedLabels = amCustomize.locationStep?.translations
161
162 if (customizedLabels) {
163 Object.keys(customizedLabels).forEach((labelKey) => {
164 const labelData = customizedLabels[labelKey]
165 const localizedLabel = labelData[localLanguage.value]
166
167 if (localizedLabel && langDetection.value) {
168 computedLabels[labelKey] = localizedLabel
169 } else if (labelData.default) {
170 computedLabels[labelKey] = labelData.default
171 }
172 })
173 }
174
175 return computedLabels
176 })
177
178 // * Sidebar steps
179 let { sidebarDataCollector } = inject('sidebarStepsFunctions', {
180 sidebarDataCollector: () => {},
181 })
182
183 const { footerBtnDisabledUpdater } = inject('changingStepsFunctions', {
184 footerBtnDisabledUpdater: () => {},
185 })
186
187 let customizeOptions = computed(
188 () => amCustomize.locationStep?.options || defaultCustomizeSettings.sbsNew.locationStep.options,
189 )
190
191 // * Any Employee
192 let anyLocation = computed(() => !store.getters['booking/getLocationId'])
193
194 let initStepOrder = [{ id: 'ServiceStep' }, { id: 'EmployeeStep' }, { id: 'LocationStep' }]
195 let stepOrder = computed(() => {
196 return 'order' in amCustomize ? amCustomize.order : initStepOrder
197 })
198
199 const filterRules = computed(() => {
200 const idx = stepOrder.value.findIndex((step) => step.id === 'LocationStep')
201 let arrRules = stepOrder.value.slice(0, idx)
202 const obj = { serviceId: null, providerId: null, locationId: null }
203 if (arrRules.some((step) => step.id === 'ServiceStep')) delete obj.serviceId
204 if (arrRules.some((step) => step.id === 'EmployeeStep')) delete obj.providerId
205 return obj
206 })
207
208 // * Set Location Options from entities
209 let locationOptions = computed(() => {
210 const relations = amEntities.value.entitiesRelations
211
212 // Collect connected location IDs
213 const locationIdSet = new Set(
214 Object.values(relations)
215 .flatMap((emp) => Object.values(emp).flat())
216 .map((id) => parseInt(id)),
217 )
218
219 const locations = store.getters['entities/filteredLocations'](
220 Object.assign(store.getters['booking/getSelection'], filterRules.value),
221 )
222
223 // Filter locations by connected IDs
224 return locations.filter((loc) => locationIdSet.has(parseInt(loc.id)))
225 })
226
227 function selectLocation(location) {
228 if (disabledLocation(location.id)) return
229
230 let locationData = {
231 reference: 'location',
232 // position will depends on fields order
233 position: 0,
234 value: location.id !== store.getters['booking/getLocationId'] ? location.name : '',
235 }
236
237 if (location.id !== store.getters['booking/getLocationId']) {
238 store.commit('booking/setLocationId', location.id)
239 } else {
240 store.commit('booking/setLocationId', null)
241 }
242
243 writeLocationData(locationData)
244 }
245
246 function disabledLocation(locationId) {
247 const relations = amEntities.value.entitiesRelations
248 const employeeId = store.getters['booking/getEmployeeId']
249 const serviceId = store.getters['booking/getServiceId']
250
251 if (employeeId) {
252 if (serviceId) {
253 return serviceId in relations[employeeId]
254 ? !relations[employeeId][serviceId].some((locId) => parseInt(locId) === locationId)
255 : true
256 }
257 return Object.values(relations[employeeId]).every(
258 (sIds) => !sIds.some((lId) => parseInt(lId) === locationId),
259 )
260 }
261
262 if (serviceId) {
263 return Object.values(relations).every(
264 (emp) =>
265 !(serviceId in emp) || !emp[serviceId].some((locId) => parseInt(locId) === locationId),
266 )
267 }
268
269 return false
270 }
271
272 function chooseAnyLocation() {
273 let locationData = {
274 reference: 'location',
275 // position will depends on fields order
276 position: 0,
277 value: '',
278 }
279
280 store.commit('booking/setLocationId', null)
281
282 writeLocationData(locationData)
283 }
284
285 function writeLocationData(locationData) {
286 let cartItem = useCartItem(store)
287
288 store.commit('booking/unsetMultipleAppointmentsData', cartItem.index)
289
290 sidebarDataCollector(locationData)
291
292 useAction(store, {}, 'SelectLocation', 'appointment', null, null)
293
294 if (customizeOptions.value.location.required) {
295 if (locationData.value) {
296 footerBtnDisabledUpdater(false)
297 } else {
298 footerBtnDisabledUpdater(true)
299 }
300 } else {
301 footerBtnDisabledUpdater(false)
302 }
303 }
304
305 // * Location - More Info
306 let moreInfoVisibility = ref(false)
307 let selectedLocation = ref(null)
308
309 const keyboardSelectionDisabled = computed(() => moreInfoVisibility.value)
310
311 watch(moreInfoVisibility, (isVisible, wasVisible) => {
312 if (wasVisible && !isVisible) {
313 stepCardLayoutReference.value?.focusSelectedOrFirstItem?.()
314 }
315 })
316
317 // * Mounted hook
318 onMounted(() => {
319 if (
320 !!customizeOptions.value.location.required &&
321 store.getters['booking/getLocationId'] === null
322 ) {
323 footerBtnDisabledUpdater(true)
324 }
325 })
326
327 onUnmounted(() => {
328 footerBtnDisabledUpdater(false)
329 })
330 </script>
331
332 <script>
333 export default {
334 name: 'LocationStep',
335 key: 'locationStep',
336 sidebarData: {
337 label: 'location_selection',
338 icon: 'locations',
339 stepSelectedData: [],
340 finished: false,
341 selected: false,
342 },
343 }
344 </script>
345
346 <style lang="scss">
347 .amelia-v2-booking #amelia-container {
348 .am-fs-location-step {
349 .am-fs__init {
350 &-item {
351 // Card content
352 &__content {
353 justify-content: space-between;
354 &.am-item-any {
355 justify-content: center;
356 }
357 }
358
359 // Card heading
360 &__heading {
361 &.am-rw-370 {
362 flex-wrap: nowrap;
363 justify-content: unset;
364 margin-bottom: 8px;
365 }
366 }
367
368 // Card name
369 &__name {
370 &.am-rw-370 {
371 display: block;
372 white-space: nowrap;
373 overflow: hidden;
374 text-overflow: ellipsis;
375 width: auto;
376 order: unset;
377 justify-self: flex-start;
378 }
379 }
380 }
381 }
382 }
383 }
384 </style>
385