Ver código fonte

update customer

tags/Baseline_30082024_FRONTEND_UAT
MSI\derek 1 ano atrás
pai
commit
b502357373
8 arquivos alterados com 150 adições e 76 exclusões
  1. +47
    -32
      src/app/(main)/staff/create/page.tsx
  2. +9
    -7
      src/app/(main)/staff/page.tsx
  3. +4
    -3
      src/app/api/staff/actions.ts
  4. +1
    -0
      src/app/api/staff/index.ts
  5. +17
    -7
      src/components/CreateStaff/CreateStaffForm.tsx
  6. +38
    -20
      src/components/CustomInputForm/CustomInputForm.tsx
  7. +7
    -7
      src/components/StaffSearch/StaffSearch.tsx
  8. +27
    -0
      src/i18n/zh/staff.json

+ 47
- 32
src/app/(main)/staff/create/page.tsx Ver arquivo

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


+ 9
- 7
src/app/(main)/staff/page.tsx Ver arquivo

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


+ 4
- 3
src/app/api/staff/actions.ts Ver arquivo

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


+ 1
- 0
src/app/api/staff/index.ts Ver arquivo

@@ -4,6 +4,7 @@ import { cache } from "react";
import "server-only";

export interface StaffResult {
action: any;
id: number;
name: string;
team: string;


+ 17
- 7
src/components/CreateStaff/CreateStaffForm.tsx Ver arquivo

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


+ 38
- 20
src/components/CustomInputForm/CustomInputForm.tsx Ver arquivo

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

+ 7
- 7
src/components/StaffSearch/StaffSearch.tsx Ver arquivo

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


+ 27
- 0
src/i18n/zh/staff.json Ver arquivo

@@ -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": "備註"
}

Carregando…
Cancelar
Salvar