refactor: enhanced assignment form

This commit is contained in:
Jannat Patel
2025-04-17 21:42:43 +05:30
parent 650594d9ea
commit 60ad86f79c
7 changed files with 179 additions and 233 deletions

View File

@@ -16,6 +16,7 @@ declare module 'vue' {
AssessmentPlugin: typeof import('./src/components/AssessmentPlugin.vue')['default']
Assessments: typeof import('./src/components/Assessments.vue')['default']
Assignment: typeof import('./src/components/Assignment.vue')['default']
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']
BatchCard: typeof import('./src/components/BatchCard.vue')['default']

View File

@@ -0,0 +1,147 @@
<template>
<Dialog
v-model="show"
:options="{
title:
assignmentID === 'new'
? __('Create an Assignment')
: __('Edit Assignment'),
size: 'lg',
actions: [
{
label: __('Save'),
variant: 'solid',
onClick: (close) => saveAssignment(close),
},
],
}"
>
<template #body-content>
<div class="space-y-4 text-base">
<FormControl
v-model="assignment.title"
:label="__('Title')"
:required="true"
/>
<FormControl
v-model="assignment.type"
type="select"
:options="assignmentOptions"
:label="__('Submission Type')"
:required="true"
/>
<div>
<div class="text-xs text-ink-gray-5 mb-2">
{{ __('Question') }}
<span class="text-ink-red-3">*</span>
</div>
<TextEditor
:content="assignment.question"
@change="(val) => (assignment.question = val)"
:editable="true"
:fixedMenu="true"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
/>
</div>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { Dialog, FormControl, TextEditor } from 'frappe-ui'
import { computed, reactive, watch } from 'vue'
import { showToast } from '@/utils'
const show = defineModel('show')
const assignments = defineModel<Assignments>('assignments')
interface Assignment {
title: string
type: string
question: string
}
interface Assignments {
data: Assignment[]
get: (params: { doctype: string; name: string }) => Promise<Assignment>
insert: {
submit: (params: Assignment, options: { onSuccess: () => void }) => void
}
}
const assignment = reactive({
title: '',
type: '',
question: '',
})
const props = defineProps({
assignmentID: {
type: String,
default: 'new',
},
})
watch(
() => props.assignmentID,
(val) => {
if (val !== 'new') {
assignments.value?.data.forEach((row) => {
if (row.name === val) {
assignment.title = row.title
assignment.type = row.type
assignment.question = row.question
}
})
}
},
{ flush: 'post' }
)
const saveAssignment = (close) => {
if (props.assignmentID == 'new') {
assignments.value.insert.submit(
{
...assignment,
},
{
onSuccess() {
close()
showToast(
__('Success'),
__('Assignment created successfully'),
'check'
)
},
}
)
} else {
assignments.value.setValue.submit(
{
...assignment,
name: props.assignmentID,
},
{
onSuccess() {
close()
showToast(
__('Success'),
__('Assignment updated successfully'),
'check'
)
},
}
)
}
}
const assignmentOptions = computed(() => {
return [
{ label: 'PDF', value: 'PDF' },
{ label: 'Image', value: 'Image' },
{ label: 'Document', value: 'Document' },
{ label: 'Text', value: 'Text' },
{ label: 'URL', value: 'URL' },
]
})
</script>

View File

@@ -54,7 +54,7 @@
<div v-else>
<div class="flex items-center text-sm space-x-2">
<div
class="flex items-center justify-center rounded border border-outline-gray-modals w-[10rem] py-5"
class="flex items-center justify-center rounded border border-outline-gray-modals bg-white w-[10rem] py-5"
>
<img :src="data[field.name]?.file_url" class="h-6 rounded" />
</div>
@@ -68,7 +68,7 @@
</div>
<X
@click="data[field.name] = null"
class="bg-surface-gray-5 rounded-md cursor-pointer stroke-1.5 w-5 h-5 p-1 ml-4"
class="border text-ink-gray-7 border-outline-gray-3 rounded-md cursor-pointer stroke-1.5 w-5 h-5 p-1 ml-4"
/>
</div>
</div>
@@ -99,7 +99,7 @@
</template>
<script setup>
import { FormControl, FileUploader, Button, Switch } from 'frappe-ui'
import { computed, onMounted } from 'vue'
import { computed } from 'vue'
import { getFileSize, validateFile } from '@/utils'
import { X } from 'lucide-vue-next'
import Link from '@/components/Controls/Link.vue'

