From 31ea0f6c8601e94033607672fb82325c0bd82972 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Fri, 5 Apr 2024 14:57:34 +0800 Subject: [PATCH] staff update markDelete --- .../{ => settings}/staff/create/page.tsx | 38 ++++--- src/app/(main)/settings/staff/edit/page.tsx | 10 ++ src/app/(main)/{ => settings}/staff/page.tsx | 2 +- src/app/api/staff/actions.ts | 13 ++- .../CreateStaff/CreateStaffForm.tsx | 43 ++++--- .../CustomInputForm/CustomInputForm.tsx | 96 ++++++++++++++-- .../StaffSearch/ConfirmDeleteModal.tsx | 106 ++++++++++++++++++ src/components/StaffSearch/StaffSearch.tsx | 47 +++++++- 8 files changed, 309 insertions(+), 46 deletions(-) rename src/app/(main)/{ => settings}/staff/create/page.tsx (82%) create mode 100644 src/app/(main)/settings/staff/edit/page.tsx rename src/app/(main)/{ => settings}/staff/page.tsx (97%) create mode 100644 src/components/StaffSearch/ConfirmDeleteModal.tsx diff --git a/src/app/(main)/staff/create/page.tsx b/src/app/(main)/settings/staff/create/page.tsx similarity index 82% rename from src/app/(main)/staff/create/page.tsx rename to src/app/(main)/settings/staff/create/page.tsx index 4e98717..5c45e53 100644 --- a/src/app/(main)/staff/create/page.tsx +++ b/src/app/(main)/settings/staff/create/page.tsx @@ -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, }, { diff --git a/src/app/(main)/settings/staff/edit/page.tsx b/src/app/(main)/settings/staff/edit/page.tsx new file mode 100644 index 0000000..4775493 --- /dev/null +++ b/src/app/(main)/settings/staff/edit/page.tsx @@ -0,0 +1,10 @@ +const EditStaff: React.FC = async () => { + + return ( + <> + sdsadasd + + ) +} + +export default EditStaff; \ No newline at end of file diff --git a/src/app/(main)/staff/page.tsx b/src/app/(main)/settings/staff/page.tsx similarity index 97% rename from src/app/(main)/staff/page.tsx rename to src/app/(main)/settings/staff/page.tsx index aae3984..ba790f6 100644 --- a/src/app/(main)/staff/page.tsx +++ b/src/app/(main)/settings/staff/page.tsx @@ -34,7 +34,7 @@ const Staff: React.FC = async () => { variant="contained" startIcon={} LinkComponent={Link} - href="/staff/create" + href="/settings/staff/create" > {t("Create Staff")} diff --git a/src/app/api/staff/actions.ts b/src/app/api/staff/actions.ts index 7d11a1f..31cfc3d 100644 --- a/src/app/api/staff/actions.ts +++ b/src/app/api/staff/actions.ts @@ -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" }, + }); }; \ No newline at end of file diff --git a/src/components/CreateStaff/CreateStaffForm.tsx b/src/components/CreateStaff/CreateStaffForm.tsx index 361a462..555ca86 100644 --- a/src/components/CreateStaff/CreateStaffForm.tsx +++ b/src/components/CreateStaff/CreateStaffForm.tsx @@ -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 = ({ - Title, - fieldLists -}) => { +const CreateStaffForm: React.FC = ({ Title, fieldLists }) => { const router = useRouter(); const { t } = useTranslation(); const [serverError, setServerError] = useState(""); @@ -48,16 +41,38 @@ const CreateStaffForm: React.FC = ({ 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 = ({ const onSubmitError = useCallback>( (errors) => { - console.log(errors) + console.log(errors); }, - [], -); + [] + ); return ( <> diff --git a/src/components/CustomInputForm/CustomInputForm.tsx b/src/components/CustomInputForm/CustomInputForm.tsx index 41c10e1..8f5d48a 100644 --- a/src/components/CustomInputForm/CustomInputForm.tsx +++ b/src/components/CustomInputForm/CustomInputForm.tsx @@ -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 = ({ // resetForm, }) => { const { t } = useTranslation(); - const { reset, register, handleSubmit, control, formState: { errors } } = useForm(); + const { + reset, + register, + handleSubmit, + control, + formState: { errors }, + } = useForm(); const [dateObj, setDateObj] = useState(null); const [value, setValue] = useState({}); const [checkboxValue, setCheckboxValue] = useState({}); @@ -105,7 +116,7 @@ const CustomInputForm: React.FC = ({ if (checkboxValue !== null) { data = { ...data, ...checkboxValue }; } - + const finalData = { ...value, ...data, @@ -176,6 +187,39 @@ const CustomInputForm: React.FC = ({ }); }); + const NumericFormatCustom = React.forwardRef( + function NumericFormatCustom(props, ref) { + const { onChange, ...other } = props; + + return ( + { + onChange({ + target: { + name: props.name, + value: values.value, + }, + }); + }} + thousandSeparator + valueIsNumericString + prefix="$" + /> + ); + } + ); + const [values, setValues] = React.useState({ + hourlyRate: "", + }); + const handleChange = (event: React.ChangeEvent) => { + setValues({ + ...values, + [event.target.name]: event.target.value, + }); + }; + return (
@@ -201,12 +245,16 @@ const CustomInputForm: React.FC = ({ 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 + } /> ); @@ -217,16 +265,19 @@ const CustomInputForm: React.FC = ({ 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 + } /> ); - }else if (field.type === "multiDate") { + } else if (field.type === "multiDate") { return ( @@ -324,6 +375,34 @@ const CustomInputForm: React.FC = ({ /> ); + } else if (field.type === "numeric-testing") { + return ( + + + { + console.log(field); + return ( + + ); + }} + /> + + + ); } else if (field.type === "numeric-positive") { return ( @@ -331,6 +410,7 @@ const CustomInputForm: React.FC = ({ fullWidth {...register(field.id)} id={field.id} + name={field.id} label={field.label} defaultValue={!field.value ? `${field.value}` : ""} inputProps={{ diff --git a/src/components/StaffSearch/ConfirmDeleteModal.tsx b/src/components/StaffSearch/ConfirmDeleteModal.tsx new file mode 100644 index 0000000..abeb962 --- /dev/null +++ b/src/components/StaffSearch/ConfirmDeleteModal.tsx @@ -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 }) => { + const { t } = useTranslation(); + return ( + <> + + + <> + + {t("Confirm")} + + <> + + {t("Are You Sure")} + + + {/* */} + + + + + {/* */} + + + + + ); +}; + +export default ConfirmModal; diff --git a/src/components/StaffSearch/StaffSearch.tsx b/src/components/StaffSearch/StaffSearch.tsx index ae3c26e..351590b 100644 --- a/src/components/StaffSearch/StaffSearch.tsx +++ b/src/components/StaffSearch/StaffSearch.tsx @@ -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 = ({ 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[] = useMemo( () => [ @@ -59,6 +61,30 @@ const StaffSearch: React.FC = ({ 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[]>( () => [ { @@ -72,8 +98,14 @@ const StaffSearch: React.FC = ({ 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: , + }, ], - [t, onStaffClick], + [t, onStaffClick, deleteClick], ); return ( @@ -94,6 +126,11 @@ const StaffSearch: React.FC = ({ staff }) => { }} /> items={filteredStaff} columns={columns} /> + ); };