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 / admin / customize / steps / StepForm / PackageAppointmentsStep.vue
ameliabooking / v3 / src / views / admin / customize / steps / StepForm Last commit date
common 5 days ago BringingAnyone.vue 5 days ago CartStep.vue 5 days ago Congratulations.vue 5 days ago DateTimeStep.vue 5 days ago EmployeeStep.vue 5 days ago Extras.vue 5 days ago InfoStep.vue 5 days ago InitStep.vue 5 days ago LocationStep.vue 5 days ago PackageAppointmentsListStep.vue 5 days ago PackageAppointmentsStep.vue 5 days ago PackageInfoStep.vue 5 days ago PackageStep.vue 5 days ago PaymentStep.vue 5 days ago RecurringStep.vue 5 days ago RecurringSummary.vue 5 days ago ServiceStep.vue 5 days ago
PackageAppointmentsStep.vue
546 lines
1 <template>
2 <div class="am-fs__pas" :class="props.globalClass">
3 <div class="am-fs__pas-service">
4 {{ packageServiceSelection.service.name }}
5 </div>
6 <div class="am-fs__pas-msg">
7 {{
8 `${labelsDisplay('package_appointment_required')}: 1. ${labelsDisplay(
9 'package_appointment_remaining',
10 )}`
11 }}
12 </div>
13 <el-form ref="packageFormRef" :rules="rules" :model="packageFormData" label-position="top">
14 <div class="am-fs__pas-filter">
15 <el-form-item
16 v-if="amCustomize[pageRenderKey].packageAppointmentsStep.options.employee.visibility"
17 :prop="'provider'"
18 >
19 <template #label>
20 <p>{{ `${labelsDisplay('package_appointment_employee')}:` }}</p>
21 </template>
22 <AmSelect
23 v-model="packageFormData.provider"
24 clearable
25 filterable
26 :placeholder="`${labelsDisplay('package_select_employee')}...`"
27 :fit-input-width="true"
28 >
29 <AmOption
30 v-for="provider in filteredEmployees"
31 :key="provider.id"
32 :value="provider.id"
33 :label="provider.firstName + ' ' + provider.lastName"
34 >
35 <AmOptionTemplate2
36 :identifier="provider.id"
37 :label="`${provider.firstName} ${provider.lastName}`"
38 :dialog-title="labelsDisplay('employee_information_package')"
39 :dialog-button-text="labelsDisplay('select_this_employee_package')"
40 :description="provider.description"
41 ></AmOptionTemplate2>
42 </AmOption>
43 </AmSelect>
44 </el-form-item>
45
46 <el-form-item
47 v-if="amCustomize[pageRenderKey].packageAppointmentsStep.options.location.visibility"
48 :prop="'location'"
49 >
50 <template #label>
51 <p>{{ `${labelsDisplay('package_appointment_location')}:` }}</p>
52 </template>
53 <AmSelect
54 v-model="packageFormData.location"
55 clearable
56 filterable
57 :placeholder="`${labelsDisplay('package_select_location')}...`"
58 :fit-input-width="true"
59 >
60 <AmOption
61 v-for="location in filteredLocations"
62 :key="location.id"
63 :value="location.id"
64 :label="location.name"
65 ></AmOption>
66 </AmSelect>
67 </el-form-item>
68 </div>
69 </el-form>
70
71 <p class="am-fs__pas-app-label">
72 {{ `${labelsDisplay('package_appointments')}:` }}
73 </p>
74
75 <AmCollapse>
76 <AmCollapseItem
77 v-for="(appointment, index) in packageServiceSelection.list"
78 :ref="(el) => (packageAppointmentsList[index] = el)"
79 :key="index"
80 :name="index.toString()"
81 class="am-fs__pas-app-items"
82 :side="true"
83 :delay="500"
84 @collapse-open="selectCalendar(index)"
85 @collapse-clicked="collapseClicked(index)"
86 >
87 <template #heading>
88 <div class="am-fs__pas-app-heading">
89 <span>
90 {{
91 index +
92 1 +
93 '. ' +
94 (appointment.date && appointment.time
95 ? getFrontedFormattedDate(appointment.date) +
96 ' ' +
97 getFrontedFormattedTime(appointment.time)
98 : labelsDisplay('package_appointments_date'))
99 }}
100 </span>
101 </div>
102 </template>
103 <template #icon-start>
104 <span
105 class="am-fs__pas-app-text"
106 :class="{
107 'am-fs__pas-app-text-selected': appointment.date && appointment.time,
108 }"
109 >
110 {{
111 appointment.date && appointment.time
112 ? labelsDisplay('package_appointments_selected')
113 : labelsDisplay('package_appointments_select')
114 }}
115 </span>
116 </template>
117 <template #default>
118 <div class="am-fs__pas-app-content">
119 <AmAdvancedSlotCalendar
120 :id="0"
121 :slots="calendarEvents"
122 :calendar-minimum-date="moment().format('YYYY-MM-DD hh:mm')"
123 :calendar-maximum-date="moment().add(1, 'year').format('YYYY-MM-DD hh:mm')"
124 :time-zone="
125 features.timeZones &&
126 amCustomize[pageRenderKey].packageAppointmentsStep.options.timeZoneVisibility
127 .visibility
128 "
129 :show-people-waiting="false"
130 :show-calendar-date-busyness="
131 amCustomize[pageRenderKey].packageAppointmentsStep.options
132 .calendarDateBusynessVisibility?.visibility ?? true
133 "
134 :label-slots-selected="labelsDisplay('package_appointments_slots_selected')"
135 :busyness="previewCalendarBusyness"
136 ></AmAdvancedSlotCalendar>
137 </div>
138 </template>
139 </AmCollapseItem>
140 </AmCollapse>
141
142 <div class="am-fs__pas-btn">
143 <AmButton size="medium" category="secondary" type="plain" :prefix="'plus'">
144 {{ labelsDisplay('package_appointments_add_more') }}
145 </AmButton>
146 </div>
147 </div>
148 </template>
149
150 <script setup>
151 import { ref, provide, inject, computed } from 'vue'
152 import AmCollapse from '../../../../_components/collapse/AmCollapse.vue'
153 import AmCollapseItem from '../../../../_components/collapse/AmCollapseItem.vue'
154 import AmSelect from '../../../../_components/select/AmSelect.vue'
155 import AmOption from '../../../../_components/select/AmOption.vue'
156 import AmOptionTemplate2 from '../../../../_components/select/parts/AmOptionTemplate2.vue'
157 import AmButton from '../../../../_components/button/AmButton.vue'
158 import AmAdvancedSlotCalendar from '../../../../_components/advanced-slot-calendar/AmAdvancedSlotCalendar.vue'
159 import {
160 getFrontedFormattedTime,
161 getFrontedFormattedDate,
162 } from '../../../../../assets/js/common/date'
163 import moment from 'moment'
164 import { useReactiveCustomize } from '../../../../../assets/js/admin/useReactiveCustomize.js'
165
166 let props = defineProps({
167 globalClass: {
168 type: String,
169 default: '',
170 },
171 })
172
173 // * Features
174 let features = inject('features')
175
176 let packageServiceSelection = ref({
177 service: {
178 name: 'Service 1',
179 },
180 list: [
181 { date: moment().format('YYYY-MM-DD'), time: '13:05' },
182 {
183 date: moment().add(3, 'days').format('YYYY-MM-DD'),
184 time: '13:05',
185 isSubstitute: false,
186 },
187 { date: '', time: '', isSubstitute: true },
188 {
189 date: moment().add(9, 'days').format('YYYY-MM-DD'),
190 time: '13:05',
191 isSubstitute: true,
192 },
193 ],
194 })
195
196 let calendarEvents = ref([])
197
198 let today = moment().format('YYYY-MM-DD')
199
200 for (let i = 0; i <= 31; i++) {
201 let block = {
202 display: 'background',
203 extendedProps: {
204 slots: { '09:00': [7, 3] },
205 slotsAvailable: 1,
206 slotsTotal: 100,
207 },
208 start: moment(today).add(i, 'd').format('YYYY-MM-DD'),
209 title: 'e',
210 }
211 calendarEvents.value.push(block)
212 }
213
214 let calendarEventDate = ref('')
215 let calendarServiceDuration = ref('0')
216 let calendarEventSlots = ref([])
217 let calendarEventSlot = ref('')
218 let calendarStartDate = ref(moment().format('YYYY-MM-DD'))
219 let calendarChangeSideBar = ref(true)
220 let calendarWaitingListSlots = ref([])
221
222 provide('calendarEventDate', calendarEventDate)
223 provide('calendarServiceDuration', calendarServiceDuration)
224 provide('calendarEvents', calendarEvents)
225 provide('calendarEventSlots', calendarEventSlots)
226 provide('calendarEventSlot', calendarEventSlot)
227 provide('calendarStartDate', calendarStartDate)
228 provide('calendarChangeSideBar', calendarChangeSideBar)
229 provide('calendarWaitingListSlots', calendarWaitingListSlots)
230
231 /**********
232 * Filter *
233 *********/
234
235 let packageFormRef = ref(null)
236
237 let packageFormData = ref({
238 provider: '',
239 location: '',
240 })
241
242 let langKey = inject('langKey')
243 let amLabels = inject('labels')
244
245 const { amCustomize } = useReactiveCustomize()
246 let pageRenderKey = inject('pageRenderKey')
247
248 let previewCalendarBusyness = computed(() => {
249 const opts = amCustomize.value[pageRenderKey.value].packageAppointmentsStep.options
250 if (!(opts.calendarDateBusynessVisibility?.visibility ?? true)) {
251 return {}
252 }
253 const base = moment().startOf('day')
254 return {
255 [base.clone().add(6, 'days').format('YYYY-MM-DD')]: 40,
256 [base.clone().add(20, 'days').format('YYYY-MM-DD')]: 82,
257 }
258 })
259
260 // * Form validation rules
261 let rules = computed(() => {
262 return {
263 provider: [
264 {
265 required:
266 amCustomize.value[pageRenderKey.value].packageAppointmentsStep.options.employee.required,
267 message: labelsDisplay('please_select_employee', 'initStep'),
268 trigger: ['blur', 'change'],
269 },
270 ],
271 location: [
272 {
273 required:
274 amCustomize.value[pageRenderKey.value].packageAppointmentsStep.options.location.required,
275 message: labelsDisplay('please_select_location', 'initStep'),
276 trigger: ['blur', 'change'],
277 },
278 ],
279 }
280 })
281
282 let filteredEmployees = ref([
283 {
284 id: 1,
285 firstName: 'Jane',
286 lastName: 'Doe',
287 price: 125,
288 pictureThumbPath: '',
289 description: "This is Jane Doe's description",
290 },
291 {
292 id: 2,
293 firstName: 'John',
294 lastName: 'Doe',
295 price: 125,
296 pictureThumbPath: '',
297 description: '',
298 },
299 {
300 id: 3,
301 firstName: 'Oktavian',
302 lastName: 'Avgust',
303 price: 125,
304 pictureThumbPath: '',
305 description: '',
306 },
307 ])
308
309 let filteredLocations = ref([
310 {
311 id: 1,
312 name: 'Location 1',
313 },
314 {
315 id: 2,
316 name: 'Location 2',
317 },
318 {
319 id: 3,
320 name: 'Location 3',
321 },
322 {
323 id: 4,
324 name: 'Location 4',
325 },
326 {
327 id: 5,
328 name: 'Location 5',
329 },
330 ])
331
332 /*********
333 * Other *
334 ********/
335 let calendarSlotDuration = 3600
336 provide('calendarSlotDuration', calendarSlotDuration)
337
338 let packageAppointmentsList = ref([])
339
340 function collapseClicked(value) {
341 packageAppointmentsList.value.forEach((item, index) => {
342 if (index !== value && item.contentVisibility) {
343 item.closingFromParent()
344 }
345 })
346 }
347
348 function selectCalendar() {}
349
350 // * Label computed function
351 function labelsDisplay(label) {
352 let computedLabel = computed(() => {
353 return amCustomize.value[pageRenderKey.value].packageAppointmentsStep.translations &&
354 amCustomize.value[pageRenderKey.value].packageAppointmentsStep.translations[label] &&
355 amCustomize.value[pageRenderKey.value].packageAppointmentsStep.translations[label][
356 langKey.value
357 ]
358 ? amCustomize.value[pageRenderKey.value].packageAppointmentsStep.translations[label][
359 langKey.value
360 ]
361 : amLabels[label]
362 })
363
364 return computedLabel.value
365 }
366 </script>
367
368 <script>
369 export default {
370 name: 'PackageAppointmentsStep',
371 key: 'packageAppointmentsStep',
372 sidebarData: {
373 label: 'package_appointment_step',
374 icon: 'calendar-pencil',
375 stepSelectedData: [],
376 finished: false,
377 selected: false,
378 type: 'packageAppointments',
379 },
380 }
381 </script>
382
383 <style lang="scss">
384 // am -- amelia
385 // fs -- form steps
386
387 #amelia-app-backend-new {
388 #amelia-container {
389 // Amelia Form Steps
390 .am-fs {
391 // pas - package appointment step
392 &__pas {
393 --am-c-pas-text: var(--am-c-main-text);
394 --am-c-pas-success: var(--am-c-success);
395
396 & > * {
397 $count: 5;
398 @for $i from 0 through $count {
399 &:nth-child(#{$i + 1}) {
400 animation: 600ms cubic-bezier(0.45, 1, 0.4, 1.2) #{$i * 100}ms am-animation-slide-up;
401 animation-fill-mode: both;
402 }
403 }
404 }
405
406 &-msg {
407 font-weight: 400;
408 font-size: 13px;
409 line-height: 18px;
410 /* $shade-900 */
411 color: var(--am-c-pas-text);
412 margin-bottom: 16px;
413 }
414
415 &-service {
416 font-size: 18px;
417 font-weight: 500;
418 line-height: 1.6;
419 color: var(--am-c-pas-text);
420 margin: 0 0 8px;
421 }
422
423 &-filter {
424 display: flex;
425 gap: 12px;
426
427 & > div {
428 & > p,
429 & > label > p {
430 font-weight: 500;
431 font-size: 15px;
432 line-height: 20px;
433 /* $shade-900 */
434 color: var(--am-c-pas-text);
435 margin-bottom: 4px;
436 display: inline;
437 }
438 }
439 }
440
441 &-app {
442 &-label {
443 font-weight: 500;
444 font-size: 14px;
445 line-height: 20px;
446 /* $shade-900 */
447 color: var(--am-c-pas-text);
448 margin-top: 16px;
449 margin-bottom: 16px;
450 }
451
452 &-items {
453 .am-collapse-item {
454 &__content {
455 display: block;
456 }
457
458 &__trigger {
459 padding: 0;
460 }
461 }
462 }
463
464 &-heading {
465 font-weight: 400;
466 font-size: 14px;
467 line-height: 20px;
468 /* $shade-900 */
469 color: var(--am-c-pas-text);
470 display: flex;
471 justify-content: space-between;
472 width: 100%;
473 margin-right: 20px;
474 }
475
476 &-content {
477 .fc-col-header {
478 width: 100%;
479 }
480
481 .fc-view-harness {
482 min-height: 240px;
483 }
484
485 .fc-scrollgrid-sync-table {
486 width: 100%;
487 min-height: 190px;
488 }
489
490 .fc-daygrid-body-balanced {
491 width: 100%;
492 }
493 }
494
495 &-text {
496 font-weight: 500;
497 font-size: 14px;
498 line-height: 20px;
499 /* $shade-900 */
500 color: var(--am-c-pas-text);
501 &-selected {
502 /* $green-900 */
503 color: var(--am-c-pas-success);
504 }
505 margin-right: 10px;
506 white-space: nowrap;
507 }
508 }
509
510 &-btn {
511 display: flex;
512 justify-content: center;
513 margin-top: 32px;
514
515 .am-button__inner {
516 font-weight: 500;
517 }
518 .am-icon-plus {
519 font-size: 16px;
520 margin-right: 8px;
521 }
522 }
523
524 .am-collapse-item {
525 $count: 20;
526 @for $i from 0 through $count {
527 &:nth-child(#{$i + 1}) {
528 animation: 600ms cubic-bezier(0.45, 1, 0.4, 1.2) #{$i * 100}ms am-animation-slide-up;
529 animation-fill-mode: both;
530 }
531 }
532
533 &__heading {
534 transition-delay: 0.5s;
535
536 &-side {
537 transition-delay: 0s;
538 }
539 }
540 }
541 }
542 }
543 }
544 }
545 </style>
546