ameliabooking
/
v3
/
src
/
views
/
admin
/
customize
/
steps
/
StepForm
/
PackageAppointmentsStep.vue
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 |