From dc2bf9f13ee82bd7c87ea69057d5d1ca771a00cf Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 23 Sep 2024 16:11:17 +0530 Subject: [PATCH] feat: category settings --- frontend/src/components/Categories.vue | 55 +++++++++++++- frontend/src/components/Modals/Settings.vue | 73 ++++++++++++------- frontend/src/components/UserDropdown.vue | 24 +++--- frontend/src/pages/CourseForm.vue | 18 ++++- frontend/src/pages/Courses.vue | 62 +++++++++++++++- frontend/src/stores/settings.js | 12 +++ .../doctype/lms_category/lms_category.json | 6 +- lms/lms/doctype/lms_course/lms_course.json | 16 +++- lms/lms/utils.py | 1 + 9 files changed, 218 insertions(+), 49 deletions(-) create mode 100644 frontend/src/stores/settings.js diff --git a/frontend/src/components/Categories.vue b/frontend/src/components/Categories.vue index 6349108c..f6771019 100644 --- a/frontend/src/components/Categories.vue +++ b/frontend/src/components/Categories.vue @@ -30,9 +30,11 @@
@@ -44,6 +46,7 @@ import { FormControl, createListResource, createResource, + debounce, } from 'frappe-ui' import { Plus, X } from 'lucide-vue-next' import { ref } from 'vue' @@ -99,4 +102,52 @@ const showCategoryForm = () => { categoryInput.value.$el.querySelector('input').focus() }, 0) } + +const updateCategory = createResource({ + url: 'frappe.client.set_value', + makeParams(values) { + return { + doctype: 'LMS Category', + name: values.name, + fieldname: { + category: values.value, + }, + } + }, +}) + +const update = debounce((name, value) => { + updateCategory.submit( + { + name: name, + value: value, + }, + { + onSuccess() { + categories.reload() + }, + } + ) +}, 500) + diff --git a/frontend/src/components/Modals/Settings.vue b/frontend/src/components/Modals/Settings.vue index a0f80c5d..49399c21 100644 --- a/frontend/src/components/Modals/Settings.vue +++ b/frontend/src/components/Modals/Settings.vue @@ -6,7 +6,7 @@

{{ __('Settings') }}

