From a60da31f4c2d0b273ec2d549984d48c0891f33fa Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Fri, 12 Jun 2026 11:53:32 -0300 Subject: [PATCH] feat: add employees CRUD module --- .../employees/_components/employee-form.tsx | 363 ++++++++++++++++++ src/app/(protected)/employees/edit/[id].tsx | 1 + src/app/(protected)/employees/index.tsx | 232 +++++++++++ src/app/(protected)/employees/new.tsx | 1 + src/domain/employees/api/employees-api.ts | 63 +++ .../employees/mappers/employee-mapper.ts | 123 ++++++ .../employees/schemas/employee-schema.ts | 74 ++++ src/domain/employees/types/employee.ts | 56 +++ .../use-cases/use-create-employee.ts | 24 ++ .../use-cases/use-delete-employee.ts | 19 + .../use-cases/use-employee-detail.ts | 13 + .../employees/use-cases/use-employees-list.ts | 14 + .../use-cases/use-update-employee.ts | 35 ++ 13 files changed, 1018 insertions(+) create mode 100644 src/app/(protected)/employees/_components/employee-form.tsx create mode 100644 src/app/(protected)/employees/edit/[id].tsx create mode 100644 src/app/(protected)/employees/index.tsx create mode 100644 src/app/(protected)/employees/new.tsx create mode 100644 src/domain/employees/api/employees-api.ts create mode 100644 src/domain/employees/mappers/employee-mapper.ts create mode 100644 src/domain/employees/schemas/employee-schema.ts create mode 100644 src/domain/employees/types/employee.ts create mode 100644 src/domain/employees/use-cases/use-create-employee.ts create mode 100644 src/domain/employees/use-cases/use-delete-employee.ts create mode 100644 src/domain/employees/use-cases/use-employee-detail.ts create mode 100644 src/domain/employees/use-cases/use-employees-list.ts create mode 100644 src/domain/employees/use-cases/use-update-employee.ts diff --git a/src/app/(protected)/employees/_components/employee-form.tsx b/src/app/(protected)/employees/_components/employee-form.tsx new file mode 100644 index 00000000..39db0c04 --- /dev/null +++ b/src/app/(protected)/employees/_components/employee-form.tsx @@ -0,0 +1,363 @@ +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>["control"]; + options: readonly string[]; + required?: boolean; + error?: boolean; + helperText?: string; +}) { + return ( + ( + + {label} + + {helperText && ( + + {helperText} + + )} + + )} + /> + ); +} + +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({ + 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 ( + + + + ); + } + + return ( + + + + + + {isEdit ? "Edit an Employee" : "Create an Employee"} + + + + + {error && ( + + {error instanceof Error ? error.message : "Failed to load employee"} + + )} + + + + General Details + + + + + + + + + + + + + + + + + + + + + + + + + Login & Role + + + {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."} + + + + + + + + + + + Address + + + + + + + + + + + + + + + + + + + Additional Details + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/app/(protected)/employees/edit/[id].tsx b/src/app/(protected)/employees/edit/[id].tsx new file mode 100644 index 00000000..133b472d --- /dev/null +++ b/src/app/(protected)/employees/edit/[id].tsx @@ -0,0 +1 @@ +export { default } from "../_components/employee-form"; diff --git a/src/app/(protected)/employees/index.tsx b/src/app/(protected)/employees/index.tsx new file mode 100644 index 00000000..50364486 --- /dev/null +++ b/src/app/(protected)/employees/index.tsx @@ -0,0 +1,232 @@ +import { useMemo, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; +import EditOutlinedIcon from "@mui/icons-material/EditOutlined"; +import { + Alert, + Box, + Button, + Chip, + CircularProgress, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + IconButton, + Paper, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TablePagination, + TableRow, + TextField, + Tooltip, + Typography, +} from "@mui/material"; +import type { EmployeeListItem } from "@/domain/employees/types/employee"; +import { useDeleteEmployee } from "@/domain/employees/use-cases/use-delete-employee"; +import { useEmployeesList } from "@/domain/employees/use-cases/use-employees-list"; +import { useDebounce } from "@/hooks/use-debounce"; + +const STATUS_COLORS: Record = { + Active: "success", + Inactive: "default", +}; + +export default function EmployeesListPage() { + const navigate = useNavigate(); + const [search, setSearch] = useState(""); + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = useState(12); + const [deleteTarget, setDeleteTarget] = useState(null); + + const debouncedSearch = useDebounce(search, 300); + const deleteEmployee = useDeleteEmployee(); + + const listParams = useMemo( + () => ({ + search: debouncedSearch, + page: page + 1, + pageSize, + }), + [debouncedSearch, page, pageSize], + ); + + const { data, isLoading, isFetching, error } = useEmployeesList(listParams); + + const rows = data?.items ?? []; + const totalCount = data?.totalCount ?? 0; + + const handleDeleteConfirm = () => { + if (!deleteTarget) { + return; + } + + deleteEmployee.mutate(deleteTarget.id, { + onSuccess: () => setDeleteTarget(null), + }); + }; + + return ( + + + + + Employees + + + {totalCount} total + + {isFetching && !isLoading && } + + + + { + setSearch(event.target.value); + setPage(0); + }} + placeholder="Search employees..." + className="min-w-sidebar" + /> + + + + + {error && ( + + {error instanceof Error ? error.message : "Failed to load employees"} + + )} + + + + + + Name + Job Title + Email + Phone + Department + Status + + Actions + + + + + {isLoading ? ( + + + + + + ) : rows.length === 0 ? ( + + + No employees found + + + ) : ( + rows.map((row) => ( + + + + + {row.jobTitle || "—"} + {row.email || "—"} + {row.phone || "—"} + {row.department || "—"} + + + + + + navigate(`/employees/edit/${row.id}`)} + > + + + + + setDeleteTarget(row)} + > + + + + + + )) + )} + +
+
+ + setPage(nextPage)} + rowsPerPage={pageSize} + onRowsPerPageChange={(event) => { + setPageSize(Number(event.target.value)); + setPage(0); + }} + rowsPerPageOptions={[12, 24, 48]} + /> + + setDeleteTarget(null)}> + Delete Employee + + + Are you sure you want to delete "{deleteTarget?.name}"? This action cannot be + undone. + + + + + + + +
+ ); +} diff --git a/src/app/(protected)/employees/new.tsx b/src/app/(protected)/employees/new.tsx new file mode 100644 index 00000000..a1aa5d20 --- /dev/null +++ b/src/app/(protected)/employees/new.tsx @@ -0,0 +1 @@ +export { default } from "./_components/employee-form"; diff --git a/src/domain/employees/api/employees-api.ts b/src/domain/employees/api/employees-api.ts new file mode 100644 index 00000000..0ea7b6c6 --- /dev/null +++ b/src/domain/employees/api/employees-api.ts @@ -0,0 +1,63 @@ +import { API_PATHS } from "@/api/api-paths"; +import { apiDelete, apiGet, apiPost, apiPut } from "@/api/api"; +import { handleApiResponse, handlePaginatedResponse } from "@/api/handle-api-response"; +import { + mapEmployeeListItems, + transformEmployee, + transformToBackend, +} from "@/domain/employees/mappers/employee-mapper"; +import type { + Employee, + EmployeesListParams, + EmployeesListResult, +} from "@/domain/employees/types/employee"; + +function buildSearchParams(params: EmployeesListParams): Record { + return { + search: params.search ?? "", + page: params.page ?? 1, + pageSize: params.pageSize ?? 12, + }; +} + +export const employeesApi = { + getList: async (params: EmployeesListParams = {}): Promise => { + const searchParams = buildSearchParams(params); + const data = await apiGet(API_PATHS.employee.getList, { searchParams }); + const paginated = handlePaginatedResponse(data, { + page: params.page, + pageSize: params.pageSize, + }); + + return { + items: mapEmployeeListItems(paginated.items), + totalCount: paginated.totalCount, + page: paginated.page, + pageSize: paginated.pageSize, + }; + }, + + getById: async (id: string | number): Promise => { + const data = await apiGet(`${API_PATHS.rest.employees}/${id}`); + return transformEmployee(handleApiResponse(data)); + }, + + create: async (payload: Employee): Promise => { + const data = await apiPost(API_PATHS.rest.employees, transformToBackend(payload)); + return transformEmployee(handleApiResponse(data)); + }, + + update: async (id: string | number, payload: Employee): Promise => { + const data = await apiPut( + `${API_PATHS.rest.employees}/${id}`, + transformToBackend({ ...payload, id }), + ); + return transformEmployee(handleApiResponse(data)); + }, + + delete: async (id: string | number): Promise => { + await apiDelete(`${API_PATHS.rest.employees}/${id}`); + }, + + getDropdown: () => apiGet(API_PATHS.employee.dropdown), +}; diff --git a/src/domain/employees/mappers/employee-mapper.ts b/src/domain/employees/mappers/employee-mapper.ts new file mode 100644 index 00000000..974476e4 --- /dev/null +++ b/src/domain/employees/mappers/employee-mapper.ts @@ -0,0 +1,123 @@ +import type { Employee, EmployeeListItem } from "@/domain/employees/types/employee"; + +function readString(record: Record, ...keys: string[]): string { + for (const key of keys) { + const value = record[key]; + if (typeof value === "string") { + return value; + } + if (typeof value === "number") { + return String(value); + } + } + return ""; +} + +function readId(record: Record): string | number { + const value = record.id ?? record.Id; + if (typeof value === "string" || typeof value === "number") { + return value; + } + return ""; +} + +export function mapEmployeeListItem(raw: unknown): EmployeeListItem { + const item = (typeof raw === "object" && raw !== null ? raw : {}) as Record; + const firstName = readString(item, "firstName", "FirstName"); + const lastName = readString(item, "lastName", "LastName"); + const name = readString(item, "name", "Name") || `${firstName} ${lastName}`.trim(); + + return { + id: readId(item), + name, + jobTitle: readString(item, "jobTitle", "JobTitle"), + email: readString(item, "email", "Email"), + phone: readString(item, "phone", "Phone"), + department: readString(item, "department", "Department"), + status: readString(item, "status", "Status"), + }; +} + +export function mapEmployeeListItems(rawItems: unknown[]): EmployeeListItem[] { + return rawItems.map(mapEmployeeListItem); +} + +export function transformEmployee(employee: unknown): Employee | null { + if (!employee || typeof employee !== "object") { + return null; + } + + const item = employee as Record; + const userName = readString(item, "userName", "UserName", "email", "Email"); + + return { + id: readId(item) || undefined, + firstName: readString(item, "firstName", "FirstName"), + middleName: readString(item, "middleName", "MiddleName"), + lastName: readString(item, "lastName", "LastName"), + email: readString(item, "email", "Email"), + phone: readString(item, "phone", "Phone"), + jobTitle: readString(item, "jobTitle", "JobTitle"), + role: readString(item, "role", "Role") || "Technician", + status: readString(item, "status", "Status") || "Active", + hireDate: readString(item, "hireDate", "HireDate"), + dateOfBirth: readString(item, "dateOfBirth", "DateOfBirth"), + address: readString(item, "address", "Address"), + department: readString(item, "department", "Department") || "Field Service", + street: readString(item, "street", "Street"), + streetLineTwo: readString(item, "streetLineTwo", "StreetLineTwo"), + city: readString(item, "city", "City"), + zip: readString(item, "zip", "Zip", "zipCode", "ZipCode"), + country: readString(item, "country", "Country") || "US", + state: readString(item, "state", "State"), + county: readString(item, "county", "County"), + timeZone: readString(item, "timeZone", "TimeZone") || "Eastern Standard Time", + gender: readString(item, "gender", "Gender"), + employeeNumber: readString(item, "employeeNumber", "EmployeeNumber"), + regionName: readString(item, "regionName", "RegionName"), + userName, + confirmUserName: userName, + smsAddress: readString(item, "smsAddress", "SmsAddress"), + website: readString(item, "website", "Website"), + facebookUrl: readString(item, "facebookUrl", "FacebookUrl"), + linkedInUrl: readString(item, "linkedInUrl", "LinkedInUrl"), + twitterUrl: readString(item, "twitterUrl", "TwitterUrl"), + }; +} + +export function transformToBackend(employee: Employee): Record { + const zipCode = employee.zip || ""; + + return { + id: employee.id, + firstName: employee.firstName || "", + middleName: employee.middleName || null, + lastName: employee.lastName || "", + email: employee.email || "", + phone: employee.phone || "", + jobTitle: employee.jobTitle || "", + role: employee.role || "Technician", + status: employee.status || "Active", + hireDate: employee.hireDate || null, + dateOfBirth: employee.dateOfBirth || null, + address: employee.address || "", + street: employee.street || "", + streetLineTwo: employee.streetLineTwo || null, + city: employee.city || "", + state: employee.state || "", + zipCode, + country: employee.country || "US", + county: employee.county || null, + department: employee.department || "Field Service", + timeZone: employee.timeZone || "Eastern Standard Time", + gender: employee.gender || null, + employeeNumber: employee.employeeNumber || null, + regionName: employee.regionName || null, + userName: employee.userName || employee.email || "", + smsAddress: employee.smsAddress || null, + website: employee.website || null, + facebookUrl: employee.facebookUrl || null, + linkedInUrl: employee.linkedInUrl || null, + twitterUrl: employee.twitterUrl || null, + }; +} diff --git a/src/domain/employees/schemas/employee-schema.ts b/src/domain/employees/schemas/employee-schema.ts new file mode 100644 index 00000000..418b13fc --- /dev/null +++ b/src/domain/employees/schemas/employee-schema.ts @@ -0,0 +1,74 @@ +import { z } from "zod"; + +export const employeeSchema = z + .object({ + firstName: z.string().min(1, "First name is required"), + middleName: z.string().optional(), + lastName: z.string().min(1, "Last name is required"), + email: z.string().optional(), + phone: z.string().optional(), + jobTitle: z.string().optional(), + role: z.string().min(1, "Role is required"), + status: z.string().optional(), + hireDate: z.string().optional(), + dateOfBirth: z.string().optional(), + address: z.string().optional(), + department: z.string().optional(), + timeZone: z.string().min(1, "Time zone is required"), + gender: z.string().optional(), + employeeNumber: z.string().optional(), + regionName: z.string().optional(), + userName: z.string().min(1, "User name is required"), + confirmUserName: z.string().min(1, "Confirm user name is required"), + street: z.string().optional(), + streetLineTwo: z.string().optional(), + city: z.string().optional(), + zip: z.string().optional(), + country: z.string().optional(), + state: z.string().optional(), + county: z.string().optional(), + smsAddress: z.string().optional(), + website: z.string().optional(), + facebookUrl: z.string().optional(), + linkedInUrl: z.string().optional(), + twitterUrl: z.string().optional(), + }) + .refine((data) => data.userName === data.confirmUserName, { + message: "User names must match", + path: ["confirmUserName"], + }); + +export type EmployeeFormValues = z.infer; + +export const employeeDefaultValues: EmployeeFormValues = { + firstName: "", + middleName: "", + lastName: "", + email: "", + phone: "", + jobTitle: "", + role: "Technician", + status: "Active", + hireDate: "", + dateOfBirth: "", + address: "", + department: "Field Service", + timeZone: "Eastern Standard Time", + gender: "", + employeeNumber: "", + regionName: "", + userName: "", + confirmUserName: "", + street: "", + streetLineTwo: "", + city: "", + zip: "", + country: "US", + state: "", + county: "", + smsAddress: "", + website: "", + facebookUrl: "", + linkedInUrl: "", + twitterUrl: "", +}; diff --git a/src/domain/employees/types/employee.ts b/src/domain/employees/types/employee.ts new file mode 100644 index 00000000..f878c231 --- /dev/null +++ b/src/domain/employees/types/employee.ts @@ -0,0 +1,56 @@ +export interface EmployeeListItem { + id: string | number; + name: string; + jobTitle: string; + email: string; + phone: string; + department: string; + status: string; +} + +export interface Employee { + id?: string | number; + firstName: string; + middleName: string; + lastName: string; + email: string; + phone: string; + jobTitle: string; + role: string; + status: string; + hireDate: string; + dateOfBirth: string; + address: string; + department: string; + street: string; + streetLineTwo: string; + city: string; + zip: string; + country: string; + state: string; + county: string; + timeZone: string; + gender: string; + employeeNumber: string; + regionName: string; + userName: string; + confirmUserName: string; + smsAddress: string; + website: string; + facebookUrl: string; + linkedInUrl: string; + twitterUrl: string; +} + +export interface EmployeesListParams { + search?: string; + page?: number; + pageSize?: number; +} + +export interface EmployeesListResult { + items: EmployeeListItem[]; + totalCount: number; + page: number; + pageSize: number; +} diff --git a/src/domain/employees/use-cases/use-create-employee.ts b/src/domain/employees/use-cases/use-create-employee.ts new file mode 100644 index 00000000..86735d5d --- /dev/null +++ b/src/domain/employees/use-cases/use-create-employee.ts @@ -0,0 +1,24 @@ +import { useMutation, useQueryClient, type UseMutationResult } from "@tanstack/react-query"; +import { useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; +import { employeesApi } from "@/domain/employees/api/employees-api"; +import type { EmployeeFormValues } from "@/domain/employees/schemas/employee-schema"; +import type { Employee } from "@/domain/employees/types/employee"; +import { queryKeys } from "@/infra/query-key/query-key"; + +export function useCreateEmployee(): UseMutationResult { + const navigate = useNavigate(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (values: EmployeeFormValues) => employeesApi.create(values as Employee), + onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: queryKeys.employees.all }); + toast.success("Employee created successfully!"); + navigate("/employees"); + }, + onError: (error: Error) => { + toast.error(error.message || "Failed to create employee"); + }, + }); +} diff --git a/src/domain/employees/use-cases/use-delete-employee.ts b/src/domain/employees/use-cases/use-delete-employee.ts new file mode 100644 index 00000000..2f777834 --- /dev/null +++ b/src/domain/employees/use-cases/use-delete-employee.ts @@ -0,0 +1,19 @@ +import { useMutation, useQueryClient, type UseMutationResult } from "@tanstack/react-query"; +import { toast } from "react-toastify"; +import { employeesApi } from "@/domain/employees/api/employees-api"; +import { queryKeys } from "@/infra/query-key/query-key"; + +export function useDeleteEmployee(): UseMutationResult { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (id: string | number) => employeesApi.delete(id), + onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: queryKeys.employees.all }); + toast.success("Employee deleted successfully!"); + }, + onError: (error: Error) => { + toast.error(error.message || "Failed to delete employee"); + }, + }); +} diff --git a/src/domain/employees/use-cases/use-employee-detail.ts b/src/domain/employees/use-cases/use-employee-detail.ts new file mode 100644 index 00000000..86ad2704 --- /dev/null +++ b/src/domain/employees/use-cases/use-employee-detail.ts @@ -0,0 +1,13 @@ +import { useQuery, type UseQueryResult } from "@tanstack/react-query"; +import { employeesApi } from "@/domain/employees/api/employees-api"; +import type { Employee } from "@/domain/employees/types/employee"; +import { queryKeys } from "@/infra/query-key/query-key"; +import { requireQueryParam } from "@/lib/query/require-query-param"; + +export function useEmployeeDetail(id: string | undefined): UseQueryResult { + return useQuery({ + queryKey: queryKeys.employees.detail(id ?? ""), + queryFn: () => employeesApi.getById(requireQueryParam(id, "Employee id")), + enabled: Boolean(id), + }); +} diff --git a/src/domain/employees/use-cases/use-employees-list.ts b/src/domain/employees/use-cases/use-employees-list.ts new file mode 100644 index 00000000..fad4af2f --- /dev/null +++ b/src/domain/employees/use-cases/use-employees-list.ts @@ -0,0 +1,14 @@ +import { keepPreviousData, useQuery, type UseQueryResult } from "@tanstack/react-query"; +import { employeesApi } from "@/domain/employees/api/employees-api"; +import type { EmployeesListParams, EmployeesListResult } from "@/domain/employees/types/employee"; +import { queryKeys } from "@/infra/query-key/query-key"; + +export function useEmployeesList( + params: EmployeesListParams, +): UseQueryResult { + return useQuery({ + queryKey: queryKeys.employees.list(params as Record), + queryFn: () => employeesApi.getList(params), + placeholderData: keepPreviousData, + }); +} diff --git a/src/domain/employees/use-cases/use-update-employee.ts b/src/domain/employees/use-cases/use-update-employee.ts new file mode 100644 index 00000000..4211cc1d --- /dev/null +++ b/src/domain/employees/use-cases/use-update-employee.ts @@ -0,0 +1,35 @@ +import { useMutation, useQueryClient, type UseMutationResult } from "@tanstack/react-query"; +import { useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; +import { employeesApi } from "@/domain/employees/api/employees-api"; +import type { EmployeeFormValues } from "@/domain/employees/schemas/employee-schema"; +import type { Employee } from "@/domain/employees/types/employee"; +import { queryKeys } from "@/infra/query-key/query-key"; + +interface UpdateEmployeeInput { + id: string | number; + values: EmployeeFormValues; +} + +export function useUpdateEmployee(): UseMutationResult< + Employee | null, + Error, + UpdateEmployeeInput +> { + const navigate = useNavigate(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ id, values }: UpdateEmployeeInput) => + employeesApi.update(id, values as Employee), + onSuccess: (_data, variables) => { + void queryClient.invalidateQueries({ queryKey: queryKeys.employees.all }); + void queryClient.invalidateQueries({ queryKey: queryKeys.employees.detail(variables.id) }); + toast.success("Employee updated successfully!"); + navigate("/employees"); + }, + onError: (error: Error) => { + toast.error(error.message || "Failed to update employee"); + }, + }); +}