Added fonts, and a lot of UI cleanup.

This commit is contained in:
Andrew Lalis 2025-02-20 13:31:56 -05:00
parent b69852817e
commit 117e7bf992
23 changed files with 291 additions and 104 deletions

View File

@ -18,15 +18,17 @@ async function logOut() {
<div> <div>
<nav class="global-navbar"> <nav class="global-navbar">
<div> <div>
<RouterLink to="/">Home</RouterLink> <span style="font-weight: bold; font-size: large;">Teacher Tools</span>
<RouterLink to="/">Apps</RouterLink>
<RouterLink to="/my-account" v-if="authStore.state">My Account</RouterLink> <RouterLink to="/my-account" v-if="authStore.state">My Account</RouterLink>
<RouterLink to="/admin-dashboard" v-if="authStore.admin">Admin</RouterLink> <RouterLink to="/admin-dashboard" v-if="authStore.admin">Admin</RouterLink>
</div> </div>
<div> <div>
<RouterLink 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.5em"> <span v-if="authStore.state" style="margin-right: 0.25em; font-style: italic; font-size: smaller;">
Logged in as <span v-text="authStore.state.username" style="font-weight: bold;"></span> Logged in as <span v-text="authStore.state.username"
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>
@ -52,7 +54,17 @@ async function logOut() {
display: inline-block; display: inline-block;
} }
.global-navbar>div>a+a { .global-navbar>div>*+* {
margin-left: 0.5em; margin-left: 1em;
}
.global-navbar a {
color: inherit;
text-decoration: none;
}
.global-navbar a:hover {
text-decoration: underline;
color: lightgray;
} }
</style> </style>

View File

