Added more strict form validation rules with user feedback #51
@@ -1,11 +1,11 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const courseEventAttendeeSchema = z.object({
|
export const courseEventAttendeeSchema = z.object({
|
||||||
attendee_id: z.number({invalid_type_error: "Must select a member"}).int().positive(),
|
attendee_id: z.number({ invalid_type_error: "Must select a member" }).int().positive(),
|
||||||
passed_bookwork: z.boolean(),
|
passed_bookwork: z.boolean(),
|
||||||
passed_qual: z.boolean(),
|
passed_qual: z.boolean(),
|
||||||
remarks: z.string(),
|
remarks: z.string(),
|
||||||
attendee_role_id: z.number({invalid_type_error: "Must select a role"}).int().positive()
|
attendee_role_id: z.number({ invalid_type_error: "Must select a role" }).int().positive()
|
||||||
})
|
})
|
||||||
|
|
||||||
export const trainingReportSchema = z.object({
|
export const trainingReportSchema = z.object({
|
||||||
@@ -19,5 +19,42 @@ export const trainingReportSchema = z.object({
|
|||||||
),
|
),
|
||||||
remarks: z.string().nullable().optional(),
|
remarks: z.string().nullable().optional(),
|
||||||
attendees: z.array(courseEventAttendeeSchema).default([]),
|
attendees: z.array(courseEventAttendeeSchema).default([]),
|
||||||
|
}).superRefine((data, ctx) => {
|
||||||
|
const trainerRole = 1;
|
||||||
|
const traineeRole = 2;
|
||||||
|
|
||||||
|
const hasTrainer = data.attendees.some((a) => a.attendee_role_id === trainerRole);
|
||||||
|
const hasTrainee = data.attendees.some((a) => a.attendee_role_id === traineeRole);
|
||||||
|
|
||||||
|
if (!hasTrainer) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["attendees"],
|
||||||
|
message: "At least one Primary Trainer is required.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasTrainee) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["attendees"],
|
||||||
|
message: "At least one Trainee is required.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//no duplicates
|
||||||
|
const idCounts = new Map<number, number>();
|
||||||
|
|
||||||
|
data.attendees.forEach((a, index) => {
|
||||||
|
idCounts.set(a.attendee_id, (idCounts.get(a.attendee_id) ?? 0) + 1);
|
||||||
|
|
||||||
|
if (idCounts.get(a.attendee_id)! > 1) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["attendees"],
|
||||||
|
message: "Cannot have duplicate attendee.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ import FieldSet from '../ui/field/FieldSet.vue'
|
|||||||
import FieldLegend from '../ui/field/FieldLegend.vue'
|
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'
|
||||||
|
import { configure } from 'vee-validate'
|
||||||
|
|
||||||
|
|
||||||
const { handleSubmit, resetForm, errors, values, setFieldValue } = useForm({
|
const { handleSubmit, resetForm, errors, values, setFieldValue } = useForm({
|
||||||
validationSchema: toTypedSchema(trainingReportSchema),
|
validationSchema: toTypedSchema(trainingReportSchema),
|
||||||
|
validateOnMount: false,
|
||||||
initialValues: {
|
initialValues: {
|
||||||
course_id: null,
|
course_id: null,
|
||||||
event_date: "",
|
event_date: "",
|
||||||
@@ -129,8 +131,17 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<VeeFieldArray name="attendees" v-slot="{ fields, push, remove }">
|
<VeeFieldArray name="attendees" v-slot="{ fields, push, remove }">
|
||||||
<FieldSet class="gap-4">
|
<FieldSet class="gap-4">
|
||||||
<FieldLegend class="scroll-m-20 text-lg tracking-tight">Attendees</FieldLegend>
|
<div class="flex flex-col gap-2">
|
||||||
<FieldDescription>Add members who attended this session.</FieldDescription>
|
<FieldLegend class="scroll-m-20 text-lg tracking-tight mb-0">Attendees</FieldLegend>
|
||||||
|
<FieldDescription class="mb-0">Add members who attended this session.</FieldDescription>
|
||||||
|
<div class="h-4">
|
||||||
|
<div class="text-red-500 text-sm"
|
||||||
|
v-if="errors.attendees && typeof errors.attendees === 'string'">
|
||||||
|
{{ errors.attendees }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<FieldGroup class="gap-4">
|
<FieldGroup class="gap-4">
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user