AddToCalendar.vue
273 lines
| 1 | <template> |
| 2 | <div v-if="settings.general.addToCalendar && booked && booked.data.length" class="am-fs-sb-cs" :style="cssVars"> |
| 3 | <p>{{amLabels.add_to_calendar}}</p> |
| 4 | <div class="am-fs-sb-cs-cals"> |
| 5 | <div v-for="(cal, index) in calendars" :key="index" class="am-fs-sb-cs-cals-cards"> |
| 6 | <a |
| 7 | v-for="calPair in cal" |
| 8 | :key="calPair.type" |
| 9 | :href="calPair.links[0]" |
| 10 | :target="(calPair.type === 'apple' || calPair.type === 'outlook') ? '_self' : '_blank'" |
| 11 | :style="{borderColor : 'var(--am-c-atc-sb-text-op10)'}" |
| 12 | class="am-fs-sb-cs-cals-card" |
| 13 | @click="executeIfMultipleLinks(calPair)" |
| 14 | > |
| 15 | <div> |
| 16 | <span :class="`am-icon-${calPair.type}`"></span> |
| 17 | </div> |
| 18 | <p :style="{color : 'var(--am-c-atc-sb-text)'}">{{ calPair.label }}</p> |
| 19 | </a> |
| 20 | </div> |
| 21 | |
| 22 | </div> |
| 23 | </div> |
| 24 | </template> |
| 25 | |
| 26 | <script setup> |
| 27 | import { computed, inject } from 'vue' |
| 28 | import { useStore } from 'vuex' |
| 29 | import { useColorTransparency } from '../../../../assets/js/common/colorManipulation' |
| 30 | |
| 31 | import { |
| 32 | settings, |
| 33 | ajaxUrl |
| 34 | } from '../../../../plugins/settings.js' |
| 35 | |
| 36 | const store = useStore() |
| 37 | |
| 38 | // * Labels |
| 39 | const amLabels = inject('amLabels') |
| 40 | |
| 41 | let ready = computed(() => store.getters['entities/getReady']) |
| 42 | |
| 43 | let booked = computed(() => store.getters['booking/getBooked']) |
| 44 | |
| 45 | let calendars = computed(() => { |
| 46 | return ready.value && booked.value ? |
| 47 | [ |
| 48 | [ |
| 49 | getCalendarLinkData(booked.value.data, 'google'), |
| 50 | { |
| 51 | type: 'outlook', |
| 52 | label: 'Outlook', |
| 53 | links: [ |
| 54 | ajaxUrl + '/bookings/ics/' + (booked.value.data.length ? booked.value.data[0].bookingId : 0) + |
| 55 | '&type=' + (booked.value.type === 'package' ? 'appointment' : booked.value.type) + getRecurringIdsParams(booked.value) + |
| 56 | '&token=' + booked.value.token |
| 57 | ] |
| 58 | } |
| 59 | ], |
| 60 | [ |
| 61 | getCalendarLinkData(booked.value.data, 'yahoo'), |
| 62 | { |
| 63 | type: 'apple', |
| 64 | label: 'Apple', |
| 65 | links: [ |
| 66 | ajaxUrl + '/bookings/ics/' + (booked.value.data.length ? booked.value.data[0].bookingId : 0) + |
| 67 | '&type=' + (booked.value.type === 'package' ? 'appointment' : booked.value.type) + getRecurringIdsParams(booked.value) + |
| 68 | '&token=' + booked.value.token |
| 69 | ] |
| 70 | } |
| 71 | ] |
| 72 | ] : [] |
| 73 | }) |
| 74 | |
| 75 | function executeIfMultipleLinks (calendar) { |
| 76 | if (calendar.links.length > 1) { |
| 77 | let popupBlocked = false |
| 78 | setTimeout(function () { |
| 79 | calendar.links.forEach(function (link, index) { |
| 80 | if (index !== 0) { |
| 81 | if (!popupBlocked) { |
| 82 | let newWin = window.open(link, '_blank') |
| 83 | try { |
| 84 | newWin.addEventListener('load', function () {}) |
| 85 | } |
| 86 | catch (e) { |
| 87 | popupBlocked = true |
| 88 | alert(amLabels.value.disable_popup_blocker) |
| 89 | } |
| 90 | } else { |
| 91 | window.open(link, '_blank') |
| 92 | } |
| 93 | } |
| 94 | }) |
| 95 | }, 1000) |
| 96 | } |
| 97 | |
| 98 | return true |
| 99 | } |
| 100 | |
| 101 | function getRecurringIdsParams (booked) { |
| 102 | let recurringIdsParams = '' |
| 103 | |
| 104 | booked.data.forEach((item, index) => { |
| 105 | if (index > 0) { |
| 106 | recurringIdsParams += '&recurring[]=' + item.bookingId |
| 107 | } |
| 108 | }) |
| 109 | |
| 110 | return recurringIdsParams |
| 111 | } |
| 112 | |
| 113 | function getCalendarLinkData(eventData, type) { |
| 114 | let links = [] |
| 115 | |
| 116 | switch (type) { |
| 117 | case ('yahoo'): |
| 118 | eventData.forEach(function (data) { |
| 119 | let location = data.locationId? |
| 120 | store.getters['entities/getLocation']( |
| 121 | data.locationId |
| 122 | ) : '' |
| 123 | |
| 124 | let address = data.cfAddress ? data.cfAddress : (location ? (location.address ? location.address : location.name) : '') |
| 125 | |
| 126 | let duration = (data.utcEnd.getTime() - data.utcStart.getTime()) / (60 * 1000) |
| 127 | |
| 128 | duration = (duration < 600 ? '0' + Math.floor((duration / 60)) : Math.floor((duration / 60)) + '') + |
| 129 | (duration % 60 < 10 ? '0' + duration % 60 : duration % 60 + '') |
| 130 | |
| 131 | let st = formatTime(new Date(data.utcStart - (data.utcStart.getTimezoneOffset() * (60 * 1000)))) |
| 132 | |
| 133 | links.push( |
| 134 | encodeURI([ |
| 135 | 'http://calendar.yahoo.com/?v=60&view=d&type=20', |
| 136 | '&title=' + (data.title || ''), |
| 137 | '&st=' + st, |
| 138 | '&dur=' + (duration || ''), |
| 139 | '&desc=' + (data.description || ''), |
| 140 | '&in_loc=' + address |
| 141 | ].join('')) |
| 142 | ) |
| 143 | }) |
| 144 | |
| 145 | return { |
| 146 | type: 'yahoo', |
| 147 | label: 'Yahoo', |
| 148 | links: links |
| 149 | } |
| 150 | |
| 151 | case ('google'): |
| 152 | eventData.forEach(function (data) { |
| 153 | let location = data.locationId? |
| 154 | store.getters['entities/getLocation']( |
| 155 | data.locationId |
| 156 | ) : '' |
| 157 | |
| 158 | let address = data.cfAddress ? data.cfAddress : (location ? (location.address ? location.address : location.name) : '') |
| 159 | |
| 160 | let startTime = formatTime(data.utcStart) |
| 161 | let endTime = formatTime(data.utcEnd) |
| 162 | |
| 163 | links.push( |
| 164 | encodeURI([ |
| 165 | 'https://www.google.com/calendar/render', |
| 166 | '?action=TEMPLATE', |
| 167 | '&text=' + (data.title || ''), |
| 168 | '&dates=' + (startTime || ''), |
| 169 | '/' + (endTime || ''), |
| 170 | '&details=' + (data.description || ''), |
| 171 | '&location=' + address, |
| 172 | '&sprop=&sprop=name:' |
| 173 | ].join('')) |
| 174 | ) |
| 175 | }) |
| 176 | |
| 177 | return { |
| 178 | type: 'google', |
| 179 | label: 'Google', |
| 180 | links: links |
| 181 | } |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | function formatTime (date) { |
| 186 | return date.toISOString().replace(/-|:|\.\d+/g, '') |
| 187 | } |
| 188 | |
| 189 | let amColors = inject('amColors') |
| 190 | |
| 191 | const cssVars = computed(() => { |
| 192 | return { |
| 193 | '--am-c-atc-sb-text-op10': useColorTransparency(amColors.value.colorSbText, 0.1), |
| 194 | '--am-c-atc-sb-text-op5': useColorTransparency(amColors.value.colorSbText, 0.05) |
| 195 | } |
| 196 | }) |
| 197 | |
| 198 | </script> |
| 199 | |
| 200 | <script> |
| 201 | export default { |
| 202 | name: 'AddToCalendar' |
| 203 | } |
| 204 | </script> |
| 205 | |
| 206 | <style lang="scss"> |
| 207 | .amelia-v2-booking #amelia-container { |
| 208 | .am-fs-sb-cs { |
| 209 | --am-c-atc-sb-text: var(--am-c-sb-text); |
| 210 | & > p { |
| 211 | text-align: center; |
| 212 | font-size: 14px; |
| 213 | line-height: 20px; |
| 214 | color: var(--am-c-atc-sb-text); |
| 215 | font-weight: 400; |
| 216 | margin-bottom: 16px; |
| 217 | } |
| 218 | &-cals{ |
| 219 | display: flex; |
| 220 | flex-wrap: wrap; |
| 221 | justify-content: center; |
| 222 | // gap: 8px; |
| 223 | &-cards { |
| 224 | display: flex; |
| 225 | flex-direction: row; |
| 226 | // gap: 8px; |
| 227 | align-items: center; |
| 228 | justify-content: center; |
| 229 | margin-bottom: 8px; |
| 230 | } |
| 231 | &-card { |
| 232 | display: flex; |
| 233 | flex-direction: column; |
| 234 | justify-content: center; |
| 235 | align-items: center; |
| 236 | width: 86px; |
| 237 | text-decoration: none; |
| 238 | color: var(--am-c-atc-sb-text); |
| 239 | border: 1px solid; |
| 240 | border-radius: 4px; |
| 241 | background-color: var(--am-c-atc-sb-text-op5); |
| 242 | padding: 16px 0 8px; |
| 243 | margin-right: 8px; |
| 244 | box-sizing: border-box; |
| 245 | box-shadow: 0 1px 1px rgba(115, 134, 169, 0.06); |
| 246 | |
| 247 | p { |
| 248 | font-style: normal; |
| 249 | font-weight: 500; |
| 250 | font-size: 15px; |
| 251 | line-height: 24px; |
| 252 | } |
| 253 | div { |
| 254 | display: flex; |
| 255 | height: 24px; |
| 256 | align-items: center; |
| 257 | span { |
| 258 | font-size: 24px; |
| 259 | color: var(--am-c-atc-sb-text); |
| 260 | } |
| 261 | .am-icon-yahoo { |
| 262 | font-size: 17px |
| 263 | } |
| 264 | } |
| 265 | &:hover { |
| 266 | background-color: var(--am-c-atc-sb-text-op10); |
| 267 | } |
| 268 | } |
| 269 | } |
| 270 | } |
| 271 | } |
| 272 | </style> |
| 273 |