feat: badge assignment from settings
This commit is contained in:
2
frontend/components.d.ts
vendored
2
frontend/components.d.ts
vendored
@@ -19,6 +19,8 @@ declare module 'vue' {
|
|||||||
AssignmentForm: typeof import('./src/components/Modals/AssignmentForm.vue')['default']
|
AssignmentForm: typeof import('./src/components/Modals/AssignmentForm.vue')['default']
|
||||||
AudioBlock: typeof import('./src/components/AudioBlock.vue')['default']
|
AudioBlock: typeof import('./src/components/AudioBlock.vue')['default']
|
||||||
Autocomplete: typeof import('./src/components/Controls/Autocomplete.vue')['default']
|
Autocomplete: typeof import('./src/components/Controls/Autocomplete.vue')['default']
|
||||||
|
BadgeAssignmentForm: typeof import('./src/components/Settings/BadgeAssignmentForm.vue')['default']
|
||||||
|
BadgeAssignments: typeof import('./src/components/Settings/BadgeAssignments.vue')['default']
|
||||||
BadgeForm: typeof import('./src/components/Settings/BadgeForm.vue')['default']
|
BadgeForm: typeof import('./src/components/Settings/BadgeForm.vue')['default']
|
||||||
Badges: typeof import('./src/components/Settings/Badges.vue')['default']
|
Badges: typeof import('./src/components/Settings/Badges.vue')['default']
|
||||||
Bagdes: typeof import('./src/components/Settings/Bagdes.vue')['default']
|
Bagdes: typeof import('./src/components/Settings/Bagdes.vue')['default']
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
>
|
>
|
||||||
<template v-slot="{ file, progress, uploading, openFileSelector }">
|
<template v-slot="{ file, progress, uploading, openFileSelector }">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="border rounded-md w-fit py-5 px-20">
|
<div class="border rounded-md w-fit py-7 px-20">
|
||||||
<Image class="size-5 stroke-1 text-ink-gray-7" />
|
<Image class="size-5 stroke-1 text-ink-gray-7" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
{{ __('Upload') }}
|
{{ __('Upload') }}
|
||||||
</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 on the course card in the course list') }}
|
{{ __(description) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
142
frontend/src/components/Settings/BadgeAssignmentForm.vue
Normal file
142
frontend/src/components/Settings/BadgeAssignmentForm.vue
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog
|
||||||
|
v-model="show"
|
||||||
|
:options="{
|
||||||
|
title:
|
||||||
|
props.badgeAssignmentID === 'new'
|
||||||
|
? __('Assign a Badge')
|
||||||
|
: __('Edit Badge Assignment'),
|
||||||
|
size: 'sm',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: __('Save'),
|
||||||
|
variant: 'solid',
|
||||||
|
onClick: ({ close }) => {
|
||||||
|
saveBadgeAssignment(close)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #body-content>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<Link
|
||||||
|
doctype="User"
|
||||||
|
v-model="badgeAssignment.member"
|
||||||
|
:label="__('Member')"
|
||||||
|
:required="true"
|
||||||
|
/>
|
||||||
|
<Link
|
||||||
|
doctype="LMS Badge"
|
||||||
|
v-model="badgeAssignment.badge"
|
||||||
|
:label="__('Badge')"
|
||||||
|
:required="true"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<label class="text-xs text-ink-gray-5 mb-1">
|
||||||
|
{{ __('Issued On') }}
|
||||||
|
<span class="text-ink-red-3">*</span>
|
||||||
|
</label>
|
||||||
|
<DatePicker
|
||||||
|
v-model="badgeAssignment.issued_on"
|
||||||
|
:placeholder="__('Select Date')"
|
||||||
|
:required="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Dialog, DatePicker, toast } from 'frappe-ui'
|
||||||
|
import type {
|
||||||
|
BadgeAssignments,
|
||||||
|
BadgeAssignment,
|
||||||
|
} from '@/components/Settings/types'
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { cleanError } from '@/utils'
|
||||||
|
import Link from '@/components/Controls/Link.vue'
|
||||||
|
|
||||||
|
const show = defineModel<boolean>({ required: true, default: false })
|
||||||
|
const defaultBadgeAssignment = {
|
||||||
|
name: '',
|
||||||
|
badge: '',
|
||||||
|
member: '',
|
||||||
|
issued_on: '',
|
||||||
|
member_name: '',
|
||||||
|
member_username: '',
|
||||||
|
member_image: '',
|
||||||
|
}
|
||||||
|
const badgeAssignments = defineModel<BadgeAssignments>('badgeAssignments')
|
||||||
|
const badgeAssignment = ref<BadgeAssignment>(defaultBadgeAssignment)
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
badgeAssignmentID: string
|
||||||
|
badge: string | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.badgeAssignmentID,
|
||||||
|
(newID) => {
|
||||||
|
if (newID === 'new') {
|
||||||
|
badgeAssignment.value = {
|
||||||
|
...defaultBadgeAssignment,
|
||||||
|
badge: props.badge || '',
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const assignment = badgeAssignments.value?.data?.find(
|
||||||
|
(assignment) => assignment.name === newID
|
||||||
|
)
|
||||||
|
if (assignment) {
|
||||||
|
badgeAssignment.value = { ...assignment }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const saveBadgeAssignment = (close: () => void) => {
|
||||||
|
if (props.badgeAssignmentID === 'new') {
|
||||||
|
createBadgeAssignment(close)
|
||||||
|
} else {
|
||||||
|
updateBadgeAssignment(close)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateBadgeAssignment = async (close: () => void) => {
|
||||||
|
badgeAssignments.value?.setValue.submit(
|
||||||
|
{
|
||||||
|
...badgeAssignment.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(__('Badge assignment updated successfully'))
|
||||||
|
close()
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(
|
||||||
|
__('Failed to update badge assignment: ') + cleanError(error)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const createBadgeAssignment = (close: () => void) => {
|
||||||
|
badgeAssignments.value?.insert.submit(
|
||||||
|
{
|
||||||
|
...badgeAssignment.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(__('Badge assignment created successfully'))
|
||||||
|
close()
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(
|
||||||
|
__('Failed to create badge assignment: ') + cleanError(error)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
192
frontend/src/components/Settings/BadgeAssignments.vue
Normal file
192
frontend/src/components/Settings/BadgeAssignments.vue
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
<template>
|
||||||
|
<div class="text-base">
|
||||||
|
<div class="flex items-center justify-between space-x-2 mb-5">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<ChevronLeft
|
||||||
|
class="size-5 stroke-1.5 text-ink-gray-5 cursor-pointer"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
show = false
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<div class="text-xl font-semibold text-ink-gray-9">
|
||||||
|
{{ props.badgeName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button @click="openForm('new')">
|
||||||
|
<template #prefix>
|
||||||
|
<Plus class="size-4 stroke-1.5" />
|
||||||
|
</template>
|
||||||
|
{{ __('New') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div v-if="assignments.data?.length">
|
||||||
|
<ListView
|
||||||
|
:rows="assignments.data"
|
||||||
|
:columns="columns"
|
||||||
|
rowKey="name"
|
||||||
|
:options="{
|
||||||
|
showTooltip: false,
|
||||||
|
onRowClick: (row: BadgeAssignment) => {
|
||||||
|
openForm(row.name)
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<ListHeader
|
||||||
|
class="mb-2 grid items-center space-x-4 rounded bg-surface-gray-2 p-2"
|
||||||
|
>
|
||||||
|
<ListHeaderItem :item="item" v-for="item in columns">
|
||||||
|
<template #prefix="{ item }">
|
||||||
|
<FeatherIcon
|
||||||
|
v-if="item.icon"
|
||||||
|
:name="item.icon"
|
||||||
|
class="h-4 w-4 stroke-1.5"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</ListHeaderItem>
|
||||||
|
</ListHeader>
|
||||||
|
<ListRows>
|
||||||
|
<ListRow :row="row" v-for="row in assignments.data">
|
||||||
|
<template #default="{ column, item }">
|
||||||
|
<ListRowItem :item="row[column.key]" :align="column.align">
|
||||||
|
<template #prefix>
|
||||||
|
<div v-if="column.key == 'member_name'">
|
||||||
|
<Avatar
|
||||||
|
class="flex items-center"
|
||||||
|
:image="row['member_image']"
|
||||||
|
:label="item"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="leading-5 text-sm">
|
||||||
|
{{ row[column.key] }}
|
||||||
|
</div>
|
||||||
|
</ListRowItem>
|
||||||
|
</template>
|
||||||
|
</ListRow>
|
||||||
|
</ListRows>
|
||||||
|
<ListSelectBanner>
|
||||||
|
<template #actions="{ unselectAll, selections }">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
@click="deleteBadgeAssignment(selections, unselectAll)"
|
||||||
|
>
|
||||||
|
<Trash2 class="h-4 w-4 stroke-1.5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ListSelectBanner>
|
||||||
|
</ListView>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex flex-col items-center justify-center mt-44">
|
||||||
|
<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 Assignments') }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="leading-5 text-base w-2/5 text-base text-center text-ink-gray-7"
|
||||||
|
>
|
||||||
|
{{ __('This badge has not been assigned to any students yet') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<BadgeAssignmentForm
|
||||||
|
v-model="showForm"
|
||||||
|
:badgeAssignmentID="currentAssignmentID"
|
||||||
|
:badge="props.badgeName"
|
||||||
|
v-model:badgeAssignments="assignments"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
createListResource,
|
||||||
|
FeatherIcon,
|
||||||
|
ListView,
|
||||||
|
ListHeader,
|
||||||
|
ListHeaderItem,
|
||||||
|
ListRows,
|
||||||
|
ListRow,
|
||||||
|
ListRowItem,
|
||||||
|
ListSelectBanner,
|
||||||
|
toast,
|
||||||
|
} from 'frappe-ui'
|
||||||
|
import { ChevronLeft, GraduationCap, Plus, Trash2 } from 'lucide-vue-next'
|
||||||
|
import { computed, inject, ref } from 'vue'
|
||||||
|
import type { BadgeAssignment } from '@/components/Settings/types'
|
||||||
|
import BadgeAssignmentForm from '@/components/Settings/BadgeAssignmentForm.vue'
|
||||||
|
|
||||||
|
const show = defineModel<boolean>()
|
||||||
|
const dayjs = inject('$dayjs') as any
|
||||||
|
const showForm = ref(false)
|
||||||
|
const currentAssignmentID = ref<string>('')
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
badgeName: string | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const assignments = createListResource({
|
||||||
|
doctype: 'LMS Badge Assignment',
|
||||||
|
fields: [
|
||||||
|
'name',
|
||||||
|
'member',
|
||||||
|
'member_name',
|
||||||
|
'member_username',
|
||||||
|
'member_image',
|
||||||
|
'issued_on',
|
||||||
|
'badge',
|
||||||
|
],
|
||||||
|
filters: {
|
||||||
|
badge: props.badgeName,
|
||||||
|
},
|
||||||
|
order_by: 'issued_on desc',
|
||||||
|
transform(data: BadgeAssignment[]) {
|
||||||
|
return data.map((item: BadgeAssignment) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
issued_on: item.issued_on
|
||||||
|
? dayjs(item.issued_on).format('DD MMM YYYY')
|
||||||
|
: null,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
auto: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const openForm = (assignmentID: string) => {
|
||||||
|
currentAssignmentID.value = assignmentID
|
||||||
|
showForm.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteBadgeAssignment = (
|
||||||
|
selections: Set<string>,
|
||||||
|
unselectAll: () => void
|
||||||
|
) => {
|
||||||
|
Array.from(selections).forEach(async (assignment: string) => {
|
||||||
|
await assignments.delete.submit(assignment)
|
||||||
|
})
|
||||||
|
unselectAll()
|
||||||
|
toast.success(__('Badge assignments deleted successfully'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: __('Member'),
|
||||||
|
key: 'member_name',
|
||||||
|
icon: 'user',
|
||||||
|
width: '80%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Issued On'),
|
||||||
|
key: 'issued_on',
|
||||||
|
icon: 'calendar',
|
||||||
|
align: 'right',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
:required="true"
|
:required="true"
|
||||||
/>
|
/>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
@update:modelValue="(opt) => (badge.reference_doctype = opt.value)"
|
@update:modelValue="(opt: any) => (badge.reference_doctype = opt.value)"
|
||||||
:modelValue="badge.reference_doctype"
|
:modelValue="badge.reference_doctype"
|
||||||
:options="referenceDoctypeOptions"
|
:options="referenceDoctypeOptions"
|
||||||
:required="true"
|
:required="true"
|
||||||
@@ -98,12 +98,12 @@ const defaultBadge = {
|
|||||||
image: '',
|
image: '',
|
||||||
grant_only_once: false,
|
grant_only_once: false,
|
||||||
event: 'New',
|
event: 'New',
|
||||||
reference_doctype: 'LMS Course',
|
reference_doctype: '',
|
||||||
condition: null,
|
condition: '',
|
||||||
user_field: 'member',
|
user_field: 'member',
|
||||||
field_to_check: '',
|
field_to_check: '',
|
||||||
}
|
}
|
||||||
const show = defineModel<boolean | undefined>()
|
const show = defineModel<boolean>({ required: true, default: false })
|
||||||
const badges = defineModel<Badges>('badges')
|
const badges = defineModel<Badges>('badges')
|
||||||
const badge = ref<Badge>(defaultBadge)
|
const badge = ref<Badge>(defaultBadge)
|
||||||
|
|
||||||
@@ -135,7 +135,6 @@ const saveBadge = (close: () => void) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateBadge = async (close: () => void) => {
|
const updateBadge = async (close: () => void) => {
|
||||||
console.log(props.badgeName, badge.value?.title)
|
|
||||||
if (props.badgeName != badge.value?.title) {
|
if (props.badgeName != badge.value?.title) {
|
||||||
await renameDoc()
|
await renameDoc()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col min-h-0 text-base">
|
<BadgeAssignments
|
||||||
|
v-if="showAssignments"
|
||||||
|
v-model="showAssignments"
|
||||||
|
:badgeName="showAssignmentsFor"
|
||||||
|
/>
|
||||||
|
<div v-else class="flex flex-col min-h-0 text-base">
|
||||||
<div class="flex items-center justify-between mb-5">
|
<div class="flex items-center justify-between mb-5">
|
||||||
<div class="flex flex-col space-y-2">
|
<div class="flex flex-col space-y-2">
|
||||||
<div class="text-xl font-semibold text-ink-gray-9">
|
<div class="text-xl font-semibold text-ink-gray-9">
|
||||||
@@ -23,6 +28,7 @@
|
|||||||
row-key="name"
|
row-key="name"
|
||||||
:options="{
|
:options="{
|
||||||
showTooltip: false,
|
showTooltip: false,
|
||||||
|
selectable: false,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<ListHeader
|
<ListHeader
|
||||||
@@ -51,7 +57,11 @@
|
|||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="column.key == 'reference_doctype'">
|
<div v-else-if="column.key == 'reference_doctype'">
|
||||||
{{ doctypeLabel[row[column.key]] || row[column.key] }}
|
{{
|
||||||
|
doctypeLabel[
|
||||||
|
row[column.key] as keyof typeof doctypeLabel
|
||||||
|
] || row[column.key]
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
<FormControl
|
<FormControl
|
||||||
v-else-if="column.key == 'grant_only_once'"
|
v-else-if="column.key == 'grant_only_once'"
|
||||||
@@ -65,15 +75,12 @@
|
|||||||
>
|
>
|
||||||
{{ row[column.key] }}
|
{{ row[column.key] }}
|
||||||
</div>
|
</div>
|
||||||
<!-- <Button v-else variant="ghost">
|
|
||||||
<Ellipsis class="size-4 stroke-1.5 text-ink-gray-9" />
|
|
||||||
</Button> -->
|
|
||||||
<Dropdown
|
<Dropdown
|
||||||
v-else
|
v-else
|
||||||
:options="getMoreOptions(row.name)"
|
:options="getMoreOptions(row.name)"
|
||||||
:button="{
|
:button="{
|
||||||
icon: 'more-horizontal',
|
icon: 'more-horizontal',
|
||||||
onblur: (e) => {
|
onblur: (e: Event) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
@@ -83,19 +90,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</ListRow>
|
</ListRow>
|
||||||
</ListRows>
|
</ListRows>
|
||||||
|
|
||||||
<ListSelectBanner>
|
|
||||||
<template #actions="{ unselectAll, selections }">
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
@click="removeAccount(selections, unselectAll)"
|
|
||||||
>
|
|
||||||
<Trash2 class="h-4 w-4 stroke-1.5" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</ListSelectBanner>
|
|
||||||
</ListView>
|
</ListView>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,14 +113,18 @@ import {
|
|||||||
ListRows,
|
ListRows,
|
||||||
ListRow,
|
ListRow,
|
||||||
ListRowItem,
|
ListRowItem,
|
||||||
ListSelectBanner,
|
toast,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { Plus, Trash2 } from 'lucide-vue-next'
|
import { Plus } from 'lucide-vue-next'
|
||||||
|
import { cleanError } from '@/utils'
|
||||||
import BadgeForm from '@/components/Settings/BadgeForm.vue'
|
import BadgeForm from '@/components/Settings/BadgeForm.vue'
|
||||||
|
import BadgeAssignments from '@/components/Settings/BadgeAssignments.vue'
|
||||||
|
|
||||||
const showForm = ref<boolean>(false)
|
const showForm = ref<boolean>(false)
|
||||||
const selectedBadge = ref<string | null>(null)
|
const selectedBadge = ref<string | null>(null)
|
||||||
|
const showAssignments = ref<boolean>(false)
|
||||||
|
const showAssignmentsFor = ref<string | null>(null)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
label: string
|
label: string
|
||||||
@@ -165,7 +163,15 @@ const getMoreOptions = (badgeName: string) => {
|
|||||||
label: __('Assignments'),
|
label: __('Assignments'),
|
||||||
icon: 'download',
|
icon: 'download',
|
||||||
onClick() {
|
onClick() {
|
||||||
console.log('assignments')
|
showAssignmentsFor.value = badgeName
|
||||||
|
showAssignments.value = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Delete'),
|
||||||
|
icon: 'trash-2',
|
||||||
|
onClick() {
|
||||||
|
deleteBadge(badgeName)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -176,6 +182,18 @@ const openForm = (badgeName: string) => {
|
|||||||
showForm.value = true
|
showForm.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteBadge = (badgeName: string) => {
|
||||||
|
badges.delete
|
||||||
|
.submit(badgeName)
|
||||||
|
.then(() => {
|
||||||
|
badges.reload()
|
||||||
|
toast.success(__('Badge deleted successfully'))
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
toast.error(cleanError(err.messages[0]) || __('Error deleting badge'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const doctypeLabel = computed(() => {
|
const doctypeLabel = computed(() => {
|
||||||
return {
|
return {
|
||||||
'LMS Course': __('Course'),
|
'LMS Course': __('Course'),
|
||||||
@@ -218,7 +236,7 @@ const columns = computed(() => {
|
|||||||
key: 'grant_only_once',
|
key: 'grant_only_once',
|
||||||
icon: 'check',
|
icon: 'check',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: '15%',
|
width: '20%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'action',
|
key: 'action',
|
||||||
|
|||||||
@@ -224,7 +224,8 @@ const tabsStructure = computed(() => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Zoom Accounts',
|
label: 'Zoom Accounts',
|
||||||
description: 'Manage the Zoom accounts for your learning system',
|
description:
|
||||||
|
'Manage zoom accounts to conduct live classes from batches',
|
||||||
icon: 'Video',
|
icon: 'Video',
|
||||||
template: markRaw(ZoomSettings),
|
template: markRaw(ZoomSettings),
|
||||||
},
|
},
|
||||||
@@ -250,7 +251,7 @@ const tabsStructure = computed(() => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Customise',
|
label: 'Customize',
|
||||||
hideLabel: false,
|
hideLabel: false,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
<div class="text-xl font-semibold text-ink-gray-9">
|
<div class="text-xl font-semibold text-ink-gray-9">
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="text-xs text-ink-gray-5">
|
<div class="text-ink-gray-6 leading-5">
|
||||||
{{ __(description) }}
|
{{ __(description) }}
|
||||||
</div> -->
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-5">
|
<div class="flex items-center space-x-5">
|
||||||
<Button @click="openForm('new')">
|
<Button @click="openForm('new')">
|
||||||
@@ -35,10 +35,10 @@
|
|||||||
>
|
>
|
||||||
<ListHeaderItem :item="item" v-for="item in columns">
|
<ListHeaderItem :item="item" v-for="item in columns">
|
||||||
<template #prefix="{ item }">
|
<template #prefix="{ item }">
|
||||||
<component
|
<FeatherIcon
|
||||||
v-if="item.icon"
|
v-if="item.icon"
|
||||||
:is="item.icon"
|
:name="item.icon"
|
||||||
class="h-4 w-4 stroke-1.5 ml-4"
|
class="h-4 w-4 stroke-1.5"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</ListHeaderItem>
|
</ListHeaderItem>
|
||||||
@@ -48,6 +48,16 @@
|
|||||||
<ListRow :row="row" v-for="row in zoomAccounts.data">
|
<ListRow :row="row" v-for="row in zoomAccounts.data">
|
||||||
<template #default="{ column, item }">
|
<template #default="{ column, item }">
|
||||||
<ListRowItem :item="row[column.key]" :align="column.align">
|
<ListRowItem :item="row[column.key]" :align="column.align">
|
||||||
|
<template #prefix>
|
||||||
|
<div v-if="column.key == 'member_name'">
|
||||||
|
<Avatar
|
||||||
|
class="flex items-center"
|
||||||
|
:image="row['member_image']"
|
||||||
|
:label="item"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<div v-if="column.key == 'enabled'">
|
<div v-if="column.key == 'enabled'">
|
||||||
<Badge v-if="row[column.key]" theme="green">
|
<Badge v-if="row[column.key]" theme="green">
|
||||||
{{ __('Enabled') }}
|
{{ __('Enabled') }}
|
||||||
@@ -87,10 +97,12 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
|
Avatar,
|
||||||
Button,
|
Button,
|
||||||
Badge,
|
Badge,
|
||||||
call,
|
call,
|
||||||
createListResource,
|
createListResource,
|
||||||
|
FeatherIcon,
|
||||||
ListView,
|
ListView,
|
||||||
ListHeader,
|
ListHeader,
|
||||||
ListHeaderItem,
|
ListHeaderItem,
|
||||||
@@ -122,6 +134,7 @@ const zoomAccounts = createListResource({
|
|||||||
'enabled',
|
'enabled',
|
||||||
'member',
|
'member',
|
||||||
'member_name',
|
'member_name',
|
||||||
|
'member_image',
|
||||||
'account_id',
|
'account_id',
|
||||||
'client_id',
|
'client_id',
|
||||||
'client_secret',
|
'client_secret',
|
||||||
@@ -170,18 +183,21 @@ const removeAccount = (selections, unselectAll) => {
|
|||||||
|
|
||||||
const columns = computed(() => {
|
const columns = computed(() => {
|
||||||
return [
|
return [
|
||||||
{
|
|
||||||
label: __('Account'),
|
|
||||||
key: 'name',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: __('Member'),
|
label: __('Member'),
|
||||||
key: 'member_name',
|
key: 'member_name',
|
||||||
|
icon: 'user',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Account Name'),
|
||||||
|
key: 'name',
|
||||||
|
icon: 'video',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: __('Status'),
|
label: __('Status'),
|
||||||
key: 'enabled',
|
key: 'enabled',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
icon: 'check-square',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export interface Badge {
|
|||||||
grant_only_once: boolean;
|
grant_only_once: boolean;
|
||||||
event: string;
|
event: string;
|
||||||
reference_doctype: string;
|
reference_doctype: string;
|
||||||
condition: string | null;
|
condition: string;
|
||||||
user_field: string;
|
user_field: string;
|
||||||
field_to_check: string;
|
field_to_check: string;
|
||||||
};
|
};
|
||||||
@@ -45,3 +45,30 @@ export interface Badges {
|
|||||||
) => void
|
) => void
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BadgeAssignment {
|
||||||
|
name: string;
|
||||||
|
member: string;
|
||||||
|
member_name: string;
|
||||||
|
member_username: string;
|
||||||
|
member_image: string;
|
||||||
|
badge: string;
|
||||||
|
issued_on: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BadgeAssignments {
|
||||||
|
data: BadgeAssignment[],
|
||||||
|
reload: () => void
|
||||||
|
insert: {
|
||||||
|
submit: (
|
||||||
|
data: BadgeAssignment,
|
||||||
|
options: { onSuccess: () => void; onError: (err: any) => void }
|
||||||
|
) => void
|
||||||
|
},
|
||||||
|
setValue: {
|
||||||
|
submit: (
|
||||||
|
data: BadgeAssignment,
|
||||||
|
options: { onSuccess: () => void; onError: (err: any) => void }
|
||||||
|
) => void
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -7,8 +7,10 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"member",
|
"member",
|
||||||
"member_name",
|
"member_name",
|
||||||
"issued_on",
|
"member_username",
|
||||||
|
"member_image",
|
||||||
"column_break_ugix",
|
"column_break_ugix",
|
||||||
|
"issued_on",
|
||||||
"badge",
|
"badge",
|
||||||
"badge_image",
|
"badge_image",
|
||||||
"badge_description"
|
"badge_description"
|
||||||
@@ -65,12 +67,25 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Member Name",
|
"label": "Member Name",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "member.username",
|
||||||
|
"fieldname": "member_username",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Member Username"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "member.user_image",
|
||||||
|
"fieldname": "member_image",
|
||||||
|
"fieldtype": "Attach Image",
|
||||||
|
"label": "Member Image"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-01-06 12:32:28.450028",
|
"modified": "2025-07-07 20:37:22.449149",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "sayali@frappe.io",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Badge Assignment",
|
"name": "LMS Badge Assignment",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
@@ -122,6 +137,7 @@
|
|||||||
"share": 1
|
"share": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
"show_title_field_in_link": 1,
|
"show_title_field_in_link": 1,
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"account_name",
|
"account_name",
|
||||||
"member",
|
"member",
|
||||||
"member_name",
|
"member_name",
|
||||||
|
"member_image",
|
||||||
"column_break_fxxg",
|
"column_break_fxxg",
|
||||||
"account_id",
|
"account_id",
|
||||||
"client_id",
|
"client_id",
|
||||||
@@ -71,12 +72,18 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_fxxg",
|
"fieldname": "column_break_fxxg",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "member.user_image",
|
||||||
|
"fieldname": "member_image",
|
||||||
|
"fieldtype": "Attach Image",
|
||||||
|
"label": "Member Image"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-05-26 18:09:09.392368",
|
"modified": "2025-07-08 12:20:48.314056",
|
||||||
"modified_by": "sayali@frappe.io",
|
"modified_by": "sayali@frappe.io",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Zoom Settings",
|
"name": "LMS Zoom Settings",
|
||||||
|
|||||||
Reference in New Issue
Block a user