Merge pull request #2108 from pateljannat/issues-185
fix: misc permission issues
This commit is contained in:
@@ -107,7 +107,7 @@
|
||||
<div class="flex flex-col gap-1 p-1">
|
||||
<div class="text-base font-medium text-ink-gray-8">
|
||||
{{
|
||||
option.value == option.label
|
||||
option.value == option.label && option.description
|
||||
? option.description
|
||||
: option.label
|
||||
}}
|
||||
@@ -124,7 +124,7 @@
|
||||
v-if="groups.length == 0"
|
||||
class="mt-1.5 rounded-md px-2.5 py-1.5 text-base text-ink-gray-5"
|
||||
>
|
||||
No results found
|
||||
{{ __('No results found') }}
|
||||
</li>
|
||||
</ComboboxOptions>
|
||||
<div v-if="slots.footer" class="border-t p-1.5 pb-0.5">
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
'border object-cover',
|
||||
shape === 'circle'
|
||||
? 'w-20 h-20 rounded-full'
|
||||
: 'w-44 h-auto min-h-20 rounded-md',
|
||||
: 'w-44 h-auto min-h-20 max-h-32 rounded-md',
|
||||
]"
|
||||
/>
|
||||
<video v-else controls class="border rounded-md w-44 h-auto">
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</div>
|
||||
<div class="grid gap-8 mt-10">
|
||||
<div v-for="(review, index) in reviews.data">
|
||||
<div class="flex items-center">
|
||||
<div class="flex">
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'Profile',
|
||||
@@ -46,11 +46,11 @@
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="review.review" class="mt-4 leading-5 text-ink-gray-7">
|
||||
{{ review.review }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="review.review" class="mt-4 leading-5 text-ink-gray-7">
|
||||
{{ review.review }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
v-model="show"
|
||||
:options="{
|
||||
size: '4xl',
|
||||
title: __('Video Statistics for {0}').format(lessonTitle),
|
||||
title: __('Video Statistics'),
|
||||
}"
|
||||
>
|
||||
<template #body-content>
|
||||
@@ -21,17 +21,22 @@
|
||||
class="mt-2 mr-5 w-[25%]"
|
||||
/> -->
|
||||
</div>
|
||||
<div v-if="currentTab" class="mt-4">
|
||||
<div
|
||||
v-if="currentTab"
|
||||
:class="{
|
||||
'mt-5': tabs.length > 1,
|
||||
}"
|
||||
>
|
||||
<div class="grid grid-cols-[55%,40%] gap-5">
|
||||
<div
|
||||
class="space-y-5 border rounded-md p-2 pt-4 h-[70vh] overflow-y-auto"
|
||||
class="space-y-5 border rounded-md p-2 pt-4 max-h-[70vh] overflow-y-auto"
|
||||
>
|
||||
<div class="grid grid-cols-[70%,30%] text-sm text-ink-gray-5">
|
||||
<div class="px-4">
|
||||
{{ __('Member') }}
|
||||
</div>
|
||||
<div class="text-center">
|
||||
{{ __('Watch Time') }}
|
||||
{{ __('Watch Time (mins)') }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -68,15 +73,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-5">
|
||||
<NumberChart
|
||||
class="border rounded-md"
|
||||
:config="{
|
||||
title: __('Average Watch Time'),
|
||||
value: averageWatchTime,
|
||||
}"
|
||||
<NumberChartGraph
|
||||
:title="__('Average Watch Time (mins)')"
|
||||
:value="averageWatchTime"
|
||||
/>
|
||||
<div v-if="isPlyrSource">
|
||||
<div class="video-player" :src="currentTab"></div>
|
||||
<div
|
||||
class="video-player"
|
||||
:data-plyr-provider="provider"
|
||||
:src="currentTab"
|
||||
></div>
|
||||
</div>
|
||||
<VideoBlock v-else :file="currentTab" />
|
||||
</div>
|
||||
@@ -101,6 +107,7 @@ import {
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { enablePlyr, formatTimestamp } from '@/utils'
|
||||
import VideoBlock from '@/components/VideoBlock.vue'
|
||||
import NumberChartGraph from '@/components/NumberChartGraph.vue'
|
||||
|
||||
const show = defineModel<boolean | undefined>()
|
||||
const currentTab = ref<string>('')
|
||||
@@ -171,7 +178,7 @@ watch(show, () => {
|
||||
|
||||
const statisticsData = computed(() => {
|
||||
const grouped = <Record<string, any[]>>{}
|
||||
statistics.data.forEach((item: { source: string }) => {
|
||||
statistics.data?.forEach((item: { source: string }) => {
|
||||
if (!grouped[item.source]) {
|
||||
grouped[item.source] = []
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ const referenceDoctypeOptions = computed(() => {
|
||||
})
|
||||
|
||||
const eventOptions = computed(() => {
|
||||
let options = ['New', 'Value Change', 'Auto Assign']
|
||||
let options = ['New', 'Value Change', 'Manual Assignment']
|
||||
return options.map((event) => ({ label: __(event), value: event }))
|
||||
})
|
||||
|
||||
|
||||
@@ -6,16 +6,18 @@
|
||||
<div class="text-xl font-semibold leading-none text-ink-gray-9">
|
||||
{{ __(label) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Badge
|
||||
v-if="data.isDirty"
|
||||
:label="__('Not Saved')"
|
||||
variant="subtle"
|
||||
theme="orange"
|
||||
/>
|
||||
<Button variant="solid" :loading="data.save.loading" @click="update">
|
||||
{{ __('Update') }}
|
||||
</Button>
|
||||
</div>
|
||||
<Button variant="solid" :loading="data.save.loading" @click="update">
|
||||
{{ __('Update') }}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="text-ink-gray-6 leading-5">
|
||||
{{ __(description) }}
|
||||
|
||||
@@ -219,6 +219,25 @@ const tabsStructure = computed(() => {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Jobs',
|
||||
columns: [
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
label: 'Allow Job Posting',
|
||||
name: 'allow_job_posting',
|
||||
type: 'checkbox',
|
||||
description:
|
||||
'If enabled, users can post job openings on the job board. Else only admins can post jobs.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '',
|
||||
columns: [
|
||||
|
||||
@@ -31,12 +31,14 @@
|
||||
<div v-if="upcoming_evals.data?.length">
|
||||
<div
|
||||
class="grid gap-4"
|
||||
:class="forHome ? 'grid-cols-1 md:grid-cols-2' : 'grid-cols-1'"
|
||||
:class="forHome ? 'grid-cols-1 md:grid-cols-4' : 'grid-cols-1'"
|
||||
>
|
||||
<div v-for="evl in upcoming_evals.data">
|
||||
<div class="border text-ink-gray-7 rounded-md p-3">
|
||||
<div
|
||||
class="border hover:border-outline-gray-3 text-ink-gray-7 rounded-md p-3"
|
||||
>
|
||||
<div class="flex justify-between mb-3">
|
||||
<span class="text-lg font-semibold text-ink-gray-9 leading-5">
|
||||
<span class="font-semibold text-ink-gray-9 leading-5">
|
||||
{{ evl.course_title }}
|
||||
</span>
|
||||
<Menu
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
<template>
|
||||
<div v-if="badge.data">
|
||||
<div class="p-5 flex flex-col items-center mt-40">
|
||||
<div class="text-3xl font-semibold">
|
||||
{{ badge.data.badge }}
|
||||
</div>
|
||||
<img
|
||||
:src="badge.data.badge_image"
|
||||
:alt="badge.data.badge"
|
||||
class="h-60 mt-2"
|
||||
/>
|
||||
<div class="">
|
||||
{{
|
||||
__('This badge has been awarded to {0} on {1}.').format(
|
||||
badge.data.member_name,
|
||||
dayjs(badge.data.issued_on).format('DD MMM YYYY')
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
{{ badge.data.badge_description }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { createResource, usePageMeta } from 'frappe-ui'
|
||||
import { computed, inject } from 'vue'
|
||||
import { sessionStore } from '../stores/session'
|
||||
|
||||
const dayjs = inject('$dayjs')
|
||||
const { brand } = sessionStore()
|
||||
|
||||
const props = defineProps({
|
||||
badgeName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const badge = createResource({
|
||||
url: 'frappe.client.get',
|
||||
makeParams(values) {
|
||||
return {
|
||||
doctype: 'LMS Badge Assignment',
|
||||
filters: {
|
||||
badge: props.badgeName,
|
||||
member: props.email,
|
||||
},
|
||||
}
|
||||
},
|
||||
auto: true,
|
||||
})
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: __('Badges'),
|
||||
},
|
||||
{
|
||||
label: badge.data.badge,
|
||||
route: {
|
||||
name: 'Badge',
|
||||
params: {
|
||||
badge: badge.data.badge,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
usePageMeta(() => {
|
||||
return {
|
||||
title: badge.data.badge,
|
||||
icon: brand.favicon,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -43,7 +43,7 @@
|
||||
}
|
||||
"
|
||||
>
|
||||
<div class="font-semibold text-ink-gray-9 text-lg mb-1">
|
||||
<div class="font-semibold text-ink-gray-9 mb-1">
|
||||
{{ cls.title }}
|
||||
</div>
|
||||
<div class="short-introduction">
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-10 md:gap-5 mt-10">
|
||||
<div class="mt-10 space-y-10">
|
||||
<UpcomingEvaluations :forHome="true" />
|
||||
<div v-if="myLiveClasses.data?.length">
|
||||
<div class="font-semibold text-lg mb-3 text-ink-gray-9">
|
||||
{{ __('Upcoming Live Classes') }}
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-5">
|
||||
<div
|
||||
v-for="cls in myLiveClasses.data"
|
||||
class="border rounded-md hover:border-outline-gray-3 p-2"
|
||||
class="border rounded-md hover:border-outline-gray-3 p-3"
|
||||
>
|
||||
<div class="font-semibold text-ink-gray-9 text-lg leading-5 mb-1">
|
||||
<div class="font-semibold text-ink-gray-9 leading-5 mb-1">
|
||||
{{ cls.title }}
|
||||
</div>
|
||||
<div class="text-ink-gray-5 leading-5 mb-4">
|
||||
|
||||
@@ -4,9 +4,14 @@
|
||||
class="sticky top-0 z-10 flex items-center justify-between border-b bg-surface-white px-3 py-2.5 sm:px-5"
|
||||
>
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
<Button variant="solid" @click="saveJob()">
|
||||
{{ __('Save') }}
|
||||
</Button>
|
||||
<div class="space-x-2">
|
||||
<Badge v-if="isDirty" theme="orange">
|
||||
{{ __('Not Saved') }}
|
||||
</Badge>
|
||||
<Button variant="solid" @click="saveJob()">
|
||||
{{ __('Save') }}
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="py-5">
|
||||
<div class="container border-b mb-4 pb-5">
|
||||
@@ -109,15 +114,25 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import {
|
||||
Badge,
|
||||
Breadcrumbs,
|
||||
call,
|
||||
FormControl,
|
||||
createResource,
|
||||
createDocumentResource,
|
||||
Button,
|
||||
TextEditor,
|
||||
usePageMeta,
|
||||
toast,
|
||||
} from 'frappe-ui'
|
||||
import { computed, onMounted, reactive, inject } from 'vue'
|
||||
import {
|
||||
computed,
|
||||
inject,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
reactive,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue'
|
||||
import { sessionStore } from '@/stores/session'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { escapeHTML, sanitizeHTML } from '@/utils'
|
||||
@@ -126,6 +141,8 @@ import Uploader from '@/components/Controls/Uploader.vue'
|
||||
const user = inject('$user')
|
||||
const router = useRouter()
|
||||
const { brand } = sessionStore()
|
||||
const isDirty = ref(false)
|
||||
const originalJobData = ref(null)
|
||||
|
||||
const props = defineProps({
|
||||
jobName: {
|
||||
@@ -134,67 +151,6 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const newJob = createResource({
|
||||
url: 'frappe.client.insert',
|
||||
makeParams(values) {
|
||||
return {
|
||||
doc: {
|
||||
doctype: 'Job Opportunity',
|
||||
company_logo: job.company_logo,
|
||||
...job,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const updateJob = createResource({
|
||||
url: 'frappe.client.set_value',
|
||||
makeParams(values) {
|
||||
return {
|
||||
doctype: 'Job Opportunity',
|
||||
name: props.jobName,
|
||||
fieldname: {
|
||||
company_logo: job.company_logo,
|
||||
...job,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const jobDetail = createResource({
|
||||
url: 'frappe.client.get',
|
||||
makeParams(values) {
|
||||
return {
|
||||
doctype: 'Job Opportunity',
|
||||
name: props.jobName,
|
||||
}
|
||||
},
|
||||
onSuccess(data) {
|
||||
if (data.owner != user.data?.name && !user.data?.is_moderator) {
|
||||
router.push({
|
||||
name: 'Jobs',
|
||||
})
|
||||
}
|
||||
Object.keys(data).forEach((key) => {
|
||||
if (Object.hasOwn(job, key)) job[key] = data[key]
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const job = reactive({
|
||||
job_title: '',
|
||||
location: '',
|
||||
country: '',
|
||||
type: 'Full Time',
|
||||
work_mode: 'On-site',
|
||||
status: 'Open',
|
||||
company_name: '',
|
||||
company_website: '',
|
||||
company_logo: null,
|
||||
description: '',
|
||||
company_email_address: '',
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (!user.data) {
|
||||
router.push({
|
||||
@@ -202,22 +158,64 @@ onMounted(() => {
|
||||
})
|
||||
}
|
||||
|
||||
if (props.jobName != 'new') jobDetail.reload()
|
||||
addKeyboardShortcuts()
|
||||
if (props.jobName != 'new') jobDetails.reload()
|
||||
window.addEventListener('keydown', keyboardShortcut)
|
||||
})
|
||||
|
||||
const addKeyboardShortcuts = () => {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 's') {
|
||||
e.preventDefault()
|
||||
saveJob()
|
||||
const job = reactive({
|
||||
job_title: '',
|
||||
type: '',
|
||||
work_mode: '',
|
||||
location: '',
|
||||
country: '',
|
||||
status: 'Open',
|
||||
description: '',
|
||||
company_name: '',
|
||||
company_website: '',
|
||||
company_email_address: '',
|
||||
company_logo: '',
|
||||
})
|
||||
|
||||
const jobDetails = createDocumentResource({
|
||||
doctype: 'Job Opportunity',
|
||||
name: props.jobName != 'new' ? props.jobName : undefined,
|
||||
onError(err) {
|
||||
toast.error(err.messages?.[0] || err)
|
||||
console.error(err)
|
||||
},
|
||||
auto: props.jobName != 'new',
|
||||
})
|
||||
|
||||
watch(
|
||||
() => jobDetails?.doc,
|
||||
() => {
|
||||
if (!jobDetails.doc) return
|
||||
if (jobDetails.doc.owner != user.data?.name && !user.data?.is_moderator) {
|
||||
router.push({
|
||||
name: 'Jobs',
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (jobDetails.doc) {
|
||||
Object.assign(job, jobDetails.doc)
|
||||
originalJobData.value = JSON.parse(JSON.stringify(jobDetails.doc))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
job,
|
||||
() => {
|
||||
isDirty.value = Object.keys(job).some((key) => {
|
||||
return job[key] != originalJobData.value?.[key]
|
||||
})
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const saveJob = () => {
|
||||
validateJobFields()
|
||||
if (jobDetail.data) {
|
||||
if (jobDetails?.doc) {
|
||||
editJobDetails()
|
||||
} else {
|
||||
createNewJob()
|
||||
@@ -225,38 +223,46 @@ const saveJob = () => {
|
||||
}
|
||||
|
||||
const createNewJob = () => {
|
||||
newJob.submit(
|
||||
{},
|
||||
{
|
||||
onSuccess(data) {
|
||||
router.push({
|
||||
name: 'JobDetail',
|
||||
params: {
|
||||
job: data.name,
|
||||
},
|
||||
})
|
||||
},
|
||||
onError(err) {
|
||||
toast.error(err.messages?.[0] || err)
|
||||
},
|
||||
}
|
||||
)
|
||||
call('frappe.client.insert', {
|
||||
doc: {
|
||||
doctype: 'Job Opportunity',
|
||||
company_logo: job.company_logo,
|
||||
...job,
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
router.push({
|
||||
name: 'JobDetail',
|
||||
params: {
|
||||
job: data.name,
|
||||
},
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err.messages?.[0] || err)
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
const editJobDetails = () => {
|
||||
updateJob.submit(
|
||||
{},
|
||||
jobDetails.setValue.submit(
|
||||
{
|
||||
company_logo: job.company_logo,
|
||||
...job,
|
||||
},
|
||||
{
|
||||
onSuccess(data) {
|
||||
jobDetails.reload()
|
||||
router.push({
|
||||
name: 'JobDetail',
|
||||
params: {
|
||||
job: data.name,
|
||||
job: props.jobName,
|
||||
},
|
||||
})
|
||||
},
|
||||
onError(err) {
|
||||
toast.error(err.messages?.[0] || err)
|
||||
console.error(err)
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -271,27 +277,38 @@ const validateJobFields = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const keyboardShortcut = (e) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 's') {
|
||||
e.preventDefault()
|
||||
saveJob()
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', keyboardShortcut)
|
||||
})
|
||||
|
||||
const jobTypes = computed(() => {
|
||||
return [
|
||||
{ label: 'Full Time', value: 'Full Time' },
|
||||
{ label: 'Part Time', value: 'Part Time' },
|
||||
{ label: 'Contract', value: 'Contract' },
|
||||
{ label: 'Freelance', value: 'Freelance' },
|
||||
{ label: __('Full Time'), value: 'Full Time' },
|
||||
{ label: __('Part Time'), value: 'Part Time' },
|
||||
{ label: __('Contract'), value: 'Contract' },
|
||||
{ label: __('Freelance'), value: 'Freelance' },
|
||||
]
|
||||
})
|
||||
|
||||
const workModes = computed(() => {
|
||||
return [
|
||||
{ label: 'On site', value: 'On-site' },
|
||||
{ label: 'Hybrid', value: 'Hybrid' },
|
||||
{ label: 'Remote', value: 'Remote' },
|
||||
{ label: __('On site'), value: 'On-site' },
|
||||
{ label: __('Hybrid'), value: 'Hybrid' },
|
||||
{ label: __('Remote'), value: 'Remote' },
|
||||
]
|
||||
})
|
||||
|
||||
const jobStatuses = computed(() => {
|
||||
return [
|
||||
{ label: 'Open', value: 'Open' },
|
||||
{ label: 'Closed', value: 'Closed' },
|
||||
{ label: __('Open'), value: 'Open' },
|
||||
{ label: __('Closed'), value: 'Closed' },
|
||||
]
|
||||
})
|
||||
|
||||
@@ -302,8 +319,11 @@ const breadcrumbs = computed(() => {
|
||||
route: { name: 'Jobs' },
|
||||
},
|
||||
{
|
||||
label: props.jobName == 'new' ? __('New Job') : __('Edit Job'),
|
||||
route: { name: 'JobForm' },
|
||||
label: props.jobName == 'new' ? __('New Job') : jobDetails.doc?.job_title,
|
||||
route:
|
||||
props.jobName == 'new'
|
||||
? {}
|
||||
: { name: 'JobDetail', params: { job: props.jobName } },
|
||||
},
|
||||
]
|
||||
return crumbs
|
||||
@@ -311,7 +331,7 @@ const breadcrumbs = computed(() => {
|
||||
|
||||
usePageMeta(() => {
|
||||
return {
|
||||
title: props.jobName == 'new' ? __('New Job') : jobDetail.data?.job_title,
|
||||
title: props.jobName == 'new' ? __('New Job') : jobDetails.doc?.job_title,
|
||||
icon: brand.favicon,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
:items="[{ label: __('Jobs'), route: { name: 'Jobs' } }]"
|
||||
/>
|
||||
<router-link
|
||||
v-if="user.data?.name"
|
||||
v-if="
|
||||
user.data?.name && settings.data?.allow_job_posting && !readOnlyMode
|
||||
"
|
||||
:to="{
|
||||
name: 'JobForm',
|
||||
params: {
|
||||
@@ -16,7 +18,7 @@
|
||||
},
|
||||
}"
|
||||
>
|
||||
<Button v-if="!readOnlyMode" variant="solid">
|
||||
<Button variant="solid">
|
||||
<template #prefix>
|
||||
<Plus class="h-4 w-4" />
|
||||
</template>
|
||||
@@ -123,7 +125,8 @@ import {
|
||||
usePageMeta,
|
||||
} from 'frappe-ui'
|
||||
import { Plus, Search } from 'lucide-vue-next'
|
||||
import { sessionStore } from '../stores/session'
|
||||
import { sessionStore } from '@/stores/session'
|
||||
import { useSettings } from '@/stores/settings'
|
||||
import { inject, computed, ref, onMounted, watch } from 'vue'
|
||||
import JobCard from '@/components/JobCard.vue'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
@@ -133,6 +136,7 @@ const user = inject('$user')
|
||||
const jobType = ref(null)
|
||||
const workMode = ref(null)
|
||||
const { brand } = sessionStore()
|
||||
const { settings } = useSettings()
|
||||
const searchQuery = ref('')
|
||||
const country = ref(null)
|
||||
const filters = ref({})
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</template>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button v-if="canSeeStats()" @click="showVideoStats()">
|
||||
<Button v-if="isAdmin" @click="showVideoStats()">
|
||||
<template #icon>
|
||||
<TrendingUp class="size-4 stroke-1.5" />
|
||||
</template>
|
||||
@@ -326,7 +326,7 @@
|
||||
@updateNotes="updateNotes"
|
||||
/>
|
||||
<VideoStatistics
|
||||
v-if="showStatsDialog"
|
||||
v-if="isAdmin"
|
||||
v-model="showStatsDialog"
|
||||
:lessonName="lesson.data?.name"
|
||||
:lessonTitle="lesson.data?.title"
|
||||
@@ -524,7 +524,14 @@ const renderEditor = (holder, content) => {
|
||||
|
||||
const markProgress = () => {
|
||||
if (user.data && lesson.data && !lesson.data.progress) {
|
||||
progress.submit()
|
||||
progress.submit(
|
||||
{},
|
||||
{
|
||||
onError(err) {
|
||||
console.error(err)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,7 +612,6 @@ watch(
|
||||
plyrSources.value = []
|
||||
await nextTick()
|
||||
resetLessonState(newChapterNumber, newLessonNumber)
|
||||
startTimer()
|
||||
updateNotes()
|
||||
checkIfDiscussionsAllowed()
|
||||
checkQuiz()
|
||||
@@ -674,6 +680,7 @@ watch(
|
||||
() => lesson.data,
|
||||
async (data) => {
|
||||
setupLesson(data)
|
||||
startTimer()
|
||||
getPlyrSource()
|
||||
updateNotes()
|
||||
if (data.icon == 'icon-youtube') clearInterval(timerInterval)
|
||||
@@ -769,17 +776,19 @@ const checkIfDiscussionsAllowed = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const isAdmin = computed(() => {
|
||||
let isInstructor = lesson.data?.instructors?.includes(user.data?.name)
|
||||
return user.data?.is_moderator || isInstructor
|
||||
})
|
||||
|
||||
const allowEdit = () => {
|
||||
if (window.read_only_mode) return false
|
||||
if (user.data?.is_moderator) return true
|
||||
if (lesson.data?.instructors?.includes(user.data?.name)) return true
|
||||
if (isAdmin.value) return true
|
||||
return false
|
||||
}
|
||||
|
||||
const allowInstructorContent = () => {
|
||||
if (user.data?.is_moderator) return true
|
||||
if (lesson.data?.instructors?.includes(user.data?.name)) return true
|
||||
return false
|
||||
return isAdmin.value
|
||||
}
|
||||
|
||||
const enrollment = createResource({
|
||||
@@ -819,11 +828,6 @@ const toggleInlineMenu = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const canSeeStats = () => {
|
||||
if (user.data?.is_moderator || user.data?.is_instructor) return true
|
||||
return false
|
||||
}
|
||||
|
||||
const showVideoStats = () => {
|
||||
showStatsDialog.value = true
|
||||
}
|
||||
|
||||
@@ -70,13 +70,16 @@
|
||||
<div class="leading-5 mb-4">
|
||||
{{ badge.badge_description }}
|
||||
</div>
|
||||
<div class="flex flex-col mb-4">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xs text-ink-gray-7 font-medium mb-1">
|
||||
{{ __('Issued on') }}:
|
||||
</span>
|
||||
{{ dayjs(badge.issued_on).format('DD MMM YYYY') }}
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
v-if="user.data?.name == profile.data?.name"
|
||||
class="flex flex-col mt-4"
|
||||
>
|
||||
<span class="text-xs text-ink-gray-7 font-medium mb-1">
|
||||
{{ __('Share on') }}:
|
||||
</span>
|
||||
@@ -125,6 +128,7 @@ import DOMPurify from 'dompurify'
|
||||
import { getLmsRoute } from '@/utils/basePath'
|
||||
|
||||
const dayjs = inject('$dayjs')
|
||||
const user = inject('$user')
|
||||
const { branding } = sessionStore()
|
||||
|
||||
const props = defineProps({
|
||||
@@ -135,13 +139,9 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const badges = createResource({
|
||||
url: 'frappe.client.get_list',
|
||||
url: 'lms.lms.api.get_badges',
|
||||
params: {
|
||||
doctype: 'LMS Badge Assignment',
|
||||
fields: ['name', 'badge', 'badge_image', 'badge_description', 'issued_on'],
|
||||
filters: {
|
||||
member: props.profile.data.name,
|
||||
},
|
||||
member: props.profile.data.name,
|
||||
},
|
||||
auto: true,
|
||||
transform(data) {
|
||||
@@ -160,14 +160,16 @@ const shareOnSocial = (badge, medium) => {
|
||||
let shareUrl
|
||||
const url = encodeURIComponent(
|
||||
`${window.location.origin}${getLmsRoute(
|
||||
`badges/${badge.badge}/${props.profile.data?.email}`
|
||||
`user/${props.profile.data?.username}`
|
||||
)}`
|
||||
)
|
||||
const summary = `I am happy to announce that I earned the ${
|
||||
badge.badge
|
||||
} badge on ${dayjs(badge.issued_on).format('DD MMM YYYY')} at ${
|
||||
const summary = __(
|
||||
'I am happy to announce that I earned the {0} badge on {1} at {2}'
|
||||
).format(
|
||||
badge.badge,
|
||||
dayjs(badge.issued_on).format('DD MMM YYYY'),
|
||||
branding.data?.app_name
|
||||
}.`
|
||||
)
|
||||
|
||||
if (medium == 'LinkedIn')
|
||||
shareUrl = `https://www.linkedin.com/shareArticle?mini=true&url=${url}&text=${summary}`
|
||||
|
||||
@@ -139,12 +139,6 @@ const routes = [
|
||||
name: 'Notifications',
|
||||
component: () => import('@/pages/Notifications.vue'),
|
||||
},
|
||||
{
|
||||
path: '/badges/:badgeName/:email',
|
||||
name: 'Badge',
|
||||
component: () => import('@/pages/Badge.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: '/quizzes',
|
||||
name: 'Quizzes',
|
||||
|
||||
Reference in New Issue
Block a user