Bladeren bron

staff update markDelete

tags/Baseline_30082024_FRONTEND_UAT
MSI\derek 1 jaar geleden
bovenliggende
commit
31ea0f6c86
8 gewijzigde bestanden met toevoegingen van 309 en 46 verwijderingen
  1. +22
    -16
      src/app/(main)/settings/staff/create/page.tsx
  2. +10
    -0
      src/app/(main)/settings/staff/edit/page.tsx
  3. +1
    -1
      src/app/(main)/settings/staff/page.tsx
  4. +11
    -2
      src/app/api/staff/actions.ts
  5. +29
    -14
      src/components/CreateStaff/CreateStaffForm.tsx
  6. +88
    -8
      src/components/CustomInputForm/CustomInputForm.tsx
  7. +106
    -0
      src/components/StaffSearch/ConfirmDeleteModal.tsx
  8. +42
    -5
      src/components/StaffSearch/StaffSearch.tsx

src/app/(main)/staff/create/page.tsx → src/app/(main)/settings/staff/create/page.tsx Bestand weergeven

@@ -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,
},
{

+ 10
- 0
src/app/(main)/settings/staff/edit/page.tsx Bestand weergeven

@@ -0,0 +1,10 @@
const EditStaff: React.FC = async () => {

return (
<>
sdsadasd
</>
)
}

export default EditStaff;

src/app/(main)/staff/page.tsx → src/app/(main)/settings/staff/page.tsx Bestand weergeven

@@ -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>

+ 11
- 2
src/app/api/staff/actions.ts Bestand weergeven

@@ -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" },
});
};

+ 29
- 14
src/components/CreateStaff/CreateStaffForm.tsx Bestand weergeven

@@ -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 (
<>


+ 88
- 8
src/components/CustomInputForm/CustomInputForm.tsx Bestand weergeven

@@ -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={{


+ 106
- 0
src/components/StaffSearch/ConfirmDeleteModal.tsx Bestand weergeven

@@ -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;

+ 42
- 5
src/components/StaffSearch/StaffSearch.tsx Bestand weergeven

@@ -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}
/>
</>
);
};


Laden…
Annuleren
Opslaan