Compare commits
No commits in common. "ac01b5c94ccb62c989b397639c7769fe873b9555" and "0a0bbe22c90ef80aedded36dc1fb01a1c5892f49" have entirely different histories.
ac01b5c94c
...
0a0bbe22c9
|
@ -1,15 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterLink, RouterView, useRoute, useRouter } from 'vue-router'
|
import { RouterLink, RouterView, useRouter } from 'vue-router'
|
||||||
import { useAuthStore } from './stores/auth'
|
import { useAuthStore } from './stores/auth'
|
||||||
import AlertDialog from './components/AlertDialog.vue'
|
import AlertDialog from './components/AlertDialog.vue'
|
||||||
import AnnouncementsBanner from './components/AnnouncementsBanner.vue'
|
import AnnouncementsBanner from './components/AnnouncementsBanner.vue'
|
||||||
import { computed } from 'vue'
|
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const onLoginPage = computed(() => route.path === '/login')
|
|
||||||
|
|
||||||
async function logOut() {
|
async function logOut() {
|
||||||
authStore.logOut()
|
authStore.logOut()
|
||||||
|
@ -20,19 +16,17 @@ async function logOut() {
|
||||||
<template>
|
<template>
|
||||||
<header>
|
<header>
|
||||||
<div>
|
<div>
|
||||||
<nav class="global-navbar" v-if="!onLoginPage">
|
<nav class="global-navbar">
|
||||||
<div>
|
<div>
|
||||||
<span style="font-weight: bold; font-size: large;">Teacher Tools</span>
|
<RouterLink to="/">Home</RouterLink>
|
||||||
<RouterLink class="link" to="/">Apps</RouterLink>
|
<RouterLink to="/my-account" v-if="authStore.state">My Account</RouterLink>
|
||||||
<RouterLink class="link" to="/my-account" v-if="authStore.state">My Account</RouterLink>
|
<RouterLink to="/admin-dashboard" v-if="authStore.admin">Admin</RouterLink>
|
||||||
<RouterLink class="link" to="/admin-dashboard" v-if="authStore.admin">Admin</RouterLink>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<RouterLink class="link" to="/login" v-if="!authStore.state">Login</RouterLink>
|
<RouterLink to="/login" v-if="!authStore.state">Login</RouterLink>
|
||||||
<span v-if="authStore.state" style="margin-right: 0.25em; font-style: italic; font-size: smaller;">
|
<span v-if="authStore.state" style="margin-right: 0.5em">
|
||||||
Logged in as <span v-text="authStore.state.username"
|
Logged in as <span v-text="authStore.state.username" style="font-weight: bold;"></span>
|
||||||
style="font-weight: bold; font-style: normal; font-size: medium;"></span>
|
|
||||||
</span>
|
</span>
|
||||||
<button type="button" @click="logOut" v-if="authStore.state">Log out</button>
|
<button type="button" @click="logOut" v-if="authStore.state">Log out</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,7 +52,7 @@ async function logOut() {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.global-navbar>div>*+* {
|
.global-navbar>div>a+a {
|
||||||
margin-left: 1em;
|
margin-left: 0.5em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,31 +2,16 @@
|
||||||
* 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, alertType: string = 'info'): Promise<void> {
|
export function showAlert(msg: string): 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', closeAlertDialog)
|
closeButton.addEventListener('click', () => dialog.close())
|
||||||
messageBox.innerText = msg
|
messageBox.innerText = msg
|
||||||
dialog.showModal()
|
dialog.showModal()
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
dialog.addEventListener('close', () => {
|
dialog.addEventListener('close', () => resolve())
|
||||||
closeButton.removeEventListener('click', closeAlertDialog)
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeAlertDialog() {
|
|
||||||
const dialog = document.getElementById('alert-dialog') as HTMLDialogElement
|
|
||||||
dialog.close()
|
|
||||||
}
|
|
||||||
|
|
|
@ -30,29 +30,12 @@ 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) {
|
||||||
if (value instanceof BadRequestError || value instanceof AuthenticationError) {
|
await showAlert(value.message)
|
||||||
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
|
||||||
|
|
|
@ -24,8 +24,6 @@ const router = useRouter()
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.class-item:hover {
|
.class-item:hover {
|
||||||
|
|
|
@ -78,9 +78,9 @@ async function resetStudentDesks() {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div v-if="cls">
|
<div v-if="cls">
|
||||||
<h1 class="align-center" style="margin-bottom: 0;">Class <span v-text="cls.number"></span></h1>
|
<h1>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>
|
<p>School Year: <span v-text="cls.schoolYear"></span></p>
|
||||||
<div class="button-bar align-center" style="margin-bottom: 1em;">
|
<div class="button-bar" style="margin-bottom: 1em;">
|
||||||
<button type="button" @click="router.push(`/classroom-compliance/classes/${cls.id}/edit-student`)">Add
|
<button type="button" @click="router.push(`/classroom-compliance/classes/${cls.id}/edit-student`)">Add
|
||||||
Student</button>
|
Student</button>
|
||||||
<button type="button" @click="router.push(`/classroom-compliance/classes/${cls.id}/import-students`)">Import
|
<button type="button" @click="router.push(`/classroom-compliance/classes/${cls.id}/import-students`)">Import
|
||||||
|
@ -89,17 +89,15 @@ async function resetStudentDesks() {
|
||||||
<button type="button" @click="deleteThisClass">Delete this Class</button>
|
<button type="button" @click="deleteThisClass">Delete this Class</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<EntriesTable :classId="cls.id" ref="entries-table" />
|
<h3>Notes</h3>
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 style="margin-bottom: 0.25em;">Notes</h3>
|
|
||||||
<form @submit.prevent="submitNote">
|
<form @submit.prevent="submitNote">
|
||||||
<textarea style="min-height: 50px; min-width: 300px;" maxlength="2000" minlength="1"
|
<textarea style="min-height: 50px; min-width: 300px;" maxlength="2000" minlength="1"
|
||||||
v-model="noteContent"></textarea>
|
v-model="noteContent"></textarea>
|
||||||
<button style="vertical-align: top; margin-left: 0.5em;" type="submit">Add Note</button>
|
<button style="vertical-align: top; margin-left: 0.5em;" type="submit">Add Note</button>
|
||||||
</form>
|
</form>
|
||||||
<ClassNoteItem v-for="note in notes" :key="note.id" :note="note" @noteDeleted="refreshNotes()" />
|
<ClassNoteItem v-for="note in notes" :key="note.id" :note="note" @noteDeleted="refreshNotes()" />
|
||||||
</div>
|
|
||||||
|
<EntriesTable :classId="cls.id" ref="entries-table" />
|
||||||
|
|
||||||
<!-- Confirmation dialog used for attempts at deleting this class. -->
|
<!-- Confirmation dialog used for attempts at deleting this class. -->
|
||||||
<ConfirmDialog ref="deleteClassDialog">
|
<ConfirmDialog ref="deleteClassDialog">
|
||||||
|
@ -111,3 +109,4 @@ async function resetStudentDesks() {
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<style scoped></style>
|
||||||
|
|
|
@ -11,7 +11,7 @@ const authStore = useAuthStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const apiClient = new ClassroomComplianceAPIClient(authStore)
|
const apiClient = new ClassroomComplianceAPIClient(authStore)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
apiClient.getClasses().handleErrorsWithAlert().then(result => {
|
apiClient.getClasses().handleErrorsWithAlert().then(result => {
|
||||||
if (result) classes.value = result
|
if (result) classes.value = result
|
||||||
})
|
})
|
||||||
|
@ -19,7 +19,7 @@ onMounted(() => {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="button-bar align-center">
|
<div class="button-bar">
|
||||||
<button type="button" @click="router.push('/classroom-compliance/edit-class')">Add Class</button>
|
<button type="button" @click="router.push('/classroom-compliance/edit-class')">Add Class</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -54,16 +54,13 @@ function resetForm() {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="centered-content">
|
<div>
|
||||||
<h2 class="align-center">
|
<h2>
|
||||||
<span v-if="cls" v-text="'Edit Class ' + cls.id"></span>
|
<span v-if="cls" v-text="'Edit Class ' + cls.id"></span>
|
||||||
<span v-if="!cls">Add New Class</span>
|
<span v-if="!cls">Add New Class</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<form @submit.prevent="submitForm" @reset.prevent="resetForm">
|
<form @submit.prevent="submitForm" @reset.prevent="resetForm">
|
||||||
<p>
|
|
||||||
Add a new class to your classroom compliance app.
|
|
||||||
</p>
|
|
||||||
<div>
|
<div>
|
||||||
<label for="number-input">Number</label>
|
<label for="number-input">Number</label>
|
||||||
<input id="number-input" type="number" v-model="formData.number" required />
|
<input id="number-input" type="number" v-model="formData.number" required />
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<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,
|
||||||
|
@ -35,35 +37,24 @@ 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 false
|
return
|
||||||
}
|
}
|
||||||
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(classId, studentId).handleErrorsWithAlert()
|
student.value = await apiClient.getStudent(classIdNumber, studentId).handleErrorsWithAlert()
|
||||||
if (!student.value) {
|
if (!student.value) {
|
||||||
await router.replace(`/classroom-compliance/classes/${classId}`)
|
await router.replace(`/classroom-compliance/classes/${classIdNumber}`)
|
||||||
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)
|
||||||
|
@ -120,42 +111,37 @@ 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)
|
||||||
.handleErrorsWithAlertNoBody()
|
if (result instanceof APIError) {
|
||||||
if (result) {
|
await showAlert(result.message)
|
||||||
|
} else {
|
||||||
await router.replace(`/classroom-compliance/classes/${newClassId}/students/${student.value.id}`)
|
await router.replace(`/classroom-compliance/classes/${newClassId}/students/${student.value.id}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div v-if="cls" class="centered-content">
|
<div v-if="cls">
|
||||||
<h2 class="align-center">
|
<h2>
|
||||||
<span v-if="student" v-text="'Edit ' + student.name"></span>
|
<span v-if="student" v-text="'Edit ' + student.name"></span>
|
||||||
<span v-if="!student">Add New Student</span>
|
<span v-if="!student">Add New Student</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p class="align-center">In class <span v-text="cls.number + ', ' + cls.schoolYear"></span></p>
|
<p>In class <span v-text="cls.number + ', ' + cls.schoolYear"></span></p>
|
||||||
|
|
||||||
<form @submit.prevent="submitForm" @reset.prevent="resetForm">
|
<form @submit.prevent="submitForm" @reset.prevent="resetForm">
|
||||||
<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 class="form-input-hint">
|
<p style="font-style: italic; font-size: smaller;">
|
||||||
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>
|
||||||
<input id="removed-checkbox" type="checkbox" v-model="formData.removed" />
|
|
||||||
<label for="removed-checkbox" style="display: inline">Removed</label>
|
<label for="removed-checkbox" style="display: inline">Removed</label>
|
||||||
<p class="form-input-hint">
|
<input id="removed-checkbox" type="checkbox" v-model="formData.removed" />
|
||||||
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>
|
||||||
|
@ -165,24 +151,19 @@ 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" style="max-width: 500px;">
|
<dialog id="move-class-dialog" ref="move-class-dialog">
|
||||||
<p>
|
<p>
|
||||||
Select a class to move {{ student?.name }} to:
|
Select a class to move them to below:
|
||||||
</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" class="form-input-hint">
|
<p v-if="moveClassDialogClassSelection">
|
||||||
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>
|
||||||
|
|
||||||
<p>
|
<div>
|
||||||
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>
|
||||||
|
|
|
@ -30,7 +30,6 @@ enum TableView {
|
||||||
const students: Ref<EntriesResponseStudent[]> = ref([])
|
const students: Ref<EntriesResponseStudent[]> = ref([])
|
||||||
const sortingChoice: Ref<string> = ref('name')
|
const sortingChoice: Ref<string> = ref('name')
|
||||||
const selectedView: Ref<TableView> = ref(TableView.FULL)
|
const selectedView: Ref<TableView> = ref(TableView.FULL)
|
||||||
const hideRemovedStudents: Ref<boolean> = ref(false)
|
|
||||||
|
|
||||||
const lastSaveState: Ref<string | null> = ref(null)
|
const lastSaveState: Ref<string | null> = ref(null)
|
||||||
const lastSaveStateTimestamp: Ref<number> = ref(0)
|
const lastSaveStateTimestamp: Ref<number> = ref(0)
|
||||||
|
@ -217,13 +216,6 @@ function getVisibleDates(): string[] {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVisibleStudents(): EntriesResponseStudent[] {
|
|
||||||
if (hideRemovedStudents.value === true) {
|
|
||||||
return students.value.filter(s => !s.removed && s.classId === props.classId)
|
|
||||||
}
|
|
||||||
return students.value
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVisibleStudentEntries(student: EntriesResponseStudent): Record<string, Entry | null> {
|
function getVisibleStudentEntries(student: EntriesResponseStudent): Record<string, Entry | null> {
|
||||||
if (selectedView.value === TableView.FULL) return student.entries
|
if (selectedView.value === TableView.FULL) return student.entries
|
||||||
if (selectedView.value === TableView.TODAY) {
|
if (selectedView.value === TableView.TODAY) {
|
||||||
|
@ -252,7 +244,7 @@ defineExpose({
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="button-bar" style="text-align: left;">
|
<div class="button-bar">
|
||||||
<button type="button" @click="showPreviousWeek" :disabled="selectedView === TableView.TODAY">Previous
|
<button type="button" @click="showPreviousWeek" :disabled="selectedView === TableView.TODAY">Previous
|
||||||
Week</button>
|
Week</button>
|
||||||
<button type="button" @click="showThisWeek" :disabled="selectedView === TableView.TODAY">This Week</button>
|
<button type="button" @click="showThisWeek" :disabled="selectedView === TableView.TODAY">This Week</button>
|
||||||
|
@ -275,12 +267,6 @@ defineExpose({
|
||||||
<option :value="TableView.WHITEBOARD">Whiteboard View</option>
|
<option :value="TableView.WHITEBOARD">Whiteboard View</option>
|
||||||
<option :value="TableView.TODAY">Today View</option>
|
<option :value="TableView.TODAY">Today View</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<span>
|
|
||||||
<input type="checkbox" id="show-removed-students-checkbox" v-model="hideRemovedStudents" />
|
|
||||||
<label for="show-removed-students-checkbox" style="display: inline; font-size: small;">Hide Removed
|
|
||||||
Students</label>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="selectedView === TableView.GRADING">
|
<p v-if="selectedView === TableView.GRADING">
|
||||||
|
@ -289,7 +275,7 @@ defineExpose({
|
||||||
|
|
||||||
<table class="entries-table" :class="{ 'entries-table-reduced-view': selectedView !== TableView.FULL }">
|
<table class="entries-table" :class="{ 'entries-table-reduced-view': selectedView !== TableView.FULL }">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="align-center">
|
<tr>
|
||||||
<th v-if="selectedView !== TableView.WHITEBOARD">#</th>
|
<th v-if="selectedView !== TableView.WHITEBOARD">#</th>
|
||||||
<th>Student</th>
|
<th>Student</th>
|
||||||
<th v-if="assignedDesks">Desk</th>
|
<th v-if="assignedDesks">Desk</th>
|
||||||
|
@ -299,13 +285,16 @@ defineExpose({
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(student, idx) in getVisibleStudents()" :key="student.id" style="height: 2em;">
|
<tr v-for="(student, idx) in students" :key="student.id" style="height: 2em;">
|
||||||
<td v-if="selectedView !== TableView.WHITEBOARD" style="text-align: right; padding-right: 0.5em;">{{ idx + 1
|
<td v-if="selectedView !== TableView.WHITEBOARD" style="text-align: right; padding-right: 0.5em;">{{ idx + 1
|
||||||
}}.</td>
|
}}.</td>
|
||||||
<StudentNameCell :student="student" :class-id="classId" />
|
<StudentNameCell :student="student" :class-id="classId" />
|
||||||
|
<!-- Desk Number: -->
|
||||||
<td v-if="assignedDesks" v-text="student.deskNumber"></td>
|
<td v-if="assignedDesks" v-text="student.deskNumber"></td>
|
||||||
|
<!-- A cell for each entry in the table's date range: -->
|
||||||
<EntryTableCell v-for="(entry, date) in getVisibleStudentEntries(student)" :key="date"
|
<EntryTableCell v-for="(entry, date) in getVisibleStudentEntries(student)" :key="date"
|
||||||
v-model="student.entries[date]" :date-str="date" :last-save-state-timestamp="lastSaveStateTimestamp" />
|
v-model="student.entries[date]" :date-str="date" :last-save-state-timestamp="lastSaveStateTimestamp" />
|
||||||
|
<!-- Score cell: -->
|
||||||
<StudentScoreCell :score="student.score" v-if="selectedView !== TableView.WHITEBOARD" />
|
<StudentScoreCell :score="student.score" v-if="selectedView !== TableView.WHITEBOARD" />
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -15,7 +15,7 @@ const sampleEntry: Ref<Entry | null> = ref({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="align-left centered-content">
|
<div class="guide-page">
|
||||||
<h2>Guide</h2>
|
<h2>Guide</h2>
|
||||||
<p>
|
<p>
|
||||||
This page provides a quick guide to using the <em>Classroom Compliance</em>
|
This page provides a quick guide to using the <em>Classroom Compliance</em>
|
||||||
|
@ -32,9 +32,7 @@ const sampleEntry: Ref<Entry | null> = ref({
|
||||||
school year to identify it.
|
school year to identify it.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
You can view all your classes with the <RouterLink class="link link-color" to="/classroom-compliance">View My
|
You can view all your classes with the <RouterLink to="/classroom-compliance">View My Classes</RouterLink>
|
||||||
Classes
|
|
||||||
</RouterLink>
|
|
||||||
link at the top of this page. Click on any of the classes you see listed,
|
link at the top of this page. Click on any of the classes you see listed,
|
||||||
and you'll go to that class' page.
|
and you'll go to that class' page.
|
||||||
</p>
|
</p>
|
||||||
|
@ -130,8 +128,7 @@ const sampleEntry: Ref<Entry | null> = ref({
|
||||||
<hr />
|
<hr />
|
||||||
<h3>Adding / Editing Students</h3>
|
<h3>Adding / Editing Students</h3>
|
||||||
<p>
|
<p>
|
||||||
As mentioned briefly in <a class="link link-color" href="#the-class-page">The Class Page</a>, you can add students
|
As mentioned briefly in <a href="#the-class-page">The Class Page</a>, you can add students one-by-one, or in bulk.
|
||||||
one-by-one, or in bulk.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
When adding a student individually, or editing an existing student, here's what you need to know:
|
When adding a student individually, or editing an existing student, here's what you need to know:
|
||||||
|
@ -152,3 +149,10 @@ const sampleEntry: Ref<Entry | null> = ref({
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.guide-page {
|
||||||
|
max-width: 50ch;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -32,8 +32,8 @@ async function doImport() {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="centered-content">
|
<div>
|
||||||
<h1 class="align-center">Import Students</h1>
|
<h1>Import Students</h1>
|
||||||
<p>
|
<p>
|
||||||
Import a large number of students to a class at once by pasting their names in the text box below, with one
|
Import a large number of students to a class at once by pasting their names in the text box below, with one
|
||||||
student per line.
|
student per line.
|
||||||
|
|
|
@ -21,18 +21,16 @@ async function downloadExport() {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<main class="app-header">
|
<main>
|
||||||
<h1>Classroom Compliance</h1>
|
<h1>Classroom Compliance</h1>
|
||||||
|
|
||||||
<div class="button-bar">
|
<div class="button-bar">
|
||||||
<RouterLink class="link" to="/classroom-compliance">View My Classes</RouterLink>
|
<RouterLink to="/classroom-compliance">View My Classes</RouterLink>
|
||||||
<RouterLink class="link" to="/classroom-compliance/guide">Guide</RouterLink>
|
<RouterLink to="/classroom-compliance/guide">Guide</RouterLink>
|
||||||
<a class="link" @click.prevent="downloadExport()" href="#">Export All Data</a>
|
<a @click.prevent="downloadExport()" href="#">Export All Data</a>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div class="align-left">
|
|
||||||
<RouterView :key="$route.fullPath" />
|
<RouterView :key="$route.fullPath" />
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { ClassroomComplianceAPIClient, type Entry, type Student } from '@/api/classroom_compliance';
|
|
||||||
import { useAuthStore } from '@/stores/auth';
|
|
||||||
import { onMounted, ref, type Ref } from 'vue';
|
|
||||||
import StudentEntryItem from './StudentEntryItem.vue';
|
|
||||||
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
student: Student
|
|
||||||
}>()
|
|
||||||
const authStore = useAuthStore()
|
|
||||||
|
|
||||||
const entries: Ref<Entry[]> = ref([])
|
|
||||||
|
|
||||||
const apiClient = new ClassroomComplianceAPIClient(authStore)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
apiClient.getStudentEntries(props.student.classId, props.student.id).handleErrorsWithAlert()
|
|
||||||
.then(result => {
|
|
||||||
if (result !== null) {
|
|
||||||
entries.value = result
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h3 class="align-center">Entries</h3>
|
|
||||||
<div>
|
|
||||||
<StudentEntryItem v-for="entry in entries" :key="entry.id" :entry="entry" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
|
@ -1,19 +1,13 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { EMOJI_ABSENT, EMOJI_BEHAVIOR_GOOD, EMOJI_BEHAVIOR_MEDIOCRE, EMOJI_BEHAVIOR_POOR, EMOJI_PHONE_COMPLIANT, EMOJI_PHONE_NONCOMPLIANT, EMOJI_PRESENT, type Entry } from '@/api/classroom_compliance';
|
import { EMOJI_ABSENT, EMOJI_BEHAVIOR_GOOD, EMOJI_BEHAVIOR_MEDIOCRE, EMOJI_BEHAVIOR_POOR, EMOJI_PHONE_COMPLIANT, EMOJI_PHONE_NONCOMPLIANT, EMOJI_PRESENT, type Entry } from '@/api/classroom_compliance';
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
entry: Entry
|
entry: Entry
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
function getFormattedDate() {
|
|
||||||
const d = new Date(props.entry.date)
|
|
||||||
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
||||||
return days[d.getDay()] + ', ' + d.toLocaleDateString()
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="student-entry-item">
|
<div class="student-entry-item">
|
||||||
<h6>{{ getFormattedDate() }}</h6>
|
<h6>{{ entry.date }}</h6>
|
||||||
<div class="icons-container">
|
<div class="icons-container">
|
||||||
<span v-if="entry.absent">{{ EMOJI_ABSENT }}</span>
|
<span v-if="entry.absent">{{ EMOJI_ABSENT }}</span>
|
||||||
<span v-if="!entry.absent">{{ EMOJI_PRESENT }}</span>
|
<span v-if="!entry.absent">{{ EMOJI_PRESENT }}</span>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import ConfirmDialog from '@/components/ConfirmDialog.vue'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { onMounted, ref, useTemplateRef, type Ref } from 'vue'
|
import { onMounted, ref, useTemplateRef, type Ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import StudentEntriesList from './StudentEntriesList.vue'
|
import StudentEntryItem from '@/apps/classroom_compliance/StudentEntryItem.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
classId: string
|
classId: string
|
||||||
|
@ -53,54 +53,43 @@ async function deleteThisStudent() {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div v-if="student" class="centered-content">
|
<div v-if="student">
|
||||||
<h1 class="align-center" style="margin-bottom: 0;">{{ student.name }}</h1>
|
<h2 v-text="student.name"></h2>
|
||||||
<p class="align-center" style="margin-top: 0;">
|
<p>
|
||||||
in
|
From
|
||||||
<RouterLink class="link link-color" :to="'/classroom-compliance/classes/' + classId">
|
<RouterLink :to="'/classroom-compliance/classes/' + classId">
|
||||||
<span v-text="'class ' + cls?.number"></span>
|
<span v-text="'class ' + cls?.number + ', ' + cls?.schoolYear"></span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</p>
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Internal ID: <span v-text="student.id"></span></li>
|
||||||
|
<li>Removed: <span v-text="student.removed"></span></li>
|
||||||
|
<li>Desk number: <span v-text="student.deskNumber"></span></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<div class="button-bar align-center">
|
<div class="button-bar">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@click="router.push(`/classroom-compliance/classes/${student.classId}/edit-student?studentId=${student.id}`)">Edit</button>
|
@click="router.push(`/classroom-compliance/classes/${student.classId}/edit-student?studentId=${student.id}`)">Edit</button>
|
||||||
<button type="button" @click="deleteThisStudent">Delete</button>
|
<button type="button" @click="deleteThisStudent">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="student-properties-table">
|
<div v-if="statistics">
|
||||||
<tbody>
|
<h3>Statistics</h3>
|
||||||
<tr>
|
<ul>
|
||||||
<th>Desk Number</th>
|
<li>Attendance Rate: {{ (statistics.attendanceRate * 100).toFixed(1) }}%</li>
|
||||||
<td>
|
<li>Phone Compliance Rate: {{ (statistics.phoneComplianceRate * 100).toFixed(1) }}%</li>
|
||||||
{{ student.deskNumber }}
|
<li>Behavior Score: {{ (statistics.behaviorScore * 100).toFixed(1) }}%</li>
|
||||||
<span v-if="student.deskNumber === 0" style="font-style: italic; font-size: small;">*No assigned desk</span>
|
<li>From a total of {{ statistics.entryCount }} entries</li>
|
||||||
</td>
|
</ul>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
|
||||||
<th>Removed</th>
|
|
||||||
<td>{{ student.removed ? 'True' : 'False' }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="statistics">
|
|
||||||
<th>Attendance Rate</th>
|
|
||||||
<td>{{ (statistics.attendanceRate * 100).toFixed(1) }}%</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="statistics">
|
|
||||||
<th>Phone Compliance Rate</th>
|
|
||||||
<td>{{ (statistics.phoneComplianceRate * 100).toFixed(1) }}%</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="statistics">
|
|
||||||
<th>Behavior Score</th>
|
|
||||||
<td>{{ (statistics.behaviorScore * 100).toFixed(1) }}%</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="statistics">
|
|
||||||
<th>Total Entries</th>
|
|
||||||
<td>{{ statistics.entryCount }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<StudentEntriesList :student="student" />
|
<h3>Entries</h3>
|
||||||
|
<p>
|
||||||
|
Below is a record of all entries saved for <span>{{ student.name }}</span>.
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<StudentEntryItem v-for="entry in entries" :key="entry.id" :entry="entry" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<ConfirmDialog ref="deleteConfirmDialog">
|
<ConfirmDialog ref="deleteConfirmDialog">
|
||||||
<p>
|
<p>
|
||||||
|
@ -111,19 +100,3 @@ async function deleteThisStudent() {
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
|
||||||
.student-properties-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.student-properties-table :is(th, td) {
|
|
||||||
border: 1px solid gray;
|
|
||||||
padding: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.student-properties-table td {
|
|
||||||
text-align: right;
|
|
||||||
font-family: 'SourceCodePro', monospace;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -5,9 +5,9 @@ defineProps<{
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<td style="text-align: right; padding-right: 0.25em;">
|
<td style="text-align: right; padding-right: 0.25em;">
|
||||||
<span v-if="score !== null" class="text-mono">
|
<span v-if="score !== null" style="font-family: monospace; font-size: large;">
|
||||||
{{ (score * 100).toFixed(1) }}%
|
{{ (score * 100).toFixed(1) }}%
|
||||||
</span>
|
</span>
|
||||||
<span v-if="score === null" style="font-style: italic; color: gray;">No score</span>
|
<span v-if="score === null" style="font-size: small; font-style: italic;">No score</span>
|
||||||
</td>
|
</td>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,28 +1,6 @@
|
||||||
@font-face {
|
|
||||||
font-family: 'Open Sans';
|
|
||||||
src: url('@/assets/fonts/OpenSans.ttf');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Open Sans';
|
|
||||||
src: url('@/assets/fonts/OpenSans-Italic.ttf');
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'SourceCodePro';
|
|
||||||
src: url('@/assets/fonts/SourceCodePro.ttf');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'SourceCodePro';
|
|
||||||
src: url('@/assets/fonts/SourceCodePro-Italic.ttf');
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
font-family: 'Open Sans', sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-bar {
|
.button-bar {
|
||||||
|
@ -34,80 +12,10 @@
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.align-left {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.centered-content {
|
|
||||||
max-width: 50ch;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: 'SourceCodePro', monospace;
|
|
||||||
color: lime;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-mono {
|
|
||||||
font-family: 'SourceCodePro', monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
label {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
|
||||||
font-family: 'SourceCodePro', monospace;
|
|
||||||
font-size: smaller;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
font-family: 'Open Sans', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
.link:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
color: lightgray;
|
|
||||||
}
|
|
||||||
.link-color {
|
|
||||||
color: rgb(165, 210, 253);
|
|
||||||
}
|
|
||||||
.link-color:hover {
|
|
||||||
color: rgb(95, 121, 190);
|
|
||||||
}
|
|
||||||
.link-ext {
|
|
||||||
color: rgb(158, 255, 134);
|
|
||||||
}
|
|
||||||
.link-ext:hover {
|
|
||||||
color: rgb(110, 179, 93);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-header {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.app-header > h1 {
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -7,12 +7,3 @@
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
</template>
|
</template>
|
||||||
<style>
|
|
||||||
.alert-dialog-error {
|
|
||||||
border-color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-dialog-warning {
|
|
||||||
border-color: orange;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
defineProps<{
|
|
||||||
name: string,
|
|
||||||
route: string
|
|
||||||
}>()
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div class="app-list-item">
|
|
||||||
<h3>
|
|
||||||
<RouterLink class="link" :to="route">{{ name }}</RouterLink>
|
|
||||||
</h3>
|
|
||||||
<p>
|
|
||||||
<slot></slot>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<style scoped>
|
|
||||||
.app-list-item {
|
|
||||||
padding: 1em;
|
|
||||||
background-color: rgb(49, 49, 49);
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-item>h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
font-size: x-large;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -36,10 +36,20 @@ defineExpose({
|
||||||
<form>
|
<form>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|
||||||
<div class="button-bar align-right">
|
<div class="confirm-dialog-buttons">
|
||||||
<button @click.prevent="onConfirmClicked">Confirm</button>
|
<button @click.prevent="onConfirmClicked">Confirm</button>
|
||||||
<button @click.prevent="onCancelClicked">Cancel</button>
|
<button @click.prevent="onCancelClicked">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
</template>
|
</template>
|
||||||
|
<style>
|
||||||
|
.confirm-dialog-buttons {
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-dialog-buttons>button {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,27 +1,25 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts"></script>
|
||||||
import AppListItem from '@/components/AppListItem.vue';
|
|
||||||
import { useAuthStore } from '@/stores/auth';
|
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="centered-content">
|
<main>
|
||||||
<h1 class="align-center">My Applications</h1>
|
<h1>Home Page</h1>
|
||||||
|
<p>
|
||||||
<div v-if="authStore.state">
|
Welcome to Teacher-Tools, a website with tools that help teachers to manage their classrooms.
|
||||||
<AppListItem name="Classroom Compliance" route="/classroom-compliance">
|
|
||||||
Track your students' phone usage and behavior, record notes, and automatically calculate weekly scores for each
|
|
||||||
student.
|
|
||||||
</AppListItem>
|
|
||||||
<p style="text-align: right; font-style: italic;">
|
|
||||||
... and more apps coming soon!
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
<hr>
|
||||||
|
<h2>Applications</h2>
|
||||||
<p v-if="!authStore.state" class="align-center">
|
<p>
|
||||||
Please <RouterLink to="/login">log in</RouterLink> to view your applications.
|
The following list of applications are available for you:
|
||||||
</p>
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<RouterLink to="/classroom-compliance">Classroom Compliance</RouterLink>
|
||||||
|
-
|
||||||
|
Track your students' phone usage and behavior patterns, and calculate weighted grades.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<em>... and more to come soon!</em>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -28,22 +28,23 @@ async function doLogin() {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<main class="centered-content">
|
<main>
|
||||||
<h1 class="align-center">Login</h1>
|
<h1>Login</h1>
|
||||||
<form>
|
<form>
|
||||||
<div class="login-input-row">
|
<div>
|
||||||
<input id="username-input" name="username" type="text" v-model="credentials.username" placeholder="Username" />
|
<label for="username-input">Username</label>
|
||||||
|
<input id="username-input" name="username" type="text" v-model="credentials.username" />
|
||||||
</div>
|
</div>
|
||||||
<div class="login-input-row">
|
<div>
|
||||||
<input id="password-input" name="password" type="password" v-model="credentials.password"
|
<label for="password-input">Password</label>
|
||||||
placeholder="Password" />
|
<input id="password-input" name="password" type="password" v-model="credentials.password" />
|
||||||
</div>
|
</div>
|
||||||
<div class="button-bar align-center">
|
<div class="button-bar">
|
||||||
<button type="button" @click="doLogin" style="font-size: large;">Login</button>
|
<button type="button" @click="doLogin">Login</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
If you forgot your password or would like to create an account, please contact <a class="link link-ext"
|
If you forgot your password or would like to create an account, please contact <a
|
||||||
href="https://andrewlalis.com/contact">Andrew Lalis</a> for assistance.
|
href="https://andrewlalis.com/contact">Andrew Lalis</a> for assistance.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
@ -53,20 +54,3 @@ async function doLogin() {
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
|
||||||
.login-input-row {
|
|
||||||
max-width: 30ch;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-input-row>input {
|
|
||||||
width: 100%;
|
|
||||||
font-size: medium;
|
|
||||||
padding: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-input-row+.login-input-row {
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -4,9 +4,9 @@ import { useAuthStore } from '@/stores/auth';
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<main v-if="authStore.state" class="centered-content">
|
<main v-if="authStore.state">
|
||||||
<h1 class="align-center">My Account</h1>
|
<h1>My Account</h1>
|
||||||
<table class="account-properties-table">
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Internal ID</th>
|
<th>Internal ID</th>
|
||||||
|
@ -33,32 +33,5 @@ const authStore = useAuthStore()
|
||||||
<p>
|
<p>
|
||||||
Please contact Andrew Lalis for help with account administration or to report any bugs you find!
|
Please contact Andrew Lalis for help with account administration or to report any bugs you find!
|
||||||
</p>
|
</p>
|
||||||
<p>
|
|
||||||
Suggestions for new features are also highly encouraged!
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Contact Andrew at <a class="link link-ext" href="https://www.andrewlalis.com/contact"
|
|
||||||
target="_blank">andrewlalis.com/contact</a>.
|
|
||||||
</p>
|
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
|
||||||
.account-properties-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-properties-table :is(th, td) {
|
|
||||||
border: 1px solid gray;
|
|
||||||
padding: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-properties-table th {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-properties-table td {
|
|
||||||
text-align: right;
|
|
||||||
font-family: 'SourceCodePro', monospace;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
Loading…
Reference in New Issue