teacher-tools/app/src/apps/classroom_compliance/ClassView.vue

203 lines
7.6 KiB
Vue

<script setup lang="ts">
import { useAuthStore } from '@/stores/auth'
import { computed, onMounted, ref, useTemplateRef, type Ref } from 'vue'
import EntriesTable from '@/apps/classroom_compliance/EntriesTable.vue'
import { useRouter } from 'vue-router'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
import { ClassroomComplianceAPIClient, type Class, type ClassNote } from '@/api/classroom_compliance'
import ClassNoteItem from './ClassNoteItem.vue'
const props = defineProps<{
id: string
}>()
const authStore = useAuthStore()
const router = useRouter()
const cls: Ref<Class | null> = ref(null)
const notes: Ref<ClassNote[]> = ref([])
const noteContent: Ref<string> = ref('')
const scoreExpression: Ref<string> = ref('')
const scorePeriod: Ref<string> = ref('')
const canUpdateScoreParameters = computed(() => {
return cls.value && (
cls.value.scoreExpression !== scoreExpression.value ||
cls.value.scorePeriod !== scorePeriod.value
)
})
const entriesTable = useTemplateRef('entries-table')
const deleteClassDialog = useTemplateRef('deleteClassDialog')
const apiClient = new ClassroomComplianceAPIClient(authStore)
onMounted(() => {
loadClass()
})
function loadClass() {
const idNumber = parseInt(props.id, 10)
apiClient.getClass(idNumber).handleErrorsWithAlert().then(result => {
if (result) {
cls.value = result
refreshNotes()
scoreExpression.value = cls.value.scoreExpression
scorePeriod.value = cls.value.scorePeriod
} else {
router.back();
}
})
}
async function deleteThisClass() {
if (!cls.value || !(await deleteClassDialog.value?.show())) return
await apiClient.deleteClass(cls.value.id).handleErrorsWithAlert()
await router.replace('/classroom-compliance')
}
async function submitNote() {
if (noteContent.value.trim().length < 1 || !cls.value) return
const note = await apiClient.createClassNote(cls.value?.id, noteContent.value).handleErrorsWithAlert()
if (note) {
noteContent.value = ''
const updatedNotes = await apiClient.getClassNotes(cls.value?.id).handleErrorsWithAlert()
if (updatedNotes !== null) {
notes.value = updatedNotes
}
}
}
function refreshNotes() {
if (cls.value === null) {
notes.value = []
return
}
apiClient.getClassNotes(cls.value.id).handleErrorsWithAlert().then(notesResult => {
if (notesResult !== null) {
notes.value = notesResult
} else {
notes.value = []
}
})
}
async function resetStudentDesks() {
if (!cls.value) return
await apiClient.resetStudentDesks(cls.value.id).handleErrorsWithAlert()
// Reload the table!
await entriesTable.value?.loadEntries()
}
async function submitScoreParametersUpdate() {
if (!cls.value) return
const result = await apiClient.updateScoreParameters(
cls.value.id,
scoreExpression.value,
scorePeriod.value
).handleErrorsWithAlert()
if (result === null) return
// The class was updated successfully, so update the page's values.
cls.value = result
scoreExpression.value = cls.value.scoreExpression
scorePeriod.value = cls.value.scorePeriod
// Reload the table with the updated scores.
await entriesTable.value?.loadEntries()
}
function resetScoreParameters() {
if (!cls.value) return
scoreExpression.value = cls.value.scoreExpression
scorePeriod.value = cls.value.scorePeriod
}
</script>
<template>
<div v-if="cls">
<h1 class="align-center" style="margin-bottom: 0;">Class <span v-text="cls.number"></span></h1>
<p class="align-center" style="margin-top: 0; margin-bottom: 2em;">For the {{ cls.schoolYear }} school year.</p>
<div class="button-bar align-center" style="margin-bottom: 1em;">
<button type="button" @click="router.push(`/classroom-compliance/classes/${cls.id}/edit-student`)">Add
Student</button>
<button type="button" @click="router.push(`/classroom-compliance/classes/${cls.id}/import-students`)">Import
Students</button>
<button type="button" @click="resetStudentDesks">Clear Assigned Desks</button>
<button type="button" @click="deleteThisClass">Delete this Class</button>
</div>
<EntriesTable :classId="cls.id" ref="entries-table" />
<div>
<h3 style="margin-bottom: 0.25em;">Notes</h3>
<form @submit.prevent="submitNote">
<textarea style="min-height: 50px; min-width: 300px;" maxlength="2000" minlength="1"
v-model="noteContent"></textarea>
<button style="vertical-align: top; margin-left: 0.5em;" type="submit">Add Note</button>
</form>
<ClassNoteItem v-for="note in notes" :key="note.id" :note="note" @noteDeleted="refreshNotes()" />
</div>
<div>
<h3 style="margin-bottom: 0.25em;">Scoring</h3>
<p style="margin-top: 0.25em; margin-bottom: 0.25em;">
Change how scores are calculated for this class here.
</p>
<form @submit.prevent="submitScoreParametersUpdate">
<div>
<label for="score-expression-input">Expression</label>
<textarea id="score-expression-input" v-model="scoreExpression" class="text-mono" minlength="1"
maxlength="255" style="min-width: 500px; min-height: 50px;"></textarea>
</div>
<p class="form-input-hint">
The expression to use to calculate each student's score. This should be a simple mathematical expression that
can use the following variables:
</p>
<ul class="form-input-hint">
<li><span class="score-expression-variable">phone</span> - The student's phone score, defined as the number of
compliant days divided by total days present.</li>
<li><span class="score-expression-variable">behavior_good</span> - The proportion of days that the student had
good behavior.</li>
<li><span class="score-expression-variable">behavior_mediocre</span> - The proportion of days that the student
had mediocre behavior.</li>
<li><span class="score-expression-variable">behavior_poor</span> - The proportion of days that the student had
poor behavior.</li>
<li><span class="score-expression-variable">behavior</span> - A general behavior score, where a student gets
full points for good behavior, half-points for mediocre behavior, and no points for poor behavior.</li>
</ul>
<p class="form-input-hint">
As an example, a common scoring expression might be 50% phone-compliance, and 50% behavior. The expression
below would achieve that:
</p>
<p style="font-family: 'SourceCodePro', monospace; font-size: smaller;">
0.5 * phone + 0.5 * behavior
</p>
<div>
<label for="score-period-select">Period</label>
<select v-model="scorePeriod">
<option value="week">Weekly</option>
</select>
</div>
<p class="form-input-hint">
The period over which to calculate scores.
</p>
<div class="button-bar">
<button type="submit" :disabled="!canUpdateScoreParameters">Save</button>
<button type="button" :disabled="!canUpdateScoreParameters" @click="resetScoreParameters">Reset</button>
</div>
</form>
</div>
<!-- Confirmation dialog used for attempts at deleting this class. -->
<ConfirmDialog ref="deleteClassDialog">
<p>
Are you sure you want to delete this class? All data associated with it (settings, students,
entries, grades, etc.) will be <strong>permanently deleted</strong>, and deleted data is not
recoverable.
</p>
</ConfirmDialog>
</div>
</template>
<style scoped>
.score-expression-variable {
font-family: 'SourceCodePro', monospace;
font-style: normal;
color: rgb(165, 210, 253);
}
</style>