Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b74c1670ca | ||
|
|
04552bdef6 | ||
|
|
ad5bf89b35 | ||
|
|
88b38dfd83 | ||
|
|
75e9ca395f | ||
|
|
6fb206cc4e | ||
|
|
62cb198492 | ||
|
|
9609329f01 | ||
|
|
c93808af94 | ||
|
|
58866260ec | ||
|
|
e6157ff411 | ||
|
|
8cca8920ee | ||
|
|
ab039dbd46 | ||
|
|
9853ab3fd9 | ||
|
|
dc2bf9f13e | ||
|
|
7c90ca4040 | ||
|
|
75a90e1f39 | ||
|
|
bc4b17cc3d | ||
|
|
8c454a333e | ||
|
|
3cda563583 | ||
|
|
545326a02a | ||
|
|
14ce5d7e23 |
@@ -31,12 +31,35 @@ describe("Course Creation", () => {
|
|||||||
.contains("Preview Video")
|
.contains("Preview Video")
|
||||||
.type("https://www.youtube.com/embed/-LPmw2Znl2c");
|
.type("https://www.youtube.com/embed/-LPmw2Znl2c");
|
||||||
cy.get("[id=tags]").type("Learning{enter}Frappe{enter}ERPNext{enter}");
|
cy.get("[id=tags]").type("Learning{enter}Frappe{enter}ERPNext{enter}");
|
||||||
cy.get(".search-input").click().type("frappe");
|
cy.get("label")
|
||||||
cy.wait(1000);
|
.contains("Category")
|
||||||
|
.parent()
|
||||||
|
.within(() => {
|
||||||
|
cy.get("button").click();
|
||||||
|
});
|
||||||
cy.get("[id^=headlessui-combobox-option-")
|
cy.get("[id^=headlessui-combobox-option-")
|
||||||
.should("be.visible")
|
.should("be.visible")
|
||||||
.first()
|
.first()
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
|
/* Instructor */
|
||||||
|
cy.get("label")
|
||||||
|
.contains("Instructors")
|
||||||
|
.parent()
|
||||||
|
.within(() => {
|
||||||
|
cy.get("input").click().type("frappe");
|
||||||
|
cy.get("input")
|
||||||
|
.invoke("attr", "aria-controls")
|
||||||
|
.as("instructor_list_id");
|
||||||
|
});
|
||||||
|
cy.get("@instructor_list_id").then((instructor_list_id) => {
|
||||||
|
cy.get(`[id^=${instructor_list_id}`)
|
||||||
|
.should("be.visible")
|
||||||
|
.within(() => {
|
||||||
|
cy.get("[id^=headlessui-combobox-option-").first().click();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
cy.get("label").contains("Published").click();
|
cy.get("label").contains("Published").click();
|
||||||
cy.get("label").contains("Published On").type("2021-01-01");
|
cy.get("label").contains("Published On").type("2021-01-01");
|
||||||
cy.button("Save").click();
|
cy.button("Save").click();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"@editorjs/paragraph": "^2.11.3",
|
"@editorjs/paragraph": "^2.11.3",
|
||||||
"@editorjs/simple-image": "^1.6.0",
|
"@editorjs/simple-image": "^1.6.0",
|
||||||
"chart.js": "^4.4.1",
|
"chart.js": "^4.4.1",
|
||||||
|
"codemirror-editor-vue3": "^2.8.0",
|
||||||
"dayjs": "^1.11.6",
|
"dayjs": "^1.11.6",
|
||||||
"feather-icons": "^4.28.0",
|
"feather-icons": "^4.28.0",
|
||||||
"frappe-ui": "^0.1.69",
|
"frappe-ui": "^0.1.69",
|
||||||
|
|||||||
151
frontend/src/components/Categories.vue
Normal file
151
frontend/src/components/Categories.vue
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col min-h-0">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="text-xl font-semibold mb-1">
|
||||||
|
{{ label }}
|
||||||
|
</div>
|
||||||
|
<Button @click="() => showCategoryForm()">
|
||||||
|
<template #icon>
|
||||||
|
<Plus v-if="!showForm" class="h-3 w-3 stroke-1.5" />
|
||||||
|
<X v-else class="h-3 w-3 stroke-1.5" />
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="showForm"
|
||||||
|
class="flex items-center justify-between my-4 space-x-2"
|
||||||
|
>
|
||||||
|
<FormControl
|
||||||
|
ref="categoryInput"
|
||||||
|
v-model="category"
|
||||||
|
:placeholder="__('Category Name')"
|
||||||
|
class="flex-1"
|
||||||
|
/>
|
||||||
|
<Button @click="addCategory()" variant="subtle">
|
||||||
|
{{ __('Add') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overflow-y-scroll">
|
||||||
|
<div class="text-base divide-y">
|
||||||
|
<FormControl
|
||||||
|
:value="cat.category"
|
||||||
|
type="text"
|
||||||
|
v-for="cat in categories.data"
|
||||||
|
class="form-control"
|
||||||
|
@change.stop="(e) => update(cat.name, e.target.value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
createListResource,
|
||||||
|
createResource,
|
||||||
|
debounce,
|
||||||
|
} from 'frappe-ui'
|
||||||
|
import { Plus, X } from 'lucide-vue-next'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const showForm = ref(false)
|
||||||
|
const category = ref(null)
|
||||||
|
const categoryInput = ref(null)
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const categories = createListResource({
|
||||||
|
doctype: 'LMS Category',
|
||||||
|
fields: ['name', 'category'],
|
||||||
|
auto: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const newCategory = createResource({
|
||||||
|
url: 'frappe.client.insert',
|
||||||
|
makeParams(values) {
|
||||||
|
return {
|
||||||
|
doc: {
|
||||||
|
doctype: 'LMS Category',
|
||||||
|
category: category.value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const addCategory = () => {
|
||||||
|
newCategory.submit(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
onSuccess(data) {
|
||||||
|
categories.reload()
|
||||||
|
category.value = null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const showCategoryForm = () => {
|
||||||
|
showForm.value = !showForm.value
|
||||||
|
setTimeout(() => {
|
||||||
|
categoryInput.value.$el.querySelector('input').focus()
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCategory = createResource({
|
||||||
|
url: 'frappe.client.rename_doc',
|
||||||
|
makeParams(values) {
|
||||||
|
return {
|
||||||
|
doctype: 'LMS Category',
|
||||||
|
old_name: values.name,
|
||||||
|
new_name: values.category,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const update = (name, value) => {
|
||||||
|
updateCategory.submit(
|
||||||
|
{
|
||||||
|
name: name,
|
||||||
|
category: value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess() {
|
||||||
|
categories.reload()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.form-control input {
|
||||||
|
padding: 1.25rem 0;
|
||||||
|
border-color: transparent;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control input:focus {
|
||||||
|
outline: transparent;
|
||||||
|
background: white;
|
||||||
|
box-shadow: none;
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control input:hover {
|
||||||
|
outline: transparent;
|
||||||
|
background: white;
|
||||||
|
box-shadow: none;
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -27,8 +27,8 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const file = computed(() => {
|
const file = computed(() => {
|
||||||
if (props.type == 'youtube') return '/Youtube.mp4'
|
if (props.type == 'youtube') return '/assets/lms/frontend/Youtube.mp4'
|
||||||
if (props.type == 'quiz') return '/Quiz.mp4'
|
if (props.type == 'quiz') return '/assets/lms/frontend/Quiz.mp4'
|
||||||
if (props.type == 'upload') return '/Upload.mp4'
|
if (props.type == 'upload') return '/assets/lms/frontend/Upload.mp4'
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<h1 class="mb-3 px-2 pt-2 text-lg font-semibold">
|
<h1 class="mb-3 px-2 pt-2 text-lg font-semibold">
|
||||||
{{ __('Settings') }}
|
{{ __('Settings') }}
|
||||||
</h1>
|
</h1>
|
||||||
<div v-for="tab in tabs">
|
<div v-for="tab in tabs" :key="tab.label">
|
||||||
<div
|
<div
|
||||||
v-if="!tab.hideLabel"
|
v-if="!tab.hideLabel"
|
||||||
class="mb-2 mt-3 flex cursor-pointer gap-1.5 px-1 text-base font-medium text-gray-600 transition-all duration-300 ease-in-out"
|
class="mb-2 mt-3 flex cursor-pointer gap-1.5 px-1 text-base font-medium text-gray-600 transition-all duration-300 ease-in-out"
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
<SidebarLink
|
<SidebarLink
|
||||||
v-for="item in tab.items"
|
v-for="item in tab.items"
|
||||||
:link="item"
|
:link="item"
|
||||||
|
:key="item.label"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
:class="
|
:class="
|
||||||
activeTab?.label == item.label
|
activeTab?.label == item.label
|
||||||
@@ -30,7 +31,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="activeTab && data.doc"
|
v-if="activeTab && data.doc"
|
||||||
class="flex flex-1 flex-col px-10 pt-8"
|
:key="activeTab.label"
|
||||||
|
class="flex flex-1 flex-col px-10 py-8"
|
||||||
>
|
>
|
||||||
<Members
|
<Members
|
||||||
v-if="activeTab.label === 'Members'"
|
v-if="activeTab.label === 'Members'"
|
||||||
@@ -38,6 +40,11 @@
|
|||||||
:description="activeTab.description"
|
:description="activeTab.description"
|
||||||
v-model:show="show"
|
v-model:show="show"
|
||||||
/>
|
/>
|
||||||
|
<Categories
|
||||||
|
v-else-if="activeTab.label === 'Categories'"
|
||||||
|
:label="activeTab.label"
|
||||||
|
:description="activeTab.description"
|
||||||
|
/>
|
||||||
<SettingDetails
|
<SettingDetails
|
||||||
v-else
|
v-else
|
||||||
:fields="activeTab.fields"
|
:fields="activeTab.fields"
|
||||||
@@ -53,13 +60,16 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { Dialog, createDocumentResource } from 'frappe-ui'
|
import { Dialog, createDocumentResource } from 'frappe-ui'
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { useSettings } from '@/stores/settings'
|
||||||
import SettingDetails from '../SettingDetails.vue'
|
import SettingDetails from '../SettingDetails.vue'
|
||||||
import SidebarLink from '@/components/SidebarLink.vue'
|
import SidebarLink from '@/components/SidebarLink.vue'
|
||||||
import Members from '@/components/Members.vue'
|
import Members from '@/components/Members.vue'
|
||||||
|
import Categories from '@/components/Categories.vue'
|
||||||
|
|
||||||
const show = defineModel()
|
const show = defineModel()
|
||||||
const doctype = ref('LMS Settings')
|
const doctype = ref('LMS Settings')
|
||||||
const activeTab = ref(null)
|
const activeTab = ref(null)
|
||||||
|
const settingsStore = useSettings()
|
||||||
|
|
||||||
const data = createDocumentResource({
|
const data = createDocumentResource({
|
||||||
doctype: doctype.value,
|
doctype: doctype.value,
|
||||||
@@ -69,8 +79,8 @@ const data = createDocumentResource({
|
|||||||
auto: true,
|
auto: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const tabs = computed(() => {
|
const tabsStructure = computed(() => {
|
||||||
let _tabs = [
|
return [
|
||||||
{
|
{
|
||||||
label: 'Settings',
|
label: 'Settings',
|
||||||
hideLabel: true,
|
hideLabel: true,
|
||||||
@@ -80,6 +90,23 @@ const tabs = computed(() => {
|
|||||||
description: 'Manage the members of your learning system',
|
description: 'Manage the members of your learning system',
|
||||||
icon: 'UserRoundPlus',
|
icon: 'UserRoundPlus',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Settings',
|
||||||
|
hideLabel: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Categories',
|
||||||
|
description: 'Manage the members of your learning system',
|
||||||
|
icon: 'Network',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Settings',
|
||||||
|
hideLabel: true,
|
||||||
|
items: [
|
||||||
{
|
{
|
||||||
label: 'Payment Gateway',
|
label: 'Payment Gateway',
|
||||||
icon: 'DollarSign',
|
icon: 'DollarSign',
|
||||||
@@ -125,8 +152,8 @@ const tabs = computed(() => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Settings',
|
label: 'Customise',
|
||||||
hideLabel: true,
|
hideLabel: false,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: 'Sidebar',
|
label: 'Sidebar',
|
||||||
@@ -168,12 +195,6 @@ const tabs = computed(() => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Settings',
|
|
||||||
hideLabel: true,
|
|
||||||
items: [
|
|
||||||
{
|
{
|
||||||
label: 'Email Templates',
|
label: 'Email Templates',
|
||||||
icon: 'MailPlus',
|
icon: 'MailPlus',
|
||||||
@@ -199,56 +220,19 @@ const tabs = computed(() => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Settings',
|
|
||||||
hideLabel: true,
|
|
||||||
items: [
|
|
||||||
{
|
{
|
||||||
label: 'Signup',
|
label: 'Signup',
|
||||||
icon: 'LogIn',
|
icon: 'LogIn',
|
||||||
description:
|
|
||||||
'Customize the signup page to inform users about your terms and policies',
|
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
label: 'Show terms of use on signup',
|
label: 'Custom Content',
|
||||||
name: 'terms_of_use',
|
name: 'custom_signup_content',
|
||||||
type: 'checkbox',
|
type: 'Code',
|
||||||
|
mode: 'htmlmixed',
|
||||||
|
rows: 10,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Terms of Use Page',
|
label: 'Ask user category',
|
||||||
name: 'terms_page',
|
|
||||||
type: 'Link',
|
|
||||||
doctype: 'Web Page',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Show privacy policy on signup',
|
|
||||||
name: 'privacy_policy',
|
|
||||||
type: 'checkbox',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Privacy Policy Page',
|
|
||||||
name: 'privacy_policy_page',
|
|
||||||
type: 'Link',
|
|
||||||
doctype: 'Web Page',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'Column Break',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Show cookie policy on signup',
|
|
||||||
name: 'cookie_policy',
|
|
||||||
type: 'checkbox',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Cookie Policy Page',
|
|
||||||
name: 'cookie_policy_page',
|
|
||||||
type: 'Link',
|
|
||||||
doctype: 'Web Page',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Ask user category during signup',
|
|
||||||
name: 'user_category',
|
name: 'user_category',
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
},
|
},
|
||||||
@@ -257,23 +241,28 @@ const tabs = computed(() => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
})
|
||||||
|
|
||||||
return _tabs.map((tab) => {
|
const tabs = computed(() => {
|
||||||
tab.items = tab.items.filter((item) => {
|
return tabsStructure.value.map((tab) => {
|
||||||
if (item.condition) {
|
return {
|
||||||
return item.condition()
|
...tab,
|
||||||
}
|
items: tab.items.filter((item) => {
|
||||||
return true
|
return !item.condition || item.condition()
|
||||||
})
|
}),
|
||||||
return tab
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(show, () => {
|
watch(show, async () => {
|
||||||
if (show.value) {
|
if (show.value) {
|
||||||
activeTab.value = tabs.value[0].items[0]
|
const currentTab = await tabs.value
|
||||||
|
.flatMap((tab) => tab.items)
|
||||||
|
.find((item) => item.label === settingsStore.activeTab)
|
||||||
|
activeTab.value = currentTab || tabs.value[0].items[0]
|
||||||
} else {
|
} else {
|
||||||
activeTab.value = null
|
activeTab.value = null
|
||||||
|
settingsStore.isSettingsOpen = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,9 +8,15 @@
|
|||||||
{{ __(description) }}
|
{{ __(description) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between my-5">
|
<div
|
||||||
|
class="my-5"
|
||||||
|
:class="{ 'flex justify-between w-full': columns.length > 1 }"
|
||||||
|
>
|
||||||
<div v-for="(column, index) in columns" :key="index">
|
<div v-for="(column, index) in columns" :key="index">
|
||||||
<div class="flex flex-col space-y-5 w-72">
|
<div
|
||||||
|
class="flex flex-col space-y-5"
|
||||||
|
:class="columns.length > 1 ? 'w-72' : 'w-full'"
|
||||||
|
>
|
||||||
<div v-for="field in column">
|
<div v-for="field in column">
|
||||||
<Link
|
<Link
|
||||||
v-if="field.type == 'Link'"
|
v-if="field.type == 'Link'"
|
||||||
@@ -18,12 +24,25 @@
|
|||||||
:doctype="field.doctype"
|
:doctype="field.doctype"
|
||||||
:label="field.label"
|
:label="field.label"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Codemirror
|
||||||
|
v-else-if="field.type == 'Code'"
|
||||||
|
v-model:value="field.value"
|
||||||
|
:label="field.label"
|
||||||
|
:height="200"
|
||||||
|
:options="{
|
||||||
|
mode: field.mode,
|
||||||
|
theme: 'seti',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
|
||||||
<FormControl
|
<FormControl
|
||||||
v-else
|
v-else
|
||||||
:key="field.name"
|
:key="field.name"
|
||||||
v-model="field.value"
|
v-model="field.value"
|
||||||
:label="field.label"
|
:label="field.label"
|
||||||
:type="field.type"
|
:type="field.type"
|
||||||
|
:rows="field.rows"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,6 +60,9 @@
|
|||||||
import { FormControl, Button } from 'frappe-ui'
|
import { FormControl, Button } from 'frappe-ui'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
|
import Codemirror from 'codemirror-editor-vue3'
|
||||||
|
import 'codemirror/theme/seti.css'
|
||||||
|
import 'codemirror/mode/htmlmixed/htmlmixed.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
fields: {
|
fields: {
|
||||||
@@ -94,3 +116,13 @@ const update = () => {
|
|||||||
props.data.save.submit()
|
props.data.save.submit()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
.CodeMirror pre.CodeMirror-line,
|
||||||
|
.CodeMirror pre.CodeMirror-line-like {
|
||||||
|
font-family: revert;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -67,25 +67,20 @@ import LMSLogo from '@/components/Icons/LMSLogo.vue'
|
|||||||
import { sessionStore } from '@/stores/session'
|
import { sessionStore } from '@/stores/session'
|
||||||
import { Dropdown } from 'frappe-ui'
|
import { Dropdown } from 'frappe-ui'
|
||||||
import Apps from '@/components/Apps.vue'
|
import Apps from '@/components/Apps.vue'
|
||||||
import {
|
import { ChevronDown, LogIn, LogOut, User, Settings } from 'lucide-vue-next'
|
||||||
ChevronDown,
|
|
||||||
LogIn,
|
|
||||||
LogOut,
|
|
||||||
User,
|
|
||||||
ArrowRightLeft,
|
|
||||||
Settings,
|
|
||||||
} from 'lucide-vue-next'
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { convertToTitleCase } from '../utils'
|
import { convertToTitleCase } from '../utils'
|
||||||
import { usersStore } from '@/stores/user'
|
import { usersStore } from '@/stores/user'
|
||||||
import { ref, markRaw } from 'vue'
|
import { useSettings } from '@/stores/settings'
|
||||||
|
import { markRaw, watch, ref } from 'vue'
|
||||||
import SettingsModal from '@/components/Modals/Settings.vue'
|
import SettingsModal from '@/components/Modals/Settings.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const showSettingsModal = ref(false)
|
|
||||||
const { logout, branding } = sessionStore()
|
const { logout, branding } = sessionStore()
|
||||||
let { userResource } = usersStore()
|
let { userResource } = usersStore()
|
||||||
|
const settingsStore = useSettings()
|
||||||
let { isLoggedIn } = sessionStore()
|
let { isLoggedIn } = sessionStore()
|
||||||
|
const showSettingsModal = ref(false)
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
isCollapsed: {
|
isCollapsed: {
|
||||||
@@ -94,6 +89,13 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => settingsStore.isSettingsOpen,
|
||||||
|
(value) => {
|
||||||
|
showSettingsModal.value = value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const userDropdownOptions = [
|
const userDropdownOptions = [
|
||||||
{
|
{
|
||||||
icon: User,
|
icon: User,
|
||||||
@@ -118,7 +120,7 @@ const userDropdownOptions = [
|
|||||||
icon: Settings,
|
icon: Settings,
|
||||||
label: 'Settings',
|
label: 'Settings',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
showSettingsModal.value = true
|
settingsStore.isSettingsOpen = true
|
||||||
},
|
},
|
||||||
condition: () => {
|
condition: () => {
|
||||||
return userResource.data?.is_moderator
|
return userResource.data?.is_moderator
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
:items="[{ label: __('Batches'), route: { name: 'Batches' } }]"
|
:items="[{ label: __('Batches'), route: { name: 'Batches' } }]"
|
||||||
/>
|
/>
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
<div class="w-40">
|
<div class="w-44">
|
||||||
<Select
|
<Select
|
||||||
v-if="categories.data?.length"
|
v-if="categories.data?.length"
|
||||||
v-model="currentCategory"
|
v-model="currentCategory"
|
||||||
:options="categories.data"
|
:options="categories.data"
|
||||||
:placeholder="__('Filter')"
|
:placeholder="__('Category')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<router-link
|
<router-link
|
||||||
|
|||||||
@@ -109,6 +109,14 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="w-1/2 mb-4">
|
||||||
|
<Link
|
||||||
|
doctype="LMS Category"
|
||||||
|
v-model="course.category"
|
||||||
|
:label="__('Category')"
|
||||||
|
:onCreate="(value, close) => openSettings(close)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
v-model="instructors"
|
v-model="instructors"
|
||||||
doctype="User"
|
doctype="User"
|
||||||
@@ -221,18 +229,20 @@ import {
|
|||||||
showToast,
|
showToast,
|
||||||
getFileSize,
|
getFileSize,
|
||||||
updateDocumentTitle,
|
updateDocumentTitle,
|
||||||
} from '../utils'
|
} from '@/utils'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
import { FileText, X } from 'lucide-vue-next'
|
import { FileText, X } from 'lucide-vue-next'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import CourseOutline from '@/components/CourseOutline.vue'
|
import CourseOutline from '@/components/CourseOutline.vue'
|
||||||
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
||||||
import { capture } from '@/telemetry'
|
import { capture } from '@/telemetry'
|
||||||
|
import { useSettings } from '@/stores/settings'
|
||||||
|
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const newTag = ref('')
|
const newTag = ref('')
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const instructors = ref([])
|
const instructors = ref([])
|
||||||
|
const settingsStore = useSettings()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
courseName: {
|
courseName: {
|
||||||
@@ -463,6 +473,12 @@ const removeImage = () => {
|
|||||||
course.course_image = null
|
course.course_image = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openSettings = (close) => {
|
||||||
|
close()
|
||||||
|
settingsStore.activeTab = 'Categories'
|
||||||
|
settingsStore.isSettingsOpen = true
|
||||||
|
}
|
||||||
|
|
||||||
const check_permission = () => {
|
const check_permission = () => {
|
||||||
let user_is_instructor = false
|
let user_is_instructor = false
|
||||||
if (user.data?.is_moderator) return
|
if (user.data?.is_moderator) return
|
||||||
|
|||||||
@@ -8,6 +8,15 @@
|
|||||||
:items="[{ label: __('Courses'), route: { name: 'Courses' } }]"
|
:items="[{ label: __('Courses'), route: { name: 'Courses' } }]"
|
||||||
/>
|
/>
|
||||||
<div class="flex space-x-2 justify-end">
|
<div class="flex space-x-2 justify-end">
|
||||||
|
<div class="w-44">
|
||||||
|
<FormControl
|
||||||
|
v-if="categories.data?.length"
|
||||||
|
type="select"
|
||||||
|
v-model="currentCategory"
|
||||||
|
:options="categories.data"
|
||||||
|
:placeholder="__('Category')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="w-36">
|
<div class="w-36">
|
||||||
<FormControl
|
<FormControl
|
||||||
type="text"
|
type="text"
|
||||||
@@ -119,11 +128,19 @@ import {
|
|||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import CourseCard from '@/components/CourseCard.vue'
|
import CourseCard from '@/components/CourseCard.vue'
|
||||||
import { Plus, Search } from 'lucide-vue-next'
|
import { Plus, Search } from 'lucide-vue-next'
|
||||||
import { ref, computed, inject } from 'vue'
|
import { ref, computed, inject, onMounted, watch } from 'vue'
|
||||||
import { updateDocumentTitle } from '@/utils'
|
import { updateDocumentTitle } from '@/utils'
|
||||||
|
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
|
const currentCategory = ref(null)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
let queries = new URLSearchParams(location.search)
|
||||||
|
if (queries.has('category')) {
|
||||||
|
currentCategory.value = queries.get('category')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const courses = createResource({
|
const courses = createResource({
|
||||||
url: 'lms.lms.utils.get_courses',
|
url: 'lms.lms.utils.get_courses',
|
||||||
@@ -168,18 +185,57 @@ const addToTabs = (label) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getCourses = (type) => {
|
const getCourses = (type) => {
|
||||||
|
let courseList = courses.data[type]
|
||||||
if (searchQuery.value) {
|
if (searchQuery.value) {
|
||||||
let query = searchQuery.value.toLowerCase()
|
let query = searchQuery.value.toLowerCase()
|
||||||
return courses.data[type].filter(
|
courseList = courseList.filter(
|
||||||
(course) =>
|
(course) =>
|
||||||
course.title.toLowerCase().includes(query) ||
|
course.title.toLowerCase().includes(query) ||
|
||||||
course.short_introduction.toLowerCase().includes(query) ||
|
course.short_introduction.toLowerCase().includes(query) ||
|
||||||
course.tags.filter((tag) => tag.toLowerCase().includes(query)).length
|
course.tags.filter((tag) => tag.toLowerCase().includes(query)).length
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return courses.data[type]
|
if (currentCategory.value && currentCategory.value != '') {
|
||||||
|
courseList = courseList.filter(
|
||||||
|
(course) => course.category == currentCategory.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return courseList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const categories = createResource({
|
||||||
|
url: 'lms.lms.api.get_categories',
|
||||||
|
makeParams() {
|
||||||
|
return {
|
||||||
|
doctype: 'LMS Course',
|
||||||
|
filters: {
|
||||||
|
published: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cache: ['courseCategories'],
|
||||||
|
auto: true,
|
||||||
|
transform(data) {
|
||||||
|
data.unshift({
|
||||||
|
label: '',
|
||||||
|
value: null,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => currentCategory.value,
|
||||||
|
() => {
|
||||||
|
let queries = new URLSearchParams(location.search)
|
||||||
|
if (currentCategory.value) {
|
||||||
|
queries.set('category', currentCategory.value)
|
||||||
|
} else {
|
||||||
|
queries.delete('category')
|
||||||
|
}
|
||||||
|
history.pushState(null, '', `${location.pathname}?${queries.toString()}`)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const pageMeta = computed(() => {
|
const pageMeta = computed(() => {
|
||||||
return {
|
return {
|
||||||
title: 'Courses',
|
title: 'Courses',
|
||||||
|
|||||||
12
frontend/src/stores/settings.js
Normal file
12
frontend/src/stores/settings.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export const useSettings = defineStore('settings', () => {
|
||||||
|
const isSettingsOpen = ref(false)
|
||||||
|
const activeTab = ref(null)
|
||||||
|
|
||||||
|
return {
|
||||||
|
isSettingsOpen,
|
||||||
|
activeTab,
|
||||||
|
}
|
||||||
|
})
|
||||||
2183
frontend/yarn.lock
Normal file
2183
frontend/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
|||||||
__version__ = "2.5.0"
|
__version__ = "2.6.0"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
51
lms/fixtures/lms_category.json
Normal file
51
lms/fixtures/lms_category.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"category": "Web Development",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "LMS Category",
|
||||||
|
"modified": "2024-09-20 12:58:16.841571",
|
||||||
|
"name": "Web Development"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "Business",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "LMS Category",
|
||||||
|
"modified": "2024-09-20 12:58:32.304850",
|
||||||
|
"name": "Business"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "Design",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "LMS Category",
|
||||||
|
"modified": "2024-09-20 12:59:12.621022",
|
||||||
|
"name": "Design"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "Personal Development",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "LMS Category",
|
||||||
|
"modified": "2024-09-20 12:59:19.287404",
|
||||||
|
"name": "Personal Development"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "Finance",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "LMS Category",
|
||||||
|
"modified": "2024-09-20 12:58:28.579714",
|
||||||
|
"name": "Finance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "Frontend",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "LMS Category",
|
||||||
|
"modified": "2024-05-08 14:05:16.979275",
|
||||||
|
"name": "Frontend"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "Framework",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "LMS Category",
|
||||||
|
"modified": "2023-06-15 18:01:41.598282",
|
||||||
|
"name": "Framework"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -115,7 +115,7 @@ scheduler_events = {
|
|||||||
"daily": ["lms.job.doctype.job_opportunity.job_opportunity.update_job_openings"],
|
"daily": ["lms.job.doctype.job_opportunity.job_opportunity.update_job_openings"],
|
||||||
}
|
}
|
||||||
|
|
||||||
fixtures = ["Custom Field", "Function", "Industry"]
|
fixtures = ["Custom Field", "Function", "Industry", "LMS Category"]
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
# -------
|
# -------
|
||||||
|
|||||||
@@ -15,12 +15,13 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Category",
|
"label": "Category",
|
||||||
|
"reqd": 1,
|
||||||
"unique": 1
|
"unique": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-15 15:14:11.341961",
|
"modified": "2024-09-23 19:33:49.593950",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Category",
|
"name": "LMS Category",
|
||||||
@@ -55,5 +56,6 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"title_field": "category"
|
"title_field": "category",
|
||||||
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@ from frappe.utils import (
|
|||||||
get_datetime,
|
get_datetime,
|
||||||
nowtime,
|
nowtime,
|
||||||
get_time,
|
get_time,
|
||||||
|
get_fullname,
|
||||||
)
|
)
|
||||||
from lms.lms.utils import get_evaluator
|
from lms.lms.utils import get_evaluator
|
||||||
import json
|
import json
|
||||||
@@ -32,6 +33,7 @@ class LMSCertificateRequest(Document):
|
|||||||
def set_evaluator(self):
|
def set_evaluator(self):
|
||||||
if not self.evaluator:
|
if not self.evaluator:
|
||||||
self.evaluator = get_evaluator(self.course, self.batch_name)
|
self.evaluator = get_evaluator(self.course, self.batch_name)
|
||||||
|
self.evaluator_name = get_fullname(self.evaluator)
|
||||||
|
|
||||||
def validate_unavailability(self):
|
def validate_unavailability(self):
|
||||||
if self.evaluator:
|
if self.evaluator:
|
||||||
|
|||||||
@@ -16,10 +16,12 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"title",
|
"title",
|
||||||
"video_link",
|
"video_link",
|
||||||
"image",
|
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"instructors",
|
"instructors",
|
||||||
"tags",
|
"tags",
|
||||||
|
"column_break_htgn",
|
||||||
|
"image",
|
||||||
|
"category",
|
||||||
"status",
|
"status",
|
||||||
"section_break_7",
|
"section_break_7",
|
||||||
"published",
|
"published",
|
||||||
@@ -237,6 +239,16 @@
|
|||||||
"fieldname": "certification_tab",
|
"fieldname": "certification_tab",
|
||||||
"fieldtype": "Tab Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Certification"
|
"label": "Certification"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_htgn",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "category",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Category",
|
||||||
|
"options": "LMS Category"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_published_field": "published",
|
"is_published_field": "published",
|
||||||
@@ -263,7 +275,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"make_attachments_public": 1,
|
"make_attachments_public": 1,
|
||||||
"modified": "2024-07-12 13:54:40.474097",
|
"modified": "2024-09-21 10:23:58.633912",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Course",
|
"name": "LMS Course",
|
||||||
|
|||||||
@@ -23,15 +23,9 @@
|
|||||||
"show_emails",
|
"show_emails",
|
||||||
"signup_settings_tab",
|
"signup_settings_tab",
|
||||||
"signup_settings_section",
|
"signup_settings_section",
|
||||||
"terms_of_use",
|
|
||||||
"terms_page",
|
|
||||||
"user_category",
|
|
||||||
"column_break_9",
|
"column_break_9",
|
||||||
"privacy_policy",
|
"custom_signup_content",
|
||||||
"privacy_policy_page",
|
"user_category",
|
||||||
"column_break_12",
|
|
||||||
"cookie_policy",
|
|
||||||
"cookie_policy_page",
|
|
||||||
"sidebar_tab",
|
"sidebar_tab",
|
||||||
"items_in_sidebar_section",
|
"items_in_sidebar_section",
|
||||||
"courses",
|
"courses",
|
||||||
@@ -92,60 +86,14 @@
|
|||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break",
|
||||||
"label": "Show Tab in Batch"
|
"label": "Show Tab in Batch"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "terms_of_use",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Show Terms of Use on Signup"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "terms_of_use",
|
|
||||||
"fieldname": "terms_page",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Terms of Use Page",
|
|
||||||
"mandatory_depends_on": "terms_of_use",
|
|
||||||
"options": "Web Page"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "signup_settings_section",
|
"fieldname": "signup_settings_section",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "privacy_policy",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Show Privacy Policy on Signup"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "privacy_policy",
|
|
||||||
"fieldname": "privacy_policy_page",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Privacy Policy Page",
|
|
||||||
"mandatory_depends_on": "privacy_policy",
|
|
||||||
"options": "Web Page"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_12",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_9",
|
"fieldname": "column_break_9",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "cookie_policy",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Show Cookie Policy on Signup"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "cookie_policy",
|
|
||||||
"fieldname": "cookie_policy_page",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Cookie Policy Page",
|
|
||||||
"mandatory_depends_on": "cookie_policy",
|
|
||||||
"options": "Web Page"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "user_category",
|
"fieldname": "user_category",
|
||||||
@@ -378,12 +326,17 @@
|
|||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Sidebar Items",
|
"label": "Sidebar Items",
|
||||||
"options": "LMS Sidebar Item"
|
"options": "LMS Sidebar Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "custom_signup_content",
|
||||||
|
"fieldtype": "HTML Editor",
|
||||||
|
"label": "Custom Signup Content"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-08-13 19:02:58.714080",
|
"modified": "2024-09-23 17:57:01.350020",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Settings",
|
"name": "LMS Settings",
|
||||||
|
|||||||
@@ -722,17 +722,6 @@ def get_lesson_count(course):
|
|||||||
return lesson_count
|
return lesson_count
|
||||||
|
|
||||||
|
|
||||||
def get_restriction_details():
|
|
||||||
user = frappe.db.get_value(
|
|
||||||
"User", frappe.session.user, ["profile_complete", "username"], as_dict=True
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"restrict": not user.profile_complete,
|
|
||||||
"username": user.username,
|
|
||||||
"prefix": frappe.get_hooks("profile_url_prefix")[0] or "/users/",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_memberships(member):
|
def get_all_memberships(member):
|
||||||
return frappe.get_all(
|
return frappe.get_all(
|
||||||
"LMS Enrollment",
|
"LMS Enrollment",
|
||||||
@@ -1220,6 +1209,7 @@ def get_course_details(course):
|
|||||||
"featured",
|
"featured",
|
||||||
"disable_self_learning",
|
"disable_self_learning",
|
||||||
"published_on",
|
"published_on",
|
||||||
|
"category",
|
||||||
"status",
|
"status",
|
||||||
"paid_course",
|
"paid_course",
|
||||||
"course_price",
|
"course_price",
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Frappe LMS VERSION\n"
|
"Project-Id-Version: Frappe LMS VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: jannat@frappe.io\n"
|
"Report-Msgid-Bugs-To: jannat@frappe.io\n"
|
||||||
"POT-Creation-Date: 2024-09-13 16:04+0000\n"
|
"POT-Creation-Date: 2024-09-20 16:04+0000\n"
|
||||||
"PO-Revision-Date: 2024-09-13 16:04+0000\n"
|
"PO-Revision-Date: 2024-09-20 16:04+0000\n"
|
||||||
"Last-Translator: jannat@frappe.io\n"
|
"Last-Translator: jannat@frappe.io\n"
|
||||||
"Language-Team: jannat@frappe.io\n"
|
"Language-Team: jannat@frappe.io\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
@@ -212,7 +212,7 @@ msgstr ""
|
|||||||
msgid "Assessment Type"
|
msgid "Assessment Type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_batch/lms_batch.py:71
|
#: lms/doctype/lms_batch/lms_batch.py:66
|
||||||
msgid "Assessment {0} has already been added to this batch."
|
msgid "Assessment {0} has already been added to this batch."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -387,7 +387,7 @@ msgstr ""
|
|||||||
msgid "Batch Updated"
|
msgid "Batch Updated"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_batch/lms_batch.py:42
|
#: lms/doctype/lms_batch/lms_batch.py:37
|
||||||
msgid "Batch end date cannot be before the batch start date"
|
msgid "Batch end date cannot be before the batch start date"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -948,11 +948,11 @@ msgstr ""
|
|||||||
msgid "Course Title"
|
msgid "Course Title"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_batch/lms_batch.py:327
|
#: lms/doctype/lms_batch/lms_batch.py:324
|
||||||
msgid "Course already added to the batch."
|
msgid "Course already added to the batch."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_batch/lms_batch.py:60
|
#: lms/doctype/lms_batch/lms_batch.py:55
|
||||||
msgid "Course {0} has already been added to this batch."
|
msgid "Course {0} has already been added to this batch."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1248,7 +1248,7 @@ msgstr ""
|
|||||||
msgid "Enrolled successfully"
|
msgid "Enrolled successfully"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_batch/lms_batch.py:92
|
#: lms/doctype/lms_batch/lms_batch.py:89
|
||||||
msgid "Enrollment Confirmation for the Next Training Batch"
|
msgid "Enrollment Confirmation for the Next Training Batch"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1297,7 +1297,7 @@ msgstr ""
|
|||||||
msgid "Evaluation Request"
|
msgid "Evaluation Request"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_batch/lms_batch.py:89
|
#: lms/doctype/lms_batch/lms_batch.py:73
|
||||||
msgid "Evaluation end date cannot be less than the batch end date."
|
msgid "Evaluation end date cannot be less than the batch end date."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2788,7 +2788,7 @@ msgstr ""
|
|||||||
msgid "Please click on the following button to set your new password"
|
msgid "Please click on the following button to set your new password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_batch/lms_batch.py:238
|
#: lms/doctype/lms_batch/lms_batch.py:235
|
||||||
msgid "Please enable Zoom Settings to use this feature."
|
msgid "Please enable Zoom Settings to use this feature."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3165,19 +3165,19 @@ msgstr ""
|
|||||||
msgid "Route"
|
msgid "Route"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_batch/lms_batch.py:167
|
#: lms/doctype/lms_batch/lms_batch.py:164
|
||||||
msgid "Row #{0} Date cannot be outside the batch duration."
|
msgid "Row #{0} Date cannot be outside the batch duration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_batch/lms_batch.py:162
|
#: lms/doctype/lms_batch/lms_batch.py:159
|
||||||
msgid "Row #{0} End time cannot be outside the batch duration."
|
msgid "Row #{0} End time cannot be outside the batch duration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_batch/lms_batch.py:144
|
#: lms/doctype/lms_batch/lms_batch.py:141
|
||||||
msgid "Row #{0} Start time cannot be greater than or equal to end time."
|
msgid "Row #{0} Start time cannot be greater than or equal to end time."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_batch/lms_batch.py:153
|
#: lms/doctype/lms_batch/lms_batch.py:150
|
||||||
msgid "Row #{0} Start time cannot be outside the batch duration."
|
msgid "Row #{0} Start time cannot be outside the batch duration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3524,7 +3524,7 @@ msgstr ""
|
|||||||
msgid "Student Name"
|
msgid "Student Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_batch/lms_batch.py:49
|
#: lms/doctype/lms_batch/lms_batch.py:44
|
||||||
msgid "Student {0} has already been added to this batch."
|
msgid "Student {0} has already been added to this batch."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3697,7 +3697,7 @@ msgstr ""
|
|||||||
msgid "The course {0} is now available on {1}."
|
msgid "The course {0} is now available on {1}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:51
|
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:53
|
||||||
msgid "The evaluator of this course is unavailable from {0} to {1}. Please select a date after {1}"
|
msgid "The evaluator of this course is unavailable from {0} to {1}. Please select a date after {1}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3705,7 +3705,7 @@ msgstr ""
|
|||||||
msgid "The quiz has a time limit. For each question you will be given {0} seconds."
|
msgid "The quiz has a time limit. For each question you will be given {0} seconds."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:69
|
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:71
|
||||||
msgid "The slot is already booked by another participant."
|
msgid "The slot is already booked by another participant."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3713,7 +3713,7 @@ msgstr ""
|
|||||||
msgid "The status of your application has changed."
|
msgid "The status of your application has changed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_batch/lms_batch.py:135
|
#: lms/doctype/lms_batch/lms_batch.py:132
|
||||||
msgid "There are no seats available in this batch."
|
msgid "There are no seats available in this batch."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4106,7 +4106,7 @@ msgstr ""
|
|||||||
msgid "Write a review"
|
msgid "Write a review"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:93
|
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:95
|
||||||
msgid "You already have an evaluation on {0} at {1} for the course {2}."
|
msgid "You already have an evaluation on {0} at {1} for the course {2}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4139,11 +4139,11 @@ msgstr ""
|
|||||||
msgid "You can find their resume attached to this email."
|
msgid "You can find their resume attached to this email."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:113
|
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:115
|
||||||
msgid "You cannot schedule evaluations after {0}."
|
msgid "You cannot schedule evaluations after {0}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:102
|
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:104
|
||||||
msgid "You cannot schedule evaluations for past slots."
|
msgid "You cannot schedule evaluations for past slots."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4208,7 +4208,7 @@ msgstr ""
|
|||||||
msgid "Your evaluation for the course {0} has been scheduled on {1} at {2} {3}."
|
msgid "Your evaluation for the course {0} has been scheduled on {1} at {2} {3}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:123
|
#: lms/doctype/lms_certificate_request/lms_certificate_request.py:125
|
||||||
msgid "Your evaluation slot has been booked"
|
msgid "Your evaluation slot has been booked"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -90,4 +90,4 @@ lms.patches.v1_0.set_published_on
|
|||||||
lms.patches.v2_0.fix_progress_percentage
|
lms.patches.v2_0.fix_progress_percentage
|
||||||
lms.patches.v2_0.add_discussion_topic_titles
|
lms.patches.v2_0.add_discussion_topic_titles
|
||||||
lms.patches.v2_0.sidebar_settings
|
lms.patches.v2_0.sidebar_settings
|
||||||
lms.patches.v2_0.delete_certificate_request_notification
|
lms.patches.v2_0.delete_certificate_request_notification #18-09-2024
|
||||||
@@ -2,4 +2,10 @@ import frappe
|
|||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
frappe.db.delete("Notification", "Certificate Request Creation")
|
delete_notification("Certificate Request Creation")
|
||||||
|
delete_notification("Certificate Request Reminder")
|
||||||
|
|
||||||
|
|
||||||
|
def delete_notification(notification_name):
|
||||||
|
if frappe.db.exists("Notification", notification_name):
|
||||||
|
frappe.db.delete("Notification", notification_name)
|
||||||
|
|||||||
@@ -227,8 +227,7 @@ def assignment_renderer(detail):
|
|||||||
|
|
||||||
|
|
||||||
def show_custom_signup():
|
def show_custom_signup():
|
||||||
if frappe.db.get_single_value(
|
settings = frappe.get_single("LMS Settings")
|
||||||
"LMS Settings", "terms_of_use"
|
if settings.custom_signup_content or settings.user_category:
|
||||||
) or frappe.db.get_single_value("LMS Settings", "privacy_policy"):
|
|
||||||
return "lms/templates/signup-form.html"
|
return "lms/templates/signup-form.html"
|
||||||
return "frappe/templates/signup.html"
|
return "frappe/templates/signup.html"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
{% set custom_signup_content = frappe.db.get_single_value("LMS Settings", "custom_signup_content") %}
|
||||||
<form class="signup-form" role="form">
|
<form class="signup-form" role="form">
|
||||||
<div class="page-card-body">
|
<div class="page-card-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if custom_signup_content %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
@@ -39,11 +41,12 @@
|
|||||||
data-fieldtype="Check" data-fieldname="terms" id="signup-terms" required>
|
data-fieldtype="Check" data-fieldname="terms" id="signup-terms" required>
|
||||||
</span>
|
</span>
|
||||||
<span class="label-area">
|
<span class="label-area">
|
||||||
{{ _("I have read and agree to your {0}").format(get_signup_optin_checks()) }}
|
{{ custom_signup_content }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="page-card-actions">
|
<div class="page-card-actions">
|
||||||
<button class="btn btn-sm btn-primary btn-block btn-signup"
|
<button class="btn btn-sm btn-primary btn-block btn-signup"
|
||||||
|
|||||||
Reference in New Issue
Block a user