Skip to content

Commit

Permalink
HRIS-250 [FE] Integrate Add New Employee Functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
acatzk committed Jul 7, 2023
1 parent 30576e0 commit 6f0421c
Show file tree
Hide file tree
Showing 13 changed files with 261 additions and 42 deletions.
10 changes: 10 additions & 0 deletions api/Schema/Queries/UserQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,15 @@ public async Task<List<User>> GetAllESLUsers([Service] UserService _userService,
{
return await _userService.ESLUsers(exceptUserId);
}

public async Task<List<Position>> GetAllPositions()
{
return await _userService.GetAllPositions();
}

public async Task<List<Role>> GetAllRoles()
{
return await _userService.GetAllRoles();
}
}
}
16 changes: 16 additions & 0 deletions api/Services/UserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,21 @@ public async Task<List<User>> ESLUsers(int? exceptUserId)
.ToListAsync();
}
}

public async Task<List<Position>> GetAllPositions()
{
using (HrisContext context = _contextFactory.CreateDbContext())
{
return await context.Positions.ToListAsync();
}
}

public async Task<List<Role>> GetAllRoles()
{
using (HrisContext context = _contextFactory.CreateDbContext())
{
return await context.Roles.ToListAsync();
}
}
}
}
130 changes: 111 additions & 19 deletions client/src/components/molecules/AddNewEmployeeModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import { useForm } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
import toast from 'react-hot-toast'
import isEmpty from 'lodash/isEmpty'
import ReactSelect from 'react-select'
import React, { FC, useEffect } from 'react'
import { User, Save, Mail, Award, RefreshCcw, X } from 'react-feather'
import { yupResolver } from '@hookform/resolvers/yup'
import { User, Save, Mail, RefreshCcw, X } from 'react-feather'
import { Controller, SubmitHandler, useForm } from 'react-hook-form'

import TextField from './../TextField'
import Input from '~/components/atoms/Input'
import useEmployee from '~/hooks/useEmployee'
import { queryClient } from '~/lib/queryClient'
import useUserQuery from '~/hooks/useUserQuery'
import SpinnerIcon from '~/utils/icons/SpinnerIcon'
import { NewEmployeeSchema } from '~/utils/validation'
import Button from '~/components/atoms/Buttons/ButtonAction'
import { customStyles } from '~/utils/customReactSelectStyles'
import ModalTemplate from '~/components/templates/ModalTemplate'
import { NewEmployeeFormValues } from '~/utils/types/formValues'
import { IEmployeeInput } from '~/utils/interfaces/employeeInterface'
import ModalHeader from '~/components/templates/ModalTemplate/ModalHeader'
import ModalFooter from '~/components/templates/ModalTemplate/ModalFooter'

Expand All @@ -20,8 +27,18 @@ type Props = {
}

const AddNewEmployeeModal: FC<Props> = ({ isOpen, closeModal }): JSX.Element => {
// USER HOOKS -> Get Positions, Get Roles
const { getAllPositionQuery, getAllRoleQuery } = useUserQuery()
const positionData = getAllPositionQuery()
const roleData = getAllRoleQuery()

// EMPLOYEE HOOKS
const { handleAddNewEmployeeMutation } = useEmployee()
const addEmployeeMutation = handleAddNewEmployeeMutation()

const {
reset,
control,
register,
handleSubmit,
formState: { errors, isSubmitting }
Expand All @@ -31,12 +48,27 @@ const AddNewEmployeeModal: FC<Props> = ({ isOpen, closeModal }): JSX.Element =>
})

// This will handle Submit and Save New Overtime
const handleSave = async (data: NewEmployeeFormValues): Promise<void> => {
const handleSave: SubmitHandler<NewEmployeeFormValues> = async (data): Promise<void> => {
return await new Promise((resolve) => {
setTimeout(() => {
alert(JSON.stringify({ ...data }, null, 2))
resolve()
}, 2000)
const request: IEmployeeInput = {
email: data.email,
firstName: data.first_name,
middleName: data.middle_name ?? '',
lastName: data.last_name,
positionId: parseInt(data.position.value),
roleId: parseInt(data.role.value)
}

addEmployeeMutation.mutate(request, {
onSuccess: () => {
// TODO: Please Specify the query key of all Employee
void queryClient.invalidateQueries().then(() => {
toast.success('Added New Employee Successfully!')
resolve()
closeModal()
})
}
})
})
}

Expand All @@ -58,9 +90,14 @@ const AddNewEmployeeModal: FC<Props> = ({ isOpen, closeModal }): JSX.Element =>
}, [isOpen])

