chore: merge conflicts
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
<ListView
|
||||
:columns="getCoursesColumns()"
|
||||
:rows="courses.data"
|
||||
row-key="name"
|
||||
row-key="batch_course"
|
||||
:options="{ showTooltip: false }"
|
||||
>
|
||||
<ListHeader
|
||||
@@ -49,7 +49,10 @@
|
||||
<ListSelectBanner>
|
||||
<template #actions="{ unselectAll, selections }">
|
||||
<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" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -133,11 +136,13 @@ const removeCourse = createResource({
|
||||
},
|
||||
})
|
||||
|
||||
const removeCourses = (selections) => {
|
||||
const removeCourses = (selections, unselectAll) => {
|
||||
selections.forEach(async (course) => {
|
||||
removeCourse.submit({ course })
|
||||
await setTimeout(1000)
|
||||
})
|
||||
courses.reload()
|
||||
setTimeout(() => {
|
||||
courses.reload()
|
||||
unselectAll()
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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
|
||||
v-if="batch.data.seat_count && seats_left > 0"
|
||||
theme="green"
|
||||
|
||||
@@ -52,7 +52,10 @@
|
||||
<ListSelectBanner>
|
||||
<template #actions="{ unselectAll, selections }">
|
||||
<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" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -142,11 +145,13 @@ const removeStudent = createResource({
|
||||
},
|
||||
})
|
||||
|
||||
const removeStudents = (selections) => {
|
||||
const removeStudents = (selections, unselectAll) => {
|
||||
selections.forEach(async (student) => {
|
||||
removeStudent.submit({ student })
|
||||
await setTimeout(1000)
|
||||
})
|
||||
students.reload()
|
||||
setTimeout(() => {
|
||||
students.reload()
|
||||
unselectAll()
|
||||
}, 500)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div
|
||||
class="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">
|
||||
<Badge
|
||||
|
||||
@@ -29,15 +29,42 @@
|
||||
<script setup>
|
||||
import { getSidebarLinks } from '../utils'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { computed, inject } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
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()
|
||||
|
||||
const router = useRouter()
|
||||
let { userResource } = usersStore()
|
||||
|
||||
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) => {
|
||||
@@ -50,6 +77,13 @@ const handleClick = (tab) => {
|
||||
logout.submit().then(() => {
|
||||
isLoggedIn = false
|
||||
})
|
||||
else if (tab.label == 'Profile')
|
||||
router.push({
|
||||
name: 'Profile',
|
||||
params: {
|
||||
username: userResource.data?.username,
|
||||
},
|
||||
})
|
||||
else router.push({ name: tab.to })
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
</template>
|
||||
<script setup>
|
||||
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')
|
||||
|
||||
@@ -93,6 +94,14 @@ const submitTopic = (close) => {
|
||||
topicResource.submit(
|
||||
{},
|
||||
{
|
||||
validate() {
|
||||
if (!topic.title) {
|
||||
return 'Title cannot be empty.'
|
||||
}
|
||||
if (!topic.reply) {
|
||||
return 'Reply cannot be empty.'
|
||||
}
|
||||
},
|
||||
onSuccess(data) {
|
||||
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 { sessionStore } from '@/stores/session'
|
||||
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 { convertToTitleCase } from '../utils'
|
||||
import { onMounted, inject } from 'vue'
|
||||
import { usersStore } from '@/stores/user'
|
||||
|
||||
const router = useRouter()
|
||||
const { logout } = sessionStore()
|
||||
let { userResource } = usersStore()
|
||||
let { isLoggedIn } = sessionStore()
|
||||
|
||||
const props = defineProps({
|
||||
isCollapsed: {
|
||||
type: Boolean,
|
||||
@@ -80,10 +90,6 @@ const branding = createResource({
|
||||
},
|
||||
})
|
||||
|
||||
const { logout } = sessionStore()
|
||||
let { userResource } = usersStore()
|
||||
|
||||
let { isLoggedIn } = sessionStore()
|
||||
const userDropdownOptions = [
|
||||
{
|
||||
icon: User,
|
||||
@@ -95,6 +101,19 @@ const userDropdownOptions = [
|
||||
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,
|
||||
label: 'Log out',
|
||||
|
||||
@@ -244,7 +244,7 @@ const newBatch = createResource({
|
||||
return {
|
||||
doc: {
|
||||
doctype: 'LMS Batch',
|
||||
meta_image: batch.image.file_url,
|
||||
meta_image: batch.image?.file_url,
|
||||
...batch,
|
||||
},
|
||||
}
|
||||
@@ -279,7 +279,7 @@ const editBatch = createResource({
|
||||
doctype: 'LMS Batch',
|
||||
name: props.batchName,
|
||||
fieldname: {
|
||||
meta_image: batch.image.file_url,
|
||||
meta_image: batch.image?.file_url,
|
||||
...batch,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -11,17 +11,23 @@
|
||||
<div class="my-3">
|
||||
{{ batch.data.description }}
|
||||
</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">
|
||||
<BookOpen class="h-4 w-4 text-gray-700 mr-2" />
|
||||
<span> {{ batch.data?.courses?.length }} {{ __('Courses') }} </span>
|
||||
</div>
|
||||
<span v-if="batch.data.courses">·</span>
|
||||
<span class="hidden lg:block" v-if="batch.data.courses"
|
||||
>·</span
|
||||
>
|
||||
<DateRange
|
||||
:startDate="batch.data.start_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">
|
||||
<Clock class="h-4 w-4 text-gray-700 mr-2" />
|
||||
<span>
|
||||
@@ -31,14 +37,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-[60%,20%] gap-20 mt-10">
|
||||
<div class="">
|
||||
<div class="grid lg:grid-cols-[60%,20%] gap-4 lg:gap-20 mt-10">
|
||||
<div class="order-2 lg:order-none">
|
||||
<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"
|
||||
v-html="batch.data.batch_details"
|
||||
></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="order-1 lg:order-none">
|
||||
<BatchOverlay :batch="batch" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,7 +54,7 @@
|
||||
{{ __('Courses') }}
|
||||
</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
|
||||
v-if="batch.data.courses"
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
<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
|
||||
:to="{
|
||||
name: 'Profile',
|
||||
@@ -38,14 +51,23 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { Breadcrumbs, createResource } from 'frappe-ui'
|
||||
import { computed } from 'vue'
|
||||
import { Breadcrumbs, FormControl, createResource } from 'frappe-ui'
|
||||
import { ref, computed } from 'vue'
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import { Search } from 'lucide-vue-next'
|
||||
|
||||
const searchQuery = ref('')
|
||||
|
||||
const participants = createResource({
|
||||
url: 'lms.lms.api.get_certified_participants',
|
||||
method: 'GET',
|
||||
cache: ['certified_participants'],
|
||||
makeParams() {
|
||||
return {
|
||||
search_query: searchQuery.value,
|
||||
}
|
||||
},
|
||||
auto: true,
|
||||
cache: ['certified-participants'],
|
||||
})
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<div
|
||||
v-show="openInstructorEditor"
|
||||
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>
|
||||
@@ -52,7 +52,10 @@
|
||||
<label class="block font-medium text-gray-600 mb-1">
|
||||
{{ __('Content') }}
|
||||
</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>
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
</EditCoverImage>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx-auto -mt-4 max-w-4xl translate-x-0 sm:px-5">
|
||||
<div class="flex items-center">
|
||||
<div class="mx-auto -mt-10 md:-mt-4 max-w-4xl translate-x-0 px-5">
|
||||
<div class="flex flex-col md:flex-row items-center">
|
||||
<div>
|
||||
<img
|
||||
v-if="profile.data.user_image"
|
||||
@@ -57,7 +57,11 @@
|
||||
{{ profile.data.headline }}
|
||||
</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>
|
||||
<Edit class="w-4 h-4 stroke-1.5 text-gray-700" />
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="mt-7">
|
||||
<div class="mt-7 mb-10">
|
||||
<h2 class="mb-3 text-lg font-semibold text-gray-900">
|
||||
{{ __('About') }}
|
||||
</h2>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="mt-7">
|
||||
<div class="mt-7 mb-10">
|
||||
<h2 class="mb-3 text-lg font-semibold text-gray-900">
|
||||
{{ __('Certificates') }}
|
||||
</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
|
||||
v-for="certificate in certificates.data"
|
||||
:key="certificate.name"
|
||||
@@ -34,13 +34,9 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const certificates = createResource({
|
||||
url: 'frappe.client.get_list',
|
||||
url: 'lms.lms.api.get_certificates',
|
||||
params: {
|
||||
doctype: 'LMS Certificate',
|
||||
fields: ['name', 'course', 'course_title', 'issue_date', 'template'],
|
||||
filters: {
|
||||
member: props.profile.data.name,
|
||||
},
|
||||
member: props.profile.data.name,
|
||||
},
|
||||
auto: true,
|
||||
})
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
</h2>
|
||||
|
||||
<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>
|
||||
{{ __('Day') }}
|
||||
</div>
|
||||
@@ -20,7 +22,7 @@
|
||||
<div
|
||||
v-if="evaluator.data"
|
||||
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
|
||||
type="select"
|
||||
@@ -44,7 +46,10 @@
|
||||
/>
|
||||
</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
|
||||
type="select"
|
||||
:options="days"
|
||||
@@ -74,7 +79,7 @@
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-900">
|
||||
{{ __('I am unavailable') }}
|
||||
</h2>
|
||||
<div class="grid grid-cols-4 gap-4">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<FormControl
|
||||
type="date"
|
||||
:label="__('From')"
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
<h2 class="mb-3 text-lg font-semibold text-gray-900">
|
||||
{{ __('Settings') }}
|
||||
</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
|
||||
:label="__('Moderator')"
|
||||
v-model="moderator"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<Breadcrumbs class="h-7" :items="breadcrumbs" />
|
||||
</header>
|
||||
<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="p-2 rounded-md bg-gray-100 mr-3">
|
||||
<BookOpen class="w-18 h-18 stroke-1.5 text-gray-700" />
|
||||
|
||||
Reference in New Issue
Block a user