From 2a210eeab0bf75675ae47c9c08ae16e66f71e46b Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Tue, 16 Jun 2026 14:50:42 -0300 Subject: [PATCH] fix(employees,pm-schedules): align forms with backend API contracts Align PM Schedule form and save payload with PMSchedule_DTO fields. Bind employee Job Title select to jobTitleId for create/update payloads. Add regression tests for both flows. --- .../employees/_components/employee-form.tsx | 27 ++- .../_components/job-title-select.tsx | 44 +++++ .../_components/pm-schedule-form.tsx | 181 ++++++++++-------- src/app/(protected)/pmschedules/index.tsx | 30 +-- .../employees/constants/job-title-options.ts | 31 +++ .../employees/lib/resolve-job-title-id.ts | 24 +++ .../pm-schedules/api/pm-schedules-api.ts | 65 ++++--- .../mappers/pm-schedule-mapper.ts | 106 +++++----- .../schemas/pm-schedule-schema.ts | 21 +- src/domain/pm-schedules/types/pm-schedule.ts | 35 ++-- .../use-cases/use-pm-schedule-form-options.ts | 32 ++++ .../use-cases/use-save-pm-schedule.ts | 15 +- .../employee-form-job-title.test.tsx | 62 ++++++ .../lib/resolve-job-title-id.test.ts | 30 +++ .../employees/mappers/employee-mapper.test.ts | 12 ++ .../pm-schedules/api/pm-schedules-api.test.ts | 95 +++++++++ .../mappers/pm-schedule-mapper.test.ts | 69 +++++++ 17 files changed, 657 insertions(+), 222 deletions(-) create mode 100644 src/app/(protected)/employees/_components/job-title-select.tsx create mode 100644 src/domain/employees/constants/job-title-options.ts create mode 100644 src/domain/employees/lib/resolve-job-title-id.ts create mode 100644 src/domain/pm-schedules/use-cases/use-pm-schedule-form-options.ts create mode 100644 src/test/app/(protected)/employees/employee-form-job-title.test.tsx create mode 100644 src/test/domain/employees/lib/resolve-job-title-id.test.ts create mode 100644 src/test/domain/pm-schedules/api/pm-schedules-api.test.ts create mode 100644 src/test/domain/pm-schedules/mappers/pm-schedule-mapper.test.ts diff --git a/src/app/(protected)/employees/_components/employee-form.tsx b/src/app/(protected)/employees/_components/employee-form.tsx index 1a20929c..a574f760 100644 --- a/src/app/(protected)/employees/_components/employee-form.tsx +++ b/src/app/(protected)/employees/_components/employee-form.tsx @@ -17,6 +17,10 @@ import { TextField, Typography, } from "@mui/material"; +import { + resolveJobTitleId, + resolveJobTitleLabel, +} from "@/domain/employees/lib/resolve-job-title-id"; import { employeeDefaultValues, employeeSchema, @@ -25,6 +29,7 @@ import { import { useCreateEmployee } from "@/domain/employees/use-cases/use-create-employee"; import { useEmployeeDetail } from "@/domain/employees/use-cases/use-employee-detail"; import { useUpdateEmployee } from "@/domain/employees/use-cases/use-update-employee"; +import { JobTitleSelect } from "@/app/(protected)/employees/_components/job-title-select"; const DEPARTMENTS = ["Administration", "Sales", "Service", "Field Service"] as const; const TIME_ZONES = [ @@ -33,13 +38,6 @@ const TIME_ZONES = [ "Mountain Standard Time", "Pacific Standard Time", ] as const; -const JOB_TITLES = [ - "Technician", - "Service Associate", - "Accountant", - "Salesperson", - "President", -] as const; const ROLES = [ "Admin", "Administrator User", @@ -110,6 +108,7 @@ export default function EmployeeFormPage() { handleSubmit, reset, control, + setValue, formState: { errors }, } = useForm({ resolver: zodResolver(employeeSchema), @@ -118,14 +117,17 @@ export default function EmployeeFormPage() { useEffect(() => { if (employee && isEdit) { + const jobTitleId = resolveJobTitleId(employee.jobTitleId, employee.jobTitle); + const jobTitle = resolveJobTitleLabel(jobTitleId, employee.jobTitle); + reset({ firstName: employee.firstName, middleName: employee.middleName, lastName: employee.lastName, email: employee.email, phone: employee.phone, - jobTitle: employee.jobTitle, - jobTitleId: employee.jobTitleId ?? null, + jobTitle, + jobTitleId, role: employee.role, status: employee.status, hireDate: employee.hireDate, @@ -245,12 +247,7 @@ export default function EmployeeFormPage() { InputLabelProps={{ shrink: true }} fullWidth /> - + diff --git a/src/app/(protected)/employees/_components/job-title-select.tsx b/src/app/(protected)/employees/_components/job-title-select.tsx new file mode 100644 index 00000000..a9f0cf75 --- /dev/null +++ b/src/app/(protected)/employees/_components/job-title-select.tsx @@ -0,0 +1,44 @@ +import { Controller, type Control, type UseFormSetValue } from "react-hook-form"; +import { FormControl, InputLabel, MenuItem, Select } from "@mui/material"; +import { JOB_TITLE_OPTIONS } from "@/domain/employees/constants/job-title-options"; +import type { EmployeeFormValues } from "@/domain/employees/schemas/employee-schema"; + +interface JobTitleSelectProps { + control: Control; + setValue: UseFormSetValue; +} + +export function JobTitleSelect({ control, setValue }: JobTitleSelectProps) { + return ( + ( + + Job Title + + + )} + /> + ); +} diff --git a/src/app/(protected)/pmschedules/_components/pm-schedule-form.tsx b/src/app/(protected)/pmschedules/_components/pm-schedule-form.tsx index aaed0b51..9ed421a3 100644 --- a/src/app/(protected)/pmschedules/_components/pm-schedule-form.tsx +++ b/src/app/(protected)/pmschedules/_components/pm-schedule-form.tsx @@ -8,39 +8,38 @@ import { Button, CircularProgress, FormControl, + FormControlLabel, InputLabel, MenuItem, Paper, Select, Stack, + Switch, TextField, Typography, } from "@mui/material"; import { + pmScheduleDefaultValues, pmScheduleSchema, type PmScheduleFormValues, } from "@/domain/pm-schedules/schemas/pm-schedule-schema"; import { usePmScheduleDetail } from "@/domain/pm-schedules/use-cases/use-pm-schedule-detail"; +import { usePmScheduleFormOptions } from "@/domain/pm-schedules/use-cases/use-pm-schedule-form-options"; import { useSavePmSchedule } from "@/domain/pm-schedules/use-cases/use-save-pm-schedule"; import { toDateInputValue } from "@/lib/time-utils"; -const defaultValues: PmScheduleFormValues = { - asset: "", - assetName: "", - frequency: "Quarterly", - lastServiceDate: "", - nextServiceDate: "", - status: "Active", - assignedTo: "", - notes: "", -}; - export default function PmScheduleFormPage() { const { id } = useParams(); const navigate = useNavigate(); const isEdit = Boolean(id); const { data: schedule, isLoading, error } = usePmScheduleDetail(id); + const { + assets, + locations, + isLoading: optionsLoading, + error: optionsError, + } = usePmScheduleFormOptions(); const savePmSchedule = useSavePmSchedule(id); const { @@ -51,25 +50,23 @@ export default function PmScheduleFormPage() { formState: { errors }, } = useForm({ resolver: zodResolver(pmScheduleSchema), - defaultValues, + defaultValues: pmScheduleDefaultValues, }); useEffect(() => { if (schedule && isEdit) { reset({ - asset: schedule.asset, - assetName: schedule.assetName, + name: schedule.name, frequency: schedule.frequency as PmScheduleFormValues["frequency"], - lastServiceDate: toDateInputValue(schedule.lastServiceDate), - nextServiceDate: toDateInputValue(schedule.nextServiceDate), - status: schedule.status as PmScheduleFormValues["status"], - assignedTo: schedule.assignedTo, - notes: schedule.notes, + startDate: toDateInputValue(schedule.startDate), + isActive: schedule.isActive, + assetId: schedule.assetId, + locationId: schedule.locationId, }); } }, [schedule, isEdit, reset]); - if (isEdit && isLoading) { + if ((isEdit && isLoading) || optionsLoading) { return ( @@ -84,27 +81,22 @@ export default function PmScheduleFormPage() { {isEdit ? "Edit PM Schedule" : "Create PM Schedule"} {error && Failed to load PM schedule} + {optionsError && ( + + {optionsError instanceof Error ? optionsError.message : "Failed to load form options"} + + )} savePmSchedule.mutate(values))}> - - - - + - - - - + - - - ( - - Status - - - )} - /> - + ( + + Asset + + + )} + /> - + ( + + Location + + + )} + /> + + ( + } + label="Active" + /> + )} + />