fix: all empty states now come from a common component
This commit is contained in:
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@@ -47,6 +47,7 @@ declare module 'vue' {
|
|||||||
Discussions: typeof import('./src/components/Discussions.vue')['default']
|
Discussions: typeof import('./src/components/Discussions.vue')['default']
|
||||||
EditCoverImage: typeof import('./src/components/Modals/EditCoverImage.vue')['default']
|
EditCoverImage: typeof import('./src/components/Modals/EditCoverImage.vue')['default']
|
||||||
EditProfile: typeof import('./src/components/Modals/EditProfile.vue')['default']
|
EditProfile: typeof import('./src/components/Modals/EditProfile.vue')['default']
|
||||||
|
EmptyState: typeof import('./src/components/EmptyState.vue')['default']
|
||||||
EvaluationModal: typeof import('./src/components/Modals/EvaluationModal.vue')['default']
|
EvaluationModal: typeof import('./src/components/Modals/EvaluationModal.vue')['default']
|
||||||
Evaluators: typeof import('./src/components/Evaluators.vue')['default']
|
Evaluators: typeof import('./src/components/Evaluators.vue')['default']
|
||||||
Event: typeof import('./src/components/Modals/Event.vue')['default']
|
Event: typeof import('./src/components/Modals/Event.vue')['default']
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"name": "frappe-ui-frontend",
|
"name": "frappe-ui-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
"codemirror-editor-vue3": "^2.8.0",
|
"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.134",
|
"frappe-ui": "^0.1.141",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"lucide-vue-next": "^0.383.0",
|
"lucide-vue-next": "^0.383.0",
|
||||||
"markdown-it": "^14.0.0",
|
"markdown-it": "^14.0.0",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
|
|||||||
@@ -55,7 +55,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ComboboxOption>
|
</ComboboxOption>
|
||||||
<div v-if="attrs.onCreate" class="absolute bottom-2 left-1 w-[98%] pt-2 bg-white border-t">
|
<div
|
||||||
|
v-if="attrs.onCreate"
|
||||||
|
class="absolute bottom-2 left-1 w-[98%] pt-2 bg-white border-t"
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="w-full !justify-start"
|
class="w-full !justify-start"
|
||||||
@@ -75,13 +78,18 @@
|
|||||||
</Combobox>
|
</Combobox>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="values.length" class="grid grid-cols-2 gap-2 mt-4">
|
<div v-if="values.length" class="grid grid-cols-2 gap-2 mt-4">
|
||||||
<div v-for="value in values" class="flex items-center justify-between break-all bg-surface-gray-2 text-ink-gray-7 word-wrap p-2 rounded-md mr-2">
|
<div
|
||||||
|
v-for="value in values"
|
||||||
|
class="flex items-center justify-between break-all bg-surface-gray-2 text-ink-gray-7 word-wrap p-2 rounded-md mr-2"
|
||||||
|
>
|
||||||
<span class="break-all">
|
<span class="break-all">
|
||||||
{{ value }}
|
{{ value }}
|
||||||
</span>
|
</span>
|
||||||
<X class="size-4 stroke-1.5 cursor-pointer" @click="removeValue(value)" />
|
<X
|
||||||
|
class="size-4 stroke-1.5 cursor-pointer"
|
||||||
|
@click="removeValue(value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- <ErrorMessage class="mt-2 pl-2" v-if="error" :message="error" /> -->
|
<!-- <ErrorMessage class="mt-2 pl-2" v-if="error" :message="error" /> -->
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
24
frontend/src/components/EmptyState.vue
Normal file
24
frontend/src/components/EmptyState.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col items-center justify-center mt-60">
|
||||||
|
<GraduationCap class="size-10 mx-auto stroke-1 text-ink-gray-5" />
|
||||||
|
<div class="text-lg font-semibold text-ink-gray-7 mb-2.5">
|
||||||
|
{{ __('No {0}').format(type?.toLowerCase()) }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="leading-5 text-base w-2/5 text-base text-center text-ink-gray-7"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
__(
|
||||||
|
'There are no {0} currently. Keep an eye out, fresh learning experiences are on the way!'
|
||||||
|
).format(type?.toLowerCase())
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { BookOpen, GraduationCap } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
type: String,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -21,8 +21,21 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="md:w-3/4 md:mx-auto py-5 mx-5">
|
<div class="md:w-3/4 md:mx-auto py-5 mx-5">
|
||||||
<div class="grid grid-cols-3 gap-5 mb-5">
|
<div class="flex items-center justify-between mb-5">
|
||||||
<FormControl v-model="titleFilter" :placeholder="__('Search by title')" />
|
<div
|
||||||
|
v-if="assignmentCount"
|
||||||
|
class="text-xl font-semibold text-ink-gray-7 mb-4"
|
||||||
|
>
|
||||||
|
{{ __('{0} Assignments').format(assignmentCount) }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="assignments.data?.length || assigmentCount > 0"
|
||||||
|
class="grid grid-cols-2 gap-5"
|
||||||
|
>
|
||||||
|
<FormControl
|
||||||
|
v-model="titleFilter"
|
||||||
|
:placeholder="__('Search by title')"
|
||||||
|
/>
|
||||||
<FormControl
|
<FormControl
|
||||||
v-model="typeFilter"
|
v-model="typeFilter"
|
||||||
type="select"
|
type="select"
|
||||||
@@ -30,6 +43,7 @@
|
|||||||
:placeholder="__('Type')"
|
:placeholder="__('Type')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<ListView
|
<ListView
|
||||||
v-if="assignments.data?.length"
|
v-if="assignments.data?.length"
|
||||||
:columns="assignmentColumns"
|
:columns="assignmentColumns"
|
||||||
@@ -46,22 +60,7 @@
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
</ListView>
|
</ListView>
|
||||||
<div
|
<EmptyState v-else type="Assignments" />
|
||||||
v-else
|
|
||||||
class="text-center p-5 text-ink-gray-5 mt-52 w-3/4 md:w-1/2 mx-auto space-y-2"
|
|
||||||
>
|
|
||||||
<Pencil class="size-10 mx-auto stroke-1 text-ink-gray-4" />
|
|
||||||
<div class="text-xl font-medium">
|
|
||||||
{{ __('No assignments found') }}
|
|
||||||
</div>
|
|
||||||
<div class="leading-5">
|
|
||||||
{{
|
|
||||||
__(
|
|
||||||
'You have not created any assignments yet. To create a new assignment, click on the "New" button above.'
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
v-if="assignments.data && assignments.hasNextPage"
|
v-if="assignments.data && assignments.hasNextPage"
|
||||||
class="flex justify-center my-5"
|
class="flex justify-center my-5"
|
||||||
@@ -81,16 +80,18 @@
|
|||||||
import {
|
import {
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
Button,
|
Button,
|
||||||
|
call,
|
||||||
createListResource,
|
createListResource,
|
||||||
FormControl,
|
FormControl,
|
||||||
ListView,
|
ListView,
|
||||||
usePageMeta,
|
usePageMeta,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { computed, inject, onMounted, ref, watch } from 'vue'
|
import { computed, inject, onMounted, ref, watch } from 'vue'
|
||||||
import { Plus, Pencil } from 'lucide-vue-next'
|
import { Plus } from 'lucide-vue-next'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { sessionStore } from '../stores/session'
|
import { sessionStore } from '../stores/session'
|
||||||
import AssignmentForm from '@/components/Modals/AssignmentForm.vue'
|
import AssignmentForm from '@/components/Modals/AssignmentForm.vue'
|
||||||
|
import EmptyState from '@/components/EmptyState.vue'
|
||||||
|
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const dayjs = inject('$dayjs')
|
const dayjs = inject('$dayjs')
|
||||||
@@ -98,6 +99,7 @@ const titleFilter = ref('')
|
|||||||
const typeFilter = ref('')
|
const typeFilter = ref('')
|
||||||
const showAssignmentForm = ref(false)
|
const showAssignmentForm = ref(false)
|
||||||
const assignmentID = ref('new')
|
const assignmentID = ref('new')
|
||||||
|
const assignmentCount = ref(0)
|
||||||
const { brand } = sessionStore()
|
const { brand } = sessionStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const readOnlyMode = window.read_only_mode
|
const readOnlyMode = window.read_only_mode
|
||||||
@@ -106,7 +108,7 @@ onMounted(() => {
|
|||||||
if (!user.data?.is_moderator && !user.data?.is_instructor) {
|
if (!user.data?.is_moderator && !user.data?.is_instructor) {
|
||||||
router.push({ name: 'Courses' })
|
router.push({ name: 'Courses' })
|
||||||
}
|
}
|
||||||
|
getAssignmentCount()
|
||||||
titleFilter.value = router.currentRoute.value.query.title
|
titleFilter.value = router.currentRoute.value.query.title
|
||||||
typeFilter.value = router.currentRoute.value.query.type
|
typeFilter.value = router.currentRoute.value.query.type
|
||||||
})
|
})
|
||||||
@@ -179,6 +181,14 @@ const assignmentColumns = computed(() => {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const getAssignmentCount = () => {
|
||||||
|
call('frappe.client.get_count', {
|
||||||
|
doctype: 'LMS Assignment',
|
||||||
|
}).then((data) => {
|
||||||
|
assignmentCount.value = data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const assignmentTypes = computed(() => {
|
const assignmentTypes = computed(() => {
|
||||||
let types = ['', 'Document', 'Image', 'PDF', 'URL', 'Text']
|
let types = ['', 'Document', 'Image', 'PDF', 'URL', 'Text']
|
||||||
return types.map((type) => {
|
return types.map((type) => {
|
||||||
|
|||||||
@@ -56,7 +56,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="px-20 pb-5 space-y-5 border-b mb-5">
|
<div class="px-20 pb-5 space-y-5 border-b mb-5">
|
||||||
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
|
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
|
||||||
{{ __('Settings') }}
|
{{ __('Settings') }}
|
||||||
@@ -80,7 +79,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="px-20 pb-5 space-y-5 border-b mb-5">
|
<div class="px-20 pb-5 space-y-5 border-b mb-5">
|
||||||
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
|
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
|
||||||
{{ __('Date and Time') }}
|
{{ __('Date and Time') }}
|
||||||
@@ -137,7 +135,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="px-20 pb-5 space-y-5 border-b mb-5">
|
<div class="px-20 pb-5 space-y-5 border-b mb-5">
|
||||||
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
|
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
|
||||||
{{ __('Configurations') }}
|
{{ __('Configurations') }}
|
||||||
@@ -205,9 +202,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
<div class="mt-1 text-ink-gray-5 text-sm leading-5">
|
<div class="mt-1 text-ink-gray-5 text-sm leading-5">
|
||||||
{{
|
{{
|
||||||
__(
|
__('Appears when the batch URL is shared on socials')
|
||||||
'Appears when the batch URL is shared on socials'
|
|
||||||
)
|
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -239,7 +234,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="px-20 pb-5 space-y-5">
|
<div class="px-20 pb-5 space-y-5">
|
||||||
<div class="text-lg text-ink-gray-9 font-semibold">
|
<div class="text-lg text-ink-gray-9 font-semibold">
|
||||||
{{ __('Payment') }}
|
{{ __('Payment') }}
|
||||||
@@ -263,7 +257,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -70,22 +70,8 @@
|
|||||||
<BatchCard :batch="batch" />
|
<BatchCard :batch="batch" />
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<EmptyState v-else-if="!batches.list.loading" type="Batches" />
|
||||||
v-else-if="!batches.list.loading"
|
|
||||||
class="flex flex-col items-center justify-center text-sm text-ink-gray-5 mt-48"
|
|
||||||
>
|
|
||||||
<BookOpen class="size-10 mx-auto stroke-1 text-ink-gray-4" />
|
|
||||||
<div class="text-lg font-medium mb-1">
|
|
||||||
{{ __('No batches found') }}
|
|
||||||
</div>
|
|
||||||
<div class="leading-5 w-2/5 text-center">
|
|
||||||
{{
|
|
||||||
__(
|
|
||||||
'There are no batches matching the criteria. Keep an eye out, fresh learning experiences are on the way soon!'
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
v-if="!batches.list.loading && batches.hasNextPage"
|
v-if="!batches.list.loading && batches.hasNextPage"
|
||||||
class="flex justify-center mt-5"
|
class="flex justify-center mt-5"
|
||||||
@@ -110,6 +96,7 @@ import { computed, inject, onMounted, ref, watch } from 'vue'
|
|||||||
import { BookOpen, Plus } from 'lucide-vue-next'
|
import { BookOpen, Plus } from 'lucide-vue-next'
|
||||||
import { sessionStore } from '@/stores/session'
|
import { sessionStore } from '@/stores/session'
|
||||||
import BatchCard from '@/components/BatchCard.vue'
|
import BatchCard from '@/components/BatchCard.vue'
|
||||||
|
import EmptyState from '@/components/EmptyState.vue'
|
||||||
|
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const dayjs = inject('$dayjs')
|
const dayjs = inject('$dayjs')
|
||||||
|
|||||||
@@ -101,22 +101,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<EmptyState v-else type="Certified Members" />
|
||||||
v-else
|
|
||||||
class="flex flex-col items-center justify-center text-sm text-ink-gray-5 mt-48"
|
|
||||||
>
|
|
||||||
<BookOpen class="size-10 mx-auto stroke-1 text-ink-gray-4" />
|
|
||||||
<div class="text-lg font-medium mb-1">
|
|
||||||
{{ __('No certified members') }}
|
|
||||||
</div>
|
|
||||||
<div class="leading-5 w-2/5 text-center">
|
|
||||||
{{
|
|
||||||
__(
|
|
||||||
'No certified members found. Please check again later or get certified yourself.'
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
@@ -130,8 +115,9 @@ import {
|
|||||||
usePageMeta,
|
usePageMeta,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { computed, inject, onMounted, ref } from 'vue'
|
import { computed, inject, onMounted, ref } from 'vue'
|
||||||
import { BookOpen, GraduationCap } from 'lucide-vue-next'
|
import { GraduationCap } from 'lucide-vue-next'
|
||||||
import { sessionStore } from '../stores/session'
|
import { sessionStore } from '../stores/session'
|
||||||
|
import EmptyState from '@/components/EmptyState.vue'
|
||||||
|
|
||||||
const currentCategory = ref('')
|
const currentCategory = ref('')
|
||||||
const filters = ref({})
|
const filters = ref({})
|
||||||
|
|||||||
@@ -127,7 +127,9 @@
|
|||||||
{{ __('Remove') }}
|
{{ __('Remove') }}
|
||||||
</Button>
|
</Button>
|
||||||
<div class="mt-2 text-ink-gray-5 text-sm">
|
<div class="mt-2 text-ink-gray-5 text-sm">
|
||||||
{{ __('Appears on the course card in the course list') }}
|
{{
|
||||||
|
__('Appears on the course card in the course list')
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -136,10 +138,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="px-10 pb-5 mb-5 space-y-5 border-b">
|
<div class="px-10 pb-5 mb-5 space-y-5 border-b">
|
||||||
<div class="text-lg font-semibold">
|
<div class="text-lg font-semibold">
|
||||||
{{ __("Settings") }}
|
{{ __('Settings') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-5">
|
<div class="grid grid-cols-2 gap-5">
|
||||||
<div class="flex flex-col space-y-5">
|
<div class="flex flex-col space-y-5">
|
||||||
@@ -174,7 +175,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="px-10 pb-5 mb-5 space-y-5 border-b">
|
<div class="px-10 pb-5 mb-5 space-y-5 border-b">
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="mb-1.5 text-sm text-ink-gray-5">
|
<div class="mb-1.5 text-sm text-ink-gray-5">
|
||||||
@@ -201,7 +201,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="px-10 pb-5 space-y-5">
|
<div class="px-10 pb-5 space-y-5">
|
||||||
<div class="text-lg font-semibold mt-5">
|
<div class="text-lg font-semibold mt-5">
|
||||||
{{ __('Pricing and Certification') }}
|
{{ __('Pricing and Certification') }}
|
||||||
@@ -225,13 +224,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-5">
|
<div class="grid grid-cols-2 gap-5">
|
||||||
<div class="space-y-5">
|
<div class="space-y-5">
|
||||||
<FormControl v-if="course.paid_course || course.paid_certificate" v-model="course.course_price" :label="__('Amount')" />
|
<FormControl
|
||||||
|
v-if="course.paid_course || course.paid_certificate"
|
||||||
|
v-model="course.course_price"
|
||||||
|
:label="__('Amount')"
|
||||||
|
/>
|
||||||
<Link
|
<Link
|
||||||
v-if="course.paid_certificate"
|
v-if="course.paid_certificate"
|
||||||
doctype="Course Evaluator"
|
doctype="Course Evaluator"
|
||||||
v-model="course.evaluator"
|
v-model="course.evaluator"
|
||||||
:label="__('Evaluator')"
|
:label="__('Evaluator')"
|
||||||
:onCreate="(value, close) => openSettings('Evaluators', close)"
|
:onCreate="
|
||||||
|
(value, close) => openSettings('Evaluators', close)
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -66,22 +66,7 @@
|
|||||||
<CourseCard :course="course" />
|
<CourseCard :course="course" />
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<EmptyState v-else-if="!courses.list.loading" type="Courses" />
|
||||||
v-else-if="!courses.list.loading"
|
|
||||||
class="flex flex-col items-center justify-center text-sm text-ink-gray-5 italic mt-48"
|
|
||||||
>
|
|
||||||
<BookOpen class="size-10 mx-auto stroke-1 text-ink-gray-4" />
|
|
||||||
<div class="text-lg font-medium mb-1">
|
|
||||||
{{ __('No courses found') }}
|
|
||||||
</div>
|
|
||||||
<div class="leading-5 w-2/5 text-center">
|
|
||||||
{{
|
|
||||||
__(
|
|
||||||
'There are no courses matching the criteria. Keep an eye out, fresh learning experiences are on the way soon!'
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
v-if="!courses.list.loading && courses.hasNextPage"
|
v-if="!courses.list.loading && courses.hasNextPage"
|
||||||
class="flex justify-center mt-5"
|
class="flex justify-center mt-5"
|
||||||
@@ -108,6 +93,7 @@ import { BookOpen, Plus } from 'lucide-vue-next'
|
|||||||
import { sessionStore } from '@/stores/session'
|
import { sessionStore } from '@/stores/session'
|
||||||
import { canCreateCourse } from '@/utils'
|
import { canCreateCourse } from '@/utils'
|
||||||
import CourseCard from '@/components/CourseCard.vue'
|
import CourseCard from '@/components/CourseCard.vue'
|
||||||
|
import EmptyState from '@/components/EmptyState.vue'
|
||||||
import router from '../router'
|
import router from '../router'
|
||||||
|
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
|
|||||||
@@ -34,7 +34,11 @@
|
|||||||
>
|
>
|
||||||
{{ __('{0} Open Jobs').format(jobCount) }}
|
{{ __('{0} Open Jobs').format(jobCount) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-2">
|
|
||||||
|
<div
|
||||||
|
v-if="jobs.data?.length || jobCount > 0"
|
||||||
|
class="grid grid-cols-1 md:grid-cols-3 gap-2"
|
||||||
|
>
|
||||||
<FormControl
|
<FormControl
|
||||||
type="text"
|
type="text"
|
||||||
:placeholder="__('Search')"
|
:placeholder="__('Search')"
|
||||||
@@ -79,21 +83,7 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<EmptyState v-else type="Job Openings" />
|
||||||
v-else
|
|
||||||
class="flex flex-col items-center justify-center text-sm text-ink-gray-5 mt-56"
|
|
||||||
>
|
|
||||||
<Laptop class="size-10 mx-auto stroke-1 text-ink-gray-4" />
|
|
||||||
<div class="text-lg font-medium mb-1">
|
|
||||||
{{ __('No jobs found') }}
|
|
||||||
</div>
|
|
||||||
<div class="leading-5 w-2/5 text-center">
|
|
||||||
{{ __('There are no jobs available at the moment.') }}
|
|
||||||
</div>
|
|
||||||
<div class="leading-5 w-1/5 text-center">
|
|
||||||
{{ __('Post a new job or check again later.') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -106,11 +96,12 @@ import {
|
|||||||
FormControl,
|
FormControl,
|
||||||
usePageMeta,
|
usePageMeta,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { Laptop, Plus, Search } from 'lucide-vue-next'
|
import { Plus, Search } from 'lucide-vue-next'
|
||||||
import { sessionStore } from '../stores/session'
|
import { sessionStore } from '../stores/session'
|
||||||
import { inject, computed, ref, onMounted, watch } from 'vue'
|
import { inject, computed, ref, onMounted, watch } from 'vue'
|
||||||
import JobCard from '@/components/JobCard.vue'
|
import JobCard from '@/components/JobCard.vue'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
|
import EmptyState from '@/components/EmptyState.vue'
|
||||||
|
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const jobType = ref(null)
|
const jobType = ref(null)
|
||||||
|
|||||||
@@ -82,22 +82,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<EmptyState v-else type="Programs" />
|
||||||
v-else
|
|
||||||
class="text-center p-5 text-ink-gray-5 mt-52 w-3/4 md:w-1/2 mx-auto space-y-2"
|
|
||||||
>
|
|
||||||
<BookOpen class="size-10 mx-auto stroke-1 text-ink-gray-4" />
|
|
||||||
<div class="text-xl font-medium">
|
|
||||||
{{ __('No programs found') }}
|
|
||||||
</div>
|
|
||||||
<div class="leading-5">
|
|
||||||
{{
|
|
||||||
__(
|
|
||||||
'There are no programs available at the moment. Keep an eye out, fresh learning experiences are on the way soon!'
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Dialog
|
<Dialog
|
||||||
v-model="showDialog"
|
v-model="showDialog"
|
||||||
@@ -129,8 +114,9 @@ import {
|
|||||||
usePageMeta,
|
usePageMeta,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { computed, inject, onMounted, ref } from 'vue'
|
import { computed, inject, onMounted, ref } from 'vue'
|
||||||
import { BookOpen, Edit, Plus, LockKeyhole } from 'lucide-vue-next'
|
import { Edit, Plus, LockKeyhole } from 'lucide-vue-next'
|
||||||
import CourseCard from '@/components/CourseCard.vue'
|
import CourseCard from '@/components/CourseCard.vue'
|
||||||
|
import EmptyState from '@/components/EmptyState.vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { sessionStore } from '../stores/session'
|
import { sessionStore } from '../stores/session'
|
||||||
import { showToast } from '@/utils'
|
import { showToast } from '@/utils'
|
||||||
|
|||||||
@@ -40,18 +40,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<EmptyState v-else />
|
||||||
v-else
|
|
||||||
class="text-center p-5 text-ink-gray-5 mt-52 w-3/4 md:w-1/2 mx-auto space-y-2"
|
|
||||||
>
|
|
||||||
<BookOpen class="size-10 mx-auto stroke-1 text-ink-gray-4" />
|
|
||||||
<div class="text-xl font-medium">
|
|
||||||
{{ __('No submissions') }}
|
|
||||||
</div>
|
|
||||||
<div class="leading-5">
|
|
||||||
{{ __('No quiz submissions found. Please check again later.') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
@@ -65,10 +54,10 @@ import {
|
|||||||
ListHeaderItem,
|
ListHeaderItem,
|
||||||
usePageMeta,
|
usePageMeta,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { BookOpen } from 'lucide-vue-next'
|
|
||||||
import { computed, onMounted, inject } from 'vue'
|
import { computed, onMounted, inject } from 'vue'
|
||||||
import { sessionStore } from '../stores/session'
|
import { sessionStore } from '../stores/session'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import EmptyState from '@/components/EmptyState.vue'
|
||||||
|
|
||||||
const { brand } = sessionStore()
|
const { brand } = sessionStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
@@ -21,6 +21,9 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</header>
|
</header>
|
||||||
<div v-if="quizzes.data?.length" class="md:w-3/4 md:mx-auto py-5 mx-5">
|
<div v-if="quizzes.data?.length" class="md:w-3/4 md:mx-auto py-5 mx-5">
|
||||||
|
<div v-if="quizCount" class="text-xl font-semibold text-ink-gray-7 mb-4">
|
||||||
|
{{ __('{0} Quizzes').format(quizCount) }}
|
||||||
|
</div>
|
||||||
<ListView
|
<ListView
|
||||||
:columns="quizColumns"
|
:columns="quizColumns"
|
||||||
:rows="quizzes.data"
|
:rows="quizzes.data"
|
||||||
@@ -53,27 +56,13 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<EmptyState v-else type="Quizzes" />
|
||||||
v-else
|
|
||||||
class="text-center p-5 text-ink-gray-5 mt-52 w-3/4 md:w-1/2 mx-auto space-y-2"
|
|
||||||
>
|
|
||||||
<BookOpen class="size-10 mx-auto stroke-1 text-ink-gray-4" />
|
|
||||||
<div class="text-xl font-medium">
|
|
||||||
{{ __('No quizzes found') }}
|
|
||||||
</div>
|
|
||||||
<div class="leading-5">
|
|
||||||
{{
|
|
||||||
__(
|
|
||||||
'You have not created any quizzes yet. To create a new quiz, click on the "New Quiz" button above.'
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
Button,
|
Button,
|
||||||
|
call,
|
||||||
createListResource,
|
createListResource,
|
||||||
ListView,
|
ListView,
|
||||||
ListRows,
|
ListRows,
|
||||||
@@ -83,19 +72,22 @@ import {
|
|||||||
usePageMeta,
|
usePageMeta,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { computed, inject, onMounted } from 'vue'
|
import { computed, inject, onMounted, ref } from 'vue'
|
||||||
import { BookOpen, Plus } from 'lucide-vue-next'
|
import { Plus } from 'lucide-vue-next'
|
||||||
import { sessionStore } from '@/stores/session'
|
import { sessionStore } from '@/stores/session'
|
||||||
|
import EmptyState from '@/components/EmptyState.vue'
|
||||||
|
|
||||||
const { brand } = sessionStore()
|
const { brand } = sessionStore()
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const quizCount = ref(0)
|
||||||
const readOnlyMode = window.read_only_mode
|
const readOnlyMode = window.read_only_mode
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!user.data?.is_moderator && !user.data?.is_instructor) {
|
if (!user.data?.is_moderator && !user.data?.is_instructor) {
|
||||||
router.push({ name: 'Courses' })
|
router.push({ name: 'Courses' })
|
||||||
}
|
}
|
||||||
|
getQuizCount()
|
||||||
})
|
})
|
||||||
|
|
||||||
const quizFilter = computed(() => {
|
const quizFilter = computed(() => {
|
||||||
@@ -114,6 +106,14 @@ const quizzes = createListResource({
|
|||||||
orderBy: 'modified desc',
|
orderBy: 'modified desc',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const getQuizCount = () => {
|
||||||
|
call('frappe.client.get_count', {
|
||||||
|
doctype: 'LMS Quiz',
|
||||||
|
}).then((data) => {
|
||||||
|
quizCount.value = data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const quizColumns = computed(() => {
|
const quizColumns = computed(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
module.exports = {
|
import frappeUIPreset from 'frappe-ui/src/tailwind/preset'
|
||||||
presets: [require('frappe-ui/src/tailwind/preset')],
|
|
||||||
|
export default {
|
||||||
|
presets: [frappeUIPreset],
|
||||||
content: [
|
content: [
|
||||||
'./index.html',
|
'./index.html',
|
||||||
'./src/**/*.{vue,js,ts,jsx,tsx}',
|
'./src/**/*.{vue,js,ts,jsx,tsx}',
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default defineConfig({
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
server: {
|
server: {
|
||||||
allowedHosts: ['fs', 'persona'],
|
allowedHosts: ['fs', 'per2'],
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@
|
|||||||
"frappe-ui",
|
"frappe-ui",
|
||||||
"frontend"
|
"frontend"
|
||||||
],
|
],
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test-local": "cypress open --e2e --browser chrome",
|
"test-local": "cypress open --e2e --browser chrome",
|
||||||
"postinstall": "cd frontend && yarn install --check-files",
|
"postinstall": "cd frontend && yarn install --check-files",
|
||||||
|
|||||||
Reference in New Issue
Block a user