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']
|
||||
AudioBlock: typeof import('./src/components/AudioBlock.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']
|
||||
Badges: typeof import('./src/components/Settings/Badges.vue')['default']
|
||||
Bagdes: typeof import('./src/components/Settings/Bagdes.vue')['default']
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
>
|
||||
<template v-slot="{ file, progress, uploading, openFileSelector }">
|
||||
<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" />
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
@@ -20,7 +20,7 @@
|
||||
{{ __('Upload') }}
|
||||
</Button>
|
||||
<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>
|
||||
|
||||
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"
|
||||
/>
|
||||
<Autocomplete
|
||||
@update:modelValue="(opt) => (badge.reference_doctype = opt.value)"
|
||||
@update:modelValue="(opt: any) => (badge.reference_doctype = opt.value)"
|
||||
:modelValue="badge.reference_doctype"
|
||||
:options="referenceDoctypeOptions"
|
||||
:required="true"
|
||||
@@ -98,12 +98,12 @@ const defaultBadge = {
|
||||
image: '',
|
||||
grant_only_once: false,
|
||||
event: 'New',
|
||||
reference_doctype: 'LMS Course',
|
||||
condition: null,
|
||||
reference_doctype: '',
|
||||
condition: '',
|
||||
user_field: 'member',
|
||||
field_to_check: '',
|
||||
}
|
||||
const show = defineModel<boolean | undefined>()
|
||||
const show = defineModel<boolean>({ required: true, default: false })
|
||||
const badges = defineModel<Badges>('badges')
|
||||
const badge = ref<Badge>(defaultBadge)
|
||||
|
||||
@@ -135,7 +135,6 @@ const saveBadge = (close: () => void) => {
|
||||
}
|
||||
|
||||
const updateBadge = async (close: () => void) => {
|
||||
console.log(props.badgeName, badge.value?.title)
|
||||
if (props.badgeName != badge.value?.title) {
|
||||
await renameDoc()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<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 flex-col space-y-2">
|
||||
<div class="text-xl font-semibold text-ink-gray-9">
|
||||
@@ -23,6 +28,7 @@
|
||||
row-key="name"
|
||||
:options="{
|
||||
showTooltip: false,
|
||||
selectable: false,
|
||||
}"
|
||||
>
|
||||
<ListHeader
|
||||
@@ -51,7 +57,11 @@
|
||||
</Badge>
|
||||
</div>
|
||||
<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>
|
||||
<FormControl
|
||||
v-else-if="column.key == 'grant_only_once'"
|
||||
@@ -65,15 +75,12 @@
|
||||
>
|
||||
{{ row[column.key] }}
|
||||
</div>
|
||||
<!-- <Button v-else variant="ghost">
|
||||
<Ellipsis class="size-4 stroke-1.5 text-ink-gray-9" />
|
||||
</Button> -->
|
||||
<Dropdown
|
||||
v-else
|
||||
:options="getMoreOptions(row.name)"
|
||||
:button="{
|
||||
icon: 'more-horizontal',
|
||||
onblur: (e) => {
|
||||
onblur: (e: Event) => {
|
||||
e.stopPropagation()
|
||||
},
|
||||
}"
|
||||
@@ -83,19 +90,6 @@
|
||||
</template>
|
||||
</ListRow>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,14 +113,18 @@ import {
|
||||
ListRows,
|
||||
ListRow,
|
||||
ListRowItem,
|
||||
ListSelectBanner,
|
||||
toast,
|
||||
} from 'frappe-ui'
|
||||
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 BadgeAssignments from '@/components/Settings/BadgeAssignments.vue'
|
||||
|
||||
const showForm = ref<boolean>(false)
|
||||
const selectedBadge = ref<string | null>(null)
|
||||
const showAssignments = ref<boolean>(false)
|
||||
const showAssignmentsFor = ref<string | null>(null)
|
||||
|
||||
const props = defineProps<{
|
||||
label: string
|
||||
@@ -165,7 +163,15 @@ const getMoreOptions = (badgeName: string) => {
|
||||
label: __('Assignments'),
|
||||
icon: 'download',
|
||||
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
|
||||
}
|
||||
|
||||
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(() => {
|
||||
return {
|
||||
'LMS Course': __('Course'),
|
||||
@@ -218,7 +236,7 @@ const columns = computed(() => {
|
||||
key: 'grant_only_once',
|
||||
icon: 'check',
|
||||
align: 'center',
|
||||
width: '15%',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
|
||||
@@ -224,7 +224,8 @@ const tabsStructure = computed(() => {
|
||||
},
|
||||
{
|
||||
label: 'Zoom Accounts',
|
||||
description: 'Manage the Zoom accounts for your learning system',
|
||||
description:
|
||||
'Manage zoom accounts to conduct live classes from batches',
|
||||
icon: 'Video',
|
||||
template: markRaw(ZoomSettings),
|
||||
},
|
||||
@@ -250,7 +251,7 @@ const tabsStructure = computed(() => {
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Customise',
|
||||
label: 'Customize',
|
||||
hideLabel: false,
|
||||
items: [
|
||||
{
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<div class="text-xl font-semibold text-ink-gray-9">
|
||||
{{ label }}
|
||||
</div>
|
||||
<!-- <div class="text-xs text-ink-gray-5">
|
||||
<div class="text-ink-gray-6 leading-5">
|
||||
{{ __(description) }}
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-5">
|
||||
<Button @click="openForm('new')">
|
||||
@@ -35,10 +35,10 @@
|
||||
>
|
||||
<ListHeaderItem :item="item" v-for="item in columns">
|
||||
<template #prefix="{ item }">
|
||||
<component
|
||||
<FeatherIcon
|
||||
v-if="item.icon"
|
||||
:is="item.icon"
|
||||
class="h-4 w-4 stroke-1.5 ml-4"
|
||||
:name="item.icon"
|
||||
class="h-4 w-4 stroke-1.5"
|
||||
/>
|
||||
</template>
|
||||
</ListHeaderItem>
|
||||
@@ -48,6 +48,16 @@
|
||||
<ListRow :row="row" v-for="row in zoomAccounts.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 v-if="column.key == 'enabled'">
|
||||
<Badge v-if="row[column.key]" theme="green">
|
||||
{{ __('Enabled') }}
|
||||
@@ -87,10 +97,12 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Badge,
|
||||
call,
|
||||
createListResource,
|
||||
FeatherIcon,
|
||||
ListView,
|
||||
ListHeader,
|
||||
ListHeaderItem,
|
||||
@@ -122,6 +134,7 @@ const zoomAccounts = createListResource({
|
||||
'enabled',
|
||||
'member',
|
||||
'member_name',
|
||||
'member_image',
|
||||
'account_id',
|
||||
'client_id',
|
||||
'client_secret',
|
||||
@@ -170,18 +183,21 @@ const removeAccount = (selections, unselectAll) => {
|
||||
|
||||
const columns = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: __('Account'),
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
label: __('Member'),
|
||||
key: 'member_name',
|
||||
icon: 'user',
|
||||
},
|
||||
{
|
||||
label: __('Account Name'),
|
||||
key: 'name',
|
||||
icon: 'video',
|
||||
},
|
||||
{
|
||||
label: __('Status'),
|
||||
key: 'enabled',
|
||||
align: 'center',
|
||||
icon: 'check-square',
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
@@ -24,7 +24,7 @@ export interface Badge {
|
||||
grant_only_once: boolean;
|
||||
event: string;
|
||||
reference_doctype: string;
|
||||
condition: string | null;
|
||||
condition: string;
|
||||
user_field: string;
|
||||
field_to_check: string;
|
||||
};
|
||||
@@ -44,4 +44,31 @@ export interface Badges {
|
||||
options: { onSuccess: () => void; onError: (err: any) => 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
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user