chore: merge conflicts
This commit is contained in:
41
.github/workflows/linters.yml
vendored
41
.github/workflows/linters.yml
vendored
@@ -4,30 +4,45 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main, develop ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
linters:
|
|
||||||
name: Semantic Commits
|
commit-lint:
|
||||||
|
name: 'Semantic Commits'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 200
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Check commit titles
|
||||||
uses: actions/setup-python@v4
|
run: |
|
||||||
|
npm install @commitlint/cli @commitlint/config-conventional
|
||||||
|
npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
|
linters:
|
||||||
|
name: Semgrep Rules
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: '3.10'
|
python-version: '3.10'
|
||||||
|
cache: pip
|
||||||
- name: Install and Run Pre-commit
|
|
||||||
uses: pre-commit/action@v2.0.3
|
|
||||||
|
|
||||||
- name: Download Semgrep rules
|
- name: Download Semgrep rules
|
||||||
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
|
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
|
||||||
|
|
||||||
- name: Download semgrep
|
|
||||||
run: pip install semgrep
|
|
||||||
|
|
||||||
- name: Run Semgrep rules
|
- name: Run Semgrep rules
|
||||||
run: semgrep ci --config ./frappe-semgrep-rules/rules
|
run: |
|
||||||
|
pip install semgrep
|
||||||
|
semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
|
||||||
|
|||||||
Submodule frappe-ui updated: c5faaae38e...5c0513c2df
@@ -21,7 +21,7 @@
|
|||||||
"chart.js": "^4.4.1",
|
"chart.js": "^4.4.1",
|
||||||
"dayjs": "^1.11.6",
|
"dayjs": "^1.11.6",
|
||||||
"feather-icons": "^4.28.0",
|
"feather-icons": "^4.28.0",
|
||||||
"frappe-ui": "^0.1.50",
|
"frappe-ui": "^0.1.54",
|
||||||
"lucide-vue-next": "^0.309.0",
|
"lucide-vue-next": "^0.309.0",
|
||||||
"markdown-it": "^14.0.0",
|
"markdown-it": "^14.0.0",
|
||||||
"pinia": "^2.0.33",
|
"pinia": "^2.0.33",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<ListView
|
<ListView
|
||||||
:columns="getCoursesColumns()"
|
:columns="getCoursesColumns()"
|
||||||
:rows="courses.data"
|
:rows="courses.data"
|
||||||
row-key="name"
|
row-key="batch_course"
|
||||||
:options="{ showTooltip: false }"
|
:options="{ showTooltip: false }"
|
||||||
>
|
>
|
||||||
<ListHeader
|
<ListHeader
|
||||||
@@ -49,7 +49,10 @@
|
|||||||
<ListSelectBanner>
|
<ListSelectBanner>
|
||||||
<template #actions="{ unselectAll, selections }">
|
<template #actions="{ unselectAll, selections }">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Button variant="ghost" @click="removeCourses(selections)">
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
@click="removeCourses(selections, unselectAll)"
|
||||||
|
>
|
||||||
<Trash2 class="h-4 w-4 stroke-1.5" />
|
<Trash2 class="h-4 w-4 stroke-1.5" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -133,11 +136,13 @@ const removeCourse = createResource({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const removeCourses = (selections) => {
|
const removeCourses = (selections, unselectAll) => {
|
||||||
selections.forEach(async (course) => {
|
selections.forEach(async (course) => {
|
||||||
removeCourse.submit({ course })
|
removeCourse.submit({ course })
|
||||||
await setTimeout(1000)
|
|
||||||
})
|
})
|
||||||
courses.reload()
|
setTimeout(() => {
|
||||||
|
courses.reload()
|
||||||
|
unselectAll()
|
||||||
|
}, 1000)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="batch.data" class="shadow rounded-md p-5" style="width: 300px">
|
<div v-if="batch.data" class="shadow rounded-md p-5 lg:w-72">
|
||||||
<Badge
|
<Badge
|
||||||
v-if="batch.data.seat_count && seats_left > 0"
|
v-if="batch.data.seat_count && seats_left > 0"
|
||||||
theme="green"
|
theme="green"
|
||||||
|
|||||||
@@ -52,7 +52,10 @@
|
|||||||
<ListSelectBanner>
|
<ListSelectBanner>
|
||||||
<template #actions="{ unselectAll, selections }">
|
<template #actions="{ unselectAll, selections }">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Button variant="ghost" @click="removeStudents(selections)">
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
@click="removeStudents(selections, unselectAll)"
|
||||||
|
>
|
||||||
<Trash2 class="h-4 w-4 stroke-1.5" />
|
<Trash2 class="h-4 w-4 stroke-1.5" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,11 +145,13 @@ const removeStudent = createResource({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const removeStudents = (selections) => {
|
const removeStudents = (selections, unselectAll) => {
|
||||||
selections.forEach(async (student) => {
|
selections.forEach(async (student) => {
|
||||||
removeStudent.submit({ student })
|
removeStudent.submit({ student })
|
||||||
await setTimeout(1000)
|
|
||||||
})
|
})
|
||||||
students.reload()
|
setTimeout(() => {
|
||||||
|
students.reload()
|
||||||
|
unselectAll()
|
||||||
|
}, 500)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<div
|
<div
|
||||||
class="course-image"
|
class="course-image"
|
||||||
:class="{ 'default-image': !course.image }"
|
:class="{ 'default-image': !course.image }"
|
||||||
:style="{ backgroundImage: 'url(' + encodeURI(course.image) + ')' }"
|
:style="{ backgroundImage: 'url(\'' + encodeURI(course.image) + '\')' }"
|
||||||
>
|
>
|
||||||
<div class="flex relative top-4 left-4 w-fit flex-wrap">
|
<div class="flex relative top-4 left-4 w-fit flex-wrap">
|
||||||
<Badge
|
<Badge
|
||||||
|
|||||||
@@ -29,15 +29,42 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getSidebarLinks } from '../utils'
|
import { getSidebarLinks } from '../utils'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { computed, inject } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { sessionStore } from '@/stores/session'
|
import { sessionStore } from '@/stores/session'
|
||||||
|
import { usersStore } from '@/stores/user'
|
||||||
|
import { LogOut, LogIn, UserRound } from 'lucide-vue-next'
|
||||||
|
|
||||||
const { logout, user } = sessionStore()
|
const { logout, user, username } = sessionStore()
|
||||||
let { isLoggedIn } = sessionStore()
|
let { isLoggedIn } = sessionStore()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
let { userResource } = usersStore()
|
||||||
|
|
||||||
const tabs = computed(() => {
|
const tabs = computed(() => {
|
||||||
return getSidebarLinks()
|
let links = getSidebarLinks()
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
links.push({
|
||||||
|
label: 'Profile',
|
||||||
|
icon: UserRound,
|
||||||
|
activeFor: [
|
||||||
|
'Profile',
|
||||||
|
'ProfileAbout',
|
||||||
|
'ProfileCertification',
|
||||||
|
'ProfileEvaluator',
|
||||||
|
'ProfileRoles',
|
||||||
|
],
|
||||||
|
})
|
||||||
|
links.push({
|
||||||
|
label: 'Log out',
|
||||||
|
icon: LogOut,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
links.push({
|
||||||
|
label: 'Log in',
|
||||||
|
icon: LogIn,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return links
|
||||||
})
|
})
|
||||||
|
|
||||||
let isActive = (tab) => {
|
let isActive = (tab) => {
|
||||||
@@ -50,6 +77,13 @@ const handleClick = (tab) => {
|
|||||||
logout.submit().then(() => {
|
logout.submit().then(() => {
|
||||||
isLoggedIn = false
|
isLoggedIn = false
|
||||||
})
|
})
|
||||||
|
else if (tab.label == 'Profile')
|
||||||
|
router.push({
|
||||||
|
name: 'Profile',
|
||||||
|
params: {
|
||||||
|
username: userResource.data?.username,
|
||||||
|
},
|
||||||
|
})
|
||||||
else router.push({ name: tab.to })
|
else router.push({ name: tab.to })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Dialog, Input, TextEditor, createResource } from 'frappe-ui'
|
import { Dialog, Input, TextEditor, createResource } from 'frappe-ui'
|
||||||
import { reactive, defineModel } from 'vue'
|
import { reactive, defineModel, computed } from 'vue'
|
||||||
|
import { showToast } from '@/utils'
|
||||||
|
|
||||||
const topics = defineModel('reloadTopics')
|
const topics = defineModel('reloadTopics')
|
||||||
|
|
||||||
@@ -93,6 +94,14 @@ const submitTopic = (close) => {
|
|||||||
topicResource.submit(
|
topicResource.submit(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
|
validate() {
|
||||||
|
if (!topic.title) {
|
||||||
|
return 'Title cannot be empty.'
|
||||||
|
}
|
||||||
|
if (!topic.reply) {
|
||||||
|
return 'Reply cannot be empty.'
|
||||||
|
}
|
||||||
|
},
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
replyResource.submit(
|
replyResource.submit(
|
||||||
{
|
{
|
||||||
@@ -108,6 +117,9 @@ const submitTopic = (close) => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
onError(err) {
|
||||||
|
showToast('Error', err.message, 'x')
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,13 +57,23 @@
|
|||||||
import LMSLogo from '@/components/Icons/LMSLogo.vue'
|
import LMSLogo from '@/components/Icons/LMSLogo.vue'
|
||||||
import { sessionStore } from '@/stores/session'
|
import { sessionStore } from '@/stores/session'
|
||||||
import { Dropdown, createResource } from 'frappe-ui'
|
import { Dropdown, createResource } from 'frappe-ui'
|
||||||
import { ChevronDown, LogIn, LogOut, User } from 'lucide-vue-next'
|
import {
|
||||||
|
ChevronDown,
|
||||||
|
LogIn,
|
||||||
|
LogOut,
|
||||||
|
User,
|
||||||
|
ArrowRightLeft,
|
||||||
|
} from 'lucide-vue-next'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { convertToTitleCase } from '../utils'
|
import { convertToTitleCase } from '../utils'
|
||||||
import { onMounted, inject } from 'vue'
|
import { onMounted, inject } from 'vue'
|
||||||
import { usersStore } from '@/stores/user'
|
import { usersStore } from '@/stores/user'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { logout } = sessionStore()
|
||||||
|
let { userResource } = usersStore()
|
||||||
|
let { isLoggedIn } = sessionStore()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
isCollapsed: {
|
isCollapsed: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -80,10 +90,6 @@ const branding = createResource({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { logout } = sessionStore()
|
|
||||||
let { userResource } = usersStore()
|
|
||||||
|
|
||||||
let { isLoggedIn } = sessionStore()
|
|
||||||
const userDropdownOptions = [
|
const userDropdownOptions = [
|
||||||
{
|
{
|
||||||
icon: User,
|
icon: User,
|
||||||
@@ -95,6 +101,19 @@ const userDropdownOptions = [
|
|||||||
return isLoggedIn
|
return isLoggedIn
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: ArrowRightLeft,
|
||||||
|
label: 'Switch to Desk',
|
||||||
|
onClick: () => {
|
||||||
|
window.location.href = '/app'
|
||||||
|
},
|
||||||
|
condition: () => {
|
||||||
|
let cookies = new URLSearchParams(document.cookie.split('; ').join('&'))
|
||||||
|
let system_user = cookies.get('system_user')
|
||||||
|
if (system_user === 'yes') return true
|
||||||
|
else return false
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: LogOut,
|
icon: LogOut,
|
||||||
label: 'Log out',
|
label: 'Log out',
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ const newBatch = createResource({
|
|||||||
return {
|
return {
|
||||||
doc: {
|
doc: {
|
||||||
doctype: 'LMS Batch',
|
doctype: 'LMS Batch',
|
||||||
meta_image: batch.image.file_url,
|
meta_image: batch.image?.file_url,
|
||||||
...batch,
|
...batch,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -279,7 +279,7 @@ const editBatch = createResource({
|
|||||||
doctype: 'LMS Batch',
|
doctype: 'LMS Batch',
|
||||||
name: props.batchName,
|
name: props.batchName,
|
||||||
fieldname: {
|
fieldname: {
|
||||||
meta_image: batch.image.file_url,
|
meta_image: batch.image?.file_url,
|
||||||
...batch,
|
...batch,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,17 +11,23 @@
|
|||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
{{ batch.data.description }}
|
{{ batch.data.description }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between w-1/2">
|
<div
|
||||||
|
class="flex flex-col gap-2 lg:gap-0 lg:flex-row lg:items-center justify-between lg:w-1/2"
|
||||||
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<BookOpen class="h-4 w-4 text-gray-700 mr-2" />
|
<BookOpen class="h-4 w-4 text-gray-700 mr-2" />
|
||||||
<span> {{ batch.data?.courses?.length }} {{ __('Courses') }} </span>
|
<span> {{ batch.data?.courses?.length }} {{ __('Courses') }} </span>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="batch.data.courses">·</span>
|
<span class="hidden lg:block" v-if="batch.data.courses"
|
||||||
|
>·</span
|
||||||
|
>
|
||||||
<DateRange
|
<DateRange
|
||||||
:startDate="batch.data.start_date"
|
:startDate="batch.data.start_date"
|
||||||
:endDate="batch.data.end_date"
|
:endDate="batch.data.end_date"
|
||||||
/>
|
/>
|
||||||
<span v-if="batch.data.start_date">·</span>
|
<span class="hidden lg:block" v-if="batch.data.start_date"
|
||||||
|
>·</span
|
||||||
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<Clock class="h-4 w-4 text-gray-700 mr-2" />
|
<Clock class="h-4 w-4 text-gray-700 mr-2" />
|
||||||
<span>
|
<span>
|
||||||
@@ -31,14 +37,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-[60%,20%] gap-20 mt-10">
|
<div class="grid lg:grid-cols-[60%,20%] gap-4 lg:gap-20 mt-10">
|
||||||
<div class="">
|
<div class="order-2 lg:order-none">
|
||||||
<div
|
<div
|
||||||
class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-gray-300 prose-th:border-gray-300 prose-td:relative prose-th:relative prose-th:bg-gray-100 prose-sm max-w-none !whitespace-normal mt-6"
|
class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-gray-300 prose-th:border-gray-300 prose-td:relative prose-th:relative prose-th:bg-gray-100 prose-sm max-w-none !whitespace-normal mt-6"
|
||||||
v-html="batch.data.batch_details"
|
v-html="batch.data.batch_details"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="order-1 lg:order-none">
|
||||||
<BatchOverlay :batch="batch" />
|
<BatchOverlay :batch="batch" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -48,7 +54,7 @@
|
|||||||
{{ __('Courses') }}
|
{{ __('Courses') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8 mt-5">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mt-5">
|
||||||
<div
|
<div
|
||||||
v-if="batch.data.courses"
|
v-if="batch.data.courses"
|
||||||
v-for="course in courses.data"
|
v-for="course in courses.data"
|
||||||
|
|||||||
@@ -3,9 +3,22 @@
|
|||||||
class="sticky top-0 z-10 flex items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5"
|
class="sticky top-0 z-10 flex items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5"
|
||||||
>
|
>
|
||||||
<Breadcrumbs :items="breadcrumbs" />
|
<Breadcrumbs :items="breadcrumbs" />
|
||||||
|
<div>
|
||||||
|
<FormControl
|
||||||
|
type="text"
|
||||||
|
placeholder="Search Participants"
|
||||||
|
v-model="searchQuery"
|
||||||
|
@input="participants.reload()"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<Search class="w-4" name="search" />
|
||||||
|
</template>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="grid grid-cols-3 gap-4 m-5">
|
|
||||||
<div v-for="participant in participants.data">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 m-5">
|
||||||
|
<div v-if="participants.data" v-for="participant in participants.data">
|
||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
name: 'Profile',
|
name: 'Profile',
|
||||||
@@ -38,14 +51,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Breadcrumbs, createResource } from 'frappe-ui'
|
import { Breadcrumbs, FormControl, createResource } from 'frappe-ui'
|
||||||
import { computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import UserAvatar from '@/components/UserAvatar.vue'
|
import UserAvatar from '@/components/UserAvatar.vue'
|
||||||
|
import { Search } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
const searchQuery = ref('')
|
||||||
|
|
||||||
const participants = createResource({
|
const participants = createResource({
|
||||||
url: 'lms.lms.api.get_certified_participants',
|
url: 'lms.lms.api.get_certified_participants',
|
||||||
|
method: 'GET',
|
||||||
|
cache: ['certified_participants'],
|
||||||
|
makeParams() {
|
||||||
|
return {
|
||||||
|
search_query: searchQuery.value,
|
||||||
|
}
|
||||||
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
cache: ['certified-participants'],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const breadcrumbs = computed(() => {
|
const breadcrumbs = computed(() => {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
<div
|
<div
|
||||||
v-show="openInstructorEditor"
|
v-show="openInstructorEditor"
|
||||||
id="instructor-notes"
|
id="instructor-notes"
|
||||||
class="py-3"
|
class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-gray-300 prose-th:border-gray-300 prose-td:relative prose-th:relative prose-th:bg-gray-100 prose-sm max-w-none !whitespace-normal mt-6 py-3"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -52,7 +52,10 @@
|
|||||||
<label class="block font-medium text-gray-600 mb-1">
|
<label class="block font-medium text-gray-600 mb-1">
|
||||||
{{ __('Content') }}
|
{{ __('Content') }}
|
||||||
</label>
|
</label>
|
||||||
<div id="content" class="py-3"></div>
|
<div
|
||||||
|
id="content"
|
||||||
|
class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-gray-300 prose-th:border-gray-300 prose-td:relative prose-th:relative prose-th:bg-gray-100 prose-sm max-w-none !whitespace-normal mt-6 py-3"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -35,8 +35,8 @@
|
|||||||
</EditCoverImage>
|
</EditCoverImage>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto -mt-4 max-w-4xl translate-x-0 sm:px-5">
|
<div class="mx-auto -mt-10 md:-mt-4 max-w-4xl translate-x-0 px-5">
|
||||||
<div class="flex items-center">
|
<div class="flex flex-col md:flex-row items-center">
|
||||||
<div>
|
<div>
|
||||||
<img
|
<img
|
||||||
v-if="profile.data.user_image"
|
v-if="profile.data.user_image"
|
||||||
@@ -57,7 +57,11 @@
|
|||||||
{{ profile.data.headline }}
|
{{ profile.data.headline }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button v-if="isSessionUser()" class="ml-auto" @click="editProfile()">
|
<Button
|
||||||
|
v-if="isSessionUser()"
|
||||||
|
class="mt-3 sm:mt-0 md:ml-auto"
|
||||||
|
@click="editProfile()"
|
||||||
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<Edit class="w-4 h-4 stroke-1.5 text-gray-700" />
|
<Edit class="w-4 h-4 stroke-1.5 text-gray-700" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mt-7">
|
<div class="mt-7 mb-10">
|
||||||
<h2 class="mb-3 text-lg font-semibold text-gray-900">
|
<h2 class="mb-3 text-lg font-semibold text-gray-900">
|
||||||
{{ __('About') }}
|
{{ __('About') }}
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mt-7">
|
<div class="mt-7 mb-10">
|
||||||
<h2 class="mb-3 text-lg font-semibold text-gray-900">
|
<h2 class="mb-3 text-lg font-semibold text-gray-900">
|
||||||
{{ __('Certificates') }}
|
{{ __('Certificates') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-3 gap-4">
|
<div class="grid grod-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
<div
|
<div
|
||||||
v-for="certificate in certificates.data"
|
v-for="certificate in certificates.data"
|
||||||
:key="certificate.name"
|
:key="certificate.name"
|
||||||
@@ -34,13 +34,9 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const certificates = createResource({
|
const certificates = createResource({
|
||||||
url: 'frappe.client.get_list',
|
url: 'lms.lms.api.get_certificates',
|
||||||
params: {
|
params: {
|
||||||
doctype: 'LMS Certificate',
|
member: props.profile.data.name,
|
||||||
fields: ['name', 'course', 'course_title', 'issue_date', 'template'],
|
|
||||||
filters: {
|
|
||||||
member: props.profile.data.name,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="grid grid-cols-4 gap-4 text-sm text-gray-700 mb-4">
|
<div
|
||||||
|
class="grid grid-cols-3 md:grid-cols-4 gap-4 text-sm text-gray-700 mb-4"
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
{{ __('Day') }}
|
{{ __('Day') }}
|
||||||
</div>
|
</div>
|
||||||
@@ -20,7 +22,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="evaluator.data"
|
v-if="evaluator.data"
|
||||||
v-for="slot in evaluator.data.slots.schedule"
|
v-for="slot in evaluator.data.slots.schedule"
|
||||||
class="grid grid-cols-4 gap-4 mb-4 group"
|
class="grid grid-cols-3 md:grid-cols-4 gap-4 mb-4 group"
|
||||||
>
|
>
|
||||||
<FormControl
|
<FormControl
|
||||||
type="select"
|
type="select"
|
||||||
@@ -44,7 +46,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-4 gap-4 mb-4" v-show="showSlotsTemplate">
|
<div
|
||||||
|
class="grid grid-cols-3 md:grod-cols-4 gap-4 mb-4"
|
||||||
|
v-show="showSlotsTemplate"
|
||||||
|
>
|
||||||
<FormControl
|
<FormControl
|
||||||
type="select"
|
type="select"
|
||||||
:options="days"
|
:options="days"
|
||||||
@@ -74,7 +79,7 @@
|
|||||||
<h2 class="mb-4 text-lg font-semibold text-gray-900">
|
<h2 class="mb-4 text-lg font-semibold text-gray-900">
|
||||||
{{ __('I am unavailable') }}
|
{{ __('I am unavailable') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-4 gap-4">
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
<FormControl
|
<FormControl
|
||||||
type="date"
|
type="date"
|
||||||
:label="__('From')"
|
:label="__('From')"
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
<h2 class="mb-3 text-lg font-semibold text-gray-900">
|
<h2 class="mb-3 text-lg font-semibold text-gray-900">
|
||||||
{{ __('Settings') }}
|
{{ __('Settings') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="flex justify-between w-3/4 mt-5">
|
<div
|
||||||
|
class="flex flex-col md:flex-row gap-4 md:gap-0 justify-between w-3/4 mt-5"
|
||||||
|
>
|
||||||
<FormControl
|
<FormControl
|
||||||
:label="__('Moderator')"
|
:label="__('Moderator')"
|
||||||
v-model="moderator"
|
v-model="moderator"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<Breadcrumbs class="h-7" :items="breadcrumbs" />
|
<Breadcrumbs class="h-7" :items="breadcrumbs" />
|
||||||
</header>
|
</header>
|
||||||
<div v-if="chartDetails.data" class="p-5">
|
<div v-if="chartDetails.data" class="p-5">
|
||||||
<div class="grid grid-cols-2 lg:grid-cols-5 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4">
|
||||||
<div class="flex items-center shadow py-2 px-3 rounded-md">
|
<div class="flex items-center shadow py-2 px-3 rounded-md">
|
||||||
<div class="p-2 rounded-md bg-gray-100 mr-3">
|
<div class="p-2 rounded-md bg-gray-100 mr-3">
|
||||||
<BookOpen class="w-18 h-18 stroke-1.5 text-gray-700" />
|
<BookOpen class="w-18 h-18 stroke-1.5 text-gray-700" />
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -330,12 +330,13 @@ def get_evaluator_details(evaluator):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
def get_certified_participants():
|
def get_certified_participants(search_query=""):
|
||||||
LMSCertificate = DocType("LMS Certificate")
|
LMSCertificate = DocType("LMS Certificate")
|
||||||
participants = (
|
participants = (
|
||||||
frappe.qb.from_(LMSCertificate)
|
frappe.qb.from_(LMSCertificate)
|
||||||
.select(LMSCertificate.member)
|
.select(LMSCertificate.member)
|
||||||
.distinct()
|
.distinct()
|
||||||
|
.where(LMSCertificate.member_name.like(f"%{search_query}%"))
|
||||||
.where(LMSCertificate.published == 1)
|
.where(LMSCertificate.published == 1)
|
||||||
.orderby(LMSCertificate.creation, order=frappe.qb.desc)
|
.orderby(LMSCertificate.creation, order=frappe.qb.desc)
|
||||||
.run(as_dict=1)
|
.run(as_dict=1)
|
||||||
@@ -355,9 +356,8 @@ def get_certified_participants():
|
|||||||
courses = []
|
courses = []
|
||||||
for course in course_names:
|
for course in course_names:
|
||||||
courses.append(frappe.db.get_value("LMS Course", course, "title"))
|
courses.append(frappe.db.get_value("LMS Course", course, "title"))
|
||||||
details.courses = courses
|
details["courses"] = courses
|
||||||
participant_details.append(details)
|
participant_details.append(details)
|
||||||
|
|
||||||
return participant_details
|
return participant_details
|
||||||
|
|
||||||
|
|
||||||
@@ -374,3 +374,14 @@ def get_assigned_badges(member):
|
|||||||
badge.update(
|
badge.update(
|
||||||
frappe.db.get_value("LMS Badge", badge.badge, ["name", "title", "image"])
|
frappe.db.get_value("LMS Badge", badge.badge, ["name", "title", "image"])
|
||||||
)
|
)
|
||||||
|
return assigned_badges
|
||||||
|
|
||||||
|
|
||||||
|
def get_certificates(member):
|
||||||
|
"""Get certificates for a member."""
|
||||||
|
return frappe.get_all(
|
||||||
|
"LMS Certificate",
|
||||||
|
filters={"member": member},
|
||||||
|
fields=["name", "course", "course_title", "issue_date", "template"],
|
||||||
|
order_by="creation desc",
|
||||||
|
)
|
||||||
|
|||||||
@@ -76,8 +76,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "video_link",
|
"fieldname": "video_link",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Video Embed Link",
|
"label": "Video Embed Link"
|
||||||
"reqd": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "short_introduction",
|
"fieldname": "short_introduction",
|
||||||
@@ -305,7 +304,6 @@
|
|||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"search_fields": "title, tags",
|
|
||||||
"show_title_field_in_link": 1,
|
"show_title_field_in_link": 1,
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
|||||||
@@ -1524,10 +1524,12 @@ def get_question_details(question):
|
|||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
def get_batch_courses(batch):
|
def get_batch_courses(batch):
|
||||||
courses = []
|
courses = []
|
||||||
course_list = frappe.get_all("Batch Course", {"parent": batch}, pluck="course")
|
course_list = frappe.get_all("Batch Course", {"parent": batch}, ["name", "course"])
|
||||||
|
|
||||||
for course in course_list:
|
for course in course_list:
|
||||||
courses.append(get_course_details(course))
|
details = get_course_details(course.course)
|
||||||
|
details.batch_course = course.name
|
||||||
|
courses.append(details)
|
||||||
|
|
||||||
return courses
|
return courses
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,9 @@
|
|||||||
"label": "Enrollments"
|
"label": "Enrollments"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"content": "[{\"id\":\"jNO4sdKxHu\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Get Started</b></span>\",\"col\":12}},{\"id\":\"5s0qRBc4rY\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/courses\\\" draggable=\\\"false\\\">Visit LMS Portal</a>\",\"col\":4}},{\"id\":\"lGMuNLpmv-\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/courses/new-course/edit\\\">Create a Course</a>\",\"col\":4}},{\"id\":\"3TVyc9AkPy\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/app/web-page/new-web-page-1\\\">Setup a Home Page</a>\",\"col\":4}},{\"id\":\"9zcbqpu2gm\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/app/lms-settings/LMS%20Settings\\\">LMS Setting</a>\",\"col\":4}},{\"id\":\"0ATmnKmXjc\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"https://docs.frappelms.com\\\">Documentation</a>\",\"col\":4}},{\"id\":\"7tGB2TYPmn\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"https://frappe.school/courses/introducing-frappe-lms\\\">Video Tutorials</a>\",\"col\":4}},{\"id\":\"C128a4abjX\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"5q4sPiv2ci\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Signups\",\"col\":6}},{\"id\":\"8NSaRaEV5u\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Enrollments\",\"col\":6}},{\"id\":\"kMuzko0uAU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"iuvIOHmztI\",\"type\":\"header\",\"data\":{\"text\":\"<span style=\\\"font-size: 18px;\\\"><b>Statistics</b></span>\",\"col\":12}},{\"id\":\"l0VTd66Uy2\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Users\",\"col\":4}},{\"id\":\"wAWZin1KKk\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course\",\"col\":4}},{\"id\":\"RLrIlFx0Hd\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Enrollments\",\"col\":4}},{\"id\":\"OuhWkhCQmq\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course Completed\",\"col\":4}},{\"id\":\"3g8QmNqUXG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Certificate\",\"col\":4}},{\"id\":\"EZsdsujs8N\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Evaluation\",\"col\":4}},{\"id\":\"s-nfsFQbGV\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"jeOBWBzHEa\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Master</b></span>\",\"col\":12}},{\"id\":\"sVhgfS5GIh\",\"type\":\"card\",\"data\":{\"card_name\":\"Course Data\",\"col\":4}},{\"id\":\"Iea0snm4Fg\",\"type\":\"card\",\"data\":{\"card_name\":\"Course Stats\",\"col\":4}},{\"id\":\"bZB7RqOl6a\",\"type\":\"card\",\"data\":{\"card_name\":\"Certification\",\"col\":4}}]",
|
"content": "[{\"id\":\"jNO4sdKxHu\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Get Started</b></span>\",\"col\":12}},{\"id\":\"5s0qRBc4rY\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/lms/courses\\\">Visit LMS Portal</a>\",\"col\":4}},{\"id\":\"lGMuNLpmv-\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/courses/new-course/edit\\\">Create a Course</a>\",\"col\":4}},{\"id\":\"3TVyc9AkPy\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/app/web-page/new-web-page-1\\\">Setup a Home Page</a>\",\"col\":4}},{\"id\":\"9zcbqpu2gm\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/app/lms-settings/LMS%20Settings\\\">LMS Setting</a>\",\"col\":4}},{\"id\":\"0ATmnKmXjc\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"https://docs.frappelms.com\\\">Documentation</a>\",\"col\":4}},{\"id\":\"7tGB2TYPmn\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"https://frappe.school/courses/introducing-frappe-lms\\\">Video Tutorials</a>\",\"col\":4}},{\"id\":\"C128a4abjX\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"5q4sPiv2ci\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Signups\",\"col\":6}},{\"id\":\"8NSaRaEV5u\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Enrollments\",\"col\":6}},{\"id\":\"kMuzko0uAU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"iuvIOHmztI\",\"type\":\"header\",\"data\":{\"text\":\"<span style=\\\"font-size: 18px;\\\"><b>Statistics</b></span>\",\"col\":12}},{\"id\":\"l0VTd66Uy2\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Users\",\"col\":4}},{\"id\":\"wAWZin1KKk\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course\",\"col\":4}},{\"id\":\"RLrIlFx0Hd\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Enrollments\",\"col\":4}},{\"id\":\"OuhWkhCQmq\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course Completed\",\"col\":4}},{\"id\":\"3g8QmNqUXG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Certificate\",\"col\":4}},{\"id\":\"EZsdsujs8N\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Evaluation\",\"col\":4}},{\"id\":\"s-nfsFQbGV\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"jeOBWBzHEa\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Master</b></span>\",\"col\":12}},{\"id\":\"sVhgfS5GIh\",\"type\":\"card\",\"data\":{\"card_name\":\"Course Data\",\"col\":4}},{\"id\":\"Iea0snm4Fg\",\"type\":\"card\",\"data\":{\"card_name\":\"Course Stats\",\"col\":4}},{\"id\":\"bZB7RqOl6a\",\"type\":\"card\",\"data\":{\"card_name\":\"Certification\",\"col\":4}}]",
|
||||||
"creation": "2021-10-21 17:20:01.358903",
|
"creation": "2021-10-21 17:20:01.358903",
|
||||||
|
"custom_blocks": [],
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Workspace",
|
"doctype": "Workspace",
|
||||||
"hide_custom": 0,
|
"hide_custom": 0,
|
||||||
@@ -144,7 +145,7 @@
|
|||||||
"type": "Link"
|
"type": "Link"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-05-11 15:41:25.514443",
|
"modified": "2024-05-09 14:44:08.590606",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS",
|
"name": "LMS",
|
||||||
|
|||||||
@@ -86,4 +86,5 @@ lms.patches.v1_0.change_jobs_url #19-01-2024
|
|||||||
lms.patches.v1_0.custom_perm_for_discussions #14-01-2024
|
lms.patches.v1_0.custom_perm_for_discussions #14-01-2024
|
||||||
lms.patches.v1_0.rename_evaluator_role
|
lms.patches.v1_0.rename_evaluator_role
|
||||||
lms.patches.v1_0.change_navbar_urls
|
lms.patches.v1_0.change_navbar_urls
|
||||||
lms.patches.v1_0.set_published_on
|
lms.patches.v1_0.set_published_on
|
||||||
|
lms.patches.v2_0.fix_progress_percentage
|
||||||
@@ -2,5 +2,7 @@ import frappe
|
|||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
if frappe.db.exists("Role", "Class Evaluator"):
|
if frappe.db.exists("Role", "Class Evaluator") and not frappe.db.exists(
|
||||||
|
"Role", "Batch Evaluator"
|
||||||
|
):
|
||||||
frappe.rename_doc("Role", "Class Evaluator", "Batch Evaluator")
|
frappe.rename_doc("Role", "Class Evaluator", "Batch Evaluator")
|
||||||
|
|||||||
10
lms/patches/v2_0/fix_progress_percentage.py
Normal file
10
lms/patches/v2_0/fix_progress_percentage.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import frappe
|
||||||
|
from lms.lms.utils import get_course_progress
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
enrollments = frappe.get_all("LMS Enrollment", fields=["name", "course", "member"])
|
||||||
|
|
||||||
|
for enrollment in enrollments:
|
||||||
|
progress = get_course_progress(enrollment.course, enrollment.member)
|
||||||
|
frappe.db.set_value("LMS Enrollment", enrollment.name, "progress", progress)
|
||||||
@@ -97,7 +97,7 @@ def get_meta(app_path):
|
|||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
"title": job_opening.title,
|
"title": job_opening.job_title,
|
||||||
"image": job_opening.company_logo,
|
"image": job_opening.company_logo,
|
||||||
"description": job_opening.company_name,
|
"description": job_opening.company_name,
|
||||||
"keywords": "Job Openings, Jobs, Vacancies",
|
"keywords": "Job Openings, Jobs, Vacancies",
|
||||||
|
|||||||
Reference in New Issue
Block a user