PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / trunk
Booking for Appointments and Events Calendar – Amelia vtrunk
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 / Parts / RecurringSetup.vue
ameliabooking / v3 / src / views / public / Parts Last commit date
Congratulations 5 days ago Payment 5 days ago Skeletons 5 days ago BackLink.vue 5 days ago BookingSkeleton.vue 5 days ago Calendar.vue 5 days ago DotsPopup.vue 5 days ago RecurringSetup.vue 5 days ago
RecurringSetup.vue
537 lines
1 <template>
2 <div
3 class="am-fs__rs"
4 :class="[{ 'am-fs__rs-mobile': checkScreen }, props.globalClass]"
5 :style="cssVars"
6 >
7 <template
8 v-if="
9 amCustomize &&
10 amCustomize.recurringStep &&
11 amCustomize.recurringStep.options.heading.visibility
12 "
13 >
14 <p>{{ amLabels.recurrence }}</p>
15 <span>{{ amLabels.recurrence_choosing_time }}</span>
16 </template>
17 <p>{{ amLabels.repeat_every }}:</p>
18 <div class="am-fs__rs-every">
19 <AmInputNumber v-model="repeatInterval" :min="1" :max="100" />
20 <AmSelect v-model="repeatType" :disabled="props.service.recurringCycle !== 'all'">
21 <AmOption
22 v-for="(type, i) in repeatTypes"
23 :key="i"
24 :label="type.label"
25 :value="type.value"
26 />
27 </AmSelect>
28 </div>
29 <div v-if="repeatType === 'weekly'">
30 <p>{{ `${amLabels.recurrence_repeat_on}:` }}</p>
31 <div class="am-fs__rs-on-week">
32 <div
33 v-for="(day, i) in weekDays"
34 :key="i"
35 :class="{
36 'am-fs__rs-on-week-selected': weekDays.find((i) => i.value === day.value).selected,
37 }"
38 @click="selectWeekDay(day.value)"
39 >
40 {{ day.label }}
41 </div>
42 </div>
43 </div>
44 <div v-if="repeatType === 'monthly'">
45 <p>{{ `${amLabels.recurrence_repeat_on}:` }}</p>
46 <div class="am-fs__rs-on-month">
47 <AmSelect v-model="repeatMonthly">
48 <AmOption
49 v-for="(type, i) in monthlyTypes"
50 :key="i"
51 :label="type.label + (i !== 0 ? ' ' + selectedDayInMonth : '')"
52 :value="type.value"
53 />
54 </AmSelect>
55 </div>
56 </div>
57 <p>{{ `${amLabels.recurrence_ends}:` }}</p>
58 <span>{{ amLabels.recurrence_choose_ends }}</span>
59 <div class="am-fs__rs-ends">
60 <div class="am-fs__rs-ends-choose">
61 <AmRadioGroup v-model="occurrenceType">
62 <AmRadio value="On">{{ amLabels.recurrence_on }}</AmRadio>
63 <AmRadio value="After">{{ amLabels.recurrence_after }}</AmRadio>
64 </AmRadioGroup>
65 </div>
66 <div class="am-fs__rs-ends-options">
67 <AmDatePickerFull
68 :calendar-minimum-date="props.startDate"
69 :disabled="occurrenceType !== 'On'"
70 :input-placeholder="amLabels.recurrence_select_date"
71 :calendar-maximum-date="props.maxDate"
72 @selected-date="selectedOccurrenceDate"
73 />
74 <div class="am-fs__rs-ends-after">
75 <AmInputNumber
76 v-model="occurrenceCount"
77 :disabled="occurrenceType !== 'After'"
78 :min="1"
79 :max="100"
80 />
81 <span>{{ ` ${amLabels.occurrences}` }}</span>
82 </div>
83 </div>
84 </div>
85 <div class="am-fs__rs-summary">
86 <p>{{ `${amLabels.appointment_repeats}:` }}</p>
87 <p>
88 {{
89 `${amLabels.recurrence_every} ${repeatInterval > 1 ? repeatInterval : ''} ${repeatTypes.find((item) => item.value === repeatType) ? repeatTypes.find((item) => item.value === repeatType).label : ''}`
90 }}
91 <span v-if="repeatType === 'weekly'">
92 {{
93 `${amLabels.repeats_on} ${weekDays
94 .filter((i) => i.selected)
95 .map((w) => w.labelFull)
96 .join(', ')}`
97 }}
98 </span>
99 <span v-if="repeatType === 'monthly'">
100 {{ ` ${amLabels.repeats_on} ${monthlyTypes[repeatMonthly].label}` }}
101 <span v-if="repeatMonthly !== 0">
102 {{ ` ${selectedDayInMonth}` }} </span
103 >,
104 </span>
105 <span>
106 {{
107 ` ${amLabels.repeats_from} ${getFrontedFormattedDate(props.startDate)} ${amLabels.repeats_at} ${getFrontedFormattedTime(props.startTime)}`
108 }}
109 </span>
110 </p>
111 <p>
112 <span v-if="occurrenceType === 'After'">
113 {{ amLabels.number_of_recurrences }} {{ ` ${occurrenceCount}` }}
114 </span>
115 <span v-else-if="occurrenceDate">
116 {{ amLabels.ends_on }}
117 {{ ` ${getFrontedFormattedDate(useStringFromDate(occurrenceDate))}` }}
118 </span>
119 </p>
120 </div>
121 </div>
122 </template>
123
124 <script setup>
125 import { useStore } from 'vuex'
126 import {
127 getFrontedFormattedTime,
128 getFrontedFormattedDate,
129 useStringFromDate,
130 weekDaysLocale,
131 weekDaysShortLocale,
132 } from '../../../assets/js/common/date'
133 import AmInputNumber from '../../_components/input-number/AmInputNumber'
134 import AmSelect from '../../_components/select/AmSelect'
135 import AmOption from '../../_components/select/AmOption'
136 import AmRadioGroup from '../../_components/radio/AmRadioGroup'
137 import AmRadio from '../../_components/radio/AmRadio'
138 import AmDatePickerFull from '../../_components/date-picker-full/AmDatePickerFull'
139 import moment from 'moment'
140 import { shortLocale } from '../../../plugins/settings.js'
141
142 import { computed, onMounted, ref, inject } from 'vue'
143 import { useColorTransparency } from '../../../assets/js/common/colorManipulation'
144
145 let props = defineProps({
146 service: {
147 type: Object,
148 default: () => {},
149 },
150 startDate: {
151 type: String,
152 default: '',
153 },
154 startTime: {
155 type: String,
156 default: '',
157 },
158 maxDate: {
159 type: String,
160 default: '',
161 },
162 globalClass: {
163 type: String,
164 default: '',
165 },
166 })
167
168 let amLabels = inject('amLabels')
169
170 let amCustomize = inject('amCustomize')
171
172 // * Store
173 let store = useStore()
174
175 // Container Width
176 let cWidth = inject('containerWidth', 0)
177 let checkScreen = computed(() => cWidth.value < 560 || cWidth.value - 240 < 520)
178
179 /************
180 * Computed *
181 ***********/
182 let repeatType = computed({
183 get: () => store.getters['recurring/getRepeatType'],
184 set: (val) => {
185 store.commit('recurring/setRepeatType', val)
186 },
187 })
188
189 let repeatInterval = computed({
190 get: () => store.getters['recurring/getRepeatInterval'],
191 set: (val) => {
192 store.commit('recurring/setRepeatInterval', val)
193 },
194 })
195
196 let occurrenceType = computed({
197 get: () => store.getters['recurring/getOccurrenceType'],
198 set: (val) => {
199 store.commit('recurring/setOccurrenceType', val)
200 },
201 })
202
203 let occurrenceDate = computed({
204 get: () =>
205 store.getters['recurring/getOccurrenceDate']
206 ? store.getters['recurring/getOccurrenceDate']
207 : new Date(props.startDate),
208 set: (val) => {
209 store.commit('recurring/setOccurrenceDate', val)
210 },
211 })
212
213 let occurrenceCount = computed({
214 get: () => store.getters['recurring/getOccurrenceCount'],
215 set: (val) => {
216 store.commit('recurring/setOccurrenceCount', val)
217 },
218 })
219
220 let repeatMonthly = computed({
221 get: () => store.getters['recurring/getMonthly'],
222 set: (val) => {
223 store.commit('recurring/setMonthly', val)
224 },
225 })
226
227 const repeatTypes = ref([
228 {
229 label: amLabels.value.recurrence_day,
230 labelPlural: amLabels.value.recurrence_days,
231 value: 'daily',
232 },
233 {
234 label: amLabels.value.recurrence_week,
235 labelPlural: amLabels.value.recurrence_weeks,
236 value: 'weekly',
237 },
238 {
239 label: amLabels.value.recurrence_month,
240 labelPlural: amLabels.value.recurrence_months,
241 value: 'monthly',
242 },
243 ])
244
245 const monthlyTypes = ref([
246 {
247 label: amLabels.value.recurrence_specific_date,
248 value: 0,
249 },
250 {
251 label: amLabels.value.recurrence_first,
252 value: 1,
253 },
254 {
255 label: amLabels.value.recurrence_second,
256 value: 2,
257 },
258 {
259 label: amLabels.value.recurrence_third,
260 value: 3,
261 },
262 {
263 label: amLabels.value.recurrence_fourth,
264 value: 4,
265 },
266 {
267 label: amLabels.value.recurrence_last,
268 value: 5,
269 },
270 ])
271
272 let currentDay = ref(moment(props.startDate).format('dddd').toLowerCase())
273
274 let weekDays = ref([
275 { label: 'Mon', labelFull: 'Monday', value: 'monday', selected: false },
276 { label: 'Tue', labelFull: 'Tuesday', value: 'tuesday', selected: false },
277 { label: 'Wed', labelFull: 'Wednesday', value: 'wednesday', selected: false },
278 { label: 'Thu', labelFull: 'Thursday', value: 'thursday', selected: false },
279 { label: 'Fri', labelFull: 'Friday', value: 'friday', selected: false },
280 { label: 'Sat', labelFull: 'Saturday', value: 'saturday', selected: false },
281 { label: 'Sun', labelFull: 'Sunday', value: 'sunday', selected: false },
282 ])
283
284 let selectedDayInMonth = computed(() => {
285 return weekDays.value.find(
286 (i) => i.value === moment(props.startDate, 'YYYY-MM-DD').format('dddd').toLowerCase(),
287 ).labelFull
288 })
289
290 function selectedOccurrenceDate(dateString) {
291 occurrenceDate.value = moment(dateString, 'YYYY-MM-DD').toDate()
292 }
293
294 function selectWeekDay(value, preselected = false) {
295 let weekDay = weekDays.value.find((i) => i.value === value)
296
297 weekDay.selected = preselected ? true : !weekDay.selected
298
299 store.commit('recurring/setDays', { value: value, selected: weekDay.selected })
300 }
301
302 onMounted(() => {
303 if (shortLocale) {
304 weekDays.value = weekDays.value.map((day, i) => {
305 return {
306 ...day,
307 label: weekDaysShortLocale[i],
308 labelFull: weekDaysLocale[i],
309 }
310 })
311 }
312
313 let selectedDays = store.getters['recurring/getDays'].filter((day) => day.selected)
314
315 if (selectedDays.length === 0) {
316 selectWeekDay(
317 weekDays.value.find((day) => currentDay.value && day.value === currentDay.value).value,
318 true,
319 )
320 } else {
321 selectedDays.forEach((val) => {
322 selectWeekDay(val.value, true)
323 })
324 }
325
326 if (props.service.recurringCycle !== 'all') {
327 store.commit('recurring/setRepeatType', props.service.recurringCycle)
328 }
329 })
330
331 // * Colors
332 let amColors = inject('amColors')
333 let cssVars = computed(() => {
334 return {
335 '--am-c-rs-text': amColors.value.colorMainText,
336 '--am-c-rs-text-op60': useColorTransparency(amColors.value.colorMainText, 0.6),
337 '--am-c-rs-bgr': amColors.value.colorMainBgr,
338 '--am-c-rs-primary': amColors.value.colorPrimary,
339 '--am-c-rs-inp-border': amColors.value.colorInpBorder,
340 }
341 })
342 </script>
343
344 <script>
345 export default {
346 name: 'RecurringSetup',
347 }
348 </script>
349
350 <style lang="scss">
351 .amelia-v2-booking {
352 #amelia-container {
353 // rs - Recurring Step
354 .am-fs__rs {
355 & > * {
356 $count: 10;
357 @for $i from 0 through $count {
358 &:nth-child(#{$i + 1}) {
359 animation: 600ms cubic-bezier(0.45, 1, 0.4, 1.2) #{$i * 100}ms am-animation-slide-up;
360 animation-fill-mode: both;
361 }
362 }
363 }
364
365 p {
366 margin-top: 16px;
367 font-weight: 500;
368 font-size: 15px;
369 line-height: 24px;
370 /* $shade-900 */
371 color: var(--am-c-rs-text);
372 margin-bottom: 4px;
373 }
374
375 span {
376 font-weight: 400;
377 font-size: 14px;
378 line-height: 20px;
379 /* $shade-500 */
380 color: var(--am-c-rs-text-op60);
381 }
382
383 &-every {
384 display: flex;
385 .am-select {
386 width: 103px;
387 }
388 & > div {
389 &:first-child {
390 margin-right: 8px;
391 }
392 }
393 }
394
395 &-on-week {
396 display: flex;
397 width: 100%;
398 justify-content: space-between;
399 /* $shade-900 */
400 color: var(--am-c-rs-text);
401 & > div {
402 font-weight: 400;
403 font-size: 15px;
404 line-height: 24px;
405 text-align: center;
406 margin: 0;
407 cursor: pointer;
408 /* $shade-250 */
409 border: 1px solid var(--am-c-rs-inp-border);
410 box-shadow: 0 1px 1px rgba(115, 134, 169, 0.06);
411 border-radius: 4px;
412 width: 52px;
413 padding: 8px 0;
414 align-content: center;
415
416 &.am-fs__rs-on-week-selected {
417 /* $blue-900 */
418 background: var(--am-c-rs-primary);
419 /* $blue-1000 */
420 border: 1px solid var(--am-c-rs-primary);
421 /* $white */
422 color: var(--am-c-rs-bgr);
423 }
424 }
425 }
426 &-ends {
427 display: flex;
428 margin: 16px 0;
429 max-width: 100%;
430
431 &-choose {
432 .am-radio-group {
433 display: flex;
434
435 & > div:first-child {
436 margin-bottom: 16px;
437 }
438 }
439 .am-radio {
440 margin-top: 5px;
441 & > span:nth-child(2) {
442 font-size: 15px;
443 font-weight: 400;
444 line-height: 1.6;
445 word-break: keep-all;
446 /* $shade-900 */
447 color: var(--am-c-rs-text);
448 }
449 }
450 }
451
452 &-options {
453 display: flex;
454 flex-direction: column;
455 .am-input-wrapper {
456 max-width: 174px;
457 width: 100%;
458 }
459
460 & > div:first-child {
461 margin-bottom: 16px;
462 }
463 }
464
465 &-after {
466 .am-input {
467 width: 40px;
468 }
469 span {
470 font-weight: 400;
471 font-size: 15px;
472 line-height: 40px;
473 text-align: center;
474 /* $shade-900 */
475 color: var(--am-c-rs-text);
476 @media only screen and (max-width: 375px) {
477 display: flex;
478 }
479 }
480 }
481 }
482 &-summary {
483 p,
484 span {
485 font-weight: 400;
486 font-size: 14px;
487 line-height: 20px;
488 /* $shade-600 */
489 color: var(--am-c-rs-text-op60);
490 margin: 0;
491 }
492 }
493
494 &-mobile {
495 .el-radio {
496 &__label {
497 white-space: nowrap;
498 }
499 }
500
501 .am-fs__rs-every {
502 & > div {
503 max-width: 162px;
504 width: 100%;
505 }
506 & .am-select {
507 width: 100%;
508 }
509 }
510
511 .am-fs__rs-ends {
512 &-after {
513 .am-input-wrapper {
514 max-width: 40px;
515
516 .el-input__inner {
517 text-align: center;
518 padding: 6px;
519 }
520 }
521 .el-input-number {
522 max-width: 60%;
523 }
524 }
525 }
526 }
527
528 &-on-month {
529 .am-select-wrapper {
530 max-width: 180px;
531 }
532 }
533 }
534 }
535 }
536 </style>
537