refactor: program list for students

This commit is contained in:
Jannat Patel
2025-08-18 15:51:07 +05:30
parent 625ddac65a
commit acd003814a
8 changed files with 235 additions and 47 deletions

View File

@@ -2,16 +2,9 @@
<Dialog
v-model="show"
:options="{
title: __('Program'),
size: '2xl',
actions: [{
label: __('Save'),
variant: 'solid',
onClick: ({ close }: { close: () => void }) => {
saveProgram(close)
}
}]
}"
title: programName === 'new' ? __('Create Program') : __('Edit Program'),
size: '2xl',
}"
>
<template #body-content>
<div class="text-base">
@@ -192,6 +185,25 @@
</template>
</Dialog>
</template>
<template #actions="{ close }">
<div class="flex justify-end space-x-2 group">
<Button
v-if="programName != 'new'"
@click="deleteProgram(close)"
variant="outline"
theme="red"
class="invisible group-hover:visible"
>
<template #prefix>
<Trash2 class="size-4 stroke-1.5" />
</template>
{{ __('Delete') }}
</Button>
<Button variant="solid" @click="saveProgram(close)">
{{ __('Save') }}
</Button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
@@ -499,6 +511,19 @@ const remove = async (
unselectAll()
}
const deleteProgram = (close: () => void) => {
if (props.programName == 'new') return
programs.value?.delete.submit(props.programName, {
onSuccess() {
toast.success(__('Program deleted successfully'))
close()
},
onError(err: any) {
toast.warning(__(err.messages?.[0] || err))
},
})
}
const courseColumns = computed(() => {
return [
{

View File

@@ -10,36 +10,38 @@
{{ __('New') }}
</Button>
</header>
<div
v-if="programs.data?.length"
class="grid grid-cols-3 gap-5 py-10 w-3/4 mx-auto"
>
<div
v-for="program in programs.data"
@click="openForm(program.name)"
class="border rounded-md p-3 hover:border-outline-gray-3 cursor-pointer space-y-2"
>
<div class="text-lg font-semibold">
{{ program.name }}
</div>
<div class="flex items-center space-x-1">
<BookOpen class="h-4 w-4 stroke-1.5 mr-1" />
<span>
{{ program.course_count }}
{{ program.course_count == 1 ? __('Course') : __('Courses') }}
</span>
</div>
<div class="flex items-center space-x-1">
<User class="h-4 w-4 stroke-1.5 mr-1" />
<span>
{{ program.member_count || 0 }}
{{ program.member_count == 1 ? __('member') : __('members') }}
</span>
<div v-if="programs.data?.length && !isStudent" class="py-10 w-3/4 mx-auto">
<div class="text-lg font-semibold text-ink-gray-9 mb-5">
{{ __('{0} Programs').format(programs.data.length) }}
</div>
<div class="grid grid-cols-3 gap-5">
<div
v-for="program in programs.data"
@click="openForm(program.name)"
class="border rounded-md p-3 hover:border-outline-gray-3 cursor-pointer space-y-2"
>
<div class="text-lg font-semibold">
{{ program.name }}
</div>
<div class="flex items-center space-x-1">
<BookOpen class="h-4 w-4 stroke-1.5 mr-1" />
<span>
{{ program.course_count }}
{{ program.course_count == 1 ? __('Course') : __('Courses') }}
</span>
</div>
<div class="flex items-center space-x-1">
<User class="h-4 w-4 stroke-1.5 mr-1" />
<span>
{{ program.member_count || 0 }}
{{ program.member_count == 1 ? __('member') : __('members') }}
</span>
</div>
</div>
</div>
</div>
<StudentPrograms v-else-if="isStudent" />
<EmptyState v-else type="Programs" />
<ProgramForm
v-model="showForm"
:programName="currentProgram"
@@ -48,11 +50,12 @@
</template>
<script setup>
import { Breadcrumbs, Button, usePageMeta, createListResource } from 'frappe-ui'
import { computed, inject, ref } from 'vue'
import { computed, inject, onMounted, ref } from 'vue'
import { BookOpen, Plus, User } from 'lucide-vue-next'
import EmptyState from '@/components/EmptyState.vue'
import { sessionStore } from '../../stores/session'
import ProgramForm from '@/pages/Programs/ProgramForm.vue'
import EmptyState from '@/components/EmptyState.vue'
import StudentPrograms from '@/pages/Programs/StudentPrograms.vue'
const { brand } = sessionStore()
const user = inject('$user')
@@ -60,6 +63,15 @@ const showForm = ref(false)
const currentProgram = ref(null)
const readOnlyMode = window.read_only_mode
onMounted(() => {
if (!user.data) {
window.location.href = '/login'
}
if (user.data?.is_moderator || user.data?.is_instructor) {
programs.reload()
}
})
const programs = createListResource({
doctype: 'LMS Program',
cache: ['program'],
@@ -72,7 +84,7 @@ const programs = createListResource({
'enforce_course_order',
'allow_self_enrollment',
],
auto: true,
auto: false,
orderBy: 'creation desc',
})
@@ -88,6 +100,10 @@ const openForm = (programName) => {
showForm.value = true
}
const isStudent = computed(() => {
return user.data?.is_student || false
})
const breadcrumbs = computed(() => [
{
label: __('Programs'),

View File

@@ -0,0 +1,87 @@
<template>
<div class="py-10 w-3/4 mx-auto">
<div class="flex items-center justify-between mb-5">
<div class="text-lg text-ink-gray-9 font-semibold">
{{ __('All Programs') }}
</div>
<TabButtons v-model="currentTab" :buttons="tabs" class="w-fit" />
</div>
<div v-for="(data, category) in programs.data">
<div v-if="category == currentTab" class="grid grid-cols-3 gap-5">
<div
v-for="program in data"
class="border rounded-md p-3 hover:border-outline-gray-3 cursor-pointer"
>
<div class="text-lg font-semibold mb-2">
{{ program.name }}
</div>
<!-- <div class="flex items-center space-x-10">
<div class="flex items-center space-x-1">
<BookOpen class="h-4 w-4 stroke-1.5" />
<span>
{{ program.course_count }}
</span>
</div>
<div class="flex items-center space-x-1">
<User class="h-4 w-4 stroke-1.5" />
<span>
{{ program.member_count || 0 }}
</span>
</div>
</div> -->
<div class="flex items-center space-x-5 text-sm text-ink-gray-7">
<div class="flex items-center space-x-1">
<BookOpen class="size-3 stroke-1.5" />
<span>
{{ program.course_count }}
{{ program.course_count == 1 ? __('course') : __('courses') }}
</span>
</div>
<div class="flex items-center space-x-1">
<User class="size-4 stroke-1.5" />
<span>
{{ program.member_count || 0 }}
{{ program.member_count == 1 ? __('member') : __('members') }}
</span>
</div>
</div>
<div v-if="Object.keys(program).includes('progress')" class="mt-5">
<ProgressBar :progress="program.progress" />
<div class="text-sm mt-1">
{{ Math.ceil(program.progress) }}% {{ __('completed') }}
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { createResource, TabButtons } from 'frappe-ui'
import { computed, ref } from 'vue'
import { BookOpen, User } from 'lucide-vue-next'
import ProgressBar from '@/components/ProgressBar.vue'
const currentTab = ref('enrolled')
const programs = createResource({
url: 'lms.lms.utils.get_programs',
auto: true,
})
const tabs = computed(() => {
return [
{
label: __('Enrolled'),
value: 'enrolled',
},
{
label: __('Published'),
value: 'published',
},
]
})
</script>