Update ui/src/components/trainingReport/trainingReportForm.vue #36
@@ -76,6 +76,7 @@ eventRouter.post('/', async (req: Request, res: Response) => {
|
|||||||
console.log();
|
console.log();
|
||||||
let data: CourseEventDetails = req.body;
|
let data: CourseEventDetails = req.body;
|
||||||
data.created_by = posterID;
|
data.created_by = posterID;
|
||||||
|
data.event_date = new Date(data.event_date);
|
||||||
const id = await insertCourseEvent(data);
|
const id = await insertCourseEvent(data);
|
||||||
res.status(201).json(id);
|
res.status(201).json(id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import pool from "../db"
|
import pool from "../db"
|
||||||
import { Course, CourseAttendee, CourseAttendeeRole, CourseEventDetails, CourseEventSummary, RawAttendeeRow } from "@app/shared/types/course"
|
import { Course, CourseAttendee, CourseAttendeeRole, CourseEventDetails, CourseEventSummary, RawAttendeeRow } from "@app/shared/types/course"
|
||||||
|
import { toDateTime } from "@app/shared/utils/time";
|
||||||
export async function getAllCourses(): Promise<Course[]> {
|
export async function getAllCourses(): Promise<Course[]> {
|
||||||
const sql = "SELECT * FROM courses WHERE deleted = false;"
|
const sql = "SELECT * FROM courses WHERE deleted = false ORDER BY name ASC;"
|
||||||
|
|
||||||
const res: Course[] = await pool.query(sql);
|
const res: Course[] = await pool.query(sql);
|
||||||
|
|
||||||
@@ -21,8 +21,8 @@ function buildAttendee(row: RawAttendeeRow): CourseAttendee {
|
|||||||
passed_qual: !!row.passed_qual,
|
passed_qual: !!row.passed_qual,
|
||||||
attendee_id: row.attendee_id,
|
attendee_id: row.attendee_id,
|
||||||
course_event_id: row.course_event_id,
|
course_event_id: row.course_event_id,
|
||||||
created_at: row.created_at,
|
created_at: new Date(row.created_at),
|
||||||
updated_at: row.updated_at,
|
updated_at: new Date(row.updated_at),
|
||||||
remarks: row.remarks,
|
remarks: row.remarks,
|
||||||
attendee_role_id: row.attendee_role_id,
|
attendee_role_id: row.attendee_role_id,
|
||||||
attendee_name: row.attendee_name,
|
attendee_name: row.attendee_name,
|
||||||
@@ -32,8 +32,8 @@ function buildAttendee(row: RawAttendeeRow): CourseAttendee {
|
|||||||
name: row.role_name,
|
name: row.role_name,
|
||||||
description: row.role_description,
|
description: row.role_description,
|
||||||
deleted: !!row.role_deleted,
|
deleted: !!row.role_deleted,
|
||||||
created_at: row.role_created_at,
|
created_at: new Date(row.role_created_at),
|
||||||
updated_at: row.role_updated_at,
|
updated_at: new Date(row.role_updated_at),
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
};
|
};
|
||||||
@@ -83,7 +83,7 @@ export async function insertCourseEvent(event: CourseEventDetails): Promise<numb
|
|||||||
const con = await pool.getConnection();
|
const con = await pool.getConnection();
|
||||||
try {
|
try {
|
||||||
await con.beginTransaction();
|
await con.beginTransaction();
|
||||||
const res = await con.query("INSERT INTO course_events (course_id, event_date, remarks, created_by) VALUES (?, ?, ?, ?);", [event.course_id, event.event_date, event.remarks, event.created_by]);
|
const res = await con.query("INSERT INTO course_events (course_id, event_date, remarks, created_by) VALUES (?, ?, ?, ?);", [event.course_id, toDateTime(event.event_date), event.remarks, event.created_by]);
|
||||||
var eventID: number = res.insertId;
|
var eventID: number = res.insertId;
|
||||||
|
|
||||||
for (const attendee of event.attendees) {
|
for (const attendee of event.attendees) {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ export interface Course {
|
|||||||
category: string;
|
category: string;
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
image_url?: string | null;
|
image_url?: string | null;
|
||||||
created_at: string;
|
created_at: Date;
|
||||||
updated_at: string;
|
updated_at: Date;
|
||||||
deleted?: number | boolean;
|
deleted?: number | boolean;
|
||||||
prereq_id?: number | null;
|
prereq_id?: number | null;
|
||||||
hasBookwork: boolean;
|
hasBookwork: boolean;
|
||||||
@@ -17,12 +17,12 @@ export interface CourseEventDetails {
|
|||||||
id: number | null; // PK
|
id: number | null; // PK
|
||||||
course_id: number | null; // FK → courses.id
|
course_id: number | null; // FK → courses.id
|
||||||
event_type: number | null; // FK → event_types.id
|
event_type: number | null; // FK → event_types.id
|
||||||
event_date: string; // datetime (not nullable)
|
event_date: Date; // datetime (not nullable)
|
||||||
|
|
||||||
guilded_event_id: number | null;
|
guilded_event_id: number | null;
|
||||||
|
|
||||||
created_at: string; // datetime
|
created_at: Date; // datetime
|
||||||
updated_at: string; // datetime
|
updated_at: Date; // datetime
|
||||||
|
|
||||||
deleted: boolean | null; // tinyint(4), nullable
|
deleted: boolean | null; // tinyint(4), nullable
|
||||||
report_url: string | null; // varchar(2048)
|
report_url: string | null; // varchar(2048)
|
||||||
@@ -44,8 +44,8 @@ export interface CourseAttendee {
|
|||||||
course_event_id: number; // PK
|
course_event_id: number; // PK
|
||||||
attendee_role_id: number | null;
|
attendee_role_id: number | null;
|
||||||
role: CourseAttendeeRole | null;
|
role: CourseAttendeeRole | null;
|
||||||
created_at: string; // datetime → ISO string
|
created_at: Date; // datetime → ISO string
|
||||||
updated_at: string; // datetime → ISO string
|
updated_at: Date; // datetime → ISO string
|
||||||
remarks: string | null;
|
remarks: string | null;
|
||||||
|
|
||||||
attendee_name: string | null;
|
attendee_name: string | null;
|
||||||
@@ -55,8 +55,8 @@ export interface CourseAttendeeRole {
|
|||||||
id: number; // PK, auto-increment
|
id: number; // PK, auto-increment
|
||||||
name: string | null; // varchar(50), unique, nullable
|
name: string | null; // varchar(50), unique, nullable
|
||||||
description: string | null; // text
|
description: string | null; // text
|
||||||
created_at: string | null; // datetime (nullable)
|
created_at: Date | null; // datetime (nullable)
|
||||||
updated_at: string | null; // datetime (nullable)
|
updated_at: Date | null; // datetime (nullable)
|
||||||
deleted: boolean; // tinyint(4)
|
deleted: boolean; // tinyint(4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export function toDateTime(date: Date): string {
|
export function toDateTime(date: Date): string {
|
||||||
|
console.log(date);
|
||||||
// This produces a CST-local time because server runs in CST
|
// This produces a CST-local time because server runs in CST
|
||||||
const year = date.getFullYear();
|
const year = date.getFullYear();
|
||||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { toTypedSchema } from '@vee-validate/zod'
|
|||||||
import { computed, onMounted, ref, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import { getAllAttendeeRoles, getAllTrainings, postTrainingReport } from '@/api/trainingReport'
|
import { getAllAttendeeRoles, getAllTrainings, postTrainingReport } from '@/api/trainingReport'
|
||||||
import { getMembers, Member } from '@/api/member'
|
import { getMembers, Member } from '@/api/member'
|
||||||
|
|
||||||
import FieldGroup from '../ui/field/FieldGroup.vue'
|
import FieldGroup from '../ui/field/FieldGroup.vue'
|
||||||
import Field from '../ui/field/Field.vue'
|
import Field from '../ui/field/Field.vue'
|
||||||
import FieldLabel from '../ui/field/FieldLabel.vue'
|
import FieldLabel from '../ui/field/FieldLabel.vue'
|
||||||
@@ -18,7 +19,8 @@ import FieldLegend from '../ui/field/FieldLegend.vue'
|
|||||||
import FieldDescription from '../ui/field/FieldDescription.vue'
|
import FieldDescription from '../ui/field/FieldDescription.vue'
|
||||||
import Checkbox from '../ui/checkbox/Checkbox.vue'
|
import Checkbox from '../ui/checkbox/Checkbox.vue'
|
||||||
|
|
||||||
const { handleSubmit, resetForm, errors, values } = useForm({
|
|
||||||
|
const { handleSubmit, resetForm, errors, values, setFieldValue } = useForm({
|
||||||
validationSchema: toTypedSchema(trainingReportSchema),
|
validationSchema: toTypedSchema(trainingReportSchema),
|
||||||
initialValues: {
|
initialValues: {
|
||||||
course_id: null,
|
course_id: null,
|
||||||
@@ -28,13 +30,22 @@ const { handleSubmit, resetForm, errors, values } = useForm({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(errors, (newErrors) => {
|
// watch(errors, (newErrors) => {
|
||||||
console.log(newErrors)
|
// console.log(newErrors)
|
||||||
}, { deep: true })
|
// }, { deep: true })
|
||||||
|
|
||||||
watch(values, (newErrors) => {
|
// watch(values, (newErrors) => {
|
||||||
console.log(newErrors.attendees)
|
// console.log(newErrors.attendees)
|
||||||
}, { deep: true })
|
// }, { deep: true })
|
||||||
|
|
||||||
|
watch(() => values.course_id, (newCourseId, oldCourseId) => {
|
||||||
|
if (!oldCourseId) return;
|
||||||
|
|
||||||
|
values.attendees.forEach((a, index) => {
|
||||||
|
setFieldValue(`attendees[${index}].passed_bookwork`, false);
|
||||||
|
setFieldValue(`attendees[${index}].passed_qual`, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const submitForm = handleSubmit(onSubmit);
|
const submitForm = handleSubmit(onSubmit);
|
||||||
|
|
||||||
@@ -50,13 +61,14 @@ function onSubmit(vals) {
|
|||||||
try {
|
try {
|
||||||
const clean: CourseEventDetails = {
|
const clean: CourseEventDetails = {
|
||||||
...vals,
|
...vals,
|
||||||
event_date: toMySQLDateTime(new Date(vals.event_date)),
|
event_date: new Date(vals.event_date),
|
||||||
}
|
}
|
||||||
|
|
||||||
postTrainingReport(clean).then((newID) => {
|
postTrainingReport(clean).then((newID) => {
|
||||||
emit("submit", newID);
|
emit("submit", newID);
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("There was an error submitting the training report", err);
|
console.error("There was an error submitting the training report", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,16 +135,31 @@ onMounted(async () => {
|
|||||||
<FieldGroup class="gap-4">
|
<FieldGroup class="gap-4">
|
||||||
|
|
||||||
<!-- Column Headers -->
|
<!-- Column Headers -->
|
||||||
|
<div class="relative">
|
||||||
<div
|
<div
|
||||||
class="grid grid-cols-[180px_155px_65px_41px_1fr_auto] gap-3 font-medium text-sm text-muted-foreground">
|
class="grid grid-cols-[180px_155px_65px_41px_1fr_auto] gap-3 font-medium text-sm text-muted-foreground">
|
||||||
|
|
||||||
<div>Member</div>
|
<div>Member</div>
|
||||||
<div>Role</div>
|
<div>Role</div>
|
||||||
<div>Bookwork</div>
|
|
||||||
<div>Qual</div>
|
<!-- Bookwork -->
|
||||||
|
<div class="text-center">Bookwork</div>
|
||||||
|
|
||||||
|
<!-- Qual -->
|
||||||
|
<div class="text-center">Qual</div>
|
||||||
|
|
||||||
<div>Remarks</div>
|
<div>Remarks</div>
|
||||||
<div></div> <!-- empty for remove button -->
|
<div></div> <!-- empty for remove button -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- FLOATING SUPERHEADER -->
|
||||||
|
<div class="absolute left-[calc(180px+155px+65px/2)] -top-5
|
||||||
|
w-[106px] text-center text-xs font-medium text-muted-foreground
|
||||||
|
pointer-events-none">
|
||||||
|
──── Pass ────
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- Attendee Rows -->
|
<!-- Attendee Rows -->
|
||||||
<template v-for="(field, index) in fields" :key="field.key">
|
<template v-for="(field, index) in fields" :key="field.key">
|
||||||
@@ -178,7 +205,8 @@ onMounted(async () => {
|
|||||||
<div class="relative inline-flex items-center group">
|
<div class="relative inline-flex items-center group">
|
||||||
|
|
||||||
<Checkbox :disabled="!selectedCourse?.hasBookwork"
|
<Checkbox :disabled="!selectedCourse?.hasBookwork"
|
||||||
:name="`attendees[${index}].passed_bookwork`" v-bind="field">
|
:name="`attendees[${index}].passed_bookwork`" :model-value="!field.checked"
|
||||||
|
@update:model-value="field['onUpdate:modelValue']">
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
<!-- Tooltip bubble -->
|
<!-- Tooltip bubble -->
|
||||||
<div v-if="!selectedCourse?.hasBookwork" class="pointer-events-none absolute -top-9 left-1/2 -translate-x-1/2
|
<div v-if="!selectedCourse?.hasBookwork" class="pointer-events-none absolute -top-9 left-1/2 -translate-x-1/2
|
||||||
@@ -201,7 +229,8 @@ onMounted(async () => {
|
|||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<div class="relative inline-flex items-center group">
|
<div class="relative inline-flex items-center group">
|
||||||
<Checkbox :disabled="!selectedCourse?.hasQual"
|
<Checkbox :disabled="!selectedCourse?.hasQual"
|
||||||
:name="`attendees[${index}].passed_qual`" v-bind="field"></Checkbox>
|
:name="`attendees[${index}].passed_qual`" :model-value="!field.checked"
|
||||||
|
@update:model-value="field['onUpdate:modelValue']"></Checkbox>
|
||||||
<!-- Tooltip bubble -->
|
<!-- Tooltip bubble -->
|
||||||
<div v-if="!selectedCourse?.hasQual" class="pointer-events-none absolute -top-9 left-1/2 -translate-x-1/2
|
<div v-if="!selectedCourse?.hasQual" class="pointer-events-none absolute -top-9 left-1/2 -translate-x-1/2
|
||||||
whitespace-nowrap rounded-md bg-popover px-2 py-1 text-xs
|
whitespace-nowrap rounded-md bg-popover px-2 py-1 text-xs
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ onMounted(async () => {
|
|||||||
@click="router.push(`/trainingReport/${report.event_id}`)">
|
@click="router.push(`/trainingReport/${report.event_id}`)">
|
||||||
<TableCell class="font-medium">{{ report.course_name.length > 30 ? report.course_shortname :
|
<TableCell class="font-medium">{{ report.course_name.length > 30 ? report.course_shortname :
|
||||||
report.course_name }}</TableCell>
|
report.course_name }}</TableCell>
|
||||||
<TableCell>{{ report.date }}</TableCell>
|
<TableCell>{{ report.date.split('T')[0] }}</TableCell>
|
||||||
<TableCell class="text-right">{{ report.created_by_name === null ? "Unknown User" :
|
<TableCell class="text-right">{{ report.created_by_name === null ? "Unknown User" :
|
||||||
report.created_by_name
|
report.created_by_name
|
||||||
}}</TableCell>
|
}}</TableCell>
|
||||||
@@ -173,7 +173,7 @@ onMounted(async () => {
|
|||||||
<p class="scroll-m-20 text-xl font-semibold tracking-tight">{{ focusedTrainingReport.course_name }}
|
<p class="scroll-m-20 text-xl font-semibold tracking-tight">{{ focusedTrainingReport.course_name }}
|
||||||
</p>
|
</p>
|
||||||
<div class="flex gap-10">
|
<div class="flex gap-10">
|
||||||
<p class="text-muted-foreground">{{ focusedTrainingReport.event_date }}</p>
|
<p class="text-muted-foreground">{{ focusedTrainingReport.event_date.split('T')[0] }}</p>
|
||||||
<p class="">Created by {{ focusedTrainingReport.created_by_name === null ? "Unknown User" :
|
<p class="">Created by {{ focusedTrainingReport.created_by_name === null ? "Unknown User" :
|
||||||
focusedTrainingReport.created_by_name
|
focusedTrainingReport.created_by_name
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user