@ -24,6 +24,8 @@ 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 {

View File

@ -78,9 +78,9 @@ async function resetStudentDesks() {
</script> </script>
<template> <template>
<div v-if="cls"> <div v-if="cls">
<h1>Class <span v-text="cls.number"></span></h1> <h1 class="align-center" style="margin-bottom: 0;">Class <span v-text="cls.number"></span></h1>
<p>School Year: <span v-text="cls.schoolYear"></span></p> <p class="align-center" style="margin-top: 0; margin-bottom: 2em;">For the {{ cls.schoolYear }} school year.</p>
<div class="button-bar" style="margin-bottom: 1em;"> <div class="button-bar align-center" 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,16 +89,18 @@ async function resetStudentDesks() {
<button type="button" @click="deleteThisClass">Delete this Class</button> <button type="button" @click="deleteThisClass">Delete this Class</button>
</div> </div>
<h3>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()" />
<EntriesTable :classId="cls.id" ref="entries-table" /> <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>
<!-- Confirmation dialog used for attempts at deleting this class. --> <!-- Confirmation dialog used for attempts at deleting this class. -->
<ConfirmDialog ref="deleteClassDialog"> <ConfirmDialog ref="deleteClassDialog">
<p> <p>
@ -109,4 +111,3 @@ async function resetStudentDesks() {
</ConfirmDialog> </ConfirmDialog>
</div> </div>
</template> </template>
<style scoped></style>

View File

@ -11,7 +11,7 @@ const authStore = useAuthStore()
const router = useRouter() const router = useRouter()
const apiClient = new ClassroomComplianceAPIClient(authStore) const apiClient = new ClassroomComplianceAPIClient(authStore)
onMounted(async () => { onMounted(() => {
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(async () => {
</script> </script>
<template> <template>
<div> <div>
<div class="button-bar"> <div class="button-bar align-center">
<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>

View File

@ -54,13 +54,16 @@ function resetForm() {
} }
</script> </script>
<template> <template>
<div> <div class="centered-content">
<h2> <h2 class="align-center">
<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 />

View File

@ -127,13 +127,13 @@ async function doMoveClass() {
} }
</script> </script>
<template> <template>
<div v-if="cls"> <div v-if="cls" class="centered-content">
<h2> <h2 class="align-center">
<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>In class <span v-text="cls.number + ', ' + cls.schoolYear"></span></p> <p class="align-center">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>
@ -151,8 +151,8 @@ async function doMoveClass() {
</p> </p>
</div> </div>
<div> <div>
<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" />
<label for="removed-checkbox" style="display: inline">Removed</label>
<p class="form-input-hint"> <p class="form-input-hint">
Check this if the student has been removed from the class. Check this if the student has been removed from the class.
</p> </p>

View File

@ -252,7 +252,7 @@ defineExpose({
</script> </script>
<template> <template>
<div> <div>
<div class="button-bar"> <div class="button-bar" style="text-align: left;">
<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>
@ -289,7 +289,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> <tr class="align-center">
<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>

View File

@ -15,7 +15,7 @@ const sampleEntry: Ref<Entry | null> = ref({
</script> </script>
<template> <template>
<div class="guide-page"> <div class="align-left centered-content">
<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>
@ -149,10 +149,3 @@ 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>

View File

@ -32,8 +32,8 @@ async function doImport() {
} }
</script> </script>
<template> <template>
<div> <div class="centered-content">
<h1>Import Students</h1> <h1 class="align-center">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.

View File

@ -21,7 +21,7 @@ async function downloadExport() {
} }
</script> </script>
<template> <template>
<main> <main class="app-header">
<h1>Classroom Compliance</h1> <h1>Classroom Compliance</h1>
<div class="button-bar"> <div class="button-bar">
@ -31,6 +31,8 @@ async function downloadExport() {
</div> </div>
<hr /> <hr />
<RouterView :key="$route.fullPath" /> <div class="align-left">
<RouterView :key="$route.fullPath" />
</div>
</main> </main>
</template> </template>

View File

@ -0,0 +1,33 @@
<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>

View File

@ -1,13 +1,19 @@
<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';
defineProps<{ const props = 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>{{ entry.date }}</h6> <h6>{{ getFormattedDate() }}</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>

View File

@ -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 StudentEntryItem from '@/apps/classroom_compliance/StudentEntryItem.vue' import StudentEntriesList from './StudentEntriesList.vue'
const props = defineProps<{ const props = defineProps<{
classId: string classId: string
@ -53,43 +53,51 @@ async function deleteThisStudent() {
} }
</script> </script>
<template> <template>
<div v-if="student"> <div v-if="student" class="centered-content">
<h2 v-text="student.name"></h2> <h2 class="align-center" v-text="student.name"></h2>
<p> <p class="align-center">
From From
<RouterLink :to="'/classroom-compliance/classes/' + classId"> <RouterLink :to="'/classroom-compliance/classes/' + classId">
<span v-text="'class ' + cls?.number + ', ' + cls?.schoolYear"></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"> <div class="button-bar align-center">
<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>
<div v-if="statistics"> <table class="student-properties-table">
<h3>Statistics</h3> <tbody>
<ul> <tr>
<li>Attendance Rate: {{ (statistics.attendanceRate * 100).toFixed(1) }}%</li> <th>Desk Number</th>
<li>Phone Compliance Rate: {{ (statistics.phoneComplianceRate * 100).toFixed(1) }}%</li> <td>{{ student.deskNumber }}</td>
<li>Behavior Score: {{ (statistics.behaviorScore * 100).toFixed(1) }}%</li> </tr>
<li>From a total of {{ statistics.entryCount }} entries</li> <tr>
</ul> <th>Removed</th>
</div> <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>
<h3>Entries</h3> <StudentEntriesList :student="student" />
<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>
@ -100,3 +108,18 @@ 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;
}
</style>

View File

@ -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" style="font-family: monospace; font-size: large;"> <span v-if="score !== null" class="text-mono">
{{ (score * 100).toFixed(1) }}% {{ (score * 100).toFixed(1) }}%
</span> </span>
<span v-if="score === null" style="font-size: small; font-style: italic;">No score</span> <span v-if="score === null" style="font-style: italic; color: gray;">No score</span>
</td> </td>
</template> </template>

View File

@ -1,6 +1,28 @@
@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: sans-serif; font-family: 'Open Sans', sans-serif;
} }
.button-bar { .button-bar {
@ -12,10 +34,46 @@
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;
}
form > div + div { form > div + div {
margin-top: 0.5em; margin-top: 0.5em;
} }
@ -25,3 +83,10 @@ form > div + div {
font-style: italic; font-style: italic;
margin-top: 0.25em; 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.

View File

@ -0,0 +1,39 @@
<script setup lang="ts">
defineProps<{
name: string,
route: string
}>()
</script>
<template>
<div class="app-list-item">
<h3>
<RouterLink :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;
}
.app-list-item>h3>a {
text-decoration: none;
color: inherit;
}
.app-list-item>h3>a:hover {
text-decoration: underline;
color: lightgray;
}
</style>

View File

@ -36,20 +36,10 @@ defineExpose({
<form> <form>
<slot></slot> <slot></slot>
<div class="confirm-dialog-buttons"> <div class="button-bar align-right">
<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>

View File

@ -1,25 +1,18 @@
<script setup lang="ts"></script> <script setup lang="ts">
import AppListItem from '@/components/AppListItem.vue';
</script>
<template> <template>
<main> <main class="centered-content">
<h1>Home Page</h1> <h1 class="align-center">My Applications</h1>
<p>
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>
<hr>
<h2>Applications</h2>
<p>
The following list of applications are available for you:
</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>

View File

@ -4,9 +4,9 @@ import { useAuthStore } from '@/stores/auth';
const authStore = useAuthStore() const authStore = useAuthStore()
</script> </script>
<template> <template>
<main v-if="authStore.state"> <main v-if="authStore.state" class="centered-content">
<h1>My Account</h1> <h1 class="align-center">My Account</h1>
<table> <table class="account-properties-table">
<tbody> <tbody>
<tr> <tr>
<th>Internal ID</th> <th>Internal ID</th>
@ -33,5 +33,30 @@ 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 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;
}
</style>