Improved alert formatting, and edit student page.
This commit is contained in:
parent
1d4d98665d
commit
b69852817e
|
@ -2,16 +2,31 @@
|
||||||
* Shows a simple message dialog, with a 'close' button. It uses a pre-rendered
|
* Shows a simple message dialog, with a 'close' button. It uses a pre-rendered
|
||||||
* dialog element that's globally defined for the whole app.
|
* dialog element that's globally defined for the whole app.
|
||||||
* @param msg The message to show.
|
* @param msg The message to show.
|
||||||
|
* @param alertType The type of alert. "info", "warning", "error"
|
||||||
* @returns A promise that resolves when the alert is closed.
|
* @returns A promise that resolves when the alert is closed.
|
||||||
*/
|
*/
|
||||||
export function showAlert(msg: string): Promise<void> {
|
export function showAlert(msg: string, alertType: string = 'info'): Promise<void> {
|
||||||
const dialog = document.getElementById('alert-dialog') as HTMLDialogElement
|
const dialog = document.getElementById('alert-dialog') as HTMLDialogElement
|
||||||
|
dialog.classList.remove('alert-dialog-error', 'alert-dialog-warning') // Clear old styling classes if they're present.
|
||||||
|
if (alertType === 'warning') {
|
||||||
|
dialog.classList.add('alert-dialog-warning')
|
||||||
|
} else if (alertType === 'error') {
|
||||||
|
dialog.classList.add('alert-dialog-error')
|
||||||
|
}
|
||||||
const messageBox = document.getElementById('alert-dialog-message') as HTMLParagraphElement
|
const messageBox = document.getElementById('alert-dialog-message') as HTMLParagraphElement
|
||||||
const closeButton = document.getElementById('alert-dialog-close-button') as HTMLButtonElement
|
const closeButton = document.getElementById('alert-dialog-close-button') as HTMLButtonElement
|
||||||
closeButton.addEventListener('click', () => dialog.close())
|
closeButton.addEventListener('click', closeAlertDialog)
|
||||||
messageBox.innerText = msg
|
messageBox.innerText = msg
|
||||||
dialog.showModal()
|
dialog.showModal()
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
dialog.addEventListener('close', () => resolve())
|
dialog.addEventListener('close', () => {
|
||||||
|
closeButton.removeEventListener('click', closeAlertDialog)
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeAlertDialog() {
|
||||||
|
const dialog = document.getElementById('alert-dialog') as HTMLDialogElement
|
||||||
|
dialog.close()
|
||||||
|
}
|
||||||
|
|
|
@ -30,12 +30,29 @@ export class APIResponse<T> {
|
||||||
async handleErrorsWithAlert(): Promise<T | null> {
|
async handleErrorsWithAlert(): Promise<T | null> {
|
||||||
const value = await this.result
|
const value = await this.result
|
||||||
if (value instanceof APIError) {
|
if (value instanceof APIError) {
|
||||||
await showAlert(value.message)
|
if (value instanceof BadRequestError || value instanceof AuthenticationError) {
|
||||||
|
await showAlert(value.message, 'warning')
|
||||||
|
} else {
|
||||||
|
await showAlert(value.message, 'error')
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleErrorsWithAlertNoBody(): Promise<boolean> {
|
||||||
|
const value = await this.result
|
||||||
|
if (value instanceof APIError) {
|
||||||
|
if (value instanceof BadRequestError || value instanceof AuthenticationError) {
|
||||||
|
await showAlert(value.message, 'warning')
|
||||||
|
} else {
|
||||||
|
await showAlert(value.message, 'error')
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
async getOrThrow(): Promise<T> {
|
async getOrThrow(): Promise<T> {
|
||||||
const value = await this.result
|
const value = await this.result
|
||||||
if (value instanceof APIError) throw value
|
if (value instanceof APIError) throw value
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { showAlert } from '@/alerts';
|
|
||||||
import { APIError } from '@/api/base';
|
|
||||||
import {
|
import {
|
||||||
ClassroomComplianceAPIClient,
|
ClassroomComplianceAPIClient,
|
||||||
type Class,
|
type Class,
|
||||||
|
@ -37,24 +35,35 @@ interface StudentFormData {
|
||||||
const formData: Ref<StudentFormData> = ref({ name: '', deskNumber: null, removed: false })
|
const formData: Ref<StudentFormData> = ref({ name: '', deskNumber: null, removed: false })
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
if (!await loadClass()) return
|
||||||
|
await loadStudent()
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loadClass(): Promise<boolean> {
|
||||||
const classIdNumber = parseInt(props.classId, 10)
|
const classIdNumber = parseInt(props.classId, 10)
|
||||||
cls.value = await apiClient.getClass(classIdNumber).handleErrorsWithAlert()
|
cls.value = await apiClient.getClass(classIdNumber).handleErrorsWithAlert()
|
||||||
if (!cls.value) {
|
if (!cls.value) {
|
||||||
await router.replace('/classroom-compliance')
|
await router.replace('/classroom-compliance')
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadStudent() {
|
||||||
|
const classId = cls.value?.id
|
||||||
|
if (!classId) return
|
||||||
if ('studentId' in route.query && typeof route.query.studentId === 'string') {
|
if ('studentId' in route.query && typeof route.query.studentId === 'string') {
|
||||||
const studentId = parseInt(route.query.studentId, 10)
|
const studentId = parseInt(route.query.studentId, 10)
|
||||||
student.value = await apiClient.getStudent(classIdNumber, studentId).handleErrorsWithAlert()
|
student.value = await apiClient.getStudent(classId, studentId).handleErrorsWithAlert()
|
||||||
if (!student.value) {
|
if (!student.value) {
|
||||||
await router.replace(`/classroom-compliance/classes/${classIdNumber}`)
|
await router.replace(`/classroom-compliance/classes/${classId}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
formData.value.name = student.value.name
|
formData.value.name = student.value.name
|
||||||
formData.value.deskNumber = student.value.deskNumber
|
formData.value.deskNumber = student.value.deskNumber
|
||||||
formData.value.removed = student.value.removed
|
formData.value.removed = student.value.removed
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
async function submitForm() {
|
async function submitForm() {
|
||||||
const classId = parseInt(props.classId, 10)
|
const classId = parseInt(props.classId, 10)
|
||||||
|
@ -111,9 +120,8 @@ async function doMoveClass() {
|
||||||
if (!choice) return
|
if (!choice) return
|
||||||
const newClassId = moveClassDialogClassSelection.value.id
|
const newClassId = moveClassDialogClassSelection.value.id
|
||||||
const result = await apiClient.moveStudentToOtherClass(cls.value.id, student.value.id, newClassId)
|
const result = await apiClient.moveStudentToOtherClass(cls.value.id, student.value.id, newClassId)
|
||||||
if (result instanceof APIError) {
|
.handleErrorsWithAlertNoBody()
|
||||||
await showAlert(result.message)
|
if (result) {
|
||||||
} else {
|
|
||||||
await router.replace(`/classroom-compliance/classes/${newClassId}/students/${student.value.id}`)
|
await router.replace(`/classroom-compliance/classes/${newClassId}/students/${student.value.id}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,17 +139,23 @@ async function doMoveClass() {
|
||||||
<div>
|
<div>
|
||||||
<label for="name-input">Name</label>
|
<label for="name-input">Name</label>
|
||||||
<input id="name-input" type="text" v-model="formData.name" required />
|
<input id="name-input" type="text" v-model="formData.name" required />
|
||||||
|
<p class="form-input-hint">
|
||||||
|
The full name of the student, which should be unique among all students in the class.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="desk-input">Desk Number</label>
|
<label for="desk-input">Desk Number</label>
|
||||||
<input id="desk-input" type="number" v-model="formData.deskNumber" />
|
<input id="desk-input" type="number" v-model="formData.deskNumber" />
|
||||||
<p style="font-style: italic; font-size: smaller;">
|
<p class="form-input-hint">
|
||||||
Set desk number to zero to remove a student's desk assignment.
|
Set desk number to zero to remove a student's desk assignment.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="removed-checkbox" style="display: inline">Removed</label>
|
<label for="removed-checkbox" style="display: inline">Removed</label>
|
||||||
<input id="removed-checkbox" type="checkbox" v-model="formData.removed" />
|
<input id="removed-checkbox" type="checkbox" v-model="formData.removed" />
|
||||||
|
<p class="form-input-hint">
|
||||||
|
Check this if the student has been removed from the class.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="button-bar">
|
<div class="button-bar">
|
||||||
<button type="submit">Save</button>
|
<button type="submit">Save</button>
|
||||||
|
@ -151,19 +165,24 @@ async function doMoveClass() {
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Dialog for moving the student to another class. -->
|
<!-- Dialog for moving the student to another class. -->
|
||||||
<dialog id="move-class-dialog" ref="move-class-dialog">
|
<dialog id="move-class-dialog" ref="move-class-dialog" style="max-width: 500px;">
|
||||||
<p>
|
<p>
|
||||||
Select a class to move them to below:
|
Select a class to move {{ student?.name }} to:
|
||||||
</p>
|
</p>
|
||||||
<select v-model="moveClassDialogClassSelection">
|
<select v-model="moveClassDialogClassSelection">
|
||||||
<option v-for="c in moveClassDialogClassChoices" :key="c.id" v-text="'Class ' + c.number" :value="c"></option>
|
<option v-for="c in moveClassDialogClassChoices" :key="c.id" v-text="'Class ' + c.number" :value="c"></option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<p v-if="moveClassDialogClassSelection">
|
<p v-if="moveClassDialogClassSelection" class="form-input-hint">
|
||||||
Will move {{ student?.name }} from class {{ cls.number }} to class {{ moveClassDialogClassSelection.number }}.
|
Will move {{ student?.name }} from class {{ cls.number }} to class {{ moveClassDialogClassSelection.number }}.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div>
|
<p>
|
||||||
|
Note: All current entries for {{ student?.name }} in class {{ cls.number }} will be retained; no data is lost.
|
||||||
|
To add new entries for {{ student?.name }}, do so via their new class.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="button-bar">
|
||||||
<button type="button" id="move-class-dialog-submit-button" @click="doMoveClass()">Submit</button>
|
<button type="button" id="move-class-dialog-submit-button" @click="doMoveClass()">Submit</button>
|
||||||
<button type="button" id="move-class-dialog-cancel-button" @click="moveClassDialog?.close()">Cancel</button>
|
<button type="button" id="move-class-dialog-cancel-button" @click="moveClassDialog?.close()">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,3 +19,9 @@ label {
|
||||||
form > div + div {
|
form > div + div {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-input-hint {
|
||||||
|
font-size: smaller;
|
||||||
|
font-style: italic;
|
||||||
|
margin-top: 0.25em;
|
||||||
|
}
|
||||||
|
|
|
@ -7,3 +7,12 @@
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
</template>
|
</template>
|
||||||
|
<style>
|
||||||
|
.alert-dialog-error {
|
||||||
|
border-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-dialog-warning {
|
||||||
|
border-color: orange;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue