@@ -43,7 +43,7 @@ const CreateStaff: React.FC = async () => { | |||
const { t } = await getServerI18n("staff"); | |||
const title = ['', t('Additional Info')] | |||
// const regex = new RegExp("^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$") | |||
// const regex = new RegExp("^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$") | |||
// console.log(regex) | |||
const fieldLists = [ | |||
[ | |||
@@ -52,8 +52,6 @@ const CreateStaff: React.FC = async () => { | |||
label: t("Staff ID"), | |||
type: "text", | |||
value: "", | |||
pattern: "^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$", | |||
message: t("input matching format"), | |||
required: true, | |||
}, | |||
{ | |||
@@ -67,55 +65,55 @@ const CreateStaff: React.FC = async () => { | |||
id: "companyId", | |||
label: t("Company"), | |||
type: "combo-Obj", | |||
options: [{id: 1, key: 1, value: 1, label: "Company A"}, {id: 2, key: 2, value: 2, label: "Company B"}], | |||
options: [{id: 1, label: "Company A"}, {id: 2, label: "Company B"}], | |||
required: true, | |||
}, | |||
{ | |||
id: "teamId", | |||
label: t("Team"), | |||
type: "combo-Obj", | |||
options: [{id: 1, key: 1, value: 1, label: "A"}, {id: 2, key: 2, value: 2, label: "B"}], | |||
options: [{id: 1, label: "A"}, {id: 2, label: "B"}], | |||
required: true, | |||
}, | |||
{ | |||
id: "departmentId", | |||
label: t("Department"), | |||
type: "combo-Obj", | |||
options: [{id: 1, key: 1, value: 1, label: "Department A"}, {id: 2, key: 2, value: 2, label: "Department B"}], | |||
options: [{id: 1, label: "Department A"}, {id: 2, label: "Department B"}], | |||
required: true, | |||
}, | |||
{ | |||
id: "gradeId", | |||
label: t("Grade"), | |||
type: "combo-Obj", | |||
options: [{id: 1, key: 1, value: 1, label: "A"}, {id: 2, key: 2, value: 2, label: "B"}], | |||
options: [{id: 1, label: "A"}, {id: 2, label: "B"}], | |||
required: true, | |||
}, | |||
{ | |||
id: "skillSetId", | |||
label: t("Skillset"), | |||
type: "combo-Obj", | |||
options: [{id: 1, key: 1, value: 1, label: "excel"}, {id: 2, key: 2, value: 2, label: "word"}], | |||
options: [{id: 1, label: "excel"}, {id: 2, label: "word"}], | |||
required: true, | |||
}, | |||
{ | |||
id: "currentPositionId", | |||
label: t("Current Position"), | |||
type: "combo-Obj", | |||
options: [{id: 1, key: 1, value: 1, label: "pos1"}, {id: 2, key: 2, value: 2, label: "pos2"}], | |||
options: [{id: 1, label: "pos1"}, {id: 2, label: "pos2"}], | |||
required: true, | |||
}, | |||
{ | |||
id: "salaryEffId", | |||
label: t("Salary Point"), | |||
type: "combo-Obj", | |||
options: [{id: 1, key: 1, value: 1, label: t("15")}, {id: 2, key: 2, value: 2, label: t("20")}], | |||
options: [{id: 1, label: t("15")}, {id: 2, label: t("20")}], | |||
required: true, | |||
}, | |||
{ | |||
id: "hourlyRate", | |||
label: t("Hourly Rate"), | |||
type: "numeric", | |||
type: "numeric-testing", | |||
value: "", | |||
required: true, | |||
}, | |||
@@ -123,29 +121,35 @@ const CreateStaff: React.FC = async () => { | |||
id: "employType", | |||
label: t("Employ Type"), | |||
type: "combo-Obj", | |||
options: [{id: 1, key: "FT", value: "FT", label: t("FT")}, {id: 2, key: "PT", value: "PT", label: t("PT")}], | |||
options: [{id: "FT", label: t("FT")}, {id: "PT", label: t("PT")}], | |||
value: "", | |||
required: true, | |||
}, | |||
{ | |||
id: "email", | |||
label: t("Email"), | |||
type: "email", | |||
type: "text", | |||
value: "", | |||
pattern: "^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$", | |||
message: t("input matching format"), | |||
required: true, | |||
}, | |||
{ | |||
id: "phone1", | |||
label: t("Phone1"), | |||
type: "numeric", | |||
type: "text", | |||
value: "", | |||
pattern: "^\\d{8}$", | |||
message: t("input correct phone no."), | |||
required: true, | |||
}, | |||
{ | |||
id: "phone2", | |||
label: t("Phone2"), | |||
type: "numeric", | |||
type: "text", | |||
value: "", | |||
pattern: "^\\d{8}$", | |||
message: t("input correct phone no."), | |||
required: true, | |||
}, | |||
], | |||
@@ -160,8 +164,10 @@ const CreateStaff: React.FC = async () => { | |||
{ | |||
id: "emergContactPhone", | |||
label: t("Emergency Contact Phone"), | |||
type: "numeric", | |||
type: "text", | |||
value: "", | |||
pattern: "^\\d{8}$", | |||
message: t("input correct phone no."), | |||
required: true, | |||
}, | |||
{ |
@@ -0,0 +1,10 @@ | |||
const EditStaff: React.FC = async () => { | |||
return ( | |||
<> | |||
sdsadasd | |||
</> | |||
) | |||
} | |||
export default EditStaff; |
@@ -34,7 +34,7 @@ const Staff: React.FC = async () => { | |||
variant="contained" | |||
startIcon={<Add />} | |||
LinkComponent={Link} | |||
href="/staff/create" | |||
href="/settings/staff/create" | |||
> | |||
{t("Create Staff")} | |||
</Button> |
@@ -1,7 +1,7 @@ | |||
"use server"; | |||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
import { StaffResult } from "."; | |||
export interface CreateCustomInputs { | |||
// Project details | |||
projectCode: string; | |||
@@ -22,19 +22,28 @@ export interface CreateStaffInputs { | |||
email: string; | |||
phone1: string; | |||
phone2: string; | |||
hourlyRate: string | number; | |||
emergContactName: string; | |||
emergContactPhone: string; | |||
employType: string; | |||
joinDate: string | null; | |||
departDate: string | null; | |||
departReason: string | null; | |||
remark: string | null; | |||
} | |||
export const saveStaff = async (data: CreateStaffInputs) => { | |||
console.log(`${BASE_API_URL}/staffs/new`) | |||
return serverFetchJson(`${BASE_API_URL}/staffs/new`, { | |||
method: "POST", | |||
body: JSON.stringify(data), | |||
headers: { "Content-Type": "application/json" }, | |||
}); | |||
}; | |||
export const deleteStaff = async (id: number) => { | |||
return serverFetchJson(`${BASE_API_URL}/staffs/delete/${id}`, { | |||
method: "DELETE", | |||
// body: JSON.stringify(id), | |||
headers: { "Content-Type": "application/json" }, | |||
}); | |||
}; |
@@ -25,18 +25,11 @@ interface Field { | |||
} | |||
interface formProps { | |||
// onSubmit: (data: any) => void; | |||
// resetForm: () => void; | |||
// Title?: string[]; | |||
// isActive: boolean; | |||
Title?: string[] | |||
Title?: string[]; | |||
fieldLists: Field[][]; | |||
} | |||
const CreateStaffForm: React.FC<formProps> = ({ | |||
Title, | |||
fieldLists | |||
}) => { | |||
const CreateStaffForm: React.FC<formProps> = ({ Title, fieldLists }) => { | |||
const router = useRouter(); | |||
const { t } = useTranslation(); | |||
const [serverError, setServerError] = useState(""); | |||
@@ -48,16 +41,38 @@ const CreateStaffForm: React.FC<formProps> = ({ | |||
async (data) => { | |||
try { | |||
console.log(data); | |||
let haveError = false; | |||
//check if joinDate exist | |||
if (data.joinDate == null && data.joinDate == "Invalid Date") { | |||
haveError = true; | |||
return haveError; | |||
} | |||
//check if joinDate > departDate | |||
if (data.departDate != null && data.departDate != "Invalid Date") { | |||
if (data.joinDate != null) { | |||
const joinDate = new Date(data.joinDate); | |||
const departDate = new Date(data.departDate); | |||
if (joinDate.getTime() > departDate.getTime()) { | |||
haveError = true; | |||
return haveError; | |||
} | |||
} | |||
} | |||
if (haveError) { | |||
return | |||
} | |||
const postData = { | |||
...data, | |||
emergContactPhone: data.emergContactPhone.toString(), | |||
phone1: data.phone1.toString(), | |||
phone2: data.phone2.toString(), | |||
} | |||
hourlyRate: typeof data.hourlyRate === 'string' ? parseInt(data.hourlyRate.replace("$", "").replace(",", "")) : 0 | |||
}; | |||
console.log(postData); | |||
setServerError(""); | |||
await saveStaff(postData); | |||
router.replace("/staff"); | |||
router.replace("/settings/staff"); | |||
} catch (e) { | |||
setServerError(t("An error has occurred. Please try again later.")); | |||
} | |||
@@ -67,10 +82,10 @@ const CreateStaffForm: React.FC<formProps> = ({ | |||
const onSubmitError = useCallback<SubmitErrorHandler<CreateStaffInputs>>( | |||
(errors) => { | |||
console.log(errors) | |||
console.log(errors); | |||
}, | |||
[], | |||
); | |||
[] | |||
); | |||
return ( | |||
<> | |||
@@ -29,6 +29,8 @@ import { DatePicker } from "@mui/x-date-pickers/DatePicker"; | |||
import dayjs from "dayjs"; | |||
import { useCallback, useEffect, useState } from "react"; | |||
import { Check, Close } from "@mui/icons-material"; | |||
import { NumericFormat, NumericFormatProps } from "react-number-format"; | |||
import * as React from "react"; | |||
// interface Option { | |||
// // Define properties of each option object | |||
@@ -52,7 +54,10 @@ interface Field { | |||
size?: number; | |||
setValue?: any[]; | |||
} | |||
interface CustomProps { | |||
onChange: (event: { target: { name: string; value: string } }) => void; | |||
name: string; | |||
} | |||
interface CustomInputFormProps { | |||
onSubmit: (data: any) => void; | |||
onSubmitError?: (data: any) => void; | |||
@@ -77,7 +82,13 @@ const CustomInputForm: React.FC<CustomInputFormProps> = ({ | |||
// resetForm, | |||
}) => { | |||
const { t } = useTranslation(); | |||
const { reset, register, handleSubmit, control, formState: { errors } } = useForm(); | |||
const { | |||
reset, | |||
register, | |||
handleSubmit, | |||
control, | |||
formState: { errors }, | |||
} = useForm(); | |||
const [dateObj, setDateObj] = useState<any>(null); | |||
const [value, setValue] = useState<any>({}); | |||
const [checkboxValue, setCheckboxValue] = useState({}); | |||
@@ -105,7 +116,7 @@ const CustomInputForm: React.FC<CustomInputFormProps> = ({ | |||
if (checkboxValue !== null) { | |||
data = { ...data, ...checkboxValue }; | |||
} | |||
const finalData = { | |||
...value, | |||
...data, | |||
@@ -176,6 +187,39 @@ const CustomInputForm: React.FC<CustomInputFormProps> = ({ | |||
}); | |||
}); | |||
const NumericFormatCustom = React.forwardRef<NumericFormatProps, CustomProps>( | |||
function NumericFormatCustom(props, ref) { | |||
const { onChange, ...other } = props; | |||
return ( | |||
<NumericFormat | |||
{...other} | |||
getInputRef={ref} | |||
onValueChange={(values) => { | |||
onChange({ | |||
target: { | |||
name: props.name, | |||
value: values.value, | |||
}, | |||
}); | |||
}} | |||
thousandSeparator | |||
valueIsNumericString | |||
prefix="$" | |||
/> | |||
); | |||
} | |||
); | |||
const [values, setValues] = React.useState({ | |||
hourlyRate: "", | |||
}); | |||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
setValues({ | |||
...values, | |||
[event.target.name]: event.target.value, | |||
}); | |||
}; | |||
return ( | |||
<form onSubmit={handleSubmit(handleFormSubmit, onSubmitError)}> | |||
<Card sx={{ display: isActive ? "block" : "none" }}> | |||
@@ -201,12 +245,16 @@ const CustomInputForm: React.FC<CustomInputFormProps> = ({ | |||
label={field.label} | |||
fullWidth | |||
{...register(field.id, { | |||
pattern: field.pattern ? new RegExp(field.pattern) : /.*/, | |||
pattern: field.pattern | |||
? new RegExp(field.pattern) | |||
: /.*/, | |||
})} | |||
defaultValue={!field.value ? `${field.value}` : ""} | |||
required={field.required ?? false} | |||
error={Boolean(errors[field.id])} | |||
helperText={Boolean(errors[field.id]) && field.message} | |||
helperText={ | |||
Boolean(errors[field.id]) && field.message | |||
} | |||
/> | |||
</Grid> | |||
); | |||
@@ -217,16 +265,19 @@ const CustomInputForm: React.FC<CustomInputFormProps> = ({ | |||
label={field.label} | |||
fullWidth | |||
{...register(field.id, { | |||
pattern: /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/, | |||
pattern: | |||
/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/, | |||
})} | |||
defaultValue={!field.value ? `${field.value}` : ""} | |||
required={field.required ?? false} | |||
error={Boolean(errors[field.id])} | |||
helperText={Boolean(errors[field.id]) && field.message} | |||
helperText={ | |||
Boolean(errors[field.id]) && field.message | |||
} | |||
/> | |||
</Grid> | |||
); | |||
}else if (field.type === "multiDate") { | |||
} else if (field.type === "multiDate") { | |||
return ( | |||
<Grid item xs={field.size ?? 6} key={field.id}> | |||
<LocalizationProvider dateAdapter={AdapterDayjs}> | |||
@@ -324,6 +375,34 @@ const CustomInputForm: React.FC<CustomInputFormProps> = ({ | |||
/> | |||
</Grid> | |||
); | |||
} else if (field.type === "numeric-testing") { | |||
return ( | |||
<Grid item xs={field.size ?? 6} key={field.id}> | |||
<FormControl fullWidth> | |||
<Controller | |||
{...register(field.id)} | |||
name={field.id} | |||
control={control} | |||
defaultValue={ | |||
!field.value ? `${field.value}` : "" | |||
} | |||
render={({ field }) => { | |||
console.log(field); | |||
return ( | |||
<NumericFormat | |||
{...field} | |||
customInput={TextField} | |||
thousandSeparator | |||
valueIsNumericString | |||
prefix="$" | |||
label={t(field.name)} | |||
/> | |||
); | |||
}} | |||
/> | |||
</FormControl> | |||
</Grid> | |||
); | |||
} else if (field.type === "numeric-positive") { | |||
return ( | |||
<Grid item xs={field.size ?? 6} key={field.id}> | |||
@@ -331,6 +410,7 @@ const CustomInputForm: React.FC<CustomInputFormProps> = ({ | |||
fullWidth | |||
{...register(field.id)} | |||
id={field.id} | |||
name={field.id} | |||
label={field.label} | |||
defaultValue={!field.value ? `${field.value}` : ""} | |||
inputProps={{ | |||
@@ -0,0 +1,106 @@ | |||
"use client"; | |||
import React, { useCallback, useMemo, useState } from "react"; | |||
import Button from "@mui/material/Button"; | |||
import { Card, Modal, Stack, Typography } from "@mui/material"; | |||
import { useTranslation } from "react-i18next"; | |||
import { Add } from "@mui/icons-material"; | |||
import Check from "@mui/icons-material/Check"; | |||
import Close from "@mui/icons-material/Close"; | |||
import { TSMS_BUTTON_THEME } from "@/theme/colorConst"; | |||
import { ThemeProvider } from "@emotion/react"; | |||
interface Props { | |||
isOpen: boolean; | |||
onConfirm: (data: any) => void; | |||
onCancel: (data: any | null) => void; | |||
// staff: StaffResult[]; | |||
} | |||
const ConfirmModal: React.FC<Props> = ({ ...props }) => { | |||
const { t } = useTranslation(); | |||
return ( | |||
<> | |||
<Modal open={props.isOpen} onClose={props.onCancel}> | |||
<Card | |||
style={{ | |||
flex: 10, | |||
marginBottom: "20px", | |||
width: "auto", | |||
minWidth: "400px", | |||
minHeight: "200px", | |||
position: "fixed", | |||
top: "50%", | |||
left: "50%", | |||
transform: "translate(-50%, -50%)", | |||
}} | |||
> | |||
<> | |||
<Typography | |||
variant="h5" | |||
id="modal-title" | |||
sx={{ | |||
flex: 1, | |||
ml: 4, | |||
mt: 2, | |||
}} | |||
> | |||
{t("Confirm")} | |||
</Typography> | |||
<> | |||
<Typography | |||
variant="h6" | |||
id="modal-title" | |||
sx={{ | |||
flex: 1, | |||
mt: 4, | |||
justifyContent: "center", | |||
textAlign: "center", | |||
}} | |||
> | |||
{t("Are You Sure")} | |||
</Typography> | |||
</> | |||
{/* <ThemeProvider theme={TSMS_BUTTON_THEME}> */} | |||
<Stack direction="row"> | |||
<Button | |||
variant="contained" | |||
endIcon={<Check />} | |||
sx={{ | |||
flex: 1, | |||
ml: 5, | |||
mr: 2, | |||
mt: 4, | |||
justifyContent: "space-between", | |||
}} | |||
onClick={props.onConfirm} | |||
// LinkComponent={Link} | |||
// href="/settings/department/new" | |||
> | |||
Proceed | |||
</Button> | |||
<Button | |||
variant="contained" | |||
startIcon={<Close />} | |||
sx={{ | |||
flex: 1, | |||
mr: 5, | |||
mt: 4, | |||
justifyContent: "space-between", | |||
}} | |||
color="warning" | |||
onClick={props.onCancel} | |||
// LinkComponent={Link} | |||
// href="/settings/department/new" | |||
> | |||
Cancel | |||
</Button> | |||
</Stack> | |||
{/* </ThemeProvider> */} | |||
</> | |||
</Card> | |||
</Modal> | |||
</> | |||
); | |||
}; | |||
export default ConfirmModal; |
@@ -1,11 +1,14 @@ | |||
"use client"; | |||
import { StaffResult } from "@/app/api/staff"; | |||
import React, { useCallback, useMemo, useState } from "react"; | |||
import React, { useCallback, useEffect, useMemo, useState } from "react"; | |||
import SearchBox, { Criterion } from "../SearchBox/index"; | |||
import { useTranslation } from "react-i18next"; | |||
import SearchResults, { Column } from "../SearchResults/index"; | |||
import EditNote from "@mui/icons-material/EditNote"; | |||
import DeleteIcon from '@mui/icons-material/Delete'; | |||
import ConfirmModal from "./ConfirmDeleteModal"; | |||
import { deleteStaff } from "@/app/api/staff/actions"; | |||
interface Props { | |||
staff: StaffResult[]; | |||
@@ -16,10 +19,9 @@ type SearchParamNames = keyof SearchQuery; | |||
const StaffSearch: React.FC<Props> = ({ staff }) => { | |||
const { t } = useTranslation(); | |||
// If claim searching is done on the server-side, then no need for this. | |||
const [filteredStaff, setFilteredStaff] = useState(staff); | |||
// const [filteredStaffRef, setFilteredStaffRef] = useState(staff); | |||
const [id, setId] = useState(0); | |||
const [isOpen, setIsOpen] = useState(false); | |||
const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||
() => [ | |||
@@ -59,6 +61,30 @@ const StaffSearch: React.FC<Props> = ({ staff }) => { | |||
console.log(staff); | |||
}, []); | |||
const deleteClick = (staff: StaffResult) => { | |||
console.log(staff.id); | |||
const temp = staff.id | |||
console.log(temp) | |||
setId(temp) | |||
setIsOpen(!isOpen) | |||
}; | |||
const onConfirm = (staff: StaffResult) => { | |||
console.log(staff); | |||
console.log(id); | |||
deleteStaff(id) | |||
// setIsOpen(!isOpen) | |||
} | |||
const onCancel = useCallback((staff: StaffResult) => { | |||
console.log(staff); | |||
// setId(0) | |||
setIsOpen(false) | |||
}, []); | |||
useEffect(() => { | |||
console.log("id"); | |||
console.log(id); | |||
}, [id]); | |||
const columns = useMemo<Column<StaffResult>[]>( | |||
() => [ | |||
{ | |||
@@ -72,8 +98,14 @@ const StaffSearch: React.FC<Props> = ({ staff }) => { | |||
{ name: "staffId", label: t("Staff ID") }, | |||
{ name: "grade", label: t("Grade") }, | |||
{ name: "currentPosition", label: t("Current Position") }, | |||
{ | |||
name: "action", | |||
label: t("Actions"), | |||
onClick: deleteClick, | |||
buttonIcon: <DeleteIcon />, | |||
}, | |||
], | |||
[t, onStaffClick], | |||
[t, onStaffClick, deleteClick], | |||
); | |||
return ( | |||
@@ -94,6 +126,11 @@ const StaffSearch: React.FC<Props> = ({ staff }) => { | |||
}} | |||
/> | |||
<SearchResults<StaffResult> items={filteredStaff} columns={columns} /> | |||
<ConfirmModal | |||
isOpen={isOpen} | |||
onConfirm={onConfirm} | |||
onCancel={onCancel} | |||
/> | |||
</> | |||
); | |||
}; | |||