@@ -28,18 +28,6 @@ import { ProjectCategory } from "@/app/api/projects"; | |||
import { Grid, Typography } from "@mui/material"; | |||
import CreateStaffForm from "@/components/CreateStaff/CreateStaffForm"; | |||
// import { Metadata } from "next"; | |||
// export const metadata: Metadata = { | |||
// title: "staffCreate", | |||
// }; | |||
// export interface Props { | |||
// allTasks: Task[]; | |||
// projectCategories: ProjectCategory[]; | |||
// taskTemplates: TaskTemplate[]; | |||
// teamLeads: Staff[]; | |||
// } | |||
interface CreateCustomInputs { | |||
projectCode: string; | |||
projectName: string; | |||
@@ -52,65 +40,84 @@ const createCustomInputs: CreateCustomInputs = { | |||
// const Title = ["title1", "title2"]; | |||
const CreateStaff: React.FC = async () => { | |||
const { t } = await getServerI18n("createStaff"); | |||
const title = ['', 'Additional Info'] | |||
const { t } = await getServerI18n("staff"); | |||
const title = ['', t('Additional Info')] | |||
// const regex = new RegExp("^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$") | |||
// console.log(regex) | |||
const fieldLists = [ | |||
[ | |||
{ | |||
id: "name", | |||
label: t("Staff Id"), | |||
id: "staffId", | |||
label: t("Staff ID"), | |||
type: "text", | |||
value: "asdasd", | |||
// required: "asdasd", | |||
// option: "asdasd", | |||
value: "", | |||
pattern: "^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$", | |||
message: t("input matching format"), | |||
required: true, | |||
}, | |||
{ | |||
id: "name", | |||
label: t("Staff Name"), | |||
type: "text", | |||
value: "asdasd", | |||
// required: "asdasd", | |||
// option: "asdasd", | |||
required: true, | |||
}, | |||
{ | |||
id: "companyId", | |||
label: t("Company"), | |||
type: "combo-Obj", | |||
// value: "asdasd", | |||
// required: "asdasd", | |||
options: [{id: 1, key: 1, value: 1, label: "Company A"}, {id: 2, key: 2, value: 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"}], | |||
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"}], | |||
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"}], | |||
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"}], | |||
required: true, | |||
}, | |||
{ | |||
id: "currentPosition", | |||
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"}], | |||
required: true, | |||
}, | |||
{ | |||
id: "salaryEffId", | |||
label: t("Salary point ID with effective date"), | |||
label: t("Salary Point"), | |||
type: "combo-Obj", | |||
options: [{id: 1, key: 1, value: 1, label: t("first")}, {id: 2, key: 2, value: 2, label: t("second")}], | |||
options: [{id: 1, key: 1, value: 1, label: t("15")}, {id: 2, key: 2, value: 2, label: t("20")}], | |||
required: true, | |||
}, | |||
{ | |||
id: "hourlyRate", | |||
label: t("Hourly Rate"), | |||
type: "numeric", | |||
value: "", | |||
required: true, | |||
}, | |||
{ | |||
id: "employType", | |||
@@ -118,25 +125,29 @@ const CreateStaff: React.FC = async () => { | |||
type: "combo-Obj", | |||
options: [{id: 1, key: "FT", value: "FT", label: t("FT")}, {id: 2, key: "PT", value: "PT", label: t("PT")}], | |||
value: "", | |||
required: true, | |||
}, | |||
{ | |||
id: "email", | |||
label: t("Email"), | |||
type: "text", | |||
type: "email", | |||
value: "", | |||
required: true, | |||
}, | |||
{ | |||
id: "phone1", | |||
label: t("Phone1"), | |||
type: "numeric", | |||
value: "", | |||
required: true, | |||
}, | |||
{ | |||
id: "phone2", | |||
label: t("Phone2"), | |||
type: "numeric", | |||
value: "", | |||
}, | |||
required: true, | |||
}, | |||
], | |||
[ | |||
{ | |||
@@ -144,24 +155,28 @@ const CreateStaff: React.FC = async () => { | |||
label: t("Emergency Contact Name"), | |||
type: "text", | |||
value: "", | |||
required: true, | |||
}, | |||
{ | |||
id: "emergContactPhone", | |||
label: t("Emergency Contact Phone"), | |||
type: "numeric", | |||
value: "", | |||
required: true, | |||
}, | |||
{ | |||
id: "joinDate", | |||
label: t("Join Date"), | |||
type: "multiDate", | |||
value: "", | |||
required: true, | |||
}, | |||
{ | |||
id: "joinPosition", | |||
id: "joinPositionId", | |||
label: t("Join Position"), | |||
type: "combo-Obj", | |||
options: [{id: 1, key: 1, value: 1, label: "pos1"}, {id: 2, key: 2, value: 2, label: "pos2"}], | |||
required: true, | |||
}, | |||
{ | |||
id: "departDate", | |||
@@ -171,7 +186,7 @@ const CreateStaff: React.FC = async () => { | |||
}, | |||
{ | |||
id: "departReason", | |||
label: t("Reason"), | |||
label: t("Depart Reason"), | |||
type: "text", | |||
value: "", | |||
}, | |||
@@ -187,7 +202,7 @@ const CreateStaff: React.FC = async () => { | |||
return ( | |||
<> | |||
<Typography variant="h4">{t("Create Staff")}</Typography> | |||
<I18nProvider namespaces={["createStaff"]}> | |||
<I18nProvider namespaces={["staff"]}> | |||
<CreateStaffForm | |||
Title={title} | |||
fieldLists={fieldLists} | |||
@@ -2,7 +2,7 @@ | |||
import { preloadClaims } from "@/app/api/claims"; | |||
import { preloadStaff, preloadTeamLeads } from "@/app/api/staff"; | |||
import StaffSearch from "@/components/StaffSearch"; | |||
import { getServerI18n } from "@/i18n"; | |||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||
import Add from "@mui/icons-material/Add"; | |||
import Button from "@mui/material/Button"; | |||
import Stack from "@mui/material/Stack"; | |||
@@ -16,9 +16,9 @@ export const metadata: Metadata = { | |||
}; | |||
const Staff: React.FC = async () => { | |||
const { t } = await getServerI18n("projects"); | |||
preloadTeamLeads() | |||
preloadStaff() | |||
const { t } = await getServerI18n("staff"); | |||
preloadTeamLeads(); | |||
preloadStaff(); | |||
return ( | |||
<> | |||
<Stack | |||
@@ -39,9 +39,11 @@ const Staff: React.FC = async () => { | |||
{t("Create Staff")} | |||
</Button> | |||
</Stack> | |||
<Suspense fallback={<StaffSearch.Loading />}> | |||
<StaffSearch /> | |||
</Suspense> | |||
<I18nProvider namespaces={["staff", "common"]}> | |||
<Suspense fallback={<StaffSearch.Loading />}> | |||
<StaffSearch /> | |||
</Suspense> | |||
</I18nProvider> | |||
</> | |||
); | |||
}; | |||
@@ -25,12 +25,13 @@ export interface CreateStaffInputs { | |||
emergContactName: string; | |||
emergContactPhone: string; | |||
employType: string; | |||
departDate: string; | |||
departReason: string; | |||
remark: string; | |||
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), | |||
@@ -4,6 +4,7 @@ import { cache } from "react"; | |||
import "server-only"; | |||
export interface StaffResult { | |||
action: any; | |||
id: number; | |||
name: string; | |||
team: string; | |||
@@ -37,16 +37,10 @@ const CreateStaffForm: React.FC<formProps> = ({ | |||
Title, | |||
fieldLists | |||
}) => { | |||
// const [formData, setFormData] = useState<any>(null); | |||
const router = useRouter(); | |||
const { t } = useTranslation(); | |||
const [serverError, setServerError] = useState(""); | |||
// const handleSubmit = (data: any) => { | |||
// console.log(data); | |||
// // Handle the form submission logic here | |||
// // setFormData(data); | |||
// }; | |||
const handleCancel = () => { | |||
router.back(); | |||
}; | |||
@@ -54,8 +48,16 @@ const CreateStaffForm: React.FC<formProps> = ({ | |||
async (data) => { | |||
try { | |||
console.log(data); | |||
const postData = { | |||
...data, | |||
emergContactPhone: data.emergContactPhone.toString(), | |||
phone1: data.phone1.toString(), | |||
phone2: data.phone2.toString(), | |||
} | |||
console.log(postData); | |||
setServerError(""); | |||
await saveStaff(data); | |||
await saveStaff(postData); | |||
router.replace("/staff"); | |||
} catch (e) { | |||
setServerError(t("An error has occurred. Please try again later.")); | |||
} | |||
@@ -63,6 +65,13 @@ const CreateStaffForm: React.FC<formProps> = ({ | |||
[router, t] | |||
); | |||
const onSubmitError = useCallback<SubmitErrorHandler<CreateStaffInputs>>( | |||
(errors) => { | |||
console.log(errors) | |||
}, | |||
[], | |||
); | |||
return ( | |||
<> | |||
{serverError && ( | |||
@@ -75,6 +84,7 @@ const CreateStaffForm: React.FC<formProps> = ({ | |||
fieldLists={fieldLists} | |||
isActive={true} | |||
onSubmit={onSubmit} | |||
onSubmitError={onSubmitError} | |||
onCancel={handleCancel} | |||
/> | |||
</> | |||
@@ -27,7 +27,7 @@ import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||
import { DemoItem } from "@mui/x-date-pickers/internals/demo"; | |||
import { DatePicker } from "@mui/x-date-pickers/DatePicker"; | |||
import dayjs from "dayjs"; | |||
import { useEffect, useState } from "react"; | |||
import { useCallback, useEffect, useState } from "react"; | |||
import { Check, Close } from "@mui/icons-material"; | |||
// interface Option { | |||
@@ -45,6 +45,8 @@ interface Field { | |||
type: string; | |||
value?: any; | |||
required?: boolean; | |||
pattern?: string; | |||
message?: string; | |||
options?: any[]; | |||
readOnly?: boolean; | |||
size?: number; | |||
@@ -53,6 +55,7 @@ interface Field { | |||
interface CustomInputFormProps { | |||
onSubmit: (data: any) => void; | |||
onSubmitError?: (data: any) => void; | |||
onCancel: () => void; | |||
// resetForm: () => void; | |||
Title?: string[]; | |||
@@ -60,20 +63,24 @@ interface CustomInputFormProps { | |||
fieldLists: Field[][]; | |||
} | |||
// interface SubComponents { | |||
// Loading: typeof CustomerSearchLoading; | |||
// } | |||
const CustomInputForm: React.FC<CustomInputFormProps> = ({ | |||
Title, | |||
isActive, | |||
fieldLists, | |||
onSubmit, | |||
onSubmitError, | |||
onCancel, | |||
// resetForm, | |||
}) => { | |||
const { t } = useTranslation(); | |||
const { reset, register, handleSubmit, control } = useForm(); | |||
const { reset, register, handleSubmit, control, formState: { errors } } = useForm(); | |||
const [dateObj, setDateObj] = useState<any>(null); | |||
const [value, setValue] = useState<any>({}); | |||
const [checkboxValue, setCheckboxValue] = useState({}); | |||
// const [dateObj, setDateObj] = useState({}); | |||
interface DateObj { | |||
[key: string]: string; | |||
@@ -98,10 +105,7 @@ const CustomInputForm: React.FC<CustomInputFormProps> = ({ | |||
if (checkboxValue !== null) { | |||
data = { ...data, ...checkboxValue }; | |||
} | |||
// if (value !== null) { | |||
// data.dropdownCombo = value; | |||
// } | |||
const finalData = { | |||
...value, | |||
...data, | |||
@@ -172,14 +176,8 @@ const CustomInputForm: React.FC<CustomInputFormProps> = ({ | |||
}); | |||
}); | |||
// useEffect(() => { | |||
// if (dateObj) { | |||
// console.log(dateObj); | |||
// } | |||
// }, [dateObj]); | |||
return ( | |||
<form onSubmit={handleSubmit(handleFormSubmit)}> | |||
<form onSubmit={handleSubmit(handleFormSubmit, onSubmitError)}> | |||
<Card sx={{ display: isActive ? "block" : "none" }}> | |||
<CardContent component={Stack} spacing={4}> | |||
<> | |||
@@ -200,17 +198,35 @@ const CustomInputForm: React.FC<CustomInputFormProps> = ({ | |||
return ( | |||
<Grid item xs={field.size ?? 6} key={field.id}> | |||
<TextField | |||
label={t(`${field.label}`)} | |||
label={field.label} | |||
fullWidth | |||
{...register(field.id, { | |||
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} | |||
/> | |||
</Grid> | |||
); | |||
} else if (field.type === "email") { | |||
return ( | |||
<Grid item xs={field.size ?? 6} key={field.id}> | |||
<TextField | |||
label={field.label} | |||
fullWidth | |||
// {...register(`asdasd`, { | |||
{...register(`${field.id}`, { | |||
required: field.required ?? false, | |||
{...register(field.id, { | |||
pattern: /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/, | |||
})} | |||
// error={Boolean(errors.projectCode)} | |||
defaultValue={!field.value ? `${field.value}` : ""} | |||
required={field.required ?? false} | |||
error={Boolean(errors[field.id])} | |||
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}> | |||
@@ -408,4 +424,6 @@ const CustomInputForm: React.FC<CustomInputFormProps> = ({ | |||
); | |||
}; | |||
// CustomInputForm.Loading = CustomerSearchLoading; | |||
export default CustomInputForm; |
@@ -15,7 +15,7 @@ type SearchQuery = Partial<Omit<StaffResult, "id">>; | |||
type SearchParamNames = keyof SearchQuery; | |||
const StaffSearch: React.FC<Props> = ({ staff }) => { | |||
const { t } = useTranslation("staff"); | |||
const { t } = useTranslation(); | |||
// If claim searching is done on the server-side, then no need for this. | |||
const [filteredStaff, setFilteredStaff] = useState(staff); | |||
@@ -61,12 +61,12 @@ const StaffSearch: React.FC<Props> = ({ staff }) => { | |||
const columns = useMemo<Column<StaffResult>[]>( | |||
() => [ | |||
// { | |||
// name: "action", | |||
// label: t("Actions"), | |||
// onClick: onClaimClick, | |||
// buttonIcon: <EditNote />, | |||
// }, | |||
{ | |||
name: "action", | |||
label: t("Actions"), | |||
onClick: onStaffClick, | |||
buttonIcon: <EditNote />, | |||
}, | |||
{ name: "team", label: t("Team") }, | |||
{ name: "name", label: t("Staff Name") }, | |||
{ name: "staffId", label: t("Staff ID") }, | |||
@@ -0,0 +1,27 @@ | |||
{ | |||
"Staff": "員工", | |||
"Team": "隊伍", | |||
"Staff Name": "員工姓名", | |||
"Staff ID": "員工編號", | |||
"Grade": "級別", | |||
"Current Position": "現職", | |||
"Actions": "編輯", | |||
"Create Staff": "新增員工", | |||
"Company": "公司", | |||
"Department": "部門", | |||
"Skillset": "技能", | |||
"Salary Point": "薪金點", | |||
"Employ Type": "職位類別", | |||
"Hourly Rate": "時薪", | |||
"Email": "時薪", | |||
"Phone1": "聯絡電話", | |||
"Phone2": "次要聯絡電話", | |||
"Additional Info": "更多資料", | |||
"Emergency Contact Name": "緊急聯絡人", | |||
"Emergency Contact Phone": "緊急聯絡人電話", | |||
"Join Date": "入職日期", | |||
"Join Position": "入職職位", | |||
"Depart Date": "離職日期", | |||
"Depart Reason": "離職原因", | |||
"Remark": "備註" | |||
} |