-
+
import { Dialog, createDocumentResource } from 'frappe-ui' import { ref, computed, watch } from 'vue' +import { useSettings } from '@/stores/settings' import SettingDetails from '../SettingDetails.vue' import SidebarLink from '@/components/SidebarLink.vue' import Members from '@/components/Members.vue' -import Categories from '../Categories.vue' +import Categories from '@/components/Categories.vue' const show = defineModel() const doctype = ref('LMS Settings') const activeTab = ref(null) +const settingsStore = useSettings() const data = createDocumentResource({ doctype: doctype.value, @@ -75,8 +79,8 @@ const data = createDocumentResource({ auto: true, }) -const tabs = computed(() => { - let _tabs = [ +const tabsStructure = computed(() => { + return [ { label: 'Settings', hideLabel: true, @@ -86,11 +90,23 @@ const tabs = computed(() => { description: 'Manage the members of your learning system', 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', icon: 'DollarSign', @@ -136,8 +152,8 @@ const tabs = computed(() => { ], }, { - label: 'Settings', - hideLabel: true, + label: 'Customise', + hideLabel: false, items: [ { label: 'Sidebar', @@ -179,12 +195,6 @@ const tabs = computed(() => { }, ], }, - ], - }, - { - label: 'Settings', - hideLabel: true, - items: [ { label: 'Email Templates', icon: 'MailPlus', @@ -210,12 +220,6 @@ const tabs = computed(() => { }, ], }, - ], - }, - { - label: 'Settings', - hideLabel: true, - items: [ { label: 'Signup', icon: 'LogIn', @@ -268,23 +272,36 @@ const tabs = computed(() => { ], }, ] +}) - return _tabs.map((tab) => { - tab.items = tab.items.filter((item) => { - if (item.condition) { - return item.condition() - } - return true - }) - return tab +const tabs = computed(() => { + return tabsStructure.value.map((tab) => { + return { + ...tab, + items: tab.items.filter((item) => { + return !item.condition || item.condition() + }), + } }) }) -watch(show, () => { +watch( + () => activeTab.value, + (value) => { + console.log('Tab watcher', value) + } +) + +watch(show, async () => { 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 { + console.log('else') activeTab.value = null + settingsStore.isSettingsOpen = false } }) diff --git a/frontend/src/components/UserDropdown.vue b/frontend/src/components/UserDropdown.vue index 348e7918..5071513d 100644 --- a/frontend/src/components/UserDropdown.vue +++ b/frontend/src/components/UserDropdown.vue @@ -67,25 +67,20 @@ import LMSLogo from '@/components/Icons/LMSLogo.vue' import { sessionStore } from '@/stores/session' import { Dropdown } from 'frappe-ui' import Apps from '@/components/Apps.vue' -import { - ChevronDown, - LogIn, - LogOut, - User, - ArrowRightLeft, - Settings, -} from 'lucide-vue-next' +import { ChevronDown, LogIn, LogOut, User, Settings } from 'lucide-vue-next' import { useRouter } from 'vue-router' import { convertToTitleCase } from '../utils' 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' const router = useRouter() -const showSettingsModal = ref(false) const { logout, branding } = sessionStore() let { userResource } = usersStore() +const settingsStore = useSettings() let { isLoggedIn } = sessionStore() +const showSettingsModal = ref(false) const props = defineProps({ isCollapsed: { @@ -94,6 +89,13 @@ const props = defineProps({ }, }) +watch( + () => settingsStore.isSettingsOpen, + (value) => { + showSettingsModal.value = value + } +) + const userDropdownOptions = [ { icon: User, @@ -118,7 +120,7 @@ const userDropdownOptions = [ icon: Settings, label: 'Settings', onClick: () => { - showSettingsModal.value = true + settingsStore.isSettingsOpen = true }, condition: () => { return userResource.data?.is_moderator diff --git a/frontend/src/pages/CourseForm.vue b/frontend/src/pages/CourseForm.vue index c6a82f36..a23fe42c 100644 --- a/frontend/src/pages/CourseForm.vue +++ b/frontend/src/pages/CourseForm.vue @@ -109,6 +109,14 @@ />
+
+ +
{ course.course_image = null } +const openSettings = (close) => { + close() + settingsStore.activeTab = 'Categories' + settingsStore.isSettingsOpen = true +} + const check_permission = () => { let user_is_instructor = false if (user.data?.is_moderator) return diff --git a/frontend/src/pages/Courses.vue b/frontend/src/pages/Courses.vue index 2291d067..ca7c94b4 100644 --- a/frontend/src/pages/Courses.vue +++ b/frontend/src/pages/Courses.vue @@ -8,6 +8,15 @@ :items="[{ label: __('Courses'), route: { name: 'Courses' } }]" />
+
+ +
{ + let queries = new URLSearchParams(location.search) + if (queries.has('category')) { + currentCategory.value = queries.get('category') + } +}) const courses = createResource({ url: 'lms.lms.utils.get_courses', @@ -168,18 +185,57 @@ const addToTabs = (label) => { } const getCourses = (type) => { + let courseList = courses.data[type] if (searchQuery.value) { let query = searchQuery.value.toLowerCase() - return courses.data[type].filter( + courseList = courseList.filter( (course) => course.title.toLowerCase().includes(query) || course.short_introduction.toLowerCase().includes(query) || 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(() => { return { title: 'Courses', diff --git a/frontend/src/stores/settings.js b/frontend/src/stores/settings.js new file mode 100644 index 00000000..61d6bedc --- /dev/null +++ b/frontend/src/stores/settings.js @@ -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, + } +}) diff --git a/lms/lms/doctype/lms_category/lms_category.json b/lms/lms/doctype/lms_category/lms_category.json index bd5ede67..0d11937c 100644 --- a/lms/lms/doctype/lms_category/lms_category.json +++ b/lms/lms/doctype/lms_category/lms_category.json @@ -15,12 +15,13 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Category", + "reqd": 1, "unique": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-06-15 15:14:11.341961", + "modified": "2024-09-23 15:25:21.319427", "modified_by": "Administrator", "module": "LMS", "name": "LMS Category", @@ -55,5 +56,6 @@ "sort_field": "modified", "sort_order": "DESC", "states": [], - "title_field": "category" + "title_field": "category", + "track_changes": 1 } \ No newline at end of file diff --git a/lms/lms/doctype/lms_course/lms_course.json b/lms/lms/doctype/lms_course/lms_course.json index f22970b4..851c6486 100644 --- a/lms/lms/doctype/lms_course/lms_course.json +++ b/lms/lms/doctype/lms_course/lms_course.json @@ -16,10 +16,12 @@ "field_order": [ "title", "video_link", - "image", "column_break_3", "instructors", "tags", + "column_break_htgn", + "image", + "category", "status", "section_break_7", "published", @@ -237,6 +239,16 @@ "fieldname": "certification_tab", "fieldtype": "Tab Break", "label": "Certification" + }, + { + "fieldname": "column_break_htgn", + "fieldtype": "Column Break" + }, + { + "fieldname": "category", + "fieldtype": "Link", + "label": "Category", + "options": "LMS Category" } ], "is_published_field": "published", @@ -263,7 +275,7 @@ } ], "make_attachments_public": 1, - "modified": "2024-07-12 13:54:40.474097", + "modified": "2024-09-21 10:23:58.633912", "modified_by": "Administrator", "module": "LMS", "name": "LMS Course", diff --git a/lms/lms/utils.py b/lms/lms/utils.py index a49208ad..1c4480b8 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -1220,6 +1220,7 @@ def get_course_details(course): "featured", "disable_self_learning", "published_on", + "category", "status", "paid_course", "course_price",