feat: exercise form and submission list
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<header
|
||||
class="sticky flex items-center justify-between top-0 z-10 border-b bg-surface-white px-3 py-2.5 sm:px-5"
|
||||
>
|
||||
<Breadcrumbs :items="breadcrumbs" />
|
||||
</header>
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between space-x-32 mb-5">
|
||||
<div class="text-xl font-semibold text-ink-gray-7">
|
||||
{{
|
||||
submissions.data?.length
|
||||
? __('{0} Submissions').format(submissions.data.length)
|
||||
: __('No Submissions')
|
||||
}}
|
||||
</div>
|
||||
<div
|
||||
v-if="submissions.data?.length"
|
||||
class="grid grid-cols-3 gap-5 flex-1"
|
||||
>
|
||||
<Link
|
||||
doctype="LMS Programming Exercise"
|
||||
v-model="filters.exercise"
|
||||
:placeholder="__('Filter by Exercise')"
|
||||
/>
|
||||
<Link
|
||||
doctype="User"
|
||||
v-model="filters.member"
|
||||
:placeholder="__('Filter by Member')"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="filters.status"
|
||||
type="select"
|
||||
:options="[
|
||||
{ label: __(''), value: '' },
|
||||
{ label: __('Passed'), value: 'Passed' },
|
||||
{ label: __('Failed'), value: 'Failed' },
|
||||
]"
|
||||
:placeholder="__('Filter by Status')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ListView
|
||||
v-if="submissions.loading || submissions.data?.length"
|
||||
:columns="submissionColumns"
|
||||
:rows="submissions.data"
|
||||
rowKey="name"
|
||||
:options="{
|
||||
selectable: false,
|
||||
}"
|
||||
>
|
||||
<ListHeader
|
||||
class="mb-2 grid items-center space-x-4 rounded bg-surface-gray-2 p-2"
|
||||
>
|
||||
<ListHeaderItem
|
||||
:item="item"
|
||||
v-for="item in submissionColumns"
|
||||
:key="item.key"
|
||||
>
|
||||
<template #prefix="{ item }">
|
||||
<FeatherIcon :name="item.icon?.toString()" class="h-4 w-4" />
|
||||
</template>
|
||||
</ListHeaderItem>
|
||||
</ListHeader>
|
||||
<ListRows>
|
||||
<router-link
|
||||
v-for="row in submissions.data"
|
||||
:to="{
|
||||
name: 'ProgrammingExerciseSubmission',
|
||||
params: {
|
||||
exerciseID: row.exercise,
|
||||
submissionID: row.name,
|
||||
},
|
||||
}"
|
||||
>
|
||||
<ListRow :row="row">
|
||||
<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 == 'status'">
|
||||
<Badge
|
||||
:theme="row[column.key] === 'Passed' ? 'green' : 'red'"
|
||||
>
|
||||
{{ row[column.key] }}
|
||||
</Badge>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ row[column.key] }}
|
||||
</div>
|
||||
</ListRowItem>
|
||||
</template>
|
||||
</ListRow>
|
||||
</router-link>
|
||||
</ListRows>
|
||||
</ListView>
|
||||
<EmptyState v-else type="Programming Exercise Submissions" />
|
||||
<div
|
||||
v-if="submissions.data && submissions.hasNextPage"
|
||||
class="flex justify-center my-5"
|
||||
>
|
||||
<Button @click="submissions.next()">
|
||||
{{ __('Load More') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Avatar,
|
||||
Badge,
|
||||
Breadcrumbs,
|
||||
Button,
|
||||
createListResource,
|
||||
FeatherIcon,
|
||||
FormControl,
|
||||
ListView,
|
||||
ListHeader,
|
||||
ListHeaderItem,
|
||||
ListRows,
|
||||
ListRow,
|
||||
ListRowItem,
|
||||
usePageMeta,
|
||||
} from 'frappe-ui'
|
||||
import { computed, inject, ref, watch } from 'vue'
|
||||
import { sessionStore } from '@/stores/session'
|
||||
import type {
|
||||
ProgrammingExerciseSubmission,
|
||||
Filters,
|
||||
} from '@/pages/ProgrammingExercises/types'
|
||||
import { useRouter } from 'vue-router'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
import EmptyState from '@/components/EmptyState.vue'
|
||||
|
||||
const { brand } = sessionStore()
|
||||
const dayjs = inject('$dayjs') as any
|
||||
const filters = ref<Filters>({
|
||||
exercise: '',
|
||||
member: '',
|
||||
status: '',
|
||||
})
|
||||
const router = useRouter()
|
||||
|
||||
const submissions = createListResource({
|
||||
doctype: 'LMS Programming Exercise Submission',
|
||||
fields: [
|
||||
'name',
|
||||
'exercise',
|
||||
'exercise_title',
|
||||
'member_name',
|
||||
'member_image',
|
||||
'status',
|
||||
'modified',
|
||||
],
|
||||
auto: true,
|
||||
transform(data: ProgrammingExercise[]) {
|
||||
return data.map((submission: ProgrammingExerciseSubmission) => {
|
||||
return {
|
||||
...submission,
|
||||
modified: dayjs(submission.modified).fromNow(),
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
watch(filters.value, () => {
|
||||
let filtersToApply: Record<string, any> = {}
|
||||
const filterFields = ['exercise', 'member', 'status']
|
||||
filterFields.forEach((field) => {
|
||||
if (filters.value[field as keyof Filters]) {
|
||||
filtersToApply[field] = filters.value[field as keyof Filters]
|
||||
router.push({
|
||||
query: {
|
||||
...router.currentRoute.value.query,
|
||||
[field]: filters.value[field as keyof Filters],
|
||||
},
|
||||
})
|
||||
} else {
|
||||
delete filtersToApply[field]
|
||||
const query = { ...router.currentRoute.value.query }
|
||||
delete query[field]
|
||||
router.push({
|
||||
query,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
submissions.update({
|
||||
filters: {
|
||||
...filtersToApply,
|
||||
},
|
||||
})
|
||||
submissions.reload()
|
||||
})
|
||||
|
||||
const submissionColumns = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: __('Member'),
|
||||
key: 'member_name',
|
||||
width: '20%',
|
||||
icon: 'user',
|
||||
},
|
||||
{
|
||||
label: __('Exercise'),
|
||||
key: 'exercise_title',
|
||||
width: '40%',
|
||||
icon: 'code',
|
||||
},
|
||||
{
|
||||
label: __('Status'),
|
||||
key: 'status',
|
||||
width: '20%',
|
||||
icon: 'check-circle',
|
||||
},
|
||||
{
|
||||
label: __('Modified'),
|
||||
key: 'modified',
|
||||
width: '20%',
|
||||
icon: 'clock',
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: __('Programming Exercise Submissions'),
|
||||
route: {
|
||||
name: 'ProgrammingExerciseSubmissions',
|
||||
},
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
usePageMeta(() => {
|
||||
return {
|
||||
title: __('Programming Exercises'),
|
||||
icon: brand.favicon,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user