View File

@@ -1,201 +0,0 @@
<template>
<header
class="sticky top-0 z-10 flex items-center justify-between border-b bg-surface-white px-3 py-2.5 sm:px-5"
>
<Breadcrumbs :items="breadcrumbs" />
<div class="space-x-2">
<router-link
v-if="assignment.doc?.name"
:to="{
name: 'AssignmentSubmissionList',
query: {
assignmentID: assignment.doc.name,
},
}"
>
<Button>
{{ __('Submission List') }}
</Button>
</router-link>
<Button variant="solid" @click="saveAssignment()">
{{ __('Save') }}
</Button>
</div>
</header>
<div class="w-3/4 mx-auto py-5">
<div class="font-semibold mb-4">
{{ __('Details') }}
</div>
<div class="grid grid-cols-2 gap-5 mt-4 mb-8">
<FormControl
v-model="model.title"
:label="__('Title')"
:required="true"
/>
<FormControl
v-model="model.type"
type="select"
:options="assignmentOptions"
:label="__('Type')"
:required="true"
/>
</div>
<div>
<div class="text-xs text-ink-gray-5 mb-2">
{{ __('Question') }}
<span class="text-ink-red-3">*</span>
</div>
<TextEditor
:content="model.question"
@change="(val) => (model.question = val)"
:editable="true"
:fixedMenu="true"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
/>
</div>
</div>
</template>
<script setup>
import {
Breadcrumbs,
Button,
createDocumentResource,
createResource,
FormControl,
TextEditor,
usePageMeta,
} from 'frappe-ui'
import {
computed,
inject,
onMounted,
onBeforeUnmount,
reactive,
watch,
} from 'vue'
import { sessionStore } from '../stores/session'
import { showToast } from '@/utils'
import { useRouter } from 'vue-router'
const user = inject('$user')
const router = useRouter()
const { brand } = sessionStore()
const props = defineProps({
assignmentID: {
type: String,
required: true,
},
})
const model = reactive({
title: '',
type: 'PDF',
question: '',
})
onMounted(() => {
if (
props.assignmentID == 'new' &&
!user.data?.is_moderator &&
!user.data?.is_instructor
) {
router.push({ name: 'Courses' })
}
if (props.assignmentID !== 'new') {
assignment.reload()
}
window.addEventListener('keydown', keyboardShortcut)
})
const keyboardShortcut = (e) => {
if (e.key === 's' && (e.ctrlKey || e.metaKey)) {
saveAssignment()
e.preventDefault()
}
}
onBeforeUnmount(() => {
window.removeEventListener('keydown', keyboardShortcut)
})
const assignment = createDocumentResource({
doctype: 'LMS Assignment',
name: props.assignmentID,
auto: false,
})
const newAssignment = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'LMS Assignment',
...values,
},
}
},
onSuccess(data) {
router.push({ name: 'AssignmentForm', params: { assignmentID: data.name } })
},
onError(err) {
showToast(__('Error'), __(err.messages?.[0] || err), 'x')
},
})
const saveAssignment = () => {
if (props.assignmentID == 'new') {
newAssignment.submit({
...model,
})
} else {
assignment.setValue.submit(
{
...model,
},
{
onSuccess(data) {
showToast(__('Success'), __('Assignment saved successfully'), 'check')
assignment.reload()
},
onError(err) {
showToast(__('Error'), __(err.messages?.[0] || err), 'x')
},
}
)
}
}
watch(assignment, () => {
Object.keys(assignment.doc).forEach((key) => {
model[key] = assignment.doc[key]
})
})
const breadcrumbs = computed(() => [
{
label: __('Assignments'),
route: { name: 'Assignments' },
},
{
label: assignment.doc ? assignment.doc.title : __('New Assignment'),
},
])
const assignmentOptions = computed(() => {
return [
{ label: 'PDF', value: 'PDF' },
{ label: 'Image', value: 'Image' },
{ label: 'Document', value: 'Document' },
{ label: 'Text', value: 'Text' },
{ label: 'URL', value: 'URL' },
]
})
usePageMeta(() => {
return {
title: assignment.doc ? assignment.doc.title : __('New Assignment'),
icon: brand.favicon,
}
})
</script>

