Added guide, and cleaned up handling of students who've moved.

This commit is contained in:
Andrew Lalis 2025-02-19 13:11:57 -05:00
parent 84d4f0abbe
commit 0a0bbe22c9
7 changed files with 206 additions and 17 deletions
api/source/api_modules/classroom_compliance
app/src

View File

@ -44,6 +44,7 @@ struct EntriesTableEntry {
struct EntriesTableStudentResponse {
ulong id;
ulong classId;
string name;
ushort deskNumber;
bool removed;
@ -53,6 +54,7 @@ struct EntriesTableStudentResponse {
JSONValue toJsonObj() const {
JSONValue obj = JSONValue.emptyObject;
obj.object["id"] = JSONValue(id);
obj.object["classId"] = JSONValue(classId);
obj.object["name"] = JSONValue(name);
obj.object["deskNumber"] = JSONValue(deskNumber);
obj.object["removed"] = JSONValue(removed);
@ -107,6 +109,7 @@ void getEntries(ref HttpRequestContext ctx) {
);
EntriesTableStudentResponse[] studentObjects = students.map!(s => EntriesTableStudentResponse(
s.id,
s.classId,
s.name,
s.deskNumber,
s.removed,
@ -126,7 +129,8 @@ void getEntries(ref HttpRequestContext ctx) {
student.id,
student.name,
student.desk_number,
student.removed
student.removed,
student.class_id
FROM classroom_compliance_entry entry
LEFT JOIN classroom_compliance_student student
ON student.id = entry.student_id
@ -166,7 +170,7 @@ void getEntries(ref HttpRequestContext ctx) {
ClassroomComplianceStudent student = ClassroomComplianceStudent(
r.getUlong(8),
r.getString(9),
cls.id,
r.getUlong(12),
r.getUshort(10),
r.getBoolean(11)
);
@ -187,6 +191,7 @@ void getEntries(ref HttpRequestContext ctx) {
// Their data should still be shown, so add the student here.
studentObjects ~= EntriesTableStudentResponse(
student.id,
student.classId,
student.name,
student.deskNumber,
student.removed,

View File

@ -218,6 +218,7 @@ void getStudentOverview(ref HttpRequestContext ctx) {
void moveStudentToOtherClass(ref HttpRequestContext ctx) {
Connection conn = getDb();
conn.setAutoCommit(false);
User user = getUserOrThrow(ctx, conn);
auto student = getStudentOrThrow(ctx, conn, user);
struct Payload {
@ -238,11 +239,24 @@ void moveStudentToOtherClass(ref HttpRequestContext ctx) {
ctx.response.writeBodyString("Invalid class was selected.");
return;
}
// Check that the new class doesn't already have a student with the same name.
bool studentNameExistsInNewClass = recordExists(
conn,
"SELECT id FROM classroom_compliance_student WHERE class_id = ? AND name = ?",
payload.classId,
student.name
);
if (studentNameExistsInNewClass) {
ctx.response.status = HttpStatus.BAD_REQUEST;
ctx.response.writeBodyString("A student in the selected class has the same name as this one.");
return;
}
// All good, so update the student's class to the desired one, and reset their desk.
update(
conn,
"UPDATE classroom_compliance_student SET class_id = ?, desk_number = 0 WHERE id = ?",
payload.classId, student.id
);
conn.commit();
// We just return 200 OK, no response body.
}

View File

@ -57,6 +57,7 @@ export function getDefaultEntry(dateStr: string): Entry {
export interface EntriesResponseStudent {
id: number
classId: number
name: string
deskNumber: number
removed: boolean

View File

@ -0,0 +1,158 @@
<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 EntryTableCell from './entries_table/EntryTableCell.vue';
import { ref, type Ref } from 'vue';
const sampleEntry: Ref<Entry | null> = ref({
id: 1,
date: '2025-01-01',
createdAt: new Date().getTime(),
absent: false,
phoneCompliant: true,
behaviorRating: 3,
comment: ''
})
</script>
<template>
<div class="guide-page">
<h2>Guide</h2>
<p>
This page provides a quick guide to using the <em>Classroom Compliance</em>
application.
</p>
<hr />
<h3>Classes</h3>
<p>
In <em>Classroom Compliance</em>, all your students' data is linked to a
particular class. You'll start by clicking <strong>Add Class</strong> for
each class you teach. It doesn't really matter if your school uses a block
schedule or daily period schedule; each class just needs a number and
school year to identify it.
</p>
<p>
You can view all your classes with the <RouterLink to="/classroom-compliance">View My Classes</RouterLink>
link at the top of this page. Click on any of the classes you see listed,
and you'll go to that class' page.
</p>
<h4 id="the-class-page">The Class Page</h4>
<p>
This is the main page you'll be working with in <em>Classroom Compliance</em>.
Here, you'll find actions to add / remove / edit the list of students in
the class, add notes to the class, and most importantly, record students'
attendance, behavior, and phone compliance.
</p>
<p>
The following actions are available here:
</p>
<ul>
<li><strong>Add Student</strong> - Add a student to the class.</li>
<li><strong>Import Students</strong> - Import a list of students in bulk. This is useful when you've just created
a class, and you want to copy and paste a list of student names from a spreadsheet.</li>
<li><strong>Clear Assigned Desks</strong> - Resets every student's assigned desk. Useful if you've just removed
assigned seats or are reshuffling everyone.</li>
<li><strong>Delete this Class</strong> - Pretty self-explanatory. This will permanently delete this class. This
CANNOT be undone, so be careful to only delete the class when you're sure you won't need it anymore.</li>
</ul>
<h5>Notes</h5>
<p>
Sometimes, you might want to write a note to remind yourself of something for a class. Write a note in the text
box, then click <strong>Add Note</strong> to add the note.
</p>
<hr />
<h3>Entries</h3>
<p>
The main point of <em>Classroom Compliance</em> is to make it easy to keep track of each student's behavior,
attendance, and phone usage. On each class' page, you'll find a large table with a row for each student, and a
column for each day of the week. This is the <strong>Entries Table</strong>.
</p>
<p>
Simply click the "+" icon to add an entry for a student, or click on the day's "+" icon to add an entry for each
student that day. Then, just click the emoji to edit the entry. A sample entry is included below for you to play
around with.
</p>
<table>
<tbody>
<tr>
<EntryTableCell date-str="2025-01-01" :last-save-state-timestamp="0" v-model="sampleEntry"
style="min-width: 150px" />
</tr>
</tbody>
</table>
<ul>
<li>Attendance: {{ EMOJI_PRESENT }} for present, and {{ EMOJI_ABSENT }} for absent.</li>
<li>Phone Compliance: {{ EMOJI_PHONE_COMPLIANT }} for compliant, and {{ EMOJI_PHONE_NONCOMPLIANT }} for
non-compliant.</li>
<li>Behavior: {{ EMOJI_BEHAVIOR_GOOD }} for good behavior, {{ EMOJI_BEHAVIOR_MEDIOCRE }} for mediocre, and {{
EMOJI_BEHAVIOR_POOR }} for poor.</li>
<li>If you'd like to add a comment, click 💬 and enter your comment in the popup.</li>
<li>Click the 🗑 to remove the entry.</li>
<li><em>Note that if a student is absent, you can't add any phone or behavior information.</em></li>
</ul>
<p>
When editing, changed entries are highlighted, and you need to click <strong>Save</strong> or <strong>Discard
Edits</strong> to save or discard your changes, respectively.
</p>
<h4>Table Views</h4>
<p>
Above the Entries Table, there's a selection of <em>views</em> to choose from. By default, <strong>Full
View</strong> is selected.
</p>
<ul>
<li><strong>Full View</strong> - Shows a full week's entries for all students, including their scores.</li>
<li><strong>Grading View</strong> - Only show students and their scores. Useful when transferring grades to
another system.</li>
<li><strong>Whiteboard View</strong> - Only shows student names and their assigned desks. Useful for showing to
your class so they know their assigned seats.</li>
<li><strong>Today View</strong> - Same as the Full View, but only shows today, which can be useful when entering
data for a class.</li>
</ul>
<h4>Scores</h4>
<p>
Students' scores are calculated per week. The calculation is shown below:
</p>
<p>
<code>phone_score * 0.3 + behavior_score * 0.7</code> where
</p>
<ul>
<li><code>phone_score = days_compliant / days_present</code></li>
<li><code>behavior_score = (good_days * 1.0 + mediocre_days * 0.5) / days_present</code></li>
</ul>
<hr />
<h3>Adding / Editing Students</h3>
<p>
As mentioned briefly in <a href="#the-class-page">The Class Page</a>, you can add students one-by-one, or in bulk.
</p>
<p>
When adding a student individually, or editing an existing student, here's what you need to know:
</p>
<ul>
<li>Each student in a class needs a <strong>unique</strong> name. Unless you have twins with the same first name,
this shouldn't be an issue.</li>
<li>If a student has an assigned desk number, you can set it. Set the desk number to 0 if there's no assigned
seating for the student.</li>
<li>To preserve a record of students who have since been removed from a class, you can check the
<strong>Removed</strong> checkbox to indicate that the student is no longer in the class. Their old data will
still show up, but you can't add any new entries for them.
</li>
<li>
The <strong>Move to another class</strong> button will, as its name implies, move the student to another one of
your classes.
</li>
</ul>
</div>
</template>
<style scoped>
.guide-page {
max-width: 50ch;
margin-left: auto;
margin-right: auto;
}
</style>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { BASE_URL, EMOJI_ABSENT, EMOJI_BEHAVIOR_GOOD, EMOJI_BEHAVIOR_MEDIOCRE, EMOJI_BEHAVIOR_POOR, EMOJI_PHONE_COMPLIANT, EMOJI_PHONE_NONCOMPLIANT, EMOJI_PRESENT } from '@/api/classroom_compliance';
import { BASE_URL } from '@/api/classroom_compliance';
import { useAuthStore } from '@/stores/auth';
const authStore = useAuthStore()
@ -23,24 +23,14 @@ async function downloadExport() {
<template>
<main>
<h1>Classroom Compliance</h1>
<p>
With this application, you can track each student's compliance to various classroom policies,
like:
</p>
<ul>
<li>Attendance: {{ EMOJI_PRESENT }} - Present, {{ EMOJI_ABSENT }} - Absent</li>
<li>Phone Usage (or lack thereof): {{ EMOJI_PHONE_COMPLIANT }} - Compliant, {{ EMOJI_PHONE_NONCOMPLIANT }} -
Noncompliant</li>
<li>Behavior: {{ EMOJI_BEHAVIOR_GOOD }} - Good, {{ EMOJI_BEHAVIOR_MEDIOCRE }} - Mediocre, {{ EMOJI_BEHAVIOR_POOR
}} - Poor</li>
</ul>
<div class="button-bar">
<RouterLink to="/classroom-compliance">View My Classes</RouterLink>
<RouterLink to="/classroom-compliance/guide">Guide</RouterLink>
<a @click.prevent="downloadExport()" href="#">Export All Data</a>
</div>
<hr />
<RouterView />
<RouterView :key="$route.fullPath" />
</main>
</template>

View File

@ -2,14 +2,18 @@
import type { EntriesResponseStudent } from '@/api/classroom_compliance';
defineProps<{
student: EntriesResponseStudent,
classId: number
classId: number,
}>()
</script>
<template>
<td class="student-name-cell" :class="{ 'student-removed': student.removed }">
<RouterLink :to="'/classroom-compliance/classes/' + classId + '/students/' + student.id" class="student-link">
<RouterLink :to="'/classroom-compliance/classes/' + student.classId + '/students/' + student.id"
class="student-link">
{{ student.name }}
</RouterLink>
<div v-if="classId !== student.classId" class="other-class-text">
<RouterLink :to="'/classroom-compliance/classes/' + student.classId">In another class</RouterLink>
</div>
</td>
</template>
<style scoped>
@ -31,4 +35,17 @@ defineProps<{
text-decoration: line-through;
color: gray;
}
.other-class-text {
font-size: x-small;
}
.other-class-text>a {
color: gray;
text-decoration: none;
}
.other-class-text>a:hover {
text-decoration: underline;
}
</style>

View File

@ -35,6 +35,10 @@ export function createClassroomComplianceRoutes(): RouteRecordRaw {
component: () => import('@/apps/classroom_compliance/ImportStudentsView.vue'),
props: true,
},
{
path: 'guide',
component: () => import('@/apps/classroom_compliance/GuideView.vue'),
},
],
}
}