const handleReset = (): void => {
const emptySelect = {
value: '',
label: ''
}
reset({
email: '',
position: '',
position: emptySelect,
role: emptySelect,
first_name: '',
middle_name: '',
last_name: ''
Expand Down Expand Up @@ -107,18 +144,73 @@ const AddNewEmployeeModal: FC<Props> = ({ isOpen, closeModal }): JSX.Element =>

{/* Position */}
<section className="col-span-2 overflow-visible">
<TextField title="Position" Icon={Award} isRequired className="flex-1">
<Input
type="text"
disabled={isSubmitting}
placeholder=""
{...register('position')}
className="py-2.5 pl-11 text-xs"
iserror={errors.position !== null && errors?.position !== undefined}
<TextField title="Position" Icon={Mail} isRequired className="flex-1">
<Controller
name="position"
control={control}
rules={{ required: true }}
render={({ field }) => (
<ReactSelect
{...field}
isClearable
placeholder=""
className="w-full"
styles={customStyles}
classNames={{
control: (state) =>
state.isFocused
? 'border-primary'
: !isEmpty(errors.position)
? 'border-rose-500 ring-rose-500'
: 'border-slate-300'
}}
value={field.value}
onChange={field.onChange}
isLoading={positionData.isLoading}
isDisabled={isSubmitting || positionData.isLoading}
options={positionData.data}
/>
)}
/>
</TextField>
{errors.position !== null && errors.position !== undefined && (
<span className="error text-[10px]">Position is required</span>
)}
</section>

{/* Role */}
<section className="col-span-2 overflow-visible">
<TextField title="Role" isRequired className="flex-1">
<Controller
name="role"
control={control}
rules={{ required: true }}
render={({ field }) => (
<ReactSelect
{...field}
isClearable
placeholder=""
className="w-full"
styles={customStyles}
classNames={{
control: (state) =>
state.isFocused
? 'border-primary'
: !isEmpty(errors.role)
? 'border-rose-500 ring-rose-500'
: 'border-slate-300'
}}
value={field.value}
onChange={field.onChange}
isLoading={roleData.isLoading}
isDisabled={isSubmitting || roleData.isLoading}
options={roleData.data}
/>
)}
/>
</TextField>
{errors?.position !== null && errors?.position !== undefined && (
<span className="error text-[10px]">{errors.position?.message}</span>
{errors.role !== null && errors.role !== undefined && (
<span className="error text-[10px]">Role is required</span>
)}
</section>

Expand Down
2 changes: 1 addition & 1 deletion client/src/components/molecules/TextField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const TextField: FC<Props> = (props): JSX.Element => {
return (
<label className={classNames('group space-y-0.5', className)}>
{title} {isRequired === true ? <span className="text-rose-500">*</span> : ''}{' '}
{isOptional === true ? <span className="italic text-slate-500">({optionalText})</span> : ''}
{isOptional === true ? <small className="italic text-slate-500">({optionalText})</small> : ''}
<div className="relative flex items-center">
<div
className={classNames(
Expand Down
7 changes: 7 additions & 0 deletions client/src/graphql/mutations/employeeMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { gql } from 'graphql-request'

export const CREATE_NEW_EMPLOYEE_MUTATION = gql`
mutation ($request: AddNewEmployeeRequestInput!) {
addNewEmployee(request: $request)
}
`
10 changes: 10 additions & 0 deletions client/src/graphql/queries/positionQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { gql } from 'graphql-request'

export const GET_ALL_POSITION_QUERY = gql`
query {
allPositions {
id
name
}
}
`
10 changes: 10 additions & 0 deletions client/src/graphql/queries/roleQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { gql } from 'graphql-request'

export const GET_ALL_ROLE_QUERY = gql`
query {
allRoles {
id
name
}
}
`
31 changes: 31 additions & 0 deletions client/src/hooks/useEmployee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import toast from 'react-hot-toast'
import { useMutation, UseMutationResult } from '@tanstack/react-query'

import { client } from '~/utils/shared/client'
import { IEmployeeInput } from '~/utils/interfaces/employeeInterface'
import { CREATE_NEW_EMPLOYEE_MUTATION } from '~/graphql/mutations/employeeMutation'

type EmployeeFuncReturnType = UseMutationResult<any, Error, IEmployeeInput, unknown>

type HookReturnType = {
handleAddNewEmployeeMutation: () => UseMutationResult<any, Error, IEmployeeInput, unknown>
}

const useEmployee = (): HookReturnType => {
const handleAddNewEmployeeMutation = (): EmployeeFuncReturnType =>
useMutation({
mutationFn: async (request: IEmployeeInput) => {
return await client.request(CREATE_NEW_EMPLOYEE_MUTATION, { request })
},
onError: async (err: Error) => {
const [errorMessage] = err.message.split(/:\s/, 2)
toast.error(errorMessage)
}
})

return {
handleAddNewEmployeeMutation
}
}

export default useEmployee
Loading

0 comments on commit 6f0421c

Please sign in to comment.