View File

@@ -3,21 +3,20 @@
class="sticky top-0 z-10 flex items-center justify-between border-b bg-surface-white px-3 py-2.5 sm:px-5"
>
<Breadcrumbs :items="breadcrumbs" />
<router-link
:to="{
name: 'AssignmentForm',
params: {
assignmentID: 'new',
},
}"
<Button
variant="solid"
@click="
() => {
assignmentID = 'new'
showAssignmentForm = true
}
"
>
<Button variant="solid">
<template #prefix>
<Plus class="w-4 h-4" />
</template>
{{ __('New') }}
</Button>
</router-link>
<template #prefix>
<Plus class="w-4 h-4" />
</template>
{{ __('New') }}
</Button>
</header>
<div class="md:w-3/4 md:mx-auto py-5 mx-5">
@@ -38,12 +37,10 @@
:options="{
showTooltip: false,
selectable: false,
getRowRoute: (row) => ({
name: 'AssignmentForm',
params: {
assignmentID: row.name,
},
}),
onRowClick: (row) => {
assignmentID = row.name
showAssignmentForm = true
},
}"
>
</ListView>
@@ -72,6 +69,11 @@
</Button>
</div>
</div>
<AssignmentForm
v-model="showAssignmentForm"
v-model:assignments="assignments"
:assignmentID="assignmentID"
/>
</template>
<script setup>
import {
@@ -86,11 +88,14 @@ import { computed, inject, onMounted, ref, watch } from 'vue'
import { Plus, Pencil } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import { sessionStore } from '../stores/session'
import AssignmentForm from '@/components/Modals/AssignmentForm.vue'
const user = inject('$user')
const dayjs = inject('$dayjs')
const titleFilter = ref('')
const typeFilter = ref('')
const showAssignmentForm = ref(false)
const assignmentID = ref('new')
const { brand } = sessionStore()
const router = useRouter()
@@ -136,7 +141,7 @@ const assignmentFilter = computed(() => {
const assignments = createListResource({
doctype: 'LMS Assignment',
fields: ['name', 'title', 'type', 'creation'],
fields: ['name', 'title', 'type', 'creation', 'question'],
orderBy: 'modified desc',
cache: ['assignments'],
transform(data) {
@@ -166,7 +171,7 @@ const assignmentColumns = computed(() => {
label: __('Created'),
key: 'creation',
width: 1,
align: 'center',
align: 'right',
},
]
})

View File

@@ -199,12 +199,6 @@ const routes = [
name: 'Assignments',
component: () => import('@/pages/Assignments.vue'),
},
{
path: '/assignments/:assignmentID',
name: 'AssignmentForm',
component: () => import('@/pages/AssignmentForm.vue'),
props: true,
},
{
path: '/assignment-submission/:assignmentID/:submissionName',
name: 'AssignmentSubmission',

View File

@@ -25,7 +25,7 @@ export default defineConfig({
}),
],
server: {
allowedHosts: ['fs', 'onb2'],
allowedHosts: ['fs', 'bs'],
},
resolve: {
alias: {