PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.8
Booking for Appointments and Events Calendar – Amelia v2.4.8
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 / steps / StepForm / RecurringSummary.vue
ameliabooking / v3 / src / views / admin / customize / steps / StepForm Last commit date
common 1 month ago BringingAnyone.vue 1 month ago CartStep.vue 1 month ago Congratulations.vue 2 days ago DateTimeStep.vue 1 month ago EmployeeStep.vue 1 month ago Extras.vue 1 month ago InfoStep.vue 1 month ago InitStep.vue 1 month ago LocationStep.vue 1 month ago PackageAppointmentsListStep.vue 1 month ago PackageAppointmentsStep.vue 1 month ago PackageInfoStep.vue 1 month ago PackageStep.vue 1 month ago PaymentStep.vue 1 month ago RecurringStep.vue 1 month ago RecurringSummary.vue 2 days ago ServiceStep.vue 1 month ago
RecurringSummary.vue
519 lines
1 <template>
2 <div ref="recurringSummaryRef" class="am-fs__ro" :class="props.globalClass" :style="cssVars">
3 <div class="am-fs__ro-warning">
4 <AmAlert
5 type="warning"
6 :title="labelsDisplay('recurring_unavailable_slots')"
7 :description="`2 ${labelsDisplay('recurring_alert_content')}`"
8 :show-icon="true"
9 :closable="false"
10 ></AmAlert>
11 </div>
12 <AmCollapse>
13 <AmCollapseItem
14 v-for="(appointment, index) in serviceSelection.list"
15 :ref="(el) => (recurringList[index] = el)"
16 :key="index"
17 :name="index.toString()"
18 class="am-fs__ro-app-items"
19 :class="{ 'am-fs__ro-app-unavailable': appointment.isSubstitute }"
20 :side="true"
21 :delay="500"
22 @collapse-open="selectCalendar(index)"
23 @collapse-clicked="collapseClicked(index)"
24 >
25 <template #heading>
26 <div class="am-fs__ro-app-heading">
27 <span>
28 {{
29 index +
30 1 +
31 '. ' +
32 (appointment.date && appointment.time
33 ? appointment.date + ' ' + appointment.time
34 : labelsDisplay('recurring_chose_date'))
35 }}
36 </span>
37 </div>
38 </template>
39
40 <template #icon-end>
41 <SideMenu :cancel-label="labelsDisplay('recurring_delete')"></SideMenu>
42 </template>
43
44 <template #default>
45 <div class="am-fs__ro-app-content">
46 <AmAdvancedSlotCalendar
47 v-if="showCalendar && index === parseInt(activeIndex)"
48 :id="index"
49 :date="appointment.date"
50 :calendar-minimum-date="moment().format('YYYY-MM-DD HH:mm')"
51 :calendar-maximum-date="moment().add(1, 'year').format('YYYY-MM-DD HH:mm')"
52 :end-time="
53 amCustomize[pageRenderKey].recurringSummary.options.endTimeVisibility.visibility
54 "
55 :time-zone="
56 features.timeZones &&
57 amCustomize[pageRenderKey].recurringSummary.options.timeZoneVisibility.visibility
58 "
59 :show-busy-slots="busyTimeSlotsVisibility"
60 :show-estimated-pricing="
61 amCustomize[pageRenderKey].recurringSummary.options.estimatedPricingVisibility
62 .visibility
63 "
64 :show-indicator-pricing="
65 amCustomize[pageRenderKey].recurringSummary.options.indicatorPricingVisibility
66 .visibility
67 "
68 :show-slot-pricing="
69 amCustomize[pageRenderKey].recurringSummary.options.slotPricingVisibility.visibility
70 "
71 :show-people-waiting="false"
72 :show-calendar-date-busyness="
73 amCustomize[pageRenderKey].recurringSummary.options.calendarDateBusynessVisibility
74 ?.visibility ?? true
75 "
76 :label-slots-selected="labelsDisplay('recurring_slots_selected')"
77 :period-pricing="periodPricing"
78 :busyness="previewCalendarBusyness"
79 :nested-item="{ inCollapse: true }"
80 @selected-date="setSelectedDate"
81 @selected-time="setSelectedTime"
82 @unselect-date="unselectDate"
83 ></AmAdvancedSlotCalendar>
84 </div>
85 </template>
86 </AmCollapseItem>
87 </AmCollapse>
88 </div>
89 </template>
90
91 <script setup>
92 import { computed, inject, provide, ref, onMounted, nextTick, watchEffect } from 'vue'
93 import moment from 'moment'
94 import AmAlert from '../../../../_components/alert/AmAlert'
95 import AmCollapse from '../../../../_components/collapse/AmCollapse'
96 import AmCollapseItem from '../../../../_components/collapse/AmCollapseItem'
97 import SideMenu from '../../../../public/StepForm/RecurringStep/parts/SideMenu.vue'
98 import AmAdvancedSlotCalendar from '../../../../_components/advanced-slot-calendar/AmAdvancedSlotCalendar.vue'
99 import { useColorTransparency } from '../../../../../assets/js/common/colorManipulation'
100 import { useCalendarEvents } from '../../../../../assets/js/common/appointments.js'
101 import { useReactiveCustomize } from '../../../../../assets/js/admin/useReactiveCustomize.js'
102
103 let props = defineProps({
104 globalClass: {
105 type: String,
106 default: '',
107 },
108 })
109
110 // Container Width
111 let cWidth = inject('containerWidth', 0)
112
113 // * Plugin Licence
114 let licence = inject('licence')
115
116 // * Features
117 let features = inject('features')
118
119 // * Languages
120 let langKey = inject('langKey')
121
122 // * Global Labels
123 let amLabels = inject('labels')
124
125 let pageRenderKey = inject('pageRenderKey')
126 const { amCustomize } = useReactiveCustomize()
127
128 let previewCalendarBusyness = computed(() => {
129 const opts = amCustomize.value[pageRenderKey.value].recurringSummary.options
130 if (!(opts.calendarDateBusynessVisibility?.visibility ?? true)) {
131 return {}
132 }
133 const base = moment().startOf('day')
134 return {
135 [base.clone().add(6, 'days').format('YYYY-MM-DD')]: 40,
136 [base.clone().add(20, 'days').format('YYYY-MM-DD')]: 82,
137 }
138 })
139
140 let busyTimeSlotsVisibility = computed(() => {
141 const opts = amCustomize.value[pageRenderKey.value].recurringSummary.options
142 return opts.busyTimeSlotsVisibility?.visibility ?? false
143 })
144
145 // * Label computed function
146 function labelsDisplay(label) {
147 let computedLabel = computed(() => {
148 return amCustomize.value[pageRenderKey.value].recurringSummary.translations &&
149 amCustomize.value[pageRenderKey.value].recurringSummary.translations[label] &&
150 amCustomize.value[pageRenderKey.value].recurringSummary.translations[label][langKey.value]
151 ? amCustomize.value[pageRenderKey.value].recurringSummary.translations[label][langKey.value]
152 : amLabels[label]
153 })
154
155 return computedLabel.value
156 }
157
158 let serviceSelection = ref({
159 list: [
160 { date: moment().format('YYYY-MM-DD'), time: '13:05' },
161 {
162 date: moment().add(3, 'days').format('YYYY-MM-DD'),
163 time: '13:05',
164 isSubstitute: false,
165 },
166 { date: '', time: '', isSubstitute: true },
167 {
168 date: moment().add(9, 'days').format('YYYY-MM-DD'),
169 time: '13:05',
170 isSubstitute: true,
171 },
172 ],
173 })
174
175 /*****************
176 * Calendar Data *
177 ****************/
178 let recurringSummaryRef = ref(null)
179 provide('formWrapper', recurringSummaryRef)
180
181 let activeIndex = ref('0')
182
183 let showCalendar = ref(false)
184
185 let periodPricing = ref({})
186
187 const previewSlots = getDummyPreviewSlots()
188
189 function getDummyPreviewSlots() {
190 const slots = {}
191 const today = moment().startOf('day')
192
193 for (let i = 0; i <= 31; i++) {
194 const current = today.clone().add(i, 'days')
195 const date = current.format('YYYY-MM-DD')
196 const dayOfWeek = current.day()
197
198 if (dayOfWeek === 0 || dayOfWeek === 6) {
199 continue
200 }
201
202 slots[date] = {}
203
204 for (let hour = 9; hour < 13; hour++) {
205 const hourStr = hour < 10 ? `0${hour}` : `${hour}`
206 slots[date][`${hourStr}:00`] = [{ e: 1, c: 3 }]
207 slots[date][`${hourStr}:30`] = [{ e: 1, c: 3 }]
208 }
209
210 if (i % 3 === 0) {
211 slots[date]['14:00'] = [{ e: 1, c: 2 }]
212 slots[date]['15:30'] = [{ e: 1, c: 2 }]
213 }
214 }
215
216 return slots
217 }
218
219 function getCustomPeriodsPricing() {
220 let result = {}
221 let today = moment()
222
223 let current = today.clone()
224
225 for (let i = 0; i <= 31; i++) {
226 let type =
227 current.day() === 6 || current.day() === 0
228 ? 'high'
229 : current.day() === 1 || current.day() === 2
230 ? 'low'
231 : 'mid'
232
233 result[current.format('YYYY-MM-DD')] = {
234 type: type,
235 slots: {
236 '09:00': {
237 type: type === 'mid' ? 'low' : type,
238 price: type === 'mid' ? 5 : type === 'low' ? 5 : 15,
239 },
240 '09:30': {
241 type: type === 'mid' ? 'low' : type,
242 price: type === 'mid' ? 5 : type === 'low' ? 5 : 15,
243 },
244 '10:00': {
245 type: type,
246 price: type === 'mid' ? 10 : type === 'low' ? 5 : 15,
247 },
248 '10:30': {
249 type: type,
250 price: type === 'mid' ? 10 : type === 'low' ? 5 : 15,
251 },
252 '11:00': {
253 type: type,
254 price: type === 'mid' ? 10 : type === 'low' ? 5 : 15,
255 },
256 '11:30': {
257 type: type,
258 price: type === 'mid' ? 10 : type === 'low' ? 5 : 15,
259 },
260 '12:00': {
261 type: type === 'mid' ? 'high' : type,
262 price: type === 'mid' ? 15 : type === 'high' ? 15 : 5,
263 },
264 '12:30': {
265 type: type === 'mid' ? 'high' : type,
266 price: type === 'mid' ? 15 : type === 'high' ? 15 : 5,
267 },
268 },
269 }
270
271 current.add(1, 'day')
272 }
273
274 return {
275 price: {
276 low: 5,
277 mid: 10,
278 high: 15,
279 uniqueMin: true,
280 uniqueMid: true,
281 uniqueMax: true,
282 },
283 dates: result,
284 }
285 }
286
287 let calendarEvents = ref(useCalendarEvents(previewSlots))
288
289 let calendarEventDate = ref('')
290
291 let calendarServiceDuration = ref('0')
292
293 let calendarEventSlots = ref([])
294 let calendarEventBusySlots = ref([])
295
296 watchEffect(() => {
297 calendarEventBusySlots.value = busyTimeSlotsVisibility.value
298 ? ['08:00', '08:30', '09:30', '12:30', '14:00']
299 : []
300 })
301
302 let calendarEventSlot = ref('')
303 let calendarStartDate = ref(moment().format('YYYY-MM-DD'))
304 let calendarChangeSideBar = ref(true)
305 let calendarWaitingListSlots = ref([])
306 provide('calendarEvents', calendarEvents)
307 provide('calendarEventDate', calendarEventDate)
308 provide('calendarEventSlots', calendarEventSlots)
309 provide('calendarEventBusySlots', calendarEventBusySlots)
310 provide('calendarEventSlot', calendarEventSlot)
311 provide('calendarStartDate', calendarStartDate)
312 provide('calendarChangeSideBar', calendarChangeSideBar)
313 provide('calendarServiceDuration', calendarServiceDuration)
314 provide('calendarWaitingListSlots', calendarWaitingListSlots)
315
316 function setSelectedDate(value) {
317 unselectDate()
318
319 if (value in previewSlots) {
320 calendarEventSlots.value = Object.keys(previewSlots[value]).sort()
321 }
322
323 if (calendarEventSlots.value.length) {
324 setSelectedTime(calendarEventSlots.value[0])
325 }
326
327 calendarEventDate.value = value
328 }
329
330 function unselectDate() {
331 calendarEventBusySlots.value = busyTimeSlotsVisibility.value
332 ? ['08:00', '08:30', '09:30', '12:30', '14:00']
333 : []
334
335 calendarEventSlots.value = []
336 calendarEventSlot.value = ''
337 calendarEventDate.value = ''
338 }
339
340 function setSelectedTime(value) {
341 calendarEventSlot.value = value
342 }
343
344 /*********
345 * Other *
346 ********/
347 let calendarSlotDuration = 3600
348
349 provide('calendarSlotDuration', calendarSlotDuration)
350
351 let recurringList = ref([])
352
353 function collapseClicked(value) {
354 recurringList.value.forEach((item, index) => {
355 if (index !== value && item?.contentVisibility) {
356 item.closingFromParent()
357 }
358 })
359 }
360
361 function selectCalendar(value) {
362 if (value !== '') {
363 activeIndex.value = value.toString()
364
365 nextTick(() => {
366 const appointment = serviceSelection.value.list[value]
367
368 if (appointment?.date) {
369 setSelectedDate(appointment.date)
370
371 if (appointment.time) {
372 setSelectedTime(appointment.time)
373 }
374 } else {
375 unselectDate()
376 }
377 })
378 }
379 }
380
381 onMounted(() => {
382 periodPricing.value =
383 !licence.isBasic && !licence.isStarter && !licence.isLite ? getCustomPeriodsPricing() : {}
384
385 showCalendar.value = true
386 })
387
388 let amColors = inject('amColors')
389
390 let cssVars = computed(() => {
391 return {
392 '--am-c-ro-warning-op10': useColorTransparency(amColors.value.colorWarning, 0.1),
393 '--am-c-ro-warning-op60': useColorTransparency(amColors.value.colorWarning, 0.6),
394 }
395 })
396 </script>
397
398 <script>
399 export default {
400 name: 'RecurringSummary',
401 key: 'recurringSummary',
402 sidebarData: {
403 label: 'recurring_summary',
404 icon: 'calendar-pencil',
405 stepSelectedData: [],
406 finished: false,
407 selected: false,
408 },
409 }
410 </script>
411
412 <style lang="scss">
413 #amelia-app-backend-new {
414 #amelia-container {
415 // ro - recurring overview
416 .am-fs__ro {
417 --am-c-ro-text: var(--am-c-main-text);
418 --am-c-ro-success: var(--am-c-success);
419
420 &-warning {
421 .am-alert {
422 i {
423 font-size: 20px;
424 }
425 margin-bottom: 16px;
426 }
427 }
428
429 .am-collapse-item {
430 &__heading {
431 transition-delay: 0.5s;
432
433 &-side {
434 transition-delay: 0s;
435 }
436 }
437 }
438
439 &-app {
440 &-unavailable {
441 .am-collapse-item__heading {
442 /* $yellow-300 */
443 background-color: var(--am-c-ro-warning-op10);
444 /* $yellow-600 */
445 border: 1px solid var(--am-c-ro-warning-op60);
446 &-active {
447 border-bottom: 0;
448 }
449 }
450 .am-collapse-item__content {
451 /* $yellow-300 */
452 background-color: var(--am-c-ro-warning-op10);
453 /* $yellow-600 */
454 border: 1px solid var(--am-c-ro-warning-op60);
455 }
456 }
457
458 &-items {
459 .am-collapse-item__content {
460 display: block;
461 }
462 .am-collapse-item__trigger {
463 padding: 0;
464 }
465 }
466
467 &-heading {
468 display: flex;
469 justify-content: space-between;
470 width: 100%;
471 font-size: 14px;
472 font-weight: 400;
473 line-height: 1.42857;
474 /* $shade-900 */
475 color: var(--am-c-ro-text);
476 margin: 0 20px 0 0;
477 }
478
479 &-text {
480 font-weight: 500;
481 font-size: 14px;
482 line-height: 20px;
483 /* $shade-900 */
484 color: var(--am-c-ro-text);
485 &-selected {
486 /* $green-900 */
487 color: var(--am-c-ro-success);
488 }
489 margin-right: 10px;
490 white-space: nowrap;
491 }
492 }
493 }
494
495 // Amelia Form Steps
496 .am-fs {
497 // Container Wrapper
498 &__main {
499 &-heading {
500 &-inner {
501 display: flex;
502 align-items: center;
503
504 .am-heading-prev {
505 margin-right: 12px;
506 }
507 }
508 }
509 &-inner {
510 &#{&}-dt {
511 padding: 0 20px;
512 }
513 }
514 }
515 }
516 }
517 }
518 </style>
519