mirror of
https://github.com/frappe/lms.git
synced 2026-04-19 22:52:29 +03:00
728 lines
18 KiB
Vue
728 lines
18 KiB
Vue
<template>
|
|
<div
|
|
class="flex h-full flex-col justify-between transition-all duration-300 ease-in-out border-e bg-surface-menu-bar"
|
|
:class="sidebarStore.isSidebarCollapsed ? 'w-14' : 'w-56'"
|
|
>
|
|
<div
|
|
class="flex flex-col overflow-y-auto"
|
|
:class="sidebarStore.isSidebarCollapsed ? 'items-center' : ''"
|
|
>
|
|
<UserDropdown :isCollapsed="sidebarStore.isSidebarCollapsed" />
|
|
<div class="flex flex-col overflow-y-auto" v-if="sidebarSettings.data">
|
|
<div v-for="link in sidebarLinks" class="mx-2 my-2.5">
|
|
<div
|
|
v-if="!link.hideLabel"
|
|
class="mb-2 mt-3 flex cursor-pointer gap-1.5 px-1 text-base font-medium text-ink-gray-5 transition-all duration-300 ease-in-out"
|
|
>
|
|
<span>{{ __(link.label) }}</span>
|
|
</div>
|
|
<nav class="space-y-1">
|
|
<div v-for="item in link.items">
|
|
<SidebarLink
|
|
:link="item"
|
|
:isCollapsed="sidebarStore.isSidebarCollapsed"
|
|
/>
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-if="sidebarSettings.data?.web_pages?.length || isModerator"
|
|
class="mt-4"
|
|
>
|
|
<div
|
|
class="flex items-center justify-between pe-2 cursor-pointer"
|
|
:class="sidebarStore.isSidebarCollapsed ? 'ps-3' : 'ps-4'"
|
|
@click="toggleWebPages"
|
|
>
|
|
<div
|
|
v-if="!sidebarStore.isSidebarCollapsed"
|
|
class="flex items-center text-ink-gray-5 my-1"
|
|
>
|
|
<span class="grid h-5 w-6 flex-shrink-0 place-items-center">
|
|
<ChevronRight
|
|
class="h-4 w-4 stroke-1.5 text-ink-gray-9 transition-all duration-300 ease-in-out"
|
|
:class="{
|
|
'rotate-90': sidebarStore.isWebpagesCollapsed,
|
|
'rtl:rotate-180': !sidebarStore.isWebpagesCollapsed,
|
|
}"
|
|
/>
|
|
</span>
|
|
<span class="ms-2">
|
|
{{ __('More') }}
|
|
</span>
|
|
</div>
|
|
<Button
|
|
v-if="isModerator && !readOnlyMode"
|
|
variant="ghost"
|
|
@click="openPageModal()"
|
|
>
|
|
<template #icon>
|
|
<Plus class="h-4 w-4 text-ink-gray-7 stroke-1.5" />
|
|
</template>
|
|
</Button>
|
|
</div>
|
|
<div
|
|
v-if="sidebarSettings.data?.web_pages?.length"
|
|
class="flex flex-col transition-all duration-300 ease-in-out"
|
|
:class="!sidebarStore.isWebpagesCollapsed ? 'block' : 'hidden'"
|
|
>
|
|
<div
|
|
v-for="link in sidebarSettings.data.web_pages"
|
|
class="mx-2 my-0.5"
|
|
>
|
|
<SidebarLink
|
|
:link="link"
|
|
:isCollapsed="sidebarStore.isSidebarCollapsed"
|
|
:showControls="isModerator ? true : false"
|
|
@openModal="openPageModal"
|
|
@deletePage="deletePage"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="m-2 flex flex-col gap-1">
|
|
<div
|
|
v-if="readOnlyMode && !sidebarStore.isSidebarCollapsed"
|
|
class="z-10 m-2 bg-surface-modal py-2.5 px-3 text-xs text-ink-gray-7 leading-5 rounded-md"
|
|
>
|
|
{{
|
|
__(
|
|
'This site is being updated. You will not be able to make any changes. Full access will be restored shortly.'
|
|
)
|
|
}}
|
|
</div>
|
|
<div
|
|
v-if="
|
|
isStudent && !profileIsComplete && !sidebarStore.isSidebarCollapsed
|
|
"
|
|
class="flex flex-col gap-3 text-ink-gray-9 py-2.5 px-3 bg-surface-white shadow-sm rounded-md"
|
|
>
|
|
<div class="flex flex-col text-p-sm gap-1">
|
|
<div class="inline-flex gap-1">
|
|
<User class="h-4 my-0.5 shrink-0" />
|
|
<div class="font-medium">
|
|
{{ __('Complete your profile') }}
|
|
</div>
|
|
</div>
|
|
<div class="text-ink-gray-7 leading-5">
|
|
{{ __('Highlight what makes you unique and show your skills.') }}
|
|
</div>
|
|
</div>
|
|
<router-link
|
|
:to="{
|
|
name: 'Profile',
|
|
params: {
|
|
username: userResource.data?.username,
|
|
},
|
|
}"
|
|
>
|
|
<Button :label="__('My Profile')" class="w-full">
|
|
<template #prefix>
|
|
<ChevronsRight class="h-4 w-4 text-ink-gray-7 stroke-1.5" />
|
|
</template>
|
|
</Button>
|
|
</router-link>
|
|
</div>
|
|
<Tooltip
|
|
v-if="
|
|
isStudent && !profileIsComplete && sidebarStore.isSidebarCollapsed
|
|
"
|
|
:text="__('Complete your profile')"
|
|
>
|
|
<router-link
|
|
:to="{
|
|
name: 'Profile',
|
|
params: {
|
|
username: userResource.data?.username,
|
|
},
|
|
}"
|
|
class="flex items-center justify-center"
|
|
>
|
|
<User class="size-4 stroke-1.5 text-ink-gray-7 cursor-pointer" />
|
|
</router-link>
|
|
</Tooltip>
|
|
<TrialBanner
|
|
v-if="
|
|
userResource.data?.is_system_manager && userResource.data?.is_fc_site
|
|
"
|
|
:isSidebarCollapsed="sidebarStore.isSidebarCollapsed"
|
|
/>
|
|
<GettingStartedBanner
|
|
v-if="showOnboarding && !isOnboardingStepsCompleted"
|
|
:isSidebarCollapsed="sidebarStore.isSidebarCollapsed"
|
|
appName="learning"
|
|
/>
|
|
|
|
<div
|
|
class="flex items-center mt-4"
|
|
:class="
|
|
sidebarStore.isSidebarCollapsed ? 'flex-col space-y-3' : 'flex-row'
|
|
"
|
|
>
|
|
<div
|
|
class="flex items-center flex-1 gap-3"
|
|
:class="sidebarStore.isSidebarCollapsed ? 'flex-col' : 'flex-row'"
|
|
>
|
|
<Tooltip v-if="readOnlyMode && sidebarStore.isSidebarCollapsed">
|
|
<CircleAlert
|
|
class="size-4 stroke-1.5 text-ink-gray-7 cursor-pointer"
|
|
/>
|
|
<template #body>
|
|
<div
|
|
class="max-w-[30ch] rounded bg-surface-gray-7 px-2 py-1 text-center text-p-xs text-ink-white shadow-xl"
|
|
>
|
|
{{
|
|
__(
|
|
'This site is being updated. You will not be able to make any changes. Full access will be restored shortly.'
|
|
)
|
|
}}
|
|
</div>
|
|
</template>
|
|
</Tooltip>
|
|
<Tooltip
|
|
v-if="showAppointmentIcon"
|
|
:text="__('Book a free onboarding session with the Frappe team')"
|
|
>
|
|
<Phone
|
|
class="size-4 stroke-1.5 text-ink-gray-7 cursor-pointer"
|
|
@click="redirectToAppointmentScreen()"
|
|
/>
|
|
</Tooltip>
|
|
<Tooltip v-if="showOnboarding" :text="__('Help')">
|
|
<CircleHelp
|
|
class="size-4 stroke-1.5 text-ink-gray-7 cursor-pointer"
|
|
@click="
|
|
() => {
|
|
showHelpModal = minimize ? true : !showHelpModal
|
|
minimize = !showHelpModal
|
|
}
|
|
"
|
|
/>
|
|
</Tooltip>
|
|
<Tooltip :text="__('Powered by Frappe Learning')">
|
|
<Zap
|
|
class="size-4 stroke-1.5 text-ink-gray-7 cursor-pointer"
|
|
@click="redirectToWebsite()"
|
|
/>
|
|
</Tooltip>
|
|
</div>
|
|
<Tooltip
|
|
:text="
|
|
sidebarStore.isSidebarCollapsed ? __('Expand') : __('Collapse')
|
|
"
|
|
>
|
|
<CollapseSidebar
|
|
class="size-4 text-ink-gray-7 duration-300 stroke-1.5 ease-in-out cursor-pointer"
|
|
:style="{
|
|
transform:
|
|
isRtl !== sidebarStore.isSidebarCollapsed
|
|
? 'rotateY(180deg)'
|
|
: '',
|
|
}"
|
|
@click="toggleSidebar()"
|
|
/>
|
|
</Tooltip>
|
|
</div>
|
|
</div>
|
|
<HelpModal
|
|
v-if="showOnboarding && showHelpModal"
|
|
v-model="showHelpModal"
|
|
v-model:articles="articles"
|
|
appName="learning"
|
|
title="Frappe Learning"
|
|
:logo="LMSLogo"
|
|
:afterSkip="(step) => capture('onboarding_step_skipped_' + step)"
|
|
:afterSkipAll="() => capture('onboarding_steps_skipped')"
|
|
:afterReset="(step) => capture('onboarding_step_reset_' + step)"
|
|
:afterResetAll="() => capture('onboarding_steps_reset')"
|
|
docsLink="https://docs.frappe.io/learning"
|
|
/>
|
|
<IntermediateStepModal
|
|
v-model="showIntermediateModal"
|
|
:currentStep="currentStep"
|
|
/>
|
|
</div>
|
|
<CommandPalette v-model="settingsStore.isCommandPaletteOpen" />
|
|
<PageModal
|
|
v-model="showPageModal"
|
|
v-model:reloadSidebar="sidebarSettings"
|
|
:page="pageToEdit"
|
|
/>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { getSidebarLinks } from '@/utils'
|
|
import { usersStore } from '@/stores/user'
|
|
import { sessionStore } from '@/stores/session'
|
|
import { useSidebar } from '@/stores/sidebar'
|
|
import { useSettings } from '@/stores/settings'
|
|
import { Button, call, createResource, Tooltip, toast } from 'frappe-ui'
|
|
import PageModal from '@/components/Modals/PageModal.vue'
|
|
import LMSLogo from '@/components/Icons/LMSLogo.vue'
|
|
import { useRouter } from 'vue-router'
|
|
import {
|
|
ref,
|
|
onMounted,
|
|
inject,
|
|
watch,
|
|
reactive,
|
|
markRaw,
|
|
h,
|
|
onUnmounted,
|
|
computed,
|
|
} from 'vue'
|
|
import {
|
|
BookOpen,
|
|
CircleAlert,
|
|
ChevronRight,
|
|
ChevronsRight,
|
|
CircleHelp,
|
|
FolderTree,
|
|
FileText,
|
|
Phone,
|
|
Plus,
|
|
User,
|
|
UserPlus,
|
|
Users,
|
|
BookText,
|
|
Zap,
|
|
} from 'lucide-vue-next'
|
|
import {
|
|
TrialBanner,
|
|
HelpModal,
|
|
GettingStartedBanner,
|
|
useOnboarding,
|
|
showHelpModal,
|
|
minimize,
|
|
IntermediateStepModal,
|
|
useTelemetry,
|
|
} from 'frappe-ui/frappe'
|
|
import InviteIcon from '@/components/Icons/InviteIcon.vue'
|
|
import UserDropdown from '@/components/Sidebar/UserDropdown.vue'
|
|
import CollapseSidebar from '@/components/Icons/CollapseSidebar.vue'
|
|
import SidebarLink from '@/components/Sidebar/SidebarLink.vue'
|
|
import CommandPalette from '@/components/CommandPalette/CommandPalette.vue'
|
|
|
|
const { user } = sessionStore()
|
|
const { userResource } = usersStore()
|
|
let sidebarStore = useSidebar()
|
|
const socket = inject('$socket')
|
|
const unreadCount = ref(0)
|
|
const sidebarLinks = ref(null)
|
|
const { capture } = useTelemetry()
|
|
const showPageModal = ref(false)
|
|
const isModerator = ref(false)
|
|
const isInstructor = ref(false)
|
|
const pageToEdit = ref(null)
|
|
const { sidebarSettings, activeTab, isSettingsOpen, programs } = useSettings()
|
|
const settingsStore = useSettings()
|
|
const showOnboarding = ref(false)
|
|
const showIntermediateModal = ref(false)
|
|
const currentStep = ref({})
|
|
const router = useRouter()
|
|
let onboardingDetails
|
|
let isOnboardingStepsCompleted = false
|
|
const readOnlyMode = window.read_only_mode
|
|
const isRtl = document.documentElement.dir === 'rtl'
|
|
const iconProps = {
|
|
strokeWidth: 1.5,
|
|
width: 16,
|
|
height: 16,
|
|
}
|
|
|
|
onMounted(() => {
|
|
setUpOnboarding()
|
|
addKeyboardShortcut()
|
|
updateSidebarLinks()
|
|
socket.on('publish_lms_notifications', (data) => {
|
|
unreadNotifications.reload()
|
|
})
|
|
})
|
|
|
|
const updateSidebarLinksVisibility = () => {
|
|
sidebarSettings.reload(
|
|
{},
|
|
{
|
|
onSuccess(data) {
|
|
Object.keys(data).forEach((key) => {
|
|
if (!parseInt(data[key])) {
|
|
sidebarLinks.value.forEach((link) => {
|
|
link.items = link.items.filter(
|
|
(item) => item.label.toLowerCase().split(' ').join('_') !== key
|
|
)
|
|
})
|
|
}
|
|
})
|
|
},
|
|
}
|
|
)
|
|
}
|
|
|
|
const addKeyboardShortcut = () => {
|
|
window.addEventListener('keydown', (e) => {
|
|
if (
|
|
e.key === 'k' &&
|
|
(e.ctrlKey || e.metaKey) &&
|
|
!e.target.classList.contains('ProseMirror')
|
|
) {
|
|
toggleCommandPalette()
|
|
e.preventDefault()
|
|
}
|
|
})
|
|
}
|
|
|
|
const toggleCommandPalette = () => {
|
|
settingsStore.isCommandPaletteOpen = !settingsStore.isCommandPaletteOpen
|
|
}
|
|
|
|
const unreadNotifications = createResource({
|
|
cache: 'Unread Notifications Count',
|
|
url: 'frappe.client.get_count',
|
|
makeParams(values) {
|
|
return {
|
|
doctype: 'Notification Log',
|
|
filters: {
|
|
for_user: user,
|
|
read: 0,
|
|
},
|
|
}
|
|
},
|
|
onSuccess(data) {
|
|
unreadCount.value = data
|
|
updateUnreadCount()
|
|
},
|
|
auto: user ? true : false,
|
|
})
|
|
|
|
const updateUnreadCount = () => {
|
|
sidebarLinks.value?.forEach((link) => {
|
|
link.items.forEach((item) => {
|
|
if (item.label === 'Notifications') {
|
|
item.count = unreadCount.value || 0
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
const openPageModal = (link) => {
|
|
showPageModal.value = true
|
|
pageToEdit.value = link
|
|
}
|
|
|
|
const deletePage = (link) => {
|
|
call('lms.lms.api.delete_documents', {
|
|
doctype: 'LMS Sidebar Item',
|
|
documents: [link.name],
|
|
}).then(() => {
|
|
sidebarSettings.reload()
|
|
toast.success(__('Page deleted successfully'))
|
|
})
|
|
}
|
|
|
|
const toggleSidebar = () => {
|
|
sidebarStore.isSidebarCollapsed = !sidebarStore.isSidebarCollapsed
|
|
localStorage.setItem(
|
|
'isSidebarCollapsed',
|
|
JSON.stringify(sidebarStore.isSidebarCollapsed)
|
|
)
|
|
}
|
|
|
|
const toggleWebPages = () => {
|
|
sidebarStore.isWebpagesCollapsed = !sidebarStore.isWebpagesCollapsed
|
|
localStorage.setItem(
|
|
'isWebpagesCollapsed',
|
|
JSON.stringify(sidebarStore.isWebpagesCollapsed)
|
|
)
|
|
}
|
|
|
|
const getFirstCourse = async () => {
|
|
let firstCourse = localStorage.getItem('firstCourse')
|
|
if (firstCourse) return firstCourse
|
|
return await call('lms.lms.onboarding.get_first_course')
|
|
}
|
|
|
|
const getFirstBatch = async () => {
|
|
let firstBatch = localStorage.getItem('firstBatch')
|
|
if (firstBatch) return firstBatch
|
|
return await call('lms.lms.onboarding.get_first_batch')
|
|
}
|
|
|
|
const steps = reactive([
|
|
{
|
|
name: 'create_first_course',
|
|
title: __('Create your first course'),
|
|
icon: markRaw(h(BookOpen, iconProps)),
|
|
completed: false,
|
|
onClick: () => {
|
|
minimize.value = true
|
|
router.push({
|
|
name: 'Courses',
|
|
})
|
|
},
|
|
},
|
|
{
|
|
name: 'create_first_chapter',
|
|
title: __('Add your first chapter'),
|
|
icon: markRaw(h(FolderTree, iconProps)),
|
|
completed: false,
|
|
dependsOn: 'create_first_course',
|
|
onClick: async () => {
|
|
minimize.value = true
|
|
let course = await getFirstCourse()
|
|
if (course) {
|
|
router.push({
|
|
name: 'CourseDetail',
|
|
params: { courseName: course },
|
|
hash: '#settings',
|
|
})
|
|
} else {
|
|
router.push({ name: 'Courses', query: { newCourse: '1' } })
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'create_first_lesson',
|
|
title: __('Add your first lesson'),
|
|
icon: markRaw(h(FileText, iconProps)),
|
|
completed: false,
|
|
dependsOn: 'create_first_chapter',
|
|
onClick: async () => {
|
|
minimize.value = true
|
|
let course = await getFirstCourse()
|
|
if (course) {
|
|
router.push({
|
|
name: 'CourseDetail',
|
|
params: { courseName: course },
|
|
hash: '#settings',
|
|
})
|
|
} else {
|
|
router.push({ name: 'Courses', query: { newCourse: '1' } })
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'create_first_quiz',
|
|
title: __('Create your first quiz'),
|
|
icon: markRaw(h(CircleHelp, iconProps)),
|
|
completed: false,
|
|
dependsOn: 'create_first_course',
|
|
onClick: () => {
|
|
minimize.value = true
|
|
router.push({ name: 'Quizzes' })
|
|
},
|
|
},
|
|
{
|
|
name: 'invite_students',
|
|
title: __('Invite your team and students'),
|
|
icon: markRaw(h(InviteIcon, iconProps)),
|
|
completed: false,
|
|
onClick: () => {
|
|
minimize.value = true
|
|
activeTab.value = 'Members'
|
|
isSettingsOpen.value = true
|
|
},
|
|
},
|
|
{
|
|
name: 'create_first_batch',
|
|
title: __('Create your first batch'),
|
|
icon: markRaw(h(Users, iconProps)),
|
|
completed: false,
|
|
onClick: () => {
|
|
minimize.value = true
|
|
router.push({ name: 'Batches' })
|
|
},
|
|
},
|
|
{
|
|
name: 'add_batch_student',
|
|
title: __('Add students to your batch'),
|
|
icon: markRaw(h(UserPlus, iconProps)),
|
|
completed: false,
|
|
dependsOn: 'create_first_batch',
|
|
onClick: async () => {
|
|
minimize.value = true
|
|
let batch = await getFirstBatch()
|
|
if (batch) {
|
|
router.push({
|
|
name: 'Batch',
|
|
params: {
|
|
batchName: batch,
|
|
},
|
|
})
|
|
} else {
|
|
router.push({ name: 'Batch' })
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'add_batch_course',
|
|
title: __('Add courses to your batch'),
|
|
icon: markRaw(h(BookText, iconProps)),
|
|
completed: false,
|
|
dependsOn: 'create_first_batch',
|
|
onClick: async () => {
|
|
minimize.value = true
|
|
let batch = await getFirstBatch()
|
|
if (batch) {
|
|
router.push({
|
|
name: 'Batch',
|
|
params: {
|
|
batchName: batch,
|
|
},
|
|
hash: '#courses',
|
|
})
|
|
} else {
|
|
router.push({ name: 'Batch' })
|
|
}
|
|
},
|
|
},
|
|
])
|
|
|
|
const articles = ref([
|
|
{
|
|
title: __('Introduction'),
|
|
opened: false,
|
|
subArticles: [
|
|
{ name: 'introduction', title: __('Introduction') },
|
|
{ name: 'setting-up', title: __('Setting up') },
|
|
],
|
|
},
|
|
{
|
|
title: __('Creating a course'),
|
|
opened: false,
|
|
subArticles: [
|
|
{ name: 'create-a-course', title: __('Create a course') },
|
|
{ name: 'add-a-chapter', title: __('Add a chapter') },
|
|
{ name: 'add-a-lesson', title: __('Add a lesson') },
|
|
],
|
|
},
|
|
{
|
|
title: __('Creating a batch'),
|
|
opened: false,
|
|
subArticles: [
|
|
{ name: 'create-a-batch', title: __('Create a batch') },
|
|
{ name: 'create-a-live-class', title: __('Create a live class') },
|
|
],
|
|
},
|
|
{
|
|
title: __('Learning Paths'),
|
|
opened: false,
|
|
subArticles: [{ name: 'add-a-program', title: __('Add a program') }],
|
|
},
|
|
{
|
|
title: __('Assessments'),
|
|
opened: false,
|
|
subArticles: [
|
|
{ name: 'quizzes', title: __('Quizzes') },
|
|
{ name: 'assignments', title: __('Assignments') },
|
|
],
|
|
},
|
|
{
|
|
title: __('Certification'),
|
|
opened: false,
|
|
subArticles: [
|
|
{ name: 'issue-a-certificate', title: __('Issue a Certificate') },
|
|
{
|
|
name: 'custom-certificate-templates',
|
|
title: __('Custom Certificate Templates'),
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: __('Monetization'),
|
|
opened: false,
|
|
subArticles: [
|
|
{
|
|
name: 'setting-up-payment-gateway',
|
|
title: __('Setting up payment gateway'),
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: __('Settings'),
|
|
opened: false,
|
|
subArticles: [{ name: 'roles', title: __('Roles') }],
|
|
},
|
|
])
|
|
|
|
const setUpOnboarding = () => {
|
|
if (userResource.data?.is_system_manager) {
|
|
onboardingDetails = useOnboarding('learning')
|
|
onboardingDetails.setUp(steps)
|
|
isOnboardingStepsCompleted = onboardingDetails.isOnboardingStepsCompleted
|
|
showOnboarding.value = true
|
|
}
|
|
}
|
|
|
|
watch(userResource, async () => {
|
|
await userResource.promise
|
|
if (userResource.data) {
|
|
isModerator.value = userResource.data.is_moderator
|
|
isInstructor.value = userResource.data.is_instructor
|
|
await programs.reload()
|
|
setUpOnboarding()
|
|
}
|
|
updateSidebarLinks()
|
|
})
|
|
|
|
watch(settingsStore.settings, () => {
|
|
updateSidebarLinks()
|
|
})
|
|
|
|
const updateSidebarLinks = () => {
|
|
sidebarLinks.value = getSidebarLinks()
|
|
updateSidebarLinksVisibility()
|
|
}
|
|
|
|
const redirectToWebsite = () => {
|
|
window.open('https://frappe.io/learning', '_blank')
|
|
}
|
|
|
|
const isStudent = computed(() => {
|
|
return userResource.data?.is_student
|
|
})
|
|
|
|
const profileIsComplete = computed(() => {
|
|
return (
|
|
userResource.data?.user_image &&
|
|
userResource.data?.headline &&
|
|
userResource.data?.bio
|
|
)
|
|
})
|
|
|
|
const showAppointmentIcon = computed(() => {
|
|
let isTrialPlan = userResource.data?.site_info?.plan?.is_trial_plan
|
|
let trialEndDate = calculateTrialEndDays(
|
|
userResource.data?.site_info?.trial_end_date
|
|
)
|
|
return (
|
|
userResource.data?.is_system_manager &&
|
|
userResource.data?.is_fc_site &&
|
|
isTrialPlan &&
|
|
trialEndDate > 0
|
|
)
|
|
})
|
|
|
|
const calculateTrialEndDays = (trialEndDate) => {
|
|
if (!trialEndDate) return 0
|
|
|
|
trialEndDate = new Date(trialEndDate)
|
|
const today = new Date()
|
|
const diffTime = trialEndDate - today
|
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
|
return diffDays
|
|
}
|
|
|
|
const redirectToAppointmentScreen = () => {
|
|
window.open(
|
|
'https://calendar.google.com/calendar/u/0/appointments/schedules/AcZssZ0c7Z3XIpW1WgbeIuktSaoX6qudoYuSdRbIlJty5TW7p4IZaOk5viHQGwTNi6HpNVqzOZOTHcle',
|
|
'_blank'
|
|
)
|
|
}
|
|
|
|
onUnmounted(() => {
|
|
socket.off('publish_lms_notifications')
|
|
})
|
|
</script>
|