Merge pull request #2293 from pateljannat/issues-218

fix: misc issues
This commit is contained in:
Jannat Patel
2026-04-06 18:55:12 +05:30
committed by GitHub
8 changed files with 218 additions and 89 deletions

View File

@@ -1,7 +1,7 @@
<template>
<div
v-if="course.title"
class="flex flex-col h-full rounded-md overflow-auto text-ink-gray-9"
class="flex flex-col h-full rounded-md overflow-auto text-ink-gray-9 bg-surface-cards"
style="min-height: 350px"
>
<div
@@ -10,7 +10,7 @@
course.image
? { backgroundImage: `url('${encodeURI(course.image)}')` }
: {
backgroundImage: getGradientColor(),
backgroundImage: gradientColor,
backgroundBlendMode: 'screen',
}
"
@@ -137,6 +137,8 @@ import { Award, BookOpen, GraduationCap, Star, Users } from 'lucide-vue-next'
import { sessionStore } from '@/stores/session'
import { Tooltip } from 'frappe-ui'
import { formatAmount } from '@/utils'
import { theme } from '@/utils/theme'
import { computed, watch } from 'vue'
import CourseInstructors from '@/components/CourseInstructors.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import ProgressBar from '@/components/ProgressBar.vue'
@@ -151,12 +153,12 @@ const props = defineProps({
},
})
const getGradientColor = () => {
let theme = localStorage.getItem('theme') == 'dark' ? 'darkMode' : 'lightMode'
const gradientColor = computed(() => {
let themeMode = theme.value === 'dark' ? 'darkMode' : 'lightMode'
let color = props.course.card_gradient?.toLowerCase() || 'blue'
let colorMap = colors[theme][color]
let colorMap = colors[themeMode][color]
return `linear-gradient(to top right, black, ${colorMap[400]})`
}
})
</script>
<style>
.course-card-pills {

View File

@@ -57,7 +57,7 @@
import { getSidebarLinks } from '@/utils'
import { useRouter } from 'vue-router'
import { call } from 'frappe-ui'
import { watch, ref, onMounted } from 'vue'
import { ref, watch } from 'vue'
import { sessionStore } from '@/stores/session'
import { useSettings } from '@/stores/settings'
import { usersStore } from '@/stores/user'
@@ -68,26 +68,13 @@ let { isLoggedIn } = sessionStore()
const { sidebarSettings } = useSettings()
const router = useRouter()
let { userResource } = usersStore()
const sidebarLinks = ref(getSidebarLinks())
const sidebarLinks = ref([])
const otherLinks = ref([])
const showMenu = ref(false)
const menu = ref(null)
const isModerator = ref(false)
const isInstructor = ref(false)
onMounted(() => {
sidebarSettings.reload(
{},
{
onSuccess(data) {
destructureSidebarLinks()
filterLinksToShow(data)
addOtherLinks()
},
}
)
})
const handleOutsideClick = (e) => {
if (menu.value && !menu.value.contains(e.target)) {
showMenu.value = false
@@ -126,65 +113,57 @@ const filterLinksToShow = (data) => {
const addOtherLinks = () => {
if (user) {
otherLinks.value.push({
label: 'Notifications',
icon: 'Bell',
to: 'Notifications',
})
otherLinks.value.push({
label: 'Profile',
icon: 'UserRound',
})
otherLinks.value.push({
label: 'Log out',
icon: 'LogOut',
})
addLink('Notifications', 'Bell', 'Notifications')
addLink('Profile', 'UserRound')
addLink('Log out', 'LogOut')
} else {
otherLinks.value.push({
label: 'Log in',
icon: 'LogIn',
})
addLink('Log in', 'LogIn')
}
}
watch(userResource, () => {
if (userResource.data) {
isModerator.value = userResource.data.is_moderator
isInstructor.value = userResource.data.is_instructor
addPrograms()
if (isModerator.value || isInstructor.value) {
addProgrammingExercises()
addQuizzes()
addAssignments()
const addLink = (label, icon, to = '') => {
if (otherLinks.value.some((link) => link.label === label)) return
otherLinks.value.push({
label: label,
icon: icon,
to: to,
})
}
const updateSidebarLinks = () => {
sidebarLinks.value = getSidebarLinks(true)
destructureSidebarLinks()
sidebarSettings.reload(
{},
{
onSuccess: async (data) => {
filterLinksToShow(data)
await addPrograms()
if (isModerator.value || isInstructor.value) {
addQuizzes()
addAssignments()
addProgrammingExercises()
}
addOtherLinks()
},
}
}
})
)
}
const addQuizzes = () => {
otherLinks.value.push({
label: 'Quizzes',
icon: 'CircleHelp',
to: 'Quizzes',
})
addLink('Quizzes', 'CircleHelp', 'Quizzes')
}
const addAssignments = () => {
otherLinks.value.push({
label: 'Assignments',
icon: 'Pencil',
to: 'Assignments',
})
addLink('Assignments', 'Pencil', 'Assignments')
}
const addProgrammingExercises = () => {
otherLinks.value.push({
label: 'Programming Exercises',
icon: 'Code',
to: 'ProgrammingExercises',
})
addLink('Programming Exercises', 'Code', 'ProgrammingExercises')
}
const addPrograms = async () => {
if (sidebarLinks.value.some((link) => link.label === 'Programs')) return
let canAddProgram = await checkIfCanAddProgram()
if (!canAddProgram) return
let activeFor = ['Programs', 'ProgramDetail']
@@ -198,7 +177,21 @@ const addPrograms = async () => {
})
}
watch(
userResource,
async () => {
await userResource.promise
if (userResource.data) {
isModerator.value = userResource.data.is_moderator
isInstructor.value = userResource.data.is_instructor
}
updateSidebarLinks()
},
{ immediate: true }
)
const checkIfCanAddProgram = async () => {
if (!userResource.data) return false
if (isModerator.value || isInstructor.value) {
return true
}

View File

@@ -68,6 +68,7 @@ import { sessionStore } from '@/stores/session'
import { call, Dropdown, toast } from 'frappe-ui'
import { useRouter } from 'vue-router'
import { convertToTitleCase } from '@/utils'
import { applyTheme, toggleTheme, theme } from '@/utils/theme'
import { usersStore } from '@/stores/user'
import { useSettings } from '@/stores/settings'
import { markRaw, watch, ref, onMounted, computed } from 'vue'
@@ -94,7 +95,6 @@ let { userResource } = usersStore()
const settingsStore = useSettings()
let { isLoggedIn } = sessionStore()
const showSettingsModal = ref(false)
const theme = ref('light')
const frappeCloudBaseEndpoint = 'https://frappecloud.com'
const $dialog = createDialog
@@ -106,9 +106,8 @@ const props = defineProps({
})
onMounted(() => {
theme.value = localStorage.getItem('theme') || 'light'
if (['light', 'dark'].includes(theme.value)) {
document.documentElement.setAttribute('data-theme', theme.value)
applyTheme(theme.value)
}
})
@@ -119,13 +118,6 @@ watch(
}
)
const toggleTheme = () => {
const currentTheme = document.documentElement.getAttribute('data-theme')
theme.value = currentTheme === 'dark' ? 'light' : 'dark'
document.documentElement.setAttribute('data-theme', theme.value)
localStorage.setItem('theme', theme.value)
}
const userDropdownOptions = computed(() => {
return [
{

View File

@@ -403,8 +403,8 @@ export function getUserTimezone() {
}
}
export function getSidebarLinks() {
let links = getSidebarItems()
export function getSidebarLinks(forMobile = false) {
let links = getSidebarItems(forMobile)
links.forEach((link) => {
link.items = link.items.filter((item) => {
@@ -419,7 +419,7 @@ export function getSidebarLinks() {
return links
}
const getSidebarItems = () => {
const getSidebarItems = (forMobile = false) => {
const { userResource } = usersStore()
const { settings } = useSettings()
@@ -441,7 +441,7 @@ const getSidebarItems = () => {
icon: 'Search',
to: 'Search',
condition: () => {
return userResource?.data
return !forMobile && userResource?.data
},
},
{
@@ -449,7 +449,7 @@ const getSidebarItems = () => {
icon: 'Bell',
to: 'Notifications',
condition: () => {
return userResource?.data
return !forMobile && userResource?.data
},
},
],
@@ -476,7 +476,7 @@ const getSidebarItems = () => {
activeFor: ['Programs', 'ProgramDetail'],
await: true,
condition: () => {
return checkIfCanAddProgram()
return checkIfCanAddProgram(forMobile)
},
},
{
@@ -514,7 +514,8 @@ const getSidebarItems = () => {
: settings.data?.contact_us_email,
condition: () => {
return (
(settings?.data?.contact_us_email &&
(!forMobile &&
settings?.data?.contact_us_email &&
userResource?.data) ||
settings?.data?.contact_us_url
)
@@ -531,7 +532,7 @@ const getSidebarItems = () => {
icon: 'CircleHelp',
to: 'Quizzes',
condition: () => {
return isAdmin()
return !forMobile && isAdmin()
},
activeFor: [
'Quizzes',
@@ -546,7 +547,7 @@ const getSidebarItems = () => {
icon: 'Pencil',
to: 'Assignments',
condition: () => {
return isAdmin()
return !forMobile && isAdmin()
},
activeFor: [
'Assignments',
@@ -559,7 +560,7 @@ const getSidebarItems = () => {
icon: 'Code',
to: 'ProgrammingExercises',
condition: () => {
return isAdmin()
return !forMobile && isAdmin()
},
activeFor: [
'ProgrammingExercises',
@@ -581,10 +582,11 @@ const isAdmin = () => {
)
}
const checkIfCanAddProgram = () => {
const checkIfCanAddProgram = (forMobile = false) => {
const { userResource } = usersStore()
const { programs } = useSettings()
if (!userResource.data) return false
if (forMobile) return false
if (userResource?.data?.is_moderator || userResource?.data?.is_instructor) {
return true
}

View File

@@ -0,0 +1,16 @@
import { ref } from 'vue'
const theme = ref<'light' | 'dark'>(localStorage.getItem('theme') as 'light' | 'dark' || 'light')
const toggleTheme = () => {
const newTheme: 'light' | 'dark' = theme.value === 'dark' ? 'light' : 'dark'
applyTheme(newTheme)
}
const applyTheme = (value: 'light' | 'dark') => {
document.documentElement.setAttribute('data-theme', value)
localStorage.setItem('theme', value)
theme.value = value
}
export { applyTheme, toggleTheme, theme }

View File

@@ -35,10 +35,10 @@ class LMSCertificate(Document):
custom_template = frappe.db.get_single_value("LMS Settings", "certification_template")
args = {
"student_name": self.member_name,
"member_name": self.member_name,
"course_name": self.course,
"course_title": frappe.db.get_value("LMS Course", self.course, "title"),
"certificate_name": self.name,
"name": self.name,
"template": self.template,
}

View File

@@ -1,5 +1,5 @@
<p>
{{ _("Dear ") }} {{ student_name }},
{{ _("Dear ") }} {{ member_name }},
</p>
<br>
<p>
@@ -10,7 +10,7 @@
{{ _("With this certification, you can now showcase your updated skills and share your achievement with your colleagues and on LinkedIn. To access your certificate, please click on the link provided below. Make sure you are logged in to the portal.") }}
</p>
<br>
<a href="/api/method/frappe.utils.print_format.download_pdf?doctype=LMS+Certificate&name={{certificate_name}}&format={{template | urlencode }}">{{ _("Certificate Link") }}</a>
<a href="/api/method/frappe.utils.print_format.download_pdf?doctype=LMS+Certificate&name={{name}}&format={{template | urlencode }}">{{ _("Certificate Link") }}</a>
<br>
<p>
{{ _("Once again, congratulations on this significant accomplishment.")}}

View File

@@ -0,0 +1,124 @@
{
"app": "lms",
"creation": "2026-04-06 18:02:13.124002",
"docstatus": 0,
"doctype": "Workspace Sidebar",
"header_icon": "book",
"idx": 0,
"items": [
{
"child": 0,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Courses",
"link_to": "LMS Course",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Enrollments",
"link_to": "LMS Enrollment",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Course Reviews",
"link_to": "LMS Course Review",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Batches",
"link_to": "LMS Batch",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Batch Enrollments",
"link_to": "LMS Batch Enrollment",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Batch Feedback",
"link_to": "LMS Batch Feedback",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Evaluation Requests",
"link_to": "LMS Certificate Request",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Evaluations",
"link_to": "LMS Certificate Evaluation",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Certificates",
"link_to": "LMS Certificate",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
}
],
"modified": "2026-04-06 18:04:32.990958",
"modified_by": "sayali@frappe.io",
"name": "Learning",
"owner": "sayali@frappe.io",
"standard": 1,
"title": "Learning"
}