Compare commits
2 Commits
49a167fb68
...
490806aaee
Author | SHA1 | Date |
---|---|---|
|
490806aaee | |
|
39e0c9ee6e |
|
@ -46,10 +46,38 @@ void getClasses(ref HttpRequestContext ctx) {
|
||||||
Connection conn = getDb();
|
Connection conn = getDb();
|
||||||
scope(exit) conn.close();
|
scope(exit) conn.close();
|
||||||
User user = getUserOrThrow(ctx, conn);
|
User user = getUserOrThrow(ctx, conn);
|
||||||
auto classes = findAll(
|
const query = "
|
||||||
|
SELECT
|
||||||
|
c.id, c.number, c.school_year,
|
||||||
|
COUNT(DISTINCT s.id) AS student_count,
|
||||||
|
COUNT(DISTINCT e.id) AS entry_count,
|
||||||
|
MAX(e.date) AS last_entry_date
|
||||||
|
FROM classroom_compliance_class c
|
||||||
|
LEFT JOIN classroom_compliance_student s ON c.id = s.class_id
|
||||||
|
LEFT JOIN classroom_compliance_entry e ON c.id = e.class_id
|
||||||
|
WHERE c.user_id = ?
|
||||||
|
GROUP BY c.id
|
||||||
|
ORDER BY school_year DESC, number ASC
|
||||||
|
";
|
||||||
|
struct ClassResponse {
|
||||||
|
ulong id;
|
||||||
|
ushort number;
|
||||||
|
string schoolYear;
|
||||||
|
uint studentCount;
|
||||||
|
uint entryCount;
|
||||||
|
string lastEntryDate;
|
||||||
|
}
|
||||||
|
ClassResponse[] classes = findAll(
|
||||||
conn,
|
conn,
|
||||||
"SELECT * FROM classroom_compliance_class WHERE user_id = ? ORDER BY school_year DESC, number ASC",
|
query,
|
||||||
&ClassroomComplianceClass.parse,
|
r => ClassResponse(
|
||||||
|
r.getUlong(1),
|
||||||
|
r.getUshort(2),
|
||||||
|
r.getString(3),
|
||||||
|
r.getUint(4),
|
||||||
|
r.getUint(5),
|
||||||
|
r.getDate(6).toISOExtString()
|
||||||
|
),
|
||||||
user.id
|
user.id
|
||||||
);
|
);
|
||||||
writeJsonBody(ctx, classes);
|
writeJsonBody(ctx, classes);
|
||||||
|
|
|
@ -16,6 +16,15 @@ export interface Class {
|
||||||
schoolYear: string
|
schoolYear: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ClassesResponseClass {
|
||||||
|
id: number
|
||||||
|
number: number
|
||||||
|
schoolYear: string
|
||||||
|
studentCount: number
|
||||||
|
entryCount: number
|
||||||
|
lastEntryDate: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface Student {
|
export interface Student {
|
||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
|
@ -100,7 +109,7 @@ export class ClassroomComplianceAPIClient extends APIClient {
|
||||||
return super.post('/classes', { number: number, schoolYear: schoolYear })
|
return super.post('/classes', { number: number, schoolYear: schoolYear })
|
||||||
}
|
}
|
||||||
|
|
||||||
getClasses(): APIResponse<Class[]> {
|
getClasses(): APIResponse<ClassesResponseClass[]> {
|
||||||
return super.get('/classes')
|
return super.get('/classes')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type Class } from '@/api/classroom_compliance'
|
import { type ClassesResponseClass } from '@/api/classroom_compliance'
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
cls: Class
|
cls: ClassesResponseClass
|
||||||
}>()
|
}>()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="class-item" @click="router.push(`/classroom-compliance/classes/${cls.id}`)">
|
<div class="class-item" @click="router.push(`/classroom-compliance/classes/${cls.id}`)">
|
||||||
<h3>Class <span v-text="cls.number"></span></h3>
|
<h3>Class <span v-text="cls.number"></span></h3>
|
||||||
<p v-text="cls.schoolYear"></p>
|
<p>
|
||||||
|
{{ cls.studentCount }} students,
|
||||||
|
{{ cls.entryCount }} entries.
|
||||||
|
<span v-if="cls.entryCount > 0">Last entry from {{ cls.lastEntryDate }}.</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ClassroomComplianceAPIClient, type Class } from '@/api/classroom_compliance'
|
import { ClassroomComplianceAPIClient, type ClassesResponseClass } from '@/api/classroom_compliance'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { type Ref, ref, onMounted } from 'vue'
|
import { type Ref, ref, onMounted } from 'vue'
|
||||||
import ClassItem from '@/apps/classroom_compliance/ClassItem.vue'
|
import ClassItem from '@/apps/classroom_compliance/ClassItem.vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const classes: Ref<Class[]> = ref([])
|
const classes: Ref<ClassesResponseClass[]> = ref([])
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
|
@ -8,10 +8,10 @@ import {
|
||||||
} from '@/api/classroom_compliance'
|
} from '@/api/classroom_compliance'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { computed, onMounted, ref, watch, type Ref } from 'vue'
|
import { computed, onMounted, ref, watch, type Ref } from 'vue'
|
||||||
import EntryTableCell from '@/apps/classroom_compliance/EntryTableCell.vue'
|
import EntryTableCell from '@/apps/classroom_compliance/entries_table/EntryTableCell.vue'
|
||||||
import { RouterLink } from 'vue-router'
|
import StudentScoreCell from '@/apps/classroom_compliance/entries_table/StudentScoreCell.vue'
|
||||||
import StudentScoreCell from '@/apps/classroom_compliance/StudentScoreCell.vue'
|
import DateHeaderCell from '@/apps/classroom_compliance/entries_table/DateHeaderCell.vue'
|
||||||
import DateHeaderCell from '@/apps/classroom_compliance/DateHeaderCell.vue'
|
import StudentNameCell from '@/apps/classroom_compliance/entries_table/StudentNameCell.vue'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -232,13 +232,7 @@ function addAllEntriesForDate(dateStr: string) {
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="student in students" :key="student.id">
|
<tr v-for="student in students" :key="student.id">
|
||||||
<!-- Student's name: -->
|
<StudentNameCell :student="student" :class-id="classId" />
|
||||||
<td :class="{ 'student-removed': student.removed }">
|
|
||||||
<RouterLink :to="'/classroom-compliance/classes/' + classId + '/students/' + student.id"
|
|
||||||
class="student-link">
|
|
||||||
<span v-text="student.name"></span>
|
|
||||||
</RouterLink>
|
|
||||||
</td>
|
|
||||||
<!-- Desk Number: -->
|
<!-- 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: -->
|
<!-- A cell for each entry in the table's date range: -->
|
||||||
|
@ -269,13 +263,4 @@ function addAllEntriesForDate(dateStr: string) {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
}
|
}
|
||||||
|
|
||||||
.student-link {
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.student-removed {
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { EntriesResponseStudent } from '@/api/classroom_compliance';
|
||||||
|
defineProps<{
|
||||||
|
student: EntriesResponseStudent,
|
||||||
|
classId: number
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<td :class="{ 'student-removed': student.removed }">
|
||||||
|
<RouterLink :to="'/classroom-compliance/classes/' + classId + '/students/' + student.id" class="student-link">
|
||||||
|
<span v-text="student.name"></span>
|
||||||
|
</RouterLink>
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.student-link {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-removed {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue