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 / public / Cabinet / common / Profile / EmployeeProfile.vue
ameliabooking / v3 / src / views / public / Cabinet / common / Profile Last commit date
parts 1 month ago EmployeeProfile.vue 1 month ago Profile.vue 1 month ago
EmployeeProfile.vue
442 lines
1 <template>
2 <div
3 ref="pageContainer"
4 class="am-caep"
5 :style="cssVars"
6 role="region"
7 :aria-busy="loading ? 'true' : 'false'"
8 :aria-label="currentRegionLabel"
9 >
10 <div v-if="ready" v-show="!loading" class="am-caep__inner" :class="responsiveClass">
11 <AmAlert
12 v-if="alertVisibility"
13 :type="messageType"
14 :show-border="true"
15 :close-after="5000"
16 custom-class="am-capi__alert"
17 :role="alertA11yRole"
18 :aria-live="alertAriaLive"
19 @close="closeAlert"
20 @trigger-close="closeAlert"
21 >
22 <template #title>
23 <span class="am-icon-checkmark-circle-full" aria-hidden="true"></span> {{ message }}
24 </template>
25 </AmAlert>
26
27 <AmTabs
28 v-model="activeTab"
29 class="am-caep__tabs"
30 :aria-label="tabsAriaLabel"
31 @tab-click="tabClick"
32 >
33 <AmTabsItem class="am-caep__tabs-item" :label="amLabels.details" name="details">
34 <div class="am-caep__sr-only">{{ amLabels.details }}</div>
35 <Details ref="detailsRef" :responsive-class="responsiveClass" />
36 </AmTabsItem>
37
38 <AmTabsItem
39 v-if="amSettings.roles.allowConfigureServices"
40 class="am-caep__tabs-item"
41 :label="amLabels.heading_services"
42 name="services"
43 >
44 <div class="am-caep__sr-only">{{ amLabels.heading_services }}</div>
45 <Services :responsive-class="responsiveClass" />
46 </AmTabsItem>
47
48 <AmTabsItem
49 v-if="amSettings.roles.allowConfigureSchedule"
50 class="am-caep__tabs-item"
51 :label="amLabels.working_hours"
52 name="workingHours"
53 >
54 <div class="am-caep__sr-only">{{ amLabels.working_hours }}</div>
55 <WeekDays :responsive-class="responsiveClass" />
56 </AmTabsItem>
57
58 <AmTabsItem
59 v-if="amSettings.roles.allowConfigureSpecialDays"
60 class="am-caep__tabs-item"
61 :label="amLabels.special_days"
62 name="specialDays"
63 >
64 <div class="am-caep__sr-only">{{ amLabels.special_days }}</div>
65 <SpecialDays ref="specialDaysRef" :responsive-class="responsiveClass" />
66 </AmTabsItem>
67
68 <AmTabsItem
69 v-if="amSettings.roles.allowConfigureDaysOff"
70 class="am-caep__tabs-item"
71 :label="amLabels.days_off"
72 name="daysOff"
73 >
74 <div class="am-caep__sr-only">{{ amLabels.days_off }}</div>
75 <DaysOff ref="daysOffRef" :responsive-class="responsiveClass" />
76 </AmTabsItem>
77
78 <AmTabsItem
79 v-if="changePasswordEnabled"
80 class="am-caep__tabs-item"
81 :label="amLabels.password"
82 name="password"
83 >
84 <div class="am-caep__sr-only">{{ amLabels.password }}</div>
85 <ChangePassword v-if="!loading" ref="changePassRef" />
86 <ProfileSkeleton v-else :item-direction="'column'" :count="2"></ProfileSkeleton>
87 </AmTabsItem>
88
89 <AmTabsItem
90 v-if="
91 amSettings.zoom.enabled ||
92 amSettings.googleCalendar.enabled ||
93 amSettings.outlookCalendar.enabled ||
94 amSettings.appleCalendar.enabled ||
95 (amSettings.payments.stripe.enabled && amSettings.payments.stripe.connect.enabled)
96 "
97 class="am-caep__tabs-item"
98 :label="amLabels.integrations_settings"
99 name="integrations"
100 >
101 <div class="am-caep__sr-only">{{ amLabels.integrations_settings }}</div>
102 <EmployeeIntegrations :responsive-class="responsiveClass" />
103 </AmTabsItem>
104 </AmTabs>
105
106 <!-- Profile Footer -->
107 <ProfileFooter
108 :tab-active="activeTab"
109 :password-active="changePasswordEnabled"
110 @change-password="changePassword"
111 @save-password="savePassword"
112 @save-changes="saveEmployee"
113 />
114 <!-- /Profile Footer -->
115 </div>
116 <Skeleton v-if="!ready || loading"></Skeleton>
117 </div>
118 </template>
119
120 <script setup>
121 import AmTabs from '../../../../_components/tabs/AmTabs.vue'
122 import AmTabsItem from '../../../../_components/tabs/AmTabsItem.vue'
123 // * Import from Vue
124 import { ref, inject, computed, provide, unref } from 'vue'
125
126 import { useElementSize } from '@vueuse/core'
127
128 // * Import from Vuex
129 import { useStore } from 'vuex'
130
131 // * Import for axios
132 import httpClient from '../../../../../plugins/axios'
133
134 // * Composables
135 import { useBackendEmployee } from '../../../../../assets/js/common/employee'
136 import { useResponsiveClass } from '../../../../../assets/js/common/responsive'
137 import { useColorTransparency } from '../../../../../assets/js/common/colorManipulation'
138
139 // * Dedicated components
140 import Skeleton from '../Authentication/parts/Skeleton.vue'
141 import Details from './parts/Details.vue'
142 import Services from './parts/Services.vue'
143 import WeekDays from './parts/WeekDays.vue'
144 import SpecialDays from './parts/SpecialDays.vue'
145 import DaysOff from './parts/DaysOff.vue'
146 import ProfileFooter from './parts/ProfileFooter.vue'
147 import ChangePassword from './parts/ChangePassword.vue'
148 import AmAlert from '../../../../_components/alert/AmAlert.vue'
149 import ProfileSkeleton from './parts/ProfileSkeleton.vue'
150 import EmployeeIntegrations from './parts/Integrations/EmployeeIntegrations.vue'
151
152 // * Store
153 let store = useStore()
154
155 // * Settings
156 const amSettings = inject('settings')
157
158 // * Labels
159 let amLabels = inject('amLabels')
160
161 // * Page Content width
162 let pageContainer = ref(null)
163 const { width: pageWidth } = useElementSize(pageContainer)
164
165 let responsiveClass = computed(() => {
166 return useResponsiveClass(pageWidth.value)
167 })
168
169 provide('pageWidth', pageWidth)
170
171 let ready = computed(() => store.getters['entities/getReady'])
172
173 /********
174 * Tabs *
175 ********/
176 const activeTab = ref('details')
177
178 function integrationsTabVisible(settings) {
179 return (
180 settings.zoom?.enabled ||
181 settings.googleCalendar?.enabled ||
182 settings.outlookCalendar?.enabled ||
183 settings.appleCalendar?.enabled ||
184 (settings.payments?.stripe?.enabled && settings.payments?.stripe?.connect?.enabled)
185 )
186 }
187
188 let currentRegionLabel = computed(() => {
189 const L = unref(amLabels)
190 switch (activeTab.value) {
191 case 'services':
192 return L.heading_services
193 case 'workingHours':
194 return L.working_hours
195 case 'specialDays':
196 return L.special_days
197 case 'daysOff':
198 return L.days_off
199 case 'password':
200 return L.password
201 case 'integrations':
202 return L.integrations_settings
203 case 'details':
204 default:
205 return L.details
206 }
207 })
208
209 let tabsAriaLabel = computed(() => {
210 const L = unref(amLabels)
211 const S = unref(amSettings)
212 const parts = [L.details]
213 if (S.roles?.allowConfigureServices) {
214 parts.push(L.heading_services)
215 }
216 if (S.roles?.allowConfigureSchedule) {
217 parts.push(L.working_hours)
218 }
219 if (S.roles?.allowConfigureSpecialDays) {
220 parts.push(L.special_days)
221 }
222 if (S.roles?.allowConfigureDaysOff) {
223 parts.push(L.days_off)
224 }
225 if (changePasswordEnabled.value) {
226 parts.push(L.password)
227 }
228 if (integrationsTabVisible(S)) {
229 parts.push(L.integrations_settings)
230 }
231 return parts.join(' / ')
232 })
233
234 let alertA11yRole = computed(() => {
235 return messageType.value === 'error' ? 'alert' : 'status'
236 })
237
238 let alertAriaLive = computed(() => {
239 return messageType.value === 'error' ? 'assertive' : 'polite'
240 })
241
242 // * Success message
243 let message = ref('')
244
245 let messageType = ref('success')
246
247 // * Change Password Alert visibility
248 let alertVisibility = ref(false)
249
250 function tabClick() {
251 alertVisibility.value = false
252 }
253
254 function closeAlert() {
255 alertVisibility.value = false
256 message.value = ''
257 }
258
259 /***********
260 * Details *
261 ***********/
262 let detailsRef = ref(null)
263 let daysOffRef = ref(null)
264 let specialDaysRef = ref(null)
265
266 let employee = computed(() => {
267 return store.getters['employee/getEmployee']
268 })
269
270 let loading = ref(false)
271
272 let timeZone = inject('timeZone')
273
274 function saveEmployee() {
275 Promise.all([
276 detailsRef.value?.employeeFormRef
277 ? detailsRef.value.employeeFormRef
278 .validate()
279 .then(() => true)
280 .catch(() => false)
281 : Promise.resolve(true),
282 daysOffRef.value ? daysOffRef.value.validate() : Promise.resolve(true),
283 specialDaysRef.value ? specialDaysRef.value.validate() : Promise.resolve(true),
284 ]).then(([detailsValid, daysOffValid, specialDaysValid]) => {
285 if (!detailsValid) {
286 activeTab.value = 'details'
287 return
288 }
289
290 if (!daysOffValid) {
291 activeTab.value = 'daysOff'
292 return
293 }
294
295 if (!specialDaysValid) {
296 activeTab.value = 'specialDays'
297 return
298 }
299
300 loading.value = true
301
302 httpClient
303 .post('/users/providers/' + employee.value.id, useBackendEmployee(store, timeZone.value), {
304 params: { source: 'cabinet-provider' },
305 })
306 .then((response) => {
307 store.commit(
308 'employee/setSavedSpecialDayList',
309 JSON.parse(JSON.stringify(response.data.data.user.specialDayList)),
310 )
311 store.commit(
312 'employee/setSavedDayOffList',
313 JSON.parse(JSON.stringify(response.data.data.user.dayOffList)),
314 )
315
316 if (daysOffRef.value) daysOffRef.value.commitEditedState()
317 if (specialDaysRef.value) specialDaysRef.value.commitEditedState()
318
319 message.value = amLabels.profile_data_success
320 messageType.value = 'success'
321 })
322 .catch((error) => {
323 message.value = error.response.data.message
324 messageType.value = 'error'
325 })
326 .finally(() => {
327 alertVisibility.value = true
328 loading.value = false
329 })
330 })
331 }
332
333 /***********
334 * Password *
335 ***********/
336 let changePassRef = ref(null)
337 let changePasswordEnabled = ref(false)
338
339 function changePassword() {
340 changePasswordEnabled.value = true
341 activeTab.value = 'password'
342 }
343
344 function savePassword() {
345 changePassRef.value.passFormRef.validate((valid) => {
346 if (valid) {
347 let user = store.getters['auth/getProfile']
348
349 loading.value = true
350
351 httpClient
352 .post('/users/providers/' + user.id, { password: store.getters['auth/getNewPassword'] })
353 .then(() => {
354 message.value = amLabels.password_success
355 messageType.value = 'success'
356 alertVisibility.value = true
357
358 changePasswordEnabled.value = false
359
360 activeTab.value = 'details'
361
362 store.commit('auth/setNewPassword', '')
363 store.commit('auth/setConfirmPassword', '')
364 })
365 .catch(() => {
366 alertVisibility.value = true
367 })
368 .finally(() => {
369 loading.value = false
370 })
371 }
372 })
373 }
374
375 // * Colors
376 let amColors = inject('amColors')
377
378 let cssVars = computed(() => {
379 return {
380 '--am-c-caep-primary': amColors.value.colorPrimary,
381 '--am-c-caep-text': amColors.value.colorMainText,
382 '--am-c-caep-text-op10': useColorTransparency(amColors.value.colorMainText, 0.1),
383 }
384 })
385 </script>
386
387 <script>
388 export default {
389 name: 'CabinetEmployeeProfile',
390 }
391 </script>
392
393 <style lang="scss">
394 @mixin am-cabinet-profile {
395 // am - amelia
396 // caep - cabinet employee profile
397 .am-caep {
398 .el-skeleton {
399 padding: 32px;
400 }
401
402 &__sr-only {
403 position: absolute;
404 width: 1px;
405 height: 1px;
406 padding: 0;
407 margin: -1px;
408 overflow: hidden;
409 clip: rect(0, 0, 0, 0);
410 white-space: nowrap;
411 border: 0;
412 }
413
414 &__tabs {
415 .el-tabs__item:focus-visible {
416 box-shadow: 0 0 2px 2px var(--am-c-caep-primary) inset;
417 border-radius: 3px;
418 }
419 }
420
421 &__inner {
422 display: block;
423 padding: 16px 32px;
424
425 .el-skeleton {
426 padding-right: 32px;
427 }
428
429 &.am-rw- {
430 &480 {
431 padding: 16px;
432 }
433 }
434 }
435 }
436 }
437
438 .amelia-v2-booking #amelia-container {
439 @include am-cabinet-profile;
440 }
441 </style>
442