added attendees to form

This commit is contained in:
2025-11-19 12:30:33 -05:00
parent 76ec0179b9
commit 403a8b394c
7 changed files with 244 additions and 19 deletions

View File

@@ -1,18 +1,10 @@
<script setup lang="ts">
import { trainingReportSchema, courseEventAttendeeSchema } from '@shared/schemas/trainingReportSchema'
import { Course, CourseAttendee } from '@shared/types/course'
import { Course, CourseAttendee, CourseAttendeeRole } from '@shared/types/course'
import { useForm, useFieldArray, FieldArray as VeeFieldArray, ErrorMessage, Field as VeeField } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import {
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage
} from '@/components/ui/form'
import { onMounted, ref } from 'vue'
import { getAllTrainings } from '@/api/trainingReport'
import { getAllAttendeeRoles, getAllTrainings } from '@/api/trainingReport'
import { getMembers, Member } from '@/api/member'
import FieldGroup from '../ui/field/FieldGroup.vue'
import Field from '../ui/field/Field.vue'
@@ -21,16 +13,23 @@ import Input from '../ui/input/Input.vue'
import FieldError from '../ui/field/FieldError.vue'
import Button from '../ui/button/Button.vue'
import Textarea from '../ui/textarea/Textarea.vue'
import { Plus, X } from 'lucide-vue-next';
import FieldSet from '../ui/field/FieldSet.vue'
import FieldLegend from '../ui/field/FieldLegend.vue'
import FieldDescription from '../ui/field/FieldDescription.vue'
const { handleSubmit, resetForm } = useForm({
const { handleSubmit, resetForm, errors } = useForm({
validationSchema: toTypedSchema(trainingReportSchema),
initialValues: {
course_id: null,
event_date: "",
remarks: "",
attendees: [],
}
})
const submitForm = handleSubmit(onSubmit);
function onSubmit(vals) {
// TODO: move this date conversion to a date library
const clean = {
@@ -41,16 +40,20 @@ function onSubmit(vals) {
console.log("SUBMITTED:", clean)
}
const { remove, push, fields } = useFieldArray('attendees');
const trainings = ref<Course[] | null>(null);
const members = ref<Member[] | null>(null);
const eventRoles = ref<CourseAttendeeRole[] | null>(null);
onMounted(async () => {
trainings.value = await getAllTrainings();
members.value = await getMembers();
eventRoles.value = await getAllAttendeeRoles();
})
</script>
<template>
<form id="trainingForm" @submit.prevent="handleSubmit(onSubmit)" class="flex flex-col gap-5">
<form id="trainingForm" @submit.prevent="submitForm" class="flex flex-col gap-5">
<FieldGroup>
<VeeField v-slot="{ field, errors }" name="course_id">
<Field :data-invalid="!!errors.length">
@@ -71,14 +74,94 @@ onMounted(async () => {
<VeeField v-slot="{ field, errors }" name="event_date">
<Field :data-invalid="!!errors.length">
<FieldLabel>Event Date</FieldLabel>
<input type="date" v-bind="field" class="border rounded p-2 w-full" />
<FieldError v-if="errors.length" :errors="errors" />
</Field>
</VeeField>
</FieldGroup>
<VeeFieldArray name="attendees" v-slot="{ fields, push, remove }">
<FieldSet class="gap-4">
<FieldLegend>Attendees</FieldLegend>
<FieldDescription>Add members who attended this session.</FieldDescription>
<FieldGroup class="gap-4">
<!-- Column Headers -->
<div
class="grid grid-cols-[180px_140px_50px_1fr_auto] gap-3 font-medium text-sm text-muted-foreground px-1">
<div>Member</div>
<div>Role</div>
<div>Passed</div>
<div>Remarks</div>
<div></div> <!-- empty for remove button -->
</div>
<!-- Attendee Rows -->
<template v-for="(field, index) in fields" :key="field.key">
<div class="grid grid-cols-[180px_140px_50px_1fr_auto] gap-3 items-start">
<!-- Member Select -->
<VeeField :name="`attendees[${index}].attendee_id`" v-slot="{ field: f, errors: e }">
<div>
<select v-bind="f" class="w-full border p-2 rounded-md">
<option value="">Select member...</option>
<option v-for="m in members" :key="m.member_id" :value="m.member_id">
{{ m.member_name }}
</option>
</select>
<FieldError v-if="e.length" :errors="e" />
</div>
</VeeField>
<!-- Role Select -->
<VeeField :name="`attendees[${index}].attendee_role_id`" v-slot="{ field: f, errors: e }">
<div>
<select v-bind="f" class="w-full border p-2 rounded-md">
<option value="">Select role...</option>
<option v-for="r in eventRoles" :key="r.id" :value="r.id">
{{ r.name }}
</option>
</select>
<FieldError v-if="e.length" :errors="e" />
</div>
</VeeField>
<!-- Passed Checkbox -->
<VeeField :name="`attendees[${index}].passed`" v-slot="{ field: f, errors: e }">
<div class="flex items-center h-[38px]">
<input type="checkbox" class="h-4 w-4" v-bind="f" />
</div>
</VeeField>
<!-- Remarks -->
<VeeField :name="`attendees[${index}].remarks`" v-slot="{ field: f, errors: e }">
<div>
<textarea v-bind="f" class="w-full border p-2 rounded-md h-[38px] resize-none"
placeholder="Optional remarks"></textarea>
<FieldError v-if="e.length" :errors="e" />
</div>
</VeeField>
<!-- Remove button -->
<Button type="button" variant="ghost" size="xs" @click="remove(index)" class="self-center">
<X class="h-4 w-4" />
</Button>
</div>
</template>
</FieldGroup>
<Button type="button" size="sm" variant="outline"
@click="push({ attendee_id: null, attendee_role_id: null, passed: false, remarks: '' })">
<Plus class="mr-1 h-4 w-4" />
Add Attendee
</Button>
</FieldSet>
</VeeFieldArray>
<FieldGroup>
<VeeField v-slot="{ field, errors }" name="remarks">
<Field :data-invalid="!!errors.length">