fix: misc ui issues

This commit is contained in:
Jannat Patel
2025-05-13 20:04:06 +05:30
parent d9d031ed2b
commit 0a71620046
22 changed files with 2904 additions and 166 deletions

View File

@@ -125,7 +125,7 @@
@click="redirectToWebsite()"
/>
</Tooltip>
<Tooltip :text="__('Help')">
<Tooltip v-if="showOnboarding" :text="__('Help')">
<CircleHelp
class="size-4 stroke-1.5 text-ink-gray-7 cursor-pointer"
@click="

View File

@@ -1,5 +1,5 @@
<template>
<div class="">
<div v-if="batch.data" class="">
<div class="w-full flex items-center justify-between pb-4">
<div class="font-medium text-ink-gray-7">
{{ __('Statistics') }}
@@ -46,7 +46,7 @@
</div>
<div class="flex items-center space-x-2">
<span class="font-semibold">
{{ batch.courses?.length }}
{{ batch.data.courses?.length }}
</span>
<span>
{{ __('Courses') }}
@@ -201,9 +201,10 @@
</div>
<StudentModal
:batch="props.batch.name"
:batch="props.batch.data.name"
v-model="showStudentModal"
v-model:reloadStudents="students"
v-model:batchModal="props.batch"
/>
<BatchStudentProgress
:student="selectedStudent"
@@ -258,15 +259,14 @@ const props = defineProps({
const students = createResource({
url: 'lms.lms.utils.get_batch_students',
cache: ['students', props.batch.name],
params: {
batch: props.batch?.name,
batch: props.batch?.data?.name,
},
auto: true,
onSuccess(data) {
chartData.value = getChartData()
showProgressChart.value =
data.length && (props.batch?.courses?.length || assessmentCount.value)
data.length && (props.batch?.data?.courses?.length || assessmentCount.value)
},
})
@@ -323,6 +323,7 @@ const removeStudents = (selections, unselectAll) => {
{
onSuccess(data) {
students.reload()
props.batch.reload()
toast.success(__('Students deleted successfully'))
unselectAll()
},
@@ -434,7 +435,7 @@ const certificationCount = createResource({
params: {
doctype: 'LMS Certificate',
filters: {
batch_name: props.batch.name,
batch_name: props.batch?.data?.name,
},
},
auto: true,

View File

@@ -92,7 +92,7 @@
{{ option.label }}
</div>
<div
v-if="option.description"
v-if="option.description && option.description != option.label"
class="text-xs text-ink-gray-7"
v-html="option.description"
></div>

View File

@@ -31,6 +31,7 @@
<div class="mb-4">
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Announcement') }}
<span class="text-ink-red-3">*</span>
</div>
<TextEditor
:fixedMenu="true"
@@ -86,10 +87,13 @@ const makeAnnouncement = (close) => {
{
validate() {
if (!props.students.length) {
return 'No students in this batch'
return __('No students in this batch')
}
if (!announcement.subject) {
return 'Subject is required'
return __('Subject is required')
}
if (!announcement.announcement) {
return __('Announcement is required')
}
},
onSuccess() {

View File

@@ -25,6 +25,21 @@
v-model="assessment"
:doctype="assessmentType"
:label="__('Assessment')"
:onCreate="(value, close) => {
close()
if (assessmentType === 'LMS Quiz') {
router.push({
name: 'QuizForm',
params: {
quizID: 'new',
},
})
} else if (assessmentType === 'LMS Assignment') {
router.push({
name: 'Assignments'
})
}
}"
/>
</div>
</template>
@@ -34,11 +49,13 @@
import { Dialog, FormControl, createResource, toast } from 'frappe-ui'
import Link from '@/components/Controls/Link.vue'
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
const show = defineModel()
const assessmentType = ref(null)
const assessment = ref(null)
const assessments = defineModel('assessments')
const router = useRouter()
const props = defineProps({
batch: {

View File

@@ -19,12 +19,21 @@
v-model="course"
:label="__('Course')"
:required="true"
:onCreate="(value, close) => {
close()
router.push({
name: 'CourseForm',
params: {
courseName: 'new'
}
})
}"
/>
<Link
doctype="Course Evaluator"
v-model="evaluator"
:label="__('Evaluator')"
:onCreate="(value, close) => openSettings(close)"
:onCreate="(value, close) => openSettings('Evaluators', close)"
class="mt-4"
/>
</template>
@@ -35,15 +44,16 @@ import { Dialog, createResource, toast } from 'frappe-ui'
import { ref, inject } from 'vue'
import Link from '@/components/Controls/Link.vue'
import { useOnboarding } from 'frappe-ui/frappe'
import { useSettings } from '@/stores/settings'
import { openSettings } from '@/utils'
import { useRouter } from 'vue-router'
const show = defineModel()
const course = ref(null)
const evaluator = ref(null)
const user = inject('$user')
const courses = defineModel('courses')
const router = useRouter()
const { updateOnboardingStep } = useOnboarding('learning')
const settingsStore = useSettings()
const props = defineProps({
batch: {
@@ -87,10 +97,4 @@ const addCourse = (close) => {
}
)
}
const openSettings = (close) => {
close()
settingsStore.activeTab = 'Evaluators'
settingsStore.isSettingsOpen = true
}
</script>

View File

@@ -14,7 +14,7 @@
<div class="text-xl font-semibold">
{{ student.full_name }}
</div>
<Badge :theme="student.progress === 100 ? 'green' : 'red'">
<Badge v-if="Object.keys(student.assessments).length || Object.keys(student.courses).length" :theme="student.progress === 100 ? 'green' : 'red'">
{{ student.progress }}% {{ __('Complete') }}
</Badge>
</div>
@@ -26,7 +26,7 @@
<div class="space-y-8">
<!-- Assessments -->
<div class="space-y-2 text-sm">
<div v-if="Object.keys(student.assessments).length" class="space-y-2 text-sm">
<div class="flex items-center border-b pb-1 font-medium">
<span class="flex-1">
{{ __('Assessment') }}
@@ -73,7 +73,7 @@
</div>
<!-- Courses -->
<div class="space-y-2 text-sm">
<div v-if="Object.keys(student.courses).length" class="space-y-2 text-sm">
<div class="flex items-center border-b pb-1 font-medium">
<span class="flex-1">
{{ __('Courses') }}

View File

@@ -19,6 +19,9 @@
doctype="User"
v-model="student"
:filters="{ ignore_user_type: 1 }"
:onCreate="(value, close) => {
openSettings('Members', close)
}"
/>
</div>
</template>
@@ -29,8 +32,10 @@ import { Dialog, createResource, toast } from 'frappe-ui'
import { ref, inject } from 'vue'
import Link from '@/components/Controls/Link.vue'
import { useOnboarding } from 'frappe-ui/frappe'
import { openSettings } from '@/utils'
const students = defineModel('reloadStudents')
const batchModal = defineModel('batchModal')
const student = ref()
const user = inject('$user')
const { updateOnboardingStep } = useOnboarding('learning')
@@ -65,6 +70,7 @@ const addStudent = (close) => {
updateOnboardingStep('add_batch_student')
students.value.reload()
batchModal.value.reload()
student.value = null
close()
},

View File

@@ -67,7 +67,7 @@
<BatchDashboard :batch="batch" :isStudent="isStudent" />
</div>
<div v-else-if="tab.label == 'Dashboard'">
<BatchStudents :batch="batch.data" />
<BatchStudents :batch="batch" />
</div>
<div v-else-if="tab.label == 'Classes'">
<LiveClass :batch="batch.data.name" />
@@ -357,7 +357,11 @@ watch(tabIndex, () => {
const canMakeAnnouncement = () => {
if (readOnlyMode) return false
if (batch.data) return user.data?.is_moderator || user.data?.is_evaluator
if (!batch.data?.students?.length)
return false
return user.data?.is_moderator || user.data?.is_evaluator
}
usePageMeta(() => {

View File

@@ -285,15 +285,14 @@ import { Image } from 'lucide-vue-next'
import { capture } from '@/telemetry'
import { useOnboarding } from 'frappe-ui/frappe'
import { sessionStore } from '../stores/session'
import { useSettings } from '@/stores/settings'
import MultiSelect from '@/components/Controls/MultiSelect.vue'
import Link from '@/components/Controls/Link.vue'
import { openSettings } from '@/utils'
const router = useRouter()
const user = inject('$user')
const { brand } = sessionStore()
const { updateOnboardingStep } = useOnboarding('learning')
const settingsStore = useSettings()
const props = defineProps({
batchName: {
@@ -500,11 +499,7 @@ const validateFile = (file) => {
}
}
const openSettings = (category, close) => {
close()
settingsStore.activeTab = category
settingsStore.isSettingsOpen = true
}
const breadcrumbs = computed(() => {
let crumbs = [

View File

@@ -20,12 +20,14 @@
</header>
<div class="p-5 pb-10">
<div
v-if="batchCount"
class="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:items-center justify-between mb-5"
>
<div class="text-lg text-ink-gray-9 font-semibold">
{{ __('All Batches') }}
</div>
<div
v-if="batches.data?.length || batchCount"
class="flex flex-col space-y-2 lg:space-y-0 lg:flex-row lg:items-center lg:space-x-4"
>
<TabButtons
@@ -86,6 +88,7 @@
import {
Breadcrumbs,
Button,
call,
createListResource,
FormControl,
Select,
@@ -93,7 +96,7 @@ import {
usePageMeta,
} from 'frappe-ui'
import { computed, inject, onMounted, ref, watch } from 'vue'
import { BookOpen, Plus } from 'lucide-vue-next'
import { Plus } from 'lucide-vue-next'
import { sessionStore } from '@/stores/session'
import BatchCard from '@/components/BatchCard.vue'
import EmptyState from '@/components/EmptyState.vue'
@@ -112,10 +115,12 @@ const is_student = computed(() => user.data?.is_student)
const currentTab = ref(is_student.value ? 'All' : 'Upcoming')
const orderBy = ref('start_date')
const readOnlyMode = window.read_only_mode
const batchCount = ref(0)
onMounted(() => {
setFiltersFromQuery()
updateBatches()
getBatchCount()
categories.value = [
{
label: '',
@@ -293,6 +298,14 @@ const canCreateBatch = () => {
return false
}
const getBatchCount = () => {
call('frappe.client.get_count', {
doctype: 'LMS Batch',
}).then((data) => {
batchCount.value = data
})
}
const breadcrumbs = computed(() => [
{
label: __('Batches'),

View File

@@ -3,7 +3,7 @@
class="sticky flex items-center justify-between top-0 z-10 border-b bg-surface-white px-3 py-2.5 sm:px-5"
>
<Breadcrumbs :items="breadcrumbs" />
<router-link :to="{ name: 'Batches' }">
<router-link :to="{ name: 'Batches', query: { 'certification': true } }">
<Button>
<template #prefix>
<GraduationCap class="h-4 w-4 stroke-1.5" />

View File

@@ -282,12 +282,12 @@ import {
watch,
getCurrentInstance,
} from 'vue'
import { Check, Image, Trash2, X } from 'lucide-vue-next'
import { Image, Trash2, X } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import { capture } from '@/telemetry'
import { useOnboarding } from 'frappe-ui/frappe'
import { sessionStore } from '../stores/session'
import { useSettings } from '@/stores/settings'
import { openSettings } from '@/utils'
import Link from '@/components/Controls/Link.vue'
import CourseOutline from '@/components/CourseOutline.vue'
import MultiSelect from '@/components/Controls/MultiSelect.vue'
@@ -297,7 +297,6 @@ const newTag = ref('')
const { brand } = sessionStore()
const router = useRouter()
const instructors = ref([])
const settingsStore = useSettings()
const app = getCurrentInstance()
const { updateOnboardingStep } = useOnboarding('learning')
const { $dialog } = app.appContext.config.globalProperties
@@ -566,12 +565,6 @@ const check_permission = () => {
}
}
const openSettings = (category, close) => {
close()
settingsStore.activeTab = category
settingsStore.isSettingsOpen = true
}
const breadcrumbs = computed(() => {
let crumbs = [
{

View File

@@ -20,12 +20,14 @@
</header>
<div class="p-5 pb-10">
<div
v-if="courseCount"
class="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:items-center justify-between mb-5"
>
<div class="text-lg text-ink-gray-9 font-semibold">
{{ __('All Courses') }}
</div>
<div
v-if="courses.data?.length || courseCount"
class="flex flex-col space-y-2 lg:space-y-0 lg:flex-row lg:items-center lg:space-x-4"
>
<TabButtons :buttons="courseTabs" v-model="currentTab" />
@@ -89,7 +91,7 @@ import {
usePageMeta,
} from 'frappe-ui'
import { computed, inject, onMounted, ref, watch } from 'vue'
import { BookOpen, Plus } from 'lucide-vue-next'
import { Plus } from 'lucide-vue-next'
import { sessionStore } from '@/stores/session'
import { canCreateCourse } from '@/utils'
import CourseCard from '@/components/CourseCard.vue'
@@ -107,12 +109,13 @@ const certification = ref(false)
const filters = ref({})
const currentTab = ref('Live')
const { brand } = sessionStore()
const readOnlyMode = window.read_only_mode
const courseCount = ref(0)
onMounted(() => {
identifyUserPersona()
setFiltersFromQuery()
updateCourses()
getCourseCount()
categories.value = [
{
label: '',
@@ -161,19 +164,22 @@ const identifyUserPersona = async () => {
if (user.data?.is_system_manager && !user.data?.developer_mode) {
let personaCaptured = await isPersonaCaptured()
if (personaCaptured) return
call('frappe.client.get_count', {
doctype: 'LMS Course',
}).then((data) => {
if (!data) {
router.push({
name: 'PersonaForm',
})
}
})
if (!courseCount.value) {
router.push({
name: 'PersonaForm',
})
}
}
}
const getCourseCount = () => {
call('frappe.client.get_count', {
doctype: 'LMS Course',
}).then((data) => {
courseCount.value = data
})
}
const updateCourses = () => {
updateFilters()
courses.update({

View File

@@ -9,7 +9,7 @@
</Button>
</header>
<div class="py-5">
<div class="container border-b mb-4 pb-4">
<div class="container border-b mb-4 pb-5">
<div class="text-lg font-semibold mb-4">
{{ __('Job Details') }}
</div>
@@ -20,6 +20,15 @@
:label="__('Title')"
:required="true"
/>
<FormControl
v-model="job.type"
:label="__('Type')"
type="select"
:options="jobTypes"
:required="true"
/>
</div>
<div class="space-y-4">
<FormControl
v-model="job.location"
:label="__('City')"
@@ -31,17 +40,8 @@
:label="__('Country')"
:required="true"
/>
</div>
<div>
<FormControl
v-model="job.type"
:label="__('Type')"
type="select"
:options="jobTypes"
class="mb-4"
:required="true"
/>
<FormControl
v-if="jobName != 'new'"
v-model="job.status"
:label="__('Status')"
type="select"
@@ -51,7 +51,7 @@
</div>
</div>
</div>
<div class="container border-b mb-4 pb-4">
<div class="container border-b mb-4 pb-5">
<div class="text-lg font-semibold mb-4">
{{ __('Company Details') }}
</div>

View File

@@ -26,10 +26,10 @@
</header>
<div>
<div
v-if="jobCount"
class="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:items-center justify-between w-full md:w-4/5 mx-auto p-5"
>
<div
v-if="jobCount"
class="text-xl font-semibold text-ink-gray-7 mb-4 md:mb-0"
>
{{ __('{0} Open Jobs').format(jobCount) }}

View File

@@ -194,7 +194,7 @@ import { computed, ref } from 'vue'
import { Plus, Trash2 } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import { sessionStore } from '@/stores/session'
import { useSettings } from '@/stores/settings'
import { openSettings } from '@/utils'
import Draggable from 'vuedraggable'
import Link from '@/components/Controls/Link.vue'
@@ -204,7 +204,6 @@ const currentForm = ref(null)
const course = ref(null)
const member = ref(null)
const router = useRouter()
const settingsStore = useSettings()
const props = defineProps({
programName: {
@@ -359,12 +358,6 @@ const memberColumns = computed(() => {
]
})
const openSettings = (category, close) => {
close()
settingsStore.activeTab = category
settingsStore.isSettingsOpen = true
}
const breadbrumbs = computed(() => {
return [
{

View File

@@ -3,6 +3,8 @@ import { Quiz } from '@/utils/quiz'
import { Assignment } from '@/utils/assignment'
import { Upload } from '@/utils/upload'
import { Markdown } from '@/utils/markdownParser'
import { useSettings } from '@/stores/settings'
import { usersStore } from '@/stores/user'
import Header from '@editorjs/header'
import Paragraph from '@editorjs/paragraph'
import { CodeBox } from '@/utils/code'
@@ -13,7 +15,6 @@ import dayjs from '@/utils/dayjs'
import Embed from '@editorjs/embed'
import SimpleImage from '@editorjs/simple-image'
import Table from '@editorjs/table'
import { usersStore } from '../stores/user'
import Plyr from 'plyr'
import 'plyr/dist/plyr.css'
@@ -551,3 +552,10 @@ export const enablePlyr = () => {
})
}, 500)
}
export const openSettings = (category, close) => {
const settingsStore = useSettings()
close()
settingsStore.activeTab = category
settingsStore.isSettingsOpen = true
}

View File

@@ -23,6 +23,7 @@ class LMSBatch(Document):
def validate(self):
self.validate_seats_left()
self.validate_batch_end_date()
self.validate_batch_time()
self.validate_duplicate_courses()
self.validate_payments_app()
self.validate_amount_and_currency()
@@ -39,6 +40,11 @@ class LMSBatch(Document):
if self.end_date < self.start_date:
frappe.throw(_("Batch end date cannot be before the batch start date"))
def validate_batch_time(self):
if self.start_time and self.end_time:
if get_time(self.start_time) >= get_time(self.end_time):
frappe.throw(_("Batch start time cannot be greater than or equal to end time."))
def validate_duplicate_courses(self):
courses = [row.course for row in self.courses]
duplicates = {course for course in courses if courses.count(course) > 1}

View File

@@ -11,9 +11,11 @@
<b>{{ _("Batch Start Date:") }}</b> {{ frappe.utils.format_date(start_date, "medium") }}
</p>
{% if medium %}
<p>
<b>{{ _("Medium:") }}</b> {{ medium }}
</p>
{% endif %}
<p>
<b>{{ _("Timings:") }}</b> {{ frappe.utils.format_time(start_time, "hh:mm a") }}

View File

@@ -2,7 +2,7 @@
"name": "frappe_lms",
"version": "1.0.0",
"description": "Easy to use, open-source, Learning Management System",
"workspaces1": [
"workspaces": [
"frappe-ui",
"frontend"
],

File diff suppressed because it is too large Load Diff