mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 00:02:06 +00:00
363 lines
12 KiB
TypeScript
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 & 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>
|
|
);
|
|
}
|