feat: google calendar integration

This commit is contained in:
Jannat Patel
2024-04-16 16:56:18 +05:30
parent 719e471678
commit 9252920a79
7 changed files with 249 additions and 64 deletions

View File

@@ -6,63 +6,73 @@
>
<Breadcrumbs class="h-7" :items="breadcrumbs" />
</header>
<div class="relative">
<div class="group relative h-[130px] w-full">
<img
v-if="profile.data.cover_image"
:src="profile.data.cover_image"
class="h-[130px] w-full"
class="h-[130px] w-full object-cover object-center"
/>
<div
v-else
:class="{ 'bg-gray-50': !profile.data.cover_image }"
:class="{ 'bg-gray-100': !profile.data.cover_image }"
class="h-[130px] w-full"
></div>
<div class="absolute top-0 right-0" v-if="isSessionUser()">
<Button variant="outline">
<template #icon>
<Edit class="w-4 h-4 stroke-1.5 text-gray-700" />
<div
class="absolute bottom-0 left-1/2 mb-4 flex -translate-x-1/2 space-x-2 opacity-0 transition-opacity focus-within:opacity-100 group-hover:opacity-100"
v-if="isSessionUser()"
>
<EditCoverImage
@select="(imageUrl) => coverImage.submit({ url: imageUrl })"
>
<template v-slot="{ togglePopover }">
<Button variant="outline" @click="togglePopover()">
<template #prefix>
<Edit class="w-4 h-4 stroke-1.5 text-gray-700" />
</template>
{{ __('Edit') }}
</Button>
</template>
</Button>
</EditCoverImage>
</div>
<div class="mx-auto -mt-4 max-w-4xl translate-x-0 sm:px-5">
<div class="flex items-center">
<div>
<img
v-if="profile.data.user_image"
:src="profile.data.user_image"
class="object-cover h-[100px] w-[100px] rounded-full border-4 border-white object-cover"
/>
<UserAvatar
v-else
:user="profile.data"
class="object-cover h-[100px] w-[100px] rounded-full border-4 border-white object-cover"
/>
</div>
<div class="ml-6">
<h2 class="mt-2 text-3xl font-semibold text-gray-900">
{{ profile.data.full_name }}
</h2>
<div class="mt-2 text-base text-gray-700">
{{ profile.data.headline }}
</div>
</div>
<Button v-if="isSessionUser()" class="ml-auto" @click="editProfile()">
<template #prefix>
<Edit class="w-4 h-4 stroke-1.5 text-gray-700" />
</template>
{{ __('Edit Profile') }}
</Button>
</div>
<div class="mb-4 mt-6">
<TabButtons
class="inline-block"
:buttons="getTabButtons()"
v-model="activeTab"
</div>
<div class="mx-auto -mt-4 max-w-4xl translate-x-0 sm:px-5">
<div class="flex items-center">
<div>
<img
v-if="profile.data.user_image"
:src="profile.data.user_image"
class="object-cover h-[100px] w-[100px] rounded-full border-4 border-white object-cover"
/>
<UserAvatar
v-else
:user="profile.data"
class="object-cover h-[100px] w-[100px] rounded-full border-4 border-white object-cover"
/>
</div>
<router-view :profile="profile" />
<div class="ml-6">
<h2 class="mt-2 text-3xl font-semibold text-gray-900">
{{ profile.data.full_name }}
</h2>
<div class="mt-2 text-base text-gray-700">
{{ profile.data.headline }}
</div>
</div>
<Button v-if="isSessionUser()" class="ml-auto" @click="editProfile()">
<template #prefix>
<Edit class="w-4 h-4 stroke-1.5 text-gray-700" />
</template>
{{ __('Edit Profile') }}
</Button>
</div>
<div class="mb-4 mt-6">
<TabButtons
class="inline-block"
:buttons="getTabButtons()"
v-model="activeTab"
/>
</div>
<router-view :profile="profile" />
</div>
</div>
<EditProfile
@@ -70,7 +80,6 @@
v-model:reloadProfile="profile"
:profile="profile"
/>
<EditCoverImage />
</template>
<script setup>
import { Breadcrumbs, createResource, Button, TabButtons } from 'frappe-ui'
@@ -114,6 +123,21 @@ const profile = createResource({
},
})
const coverImage = createResource({
url: 'frappe.client.set_value',
makeParams(values) {
return {
doctype: 'User',
name: profile.data?.name,
fieldname: 'cover_image',
value: values.url,
}
},
onSuccess() {
profile.reload()
},
})
const setActiveTab = () => {
let fragments = route.path.split('/')
let sections = ['certificates', 'roles', 'evaluations']

View File

@@ -4,7 +4,7 @@
{{ __('My availability') }}
</h2>
<div class="w-3/4">
<div class="">
<div class="grid grid-cols-4 gap-4 text-sm text-gray-700 mb-4">
<div>
{{ __('Day') }}
@@ -18,8 +18,8 @@
</div>
<div
v-if="slots.data"
v-for="slot in slots.data.schedule"
v-if="evaluator.data"
v-for="slot in evaluator.data.slots.schedule"
class="grid grid-cols-4 gap-4 mb-4 group"
>
<FormControl
@@ -70,7 +70,7 @@
{{ __('Add Slot') }}
</Button>
</div>
<div class="mt-10 w-3/4">
<div class="my-10">
<h2 class="mb-4 text-lg font-semibold text-gray-900">
{{ __('I am unavailable') }}
</h2>
@@ -103,13 +103,28 @@
/>
</div>
</div>
<div>
<h2 class="mb-4 text-lg font-semibold text-gray-900">
{{ __('My calendar') }}
</h2>
<div
v-if="evaluator.data?.calendar && evaluator.data?.is_authorized"
class="flex items-center bg-green-100 text-green-900 text-sm p-1 rounded-md mb-4 w-fit"
>
<Check class="h-4 w-4 stroke-1.5 mr-2" />
{{ __('Your calendar is set.') }}
</div>
<Button @click="() => authorizeCalendar.submit()">
{{ __('Authorize Google Calendar Access') }}
</Button>
</div>
</div>
</template>
<script setup>
import { createResource, FormControl, Button } from 'frappe-ui'
import { computed, reactive, ref } from 'vue'
import { showToast, convertToTitleCase } from '@/utils'
import { Plus, X } from 'lucide-vue-next'
import { Plus, X, Check } from 'lucide-vue-next'
const props = defineProps({
profile: {
@@ -128,12 +143,16 @@ const newSlot = reactive({
end_time: '',
})
const slots = createResource({
const evaluator = createResource({
url: 'lms.lms.api.get_evaluator_details',
params: {
evaluator: props.profile.data?.name,
},
auto: true,
onSuccess(data) {
if (data.slots.unavailable_from) from.value = data.slots.unavailable_from
if (data.slots.unavailable_to) to.value = data.slots.unavailable_to
},
})
const createSlot = createResource({
@@ -142,7 +161,7 @@ const createSlot = createResource({
return {
doc: {
doctype: 'Evaluator Schedule',
parent: slots.data?.name,
parent: evaluator.data?.slots.name,
parentfield: 'schedule',
parenttype: 'Course Evaluator',
...newSlot,
@@ -151,7 +170,7 @@ const createSlot = createResource({
},
onSuccess() {
showToast('Success', 'Slot added successfully', 'check')
slots.reload()
evaluator.reload()
showSlotsTemplate.value = 0
newSlot.day = ''
newSlot.start_time = ''
@@ -190,7 +209,7 @@ const deleteSlot = createResource({
},
onSuccess() {
showToast('Success', 'Slot deleted successfully', 'check')
slots.reload()
evaluator.reload()
},
onError(err) {
showToast('Error', err.messages?.[0] || err, 'x')
@@ -202,7 +221,7 @@ const updateUnavailability = createResource({
makeParams(values) {
return {
doctype: 'Course Evaluator',
name: slots.data?.name,
name: evaluator.data?.slots.name,
fieldname: values.field,
value: values.value,
}
@@ -243,6 +262,19 @@ const deleteRow = (name) => {
deleteSlot.submit({ name })
}
const authorizeCalendar = createResource({
url: 'frappe.integrations.doctype.google_calendar.google_calendar.authorize_access',
makeParams() {
return {
g_calendar: evaluator.data?.calendar,
reauthorize: 1,
}
},
onSuccess(data) {
window.open(data.url)
},
})
const days = computed(() => {
return [
{