shoc-frontend-new/src/app/(protected)/employees/_components/employee-form.tsx
2026-06-12 11:53:32 -03:00

363 lines
12 KiB
TypeScript

import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useNavigate, useParams } from "react-router-dom";
import {
Alert,
Box,
Button,
CircularProgress,
Divider,
FormControl,
InputLabel,
MenuItem,
Paper,
Select,
Stack,
TextField,
Typography,
} from "@mui/material";
import {
employeeDefaultValues,
employeeSchema,
type EmployeeFormValues,
} from "@/domain/employees/schemas/employee-schema";
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";
const DEPARTMENTS = ["Administration", "Sales", "Service", "Field Service"] as const;
const TIME_ZONES = [
"Eastern Standard Time",
"Central Standard Time",
"Mountain Standard Time",
"Pacific Standard Time",
] as const;
const JOB_TITLES = [
"Technician",
"Service Associate",
"Accountant",
"Salesperson",
"President",
] as const;
const ROLES = [
"Admin",
"Administrator User",
"Dispatcher",
"Editor User",
"Employee",
"Read Only User",
"Technician",
] as const;
const US_STATES = ["AL", "AK", "AZ", "CA", "FL", "NY", "TX"] as const;
function SelectField({
name,
label,
control,
options,
required,
error,
helperText,
}: {
name: keyof EmployeeFormValues;
label: string;
control: ReturnType<typeof useForm<EmployeeFormValues>>["control"];
options: readonly string[];
required?: boolean;
error?: boolean;
helperText?: string;
}) {
return (
<Controller
name={name}
control={control}
render={({ field }) => (
<FormControl fullWidth required={required} error={error}>
<InputLabel id={`${name}-label`}>{label}</InputLabel>
<Select {...field} labelId={`${name}-label`} label={label} value={field.value ?? ""}>
<MenuItem value="">
<em>Select...</em>
</MenuItem>
{options.map((option) => (
<MenuItem key={option} value={option}>
{option}
</MenuItem>
))}
</Select>
{helperText && (
<Typography variant="caption" color="error" className="mt-1">
{helperText}
</Typography>
)}
</FormControl>
)}
/>
);
}
export default function EmployeeFormPage() {
const { id } = useParams();
const navigate = useNavigate();
const isEdit = Boolean(id);
const { data: employee, isLoading, error } = useEmployeeDetail(id);
const createEmployee = useCreateEmployee();
const updateEmployee = useUpdateEmployee();
const {
register,
handleSubmit,
reset,
control,
formState: { errors },
} = useForm<EmployeeFormValues>({
resolver: zodResolver(employeeSchema),
defaultValues: employeeDefaultValues,
});
useEffect(() => {
if (employee && isEdit) {
reset({
firstName: employee.firstName,
middleName: employee.middleName,
lastName: employee.lastName,
email: employee.email,
phone: employee.phone,
jobTitle: employee.jobTitle,
role: employee.role,
status: employee.status,
hireDate: employee.hireDate,
dateOfBirth: employee.dateOfBirth,
address: employee.address,
department: employee.department,
timeZone: employee.timeZone,
gender: employee.gender,
employeeNumber: employee.employeeNumber,
regionName: employee.regionName,
userName: employee.userName,
confirmUserName: employee.confirmUserName,
street: employee.street,
streetLineTwo: employee.streetLineTwo,
city: employee.city,
zip: employee.zip,
country: employee.country,
state: employee.state,
county: employee.county,
smsAddress: employee.smsAddress,
website: employee.website,
facebookUrl: employee.facebookUrl,
linkedInUrl: employee.linkedInUrl,
twitterUrl: employee.twitterUrl,
});
}
}, [employee, isEdit, reset]);
const isSaving = createEmployee.isPending || updateEmployee.isPending;
const onSubmit = (values: EmployeeFormValues) => {
if (isEdit && id) {
updateEmployee.mutate({ id, values });
return;
}
createEmployee.mutate(values);
};
if (isEdit && isLoading) {
return (
<Box className="flex justify-center p-10">
<CircularProgress />
</Box>
);
}
return (
<Box className="mx-auto max-w-5xl p-4">
<Paper variant="outlined" className="p-6">
<Stack spacing={3}>
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Typography variant="h5" component="h1">
{isEdit ? "Edit an Employee" : "Create an Employee"}
</Typography>
<Button variant="text" onClick={() => navigate("/employees")}>
Back to list
</Button>
</Stack>
{error && (
<Alert severity="error">
{error instanceof Error ? error.message : "Failed to load employee"}
</Alert>
)}
<Box component="form" onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-4">
<Typography variant="subtitle1" className="font-semibold">
General Details
</Typography>
<Stack direction={{ xs: "column", md: "row" }} spacing={2}>
<TextField
{...register("firstName")}
label="First Name"
required
error={Boolean(errors.firstName)}
helperText={errors.firstName?.message}
fullWidth
/>
<TextField {...register("middleName")} label="Middle Name" fullWidth />
<TextField
{...register("lastName")}
label="Last Name"
required
error={Boolean(errors.lastName)}
helperText={errors.lastName?.message}
fullWidth
/>
</Stack>
<Stack direction={{ xs: "column", md: "row" }} spacing={2}>
<SelectField
name="department"
label="Department"
control={control}
options={DEPARTMENTS}
/>
<SelectField
name="timeZone"
label="Time Zone"
control={control}
options={TIME_ZONES}
required
error={Boolean(errors.timeZone)}
helperText={errors.timeZone?.message}
/>
<SelectField
name="gender"
label="Gender"
control={control}
options={["Male", "Female", "Not specified", "Other"]}
/>
</Stack>
<Stack direction={{ xs: "column", md: "row" }} spacing={2}>
<TextField
{...register("dateOfBirth")}
label="Date of Birth"
type="date"
InputLabelProps={{ shrink: true }}
fullWidth
/>
<SelectField
name="jobTitle"
label="Job Title"
control={control}
options={JOB_TITLES}
/>
<TextField {...register("employeeNumber")} label="Employee #" fullWidth />
</Stack>
<Stack direction={{ xs: "column", md: "row" }} spacing={2}>
<TextField
{...register("hireDate")}
label="Hire Date"
type="date"
InputLabelProps={{ shrink: true }}
fullWidth
/>
<SelectField
name="regionName"
label="Region Name"
control={control}
options={["Region 1", "Region 2"]}
/>
</Stack>
<Divider />
<Typography variant="subtitle1" className="font-semibold">
Login &amp; Role
</Typography>
<Typography variant="body2" color="text.secondary">
{isEdit
? "If the Username is changed, the Employee's account will have to be confirmed again before they can log in."
: "A confirmation email will be sent to activate the Employee's account."}
</Typography>
<Stack direction={{ xs: "column", md: "row" }} spacing={2}>
<SelectField
name="role"
label="Role"
control={control}
options={ROLES}
required
error={Boolean(errors.role)}
helperText={errors.role?.message}
/>
<TextField
{...register("userName")}
label="User Name"
type="email"
required
error={Boolean(errors.userName)}
helperText={errors.userName?.message}
fullWidth
/>
<TextField
{...register("confirmUserName")}
label="Confirm User Name"
type="email"
required
error={Boolean(errors.confirmUserName)}
helperText={errors.confirmUserName?.message}
fullWidth
/>
</Stack>
<Divider />
<Typography variant="subtitle1" className="font-semibold">
Address
</Typography>
<Stack direction={{ xs: "column", md: "row" }} spacing={2}>
<TextField {...register("street")} label="Address 1" fullWidth />
<TextField {...register("streetLineTwo")} label="Address 2" fullWidth />
</Stack>
<Stack direction={{ xs: "column", md: "row" }} spacing={2}>
<TextField {...register("city")} label="City" fullWidth />
<TextField {...register("zip")} label="Zip/Postal Code" fullWidth />
</Stack>
<Stack direction={{ xs: "column", md: "row" }} spacing={2}>
<SelectField name="country" label="Country" control={control} options={["US"]} />
<SelectField
name="state"
label="State / Province"
control={control}
options={US_STATES}
/>
<TextField {...register("county")} label="County" fullWidth />
</Stack>
<Divider />
<Typography variant="subtitle1" className="font-semibold">
Additional Details
</Typography>
<Stack direction={{ xs: "column", md: "row" }} spacing={2}>
<TextField {...register("smsAddress")} label="SMS Address" type="email" fullWidth />
<TextField {...register("website")} label="Website" fullWidth />
</Stack>
<Stack direction={{ xs: "column", md: "row" }} spacing={2}>
<TextField {...register("facebookUrl")} label="Facebook" fullWidth />
<TextField {...register("linkedInUrl")} label="LinkedIn" fullWidth />
<TextField {...register("twitterUrl")} label="Twitter" fullWidth />
</Stack>
<Stack direction="row" justifyContent="flex-end" spacing={2}>
<Button variant="outlined" onClick={() => navigate("/employees")} disabled={isSaving}>
Cancel
</Button>
<Button type="submit" variant="contained" disabled={isSaving}>
{isSaving ? "Saving..." : isEdit ? "Update" : "Create"}
</Button>
</Stack>
</Box>
</Stack>
</Paper>
</Box>
);
}