Training-Report #27
@@ -87,7 +87,7 @@ export async function insertCourseEvent(event: CourseEventDetails): Promise<numb
|
||||
}
|
||||
|
||||
export async function getCourseEvents(): Promise<CourseEventSummary[]> {
|
||||
const sql = "SELECT E.id, E.course_id, E.event_date, E.created_by, C.name FROM course_events as E LEFT JOIN courses AS C ON E.course_id = C.id;";
|
||||
const sql = "SELECT E.id AS event_id, E.course_id, E.event_date AS date, E.created_by, C.name AS course_name FROM course_events as E LEFT JOIN courses AS C ON E.course_id = C.id;";
|
||||
let events: CourseEventSummary[] = await pool.query(sql);
|
||||
console.log(events);
|
||||
return events;
|
||||
|
||||
20
shared/schemas/trainingReportSchema.ts
Normal file
20
shared/schemas/trainingReportSchema.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const trainingReportSchema = z.object({
|
||||
id: z.number().int().positive().optional(),
|
||||
course_id: z.number().int(),
|
||||
event_date: z
|
||||
.string()
|
||||
.refine(
|
||||
(val) => !isNaN(Date.parse(val)),
|
||||
"event_date must be a valid ISO date string"
|
||||
),
|
||||
remarks: z.string().nullable().optional(),
|
||||
})
|
||||
|
||||
export const courseEventAttendeeSchema = z.object({
|
||||
attendee_id: z.number().int().positive(),
|
||||
passed: z.boolean(),
|
||||
remarks: z.string(),
|
||||
attendee_role_id: z.number().int().positive()
|
||||
})
|
||||
15
ui/src/api/trainingReport.ts
Normal file
15
ui/src/api/trainingReport.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { CourseEventDetails, CourseEventSummary } from '@shared/types/course'
|
||||
|
||||
//@ts-ignore
|
||||
const addr = import.meta.env.VITE_APIHOST;
|
||||
|
||||
export async function getTrainingReports(): Promise<CourseEventSummary[]> {
|
||||
const res = await fetch(`${addr}/courseEvent`);
|
||||
|
||||
if (res.ok) {
|
||||
return await res.json() as Promise<CourseEventSummary[]>;
|
||||
} else {
|
||||
console.error("Something went wrong");
|
||||
throw new Error("Failed to load training reports");
|
||||
}
|
||||
}
|
||||
59
ui/src/pages/TrainingReport.vue
Normal file
59
ui/src/pages/TrainingReport.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { getTrainingReports } from '@/api/trainingReport';
|
||||
import { trainingReportSchema, courseEventAttendeeSchema } from '@shared/schemas/trainingReportSchema'
|
||||
import { CourseEventDetails, CourseEventSummary } from '@shared/types/course';
|
||||
import { toTypedSchema } from '@vee-validate/zod';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCaption,
|
||||
TableCell,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
|
||||
const trainingReports = ref<CourseEventSummary[] | null>(null);
|
||||
const loaded = ref(false);
|
||||
|
||||
const focusedTrainingReport = ref<CourseEventDetails | null>(null);
|
||||
|
||||
onMounted(async () => {
|
||||
trainingReports.value = await getTrainingReports();
|
||||
loaded.value = true;
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="w-[100px]">
|
||||
Training
|
||||
</TableHead>
|
||||
<TableHead>Date</TableHead>
|
||||
<TableHead class="text-right">
|
||||
Posted By
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody v-if="loaded">
|
||||
<TableRow v-for="report in trainingReports" @click="console.log(report.event_id);">
|
||||
<TableCell class="font-medium">{{ report.course_name }}</TableCell>
|
||||
<TableCell>{{ report.date }}</TableCell>
|
||||
<TableCell class="text-right">{{ report.created_by === null ? "Unknown User" : report.created_by }}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
</div>
|
||||
<!-- view training report section -->
|
||||
<div>
|
||||
DETAILS
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -16,6 +16,7 @@ const router = createRouter({
|
||||
{ path: '/loa', component: () => import('@/pages/SubmitLOA.vue'), meta: { requiresAuth: true, memberOnly: true } },
|
||||
{ path: '/transfer', component: () => import('@/pages/Transfer.vue'), meta: { requiresAuth: true, memberOnly: true } },
|
||||
{ path: '/calendar', component: () => import('@/pages/Calendar.vue'), meta: { requiresAuth: true, memberOnly: true } },
|
||||
{ path: '/trainingReport', component: () => import('@/pages/TrainingReport.vue'), meta: { requiresAuth: true, memberOnly: true } },
|
||||
|
||||
// ADMIN / STAFF ROUTES
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user